2. SObjectizer is a small tool for simplification of development of
concurrent and event-driven applications in C++.
SObjectizer is responsible for:
● in-process message dispatching;
● providing working thread for message processing;
● SObjectizer Run-Time’s parameters tuning.
SObjectizer Team, May 2017
3. SObjectizer can be used for development of not only small
utilities but also of a large, distributed and highly loaded
software systems.
The whole application can be build upon SObjectizer.
Or SObjectizer can be used only as small part of an application
developed on the top of Qt, wxWidgets, ACE, Boost, etc.
SObjectizer Team, May 2017
4. SObjectizer allows to build a concurrent application as a set of
agent-objects which interact with each other only by means of
asynchronous messages.
SObjectizer was designed under the influence of several
approaches. And the Actor Model*
is one of them. But the
history of SObjectizer started long before this model became
widely known.
*
http://en.wikipedia.org/wiki/Actor_model SObjectizer Team, May 2017
5. SObjectizer’s main ideas and principles were formulated in the
middle of 1990s, during the development of SCADA Objectizer
project in Development Bureau of System Programming in
Homyel, Belarus (1996-2000).
SCADA Objectizer’s ideas were reused in the new project
SObjectizer-4 in 2002.
Evolution of SObjectizer-4 was stopped in 2010 and the
development of SObjectizer-5 started.
SObjectizer Team, May 2017
6. SObjectizer was an in-house project of JSC Intervale*
for the
long time.
SObjectizer was used in the development of the following
software systems:
● SMS/USSD traffic service;
● financial transaction handling;
● software parameters monitoring.
*
www.intervale.ru SObjectizer Team, May 2017
7. SObjectizer was published as an OpenSource project on
SourceForge under 3-clauses BSD-licence in 2006.
Since 2013 SObjectizer’s development completely moved to
SourceForge.
SObjectizer now is an independent project which a totally
separated from JSC Intervale.
SObjectizer Team, May 2017
8. SObjectizer-5 is developed using C++11 with broad usage of
C++11 standard library.
Supported platforms and compilers:
● Visual C++ 12.0/14.0/15.0, GCC 4.8-7.1, clang 3.9/4.0 on
Windows
● GCC 4.8-6.3, clang 3.4-4.0 on Linux, FreeBSD and MacOS;
● GCC 5.3, clang 3.7 on Android (via CrystaX NDK).
Support of other platforms is possible in the case when
SObjectizer’s developers will have an access to those platforms.
SObjectizer Team, May 2017
9. More than 20 releases of SObjectizer-5 were published since
May 2013.
The last stable version ‒ 5.5.19, was published in May 2017.
Version 5.5.19 is ~29 KLOC of SObjectizer core.
Plus ~33 KLOC of tests.
Plus ~10 KLOC of samples.
Plus SObjectizer’s core documentation*.
Plus articles and presentations** (some of them in Russian).
*
http://sourceforge.net/p/sobjectizer/wiki/Basics/
**
http://sourceforge.net/p/sobjectizer/wiki/Articles/ SObjectizer Team, May 2017
10. Using SObjectizer-5.5 a programmer must define
messages/signals and implement agents for processing them.
Agents are created by the programmer and are bound to
dispatchers. Dispatchers are responsible for message
dispatching and providing of working thread on which agents
handle messages.
SObjectizer Team, May 2017
11. SObjectizer has several ready-to-use dispatchers:
● one_thread. All agents work on a single working thread;
● active_obj. Every agent works on a single dedicated working thread;
● active_group. A single dedicated working thread is allocated for a group of
agents;
● thread_pool. A working thread is selected from thread pool. Agents can be
moved from one working thread to another. But an agent can’t work on two
threads at the same time;
● adv_thread_pool. A working thread is selected from thread pool. Agents can be
moved from one working thread to another. Moreover an agent can work on
several threads at the same time (if the agent’s event handlers are marked as
thread safe);
● prio_one_thread (strictly_ordered and quoted_round_robin). One working
thread and dispatching with respect to agent’s priorities;
● prio_dedicated_threads::one_per_prio. One working thread per a priority.
SObjectizer Team, May 2017
12. A programmer can create any number of dispatchers needed. This
allows to bind agents to different context in such a way that the impact
of one agent on another will be minimal. For example:
● one one_thread dispatcher for AMQP-client agent;
● one thread_pool dispatcher for handling requests from AMQP-queues;
● one active_obj dispatcher for DBMS-related agents;
● yet another active_obj dispatcher for agents whose work with HSMs
connected to the computer;
● and yet another thread_pool dispatcher for agents for managing all the
stuff described above.
SObjectizer Team, May 2017
13. A programmer can create as many agents as he needs.
Agent is a lightweight entity.
There could be thousands, millions and hundreds of millions of
agents.
Number of agents is limited only by amount of RAM and the
common sense of a programmer.
SObjectizer Team, May 2017
14. There is an SObjectizer example which demonstrates almost all
key features of SObjectizer-5.5:
● agent’s states,
● messages and signals,
● parent and children cooperations,
● dispatchers,
● delayed messages and so on...
SObjectizer Team, May 2017
15. Parent agent creates pinger and pointer agent pair. They do
message exchange for one second.
After one second the parent agent deregisters pinger and
ponger.
On finish pinger and ponger tell the parent how many
messages they received.
SObjectizer Team, May 2017
16. Example Code (signals and message definition):
#include <iostream>
#include <string>
// Main SObjectizer header file.
#include <so_5/all.hpp>
// Ping signal.
// Signal is a special kind of message without actual data.
// Sending of signals is like sending only atoms in Erlang.
struct ping : public so_5::signal_t {};
// Pong signal.
struct pong : public so_5::signal_t {};
// Message with result of pinger/ponger run.
// Unlike signal message must have actual data.
// Can be derived from so_5::message_t, but
// since v.5.5.9 it is not necessary.
struct run_result
{
std::string m_result;
};
SObjectizer Team, May 2017
17. Example Code (pinger agent, beginning):
// Pinger agent.
class pinger : public so_5::agent_t
{
public :
pinger( context_t ctx, so_5::mbox_t parent )
: so_5::agent_t{ ctx } // A working context.
, m_parent{ std::move(parent) } // Parent's mbox for result sending.
{}
// Ponger mbox will be available only after creation of pinger/ponger pair.
void set_ponger_mbox( const so_5::mbox_t & mbox ) { m_ponger = mbox; }
// This method is automatically called by SObjectizer during agent's registration procedure.
virtual void so_define_agent() override {
// Subscription for only one signal.
so_default_state().event< pong >( [this] {
++m_pongs;
so_5::send< ping >( m_ponger );
} );
}
SObjectizer Team, May 2017
18. Example Code (pinger agent, end):
// This method is automatically called by SObjectizer just after successful registration.
// Pinger uses this method to initiate message exchange.
virtual void so_evt_start() override {
// Sending signal by corresponding function.
so_5::send< ping >( m_ponger );
}
// This method is automatically called by SObjectizer just before deregistration of agent.
virtual void so_evt_finish() override {
// Sending result message by corresponding function.
so_5::send< run_result >(
// Receiver of the message.
m_parent,
// This will be forwarded to run_result's constructor.
"pongs: " + std::to_string( m_pongs ) );
}
private :
const so_5::mbox_t m_parent;
so_5::mbox_t m_ponger;
unsigned int m_pongs = 0;
};
SObjectizer Team, May 2017
19. Example Code (pоnger agent):
// Ponger agent is very similar to pinger. But it hasn't so_evt_start method
// because it will wait the first ping signal from the pinger.
// It also does subscription in the constructor (SObjectizer allows definition
// of subscriptions outside of so_define_agent method).
class ponger : public so_5::agent_t {
public :
ponger(context_t ctx, so_5::mbox_t parent)
: so_5::agent_t{ctx}, m_parent{std::move(parent)}
{
so_default_state().event< ping >( [this]{
++m_pings;
so_5::send< pong >( m_pinger );
} );
}
void set_pinger_mbox( const so_5::mbox_t & mbox ) { m_pinger = mbox; }
virtual void so_evt_finish() override {
so_5::send< run_result >( m_parent, "pings: " + std::to_string( m_pings ) );
}
private :
const so_5::mbox_t m_parent;
so_5::mbox_t m_pinger;
unsigned int m_pings = 0;
};
SObjectizer Team, May 2017
20. Example Code (parent agent, beginning):
// Parent agent.
// Creates pair of pinger/ponger agents, then limits their working time,
// then handles their run results.
class parent : public so_5::agent_t
{
private :
// Time limit signal.
struct stop : public so_5::signal_t {};
// Additional state for the agent.
// This state means that the first result from children agents
// has been received and that the parent expects the last one.
const so_5::state_t st_first_result_got{ this };
// Result's accumulator.
std::string m_results;
public :
parent( context_t ctx )
: so_5::agent_t{ ctx }
{}
SObjectizer Team, May 2017
21. Example Code (parent agent, continued-1):
virtual void so_define_agent() override {
// Arriving of time limit signal means that
// child cooperation must be deregistered.
so_default_state().event< stop >( [this] {
so_environment().deregister_coop(
// Name of cooperation for deregistration.
"pinger_ponger",
// The reason of deregistration,
// this value means that deregistration is caused
// by application logic.
so_5::dereg_reason::normal );
} );
// NOTE: type of message is automatically deduced from
// event handlers signatures.
// First result will be received in default state.
so_default_state().event( &parent::evt_first_result );
// But the second one will be received in next state.
st_first_result_got.event( &parent::evt_second_result );
}
SObjectizer Team, May 2017
22. Example Code (parent agent, continued-2):
virtual void so_evt_start() {
// Creation of child cooperation with pinger and ponger.
so_5::introduce_child_coop(
*this, // Parent of the new coop.
"pinger_ponger", // Name of child coop.
// Child cooperation will use active_obj dispatcher.
// So pinger and ponger will work on the different working threads.
so_5::disp::active_obj::create_private_disp( so_environment() )->binder(),
[&]( so_5::coop_t & coop )
{
// Filling the child cooperation.
auto a_pinger = coop.make_agent< pinger >(so_direct_mbox());
auto a_ponger = coop.make_agent< ponger >(so_direct_mbox());
a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() );
a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() );
} );
// Limit the pinger/ponger exchange time.
so_5::send_delayed< stop >( *this, std::chrono::seconds( 1 ) );
}
SObjectizer Team, May 2017
23. Example Code (parent agent, end):
private :
// Event handler for the first result.
void evt_first_result( const run_result & evt ) {
m_results = evt.m_result + "; ";
// State of the agent must be changed.
this >>= st_first_result_got;
}
// Event handler for the next (and last) result.
void evt_second_result( const run_result & evt ) {
m_results += evt.m_result;
// Show the results and finish work.
std::cout << m_results << std::endl;
// This is deregistration of the last live cooperation.
// SO Environment will finish its work.
so_deregister_agent_coop_normally();
}
};
SObjectizer Team, May 2017
24. Example Code (main function):
int main()
{
try
{
// Create SO Environment objects and run SO Run-Time inside it.
so_5::launch(
// SO Environment initialization routine.
[]( so_5::environment_t & env )
{
// We must have the cooperation with just one agent inside it.
// The name for the cooperation will be generated automatically.
env.introduce_coop( []( so_5::coop_t & coop ) {
// The single agent in the cooperation.
coop.make_agent< parent >();
} );
} );
}
catch( const std::exception & x )
{
std::cerr << "Exception: " << x.what() << std::endl;
}
}
SObjectizer Team, May 2017
25. Result:
pongs: 1005040; pings: 1005041
Just about 2M messages per seconds with the exchange by a
single message between two different threads (pinger and
ponger work on different threads).
Core i7 2.4GHz, 8GiB RAM, Win8.1 64-bit,
Visual C++ 2013 64-bit
SObjectizer Team, May 2017