Skip to content

Message Ownership

Paul N Stickney edited this page Feb 1, 2020 · 34 revisions

Message Lifetime

Messages in VT are allocated off-stack and have internally managed reference counts. As such, they must be handled correctly to avoid memory leaks. The MsgPtr (in 1.0 this is named MsgSharedPtr) type acts a a shared-ref wrapper to ensure proper lifetime management.

Note: The method of message creation is currently under some updates. The original method uses makeSharedMessage which returns a MsgT*. VT takes ownership of the messages these raw pointers represent in send and broadcast calls. A message is created with makeSharedMessage MUST either be guaranteed to be passed to VT for transmission or wrapped inside a MsgPtr (via promoteMsg in 1.0).

Good (1.0):

M* msg = makeSharedMessage<M>(..);
theMsg->sendMsg(dest, msg);

theMsg->sendMsg(dest, makeSharedMessage<M>(..));

MsgPtr<M> msg = makeMessage<M>(..);
theMsg->sendMsg(dest, msg.get()) // MsgPtr<M>.get() -> M*

theMsg->sendMsg(dest, makeMessage<M>(..).get())

Bad (1.0):

// Messages must be created via appropriate methods.
M* msg = new M{..};

// Message is leaked when it is not supplied to sendMsg.
// Only create messages as they are immediately going to be used.
M* msg = makeSharedMessage<M>(..);
if (maybe_send) {
    theMsg->sendMsg(dest, msg);
}

1.1 Proposal A: Allow direct new calls, with the same caveats as makeSharedMessage and discourage use of makeSharedMessage.

// Inline usages of message creation is encouraged for send/broadcast calls.
theMsg()->sendMsg(dest, new M{..});

// Still prone to memory leaks if lifetime ownership is not established.
M* msg = new M{..};
if (maybe_send) {
    theMsg()->sendMsg(dest, msg);
}

1.1 Proposal B: Accept MsgPtr&/MsgPtr&& in send/broadcast and discourage the use of makeSharedMessage. Maintain that new M{..} should be avoided. This requires the use of temporaries or std::move.

// Temporary for MsgPtr&
MsgPtr<M> msg = makeMessage<M>{..};
theMsg()->sendMsg(dest, /* MsgPtr<M>& */ msg);

// std::move for lvalue-to-MsgPtr&&
theMsg()->sendMsg(dest, std::move(makeMessage<M>{..}));

Send-once

Messages in VT can be sent at most once. It is an error if the same message is sent multiple times.

Messages received from send/broadcast callbacks are considered sent and cannot be directly sent again. A new message must be constructed.

Invalid code:

T* msg = makeSharedMessage<T>(..);
theMsg()->sendMsg(dest, msg);
// Invalid: message has already been sent
theMsg()->sendMsg(dest, msg);

T* msg = makeSharedMessage<T>(..);
theMsg()->sendMsg(dest, msg, [](T* cb_message) {
  // Invalid: messages from callbacks cannot be re-sent.
  theMsg()->sendMsg(dest, cb_message);
});

1.1 Proposal: Active message supports a Copy Constructor that resets the transmission state of the new message such that the following is valid:

T* msg = makeSharedMessage<T>(..);
theMsg()->sendMsg(dest, msg, [](T* cb_message) {
  // Valid: a copy of the message without transmission state is created and sent.
  theMsg()->sendMsg(dest, makeSharedMessage(*cb_message));
});

The new message has a new lifetime as well, subject to all other lifetime rules. In degenerate cases this might result in excessive data copying.

Messages in Callbacks

Callbacks are given a M* value once a message is received. The lifetime of message is only for the duration of the callback. If the message needs to extend beyond the callback lifetime, wrap it inside a MsgPtr. For 1.0 this is done using promoteMsg<M>(rawMsgPtr) which returns a MsgPtr<M>.

1.1 Proposal: Create a MsgPtr with MsgPtr<M>{rawMsgPtr}.