SO 5.8 ByExample Cooperation Notifications - Stiffstream/sobjectizer GitHub Wiki

Note. It is better to read SO-5.8 Basics before this text.

Introduction

This example demonstrates the following SObjectizer mechanisms:

  • parent-child cooperation relationship;
  • cooperation notifications;
  • exception handling;
  • autoshutdown of SObjectizer Environment.

There is a parent agent which creates a child cooperation several times. Sometimes an agent from the child cooperation throws an exceptions. This leads to the deregistration of child cooperation.

The parent agent receives notification about its child cooperation registration and deregistration, and the event handler of this agent for cooperation deregistration notification creates a new child cooperation as a replacement for the deregistered cooperation.

This sample finishes after some number of iterations.

Sample code

#include <iostream>
#include <stdexcept>

// Main SObjectizer header file.
#include <so_5/all.hpp>

// A class of an agent which will throw an exception.
class a_child_t final :	public so_5::agent_t
{
	public :
		a_child_t( context_t ctx, bool should_throw )
			:	so_5::agent_t{ ctx }
			,	m_should_throw{ should_throw }
		{}

		void so_evt_start() override
		{
			if( m_should_throw )
				throw std::runtime_error( "A child agent failure!" );
		}

	private :
		const bool m_should_throw;
};

// A class of parent agent.
class a_parent_t final : public so_5::agent_t
{
	public :
		a_parent_t( context_t ctx )
			:	so_5::agent_t{ ctx }
			,	m_counter{ 0 }
			,	m_max_counter{ 3 }
		{}

		void so_define_agent() override
		{
			so_default_state()
				.event( &a_parent_t::evt_child_created )
				.event( &a_parent_t::evt_child_destroyed );
		}

		void so_evt_start() override
		{
			register_child_coop();
		}

	private :
		int m_counter;
		const int m_max_counter;

		void evt_child_created(
			const so_5::msg_coop_registered & evt )
		{
			std::cout << "coop_reg: " << evt.m_coop << std::endl;

			if( m_counter >= m_max_counter )
				so_deregister_agent_coop_normally();

			// Otherwise should wait for cooperation shutdown.
		}

		void evt_child_destroyed(
			const so_5::msg_coop_deregistered & evt )
		{
			std::cout << "coop_dereg: " << evt.m_coop
				<< ", reason: " << evt.m_reason.reason() << std::endl;

			++m_counter;
			register_child_coop();
		}

		void register_child_coop()
		{
			using namespace so_5;

			introduce_child_coop( *this,
				[this]( coop_t & coop )
				{
					coop.add_reg_notificator(
							make_coop_reg_notificator( so_direct_mbox() ) );
					coop.add_dereg_notificator(
							make_coop_dereg_notificator( so_direct_mbox() ) );
					coop.set_exception_reaction( deregister_coop_on_exception );

					coop.make_agent< a_child_t >( m_counter < m_max_counter );

					std::cout << "registering coop: " << coop.handle()
							<< std::endl;
				} );
		}
};

int main()
{
	try
	{
		so_5::launch(
			// The SObjectizer Environment initialization.
			[]( so_5::environment_t & env )
			{
				// Creating and registering a cooperation.
				env.register_agent_as_coop( env.make_agent< a_parent_t >() );
			} );
	}
	catch( const std::exception & ex )
	{
		std::cerr << "Error: " << ex.what() << std::endl;
		return 1;
	}

	return 0;
}

Sample in details

Child agent

The child agent is implemented as class a_child_t. It does very simple thing: checks the boolean flag m_should_throw in so_evt_start() and if it is true throws an exception. This boolean flag receives its value in the constructor of a_child_t.

Parent agent

The parent agent handles three events:

  • so_evt_start() -- the standard notification from SObjectizer about completeness of registration process. The parent agent initiates the creation of a child cooperation;
  • evt_child_created() -- a reaction to notification about child cooperation registration completeness. The parent agent prints information from notification. Then checks the iteration count. If no more iterations left then SObjectizer Environment is forced to shut down;
  • evt_child_destroyed() -- a reaction to notification about child cooperation deregistration. The parent agent initiates creation of a new child cooperation.

All the interesting stuff is in a_parent_t::register_child_coop(), and this method is described in details below.

Setting up parent-child relation

A parent-child relation for the new cooperation is set by so_5::introduce_child_coop() helper function.

When parent-child relation is set SObjectizer takes lifetime of child cooperation is dependence of lifetime of its parent cooperation. When parent cooperation deregistered SObjectizer automatically deregisters all children cooperations. And deregistration of parent cooperation completes only when deregistration for all children cooperations are done.

Because of this there is no call to deregister_coop for deregistering the child cooperation. The parent cooperation simply deregisters itself by the call:

so_deregister_agent_coop_normally();

And SObjectizer Environment takes the rest.

Autoshutdown of SObjectizer Environment

There is no call to environment_t::stop() in this sample but SObjectizer Environment automatically finishes when the parent cooperation deregisters itself. This is because of autoshutdown mode. By default, this mode is turned on and SObjectizer Environment finishes its work when the last working cooperation completely deregistered.

Cooperation notifications

There is a mechanism of cooperation notificators in SObjectizer. There are two types of notificators. The most important notificator is a cooperation deregistration notificator. This is a functional object with the following prototype:

void dereg_notificator(
  so_5::environment_t & env,
  const so_5::coop_handle_t & handle,
  const so_5::coop_dereg_reason_t & reason) noexcept;

A functional object with this prototype can be added to a list of cooperation deregistration notificators by the coop_t::add_dereg_notificator() method.

There is a standard implementation of deregistration notificator in SObjectizer. This implementation is created by so_5::make_coop_dereg_notificator() function and simply sends a message so_5::msg_coop_deregistered to the specified mbox.

Cooperation deregistration notification is important because it is hard to detect the precise moment when cooperation is completely deregistered. When environment_t::deregister_coop() is called the process of cooperation deregistration is just started. It could take some time. And the only way to know when the deregistration process is done is to use cooperation deregistration notification.

Deregistration notification could be used for different purposes. In this example, it is used as a signal for restarting of a child cooperation. So, the parent cooperation is working as a supervisor here.

As opposite to cooperation deregistration notificators there are cooperation registration notificators. They are functional objects with prototype:

void reg_notificator(
    so_5::environment_t & env,
    const so_5::coop_handle_t & handle) noexcept;

Cooperation registration notificators are added to cooperation object by coop_t::add_reg_notificator() method. And there is a standard implementation of registration notificator in SObjectizer, which sends the so_5::msg_coop_registered messages.

The standard implementation of cooperation registration and deregistration notificators are used in this example. The direct mbox for the parent agent is used as a receiver for messages msg_coop_registered and msg_coop_deregistered.

coop.add_reg_notificator(
	so_5::make_coop_reg_notificator( so_direct_mbox() ) );
coop.add_dereg_notificator(
	so_5::make_coop_dereg_notificator( so_direct_mbox() ) );

Dealing with exception from child agent

The agent a_child_t throws an exception. By default, unhandled exception leads to abnormal application termination (SObjectizer RunTime calls std::abort()). But not in this example.

This is because an exception reaction to the unhandled exception is specified for the child cooperation:

coop.set_exception_reaction(
	so_5::deregister_coop_on_exception );

This dictates SObjectizer not to call std::abort() and do the deregistration of cooperation instead. The cooperation is deregistered in usual way and the parent cooperation receives a notification about deregistration completeness.

For more information about dealing with unhandled exceptions see SO-5.8 InDepth Exceptions.

⚠️ **GitHub.com Fallback** ⚠️