{"id":401582,"date":"2024-06-29T15:57:26","date_gmt":"2024-06-29T15:57:26","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=401582"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-29T15:57:26","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=401582","title":{"rendered":"<span>Overview of recent changes in rotor (v0.10\u2026 v0.14)<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/a8\/sa\/xw\/a8saxwtazuvhttv9eeoutsst7z8.png\" alt=\"actor system\" data-src=\"https:\/\/habrastorage.org\/webt\/a8\/sa\/xw\/a8saxwtazuvhttv9eeoutsst7z8.png\"\/><\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> is a <a href=\"https:\/\/basiliscos.github.io\/cpp-rotor-docs\/md__home_b_development_cpp_cpp-rotor_docs_Rationale.html\" rel=\"nofollow noopener noreferrer\">non-intrusive<\/a> event loop friendly C++ actor micro framework with hierarchical supervising, similar to its elder brothers like <a href=\"https:\/\/actor-framework.org\/\" rel=\"nofollow noopener noreferrer\">caf<\/a> and <a href=\"https:\/\/github.com\/Stiffstream\/sobjectizer\" rel=\"nofollow noopener noreferrer\">sobjectizer<\/a>. There is a bulk of important changes since the last release announcement <a href=\"https:\/\/habr.com\/en\/company\/crazypanda\/blog\/522588\/\">v0.09<\/a><\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h2 id=\"generic-timers-interface-v010\">Generic timers interface (v0.10)<\/h2>\n<p>  <\/p>\n<p>Timers are ubiquitous generally in all actor frameworks, since they make programs more reliable. Until <code>v0.10<\/code> there was no way to spawn timer, and it required to access to the underlying event loop and use it&#8217;s API. It was inconvenient and breaking abstractions: in actor it must be accessed to supervisor, than cast it to event-loop specific type, then obtain event-loop and spawn timer. Upon timer triggering the <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a>-specific mechanics have to be launched, to let all messaging work. The timer cancellation is also event-loop specific and also required additional efforts, which &#171;pollute&#187; pure actor logic code.<\/p>\n<p>  <\/p>\n<p>Since <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> <code>v0.10<\/code> it is possible to have something like<\/p>\n<p>  <\/p>\n<pre><code class=\"cpp\">namespace r = rotor;  struct some_actor_t: r::actor_base_t {     void on_start() noexcept {         timer_request = start_timer(timeout, *this, &amp;some_actor_t::on_timer);     }      void on_timer(r::request_id_t, bool cancelled) noexcept {         ...;     }      void some_method() noexcept {         ...         cancel_timer(timer_id);     }      r::request_id_t timer_id; };<\/code><\/pre>\n<p>  <\/p>\n<p>It should be noted, that on shutdown finish moment all timers have to be canceled, otherwise it will be undefined behavior.<\/p>\n<p>  <\/p>\n<h2 id=\"request-cancellation-support-v010\">Request cancellation support (v0.10)<\/h2>\n<p>  <\/p>\n<p>In my opinion all messages in <a href=\"https:\/\/actor-framework.org\/\" rel=\"nofollow noopener noreferrer\">caf<\/a> have request-response semantics, while in <a href=\"https:\/\/github.com\/Stiffstream\/sobjectizer\" rel=\"nofollow noopener noreferrer\">sobjectizer<\/a> it has &#171;fire-and-forget&#187; messaging. <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> has both kinds of messaging, &#171;fire-and-forget&#187; is by default, the request-response is done on top of regular messaging.<\/p>\n<p>  <\/p>\n<p>Both <a href=\"https:\/\/actor-framework.org\/\" rel=\"nofollow noopener noreferrer\">caf<\/a> and <a href=\"https:\/\/github.com\/Stiffstream\/sobjectizer\" rel=\"nofollow noopener noreferrer\">sobjectizer<\/a> have per-actor <strong>managed messages queue<\/strong>, which means <em>the framework<\/em> does not deliver a message to actor until the previous message processing has been complete. Contrary, <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> has no managed queue, which means that the user actor has to create own queue and actor overload protection, if necessery. For immediately processed messages, like ping-pong, it does not matter, but for &#171;heavy&#187; requests which trigger I\/O, it does. For example, if an actor polls the remote side via HTTP requests, usually, it is undesirable to start a new request, when the previous one has not been finished. Again, the next message is not delivered, until the previous message is processed, does not matter, which message it is.<\/p>\n<p>  <\/p>\n<p>It also means, that with managed messages queues, the cancellation is, in general, impossible, because the cancellation message is still located in queue and it has no chance to be processed until the previous message is done.<\/p>\n<p>  <\/p>\n<p>In <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> you have to develop your own queue and store messages, and if cancel message arrives, your actor should search the request in queue, and then reply to it with canceled status. It should be noted, that only request-messages can be canceled, as they are referable.<\/p>\n<p>  <\/p>\n<pre><code class=\"cpp\">namespace r = rotor;  namespace payload { struct pong_t {}; struct ping_t {     using response_t = pong_t; }; } \/\/ namespace payload  namespace message { using ping_request_t = r::request_traits_t&lt;payload::ping_t>::request::message_t; using ping_response_t = r::request_traits_t&lt;payload::ping_t>::response::message_t; using ping_cancel_t = r::request_traits_t&lt;payload::ping_t>::cancel::message_t; } \/\/ namespace message  struct some_actor_t: r::actor_base_t {     using ping_ptr_t = r::intrusive_ptr_t&lt;message::ping_request_t>;      void on_ping(ping_request_t&amp; req) noexcept {         \/\/ just store request for further processing         ping_req.reset(&amp;req);     }      void on_cancel(ping_cancel_t&amp;) noexcept {         if (req) {             \/\/ make_error is v0.14 feature             make_response(*req, make_error(r::make_error_code(r::error_code_t::cancelled)));             req.reset();         }     }      ping_ptr_t ping_req; };<\/code><\/pre>\n<p>  <\/p>\n<p>It should be mentioned, that requests cancellation <em>can be done<\/em> <a href=\"https:\/\/github.com\/Stiffstream\/sobjectizer\" rel=\"nofollow noopener noreferrer\">sobjectizer<\/a>, but, first, you have to roll your own request\/response mechanism, and, second, your own queue in addition of sobjectizer&#8217;s queue, i.e. unneeded performance penalties. To my opinion, sobjectizer is mostly suited for <em>blocking operaions<\/em> processing, which, of course, cannot be cancelled in any framework.<\/p>\n<p>  <\/p>\n<h2 id=\"stdthread-backendsupervisor-v012\">std::thread backend\/supervisor (v0.12)<\/h2>\n<p>  <\/p>\n<p>This is long-awaited feature, which makes <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> to be <a href=\"https:\/\/github.com\/Stiffstream\/sobjectizer\" rel=\"nofollow noopener noreferrer\">sobjectizer<\/a>-like: in the case, when an actor has to perform <em>blocking operations<\/em> and it does not need any event loop. For example, in message handler there is a need to do CPU-intensive computation.<\/p>\n<p>  <\/p>\n<p>Obviously, during blocking operations there is no way to let timers trigger or other messages to be delivered. In other words, during blocking operations actor looses its reactivity, as it cannot react to incoming messages. To cope with that blocking operation it should be split into smaller iterative chunks, and when an actor has done proessing current chuck, it should send self a message, with the index of the next chunk etc., until all chunks are done. That will give an execution thread some breath and let <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> deliver other messages, execute timed out code etc. For example, instead of computing sha512 for the whole 1TB file, it can be split into computation of chunks for 1MB each, and make the thread reasonably well reactive. This is universal technique and can be applied to any actor framework.<\/p>\n<p>  <\/p>\n<p>Of course, the whole hierarchy of actors can be spawn on the <code>std::thread<\/code> backend, not just a single actor. Another moment, which should be emphasized, is that <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> has to be hinted, which message handlers are heavy\/blocking, to allow <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> update timers after them. This should be done during subscription phase, i.e.<\/p>\n<p>  <\/p>\n<pre><code class=\"cpp\">struct sha_actor_t : public r::actor_base_t {     ...     void configure(r::plugin::plugin_base_t &amp;plugin) noexcept override {         r::actor_base_t::configure(plugin);         plugin.with_casted&lt;r::plugin::starter_plugin_t>([&amp;](auto &amp;p) {             p.subscribe_actor(&amp;sha_actor_t::on_process)->tag_io(); \/\/ important         });     }<\/code><\/pre>\n<p>  <\/p>\n<p>The full source code of <code>sha512<\/code> reactive actor, which reacts to <code>CTRL+c<\/code>, is available via the <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\/blob\/master\/examples\/thread\/sha512.cpp\" rel=\"nofollow noopener noreferrer\">link<\/a>.<\/p>\n<p>  <\/p>\n<h2 id=\"actor-identity-v014\">Actor Identity (v0.14)<\/h2>\n<p>  <\/p>\n<p>The canonical way to identify an actor is to check its main address (in <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> it is possible to let actor have multiple addresses, similarly in <a href=\"https:\/\/github.com\/Stiffstream\/sobjectizer\" rel=\"nofollow noopener noreferrer\">sobjectizer<\/a> it is possible to subscribe to multiple <code>mboxes<\/code>). However, sometimes, it is desirable just to log something like actor name in supervisor, when the actor dies, i.e.<\/p>\n<p>  <\/p>\n<pre><code class=\"cpp\">struct my_supervisor_t : public r::supervisor_t {     void on_child_shutdown(actor_base_t *actor) noexcept override {         std::cout &lt;&lt; \"actor \" &lt;&lt; (void*) actor->get_address().get() &lt;&lt; \" died \\n\";     } }<\/code><\/pre>\n<p>  <\/p>\n<p>Actor address is dynamic, and it changes on every program launch, so this information is almost useless. To make it sense, actor should<br \/>  print its address somewhere, i.e. via overriding <code>on_start()<\/code> method. However, the solution was rather inconvenient. That&#8217;s why I decided to introduce <code>std::string identity<\/code> property into <code>actor_base_t<\/code> class. The actor identity can be set from constructor, or when <code>address_maker_plugin_t<\/code> is configured, i.e.:<\/p>\n<p>  <\/p>\n<pre><code class=\"cpp\">struct some_actor_t : public t::actor_baset_t {     void configure(r::plugin::plugin_base_t &amp;plugin) noexcept override {         plugin.with_casted&lt;r::plugin::address_maker_plugin_t>([&amp;](auto &amp;p) {             p.set_identity(\"my-actor-name\", false);         });         ...     } };<\/code><\/pre>\n<p>  <\/p>\n<p>Now it is possible to output actor identity in it&#8217;s supervisor:<\/p>\n<p>  <\/p>\n<pre><code class=\"cpp\">struct my_supervisor_t : public r::supervisor_t {     void on_child_shutdown(actor_base_t *actor) noexcept override {         std::cout &lt;&lt; actor->get_identity() &lt;&lt; \" died \\n\";     } }<\/code><\/pre>\n<p>  <\/p>\n<p>Sometimes, actors are unique within program and sometimes there are more then one instance of the same actor type. To distinguish between them, the address can be appended to the actor identity. That is what the second <code>bool<\/code> parameter does in <code>set_identity(name, append_addr)<\/code> method above.<\/p>\n<p>  <\/p>\n<p>By default actor identity is something like <code>actor 0x7fd918016d70<\/code> or <code>supervisor 0x7fd218016d70<\/code>. The identity feature is available since <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> <code>v0.14<\/code>.<\/p>\n<p>  <\/p>\n<h2 id=\"extended-error-instead-of-stderror_code-shutdown-reason-v014\">Extended Error instead of std::error_code, shutdown reason (v0.14)<\/h2>\n<p>  <\/p>\n<p>When something bad happens, and a proper response cannot be generated, then until <code>v0.14<\/code> response contained <code>std::error_code<\/code>. It serves quite well, but due to hierarchical nature of <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> it was not enough. Consider the case: a supervisor launches two child actors, and one of them fails to initialize. That will cascade supervisor and the second child-actor to do shutdown. However, from the context of the second actor and its supervisor it is not clear, why the second child was requested to shutdown, because <code>std::error_code<\/code> just does not contain enough information.<\/p>\n<p>  <\/p>\n<p>That&#8217;s why <code>rotor::extended_error_t<\/code> was introduced. It contains <code>std::error_code<\/code>, <code>std::string<\/code> context (which is usually actor identity), and the smart pointer to the next extended error, which caused the current one. Now, to the fail of hierarchy of actors can be represented by chain of errors, where shutdown of each actor can be retraced:<\/p>\n<p>  <\/p>\n<pre><code class=\"cpp\">struct my_supervisor_t : public r::supervisor_t {     void on_child_shutdown(actor_base_t *actor) noexcept override {         std::cout &lt;&lt; actor->get_identity() &lt;&lt; \" died, reason :: \" &lt;&lt; actor->get_shutdown_reason()->message();     } }<\/code><\/pre>\n<p>  <\/p>\n<p>It will output something like:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">actor-2 due to supervisor shutdown has been requested by supervisor &lt;- actor-1 due initialization failed<\/code><\/pre>\n<p>  <\/p>\n<p>Along with actor identity, this feature brings more diagnostic tools to developers, who use <a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a>.<\/p>\n<p><\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/543364\/\"> https:\/\/habr.com\/ru\/articles\/543364\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/a8\/sa\/xw\/a8saxwtazuvhttv9eeoutsst7z8.png\" alt=\"actor system\" data-src=\"https:\/\/habrastorage.org\/webt\/a8\/sa\/xw\/a8saxwtazuvhttv9eeoutsst7z8.png\"\/><\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/basiliscos\/cpp-rotor\" rel=\"nofollow noopener noreferrer\">rotor<\/a> is a <a href=\"https:\/\/basiliscos.github.io\/cpp-rotor-docs\/md__home_b_development_cpp_cpp-rotor_docs_Rationale.html\" rel=\"nofollow noopener noreferrer\">non-intrusive<\/a> event loop friendly C++ actor micro framework with hierarchical supervising, similar to its elder brothers like <a href=\"https:\/\/actor-framework.org\/\" rel=\"nofollow noopener noreferrer\">caf<\/a> and <a href=\"https:\/\/github.com\/Stiffstream\/sobjectizer\" rel=\"nofollow noopener noreferrer\">sobjectizer<\/a>. There is a bulk of important changes since the last release announcement <a href=\"https:\/\/habr.com\/en\/company\/crazypanda\/blog\/522588\/\">v0.09<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-401582","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/401582","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=401582"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/401582\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=401582"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=401582"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=401582"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}