SObjectizer  5.5
so-5.5.23 Enveloped Messages

A new feature was introduced in v.5.5.23: envelopes with messages inside. This feature allows to send a message or signal that enveloped in a special object called envelope. An envelope can carry some additional information and can perform some actions when message/signal is delivered to a receiver.

An Important Note

This new feature is a low-level feature that intended to be used by SObjectizer's developers and those developers who want to extend SObjectizer functionality. It seems that ordinary users will be protected from envelope-related details by some high-level code (something like a special tools from so_5_extra).

Because of that the sense of reading the following text exists only if you want to write your own type of an envelope.

What Is An Envelope

An envelope is a special case of a message. An envelope carries a message or signal (or another envelope) inside. An envelope is implemented as class derived from so_5::enveloped_msg::envelope_t.

Delivery Process For An Envelope

Because an envelope is a message the delivery of an envelope is almost the same. For example, dispatchers have no knowledge about envelopes at all – they see just instances of so_5::message_t type.

Key differences in delivery process can be found in two places:

Passing An Envelope To A Mbox

A new method has been added to so_5::abstract_message_box_t type: do_deliver_enveloped_msg. This method has the same semantic as do_deliver_message and do_deliver_service_request, but intended to be used with enveloped messages only.

If you want to send an envelope you have to pass your envelope to do_deliver_enveloped_msg method. For example:

class my_envelope final : public so_5::enveloped_msg::envelope_t {
const so_5::message_ref_t payload_;
public:
my_envelope(so_5::message_ref_t payload) : payload_{std::move(payload)} {...}
...
};
void make_and_send_envelope(
// The destination for an envelope.
const so_5::mbox_t & to,
// Type of message to be delivered inside an envelope.
const std::type_index & msg_type,
// Message to send inside an envelope.
{
to->do_deliver_enveloped_msg(
msg_type,
// Envelope instance.
std::make_unique<my_envelope>(std::move(what)),
1u);
}

msg_type For An Envelope

New method do_deliver_enveloped_msg requires msg_type argument. This is a type of envelope's payload, not a type of envelope itself. For example:

struct my_message final : public so_5::message_t {...};
class my_envelope final : public so_5::enveloped_msg::envelope_t {...};
// Create a payload of type my_message.
auto payload = std::make_unique<my_message>(...);
// Create an envelope with payload inside.
auto envelope = std::make_unique<my_envelope>(std::move(payload));
// Send envelope with my_message inside.
mbox->do_deliver_enveloped_msg(
std::move(envelope),
1u);

Extraction Of Envelope's Payload

There are several points in message delivery process where the payload should be extracted from an envelope:

To extract the payload the corresponding evelope's hook is called.

An envelope can decide should the payload be extracted or not. It is possible that an envelope won't provide access to the payload for some reason. In that case the payload will be ignored. It means that if an envelope is delivered to a receiver but don't provide access to the payload then an event handler for the receiver won't be called (as if message was lost). The same is for transformation case: if an envelope don't provide access to the payload for limit_then_transform then message won't be transformed and will be simple thrown out.

This is the main feature of an envelope: the possibility to "hide" the payload from a receiver (or transformation procedure).

Envelope's Hook

The access_hook() is called when SObjectizer need to access the payload of an enveloped message.

access_hook() can be called in different cases: when a message is delivered to a receiver, when overload reaction must be performed, when a delivery filter has to analyze the message and so on. The context of access_hook() call is identified by context argument of access_hook(). That argument can have the following values:

If the envelope decided that the access to the payload can be provided it should call invoke() method for invoker object (the invoker object is passed as argument to access_hook()):

class my_envelope final : public so_5::enveloped_msg::envelope_t {
...
public:
void access_hook(access_context_t context, handler_invoker_t & invoker) const noexcept {
if(access_can_be_granted(context))
invoker.invoke(payload_info_t{payload_});
}
...
};

Please note that access_hook() is marked as noexcept. It must not allow exceptions to leave the hook.

Multiple Invocations Of Hooks

If an enveloped is passed to MPMC-mbox then hook methods can be called more than once. It is because here can be several receivers of the message inside the envelope.

Envelope And Multithreading

Because envelopes can be sent to MPMC-mboxes there is a possibility that envelope's hooks will be called at the same time on different threads.

It means that envelope classes should be thread safe and should allow invocation of hooks on several threads simultaneously.

Envelope And Message Limits

Message limits know almost nothing about envelopes. Strictly speaking only limit_then_transform knows that envelopes exist and knows how to extract of envelope's payload from an envelope. But no more.

It means that behaviour of an envelope have no influence on message limits.

For example let's consider an envelope that allows to revoke a message. Something like:

// A special envelope with atomic revocation flag.
class revocable_envelope_t final : so_5::enveloped_msg::envelope_t {
friend class revocable_msg_id_t;
std::atomic_bool revoked_;
public:
...
void access_hook(access_context_t, handler_invoker_t & invoker) const noexcept {
if(!revoked_.load(std::memory_order_acquire))
invoker.invoke(payload_info_t{payload_});
}
...
};
// A special handler that allows to revoke envelope with a message.
class revocable_msg_id_t {
public:
... // Constructor/destructor and other stuff.
void revoke() {
envelope_->revoked_.store(true, std::memory_order_release);
}
};
// A special helper function to make and send a message inside revocable message.
template<typename Msg, typename... Args>
revocable_msg_id_t make_and_send(
const so_5::mbox_t & destination,
Args && ...args)
{
std::make_unique<revocable_envelope_t>(
std::make_unique<Msg>(std::forward<Args>(args)...)));
revocable_msg_id_t result{envelope};
destination->do_deliver_enveloped_msg(
envelope,
1u);
return result;
}
// Send a revocable message.
auto id = make_and_send<my_message>(to, ...);
...
// Revoke a message.
id.revoke();

If revoke() method is called when message is waiting in receiver's queue then the message won't be delivered to the receiver (because envelope won't provide access to the payload). But the calling to revoke() do not decrement the quantity of messages on type my_message in receiver's queue. Quantity of messages will be decremented only after extraction of an envelope from receiver's queue.

It means that revoke() have no influence on message limits.

Nested Envelopes

It is possible to create envelopes that holds another envelopes.

There is no need to know a type of the payload. For example this simple envelope can hold envelopes as well as messages or signals:

class demo_envelope final : public so_5::enveloped_msg::envelope_t {
public:
demo_envelope(so_5::message_ref_t payload) : payload_{std::move(payload)} {}
void access_hook(access_context_t, handler_invoker_t & invoker) const noexcept {
invoker.invoke(payload_info_t{payload_});
}
};

This is because the standard implementations of handler_invoker_t check the kind of payload. And if a payload is another envelope these implementation call appropriate hook for that envelope recursively.

An user should take care of payload kind only if he or she implements own handler_invoker_t class. In that case so_5::message_t::kind_t and so_5::message_kind() should be used.

Ready To Use Envelopes

SObjectizer v.5.5.23 doesn't provide ready to use envelopes. SObjectizer core only implements basic functionality for envelopes.

Ready to use envelopes can be found in so_5_extra library that is built on top of SObjectizer.