Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Dive into SObjectizer 5.5. Ninth Part: Message Chains

649 views

Published on

The next part of "Dive into SObjectizer-5.5" serie. This part introduces message chains. Message chains allows to use CSP-like concurrency in C++.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Dive into SObjectizer 5.5. Ninth Part: Message Chains

  1. 1. Dive into SObjectizer-5.5 SObjectizer Team, May 2017 Ninth Part: Message Chains (at v.5.5.19)
  2. 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
  3. 3. Introduction SObjectizer Team, May 2017
  4. 4. The main purpose of message chains is to simplify interaction between SObjectizer- and non-SObjectizer parts of application. SObjectizer Team, May 2017
  5. 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. 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. 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. 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
  9. 9. mchain_t Type SObjectizer Team, May 2017
  10. 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
  11. 11. Types of mchains SObjectizer Team, May 2017
  12. 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. 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. 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. 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. 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
  17. 17. More About Size-Limited mchains SObjectizer Team, May 2017
  18. 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. 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. 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. 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. 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. 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
  24. 24. Receiving And Handling Messages From mchains SObjectizer Team, May 2017
  25. 25. so_5::receive function which allows us to receive and handle messages from mchain has two variants. SObjectizer Team, May 2017
  26. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  39. 39. mchains Are MPMC Queues SObjectizer Team, May 2017
  40. 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. 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
  42. 42. Message Handlers SObjectizer Team, May 2017
  43. 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. 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. 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. 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. 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
  48. 48. Usage Of mchains With Send Functions SObjectizer Team, May 2017
  49. 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. 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. 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. 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
  53. 53. mchains As mboxes SObjectizer Team, May 2017
  54. 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. 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. 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. 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. 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. 59. Usage Of mchains For Programming In CSP Style SObjectizer Team, May 2017
  60. 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. 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. 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. 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); }
  64. 64. Some Final Words SObjectizer Team, May 2017
  65. 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. 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
  67. 67. Additional Information: Project’s home: http://sourceforge.net/projects/sobjectizer Documentation: http://sourceforge.net/p/sobjectizer/wiki/ Forum: http://sourceforge.net/p/sobjectizer/discussion/ Google-group: https://groups.google.com/forum/#!forum/sobjectizer Support: https://stiffstream.com

×