2. This is the next part of the series of presentations with deep
introduction into features of SObjectizer-5.5.
This part is dedicated to message chains feature.
The feature is relatively new.
It was introduced in v.5.5.13.
SObjectizer Team, May 2017
4. The main purpose of message chains is to simplify
interaction between SObjectizer- and non-SObjectizer parts
of application.
SObjectizer Team, May 2017
5. It is pretty simple to send a message from a
non-SObjectizer-part to a SObjectizer-part of an application:
a message or signal can be sent to some mbox in a usual
way.
However, there was no a ready to use way to receive a reply
message or signal back. Because in non-SObjectizer-part
there couldn't be an entity behind mbox until v.5.5.13.
Then message chains (or mchains) were added...
SObjectizer Team, May 2017
6. Currently, it is possible to send a message or a signal to a
mchain from SObjectizer-part and receive and handle it in
non-SObjectizer-part via receive function.
Let's see how it looks like...
SObjectizer Team, May 2017
7. The agent which receives requests and sends replies:
class request_processor final : public so_5::agent_t
{
const so_5::mchain_t m_reply_to;
public :
request_processor( context_t ctx, so_5::mchain_t reply_to )
: so_5::agent_t{ ctx }, m_reply_to{ std::move(reply_to) }
{
// Requests will be sent from "handler" mbox.
so_subscribe( so_environment().create_mbox( "handler" ) )
.event( [&]( const request & msg ) {
... // Some request handling
// Sending a reply back.
so_5::send< reply >( m_reply_to, ... );
} );
...
}
...
};
SObjectizer Team, May 2017
8. The non-SObjectizer-part which sends requests and
receives responses:
so_5::wrapped_env_t sobj;
// mchain for replies.
// Simplest mchain without any limitations.
auto reply_ch = create_mchain( sobj );
// Launch request_processor and pass reply chain to it.
make_and_launch_request_processor( sobj, reply_ch );
// Sending requests and handling replies.
while( has_something_to_do() ) {
// Send new requests ordinary way.
so_5::send< request >( sobj.environment().create_mbox( "handler" ), ... );
// Receive and handle response.
receive( reply_ch, so_5::infinite_wait, []( const reply & msg ) { ... } );
}
SObjectizer Team, May 2017
10. The mchain_t type is similar to mbox_t.
It is an alias for a smart intrusive pointer to
abstract_message_chain_t.
It means that mchain created by create_mchain will be
destroyed automatically right after destruction of the last
mchain_t object pointing to it.
SObjectizer Team, May 2017
12. There are two types of mchains:
1. Unlimited mchains. The mchain has no limitation for the
number of messages to be stored inside mchain. This
number is limited only by the amount of available
memory and by common sense of developer.
2. Limited mchains. The mchain has a strict limit for the
number of messages. It is just impossible to push yet
another message into full size-limited mchain.
SObjectizer Team, May 2017
13. The type of mchain is specified at creation of mchain
instance. Once created, the type of mchain cannot be
changed.
The type of mchain is specified by the content of
mchain_params_t instance passed to
environment_t::create_mchain method.
There are several helper functions which simplify creation of
mchains of various types...
SObjectizer Team, May 2017
14. Creation of an unlimited mchain:
so_5::mchain_t m1 = env.create_mchain(so_5::make_unlimited_mchain_params());
// Or via free helper function.
so_5::environment_t & env = ...;
so_5::mchain_t m1 = create_mchain(env);
// Or via another helper function.
so_5::wrapped_env_t sobj;
so_5::mchain_t m1 = create_mchain(sobj);
SObjectizer Team, May 2017
15. Creation of a size-limited mchain without waiting for attempt
of pushing new message to the full mchain:
so_5::mchain_t m2 = env.create_mchain(
so_5::make_limited_without_waiting_mchain_params(
// Capacity of mchain's message queue.
200,
// mchain's message queue will grow and shrink dynamically.
so_5::mchain_props::memory_usage_t::dynamic,
// Drop new message if mchain is full.
so_5::mchain_props::overflow_reaction_t::drop_newest));
// Or via free helper function.
so_5::mchain_t m2 = create_mchain(env, 200,
so_5::mchain_props::memory_usage_t::dynamic,
so_5::mchain_props::overflow_reaction_t::drop_newest);
SObjectizer Team, May 2017
16. Creation of a size-limited mchain with waiting for attempt of
pushing new message to full mchain:
so_5::mchain_t m3 = env.create_mchain(
so_5::make_limited_with_waiting_mchain_params(
// Capacity of mchain's message queue.
200,
// mchain's message queue will use preallocated fixed size storage
so_5::mchain_props::memory_usage_t::preallocated,
// New message will be dropped if mchain is full after a timeout.
so_5::mchain_props::overflow_reaction_t::drop_newest,
// A time for waiting for a free room in mchain if the mchain is full.
std::chrono::milliseconds(500)));
// Or via free helper function.
so_5::mchain_t m3 = create_mchain(env,
std::chrono::milliseconds(500),
200,
so_5::mchain_props::memory_usage_t::preallocated,
so_5::mchain_props::overflow_reaction_t::drop_newest);
SObjectizer Team, May 2017
18. Size-limited mchains are quite different from size-unlimited
mchains:
a size-limited mchain can't contain more messages than
the max capacity of the mchain.
So, there should be some reaction for attempt to add
another message to full mchain.
SObjectizer Team, May 2017
19. There are size-limited mchains which will wait for some time
for attempt of pushing new message to the full mchain.
If there is a free space in the mchain after that waiting then
the new message will be stored in the mchain.
Otherwise, the appropriate overload reaction will be
performed.
SObjectizer Team, May 2017
20. Also, there are size-limited mchains without any waiting time
for the full mchain.
If there is no free space in the mchain then the appropriate
overload reaction will be performed immediately.
SObjectizer Team, May 2017
21. There are four overload reactions which can be selected for
the mchain at the moment of its creation:
1. Dropping new message. It means that the new message will
be simply ignored.
2. Removing the oldest message from the mchain. It means that
the oldest message in the mchain will be removed and it will
not be processed.
3. Throwing an exception. The exception with error code
rc_msg_chain_overflow will be raised as a result of attempt of
pushing a new message to the full mchain.
4. Aborting the whole application by calling std::abort().
SObjectizer Team, May 2017
22. Another important property which should be specified for
size-limited mchain at the creation time is the type of
memory usage.
The memory for storing messages inside mchain can be
used dynamically: it will be allocated when mchain grows
and deallocated when mchain shrinks.
Likewise, the memory for mchain can be preallocated and
there will be a fixed-size buffer for messages. The buffer size
will not change during growth and shrinking of the mchain.
SObjectizer Team, May 2017
23. All these properties of size-limited mchains are controlled by
contents of the corresponding mchain_params_t:
so_5::mchain_t chain = env.create_mchain(
so_5::make_limited_with_waiting_mchain_params(
// No more than 200 messages in the mchain.
200,
// Memory will be preallocated.
so_5::mchain_props::memory_usage_t::preallocated,
// An exception will be thrown if there is no free room in the mchain.
so_5::mchain_props::overflow_reaction_t::throw_exception,
// But before throwing an exception there will be 500ms timeout
std::chrono::milliseconds(500)));
SObjectizer Team, May 2017
25. so_5::receive function which allows us to receive and handle
messages from mchain has two variants.
SObjectizer Team, May 2017
26. The first variant of receive takes mchain_t, timeout and the
list of message handlers:
so_5::mchain_t ch = env.create_mchain(...);
...
receive(ch, std::chrono::milliseconds(500),
[](const reply1 & r) {...},
[](const reply2 & r) {...},
...
[](const replyN & r) {...});
Note: because of ADL there is no need to write a call to receive as
so_5::receive. Appropriate receive version will be found in so_5
namespace automatically.
SObjectizer Team, May 2017
27. What does this call do?
receive(ch, std::chrono::milliseconds(500),
[](const reply1 & r) {...},
...
[](const replyN & r) {...});
It checks the mchain and waits for no more than 500ms if the
mchain is empty.
If the mchain is not empty it extracts just one message from the
mchain and tries to find an appropriate handler for it. If the handler
is found it is called. If the handler is not found then the extracted
message will be thrown out without any processing.
SObjectizer Team, May 2017
28. If the mchain is empty even after the specified timeout then
receive does nothing.
There are two special values which can be used as timeout:
● value so_5::no_wait specifies zero timeout. It means that
receive will not wait if the mchain is empty;
● value so_5::infinite_wait specifies endless waiting. The
return from receive will be on arrival of any message. Or
if the mchain is closed explicitly.
SObjectizer Team, May 2017
29. The second variant of receive is much more complicated:
receive(
// Extract no more than 1000 messages from queue.
// Take no more than 2s of total extraction and processing time.
from(chain).extract_n( 1000 ).total_time( seconds(2) ),
[](const reply1 & msg) {...},
[](const reply2 & msg) {...},
...
[](const reply3 & msg) {...} );
SObjectizer Team, May 2017
30. The advanced version of receive allows us to specify a
bunch of exit-conditions. For example:
● handle no more than N messages;
● extract no more than N messages (number of extracted
messages can be larger than the number of handled
messages);
● max waiting time in case of an empty mchain;
● max processing time for the whole receive;
● some external condition fulfilled.
SObjectizer Team, May 2017
31. Both receive versions return an object of
mchain_receive_result_t type.
Methods of that object allow us to get the number of extracted and
handled messages, and also the status of receive operation:
// Sending requests and handling replies.
while( has_something_to_do() ) {
// Send new requests ordinary way.
so_5::send< request >( sobj.environment().create_mbox( "handler" ), ... );
// Receive and handle response.
auto r = receive( from(reply_ch).empty_timeout( std::chrono::seconds(1) ),
[]( const reply & msg ) { ... } );
if( !r.handled() )
... // No reply arrived within 1s.
}
SObjectizer Team, May 2017
32. The receive functions family allows to receive and handle
messages from just one mchain.
But since v.5.5.16 there is select functions family which
allows to receive and handle messages from several
mchains...
SObjectizer Team, May 2017
33. The simplest variant of select is like the simplest variant of
receive: it extracts just one message.
so_5::mchain_t ch1 = env.create_mchain(...);
so_5::mchain_t ch2 = env.create_mchain(...);
so_5::mchain_t ch3 = env.create_mchain(...);
...
so_5::select(std::chrono::milliseconds(500),
case_(ch1, [](const reply1 & r) {...}),
case_(ch2, [](const reply2 & r) {...}),
case_(ch2, [](const reply3 & r) {...}));
The return from select will be on extraction of one message from
any of (ch1, ch2, ch3) mchains or if all mchains are empty within
500ms.
SObjectizer Team, May 2017
34. Also, there is more advanced version of select which can
receive and handle more than one message from mchains.
It receives a so_5::mchain_select_params_t objects with list
of conditions and returns control if any of those conditions
becomes true...
SObjectizer Team, May 2017
35. For example:
// Receive and handle 3 messages.
// It could be 3 messages from ch1. Or 2 messages from ch1 and 1 message
// from ch2. Or 1 message from ch1 and 2 messages from ch2. And so on...
// If there are no 3 messages in mchains the select will wait infinitely. A return from select
// will be after handling of 3 messages or if all mchains are closed explicitly.
select(from_all().handle_n(3),
case_( ch1, handler1_1, handler1_2, ...),
case_( ch2, handler2_1, handler2_2, ...));
// Receive and handle 3 messages.
// If there are no 3 messages in chains the select will wait no more than 200ms.
// A return from select will be after handling of 3 messages or if all mchains are closed
// explicitly, or if there are no messages for more than 200ms.
select(from_all().handle_n(3).empty_timeout(milliseconds(200)),
case_( ch1, handler1_1, handler1_2, ...),
case_( ch2, handler2_1, handler2_2, ...));
SObjectizer Team, May 2017
36. The advanced version of select allows to specify a bunch of
exit-conditions. For example:
● handle no more than N messages;
● extract no more than N messages (number of extracted
messages can be larger than the number of handled
messages);
● max waiting time in case of empty mchains;
● max processing time for the whole select;
● some external condition fulfilled.
SObjectizer Team, May 2017
37. The advanced version of select is very similar to advanced
version of receive. In fact almost all exit conditions for
advanced select are just twins for exit conditions of
advanced receive.
But select has yet another exit condition: select finishes its
work if all mchains become closed.
SObjectizer Team, May 2017
38. Both select versions return an object of
mchain_receive_result_t type.
Methods of that object allow us to get the number of extracted and
handled messages, and also the status of receive operation.
If no one message has been extracted because all mchains
become closed then select returns
extraction_status_t::chain_closed value as status of select
operation.
SObjectizer Team, May 2017
40. Since v.5.5.16 mchains are implemented as Multi-Producer
and Multi-Consumer queues.
It means that it is possible to use the same mchain in parallel
calls to several receive and select on different threads.
This feature can be used for implementation of very simple
load-balancing scheme...
SObjectizer Team, May 2017
41. An example of a very simple load-balancing scheme:
void worker_thread(so_5::mchain_t ch) {
// Handle all messages until mchain will be closed.
receive(from(ch), handler1, handler2, handler3, ...);
}
...
// Message chain for passing messages to worker threads.
auto ch = create_mchain(env);
// Worker threads.
thread worker1{worker_thread, ch};
thread worker2{worker_thread, ch};
thread worker3{worker_thread, ch};
auto thr_joiner = auto_join(worker1, worker2, worker3);
// Send messages to workers.
while(has_some_work()) {
so_5::send< some_message >(ch, ...);
...
}
// Close chain and finish worker threads.
close_retain_content(ch);
SObjectizer Team, May 2017
43. Message handlers which are passed to receive and select
functions family must be lambda-functions or functional
objects with the following format:
return_type handler( const message_type& );
return_type handler( message_type );
return_type handler( const so_5::mhood_t<message_type>& );
return_type handler( so_5::mhood_t<message_type> );
return_type handler( so_5::mhood_t<so_5::mutable_msg<message_type>> );
return_type handler( so_5::mutable_mhood_t<message_type> );
SObjectizer Team, May 2017
44. Some examples of handlers prototypes:
struct classical_message : public so_5::message_t { ... };
struct user_message { ... };
...
receive(chain, so_5::infinite_wait,
[](const classical_message & m) {...},
[](const user_message & m) {...},
[](const int & m) {...},
[](long m) {...});
SObjectizer Team, May 2017
45. The handler for signal of type signal_type can be created via
using so_5::handler<signal_type>() function:
struct stopped : public so_5::signal_t {};
struct waiting : public so_5::signal_t {};
...
receive(chain, so_5::infinite_wait,
[](const classical_message & m) {...},
[](const user_message & m) {...},
so_5::handler<stopped>( []{...} ),
so_5::handler<waiting>( []{...} ));
SObjectizer Team, May 2017
46. If so_5::mhood_t<T> message wrapper is used the type T
can be the type of message or signal:
struct check_status : public so_5::signal_t {};
struct reconfig { std::string config_file; };
...
receive(chain, so_5::infinite_wait,
[](const mhood_t<reconfig> & msg) {...},
[](mhood_t<check_status>) {...},
... );
SObjectizer Team, May 2017
47. The most important thing about message/signal handlers for
receive and select/case_ functions:
All message handlers must handle different message
types. It is an error if some handlers are defined for the
same message type.
SObjectizer Team, May 2017
49. All traditional send-functions like send, send_delayed and
send_periodic work with mchains in the same way as they
work with mboxes.
SObjectizer Team, May 2017
50. It allows us to write the code in a traditional way:
// Sending a message.
so_5::send<my_message>(ch, ... /* some args for my_message's constructor */);
// Sending a delayed message.
so_5::send_delayed<my_message>(ch, std::chrono::milliseconds(200),
... /* some args for my_message's constructor */);
// Sending a periodic message.
auto timer_id = so_5::send_periodic<my_message>(ch,
std::chrono::milliseconds(200),
std::chrono::milliseconds(250),
... /* some args for my_message's constructor */);
SObjectizer Team, May 2017
51. The functions for performing service request, request_value
and request_future, work with mchains too.
It means that agent can make a service request to
non-SObjectizer part of an application and receive the result
of that request as usual.
SObjectizer Team, May 2017
52. Service request initiated by an agent:
// Somewhere in SObjectizer part of an application...
class worker : public so_5::agent_t {
const so_5::mchain_t m_request_ch;
...
void some_event_handler() {
auto f = so_5::request_future<response, request>(m_request_ch,
... /* some args for request's constructor */);
...
handle_response(f.get());
}
};
...
// Somewhere in non-SObjectizer part of an application...
const so_5::mchain_t request_ch = ...;
receive( from(request_ch).handle_n(1000),
[](const request & req) -> response {...}, ... );
SObjectizer Team, May 2017
54. There is a method abstract_message_chain_t::as_mbox
which can represent mchain as almost ordinary mbox.
This method returns mbox_t and the mbox can be used for
sending messages and performing service request to the
mchain.
SObjectizer Team, May 2017
55. Method as_mbox can be useful if there is a necessity to hide
the fact of mchain existence.
For example, an agent inside SObjectizer’s part of an
application can receive mbox and think that there is another
agent on the opposite side...
SObjectizer Team, May 2017
56. mchain as mbox in SObjectizer-part of an application:
class request_processor final : public so_5::agent_t
{
const so_5::mbox_t m_reply_to;
public :
request_processor( context_t ctx, so_5::mbox_t reply_to )
: so_5::agent_t{ ctx }, m_reply_to{ std::move(reply_to) }
{
// Requests will be sent from "handler" mbox.
so_subscribe( so_environment().create_mbox( "handler" ) )
.event( [&]( const request & msg ) {
... // Some request handling
// Sending a reply back.
so_5::send< reply >( m_reply_to, ... );
} );
...
}
...
};
SObjectizer Team, May 2017
57. mchain as mbox in non-SObjectizer-part of an application:
so_5::wrapped_env_t sobj;
// mchain for replies.
// Simplest mchain without any limitations.
auto reply_ch = create_mchain( sobj );
// Launch request_processor and pass reply mchain to it.
// mchain will be represented as mbox.
make_and_launch_request_processor( sobj, reply_ch->as_mbox() );
// Sending requests and handling replies.
while( has_something_to_do() ) {
// Send new requests in an ordinary way.
so_5::send< request >( sobj.environment().create_mbox( "handler" ), ... );
// Receive and handle response.
receive( reply_ch, so_5::infinite_wait, []( const reply & msg ) { ... } );
}
SObjectizer Team, May 2017
58. The only difference between ordinary mbox created by
environment_t::create_mbox and mbox created by as_mbox
is that the second doesn’t allow to create subscriptions on it.
It means that agent request_processor from the example
above can't subscribe its event handlers to messages from
m_reply_to.
SObjectizer Team, May 2017
59. Usage Of mchains For Programming In CSP Style
SObjectizer Team, May 2017
60. mchains can be used for development of multithreaded
applications based on SObjectizer even without agents.
Threads can interact with each other by means of mchains in
CSP* style.
One thread can send messages to the mchain via ordinary
send function. Another thread can receive and handle them
via receive function.
SObjectizer Team, May 2017* https://en.wikipedia.org/wiki/Communicating_sequential_processes
61. Let's see how concurrent HelloWorld example looks like in
Go language...
...and in C++ with SObjectizer's mchains.
SObjectizer Team, May 2017
62. SObjectizer Team, May 2017
Go version (code before main):
package main
import "fmt"
C++ version (code before main):
#include <iostream>
#include <thread>
#include <so_5/all.hpp>
using namespace std;
using namespace std::literals;
using namespace so_5;
template< typename T >
void operator<<( mchain_t & to, T && o ) {
send< T >( to, forward<T>(o) );
}
template< typename T >
void operator>>( mchain_t & from, T & o ) {
receive(from, infinite_wait, [&o]( const T & msg ) { o = msg;
});
}
Please note that SObjectizer does not provide shorthands like Go's <- operator and
that’s why we need to define similar operators << and >> by hand.
63. SObjectizer Team, May 2017
Go version (function main):
func main() {
sayHello := make(chan string)
sayWorld := make(chan string)
quitter := make(chan string)
go func() {
for i := 0; i < 1000; i++ {
fmt.Print("Hello ")
sayWorld <- "Do it"
<-sayHello
}
sayWorld <- "Quit"
}()
go func() {
for {
var reply string
reply = <-sayWorld
if (reply == "Quit") {
break
}
fmt.Print("World!n")
sayHello <- "Do it"
}
quitter <- "Done"
}()
<-quitter
}
C++ version (function main):
int main() {
wrapped_env_t sobj;
auto say_hello = create_mchain( sobj );
auto say_world = create_mchain( sobj );
thread hello{ [&] {
string reply;
for( int i = 0; i != 1000; ++i ) {
cout << "Hello ";
say_world << "Do it"s;
say_hello >> reply;
}
say_world << "Quit"s;
} };
thread world{ [&] {
for(;;) {
string reply;
say_world >> reply;
if( "Quit" == reply )
break;
cout << "World" << endl;
say_hello << "Do it"s;
}
} };
auto_join(hello, world);
}
65. mchains are relatively new addition to SObjectizer. They add
an easy way of communication between SObjectizer- and
non-SObjectizer-parts of an applications.
Likewise, mchains provide another way of solving
producer-consumer problem. For example, size-limited
mchains can be used for implementation of new
mechanisms of overload control.
It is worth noting that they allow us to write multithreaded
applications in CSP style without using agents.
SObjectizer Team, May 2017
66. Thus, mchains can lead to new ways of writing
SObjectizer-based programs.
Most likely, functionality of mchains will be extended in the
future versions of SObjectizer.
This presentation should not be considered as a
comprehensive guide to SObjectizer's mchains. For more
information on mchains please see the corresponding Wiki
section.
SObjectizer Team, May 2017