Learning about active handlers
/* * This is a user-defined message that can be sent to a node via VT's active * message interface. A message in VT must be derived from the type * vt::Message. The derived class will include the ``envelope'', which includes * the handler, and other information for processing the message */ // VT Base Message // \----------------/ // \ / struct MyMsg : ::vt::Message { // In general, a default constructor is required for the a message because it // may be reconstructed by VT MyMsg() = default; // A normal constructor MyMsg(int in_a, int in_b) : a_(in_a), b_(in_b) { } int getA() const { return a_; } int getB() const { return b_; } private: int a_ = 0, b_ = 0; }; /* * Following are two active message handler declarations. These are the * functions that can be remotely invoked by sending a message in VT. VT allows * for multiple styles for handlers. The first (msgHandlerA) is the C-style * active message handler. It is exactly equivalent (in terms of practical use * ramifications) to the functor style, which follows (MsgHandlerB). Either * style can be used to define a message handler. The only slight benefit the * functor has is uniqueness in type, when enables introspection of the message * type. This is realized in the sending side code: the function style requires * the message type before the value function template parameter. However, both * are *fully type checked* and will not allow the wrong message type to be * sent. */ // Forward declaration for the active message handler (function ptr style) static void msgHandlerA(MyMsg* msg); // Forward declaration for the active message handler (functor style) struct MsgHandlerB { void operator()(MyMsg* msg); }; static inline void activeMessageNode() { NodeType const this_node = ::vt::theContext()->getNode(); NodeType const num_nodes = ::vt::theContext()->getNumNodes(); (void)num_nodes; // don't warn about unused variable /* * A basic active message send essentially does an * ``MPI_Send(..,destination,...)'' to the destination node passed to * ::vt::theMsg()->sendMsg(destination). The handler, which is passed as the * second template argument, is the function that is triggered when the * message arrives on the destination node. * * The theMsg()->sendMsg(..) does not serialize the message sent to the * destination node. Even if the message has a serialize method (the above * example does not), it is sent as bytes. This is because sendMsg always just * sends the data directly that is passed to it. * * This example uses the function pointer style compared to the send inside of * msgHandlerA, which uses the functor style send. */ if (this_node == 0) { NodeType const to_node = 1; auto msg = ::vt::makeMessage<MyMsg>(29,32); ::vt::theMsg()->sendMsg<msgHandlerA>(to_node, msg); } } static void msgHandlerA(MyMsg* msg) { /* * VT provides vtAssert (and a half-dozen variants) that replace the simple * the assert call found in cassert. */ vtAssert(msg->getA() == 29, "Value a incorrect"); vtAssert(msg->getB() == 32, "Value b incorrect"); auto const cur_node = ::vt::theContext()->getNode(); ::fmt::print("msgHandlerA: triggered on node={}\n", cur_node); /* Node 0 sends MyMsg to node 1 in the above code so this should execute on * node 1 */ vtAssert(cur_node == 1, "This handler should execute on node 1"); /* * In response to this message, node 1 sends a message back to node 0. This * invocation uses the functor style send. * * -- Message Ownsership -- * When an API that 'sends a message' is called, ownership is relinquished. * * The loss of ownership is the same as using std::move and the MsgPtr object * supplied becomes invalid for later use. This use includes attempting * to send the same message twice. std::move can be used explicit if desired. * * A vtAssert will be raised if such an invalid use is detected. */ NodeType const to_node = 0; auto msg2 = ::vt::makeMessage<MyMsg>(10,20); ::vt::theMsg()->sendMsg<MsgHandlerB>(to_node, msg2); // Alternate/equivalent form with explicit (and optional) std::move: // // ::vt::theMsg()->sendMsg<MsgHandlerB, MyMsg>(to_node, std::move(msg2)); // // In both cases it is invalid to attempt to use msg2 here as the ownership // of the message has been relinquished/lost when making the call. } void MsgHandlerB::operator()(MyMsg* msg) { auto const cur_node = ::vt::theContext()->getNode(); vtAssert(msg->getA() == 10, "Value a incorrect"); vtAssert(msg->getB() == 20, "Value b incorrect"); vtAssert(cur_node == 0, "This handler should execute on node 0"); ::fmt::print("msgHandlerB: triggered on node={}\n", cur_node); }