This is the next part of the serie of presentations with deep introduction into features of SObjectizer-5.5.
This part is dedicated to such important feature as cooperations.
2. This is the next part of the serie of presentations with deep
introduction into features of SObjectizer-5.5.
This part is dedicated to such important feature as
cooperations. In particular:
● parent-child relationship;
● resource lifetime management;
● reg/dereg notificators.
SObjectizer Team, Jan 2016
4. Cooperation (or coop in short) is a way for binding several
tightly related agents into a whole entity.
Coop is registered in SObjectizer Environment in a
transactional manner: all agents from the cooperation must
be registered successfully or no one of them.
When a coop is being deregistered all its agents are
deregistered and destroyed at the same time.
SObjectizer Team, Jan 2016
5. There could be agents which require creation of additional
coop(s) for performing their work.
Let's imagine an agent which is responsible of receiving and
processing of some requests.
Payment requests, for example.
SObjectizer Team, Jan 2016
6. Processing of each payment request requires several
operations:
● checking payments parameters,
● checking the operation risks,
● checking the availability of funds
● and so on...
The request receiver could do those actions for every
payment by itself. But this will lead to very complicated logic
of request receiver.
SObjectizer Team, Jan 2016
7. There is much simpler approach: delegation of processing of
one request to a separate processor agent.
In that case the request receiver will only receive new
requests and create new coops with actual request
processors for each new request.
Receiver and processor agents will have more simple logic
and it is good.
But there will be a new question...
SObjectizer Team, Jan 2016
8. Who and how will control lifetime of all cooperations with
request processors?
SObjectizer Team, Jan 2016
9. Very simple view of that problem:
Someone could call deregister_coop() for request
receiver’s coop. As result all coops with request
processors must be deregistered too.
But how it could be done?
SObjectizer Team, Jan 2016
10. Such feature as child coop is coming into play here.
SObjectizer Team, Jan 2016
11. SObjectizer-5 allows to mark new coop as a child of any
existing coop.
In this case SObjectizer guarantees that all children coops
will be deregistered and destroyed before its parent coop.
SObjectizer Team, Jan 2016
12. It means that if we have, for example, a parent coop with
name “manager” and a child coop with name
“request_receiver” and do call:
env.deregister_coop( "manager", so_5::dereg_reason::normal );
Then SObjectizer-5 will deregister and destroy
“request_receiver” and only then “manager” coop will be
deregistered and destroyed.
SObjectizer Team, Jan 2016
13. Child coop could have its own child coops too.
It means that “request_receiver” coop could have any
number of child coops like “request_processor_1”,
“request_processor_2” and so on.
All of them will be automatically destroyed when top-level
parent coop “manager” is deregistered.
SObjectizer Team, Jan 2016
14. Parent-child relationship between coops allows to build
coops hierarchies: a top-level coop creates child coops,
those create its own child coops and so on.
If a top-level coop is being deregistered by some reason
then all its child coops (and children of children and so on)
will be deregistered too. A programmer could no takes care
about this.
SObjectizer Team, Jan 2016
15. There are several ways to make a child coop...
SObjectizer Team, Jan 2016
16. The first way, the oldest and verbose:
auto coop = env.create_coop( "request_processor_1" );
coop->set_parent_coop_name( "request_receiver" );
... // Filling the coop.
env.register_coop( std::move( coop ) );
Coop “request_processor_1” will be a child for coop
“request_receiver”.
SObjectizer Team, Jan 2016
17. The second one applicable if there is an agent from the
parent coop:
void parent_agent::make_new_coop()
{
auto coop = so_5::create_child_coop( *this, "request_processor_1" );
... // Filling the coop.
so_environment().register_coop( std::move( coop ) );
}
Name of the parent coop will be set automatically.
SObjectizer Team, Jan 2016
18. The third one is available since v.5.5.5:
void parent_agent::make_new_coop()
{
so_5::introduce_child_coop( *this, "request_processor_1",
[]( so_5::coop_t & coop ) {
... // Filling the coop.
} );
}
Name of the parent coop will be set automatically.
SObjectizer Team, Jan 2016
20. Sometimes it is necessary to manage lifetime of some
resources.
For example all agents from your coop should have a
reference to the same DB connection object.
If this connection object is allocated dynamically you can
pass a shared_ptr to every agent's constructor. Something
like:
SObjectizer Team, Jan 2016
22. In such case you make tight coupling between application
domain logic of your agents and DB connection lifetime
management.
SObjectizer Team, Jan 2016
23. What if this lifetime management need to be changed in the
future? What if connection object is controlled by someone
else and you have a simple reference to it (not shared_ptr)?
You will need to do some rewriting...
SObjectizer Team, Jan 2016
24. class first_agent : public so_5::agent_t { ...
public :
first_agent( context_t ctx, db_connection & conn, ... ); // The constructor has to be changed.
...
private :
db_connection & connection_; // Declaration of the member has to be changed.
};
class second_agent : public so_5::agent_t { ...
public :
second_agent( context_t ctx, db_connection & conn, ... ); // The constructor has to be changed.
...
private :
db_connection & connection_; // Declaration of the member has to be changed.
};
...
db_connection & conn = receive_connection_from_somewhere();
env.introduce_coop( [&conn]( so_5::coop_t & coop ) {
coop.make_agent< first_agent >( conn, ... );
coop.make_agent< second_agent >( conn, ... );
...
} );
SObjectizer Team, Jan 2016
25. SObjectizer-5 has a tool for decoupling resource lifetime
management from agent's domain-specific logic.
This is coop_t::take_under_control().
Method coop_t::take_under_control() allows to pass
dynamically allocated object under the control of the
cooperation.
The object placed under control will be deallocated only after
destroying all agents of the coop.
SObjectizer Team, Jan 2016
26. The behaviour of take_under_control() allows to use the
reference to controlled object even from agent's destructor...
SObjectizer Team, Jan 2016
27. class request_processor : public so_5::agent_t
{
public :
request_processor( context_t ctx, db_connection & connection );
~request_processor() {
if( !commited() )
// Assume that this reference is still valid.
connection_.commit();
}
...
private :
db_connection & connection_;
};
...
env.introduce_coop( []( so_5::coop_t & coop )
{
// Place DB connection under the control of the cooperation.
auto & connection = *coop.take_under_control(create_connection(...));
// Reference to DB connection will be valid even in requests_processor's destructor.
coop->make_agent< request_processor >( connection );
...
} );
SObjectizer Team, Jan 2016
29. class request_receiver : public so_5::agent_t
{
std::unique_ptr< db_connection > connection_;
...
public :
virtual void so_evt_start() override {
connection_ = make_db_connection(...);
...
}
...
private :
void evt_new_request( const request & evt ) {
// New request handler must be created.
so_5::introduce_child_coop( *this, [=]( so_5::coop_t & coop ) {
// Reference to DB connection will be valid even in requests_processor's destructor.
// It is because agents from child cooperation will be destroyed before any of
// agents from the parent cooperation.
coop->make_agent< request_processor >( connection );
...
} );
}
};
SObjectizer Team, Jan 2016
31. It is not easy to detect precise moments when a coop is
completely registered or completely deregistered.
The biggest problem is a detection of complete coop
deregistration.
It is because calling to environment_t::deregister_coop() just
initiates coop deregistration process. But the entire process
of deregistration could take a long time.
SObjectizer Team, Jan 2016
32. To simplify that there are such things as registration and
deregistration notificators.
Notificator could be bound to a coop and it will be called
when coop registration/deregistration process is finished.
SObjectizer Team, Jan 2016
33. The simplest reg/dereg notificators:
env.introduce_coop( []( so_5::coop_t & coop ) {
coop.add_reg_notificator(
[]( so_5::environment_t &, const std::string & name ) {
std::cout << "registered: " << name << std::endl;
} );
coop.add_dereg_notificator(
[]( so_5::environment_t &,
const std::string & name,
const so_5::coop_dereg_reason_t & ) {
std::cout << "deregistered: " << name << std::endl;
} );
...
} );
Name of coop will be printed to stdout on coop registration
and deregistration.
SObjectizer Team, Jan 2016
34. Usually reg/dereg notifications are used for sending
messages to some mboxes.
Because this scenario is widely used there are two ready-to-
use notificators in SObjectizer-5.5.
They are created by make_coop_reg_notificator() and
make_coop_dereg_notificator() functions...
SObjectizer Team, Jan 2016
35. The standard notificators:
auto notify_mbox = env.create_local_mbox();
env.introduce_coop( [&]( so_5::coop_t & coop ) {
// An instance of so_5::msg_coop_registered will be sent to notify_mbox
// when the cooperation is registered.
coop.add_reg_notificator( so_5::make_coop_reg_notificator( notify_mbox ) );
// An instance of so_5::msg_coop_deregistered will be sent to
// notify_mbox when the cooperation is deregistered.
coop.add_dereg_notificator( so_5::make_coop_dereg_notificator( notify_mbox ) );
...
} );
SObjectizer Team, Jan 2016
36. Coop dereg notificators can be used for implementation of
Erlang-like supervisors.
For example, request receiver could receive notification
about deregistration of child coops and restart them if they
fail...
SObjectizer Team, Jan 2016
37. Very simple way of controlling a child (1/2):
class request_receiver : public so_5::agent_t
{
public :
virtual void so_define_agent() override {
// msg_coop_deregistered must be handled.
so_subscribe_self().event( &request_receiver::evt_child_finished );
...
}
...
private :
void evt_new_request( const request & req ) {
auto child_name = store_request_info( req );
so_5::introduce_child_coop( child_name,
[&]( so_5::coop_t & coop ) {
... // Filling the coop with agents.
// Dereg notificator is necessary to receive info about child disappearance.
SObjectizer Team, Jan 2016
38. Very simple way of controlling a child (2/2):
// Standard notificator will be used.
coop.add_dereg_notificator(
// We want a message to request_receiver direct_mbox.
so_5::make_coop_dereg_notificator( so_direct_mbox() ) );
} );
...
}
void evt_child_finished( const so_5::msg_coop_deregistered & evt ) {
// If child cooperation failed its dereg reason will differ
// from the normal value.
if( so_5::dereg_reason::normal != evt.m_reason.reason() )
recreate_child_coop( evt.m_coop_name );
else
remove_request_info( evt.m_coop_name );
}
...
};
SObjectizer Team, Jan 2016
39. That is almost all what it needs to be known about agent
coops.
There are yet more issues like coop deregistration reasons
and exception reaction inheritance…
But those topics will be covered in the next parts of “Dive
into SObjectizer-5.5”
SObjectizer Team, Jan 2016