SO 5.8 InDepth Synchronous WrappedEnv Constructor - Stiffstream/sobjectizer GitHub Wiki
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.