Dive into SObjectizer 5.5. Second part. States

Post on 16-Aug-2015

48 views 0 download

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

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