Skip to content

SO 5.8 InDepth Synchronous WrappedEnv Constructor

Yauheni Akhotnikau edited this page Mar 20, 2024 · 2 revisions

Synchronous mode for wrapped_env_t constructor

The wrapped_env_t constructor has several overloads that receive init-function. Until v.5.8.2 the wrapped_env_t executed this init-function in asynchronous mode. The asynchronous mode means:

  • a separate thread is started,
  • the init-function is called on it,
  • and the constructor doesn't wait while the init-function completes.

The main consequence is that the constructor may complete before execution of the init-function on the separate thread. Sometime it may lead to errors like that:

so_5::mbox_t target_mbox;
so_5::wrapped_env_t sobjectizer{
  [&](so_5::environment_t & env) {
    env.introduce_coop([&](so_5::coop_t & coop) {
        target_mbox = coop.make_agent<my_agent>(...)->so_direct_mbox(); // (1)
      });
  }
};
so_5::send<my_message>(target_mbox, ...); // (2)

The code at point (1) may be run after the code at point (2). In that case an attempt to dereference a nullptr in the target_mbox occurs at the point (2).

It's easy to solve this problem by using something like that:

std::promise<so_5::mbox_t> target_mbox_promise;
so_5::wrapped_env_t sobjectizer{
  [&](so_5::environment_t & env) {
    env.introduce_coop([&](so_5::coop_t & coop) {
        target_mbox_promise.set_value( coop.make_agent<my_agent>(...)->so_direct_mbox() ); // (1)
      });
  }
};
so_5::mbox_t target_mbox = target_mbox_promise.get_future().get();
so_5::send<my_message>(target_mbox, ...); // (2)

But it requires additional efforts from a developer.

There is another consequence of asynchronous mode for init-function execution: it's being called on a separate thread and if init-function throws then there is no appropriate exception-handler on that thread.

It means that if init-function throws in the asynchronous mode then the exception thrown won't be handled and will terminate the whole application.

Because of the factors described above a synchronous mode is introduced in v.5.8.2.

The synchronous mode means that the constructor of the wrapped_env_t will wait for the completion of the init-function:

so_5::mbox_t target_mbox;
so_5::wrapped_env_t sobjectizer{
  so_5::wrapped_env_t::wait_init_completion,
  [&](so_5::environment_t & env) {
    env.introduce_coop([&](so_5::coop_t & coop) {
        target_mbox = coop.make_agent<my_agent>(...)->so_direct_mbox(); // (1)
      });
  }
};
so_5::send<my_message>(target_mbox, ...); // (2)

It's now guaranteed that code at point (1) completes before the execution of the code at point (2).

Another feature of the synchronous mode is rethrowing an exception thrown from init-function. For example:

class my_exception final : public std::runtime_error {...}
...
try {
  so_5::wrapped_env_t sobjectizer{
      so_5::wrapped_env_t::wait_init_completion,
      [](so_5::environment_t &) {
        throw my_exception{...};
      }
    };
}
catch(const my_exception & x) { // An exception from init-function will
                                // be caught here!
  ... // handling
}

It means that in the synchronous mode an exception is intercepted in the context of a separate thread (where the init-function is called) and then transferred to the thread where the instance of wrapped_env_t is created. Then this exception is rethrown from the constructor of the wrapped_env_t.

ATTENTION. Please note that the synchronous mode should be turned on explicitly by passing so_5::wrapped_env_t::wait_init_completion value as the first argument to a constructor of the wrapped_env_t. And the synchronous mode is supported only by constructors that accept an init-function.

A new type of subscription storage: flat-set based

SObjectizer supports several type of containers for holding agents subscriptions: one is based on unsorted vector, another uses std::map, yet another uses std::unordered_map. There is also a combined type: one container is for small number of subscriptions and another for a big number.

Version 5.8.2 adds another type: it uses a vector with ordered items (aka flat-set) and binary search for manipulation with subscriptions. This type of subscription storage may be more effective that map- or unordered_map-based storages in some cases.

The new type of subscription storage can be specified via a new so_5::flat_set_based_subscription_storage_factory() function.

There is also a new section in the Wiki that describes subscription storages.

A possibility to set "global" subscription storage factory

There is a new default_subscription_storage_factory() method in so_5::environment_params_t class. This method allows to specify a factory for subscription storage to be used by default:

so_5::launch( [](so_5::environment_t & env) {...},
  [](so_5::environment_params_t & params) {
    params.default_subscription_storage_factory(
      so_5::flat_set_based_subscription_storage_factory(32u) );
  } );

Optional name for an agent

Version 5.8.2 adds a possibility to set a name for an agent:

class my_agent final : public so_5::agent_t
{
public:
    my_agent(context_t ctx, std::string_view name)
        : so_5::agent_t{ ctx + name_for_agent(name) }
    {}
    ...
};

This name can be obtained via a new so_5::agent_t::so_agent_name() method.

There is also a new section in the Wiki that describes optional agent names in more details.

A couple of new methods for so_5::stats::prefix_t and so_5::stats::suffix_t

Methods as_string_view() have been added to so_5::stats::prefix_t and so_5::stats::suffix_t classes.

Clone this wiki locally