dive into sobjectizer 5.5. second part. states
TRANSCRIPT
Dive into SObjectizer-5.5
SObjectizer Team, May 2015
Second Part: Agent’s States
The main features of SObjectizer-5.5, like agents, cooperations, messages/signal, mboxes, dispatchers and delayed delivery, were described in the previous part.
The next important feature is agent’s state.
SObjectizer Team, May 2015
Agent in SObjectizer is a finite-state machine.
The behaviour of an agent depends on the current state of the agent and the received message.
SObjectizer Team, May 2015
An agent can receive and process different messages in each state. In other words an agent can receive a message in one state but ignore it in another state.
Or, if an agent receives the same message in several states, it can handle the message differently in each state.
SObjectizer Team, May 2015
Where it can be useful?
SObjectizer Team, May 2015
Let’s imagine an agent which works as a handler of HTTP GET requests.
It receives messages with HTTP requests parameters and makes appropriate HTTP response.
There could be several states for request processor with different reaction on messages with HTTP requests...
SObjectizer Team, May 2015
There is a normal state in which the agent fully handles every request and sends a normal response back.
There could be a “pause” state in which the agent ignores all incoming requests (so the request initiator will receive HTTP 408 status)
And...
SObjectizer Team, May 2015
...there could be an “overloaded” state in which the agent will process requests differently: instead of making full processing the agent will quickly generate a response with HTTP 503 status and special text.
The agent could switch from normal state to “overloaded” when a peak of requests is detected. And the agent will switch back to its normal state after the peak of incoming requests is processed.
SObjectizer Team, May 2015
There is a special class for representing agent’s states: so_5::rt::state_t.
The definition of new state for an agent means creation of new instance of state_t.
SObjectizer Team, May 2015
States are usually represented as a constant members of agent’s class. Those members are usually initialized by so_make_state() method:
class connection_t : public so_5::rt::agent_t{ const so_5::rt::state_t st_not_connected = so_make_state(); const so_5::rt::state_t st_connecting = so_make_state(); const so_5::rt::state_t st_connected = so_make_state(); ...};
SObjectizer Team, May 2015
A state can have a textual name:
class connection_t : public so_5::rt::agent_t{ const so_5::rt::state_t st_not_connected = so_make_state("not_connected"); const so_5::rt::state_t st_connecting = so_make_state("connecting"); const so_5::rt::state_t st_connected = so_make_state("connected"); ...};
It could be useful for debugging and logging.
SObjectizer Team, May 2015
There are several ways of changing agent’s state:// Very old and basic way.so_change_state( st_connecting );
// More modern and short way.st_connecting.activate();
// Yet more modern way.this >>= st_connecting;
The current state can be obtained via so_current_state() method:
if( st_connected == so_current_state() ) …
SObjectizer Team, May 2015
Every agent already has one state: the default one.
The default state can be accessed via so_default_state() method:
// Returning agent to the default state.this >>= so_default_state();
SObjectizer Team, May 2015
Even ad-hoc agents have the default state.
But it is the only one state they have.
Because there is no user-defined class for an ad-hoc agent then there is no possibility to define new states for ad-hoc agent. Thus there is no possibility to change state of ad-hoc agent.
SObjectizer Team, May 2015
The most important part of usage of agent’s states is subscription to a message with respect to a specific state...
SObjectizer Team, May 2015
The simple usage of so_subscribe() and so_subscribe_self() methods leads to subscription only for the default agent’s state.
It means that:
so_subscribe_self().event(…);
is the equivalent of:
so_subscribe_self().in( so_default_state() ).event(…);
SObjectizer Team, May 2015
To make subscription to a message for a specific state it is necessary to use in() method in a subscription chain:
so_subscribe_self().in( st_not_connected ).event(…);so_subscribe_self().in( st_connecting ).event(…);so_subscribe_self().in( st_connected ).event(…);
SObjectizer Team, May 2015
The in() methods can be chained if the same event handler is used in several states:
so_subscribe_self() .in( st_not_connected ) .in( st_connecting ) .event< get_status >( [] -> std::string { return "not connected"; } );
so_subscribe_self() .in( st_connected ) .event< get_status >( [] -> std::string { return "connected"; } );
SObjectizer Team, May 2015
There is another way to make subscription for a specific state:
st_not_connected.event(…);
st_connecting.event(…).event(…);
st_connected.event(…).event(…).event(…)….event(…);
SObjectizer Team, May 2015
Let’s see an example of usage of agent’s states.
The very simple implementation of well known task of Dining Philosophers will be used as demo.
SObjectizer Team, May 2015
The philosophers and forks will be represented by agents which interacts with each other by messages and signals.
We need the following messages/signals for interaction:
SObjectizer Team, May 2015
A philosopher will send msg_take message to a fork to get it. The message contains the philosopher’s mbox on which a reply will be sent:
struct msg_take : public so_5::rt::message_t{ const so_5::rt::mbox_t m_who;
msg_take( so_5::rt::mbox_t who ) : m_who( std::move(who) ) {}};
SObjectizer Team, May 2015
Signals msg_taken and msg_busy will be sent by a fork agent back to a philosopher agent:
struct msg_taken : public so_5::rt::signal_t {};struct msg_busy : public so_5::rt::signal_t {};
Signal msg_put will be sent by a philosopher agent when it doesn’t need the fork anymore:
struct msg_put : public so_5::rt::signal_t {};
SObjectizer Team, May 2015
A fork agent is the simplest one.
So, let’s start from definition of that agent…
SObjectizer Team, May 2015
A fork agent needs two states: free and taken by some philosopher:
class a_fork_t : public so_5::rt::agent_t{… private : const so_5::rt::state_t st_free = so_make_state( "free" ); const so_5::rt::state_t st_taken = so_make_state( "taken" );};
SObjectizer Team, May 2015
A fork agent must start its work in st_free state.
But its initial state is the default state.
So it is necessary to switch agent to st_free state in the beginning.
Usually it is done in so_define_agent() method.
SObjectizer Team, May 2015
Switching a fork agent to st_free state at the beginning of the agent definition:
class a_fork_t : public so_5::rt::agent_t{public :… virtual void so_define_agent() override { this >>= st_free;…
SObjectizer Team, May 2015
A fork agent handles two messages: msg_take and msg_put. But an agent does it differently in each state.
On msg_take in st_free the agent must be switched to st_taken and msg_taken must be sent back.
Msg_put should not be received in st_free so there will not be a subscription to msg_put in st_free.
SObjectizer Team, May 2015
Subscription for st_free state:
class a_fork_t : public so_5::rt::agent_t{… virtual void so_define_agent() override { … st_free.event( [=]( const msg_take & evt ) { this >>= st_taken; so_5::send< msg_taken >( evt.m_who ); } );…
SObjectizer Team, May 2015
Two messages must be processed in st_taken state: msg_take and msg_put.
On msg_take the agent must reply with msg_busy.
On msg_put the agent must switch its state to st_free.
SObjectizer Team, May 2015
Subscription for st_taken state:
class a_fork_t : public so_5::rt::agent_t{… virtual void so_define_agent() override { … st_taken.event( []( const msg_take & evt ) { so_5::send< msg_busy >( evt.m_who ); } ) .event< msg_put >( [=] { this >>= st_free; } );…
SObjectizer Team, May 2015
The full a_fork_t code (1/2):class a_fork_t : public so_5::rt::agent_t{public : a_fork_t( so_5::rt::environment_t & env ) : so_5::rt::agent_t( env ) {}
virtual void so_define_agent() override { this >>= st_free;
st_free.event( [=]( const msg_take & evt ) { this >>= st_taken; so_5::send< msg_taken >( evt.m_who ); } );
SObjectizer Team, May 2015
The full a_fork_t code (2/2): st_taken.event( []( const msg_take & evt ) { so_5::send< msg_busy >( evt.m_who ); } ) .event< msg_put >( [=] { this >>= st_free; } ); }
private : const so_5::rt::state_t st_free = so_make_state( "free" ); const so_5::rt::state_t st_taken = so_make_state( "taken" );};
SObjectizer Team, May 2015
A philosopher agent is more complex and requires more states:● initial state st_thinking from which agent switches to
st_wait_left;
● st_wait_left for waiting answer from the left fork. Then agent switches to st_wait_right or st_thinking;
● st_wait_right for waiting answer from the right fork. Then agent switches to st_eating or st_thinking.
● st_eating from which agent switches to st_thinking.
SObjectizer Team, May 2015
The graphical representation:
SObjectizer Team, May 2015
st_thinkingenter: send delayed msg_stop_thinking
st_wait_leftenter: send msg_take to
the left fork
st_wait_rightenter: send msg_take to
the right fork
st_eatingenter: send delayed
msg_stop_eating
msg_stop_thinking
msg_busy
msg_takenmsg_busy
msg_taken
msg_stop_eating
Definition of a philosopher agent states:class a_philosopher_t : public so_5::rt::agent_t{…private : const so_5::rt::state_t st_thinking = so_make_state( "thinking" ); const so_5::rt::state_t st_wait_left = so_make_state( "wait_left" ); const so_5::rt::state_t st_wait_right = so_make_state( "wait_right" ); const so_5::rt::state_t st_eating = so_make_state( "eating" );…};
SObjectizer Team, May 2015
Two additional signals are needed for a philosopher agent. They are defined as private types because no one except philosopher can use them:class a_philosopher_t : public so_5::rt::agent_t{ struct msg_stop_thinking : public so_5::rt::signal_t {}; struct msg_stop_eating : public so_5::rt::signal_t {};…
SObjectizer Team, May 2015
Now the subscriptions of a philosopher agent can be defined...class a_philosopher_t : public so_5::rt::agent_t{… virtual void so_define_agent() override {
SObjectizer Team, May 2015
The behaviour in st_thinking state is very simple.
Once msg_stop_thinking is received the agent must switch to st_wait_left and send msg_take to the left fork:st_thinking.event< msg_stop_thinking >( [=] { this >>= st_wait_left; so_5::send< msg_take >( m_left_fork, so_direct_mbox() ); } );
SObjectizer Team, May 2015
A philosopher must react on two messages in st_wait_left state:
● on msg_taken it must switch to st_wait_right and ask for the right fork;
● on msg_busy it must return to st_thinking.
st_wait_left.event< msg_taken >( [=] { this >>= st_wait_right; so_5::send< msg_take >( m_right_fork, so_direct_mbox() ); } ) .event< msg_busy >( [=] { think(); } );
SObjectizer Team, May 2015
A very similar logic is for st_wait_right state. But on msg_busy the left fork must be freed. And msg_taken means switching to st_eating.
st_wait_right.event< msg_taken >( [=] { this >>= st_eating; so_5::send_delayed_to_agent< msg_stop_eating >( *this, pause() ); } ) .event< msg_busy >( [=] { so_5::send< msg_put >( m_left_fork ); think(); } );
SObjectizer Team, May 2015
Only one signal must be handled in st_eating state: msg_stop_eating. Both forks must be freed and agent must be switched to st_thinking.
st_eating.event< msg_stop_eating >( [=] { so_5::send< msg_put >( m_right_fork ); so_5::send< msg_put >( m_left_fork ); think(); } );
SObjectizer Team, May 2015
In contradiction to a_fork_t there is no switching from the default state in the philosopher’s so_define_agent().
But how a philosopher starts its work in st_thinking?
SObjectizer Team, May 2015
It is done in so_evt_start() by calling think() helper method:class a_philosopher_t : public so_5::rt::agent_t{… virtual void so_evt_start() override { think(); }
private : void think() { this >>= st_thinking; so_5::send_delayed_to_agent< msg_stop_thinking >( *this, pause() ); }…
SObjectizer Team, May 2015
The full a_philosopher_t code (1/5):class a_philosopher_t : public so_5::rt::agent_t{ struct msg_stop_thinking : public so_5::rt::signal_t {}; struct msg_stop_eating : public so_5::rt::signal_t {};
public : a_philosopher_t( so_5::rt::environment_t & env, so_5::rt::mbox_t left_fork, so_5::rt::mbox_t right_fork ) : so_5::rt::agent_t( env ) , m_left_fork( std::move( left_fork ) ) , m_right_fork( std::move( right_fork ) ) {}
SObjectizer Team, May 2015
The full a_philosopher_t code (2/5): virtual void so_define_agent() override { st_thinking.event< msg_stop_thinking >( [=] { this >>= st_wait_left; so_5::send< msg_take >( m_left_fork, so_direct_mbox() ); } );
st_wait_left.event< msg_taken >( [=] { this >>= st_wait_right; so_5::send< msg_take >( m_right_fork, so_direct_mbox() ); } ) .event< msg_busy >( [=] { think(); } );
SObjectizer Team, May 2015
The full a_philosopher_t code (3/5): st_wait_right.event< msg_taken >( [=] { this >>= st_eating; so_5::send_delayed_to_agent< msg_stop_eating >( *this, pause() ); } ) .event< msg_busy >( [=] { so_5::send< msg_put >( m_left_fork ); think(); } );
st_eating.event< msg_stop_eating >( [=] { so_5::send< msg_put >( m_right_fork ); so_5::send< msg_put >( m_left_fork ); think(); } ); }
SObjectizer Team, May 2015
The full a_philosopher_t code (4/5): virtual void so_evt_start() override { think(); }
private : const so_5::rt::state_t st_thinking = so_make_state( "thinking" ); const so_5::rt::state_t st_wait_left = so_make_state( "wait_left" ); const so_5::rt::state_t st_wait_right = so_make_state( "wait_right" ); const so_5::rt::state_t st_eating = so_make_state( "eating" );
const so_5::rt::mbox_t m_left_fork; const so_5::rt::mbox_t m_right_fork;
SObjectizer Team, May 2015
The full a_philosopher_t code (5/5): void think() { this >>= st_thinking; so_5::send_delayed_to_agent< msg_stop_thinking >( *this, pause() ); }
static std::chrono::milliseconds pause() { return std::chrono::milliseconds( 250 + (std::rand() % 250) ); }};
SObjectizer Team, May 2015
It was a short description of such important part of SObjectizer-5.5 as agent’s states.
But there are two important questions:
1. Are agent’s states really useful?2. And how often they are used in solving real-world
problems?
SObjectizer Team, May 2015
There is one answer for both questions:
Simple agents almost always use the only default state. But the complex ones use agent’s states heavily. And for big agents with sophisticated behaviour states are very useful.
There are no other features which could replace agent’s states in such cases.
SObjectizer Team, May 2015
Additional Information:
Project’s home: http://sourceforge.net/projects/sobjectizer
Documentation: http://sourceforge.net/p/sobjectizer/wiki/
Forum: http://sourceforge.net/p/sobjectizer/discussion/
Google-group: https://groups.google.com/forum/#!forum/sobjectizer
GitHub mirror: https://github.com/masterspline/SObjectizer