Four years of development SObjectizer-5.5. How has the SObjectizer changed during this time?

    The first version of SObjectizer, as part of the 5.5 branch, was released a little more than four years ago - at the beginning of October 2014. And today another version numbered 5.5.23 , which, quite possibly, will close the history of the development of SObjectizer-5.5. In my opinion, this is an excellent reason to look back and see what has been done over the past four years.

    In this article I will try to thesesize the most important and significant changes and innovations: what was added, why, how did this affect SObjectizer itself or its use.

    Perhaps someone such a story would be interesting from the point of view of archeology. And someone may be prevented from such a dubious adventure as developing your own actor framework for C ++;)

    A small lyrical digression about the role of old C ++ compilers


    The history of SObjectizer-5 began in the middle of 2010. At the same time, we immediately focused on C ++ 0x. Already in 2011, the first versions of SObjectizer-5 were used to write production code. It’s clear that we didn’t have compilers with normal C ++ 11 support.

    For a long time, we could not fully use all the capabilities of the “modern C ++”: variadic templates, noexcept, constexpr, etc. This could not but affect the API SObjectizer. And it affected a very, very long time. Therefore, if while reading the description of a certain feature you have the question “Why wasn’t it done before?”, Then the answer to this question is most likely: “Because it wasn’t possible before.”

    What has appeared and / or changed in SObjectizer 5.5 since that time?


    In this section, we will go through a number of features that have had a significant impact on SObjectizer. The order in this list is random and not related to the “significance” or “weight” of the described features.

    Waiver of so_5 :: rt namespace


    What happened?


    Initially, in the fifth SObjectizer, all that pertained to the SObjectizer runtime was defined within the so_5 :: rt namespace. For example, we had so_5 :: rt :: environment_t, so_5 :: rt :: agent_t, so_5 :: rt :: message_t, etc. What can be seen, for example, in the traditional HelloWorld example from SO-5.5.0:

    #include<so_5/all.hpp>classa_hello_t :public so_5::rt::agent_t
    {
    public:
       a_hello_t( so_5::rt::environment_t & env ) : so_5::rt::agent_t( env )
       {}
       voidso_evt_start() override
       {
          std::cout << "Hello, world! This is SObjectizer v.5." << std::endl;
          so_environment().stop();
       }
       voidso_evt_finish() override
       {
          std::cout << "Bye! This was SObjectizer v.5." << std::endl;
       }
    };
    intmain(){
       try
       {
          so_5::launch( []( so_5::rt::environment_t & env ) {
             env.register_agent_as_coop( "coop", newa_hello_t( env ) );
          } );
       }
       catch( conststd::exception & ex )
       {
          std::cerr << "Error: " << ex.what() << std::endl;
          return1;
       }
       return0;
    }

    The abbreviation “rt” stands for “run-time”. And it seemed to us that the entry “so_5 :: rt” is much better and more practical than “so_5 :: runtime”.

    But it turned out that for many people “rt” is only “real-time” and nothing else. And the use of “rt” as an abbreviation for “runtime” disrupts their feelings so much that sometimes the announcements of the SObjectizer versions in RuNet turned into holivars on the subject of the “non-admissibility of the interpretation of“ rt ”differently than“ real-time ”.

    In the end, we are tired of it. And we just closed the namespace "so_5 :: rt".

    What happened?


    Everything that was defined inside “so_5 :: rt” just jumped into “so_5”. As a result, the same HelloWorld now looks like this:

    #include<so_5/all.hpp>classa_hello_t :public so_5::agent_t
    {
       public:
          a_hello_t( context_t ctx ) : so_5::agent_t( ctx )
          {}
          voidso_evt_start() override
          {
             std::cout << "Hello, world! This is SObjectizer v.5 ("
                   << SO_5_VERSION << ")" << std::endl;
             so_environment().stop();
          }
          voidso_evt_finish() override
          {
             std::cout << "Bye! This was SObjectizer v.5." << std::endl;
          }
    };
    intmain(){
       try
       {
          so_5::launch( []( so_5::environment_t & env ) {
             env.register_agent_as_coop( "coop", env.make_agent<a_hello_t>() );
          } );
       }
       catch( conststd::exception & ex )
       {
          std::cerr << "Error: " << ex.what() << std::endl;
          return1;
       }
       return0;
    }

    But the old names from “so_5 :: rt” are still accessible, through ordinary using-and (typedefs). So the code written for the first versions of SO-5.5 turns out to be workable in the newer versions of SO-5.5.

    Finally, the so_5 :: rt namespace will be removed in version 5.6.

    What impact did it have?


    Probably, the code on SObjectizer is now more readable. Still, “so_5 :: send ()” is perceived better than “so_5 :: rt :: send ()”.

    Well, we, as the developers of SObjectizer, have a headache diminished. Around the announcements of SObjectizer at one time there was too much empty talk and unnecessary reasoning (starting from the questions “Why do we need actors in C ++ in general” and ending with “Why do not you use PascalCase for naming entities”). One flaming theme became smaller and it was good :)

    Simplify the sending of messages and the evolution of message handlers


    What happened?


    Even in the very first versions of SObjectizer-5.5, the sending of a regular message was carried out using the deliver_message method, which had to be called from the recipient's mbox. To send a deferred or periodic message, it was necessary to call single_timer / schedule_timer on an object of type environment_t. And already sending a synchronous request to another agent generally required a whole chain of operations. Here, for example, how it all could look like four years ago (std :: make_unique () is already used here, which was not yet available in C ++ 11):

    // Отсылка обычного сообщения.
    mbox->deliver_message(std::make_unique<my_message>(...));
    // Отсылка отложенного сообщения.
    env.single_timer(std::make_unique<my_message>(...), mbox, std::chrono::seconds(2));
    // Отсылка периодического сообщения.auto timer_id = env.schedule_timer(
       std::make_unique<my_message>(...),
       mbox, std::chrono::seconds(2), std::chrono::seconds(5));
    // Отсылка синхронного запроса с ожидание ответа в течении 10 секунд.auto reply = mbox->get_one<std::string>()
       .wait_for(std::chrono::seconds(10))
       .sync_get(std::make_unique<my_message>(...)); 
    

    In addition, the format of message handlers in SObjectizer to version 5.5 has evolved. If initially in SObjectizer-5 all handlers should have the format:

    voidevt_handler(const so_5::event_data_t<Msg> & cmd);

    then over time some more were added to the allowed formats:

    // Для случая, когда Msg -- это сообщение, а не сигнал.ret_value evt_handler(const Msg & msg);
    ret_value evt_handler(Msg msg);
    // Для случая, когда обработчик вешается на сигнал.ret_value evt_handler();
    

    New handler formats have become widely used, because constantly writing “const so_5 :: event_data_t <Msg> &” is something else a pleasure. But, on the other hand, simpler formats were not friendly to template agents. For example:

    template<typename Msg_To_Process>
    classmy_actor :public so_5::agent_t {
       voidon_receive(const Msg_To_Process & msg){ // Oops!
          ...
       }
    };

    Such a template agent will only work if Msg_To_Process is a message type, not a signal.

    What happened?


    In the 5.5 branch, the family of send-functions appeared and significantly evolved. To do this, it was necessary, first, to have at its disposal compilers with support for the variadic templates. And, secondly, to accumulate sufficient experience, both with variadic templates in general, and with the first versions of send-functions. Moreover, in different contexts: in ordinary agents, in ad-hoc-agents, and in agents that are implemented by template classes, and outside of agents in general. Including when using send-functions with mchain (they will be discussed below).

    In addition to the send functions, the request_future / request_value functions also appeared, which are intended for synchronous interaction between agents.

    As a result, sending messages now looks like this:

    // Отсылка обычного сообщения.
    so_5::send<my_message>(mbox, ...);
    // Отсылка отложенного сообщения.
    so_5::send_delayed<my_message>(env, mbox, std::chrono::seconds(2), ...);
    // Отсылка периодического сообщения.auto timer_id = so_5::send_periodic<my_message>(
       env, mbox, std::chrono::seconds(2), std::chrono::seconds(5), ...);
    // Отсылка синхронного запроса с ожидание ответа в течении 10 секунд.auto reply =so_5::request_value<std::string, my_message>(mbox, std::chrono::seconds(10), ...);

    Added another possible format for message handlers. Moreover, it is this format that will be left in the next major releases of SObjectizer as the main one (and, possibly, the only one). This is the following format:

    ret_type evt_handler(so_5::mhood_t<Msg> cmd);

    Where Msg can be both a message type and a signal type.

    Such a format not only erases the line between agents in the form of ordinary classes and agents in the form of template classes. But it also makes it easier to send the message / signal (thanks to the send function family):

    void my_agent::on_msg(mhood_t<Some_Msg> cmd) {
       ... // Какие-то собственные действия.// Делегируем обработку этого же сообщения другому агенту.
       so_5::send(another_agent, std::move(cmd));
    }

    What impact did it have?


    The appearance of send-functions and message handlers that receive mhood_t <Msg> can be said to fundamentally change the code in which messages are sent and processed. This is exactly the case when it only remains to regret that at the very beginning of work on SObjectizer-5 we did not have any compilers with support for the variadic templates, nor any experience of using them. The family of send-functions and mhood_t should have been from the very beginning. But the story turned out the way it did ...

    Support for custom message types


    What happened?


    Initially, all sent messages were supposed to be the heir classes of the so_5 :: message_t class. For example:

    structmy_message :public so_5::message_t {
       ... // Атрибуты my_message.
       my_message(...) : ... {...} // Конструктор для my_message.
    };

    While only we ourselves used the fifth SObjectizer, this did not raise any questions. Well, so and so.

    But as soon as third-party users began to be interested in SObjectizer, we immediately faced a regularly recurring question: “Do I have to inherit the message from so_5 :: message_t?” This issue was especially relevant in situations where it was necessary to send objects types that the user could not influence at all. Let's say a user uses SObjectizer and some other external library. And in this external library there is some type M whose objects the user would like to send as messages. Well, how in such conditions to make friends type M and so_5 :: message_t? Only additional wrappers that the user had to write manually.

    What happened?


    We added the ability to send messages to SObjectizer-5.5, even if the message type is not inherited from so_5 :: message_t. Those. Now the user can easily write:

    so_5::send<std::string>(mbox, "Hello, World!");

    So_5 :: message_t still remains under the hood, send () simply understands that std :: string is not inherited from so_5 :: message_t and inside not a simple std :: string, but a special inheritor from so_5 :: message_t, inside of which the user std :: string is already located.

    Similar template magic is applied when subscribing. When the SObjectizer sees a message handler of the form:

    voidevt_handler(mhood_t<std::string> cmd){...}

    then SObjectizer understands that a special message will actually come with a std :: string object inside. And what you need to call the handler with the transfer to it of a link to std :: string from this special message.

    What impact did it have?


    Using SObjectizer has become easier, especially when you need to send not only objects of your own types as messages, but also objects of types from external libraries. Several people even took the time to say a special thank you for this feature.

    Mutable messages


    What happened?


    Initially, only the 1: N interaction model was used in SObjectizer-5. Those. A sent message could have more than one recipient (and there could be more than one). Even if the agents needed to interact in 1: 1 mode, they still communicated through a multi-producer / multi-consumer mailbox. Those. in 1: N mode, just N in this case was strictly one.

    In circumstances where a message can be received by more than one recipient agent, the sent messages must be immutable. That is why message handlers had the following formats:

    // Доступ к сообщению только через константную ссылку.ret_type evt_handler(constevent_data_t<Msg> & cmd);
    // Доступ к сообщению только через константную ссылку.ret_type evt_handler(const Msg & msg);
    // Доступ к копии сообщения.// Модификация этой копии не сказывается на других копиях.ret_type evt_handler(Msg msg);

    In general, a simple and understandable approach. However, it is not very convenient when agents need to communicate with each other in 1: 1 mode and, for example, transfer ownership of some data to each other. Let's say that such a simple message cannot be made if all messages are strictly immutable objects:

    structprocess_image :public so_5::message_t {
       std::unique_ptr<gif_image> image_;
       process_image(std::unique_ptr<gif_image> image) : image_{std::move(image)) {}
    };

    More precisely, it would be possible to send such a message. But having received it as a constant object, removing the contents of process_image :: image_ to yourself just wouldn't work. I would have to mark such an attribute as mutable. But then we would lose control of the compiler in the case when the process_image for some reason is sent in 1: N mode.

    What happened?


    In SObjectizer 5.5, the ability to send and receive mutable messages was added. In this case, the user must in a special way mark the message both when sending and subscribing to it.

    For example:

    // Отсылаем обычное иммутабельное сообщение.
    so_5::send<my_message>(mbox, ...);
    // Отсылаем мутабельное сообщение типа my_message.
    so_5::send<so_5::mutable_msg<my_message>>(mbox, ...);
    ...
    // Обработчик для обычного иммутабельного сообщения.void my_agent::on_some_event(mhood_t<my_message> cmd) {...}
    // Обработчик для мутабельного сообщения типа my_message.void my_agent::on_another_event(mhood_t<so_5::mutable_msg<my_message>> cmd) {...}
    

    For SObjectizer, my_message and mutable_msg <my_message> are two different types of messages.

    When the send function sees that it is asked to send a mutable message, the send function checks and in which mailbox they are trying to send the message. If this is a multi-consumer box, then sending is not performed, and an exception is thrown with the corresponding error code. Those. SObjectizer guarantees that a mutable message can only be used when interacting in 1: 1 mode (via single-consumer boxes or mchains, which are a type of single-consumer boxes). To ensure this guarantee, by the way, SObjectizer prohibits sending mutable messages in the form of periodic messages.

    What impact did it have?


    With mutable messages it turned out unexpectedly. We added them to SObjectizer as a result of the discussion on the sidelines of a report on SObjectizer in C ++ Russia-2017 . With the feeling of “well, just asking, it means someone needs, so it’s worth a try.” Well, did without much hope for widespread demand. Although it took a very long time to “smoke bamboo” before it came up with how to add mutable messages to SO-5.5 without breaking compatibility.

    But when the mutable messages appeared in SObjectizer, it turned out that there were not so many applications for them. And that mutable messages are used surprisingly often (mention of this can be found in the second part of the story about the Shrimp demo project). So, in practice, this feature turned out to be more than useful, since it allows you to solve problems that, without the support of mutable messages at the SObjectizer level, did not have a normal solution.

    Agents in the form of hierarchical finite automata


    What happened?


    The agents in SObjectizer were originally state machines. Agents had to explicitly describe states and make subscriptions to messages in specific states.
    For example:

    classworker :public so_5::agent_t {
       state_t st_free{this, "free"};
       state_t st_bufy{this, "busy"};
    ...
       voidso_define_agent() override {
          // Делаем подписку для состояния st_free.
          so_subscribe(mbox).in(st_free).event(...);
          // Делаем подписку для состояния st_busy.
          so_subscribe(mbox).in(st_busy).event(...);
          ...
       }
    };

    But they were simple finite automata. States could not be nested in each other. There was no support for entry and exit exit handlers. There were no restrictions on the time spent in the state.

    Even such limited support for state machines was convenient and we used it for years. But at one point, we wanted more.

    What happened?


    SObjectizer adds support for hierarchical finite automata.

    Now states can be nested into each other. Event handlers from parent states are automatically “inherited” by child states.

    Handlers for entering and exiting state are supported.

    It is possible to set a limit on the time the agent is in the state.

    It is possible to store history for the state.

    In order not to be unfounded, here is an example of an agent that is not a complex hierarchical state machine (the code from the standard example of blinking_led):

    classblinking_ledfinal :public so_5::agent_t {
       state_t off{ this }, blinking{ this },
          blink_on{ initial_substate_of{ blinking } },
          blink_off{ substate_of{ blinking } };
    public :
       structturn_on_offfinal :public so_5::signal_t {};
       blinking_led( context_t ctx ) : so_5::agent_t{ ctx } {
          this >>= off;
          off.just_switch_to< turn_on_off >( blinking );
          blinking.just_switch_to< turn_on_off >( off );
          blink_on
             .on_enter( []{ std::cout << "ON" << std::endl; } )
             .on_exit( []{ std::cout << "off" << std::endl; } )
             .time_limit( std::chrono::milliseconds{1250}, blink_off );
          blink_off
             .time_limit( std::chrono::milliseconds{750}, blink_on );
       }
    };

    All this we have already described in a separate article , there is no need to repeat.

    There is currently no support for orthogonal states. But this fact has two explanations. First, we tried to make this support and faced a number of difficulties, overcoming of which seemed too costly to us. And, secondly, no one has asked for orthogonal states yet. When asked, then back to this topic.

    What impact did it have?


    There is a feeling that is very serious (although we are, of course, subjective and biased). Indeed, it is one thing when you encounter complex finite automata in the subject domain and you begin to look for workarounds, to simplify something, to spend additional forces on something. And it is quite another thing when you can display objects from your applied task into your C ++ code almost 1-in-1.

    In addition, judging by the questions that are asked, for example, the behavior of input / output handlers to / from the state, this functionality is used.

    mchains


    What happened?


    There was an interesting situation. SObjectizer was often used in such a way that only part of the application was written in SObjectizer. The rest of the code in the application could have nothing to do with the actors in general, nor with SObjectizer in particular. For example, a GUI application in which the SObjectizer is used for some background tasks, while the main work is performed on the main application thread.

    And in such cases, it turned out that from the non-SObjectizer parts into the SObjectizer parts, sending information is easier than ever: it is enough to call ordinary send functions. But with the dissemination of information in the opposite direction is not so simple. It seemed to us that this is not good and that we should have some convenient channels of communication between SObjectizer parts of the application and non-SObjectizer parts right out of the box.

    What happened?


    So in SObjectizer appeared message chains or, in more familiar notation, mchains.

    Mchain is such a specific version of a single-consumer mailbox to which messages are sent by ordinary send functions. But to extract messages from mchain, you do not need to create agents and sign them. There are two special functions that can be called even inside agents, even outside agents: receive () and select (). The first reads messages from only one channel, while the second can read messages from several channels at once:

    usingnamespace so_5;
    mchain_t ch1 = env.create_mchain(...);
    mchain_t ch2 = env.create_mchain(...);
    select( from_all().handle_n(3).empty_timeout(200ms),
      case_(ch1,
          [](mhood_t<first_message_type> msg) { ... },
          [](mhood_t<second_message_type> msg) { ... }),
      case_(ch2,
          [](mhood_t<third_message_type> msg ) { ... },
          [](mhood_t<some_signal_type>){...},
          ... ));

    We have already told about mchains here several times: in August 2017 and in May 2018 . Therefore, we will not dwell on the topic of how the work with mchains looks.

    What impact did it have?


    After the appearance of mchains in SObjectizer-5.5, it turned out that SObjectizer, in fact, became an even less “actor” framework than it was before. To support Actor Model and Pub / Sub, SObjectizer also added support for communicating sequential processes (CSP). Mchains allow developing quite complex multi-threaded applications on the SObjectizer without actors at all. And for some tasks it is more than convenient. What we ourselves use from time to time.

    Message limits mechanism


    What happened?


    One of the most serious shortcomings of the Model Actors is a predisposition to the occurrence of overloads. It is very easy to be in a situation where the sending actor sends messages to the receiving actor at a higher rate than the receiving actor can process messages.

    As a rule, sending messages in actor frameworks is a non-blocking operation. Therefore, in the event of the “smart-producer and brake-consumer” pair, the queue of the recipient actor will increase while there remains at least some free memory.

    The main difficulty of this problem is that a good overload protection mechanism should be tailored to the application and features of the subject area. For example, to understand which messages can be duplicated (and, therefore, be able to safely discard duplicates). To understand which messages cannot be thrown away in any way. Who can stop and how much, and who generally can not. Etc.

    Another difficulty is that it is not always necessary to have a good defense mechanism. At times, it is enough to have something primitive, but effective, available out of the box and easy to use. In order not to force the user to do his overload control where it is enough just to throw out the “extra” messages or to send these messages to some other agent.

    What happened?


    In order to use ready-made overload protection tools in simple scenarios, so-called were added to SObjectizer-5.5. message limits. This mechanism allows you to discard unnecessary messages, or forward them to other recipients, or even just to interrupt the application if the limits are exceeded. For example:

    classworker :public so_5::agent_t {
    public:
       worker(context_t ctx) : so_5::agent_t{ ctx
          // Если в очереди больше 100 сообщений handle_data,// то последующие сообщения должны пересылаться// другому агенту.
          + limit_then_redirect<handle_data>(100, [this]{ return another_worker_;})
          // Если в очереди больше 1 сообщения check_status,// то остальные можно выбросить и не обрабатывать.
          + limit_then_drop<check_status>(1)
          // Если в очереди больше 1 сообщения reconfigure,// то работу приложения нужно прерывать, т.к. последующий reconfigure// не может быть отослан пока не обработан предыдущий.
          + limit_then_abore<reconfigure>(1) }
       {...}
       ...
    };

    In more detail, this topic is disclosed in a separate article .

    What impact did it have?


    This is not to say that the emergence of message limits has become something that drastically changed SObjectizer, its principles of operation, or work with it. Rather, it can be compared with a reserve parachute, which is used only as a last resort. But when you have to use it, you are glad that it exists at all.

    Message delivery tracing mechanism


    What happened?


    SObjectizer-5 was a “black box” for developers. In which the message is sent and ... And it either comes to the recipient, or does not come.

    If the message does not reach the recipient, the user is faced with the need to go through an exciting quest in search of the cause. In most cases, the reasons are trivial: either the message was sent to the wrong mbox, or no subscription was made (for example, the user made a subscription in one agent state, but forgot to make it in another). But there may be more complex cases where the message is, say, rejected by the overload protection mechanism.

    The problem was that the message delivery mechanism was hidden deep inside the SObjectizer Run-Time and, therefore, it was difficult to even forward the message delivery process to the SObjectizer developers, let alone the users. Especially about novice users who have made the greatest number of such trivial errors.

    What happened?


    In SObjectizer-5.5, a special mechanism for tracing the message delivery process has been added, called message delivery tracing (or simply msg_tracing). This mechanism and its capabilities were described in more detail in a separate article .

    So now, if messages are lost during delivery, you can simply turn on msg_tracing and see why this happens.

    What impact did it have?


    Debugging applications written in SObjectizer has become much simpler and more enjoyable. Even for ourselves .

    The concept of env_infrastructure and single-threaded env_infrastructures


    What happened?


    We have always considered SObjectizer as a tool to simplify the development of multi-threaded code. Therefore, the first versions of SObjectizer-5 were written to work only in a multithreaded environment.

    This was expressed as the use of synchronization primitives inside SObjectizer to protect the internals of SObjectizer when working in a multi-threaded environment. This is also the case for creating several auxiliary work threads inside SObjectizer itself (for performing such important operations as timer maintenance and completion of deregistration of agent cooperations).

    Those. SObjectizer was created for multi-threaded programming and for use in a multi-threaded environment. And it quite suited us.

    However, as SObjectizer was used “in the wild”, situations were discovered where the task was difficult enough for the actors to be used in its solution. But, at the same time, all the work is possible and, moreover, it was necessary to do it on one single work flow.

    And we faced a very interesting problem: is it possible to teach SObjectizer to work on a single working thread?

    What happened?


    It turned out that you can.

    It cost us dearly, it took a lot of time and effort to come up with a solution. But the decision was invented.

    A concept such as environment infrastructure (or env_infrastructure in a slightly abbreviated form) was introduced. Env_infrastructure took over the task of managing the internal kitchen of SObjectizer. In particular, he solved such issues as servicing timers, performing registration operations and deregistration of cooperatives.

    For SObjectizer, several single-threaded env_infrastructures options were made. This made it possible to develop single-threaded applications on SObjectizer, within which there are normal agents that exchange normal messages with each other.

    We talked more about this functionality in a separate article..

    What impact did it have?


    Perhaps the most important thing that happened during the implementation of this feature is the breaking of our own templates. A look at the SObjectizer will never be the same. So many years, consider SObjectizer exclusively as a tool for developing multi-threaded code. And then again! And to find out that single-threaded code in SObjectizer can also be developed. Life is full of surprises.

    Run-time monitoring tools


    What happened?


    SObjectizer-5 was not just a black box for the message delivery mechanism. But there were also no means to find out how many agents are currently working inside the application, how many and which dispatchers are created, how many working threads they have involved, how many messages are waiting in dispatchers queues, etc.

    All this information is very useful for monitoring applications running 24/7. But also for debugging, at times I would also like to understand whether queues are growing or whether the number of agents is increasing or decreasing.

    Unfortunately, for the time being, the time we simply didn’t reach to add to SObjectizer the means for collecting and disseminating such information.

    What happened?


    At one point , tools for run-time monitoring of SObjectizer’s internals appeared in SObjectizer-5.5 . By default, run-time monitoring is disabled, but if it is enabled, then messages will be sent to a special mbox regularly, inside of which there will be information about the number of agents and cooperations, the number of timers, working threads owned by dispatchers (and there will already be information about the number of messages in the queues, the number of agents attached to these threads).

    Plus, over time, it became possible to additionally include the collection of information about how much time agents spend inside event handlers. This makes it possible to detect situations when an agent is too slow (or spends time on blocking calls).

    What impact did it have?


    In our practice, run-time monitoring is not often used. But when it is needed, then you realize its importance. Indeed, without such a mechanism, it is sometimes impossible (well, or very difficult) to deal with what and how [does] work.

    So this is a feature from the "can and do" category, but its presence, in our opinion, immediately translates the instrument into another weight category. Since making a prototype of an actor framework “on the knee” is not so difficult. Many have done this and many more will do it. But then provide your development with such a thing as run-time monitoring ... Not all knee drafts survive to this.

    And one more thing


    For four years, many innovations and changes have been found in SObjectizer-5.5, the description of which, even in brief form, will take too much space. Therefore, we denote part of them literally by one line. In random order, without any priorities.

    Support for the CMake build system has been added to SObjectizer 5.5.

    Now SObjectizer-5 can be built both as a dynamic and as a static library.

    SObjectizer-5.5 is now built and running under Android (both through the CrystaX NDK and through the latest Android NDK).

    There were private dispatchers. Now you can create and use dispatchers that no one but you can see.

    The delivery filters mechanism has been implemented. Now, when subscribing to messages from MPMC-mboxes, you can prohibit the delivery of messages whose content you are not interested in.

    The means of creating and registering cooperations are significantly simplified: introduce_coop / introduce_child_coop, make_agent / make_agent_with_binder methods and that's it.

    The concept of the factory of lock-objects appeared and now you can choose which lock-objects you need (based on mutex, spinlock, combined or some other).

    The wrapped_env_t class has appeared, and now you can launch SObjectizer in your application not only with so_5 :: launch ().

    The concept of stop_guard has appeared and now it is possible to influence the process of finishing the work of SObjectizer. For example, you can prohibit SObjectizer from stopping until some agents have completed their applied work.

    Now it is possible to intercept messages that were delivered to the agent, but were not processed by the agent (the so-called dead_letter_handlers).

    Now you can wrap messages in special "envelopes". Envelopes may carry additional information about the message and may perform some action when the message is delivered to the recipient.

    From 5.5.0 to 5.5.23 in numbers


    It is also interesting to look at the progress made in terms of the amount of code / tests / examples. Here is what the cloc utility tells us about the amount of SObjectizer-5.5.0 kernel code:

    -------------------------------------------------- -----------------------------
    Language files blank comment code
    -------------------------------------------------- -----------------------------
    C / C ++ Header 58 2119 5156 5762
    C ++ 39 1167 779 4759
    Ruby 2 30 2 75
    -------------------------------------------------- -----------------------------
    SUM: 99 3316 5937 10596
    -------------------------------------------------- -----------------------------
    

    And here is the same, but for v.5.5.23 (of which 1147 lines are optional-lite library code):
    -------------------------------------------------- -----------------------------
    Language files blank comment code
    -------------------------------------------------- -----------------------------
    C / C ++ Header 133 6279 22173 21068
    C ++ 53 2498 2760 10398
    CMake 2 29 0 177
    Ruby 4 53 2 129
    -------------------------------------------------- -----------------------------
    SUM: 192 8859 24935 31772
    -------------------------------------------------- -----------------------------
    

    The volume of tests for v.5.5.0:
    -------------------------------------------------- -----------------------------
    Language files blank comment code
    -------------------------------------------------- -----------------------------
    C ++ 84 2510 390 11540
    Ruby 162 496 0 1054
    C / C ++ Header 1 11 0 32
    -------------------------------------------------- -----------------------------
    SUM: 247 3017 390 12626
    -------------------------------------------------- -----------------------------
    

    Tests for v.5.5.23:
    -------------------------------------------------- -----------------------------
    Language files blank comment code
    -------------------------------------------------- -----------------------------
    C ++ 324 7345 1305 35231
    Ruby 675 2353 0 4671
    CMake 338 43 0 955
    C / C ++ Header 11 107 3 448
    -------------------------------------------------- -----------------------------
    SUM: 1348 9848 1308 41305
    

    Well, the examples for v.5.5.0:
    -------------------------------------------------- -----------------------------
    Language files blank comment code
    -------------------------------------------------- -----------------------------
    C ++ 27 765 463 3322
    Ruby 28 95 0 192
    -------------------------------------------------- -----------------------------
    SUM: 55,860,463,314
    

    They are the same, but for v.5.5.23:
    -------------------------------------------------- -----------------------------
    Language files blank comment code
    -------------------------------------------------- -----------------------------
    C ++ 67 2141 2061 9341
    Ruby 133 451 0 868
    CMake 67 93 0 595
    C / C ++ Header 1 12 11 32
    -------------------------------------------------- -----------------------------
    SUM: 268 2697 2072 10836
    

    Almost everywhere, an increase of almost three times.

    And the amount of documentation for SObjectizer has probably increased even more.

    Plans for the near (and not only) future


    The preliminary development plans for SObjectizer after the release of version 5.5.23 were described here about a month ago. Basically, they have not changed. But there was a feeling that version 5.6.0, the release of which is scheduled for the beginning of 2019, will need to be positioned as the beginning of the next stable branch of SObjectizer. With an eye to the fact that in the course of 2019, the SObjectizer will develop within the framework of the 5.6 branch without any significant breaking changes.

    This will enable those who are currently using SO-5.5 in their projects to gradually switch to SO-5.6 without fear that they will have to switch to SO-5.7.

    The version 5.7, in which we want to allow ourselves to deviate somewhere from the basic principles of SO-5.5 and SO-5.6, in 2019 will be regarded as experimental. With stabilization and release, if all goes well, already in the 2020th year.

    Conclusion


    In conclusion, I would like to thank everyone who helped us with the development of SObjectizer all this time. And separately I want to say thanks to all those who dared to try to work with SObjectizer. Your feedback has always been very useful to us.

    To those who have not yet used SObjectizer, we want to say: try. This is not as scary as it may seem.

    If you didn’t like something or didn’t have enough in SObjectizer - tell us. We always listen to constructive criticism. And, if it is in our power, we embody the wishes of users in life.

    Also popular now: