[ask-for-comments]Receiving messages by its base class (draft in so5extra's dev branch) #85
eao197
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
In this post I want to briefly describe a long-awaiting feature that has been implemented in a development branch of so5extra. The main goal is to find any fatal defects in the proposed solution before the release. If there won't be too many drawbacks found then the current implementation will be released "as is". But if readers point me at some significant blunders then I'll try to make another iteration to fix it.
The problem
The problem was described several years ago in the issues #24 and #25: SObjectizer lacks a possibility to receive a message by its base class.
For example, let's suppose we have something like:
And we want to write an agent that handles messages of type
camera_vendor_1_image
. It means that such an agent can receive messages of typeimage_type_one
andimage_type_two
.Or an agent that handles messages of type
basic_image
-- and it means that such an agent can handle any descendant ofbasic_image
.I tried to find a solution in the past but didn't succeed 😒
Not so long ago I got an idea that seemed promising and tried to make it work. Now it's implemented in the development branch and I can say some words of how this problem can be addressed now.
The solution
Disclaimers
First of all, the described solution is now a part of a companion project so5extra. It may not be convenient for someone, but I decided to put it into so5extra first. If there will be positive experience with such a feature then the implementation will be transferred to the SObjectizer's core (like it happened with unique_subscriber mbox).
The proposed solution requires inheritance from a couple of special classes. It's because C++ doesn't have compile- and run-time reflections and I haven't found another way to determine the relationship between classes. I'm not happy with the direction I'm going, so if someone knows a better solution please let me know (but see a note at the end of the post).
so_5::extra::msg_hierarchy
All the functionality described below is in the
so_5::extra::msg_hierarchy
namespace.It's required to include
pub.hpp
header file fromso_5_extra/msg_hierarchy
:Describe a hierarchy the special way
The first thing a user has to do is to describe message hierarchy by using two special template classes:
The most annoying thing is the necessity of calling the constructor of
node_t
explicitly:Fortunately the compiler complies when the constructor of
node_t
isn't called so it's impossible to forget.The demuxer and sending_mbox
The next step is the creation of a special machinery that will demultiplex messages between subscribers. The proxy to this machinery is a
demuxer_t<Root_Msg>
template class:so_5::extra::msg_hierarchy::demuxer_t<basic_image> demuxer{ ... }; ...
An instance of
demuxer_t
can be seen as a proxy object that hides a special demuxing-controller and a bunch of mboxes. Only mboxes created by thisdemuxer_t
instance can be used for delivery of messages for a hierarchy started fromRoot_Msg
type.To send a message a special sending_mbox has to be obtained from the demuxer via
sending_mbox
method:Multi- or Single-consumer demuxer
The sending_mbox created by a demuxer can be multi-producer/multi-consumer or multi-producer/single-consumer. A multi-consumer mbox prohibits delivery of mutable messages.
Type of sending_mbox is specified in the constructor of a demuxer and can't be changed later:
Receive incoming messages
To receive messages from a hierarchy it's required to do:
consumer_t<Root_Msg>
type;Usually it looks like that:
The receiving_mboxes
The main trick is to use a separate receiving_mbox for every type of message a user wants to receive.
This approach contradicts with the usual way of receiving messages from mbox in SObjectizer: when we work with "normal" messages we can subscribe to messages of different types from a single mbox:
Unfortunately, I didn't find a way to add information about message hierarchy to ordinary subscriptions. So my idea is to use a separate mbox for every message type in the hierarchy:
When a user asks for a separate mbox for a message of type A the demuxing-controller (hidden behind demuxer and consumer objects) checks the type A and creates special relations between sending_mbox and a particular receiver. Because of such relation the demuxer-controller knows destinations for message delivery when a message of type A (or any other type derived from A) is being sent to the sending_mbox.
I can say that the idea of separate mboxes for every type of message is a cornerstone in the implementation of so5extra's msg_hierarchy. This idea allows me to implement the required functionality.
I think that if C++ had run-time reflection like Java or C#, then message delivery by using base classes may be implemented without a need of a separate receiving mboxes. But C++ lacks the run-time reflection for now and I'm in doubt C++ will have it in an observable future.
Consumer
A consumer object plays a very important role and solves two tasks.
The first task is avoiding delivering the same message instance to one subscriber several times.
All receiving_mboxes are bound to corresponding consumer objects and this protects from repeated delivery of a message to a subscriber. Let's suppose that one agent has subscriptions to message types
image_type_one
,camera_vendor_1_image
andbasic_image
. When an instance ofimage_type_one
is sent it can be delivered to subscribers of message typeimage_type_one
, and also for subscribers of message typecamera_vendor_1_image
, and also for subscribers of message typebasic_image
. If one agent has subscriptions for all of those types then it may receive the message three times. But it is obviously not what we want.When receiving_mboxes are bound to a consumer object then the demuxing-controller understands that there is just one subscriber behind those mboxes. It allows the demuxing-controller to stop delivering a message to this subscriber just after the first receiving_mbox is found.
The second task is the deactivation of receiving mboxes when the consumer object is destroyed.
When a receiving_mbox is created for a consumer and subscription is made then there are two owners of this receiving_mbox: one is the demuxer-controller object that performs message delivery, and another is the agent that created the subscription.
Unfortunately, it's hard to detect a moment when the agent drops its subscriptions and doesn't need receiving_mbox anymore. It's possible, but requires cyclic references between entities and this provokes memory leaks. So I decided to go the simpler way: the destructor of the consumer object informs the demuxing-controller that consumers no longer exist. The demuxing-controller drops all references to receiving_mboxes created by such a consumer. It may look like "deactivation" of these receiving_mboxes: there could be references to them, but no new messages will be delivered to such deactivated mboxes.
Because of that it's important to synchronize the lifetime of a consumer object with the lifetime of an agent that uses this consumer object. And the simplest way to make such synchronization is to hold a consumer object as a field of an agent:
When such an agent is being deregistered all its subscription will be dropped. It means that the only owner of receiving_mboxes will be the demuxing-controller. But when the agent is physically destroyed then the corresponding consumer object is destroyed too, the destructor of the consumer object tells the demuxing-controller that receiving_mboxes are no longer needed and the demuxing-controller deactivates them. This way we avoid a memory leak.
Delivering of mutable messages
The delivery of mutable messages is supported. It requires creation of a demuxer with
single_consumer
type:so_5::extra::msg_hierarchy::demuxer_t<basic_image> mpsc_demuxer{ env, so_5::extra::msg_hierarchy::single_consumer};
Then a receiving_mbox for a mutable message has to be requested:
Then an instance of mutable message can be sent via the sending_mbox:
However, there is one important nuance related to mutable messages: it's possible to make subscriptions that allow receiving a mutable message by several subscribers. For example:
At the subscription time it's unknown whether such subscriptions lead to problems or not. If there won't be messages of type
image_type_one
(or any derived type), then there is no problem.But if a message of type
image_type_one
is sent then we have two subscribers for a single instance of a mutable message. This is prohibited.Because of that the corresponding demuxing-controller checks the number of actual subscribers for a message at the send time. If there are more than one subscriber then the send will fail with an exception. Such an exception can be a big problem, for example, if a delayed message is being sent. So additional care has to be taken when working with mutable messages.
What can't be done with so5extra's msg_hierarchy?
Signals are not supported
All types in a message hierarchy have to be ordinary messages. Signals are not supported at all. This is a principal moment and it can't be changed, because I need a message instance to call some methods from it to make message upcasting during delivery.
Only messages from a hierarchy can be delivered
If we describe a message hierarchy like shown above and then create a demuxer object then only messages from this hierarchy can be delivered via the sending_mbox. For example:
Currently I'm in doubt if this is the right approach. Maybe it is too restrictive. And maybe I have to allow something like that:
What stops me is the cases like that:
Generally speaking,
any_unspecified_message
maps tocamera_vendor_2_image
orimage_base
too.A note about alternatives for node_t
An obvious way to avoid mixing
node_t
into the hierarchy is to use a macro:But I don't like this approach by two reasons:
Beta Was this translation helpful? Give feedback.
All reactions