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. Usages of new M{..} are considered coding errors. This requires the use of temporaries or std::move.

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

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

1.1 Proposal B-Extended: Messages should be created with MsgPtr<T>{..}, which has a templated perfect-forwarding overloaded. This allows usage as an xref directly. Usages of makeSharedMessage and makeMessage are discouraged. Usages of new M{..} are considered coding errors. Usages of a temporary (and MsgPtr& parameters) are not affected.

theMsg()->sendMsg(dest, MsgPtr<M>{..});    // MsgPtr&& param (xref)

auto msg = MsgPtr<M>{..};                  // uniform message creation
theMsg()->sendMsg(dest, msg);              // MsgPtr& param

This has a negative effect of possible confusing error messages. It has a positive side-effect of being incompatible with lambda-copy captures, which may be suspect (a separate internal type can be exposed for this case).

1.1 Proposal B-Extended: MsgSharedPtr is renamed to Msg (instead of just MsgPtr).

This is then the fundamental method of user code to supply messages into VT. From an end-user view there is no reason to consider it is a 'pointer', or has any relation to a unique/shared ptr, as it is really only the lifetime guarantee that is of relevance.

Callbacks are still M*, although such may be covered by a future proposal as such might be warranted for new API endpoints.

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}.