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. Introductory part

1,519 views

Published on

This is an introductory part of more detailed information about SObjectizer-5.5.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Dive into SObjectizer 5.5. Introductory part

  1. 1. Dive into SObjectizer-5.5 SObjectizer Team, Jan 2016 Introductory part (at v.5.5.15)
  2. 2. SObjectizer is a small framework that simplifies the development of concurrent and event-driven applications in C++. SObjectizer Team, Jan 2016
  3. 3. SObjectizer was designed under the influence of several approaches. The Actor Model* is one of them. But the history of SObjectizer started long before this model became widely known. * http://en.wikipedia.org/wiki/Actor_model SObjectizer Team, Jan 2016
  4. 4. SObjectizer’s main ideas and principles were formulated in the middle of 1990s, during the development of SCADA Objectizer project in Development Bureau of System Programming in Homyel, Belarus (1996-2000). SCADA Objectizer’s ideas were reused in the new project SObjectizer-4 in 2002. SObjectizer Team, Jan 2016
  5. 5. Evolution of SObjectizer-4 was stopped in 2010 and the development of SObjectizer-5 started. SObjectizer Team, Jan 2016
  6. 6. SObjectizer was an in-house project of JSC Intervale* for the long time. It was used in the development of the following software systems: ● SMS/USSD traffic service ● financial transaction handling ● software parameters monitoring. * www.intervale.ru SObjectizer Team, Jan 2016
  7. 7. SObjectizer was published as an OpenSource project on SourceForge under 3-clauses BSD- licence in 2006. Since 2013 SObjectizer’s development completely moved to SourceForge. SObjectizer now is an independent project which is totally separated from JSC Intervale. SObjectizer Team, Jan 2016
  8. 8. SObjectizer allows to build a concurrent application as a set of agent-objects... ...which interact with each other only by means of asynchronous messages. SObjectizer Team, Jan 2016
  9. 9. Every agent receives its own working context. This context is used for message processing. An agent is bound to its context. It allows not to worry about defense of integrity of the agent’s data in the multithreaded environment. This defense is automatically performed by SObjectizer itself! SObjectizer Team, Jan 2016
  10. 10. Let’s dive inside! SObjectizer Team, Jan 2016
  11. 11. All SObjectizer’s work is performed inside SObjectizer Environment. SObjectizer Environment is a container for SObjectizer Run-Time, agent’s cooperations, message boxes, dispatchers and timer thread. It is possible to create several Environments in one application. Each Environment will work independently from others. SObjectizer Team, Jan 2016
  12. 12. SObjectizer Environment is created by so_5::launch() function. A new instance of Environment is created and started inside so_5::launch(). The control from so_5::launch() is returned only when Environment finished its execution. What happened inside Environment is completely dependent on user supplied starting function. SObjectizer Team, Jan 2016
  13. 13. It looks like that: #include <iostream> #include <so_5/all.hpp> void init( so_5::environment_t & env ) { ... } int main() { try { so_5::launch( &init ); } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } } SObjectizer Team, Jan 2016
  14. 14. #include <iostream> #include <so_5/all.hpp> void init( so_5::environment_t & env ) { ... } int main() { try { so_5::launch( &init ); } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } } Main header file with all necessary definitions. It looks like that: SObjectizer Team, Jan 2016
  15. 15. #include <iostream> #include <so_5/all.hpp> void init( so_5::environment_t & env ) { ... } int main() { try { so_5::launch( &init ); } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } } User supplied starting function. It creates all application specific stuff. It looks like that: SObjectizer Team, Jan 2016
  16. 16. #include <iostream> #include <so_5/all.hpp> void init( so_5::environment_t & env ) { ... } int main() { try { so_5::launch( &init ); } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } } SObjectizer Environment object. Starting function will work inside it. It looks like that: SObjectizer Team, Jan 2016
  17. 17. #include <iostream> #include <so_5/all.hpp> void init( so_5::environment_t & env ) { ... } int main() { try { so_5::launch( &init ); } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } } Creation of Environment, starting it and invoking init() inside that Environment. Control will be returned when all application-specific agents finish their work. It looks like that: SObjectizer Team, Jan 2016
  18. 18. #include <iostream> #include <so_5/all.hpp> void init( so_5::environment_t & env ) { ... } int main() { try { so_5::launch( &init ); } catch( const std::exception & x ) { std::cerr << "Exception: " << x.what() << std::endl; } } Error handling. Error reporting is done via exceptions. It looks like that: SObjectizer Team, Jan 2016
  19. 19. Usually one or more agent’s cooperations are created inside the starting function. Cooperation is a group of agents which must work together and can’t exist one without another. For example: pinger and ponger agents which send ping/pong messages back and forth. There is no any sense in pinger agent without ponger agent. They must appear and disappear at the same time. SObjectizer Team, Jan 2016
  20. 20. Creation of a cooperation with two agents: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); coop->make_agent< pinger >(); coop->make_agent< ponger >(); env.register_coop( std::move( coop ) ); } SObjectizer Team, Jan 2016
  21. 21. void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); coop->make_agent< pinger >(); coop->make_agent< ponger >(); env.register_coop( std::move( coop ) ); } Creation consists of three steps. At the beginning the Environment creates a cooperation... Creation of a cooperation with two agents: SObjectizer Team, Jan 2016
  22. 22. void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); coop->make_agent< pinger >(); coop->make_agent< ponger >(); env.register_coop( std::move( coop ) ); } Then the cooperation is filled up with agents... make_agent() method is like std::make_unique from C++14. It creates dynamically allocated agent of specified type. Creation of a cooperation with two agents: SObjectizer Team, Jan 2016
  23. 23. void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); coop->make_agent< pinger >(); coop->make_agent< ponger >(); env.register_coop( std::move( coop ) ); } Then the cooperation is being registered. Creation of a cooperation with two agents: SObjectizer Team, Jan 2016
  24. 24. void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); coop->make_agent< pinger >(); coop->make_agent< ponger >(); env.register_coop( std::move( coop ) ); } Every cooperation must have unique name. The uniqueness is checked inside register_coop(). But SObjectizer can be asked to generate a name for the new cooperation automatically. Creation of a cooperation with two agents: SObjectizer Team, Jan 2016
  25. 25. void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); coop->make_agent< pinger >(); coop->make_agent< ponger >(); env.register_coop( std::move( coop ) ); } Starting function finishes after cooperation registration. But the Environment will work until that cooperation will be deregistered. Or the Environment stop function will be called explicitly. Creation of a cooperation with two agents: SObjectizer Team, Jan 2016
  26. 26. What is an agent? Let’s start with the simplest agent in this example. A ponger agent. SObjectizer Team, Jan 2016
  27. 27. It does very simple tasks: ● receives ping messages ● replies with pong messages. So first of all let’s define those messages... SObjectizer Team, Jan 2016
  28. 28. Definition of the messages: struct ping : public so_5::message_t { unsigned int m_req; ping( unsigned int req ) : m_req{ req } {} }; struct pong : public so_5::message_t { unsigned int m_resp; pong( unsigned int resp ) : m_resp{ resp } {} }; SObjectizer Team, Jan 2016
  29. 29. struct ping : public so_5::message_t { unsigned int m_req; ping( unsigned int req ) : m_req{ req } {} }; struct pong : public so_5::message_t { unsigned int m_resp; pong( unsigned int resp ) : m_resp{ resp } {} }; Every message must be represented by a separate C++ class (or a structure). Message dispatching and selection of a handler are based on the message type information. Definition of the messages: SObjectizer Team, Jan 2016
  30. 30. struct ping : public so_5::message_t { unsigned int m_req; ping( unsigned int req ) : m_req{ req } {} }; struct pong : public so_5::message_t { unsigned int m_resp; pong( unsigned int resp ) : m_resp{ resp } {} }; Messages types with some data inside usually derived from common base class so_5::message_t. In v. 5.5.15 it is not necessary but inheritance is shown here just for demonstration purposes. There are also messages without actual data. They are called signals. They will be described further. Definition of the messages: SObjectizer Team, Jan 2016
  31. 31. struct ping : public so_5::message_t { unsigned int m_req; ping( unsigned int req ) : m_req{ req } {} }; struct pong : public so_5::message_t { unsigned int m_resp; pong( unsigned int resp ) : m_resp{ resp } {} }; SObjectizer has no significant limits for message’s content. In this particular case the m_req and m_resp fields are necessary for the sample logic. They have no relation to SObjectizer features. Definition of the messages: SObjectizer Team, Jan 2016
  32. 32. ponger agent: class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; SObjectizer Team, Jan 2016
  33. 33. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; Every ordinary agent must be represented by a C++ class. There also can be non ordinary agents named ad-hoc agents. They will be described further. ponger agent: SObjectizer Team, Jan 2016
  34. 34. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; Class of ordinary agent must be derived from common base type so_5::agent_t. ponger agent: SObjectizer Team, Jan 2016
  35. 35. ponger agent: class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; Every agent must be bound to an Environment where an agent will work. Because of that a reference to Environment object must be passed to the agent’s constructor and redirected to the constructor of the base type so_5::agent_t. A reference to Environment is inside of context_t object. SObjectizer Team, Jan 2016
  36. 36. ponger agent: class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; SObjectizer calls so_define_agent() method before an agent will be registered. Agent must perform all necessary tuning actions in this method. Create message subscriptions in particular. SObjectizer Team, Jan 2016
  37. 37. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; Ponger agent subscribes only to one message of ping type. This message is going from a message box with name “table”. Message box must be specified explicitly. But the message type is deduced by SObjectizer automatically from the message handler signature. ponger agent: SObjectizer Team, Jan 2016
  38. 38. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; A message handler method is named event handler method. Or just event. ponger agent: SObjectizer Team, Jan 2016
  39. 39. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; A message instance which caused an event is passed to event handler by const reference. Event handler must not modify that message instance because it can be handled by the different agents at the same time. ponger agent: SObjectizer Team, Jan 2016
  40. 40. Agent replies by sending a pong message instance to the message box with name “table”. ponger agent: class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; SObjectizer Team, Jan 2016
  41. 41. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; so_5::send() template function constructs an object of pong type and sends it to the message box specified. ponger agent: SObjectizer Team, Jan 2016
  42. 42. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; All arguments after message box are passed to the message object’ s constructor. In this case it is resp argument for the pong’s constructor. ponger agent: SObjectizer Team, Jan 2016
  43. 43. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &ponger::evt_ping ); } private : const so_5::mbox_t m_table; void evt_ping( const ping & evt ) { so_5::send< pong >( m_table, evt.m_req ); } }; Ponger agent gets a reference to the message box for message exchange by itself. In this case it is a message box with name “table”. This box is created by invocation of create_mbox(). A reference to message box is stored inside the agent. ponger agent: SObjectizer Team, Jan 2016
  44. 44. pinger agent (beginning): class pinger : public so_5::agent_t { public : pinger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &pinger::evt_pong ); } virtual void so_evt_start() override { so_5::send< ping >( m_table, 500 ); } SObjectizer Team, Jan 2016
  45. 45. class pinger : public so_5::agent_t { public : pinger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &pinger::evt_pong ); } virtual void so_evt_start() override { so_5::send< ping >( m_table, 500 ); } Pinger agent is very similar to the ponger agent: receives a context_t object in its constructor and creates a reference to message box with name “table”. Pinger also subscribes to a single message in so_define_agent(). pinger agent (beginning): SObjectizer Team, Jan 2016
  46. 46. class pinger : public so_5::agent_t { public : pinger( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ).event( &pinger::evt_pong ); } virtual void so_evt_start() override { so_5::send< ping >( m_table, 500 ); } But there is one significant distinction: so_evt_start() method. This method is called just after successful registration of the agent’s cooperation. An agent can perform any starting actions in that method. In this particular case: sends the very first ping message. pinger agent (beginning): SObjectizer Team, Jan 2016
  47. 47. pinger agent (ending): private : const so_5::mbox_t m_table; void evt_pong( const pong & evt ) { if( evt.m_resp ) so_5::send< ping >( m_table, evt.m_resp - 1 ); else so_deregister_agent_coop_normally(); } }; SObjectizer Team, Jan 2016
  48. 48. pinger agent (ending): private : const so_5::mbox_t m_table; void evt_pong( const pong & evt ) { if( evt.m_resp ) so_5::send< ping >( m_table, evt.m_resp - 1 ); else so_deregister_agent_coop_normally(); } }; In the evt_pong() the agent can continue the message exchange by sending the next ping message. Or, in the case when all messages have been sent, cooperation deregistration can be initiated. SObjectizer Team, Jan 2016
  49. 49. pinger agent (ending): private : const so_5::mbox_t m_table; void evt_pong( const pong & evt ) { if( evt.m_resp ) so_5::send< ping >( m_table, evt.m_resp - 1 ); else so_deregister_agent_coop_normally(); } }; The reasons for deregistering a cooperation might be different. In this case the deregistration is a normal part of the application logic. There will be no live cooperations after deregistration of pinger/ponger cooperation. So Environment will finish its work and so_5::launch will return control to the caller. SObjectizer Team, Jan 2016
  50. 50. Few words about message boxes (mboxes)... Despite other similar tools like Erlang, Akka or CAF, in SObjectizer a message is sent not to an agent, but to a message box (mbox). There could be one agent behind the mbox. Or many agents. Or none. SObjectizer Team, Jan 2016
  51. 51. There are two types of mboxes in SObjectizer: SObjectizer Team, Jan 2016
  52. 52. Multi-Producers/Multi-Consumers mboxes. They are like “bulletin boards”. A message sent to such mbox becomes available for all subscribers of the mbox. MPMC-mbox is shown in the code above. SObjectizer Team, Jan 2016
  53. 53. Multi-Producers/Single-Consumer mboxes. Mboxes of that type have only one subscriber. It is an agent who owns such mbox. SObjectizer Team, Jan 2016
  54. 54. Lets add another agent to the example to show specifics of MPMC-mboxes... This agent will listen the message exchange between pinger and ponger agents, and will count the messages sent. SObjectizer Team, Jan 2016
  55. 55. listener agent: class listener : public so_5::agent_t { public : listener( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ) .event( [this]( const ping & ) { ++m_pings; } ) .event( [this]( const pong & ) { ++m_pongs; } ); } virtual void so_evt_finish() override { std::cout << "result: " << m_pings << "/" << m_pongs << std::endl; } private : const so_5::mbox_t m_table; unsigned int m_pings = 0; unsigned int m_pongs = 0; }; SObjectizer Team, Jan 2016
  56. 56. listener agent: class listener : public so_5::agent_t { public : listener( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ) .event( [this]( const ping & ) { ++m_pings; } ) .event( [this]( const pong & ) { ++m_pongs; } ); } virtual void so_evt_finish() override { std::cout << "result: " << m_pings << "/" << m_pongs << std::endl; } private : const so_5::mbox_t m_table; unsigned int m_pings = 0; unsigned int m_pongs = 0; }; It is necessary to receive two messages. Because of that the agent subscribes two events. Lambda-functions are used instead of event handler methods. Types of messages are automatically deduced from lambdas signatures. SObjectizer Team, Jan 2016
  57. 57. listener agent: class listener : public so_5::agent_t { public : listener( context_t ctx ) : so_5::agent_t( ctx ) , m_table( env.create_mbox( "table" ) ) {} virtual void so_define_agent() override { so_subscribe( m_table ) .event( [this]( const ping & ) { ++m_pings; } ) .event( [this]( const pong & ) { ++m_pongs; } ); } virtual void so_evt_finish() override { std::cout << "result: " << m_pings << "/" << m_pongs << std::endl; } private : const so_5::mbox_t m_table; unsigned int m_pings = 0; unsigned int m_pongs = 0; }; so_evt_finish() method is the opposite to so_evt_start(). It is called for agent just before the very end of agent’s work. In this case that method is used for result printing. SObjectizer Team, Jan 2016
  58. 58. If we add a listener to the cooperation: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); coop->make_agent< pinger >(); coop->make_agent< ponger >(); coop->make_agent< listener >(); env.register_coop( std::move( coop ) ); } SObjectizer Team, Jan 2016
  59. 59. Then we will see at the end: result: 501/501 It means that message sending to MPMC-mbox is a broadcast message sending. SObjectizer Team, Jan 2016
  60. 60. MPMC-mboxes must be created manually. MPSC-mboxes, in contradiction, are created automatically for every agent. It means that every agent has its own MPSC-mbox. In SObjectizer's terms such mbox is often named direct_mbox. SObjectizer Team, Jan 2016
  61. 61. When a message is sent to MPSC-mbox it will go to mbox owner for processing. Or just discarded if mbox owner is not subscribed to that message. It means that if two agents interact via direct_mboxes nobody can listen them. SObjectizer Team, Jan 2016
  62. 62. But direct_mboxes are used not for creation of a “private channels” for message exchanges. Direct_mboxes are more efficient than MPMC- mboxes. Dispatching for direct_mboxes is simpler and requires fewer internal locks. Therefore usage of direct_mboxes is more preferable if application logic doesn’t require broadcast message sending. SObjectizer Team, Jan 2016
  63. 63. There is no need in broadcast message sending in the example above. Lets rewrite it with direct_mboxes. And throw out a listener agent. Lets pinger and ponger agent count the messages by themselves. And replace messages with signals. SObjectizer Team, Jan 2016
  64. 64. A signal is a special case of a message when only the fact of the message existence is important. But the message itself has no any data inside. It is like sending of atoms in Erlang, when only an atom is sent without any additional data. SObjectizer Team, Jan 2016
  65. 65. In the application code written with SObjectizer signals are so widely used that SObjectizer’s API was extended to simplify usage of signals. At the API level the work with signal is very similar to the work with messages. Sometimes. Sometimes not. SObjectizer Team, Jan 2016
  66. 66. Replace ping and pong messages with signals... SObjectizer Team, Jan 2016
  67. 67. Replace ping and pong messages with signals... SObjectizer Team, Jan 2016 struct ping : public so_5::message_t { unsigned int m_req; ping( unsigned int req ) : m_req{ req } {} }; struct pong : public so_5::message_t { unsigned int m_resp; pong( unsigned int resp ) : m_resp{ resp } {} }; struct ping : public so_5::signal_t {}; struct pong : public so_5::signal_t {};
  68. 68. Replace ping and pong messages with signals... SObjectizer Team, Jan 2016 struct ping : public so_5::message_t { unsigned int m_req; ping( unsigned int req ) : m_req{ req } {} }; struct pong : public so_5::message_t { unsigned int m_resp; pong( unsigned int resp ) : m_resp{ resp } {} }; struct ping : public so_5::signal_t {}; struct pong : public so_5::signal_t {}; Signal types must be derived from so_5::signal_t and must not contain any data inside.
  69. 69. Changing of pinger agent (beginning): class pinger : public so_5::agent_t { public : pinger( context_t ctx ) : so_5::agent_t( ctx ) {} void set_ponger_mbox( const so_5::mbox_t & mbox ) { m_ponger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< pong >( [this]{ ++m_pongs; so_5::send< ping >( m_ponger ); } ); } SObjectizer Team, Jan 2016
  70. 70. Changing of pinger agent (beginning): class pinger : public so_5::agent_t { public : pinger( context_t ctx ) : so_5::agent_t( ctx ) {} void set_ponger_mbox( const so_5::mbox_t & mbox ) { m_ponger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< pong >( [this]{ ++m_pongs; so_5::send< ping >( m_ponger ); } ); } A direct_mbox can be accessed only after the agent creation. Because of that a separate method is needed for connecting of pinger and ponger agents. It will be called after instantiation of the agent. SObjectizer Team, Jan 2016
  71. 71. Changing of pinger agent (beginning): class pinger : public so_5::agent_t { public : pinger( context_t ctx ) : so_5::agent_t( ctx ) {} void set_ponger_mbox( const so_5::mbox_t & mbox ) { m_ponger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< pong >( [this]{ ++m_pongs; so_5::send< ping >( m_ponger ); } ); } so_subscribe_self() method is used for subscription to message from agent's direct_mbox. Method event receives a lambda- function with reaction to pong signal. SObjectizer Team, Jan 2016
  72. 72. Changing of pinger agent (beginning): class pinger : public so_5::agent_t { public : pinger( context_t ctx ) : so_5::agent_t( ctx ) {} void set_ponger_mbox( const so_5::mbox_t & mbox ) { m_ponger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< pong >( [this]{ ++m_pongs; so_5::send< ping >( m_ponger ); } ); } The signal type must be explicitly specified during subscription. A method or lambda-function without argument can only be used as signal handler. Unlike a message there is no a signal instance to be passed to a handler. Because of that there is no way to deduce signal type by the event handler’s signature. SObjectizer Team, Jan 2016
  73. 73. Changing of pinger agent (ending): virtual void so_evt_start() override { so_5::send< ping >( m_ponger ); } virtual void so_evt_finish() override { std::cout << "pongs: " << m_pongs << std::endl; } private : so_5::mbox_t m_ponger; unsigned int m_pongs = 0; }; SObjectizer Team, Jan 2016
  74. 74. Changing of pinger agent (ending): virtual void so_evt_start() override { so_5::send< ping >( m_ponger ); } virtual void so_evt_finish() override { std::cout << "pongs: " << m_pongs << std::endl; } private : so_5::mbox_t m_ponger; unsigned int m_pongs = 0; }; Signal sending is performed by the same so_5::send() function. Like a message sending. But there is no any other arguments after receiver mbox. SObjectizer Team, Jan 2016
  75. 75. Similar changes to a ponger agent (beginning): class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx) {} void set_pinger_mbox( const so_5::mbox_t & mbox ) { m_pinger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< ping >( [this]{ ++m_pings; so_5::send< pong >( m_pinger ); } ); } SObjectizer Team, Jan 2016
  76. 76. Similar changes to a ponger agent (ending): virtual void so_evt_finish() override { std::cout << "pings: " << m_pings << std::endl; } private : so_5::mbox_t m_pinger; unsigned int m_pings = 0; }; SObjectizer Team, Jan 2016
  77. 77. Creation of the cooperation becomes more verbose: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); auto a_pinger = coop->make_agent< pinger >(); auto a_ponger = coop->make_agent< ponger >(); a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() ); a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() ); env.register_coop( std::move( coop ) ); } SObjectizer Team, Jan 2016
  78. 78. Moreover there is an error... SObjectizer Team, Jan 2016
  79. 79. No one stops them! They will ping each other infinitely. SObjectizer Team, Jan 2016
  80. 80. Fix this problem by adding another agent that will stop example after one second... SObjectizer Team, Jan 2016
  81. 81. Because this agent will handle only one event there is no need to define a separate class for it, redefine so_define_agent() method and so on... An ad-hoc agent will be used instead. SObjectizer Team, Jan 2016
  82. 82. Ad-hoc agent for stopping the work after one second: struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent(); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); SObjectizer Team, Jan 2016
  83. 83. Ad-hoc agent for stopping the work after one second: struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent(); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); Stop signal. SObjectizer Team, Jan 2016
  84. 84. Ad-hoc agent for stopping the work after one second: struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent(); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); Ad-hoc agent creation. A handle is returned. This handle can be used for agent tuning. SObjectizer Team, Jan 2016
  85. 85. Ad-hoc agent for stopping the work after one second: struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent(); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); The new agent is subscribed to just one signal. SObjectizer Team, Jan 2016
  86. 86. Ad-hoc agent for stopping the work after one second: struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent(); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); Signal is expected from direct_mbox of the the agent. SObjectizer Team, Jan 2016
  87. 87. Ad-hoc agent for stopping the work after one second: struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent(); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); The signal handler will stop SObjectizer Environment. The single cooperation will be deregistered automatically. SObjectizer Team, Jan 2016
  88. 88. Ad-hoc agent is created and tuned. A delayed for one second stop signal must be sent: env.register_coop( std::move( coop ) ); so_5::send_delayed< stop >( stopper, std::chrono::seconds(1) ); SObjectizer Team, Jan 2016
  89. 89. Ad-hoc agent is created and tuned. A delayed for one second stop signal must be sent: env.register_coop( std::move( coop ) ); so_5::send_delayed< stop >( stopper, std::chrono::seconds(1) ); A so_5::send_delayed function make delayed send of a signal or a message. In this case a stop signal will be sent to the direct_mbox of the new ad-hoc agent with one second delay. SObjectizer Team, Jan 2016
  90. 90. Finally the starting function becomes: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname ); auto a_pinger = coop->make_agent< pinger >(); auto a_ponger = coop->make_agent< ponger >(); a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() ); a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() ); struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent(); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); env.register_coop( std::move( coop ) ); so_5::send_delayed< stop >( stopper, std::chrono::seconds(1) ); } SObjectizer Team, Jan 2016
  91. 91. Running the updated example... pongs: 4441168 pings: 4441169 SObjectizer Team, Jan 2016
  92. 92. Just above 8M messages per seconds. Core i7 2.4GHz, 8GiB RAM, Win8.1 64-bit, Visual C++ 13.0 64-bit. SObjectizer Team, Jan 2016
  93. 93. So far, so good. But which working context has been used? SObjectizer Team, Jan 2016
  94. 94. All agents were working on a single thread! This example just shows the effectiveness of message passing between agents which are working on the same context. But who choose the context? And how an agent can be bound to another context? SObjectizer Team, Jan 2016
  95. 95. A programmer makes choice of context when binds an agent to a dispatcher. If dispatcher is not specified then an agent will be bound to default dispatcher. Just like in the example above. SObjectizer Team, Jan 2016
  96. 96. The default dispatcher runs all the agents on a single working thread. There is a something like “cooperative multitasking” for agents on the default dispatcher. If one of them will slow down the other will slow down too. But it is possible to create any number of various dispatchers and bind agents to the them. SObjectizer Team, Jan 2016
  97. 97. Lets bind pinger and ponger agent to different working threads (every agent will have its own dedicated working thread)... An instance of active_obj dispatcher will be created for that. Agents will be bound to it. This dispatcher creates a dedicated thread for each agent bound to it (an agent becomes an active object). SObjectizer Team, Jan 2016
  98. 98. There is nothing to change inside the agents... All the changes will be in the starting function only. SObjectizer Team, Jan 2016
  99. 99. Binding of the agents to different dispatches: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname, so_5::disp::active_obj::create_private_disp( env )->binder() ); auto a_pinger = coop->make_agent< pinger >(); auto a_ponger = coop->make_agent< ponger >(); a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() ); a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() ); struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent( so_5::create_default_disp_binder() ); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); env.register_coop( std::move( coop ) ); so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) ); } SObjectizer Team, Jan 2016
  100. 100. Binding of the agents to different dispatches: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname, so_5::disp::active_obj::create_private_disp( env )->binder() ); auto a_pinger = coop->make_agent< pinger >(); auto a_ponger = coop->make_agent< ponger >(); a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() ); a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() ); struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent( so_5::create_default_disp_binder() ); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); env.register_coop( std::move( coop ) ); so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) ); } Creation of the active objects dispatcher. A binder for that dispatcher will be used as main dispatcher binder for the new coop. SObjectizer Team, Jan 2016
  101. 101. Binding of the agents to different dispatches: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname, so_5::disp::active_obj::create_private_disp( env )->binder() ); auto a_pinger = coop->make_agent< pinger >(); auto a_ponger = coop->make_agent< ponger >(); a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() ); a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() ); struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent( so_5::create_default_disp_binder() ); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); env.register_coop( std::move( coop ) ); so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) ); } Pinger and ponger agents are being added to the cooperation without any additional information. It means they will be bound to the main cooperation dispatcher. It is the active_obj dispatcher created above. SObjectizer Team, Jan 2016
  102. 102. Binding of the agents to different dispatches: void init( so_5::environment_t & env ) { auto coop = env.create_coop( so_5::autoname, so_5::disp::active_obj::create_private_disp( env )->binder() ); auto a_pinger = coop->make_agent< pinger >(); auto a_ponger = coop->make_agent< ponger >(); a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() ); a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() ); struct stop : public so_5::signal_t {}; auto stopper = coop->define_agent( so_5::create_default_disp_binder() ); stopper.event< stop >( stopper, [&env]{ env.stop(); } ); env.register_coop( std::move( coop ) ); so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) ); } But there is no need for a separate thread for stopper agent. Because of that this agent is being bound to the default dispatcher explicitly. Without this the stopper will be bound to the cooperation main dispatcher. SObjectizer Team, Jan 2016
  103. 103. What we get now? SObjectizer Team, Jan 2016
  104. 104. What we get now? pings: pongs: 1234623 1234624 SObjectizer Team, Jan 2016
  105. 105. Is it ok? SObjectizer Team, Jan 2016
  106. 106. Yes. It is. And there could be more strange results. But what happened? SObjectizer Team, Jan 2016
  107. 107. Pinger and ponger agents are now working on different threads and compete for std::cout object. As result the output to std::cout got mixed. It could be even worse. Or could not be mixed at all. This is concurrency in action... SObjectizer Team, Jan 2016
  108. 108. pings: pongs: 1234623 1234624 What else? SObjectizer Team, Jan 2016
  109. 109. The performance has dropped. We have seen 8M messages per second on one thread. And just 2M on two threads. It is expected result. Passing a single message from thread to thread is an expensive operation. SObjectizer Team, Jan 2016
  110. 110. But is there any changes in the agents? SObjectizer Team, Jan 2016
  111. 111. Ponger agent for one thread: class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) {} void set_pinger_mbox( const so_5::mbox_t & mbox ) { m_pinger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< ping >( [this]{ ++m_pings; so_5::send< pong >( m_pinger ); } ); } virtual void so_evt_finish() override { std::cout << "pings: " << m_pings << std::endl; } private : so_5::mbox_t m_pinger; unsigned int m_pings = 0; }; SObjectizer Team, Jan 2016
  112. 112. class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) {} void set_pinger_mbox( const so_5::mbox_t & mbox ) { m_pinger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< ping >( [this]{ ++m_pings; so_5::send< pong >( m_pinger ); } ); } virtual void so_evt_finish() override { std::cout << "pings: " << m_pings << std::endl; } private : so_5::mbox_t m_pinger; unsigned int m_pings = 0; }; Ponger agent for two threads: class ponger : public so_5::agent_t { public : ponger( context_t ctx ) : so_5::agent_t( ctx ) {} void set_pinger_mbox( const so_5::mbox_t & mbox ) { m_pinger = mbox; } virtual void so_define_agent() override { so_subscribe_self().event< ping >( [this]{ ++m_pings; so_5::send< pong >( m_pinger ); } ); } virtual void so_evt_finish() override { std::cout << "pings: " << m_pings << std::endl; } private : so_5::mbox_t m_pinger; unsigned int m_pings = 0; }; SObjectizer Team, Jan 2016 Ponger agent for one thread:
  113. 113. It is a direct consequence of interaction only by asynchronous messages. Because of that agents are unaware about working context. Providing an appropriate set of different dispatchers is a task of SObjectizer. SObjectizer Team, Jan 2016
  114. 114. SObjectizer has several ready-to-use dispatchers. There are one_thread, active_obj, active_group, thread_pool, adv_thread_pool and three more dispatchers which support priorities of agents... SObjectizer Team, Jan 2016
  115. 115. A programmer can not only select the appropriate type of a dispatcher... ...but can create any number of those dispatchers. SObjectizer Team, Jan 2016
  116. 116. For example: ● one one_thread dispatcher for AMQP-client agent; ● one thread_pool dispatcher for handling requests from AMQP-queues; ● one active_obj dispatcher for DBMS-related agents; ● yet another active_obj dispatcher for agents whose work with HSMs connected to the computer; ● and yet another thread_pool dispatcher for agents for managing all the stuff described above. SObjectizer Team, Jan 2016
  117. 117. But there are yet more SObjectizer features... Such important things like: ● agent’s states ● periodic messages ● synchronous agents interaction ● child cooperation ● exception handling ● Run-Time parameters tuning and so on... SObjectizer Team, Jan 2016
  118. 118. ...will be described in a more deep dive But introductory part is finished. SObjectizer Team, Jan 2016
  119. 119. 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 GitHub mirror: https://github.com/masterspline/SObjectizer

×