SObjectizer System Configurator (or sysconf) library helps to build applications out of several modules -- .dll files (or .so files on linux). Each module is intended to cover a part of the application logic. Once the module is loaded into sysconf based application (hence running SObjectizer Environment) it provides an opportunity to register agent cooperations designed to perform some type of a job or to add an extra layer(s) into SObjectizer Environment. The list of used modules, the order they are loaded, and parameters for starting agent cooperations or adding extra layers are defined by the configuration file called sysconf-script.
Sysconf-script consists of commands. Supported commands perform the following operations: load or unload module, register or deregister cooperation, add an extra layer, add a dispatcher of a particular type.
Sysconf comes with several default modules. These modules provide basic application infrastructure, like break flag handlers (Ctrl+C), initialization and starting of so_5_transport layer and mbapi_4 layer, initialization and starting of mbapi_4 Input/Output channels.
Typically sysconf is used in the following way:
Just a few basic sysconf concepts are necessary for a better understanding of how to use sysconf correctly.
In cases when a specific cooperation of agents must be running exclusively (like a singleton object). For example a break flag handler cooperation that listens to break signals (like CTRL+C) must be running as a single instance, application logger facility cooperation must be running as a single instance as well (so_log_2).
coop_handler
-- is a sysconf registration wrapper for such single instance cooperations.
A more common case when working with cooperations is to create several instances of some specific kind of cooperation. Each cooperation does the same type of job but with a different parameters. For example io channels for handling connections to a different servers are doing the same job but each connection is instantiated with its own params and works independently.
coop_factory
-- is a sysconf registration wrapper for such factory production cooperations.
In cases when agent logic relies on some extra layers (so_5_transport for example) it is needed to be able to add an extra layers to a running SObjectizer Environment thus making it possible to use such agents.
layer_handler
-- is a sysconf wrapper for adding extra layer to a running SObjectizer Environment.
sysconf-script -- is a specifically formatted file that contains sysconf commands. Each command performs on of the following operation:
In practice application is started with a regular sysconf loader that runs single sysconf script and thereby forms a concrete application. But the user still has an option to run another sysconf-scripts using sysconf layer API.
Sysconf includes several regular loaders -- executables that starts SObjectizer Environment and sysconf. After starting SObjectizer with sysconf sysconf-loader runs user specefied sysconf-script.
First of all user must implement agents that perform some task that user tries to solve.
Then the cooperation of these agents must be wrapped with registration wrapper (coop_handler or coop_factory). Having this registration items it is possible to create a sysconf module that includes this coop_handler or coop_factory. In general sysconf module can include a plenty of coop_handler or coop_factory but all of them must be provided with a unique name.
A simple example of coop_handler/coop_factory is a function with a special signature that creates cooperation and registers it in SObjectizer Environment.
Once the registration wrapper is ready a sysconf module can be defined. To define module sysconf includes a set of helpers macros. Definition must enumerate all coop_handler and coop_factory wrappers of the module.
Here is an example of a simple module definition:
so_sysconf_4::coop_info_t my_coop_handler( so_5::rt::so_environment_t & env, const std::string & coop_name, const std::string & cfg_file ) { env.register_agent_as_coop( coop_name(), new my_agent_t( env, load_config( cfg_file ) ) ); return so_sysconf_4::coop_info_t(); } SYSCONF_4_MODULE_DESCRIPTION_START_STD() SYSCONF_4_MODULE_DESCRIPTION_ADD_HANDLER_STD( so_sysconf_4::create_coop_handler( "my_coop", &my_coop_handler, so_sysconf_4::FATAL_ERROR_SHOULD_BE_INITIATED ) ) SYSCONF_4_MODULE_DESCRIPTION_FINISH_STD()
When sysconf gets a command to run sysconf-script it parses specified file and builds a sequence of commands and then executes them one by one.
When running load module command sysconf checks if this module has been already loaded. If the module is not already loaded then sysconf opens and dynamically links a specified .dll file (or .so file) and if it goes without errors then sysconf searches and calls a specific function that is expectes to be present in each sysconf module. Via this function sysconf receives module description object that introduces all coop_handler, coop_factory and layer_handler that come within this module.
Register coop command is intended to register cooperation of agents via coop_handler registration wrapper. When running a register coop command sysconf checks whether this coop_handler exists. If so then sysconf check if cooperation has been already registered via that coop_handler. If cooperation wasn’t previously registered then sysconf calls coop_handler wrapper interface in order to register cooperation. If cooperation was successfully registered sysconf increments reference counter for a module where coop_handler resides.
Make coop command is intended to register cooperation of agents via coop_factory registration wrapper. When running a make coop command sysconf checks whether this coop_factory exists. If so then sysconf calls coop_factory wrapper interface in order to register cooperation. If cooperation was successfully registered sysconf increments reference counter for a module where coop_factory resides.
Deregister coop command is intended to deregister cooperation that was previously registered via sysconf by coop_handler or coop_factory. When running a deregister coop command sysconf checks whether cooperation with specified name was registered via sysconf and if so then sysconf calls appropriate registration wrapper interface to deregister cooperation. When cooperation is completely deregistered sysconf decrements reference counter for a module where coop_handler or coop_factory resides.
Add extra layer command is intended to add an extra layer to a running SObjectizer Environment. When running a make coop command sysconf checks whether this layer_handler exists. And if so then sysconf tries to add an extra layer through calling layer_handler interface. If the layer was added successfully then sysconf increments reference counter for a module where layer_handler resides.
When running load module command sysconf checks the reference counter for that module. If the counter is not zero then there are still active cooperations (or layers) depending on this module. So it is incorrect to unload the module hence corrupting OS process because the code that is still running is in the module. In this case sysconf ignores the command. If the counter is zero then it is safe to unload the module. So in this case sysconf removes module description from its internal lists (forgets about all coop_handlers/coop_factories comming with this module) and then unloads specified .dll (.so) file.
In most cases cooperation needs some parameters to initialize agents. Sysconf solution for this is to put parameters into configuration file and pass the path name of that file to registration wrapper (coop_handler/coop_factory). Sysconf gets the path name from register coop or make coop command specified in sysconf script. Implementing coop_handler (coop_factory) user obtains all necessary params from configuration file (parsing task is a part of the user domain) and initializes agents with them.
Simple coop_handler is a functiom with the following signature:
so_sysconf_4::coop_info_t handler( so_5::rt::so_environment_t & env, const std::string & coop_name, const std::string & cfg_file );
The implementation of this function reads configuration from file specified by cfg_file and then registers agent cooperation with name coop_name in SObjectizer Environment.
If registration process was finished successfully then function must return so_sysconf_4::coop_info_t instance. In most cases the return of empty instance of so_sysconf_4::coop_info_t is enough:
so_sysconf_4::coop_info_t my_handler( so_5::rt::so_environment_t & env, const std::string & coop_name, const std::string & cfg_file ) { my_config_t cfg = load_config( cfg_file ); env.register_agent_as_coop( coop_name, new my_agent_t( env, cfg ) ); return so_sysconf_4::coop_info_t(); }
Simple coop_handler is a also a functiom with specific signature:
so_sysconf_4::coop_info_t handler( so_5::rt::so_environment_t & env, const std::string & coop_name, const std::string & cfg_file );
Implementation must obtain agents parameters from cfg_file and pass them to agent(s) of cooperation and register cooperation in SObjectizer Environment.
If registration process was finished successfully then function must return so_sysconf_4::coop_info_t instance. In most cases the return of empty instance of so_sysconf_4::coop_info_t is enough:
so_sysconf_4::coop_info_t my_handler( so_5::rt::so_environment_t & env, const std::string & coop_name, const std::string & cfg_file ) { my_config_t cfg = load_config( cfg_file ); env.register_agent_as_coop( coop_name, new my_agent_t( env, cfg ) ); return so_sysconf_4::coop_info_t(); }
The key idea is to introduce coop_handler, coop_factory and layer_handler items to sysconf. And implementation of idea is that each module as dynamicly linked object exports a function with conventional name module_description. Implementation of this function is a part of the user domain.
Sysconf gives a set of helper macros that help to build this function correctly. Main macros used for building module_description are:
SYSCONF_4_MODULE_DESCRIPTION_START_STD()
and SYSCONF_4_MODULE_DESCRIPTION_FINISH_STD()
. First macro starts module_description definition and the second one finishes it.
SYSCONF_4_MODULE_DESCRIPTION_ADD_HANDLER_STD()
-- add coop_handler helper macro.
SYSCONF_4_MODULE_DESCRIPTION_ADD_FACTORY_STD()
-- add coop_factory helper macro.
Pair of SYSCONF_4_MODULE_DESCRIPTION_ADD_XXX_STD()
macros are usually used with with create_coop_handler and create_coop_factory helper functions:
SYSCONF_4_MODULE_DESCRIPTION_START_STD() SYSCONF_4_MODULE_DESCRIPTION_ADD_HANDLER_STD( so_sysconf_4::create_coop_handler( "log_manager::file_log_manager", &log_manager::file_log_manager::factory, so_sysconf_4::FATAL_ERROR_SHOULD_BE_INITIATED ) ) SYSCONF_4_MODULE_DESCRIPTION_ADD_FACTORY_STD( so_sysconf_4::create_coop_factory( "mqtt_client", &mqtt_cliner::factory, so_sysconf_4::FATAL_ERROR_SHOULD_BE_INITIATED ) ) SYSCONF_4_MODULE_DESCRIPTION_ADD_FACTORY_STD( so_sysconf_4::create_coop_factory( "http_client", &http_client::factory, so_sysconf_4::FATAL_ERROR_SHOULD_BE_INITIATED ) ) SYSCONF_4_MODULE_DESCRIPTION_FINISH_STD()
When Sysconf fails to register cooperation it should know how to handle this. The options are:
To set an option for handling registration errors the third argument of create_coop_factory and create_coop_handler functions is used. It can accept th following values:
FAILURE_SHOULD_BE_IGNORED
-- ignore and continue;FATAL_ERROR_SHOULD_BE_INITIATED
-- treat it as fatal error and stop application.Sysconf-script syntax is based on Curl syntax (Curl programming language). Scrip is a text file in a simple ASCII coding. Script constists of a sequence of tags. Each tag might contain parameters or child tags. Each tag is started with opening curly brace immediately followed by tag name. Tag name cannot include spaces.
Script might include comments ignored by parser. There are a single line comment syntax and a multiline comment syntax:
||
and continues until the end of line;|#
and ends with #|
.Multiline comments cannot be nested.
Tag parameters can be string or numbers. Strings are enclosed in double quotes. For example:
{pause 3000 {comment "before dereg coop" } }
Tag {pause}
has a numeric parameter 3000 and child tag {comment}
has a string parameter “before dereg coop”.
Characters {
, }
, |
, \
, "
are control characters. That's why they connot be use directly. To use them in strings one should escape them:
\{
;\}
;\|
;\\
;\"
.Note. In strings enclosed in double quotes {
, }
and |
characters can be used without escaping. It means that following lines are equivalent:
{make-coop {factory "{trx-processor}-{type-0274}" } … } {make-coop {factory "\{trx-processor\}-\{type-0274\}" } … }
String parameters can contain numeric escape sequences that define a characters with specific codes. Three radix are supported:
\b
or \B
and must be followed by exactly 8 bits. For example \b01000001
;\o
or \O
and must be followed by 3 octal digits. For example \o017
;\x
or \X
and must be followed by 2 hex digits. For example \xC5
.There are also a standard escape sequences:
\n
– new line;\r
– carriage return;\t
– tab.Numeric values can be defined in different bases. Four bases are available:
0b101110111000
;0o5670
or 0O5670
;3000
;0xBB8
(0xbb8
) or 0XBB8
(0Xbb8
).Tags used in sysconf-script will be described with the help of the following notation.
Tags are described in the same order they are expected in input stream. For example:
{sysconf-script {load-dll ...} {make-coop ...} {wait-for ...} {sysconf-stop} }
If tag contains parameter then its type is enclosed in angle brackets:
{pause <uint:milliseconds> {comment <str:comment-string>} }
In this sample tag {pause}
expects uint value (milliseconds) and tag {comment}
expects string parameter (comment-string). In real script values are used without angle brackets:
{pause 3000 {comment "wait for resource allocation"} }
If there is a constraint applied to tag value it is specified after parameter type:
{add-thread-pool-disp <str:dispatcher-name> {thread-count <uint:1..1000000>} } {load-dll <str:name> {os-name-convert <str:(none|simple)>} }
Tag {thread-count}
can have values in the range from 1 to 1000000 and tag {os-name-convert}
can have value "none" or "simple".
For optional tags square brackets are used. For example:
{pause <uint> [{comment <str>}] }
Tag {comment}
is optional and can be skipped when tag pause is used.
Some tags can be used multiple times. To specify this *
and +
indicators are used. *
denotes zero or more times and +
denotes one or more times:
{sysconf-script {load-dll ...}* {make-coop ...}* {wait-for ...}* ... }
Sysconf-script must consist of a single root tag {sysconf-script}
that has nested tags. The order of child tag of {sysconf-script}
matters: operations defined by tags are executed in the same order as they go in {sysconf-script}
.
Commands like {load-dll}
and {unload-dll}
, {reg-coop}
/{make-coop}
and {dereg-coop}
are not forced to be used in pairs. It means that if one needs to load specific module ({load-dll}
) once at start of application then it is not necessary to apply matching {unload-dll}
. When sysconf stops it unloads all loaded modules automatically. The same applies to commands {reg-coop}
and {make-coop}
-- when sysconf ends its work it automatically deregisters all created cooperations.
Child tags of {sysconf-script}
are the following:
{sysconf-script {add-active-group-disp ...}* {add-active-obj-disp ...}* {add-adv-thread-pool-disp ...}* {add-one-thread-disp ...}* {add-thread-pool-disp ...}* {add-extra-layer ...}* {load-dll ...}* {unload-dll ...}* {reg-coop ...}* {make-coop ...}* {dereg-coop ...}* {pause ...}* {wait-for ...}* [{sysconf-stop ...}] }
Adds an instance of active_group disp with specified name.
Syntax:
{add-active-group-disp <str:dispatcher-name>}
Adds an instance of active_obj disp with specified name.
Syntax:
{add-active-obj-disp <str:dispatcher-name>}
Adds an instance of adv_thread_pool disp with specified name and number of threads.
Syntax:
{add-adv-thread-pool-disp <str:dispatcher-name> [{thread-count <uint:1..1000000>}] }
Optional child tag {thread-count}
sets the number of working threads. If {thread-count}
is not specified then the number of working threads is set automatically (the number of available cores).
Adds an instance of one_thread disp with specified name.
Syntax:
{add-one-thread-disp <str:dispatcher-name>}
Adds an instance of thread_pool disp with specified name and number of threads.
Syntax:
{add-thread-pool-disp <str:dispatcher-name> [{thread-count <uint:1..1000000>}] }
Optional child tag {thread-count}
sets the number of working threads. If {thread-count}
is not specified then the number of working threads is set automatically (the number of available cores).
Adds an extra layer to SObjectizer Environment.
Syntax:
{add-extra-layer <str:layer-handler-name> [{cfg-file <str:config-file-name>}] }
When add extra layer command is executed sysconf searches layer_handler with corresponding name and calls it to register an extra layer in Sobjectizer Environment. Optional tag {cfg-file}
specifies configuration file of a layer if necessary. If tag {cfg-file}
was not specified then empty string is passed to layer registrator wrapper.
Loads sysconf module with specified name.
Syntax:
{load-dll <str:library-name> [{os-name-convert <str:(none|simple)>}] [{mandatory}] }
Sysconf dynamically links .dll (.so) file to a running application process.
Optional child tag {mandatory}
defines whether a fail to load module would be treated as a fatal error. When tag {mandatory}
is specified and load module fails then sysconf stops the application.
Optional child tag {os-name-convert}
defines how to transform specified module name.
Tag accepts two values: "none" and "simple". In case of "none" value module name would be accepted as is. In case of "simple" value module name would be transformed in following way:
Note. If {os-name-conver}
has "simple" hence adding lib prefix on Unix platforms then transformation is applied to module name string in general. So if sysconf module is specified with the help of absolute or relative path then the transformation will be incorrect (/usr/lib/myconverter
will be transformed to lib/usr/lib/myconverter.so
instead of /usr/lib/libmyconverter.so
).
Note. Sysconf follows OS standard rules when selecting directories to search for a specified module.
Loads sysconf module with specified name.
Syntax:
{unload-dll <str:library-name> [{os-name-convert <str:(none|simple)>}] }
Tag value and child tag {os-name-convert}
have the same meaning as for {load-dll}
.
Sysconf module can be unloaded only if reference count to that module is zero. It means that there is no running cooperations or layer that are provided by the module.
Note. If child tag {os-name-conver}
was specified for {load-dll}
then it also must be specified in {unload-dll}
tag.
Creates and registers agent cooperation with the help of coop_handler registration wrapper.
Syntax:
{reg-coop <str:coop-name> [{cfg-file <str:config-file-name>}] }
When reg-coop command is executed sysconf searches coop_handler with specified name. If coop_handler was found then sysconf calls coop_handler to register cooperation of agents.
Optional child tag {cfg-file}
specifies configuration file with parameters for cooperation and its agents. Sysconf passes configuration file path to coop_handler. If {cfg-file}
is not specified then sysconf passes empty string.
When using coop_handler only single cooperation registration is allowed. An attempt to perform second registration of already active coop_handler is treated as fatal error hence sysconf stops the application.
Creates and registers agent cooperation with the help of coop_factory registration wrapper.
Syntax:
{make-coop {factory <str:coop-factory-name>} {coop <str:coop-name>} [{cfg-file <str:config-file-name>}] }
When make-coop command is executed sysconf searches coop_factory with specified name. If coop_factory was found sysconf calls coop_factory to register cooperation with name specified by mandatory child tag {coop}
.
Optional child tag {cfg-file}
specifies configuration file with parameters for cooperation and its agents. Sysconf passes configuration file path to coop_factory. If {cfg-file}
is not specified then sysconf passes empty string.
An attempt to make coop with already existing name is treated as fatal error hence sysconf stops the application.
Deregisters cooperation with specified name.
Syntax:
{dereg-coop <str:coop-name> [{timeout <uint:milliseconds>}] [{comment <str:dereg-comment>}] }
When dereg-coop command is executed sysconf searches specified name in the list of cooperations that were registered through sysconf ({reg-coop}
or {make-coop}
).
When optional child tag {timeout}
was specified then sysconf waits for a given period of time for cooperation deregistration confirmation. Period is specified milliseconds. If there was no confirmation during timeout sysconf stops the application.
Optional child tag {comment}
is used to trace the operation with specified message.
Pauses sysconf-script execution for a given period in milliseconds.
Syntax:
{pause <uint:milliseconds> [{comment <str:pause-comment>}] }
Sysconf waits for a given period before executing next command.
Optional child tag {comment}
is used to trace the operation with specified message.
Stops the execution of the script until the specified event occurs.
Syntax:
{wait-for <str:event-name> [{timeout <uint:milliseconds>}] [{comment <str:pause-comment>}] }
Sysconf pauses script execution until the event with specified name occurs.
If optional child tag {timeout}
is present it sets max timeout for waiting. If by the end of timeout event didn’t occur sysconf initiates fatal error hence sysconf stops the application.
Timeout is specified in milliseconds.
Optional child tag {comment}
is used to trace the operation with specified message.
Stops sysconf application.
Syntax:
{sysconf-stop}
Sysconf stops running application. It finished sysconf work and stops SObjectizer Environment.
It is a common practice to keep different types of files used by application in different locations: configuration files might be stored in one directory and log files in another directory and temporary file in yet another directory. Sysconf introduces the concept of app_paths that includes the set of standard application directories.
Sysconf app_paths includes location for the following types of files:
Standard sysconf loaders provide the setting of app_path directories through command line arguments. Sysconf registrators wrappers can access app_path directories via sysconf-layer API:
so_sysconf_layer_t::etc_path()
-- returns path to configuration files directory;so_sysconf_layer_t::log_path()
-- returns path to log files directory;so_sysconf_layer_t::data_path()
-- returns path to data files directory;so_sysconf_layer_t::tmp_path()
-- returns path to temporary files directory.Sysconf-layer API also provides helper methods for creating file path for specified file name. For example, if one needs to resolve name "my_handler.cfg" relatively to app_paths, it is enough to call so_sysconf_layer_t::etc_file_name("my_handler.cfg")
and the value returned by this call will be the path to specified file in so_sysconf_layer_t::etc_path()
directory. The real code might look as following:
so_sysconf_4::coop_info_t my_handler( so_5::so_environment_t & env, const std::string & coop_name, const std::string & cfg_file ) { env.register_agent_as_coop( new my_agent_t( env, load_config( so_sysconf_4::layer(env).etc_file_name(cfg_file) ) ) ); return so_sysconf_4::coop_info_t(); }
Besides etc_file_name()
method sysconf_layer_t provides methods log_file_name()
, data_file_name()
и tmp_file_name()
.
By default all app_path directories are set to "./" -- the current directory.
Standard sysconf loaders are console utils without GUI-interface. Help note can be printed by executing loader with -h
(--help
) parameter.
There is a set of common arguments for all standards sysconf loaders. Also there are some specific parameters for different sysconf loaders.
All standard sysconf loaders have the following common arguments:
-s
or --script
-- sets location of sysconf-script that defines application. Mandatory.--app-path-etc
-- sets path to configuration files directory. Optional.--app-path-log
-- sets path to log files directory. Optional.--app-path-data
-- sets path to data files directory. Optional.--app-path-tmp
-- sets path to temporary files directory. Optional.--abort-timeout
-- sets timeout in milliseconds that is given to sysconf application for finishing normally when shutdown was initiated. If, after the shutdown was initiated SObjectizer Environment did not finish its work in a given period then the application is aborted by calling std::abort()
.--ostream-logger
-- enables logging of operations performed by sysconf to standard output.Sysconf loaders also support --disp-one-thread
, --disp-active-obj
and --disp-active-group
options. But since version 4.3 it is not recommended to use them because dispatchers can be added in sysconf-script by {add-*-disp}
commands. These arguments are left for backward compatibility.
Parameters --script
and --app-path-etc
are independent. It means that if sysconf-script is located in the same directory as configuration files and its location is set by --app-path-etc
argument then --script
must include a path to that directory.
For example if there is a cfg files directory called my-service-config-1.0
and there is a sysconf-script sysconf.cfg
:
{sysconf-script {load-dll "my_service-1.0" {mandatory}} {make-coop {factory "my_service::main"} {coop "my_service"} {cfg-file "main.cfg"} } }
Then the start of so_sysconf.process would be the following:
so_sysconf.process --script my-service-config-1.0/sysconf.cfg --app-path-etc my-service-config-1.0
so_sysconf.process is the basic sysconf loader that runs as a simple console application.
This sysconf loader works on all supported platforms.
Supports only a common set of sysconf application command line arguments.
so_sysconf.ntservice enables sysconf application to run as Windows service.
This loader registers windows service when started with --svc-install
argument.
To start an already registered service one can execute so_sysconf.ntservice with --svc-start
argument or to use services.msc.
To stop the service one can execute so_sysconf.ntservice with --svc-stop
argument or to use services.msc.
This loader deregisters windows service when started with --svc-remove argument
. The service must not be running when removing.
so_sysconf.ntservice also supports the following arguments:
--svc-name
-- service name. Mandatory parameter.--svc-work-path
-- working path to run the service executable in. This parameter must be used with --svc-install
option. --svc-use-current-path
-- a shorthand for previous parameter. This option has the same effect as providing --svc-work-path
with current directory path. --svc-manual-startup
-- set manual startup type for installed service. By default service is started automatically when system starts. This option is used with --svc-install
option.--svc-debug
-- force to run so_sysconf.ntservice as console application.Lets install service with name my-service-1.0
and specific working directory. Let sysconf-script path be relative to working directory:
so_sysconf.ntservice --svc-name my-service-1.0 --svc-install \ --svc-work-path C:\MyService\Bin --script ..\Config\sysconf.cfg \ --app-path-etc ..\Config
Sysconf-script absolute path would be C:\MyService\Config\sysconf.cfg
.
Lets install service with name my-service-2.0
and working directory set to current path (C:\MyService\Bin
). And let sysconf-script path be relative to working directory:
so_sysconf.ntservice --svc-name my-service-2.0 --svc-use-current-path \ --script ..\Config\sysconf.cfg --app-path-etc ..\Config
Sysconf-script absolute path would be C:\MyService\Config\sysconf.cfg
.
so_sysconf.daemon enables sysconf application to run as unix daemon.
After the start so_sysconf.daemon forks the process and then closes standard io streams. After this SObjectizer Environment is started and sysconf runs initial sysconf-script.
Working directory is set to directory where so_sysconf.daemon was started.
so_sysconf.daemon has no additional command line arguments.
Using sysconf it is error-prone when a single cooperation initiates SObjectizer Environment to stop because it might break the work of other cooperations: for example some cooperations need to properly close sessions/connections to external resources or to store unsaved (cached) data and to commit ongoing transactions.
If one needs to stop application (sysconf and SObjectizer Environment ) then the so_sysconf_layer_t::stop()
method must be used:
void my_agent::evt_some_action() { if( <shutdown condition> ) so_sysconf_4::layer(so_environment()).stop(); ... }
If the agent needs to be notified of shutdown initiation in order to perform specific actions (properly close handles, finish sessions etc) then agent needs to apply the following scenario:
so_sysconf_4::msg_shutdown
message;so_sysconf_layer_t::subscribe_to_shutdown()
method;so_sysconf_4::msg_shutdown
by calling so_sysconf_layer_t::unsubscribe_from_shutdown()
method.The real code might look as following:
void my_agent::so_define_agent() { … so_sysconf_4::layer(so_environment()). subscribe_to_shutdown( this, so_default_state(), &my_agent::evt_shutdown ); ... } … void my_agent::evt_shutdown( const so_5::rt::event_data_t< so_sysconf_4::msg_shutdown > & evt ) { // Agent specific actions … … // Now it is safe to shutdown application. so_sysconf_4::layer(so_environment()). unsubscribe_from_shutdown(this); }
Note. If agnent uses subscribe_to_shutdown()
then it must also use unsubscribe_from_shutdown()
. Because if not to use unsubscribe_from_shutdown()
then sysconf will not know that the agent finished its work.
Note. In order to subscribe on msg_shutdown in several states one needs to call subscribe_to_shutdown()
for each state. But to cancel subscription on msg_shutdown a single call to unsubscribe_from_shutdown()
is enough.
To initiate application shutdown in cases when it is impossible or hard to recover (fatal error) there is a method so_sysconf_layer_t::init_fatal_error()
:
void my_agent::evt_store_cache() { try { store_cache_to_disk(); } catch( const std::exception & x ) { so_sysconf_4::layer(so_environment()). init_fatal_error( "my_agent", "unable to store cache to disk", x.what() ); } }
The process of shutdown is the same as normal shutdown except that sysconf will log that the reason of shutdown was fatal error.
In some cases it is necessary to pause sysconf-script execution until some action are completed. Such cases can be handled by named events mechanism.
Sysconf defines so_sysconf_4::named_event_t
class. Instances of that class must be created with unique names for particular application.
The instance of so_sysconf_4::named_event_t
must be introduced to sysconf with the help of module_description()
function.
The reference to named event object must be passed to agent which shall trigger that event. When agent decides that event occured it calls named_event_t::trigger_event()
method and after this event considered triggered. If sysconf was waiting for that event then it continues sysconf-script execution. And If not then even if sysconf meets a command to wait for that event then it will continue execution without waiting because event is already occured.
The real code might look as following:
class some_heavy_data_agent : public so_5::rt::agent_t { public : some_heavy_data_agent( so_5::rt::so_environment_t & env, so_sysconf_4::named_event_t & data_loading_done ) : so_5::rt::agent_t( env ) , m_data_loading_done( data_loading_done ) {} ... virtual void so_evt_start() override { ... // Loads lots of data to initialize agent. } void evt_data_loading_done() { // Data is loaded. m_data_loading_done.trigger_event(); } ... private : so_sysconf_4::named_event_t & m_data_loading_done; ... }; SYSCONF_4_MODULE_DESCRIPTION_START_STD() std::unique_ptr< so_sysconf_4::named_event_t > event( new so_sysconf_4::named_event_t( "heavy_data_loading_event" ) ); SYSCONF_4_MODULE_DESCRIPTION_ADD_HANDLER_STD( new heavy_data_coop_handler_t( "heavy_data_coop", *event ) ) SYSCONF_4_MODULE_DESCRIPTION_ADD_EVENT_STD( std::move( event ) ); SYSCONF_4_MODULE_DESCRIPTION_FINISH_STD()
And sysconf-script part might look like this:
{sysconf-script {load-dll "heavy_data_coop" {mandatory}} {reg-coop "heavy_data_coop" {cfg-file "config.cfg"}} {wait-for "heavy_data_loading_event" || Waits to more than 60 seconds. {timeout 60000} {comment "initial data loading..."} } ... }
If simple coop_handler is not enough, then one can create advanced coop_handler derived from so_sysconf_4::coop_handler_t
directly. So it is possible to override so_sysconf_4::coop_handler_t
methods. The main method is coop_handler_t::reg()
. This method is called by sysconf to register cooperation.
Also there is coop_handler_t::dereg()
method that is called by sysconf to deregister cooperation. The default implementation covers most of the cases and it just calls so_environment_t::deregister_coop()
.
The reasons to create one’s own successor of coop_handler_t might be the need to create some special object with lifetime greater than lifetime of cooperation. Or the reason might be the code design simplification when there are a lot of complicated logic that is easier to implement on the context of a class.
The main difference function-based coop_handler and class-based coop_handler is that function based implementations obtain cooperation name as parameter and class-based implementations obtain cooperation name in constructor.
Sample coop_handler derived from so_sysconf_4::coop_handler_t
:
class heavy_data_coop_handler_t : public so_sysconf_4::coop_handler_t { public : heavy_data_coop_handler_t( const std::string & coop_name, so_sysconf_4::named_event_t & event ) : so_sysconf_4::coop_handler_t( coop_name ) , m_event( event ) {} virtual so_sysconf_4::coop_info_t reg( so_5::so_environment_t & env, const std::string & cfg_file ) override { my_config_t cfg = load_config( so_sysconf_4::layer(env).etc_file_name(cfg_file) ); env.register_agent_as_coop( // query_name() -- is a base class function // that returns cooperation name set in constructor. query_name(), new heavy_data_agent_t( env, cfg, m_event ) ); return so_sysconf_4::coop_info_t(); } private : so_sysconf_4::named_event_t & m_event; my_config_t load_config( const std::string & cfg_name ) const { … } }; ~~~~ To register class-based coop_handler in module description one needs to instantiate it in macro `SYSCONF_4_MODULE_DESCRIPTION_ADD_HANDLER_STD`:
::c++
SYSCONF_4_MODULE_DESCRIPTION_ADD_HANDLER_STD(
std::unique_ptr< so_sysconf_4::coop_handler_t >(
new my_coop_handler_t() ) )
# Advanced coop_factory derived from coop_factory_t directly If simple coop_factory is not enough, then one can create advanced coop_factory derived from `so_sysconf_4::coop_factory_t` directly. So it is possible to override `so_sysconf_4::coop_factory_t` methods. The main method is `coop_factory_t::reg()`. This method is called by sysconf to register cooperation. Also there is `coop_factory_t::dereg()` method that is called by sysconf to deregister cooperation. The default implementation covers most of the cases and it just calls `so_environment_t::deregister_coop()`. The reasons to create one’s own successor of coop_handler_t might be the need to create some special object with lifetime greater than lifetime of cooperation. Or the reason might be the code design simplification when there are a lot of complicated logic that is easier to implement on the context of a class. To register class-based coop_factory in module description one needs to instantiate it in macro `SYSCONF_4_MODULE_DESCRIPTION_ADD_FACTORY_STD`:
SYSCONF_4_MODULE_DESCRIPTION_ADD_FACTORY_STD(
std::unique_ptr< so_sysconf_4::coop_factory_t >(
new my_coop_factory_t() ) )
# layer_handler Simple layer_handler is a function with following signature:
::c++
void
layer_handler(
so_5::rt::so_environment_t & env,
const std::string & cfg_file );
To register layer_handler in module description one needs to use `SYSCONF_4_MODULE_DESCRIPTION_ADD_LAYER_HANDLER_STD` macro :
::c++
SYSCONF_4_MODULE_DESCRIPTION_START_STD()
SYSCONF_4_MODULE_DESCRIPTION_ADD_LAYER_HANDLER_STD( so_sysconf_4::create_layer_handler( "mbapi_layer", &layer_handler ) )
SYSCONF_4_MODULE_DESCRIPTION_FINISH_STD()
~~~~~
If simple layer_handler is not enough, then one can create advanced coop_factory derived from so_sysconf_4::layer_handler_t
directly. So it is possible to override so_sysconf_4::layer_handler_t
methods. The main method is layer_handler_t::add()
. This method is called by sysconf to add extra layer to SObjectizer Environment. Sysconf-script command for adding extra layer is {add-extra-layer}
.
If application uses several instances of SObjectizer Environment and each instance can use sysconf independently.
It means that if one module defines some coop_handler then each instance of sysconf will have a separate object of coop_handler. The same applies to coop_factory and layer_handler objects.
This feature must be should be considered when implementing coop_handler/coop_factory/layer_handler that use global or static variables.