Skip to content

SO 5.8 InDepth Synchronous WrappedEnv Constructor

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

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.

Clone this wiki locally