Concepts
Plugins and control
A plugin is a class that runs in the process of a device. It can read and set properties, follow the state of the
device and drive the state machine. Each device that starts with runDevice.h has two built-in plugins: config
reads the channel configuration, and control moves the device through its states.
Parts of the plugin system
Section titled “Parts of the plugin system”| Class | Task |
|---|---|
fair::mq::Plugin |
The base class of all plugins. It holds the name, the version, the maintainer and the homepage of the plugin, and it gives the plugin API. |
fair::mq::PluginManager |
Finds, loads, creates and owns the plugins. |
fair::mq::PluginServices |
Connects the plugins to the device and to its properties. All plugins of a device use the same object. |
fair::mq::DeviceRunner |
Starts the device with its properties and its plugins. runDevice.h uses it. |
A plugin does not use the device directly. Each call goes through PluginServices:
plugin ──► PluginServices ──► Device (state, transitions) └─► ProgOptions (properties)How a device starts
Section titled “How a device starts”DeviceRunner::Run() loads and creates the plugins in this sequence
(DeviceRunner.cxx):
- The constructor of
DeviceRunnermakes the plugin manager. The plugin manager reads-Sand-Pand loads the plugins of-P, in their sequence. Run()loads the built-in pluginconfig.- The runner hook
hooks::LoadPluginsruns. Your code can load more plugins here. Run()loads the built-in plugincontrol.- The runner hook
hooks::SetCustomCmdLineOptionsruns.runDevice.hadds the options ofaddCustomOptions()here. - The runner adds the options of all plugins to the command-line options. The runner hook
hooks::ModifyRawCmdLineArgsruns. Then the runner parses the command line. With--helpor--print-options,Run()prints the options and returns 0. - The runner hook
hooks::InstantiateDevicemakes the device. - The plugin manager makes the
PluginServicesobject. Then it creates the plugins, in the load sequence. - The device runs its state machine until the state
EXITING. - The runner waits until no plugin has the controller role. Then
Run()returns 0.
The constructor of a plugin runs after the parsing of the command line, so a plugin can read its options there. The control plugin comes last. A plugin that takes the controller role in its constructor gets it before the control plugin.
To load plugins without DeviceRunner, see
Load plugins without DeviceRunner.
Load a plugin
Section titled “Load a plugin”Give the name of a plugin to -P (--plugin), or to
PluginManager::LoadPlugin() in a runner hook. A prefix selects the
mechanism (PluginManager.cxx):
| Name | Mechanism | Where the plugin manager looks |
|---|---|---|
name or d:name |
Dynamic | In a shared library file. See Plugin files and search paths. |
p:name |
Prelinked | In the program. The executable links the library of the plugin at build time. |
s:name |
Static | In the program: the built-in plugins config and control, or a plugin that the executable contains. |
Give -P once for each plugin, for example -P first -P second. The plugin manager does not split a name at commas.
It loads a dynamic or a prelinked plugin only once, also when you give its name twice.
Plugin files and search paths
Section titled “Plugin files and search paths”For the dynamic plugin name, the plugin manager looks for two file names:
libfairmq-plugin-name.so and then libFairMQPlugin_name.so (the suffix is the one of the platform, .so on Linux).
It looks in these places, in this sequence:
- Each directory of
-S(--plugin-search-path). - The system lookup of the dynamic linker: the system library directories,
RUNPATHandLD_LIBRARY_PATH.
-S takes one or more directories. The paths without a prefix make the list. Then the plugin manager puts each path
with the prefix < at the start of the list. It puts each path with the prefix > at the end. Without -S, the list
is empty. The test PluginManager.Factory gives this example:
-S '>/lib' '</home/user/lib' /usr/local/lib /usr/libThe search paths are then /home/user/lib, /usr/local/lib, /usr/lib and /lib. Put quotes around paths with
< or >, because the shell uses these characters for redirection. The plugin manager ignores a directory that does
not exist. A path to a file, or an empty path, gives the error PluginManager::BadSearchPath.
When no file loads, the plugin manager throws PluginManager::PluginLoadError:
Could not load dynamic plugin name from configured search paths (-S) nor with system lookup (system libdirs, RUNPATH, $LD_LIBRARY_PATH).The main() function of runDevice.h catches the exception, logs it and returns 1. A file that exists but does not
load, for example because of a missing symbol, gives the same error.
What a plugin can do
Section titled “What a plugin can do”The functions of fair::mq::Plugin call PluginServices. They give the name of the plugin as the ID of the
subscriber and of the controller.
The state machine calls the state callback at the start of each new state, in the thread of the state machine. The device starts the handler of the state after all callbacks return, so keep the callbacks short. A callback for a property change runs in the thread that changes the property.
GetNumberOfConnectedPeers() exists for tests. The code marks it for removal.
The controller role
Section titled “The controller role”Only one plugin at a time can change the state of the device. This plugin is the controller.
| Function | Effect |
|---|---|
TakeDeviceControl() |
The plugin becomes the controller, if no plugin has the role. If the plugin has the role, nothing changes. If another plugin has the role, the function throws PluginServices::DeviceControlError. |
ChangeDeviceState(transition) |
Requests a transition. If no plugin has the role, the plugin takes it first. If another plugin has the role, the function throws DeviceControlError. It returns false when the device cannot do the transition now. |
StealDeviceControl() |
The plugin becomes the controller, also when another plugin has the role. The control plugin uses it after a signal. |
ReleaseDeviceControl() |
The plugin gives the role back, if it has the role. |
DeviceRunner waits after the end of the state machine until no plugin has the role. Thus a controller must release
the role, normally in the state EXITING.
The device changes the state when the handler of the current state returns. A requested transition interrupts
blocking transfers, and NewStatePending() then returns true, so that the
handler can return. See Device states.
The config plugin
Section titled “The config plugin”The built-in plugin config adds the device options, for example --id, --transport, --session, --rate,
--mq-config, --channel-config and the options of the shmem transport. At the start of the state
INITIALIZING DEVICE, it reads the channel configuration and sets the channel properties:
SubscribeToDeviceStateChange([&](DeviceState newState) { if (newState == DeviceState::InitializingDevice) { string idForParser;
if (PropertyExists("config-key")) { idForParser = GetProperty<string>("config-key"); } else if (PropertyExists("id")) { idForParser = GetProperty<string>("id"); }
if (!idForParser.empty()) { try { if (PropertyExists("mq-config")) { LOG(debug) << "mq-config: Using default JSON parser"; SetProperties(JSONParser(GetProperty<string>("mq-config"), idForParser)); } else if (PropertyExists("channel-config")) { LOG(debug) << "channel-config: Parsing channel configuration"; SetProperties(SuboptParser(GetProperty<vector<string>>("channel-config"), idForParser)); } else { LOG(info) << "fair::mq::plugins::Config: no channels configuration provided via --mq-config or --channel-config"; } } catch (exception& e) { LOG(error) << e.what(); } } else { LOG(warn) << "No device ID or config-key provided for the configuration parser."; } }});The key of the device in the configuration is --config-key, or --id if --config-key is not set. --mq-config
has priority over --channel-config. When the parser fails, the plugin logs the error and the device continues. See
Configuration and properties.
The control plugin
Section titled “The control plugin”The built-in plugin control has two options: --control (default dynamic) and --catch-signals (default 1).
Its constructor takes the controller role, unless --control is none. If another plugin has the role already, the
control plugin logs the reason at the severity debug and starts no controller. Its signal handling does not depend
on the controller role.
Control modes
Section titled “Control modes”--control |
What the control plugin does |
|---|---|
dynamic (default), interactive, external |
It runs the startup sequence. Then it reads keys from the terminal until you press q, and runs the shutdown sequence. |
static |
It runs the startup sequence. When the device goes back to READY, for example because ConditionalRun() returns false, it runs the shutdown sequence. |
gui |
It runs the startup sequence. Then it waits for a signal or for the state ERROR. |
none |
It does not take the controller role. Another plugin or your code must change the states. |
| Another value | It logs Unrecognized control mode at the severity error and uses static. |
Use --control static in scripts. This mode needs no terminal, and it ends the device when the work in RUNNING is
done. The test scripts of the examples use it.
For the effect of each mode on the end of a run, see What the controllers do.
The startup sequence requests the transitions INIT DEVICE, COMPLETE INIT, BIND, CONNECT, INIT TASK and
RUN. Before each request, the plugin waits for the state that the previous transition gives. At the end, the device
is in RUNNING.
The shutdown sequence starts from the current state and repeats until EXITING. Then the plugin releases the
controller role.
| Current state | Transition |
|---|---|
RUNNING |
STOP |
READY |
RESET TASK |
DEVICE READY, BOUND, INITIALIZED |
RESET DEVICE |
INITIALIZING DEVICE |
COMPLETE INIT |
IDLE |
END |
Keys of the interactive mode
Section titled “Keys of the interactive mode”In the modes dynamic, interactive and external, a key works without Enter, and the terminal does not show it.
| Key | Action |
|---|---|
h |
Print the help and the state machine with the keys. |
c |
Log the current state, at the severity state. |
p |
Log the number of connected peers of each sub-channel. |
i |
Request INIT DEVICE, wait for INITIALIZING DEVICE, then request COMPLETE INIT. |
b |
Request BIND. |
x |
Request CONNECT. |
j |
Request INIT TASK. |
r |
Request RUN. |
s |
Request STOP. |
t |
Request RESET TASK. |
d |
Request RESET DEVICE. |
k, l |
Move the console severity one step up (fewer lines) or down (more lines). |
n, m |
Move the log verbosity one step up or down. |
q |
Stop reading keys and run the shutdown sequence. |
| Another key | Print Invalid input: […]. and the help. |
A key that the current state does not accept changes nothing. The state machine logs
No transition from state … on transition … at the severity state.
Signals
Section titled “Signals”With --catch-signals greater than 0, the control plugin handles SIGINT and SIGTERM
(Control.cxx):
- The first signal starts a graceful shutdown. The plugin logs
Received device shutdown request (signal …)andWaiting for graceful device shutdown. Hit Ctrl-C again to abort immediately. - The plugin stops its controller.
- If its shutdown sequence has not run yet, the plugin takes the controller role with
StealDeviceControl(), also from another plugin. Then it runs the shutdown sequence. - A
SIGINTafter an earlier signal ends the process at once withstd::abort(). A secondSIGTERMhas no effect.
With --catch-signals 0, the plugin logs the warning Signal handling (e.g. Ctrl-C) has been deactivated. A signal
then has its default effect, and the device does not run the shutdown sequence.
When the device goes to ERROR
Section titled “When the device goes to ERROR”An exception in a hook, or the transition ERROR FOUND, moves the device to ERROR. No transition leaves this state.
- The state machine stops, and
RunStateMachine()throws: the exception of the hook, orStateMachine::ErrorStateException. - The control plugin stops its controller. It does not run the shutdown sequence.
- The
main()function ofrunDevice.hcatches the exception, logsUncaught exception reached the top of main: …and returns 1.
DeviceRunner::RunWithExceptionHandlers() also returns 1. It
logs Uncaught exception reached the top of DeviceRunner: …. The tests in test/device/_exceptions.cxx check the exit
code 1 for the modes static and interactive.
Options
Section titled “Options”| Option | Default | Description |
|---|---|---|
--controlstring | dynamic | Controller of the state machine: static, dynamic (also interactive and external), gui or none. An unknown value gives static, with an error in the log.control plugin |
--catch-signalsint | 1 | Handle SIGINT and SIGTERM: 1 (yes) or 0 (no). The first signal starts the shutdown sequence. A second SIGINT aborts the process.control plugin |
-S, --plugin-search-pathvector<string>, several values | — | Directories to search for plugins, in this order. Put < before a path to put it at the start of the list, or > to put it at the end. If a plugin is not in these directories, the device tries the system lookup of libraries (system directories, RUNPATH, LD_LIBRARY_PATH).PluginManager |
-P, --pluginvector<string> | — | Load these plugins, in this sequence. For the file libfairmq-plugin-example.so, the name is example. Put p: before the name of a prelinked plugin.PluginManager |
| No option matches. Clear the filter or choose All. | ||
Related pages
Section titled “Related pages”- Write a plugin: make your own plugin and load it with
-P. - Control a device from code: a controller plugin, runner hooks, or a device in a thread.
- Device states: the states and transitions that a controller requests.
- custom-controller: an example of a controller plugin.
- dds: a topology that needs a DDS plugin to give the addresses to the connecting channels.
- Command-line options: all options of a device.