Skip to content

Concepts

Configuration and properties

A device keeps its complete configuration as properties: values with a name, in one store of the type fair::mq::ProgOptions. The command-line options, the channel configuration and the values from plugins all go into this store. The device, its plugins and your code read the configuration from there, also while the device runs.

A property has a key of the type std::string and a value of any C++ type. The value is a fair::mq::Property, which is a boost::any. fair::mq::Properties is a std::map<std::string, Property> for several properties. The store protects its data with a mutex, so all threads can use it.

A device has a pointer to its store: fConfig, also from GetConfig(). A device that you make with the constructor Device() has its own internal store. SetConfig() replaces it with another store and discards the internal store (Device.cxx). With runDevice.h, the device runner makes the store and gives it to the device with SetConfig().

The old name FairMQProgOptions is a deprecated alias of fair::mq::ProgOptions. Refer to Move from the FairMQ* names.

A device made with runDevice.h gets its properties in this sequence (DeviceRunner.cxx):

  1. The device runner loads the config plugin and the control plugin, and collects the options of all plugins and your addCustomOptions().
  2. It parses the command line. Each option becomes a property with the name of the option, for example id or severity. An option that you do not give gets its default, if it has one.
  3. It makes the device with your getDevice(), gives it the store, and makes the plugins.
  4. It logs all properties with the severity debug, and starts the state machine.
  5. When the device goes into the state INITIALIZING DEVICE, the config plugin reads --mq-config or --channel-config. It stores each channel property as chans.<name>.<index>.<property>.
  6. When the controller requests the transition COMPLETE INIT, the device reads its properties and makes its sub-channels.

Without DeviceRunner, your program parses the command line itself. See Parse options without DeviceRunner.

Plugins and your code can set properties at all times with SetProperty(). The device also writes a property: after it binds or connects a sub-channel, it writes the address that it uses to chans.<name>.<index>.address (Device.cxx).

The device reads most properties once, at the request of the transition COMPLETE INIT (Device.cxx). A later change has an effect only when the device goes through INITIALIZING DEVICE again, for example after the transition RESET DEVICE.

Properties The device reads them A later change
id, rate, init-timeout, transport, network-interface At the request of COMPLETE INIT No effect
chans.<name>.<index>.<property> At the request of COMPLETE INIT No effect, except for the address of a connecting sub-channel that the device could not connect yet
io-threads, session, shmid, the shm-* and bad-alloc-* options When the device makes the transport, at the request of COMPLETE INIT No effect
control, catch-signals When the device runner makes the control plugin No effect
severity, file-severity, verbosity, color, log-to-file After the parse of the command line The device runner applies the new value at once (DeviceRunner.cxx)
Your custom options When your code reads them Your code decides

The device and the plugins use the same functions. A device calls them on fConfig. A plugin calls them on itself, for example Plugin::GetProperty(). In a plugin, Count() has the name PropertyExists().

Function Effect If the key does not exist
GetProperty<T>(key) Returns the value. Throws PropertyNotFoundError.
GetProperty<T>(key, ifNotFound) Returns the value. Returns ifNotFound.
GetPropertyAsString(key) Returns the value as text. Throws PropertyNotFoundError.
GetPropertyAsString(key, ifNotFound) Returns the value as text. Returns ifNotFound.
GetProperties(regex) Returns the properties whose full key matches the regular expression. Returns an empty map and logs a warning.
GetPropertiesStartingWith(prefix) Returns the properties whose key starts with prefix. Returns an empty map.
GetPropertiesAsString(regex) The same as GetProperties(), with the values as text. Returns an empty map and logs a warning.
GetPropertiesAsStringStartingWith(prefix) The same as GetPropertiesStartingWith(), with the values as text. Returns an empty map.
GetPropertyKeys() Returns all keys.
Count(key) Returns 1 if the property exists, otherwise 0.
SetProperty<T>(key, value) Sets the value. Makes the property.
UpdateProperty<T>(key, value) Sets the value and returns true. Returns false.
SetProperties(properties) Sets several values. Makes the properties.
UpdateProperties(properties) Sets several values and returns true. Returns false and changes nothing.
DeleteProperty(key) Removes the property. Does nothing.

The type T must be the exact type of the stored value. For another type, the read throws boost::bad_any_cast. The type of an option is the type in its declaration, for example uint64_t for --max-iterations of the 1-1 sampler. Command-line options shows the type of each option.

Subscribe<T>() registers a callback for the changes of all properties of the type T. SubscribeAsString() registers a callback that gets each change of each type, with the value as text. SetProperty(), UpdateProperty(), SetProperties() and UpdateProperties() call the callbacks (ProgOptions.h).

  • The callback gets the key and the new value. It runs in the thread that changes the property, before the change function returns.
  • Each subscription has the name of a subscriber. A second subscription with the same name and the same type replaces the first (EventManager.h).
  • Unsubscribe<T>() and UnsubscribeAsString() remove a subscription. For a name without a subscription, they throw std::out_of_range.
  • A plugin uses SubscribeToPropertyChange<T>(). The name of the plugin is the name of the subscriber.

This test of the plugin services shows the callbacks for three types, and the effect of an unsubscribe:

test/plugin_services/_config.cxx
TEST_F(PluginServices, ConfigCallbacks)
{
mServices.SubscribeToPropertyChange<string>("test", [](const string& key, string value) {
if (key == "chans.data.0.address") { ASSERT_EQ(value, "tcp://localhost:4321"); }
if (key == "custom1") { ASSERT_EQ(value, "test1"); }
if (key == "custom2") { ASSERT_EQ(value, "test2"); }
});
mServices.SubscribeToPropertyChange<int>("test", [](const string& key, int /*value*/) {
if (key == "chans.data.0.rcvBufSize") {
FAIL(); // should not be called because we unsubscribed
}
});
mServices.SubscribeToPropertyChange<double>("test", [](const string& key, double value) {
if (key == "data-rate") { ASSERT_EQ(value, 0.9); }
});
mServices.SetProperty<string>("chans.data.0.address", "tcp://localhost:4321");
mServices.SetProperty<double>("data-rate", 0.9);
mServices.UnsubscribeFromPropertyChange<int>("test");
mServices.SetProperty<int>("chans.data.0.rcvBufSize", 100);
}
test/plugin_services/_config.cxx:76–100

A property can hold a value of your own type. To convert it to text, register the type with PropertyHelper::AddType<T>(). The type must have an operator<< for std::ostream. Without the registration, GetPropertyAsString() returns [unidentified_type]. SetProperties(), UpdateProperties(), GetPropertiesAsString() and GetPropertiesAsStringStartingWith() throw std::out_of_range for such a value (Properties.h).

The configuration of a sub-channel is a group of properties with the keys chans.<name>.<index>.<property>, for example chans.data.0.address. Channel properties lists all of them. The parsers of --channel-config and JSON files make all 15 properties of each sub-channel.

  • GetChannelInfo() counts the sub-channels of each channel by their type properties (ProgOptions.cxx). A sub-channel without a type property does not exist for the device.
  • The device makes the sub-channels with the indexes 0 to N-1, where N is the count. Thus use the indexes 0, 1, 2 and so on, without gaps.
  • The device reads the channel properties at the request of COMPLETE INIT. Set them before this request, for example in a plugin in the state INITIALIZING DEVICE, or in the hook Init() of the device.

Configure channels shows the three ways to give the channel properties.

The config plugin is a part of each device made with runDevice.h. It declares the options of the groups Device and Shared memory in the table of options, and reads the channel configuration (Config.cxx).

When the device goes into the state INITIALIZING DEVICE, the config plugin:

  1. Takes the value of --config-key, or the value of --id if --config-key is not there.
  2. Reads the JSON file of --mq-config for this value. If there is no --mq-config, it reads --channel-config.
  3. Stores the channel properties with SetProperties().

If the parser throws an exception, the plugin logs the message with the severity error, and the device continues without these channels. If --id and --config-key are empty, the plugin logs a warning and reads nothing. JSON configuration lists the errors.

The device runner makes the plugins of -P before the config plugin. In INITIALIZING DEVICE, their callbacks thus run first. The config plugin then replaces the values of all channel properties that the configuration also gives.

Your own options become properties as well. With runDevice.h, declare them in addCustomOptions(), and read them with GetProperty<T>(), usually in InitTask(). Command-line options shows the code of the 1-1 example.

  • --print-options prints all options and their values, and stops. It does not show the channel properties, because the config plugin reads them later.
  • At the start, the device logs all properties with the severity debug, after the line Configuration:.
  • With --severity trace, the parsers log each property of each sub-channel.
  • With --severity debug, the device logs Attached channel <name> to <endpoint> (bind|connect) (<type>) for each endpoint.
  • In your code, GetPropertiesAsStringStartingWith("chans.") returns all channel properties as text.