Runtime Configuration Repository
The purpose of the Runtime Configuration Repository is to make available runtime configuration parameters to SRTC components. The parameters are a subset of configuration parameters from the Persistent Configuration Repository that pertain to the current active deployment.
SRTC components must read their configuration parameters from the Runtime Configuration Repository
during the initialisation activity when the Init command is received.
Datapoint creation is normally not needed by individual SRTC components,
since these are typically created by the RTC Supervisor before the component is initialised.
If a case arises that a datapoint needs to be created anyway,
this should also occur during the initialisation activity.
Certain computed results can also be written to the repository as dynamic datapoints while the component is running. Such dynamic datapoints should be set up in the Persistent Configuration Repository if they require default initial values. This allows the default values to be adjusted and handled in the same manner as any other static datapoint value. This means that the datapoint is created by the RTC Supervisor when populating the Runtime Configuration Repository during initialisation. Otherwise the datapoint will have to be created by the component during the initialisation activity.
Datapoints should typically not be deleted from the Runtime Configuration Repository by SRTC components directly. Cleanup of the Runtime Configuration Repository is done externally. Still, it is possible to delete a datapoint if needed.
Data Access
An overview of the API is provided here, which should be enough to become familiar with it and to be able to use it. Refer to the API reference documentation for technical details.
Access to the Runtime Configuration Repository should always be performed through an instance of
RuntimeRepoIf.
The RTC Toolkit framework will automatically prepare such an instance when requested from the
ServiceContainer,
as long as it is correctly configured in the Service Discovery with the
runtime_repo_endpoint datapoint.
The following URI schemes are supported for runtime_repo_endpoint:
- rtr
This is the fully fledged Runtime Configuration Repository implemented as a standalone server process, e.g.
rtr://127.0.0.1:8000/rtr_topic.
The ServiceContainer is itself passed to the constructor of the user derived BusinessLogic
class, and is accessible in user code through the attribute m_services.
The following is an example of how to retrieve the RuntimeRepoIf within the Initialising
method of BusinessLogic:
void BusinessLogic::Initialising(componentFramework::StopToken st) {
auto repository = m_services.Get<RuntimeRepoIf>();
// Can now access the datapoints with repository ...
}
Datapoint Paths
Configuration parameters are stored in a tree hierarchy as leaf nodes. This can also be described as a folder structure, similar to a file system. The nodes from the root of the tree to a particular leaf node form the components of a path. By adding the ‘/’ character as the path separator between each component, this forms a datapoint path string.
The canonical structure for the path is as follows:
/<component>/{static,dynamic}/<parameter>
Where <component> will typically be the SRTC component instance name and <parameter> can
represent a hierarchical sub-path with multiple sub-folders if a deeper hierarchy of configuration
parameters is desired for a particular SRTC component, besides the basic grouping into static
and dynamic parameters.
Path components must contain only lowercase alpha numeric characters or the underscore, i.e. characters from the set [a-z0-9_].
Note
The canonical path structure is a suggested convention to follow.
Reusable components delivered by the RTC Toolkit follow this convention.
However, it is not enforced by RuntimeRepoIf,
except for making sure that the path components only contain characters from the accepted
character set.
Therefore the user is technically free to choose any path structure desired.
The paths are handled in the API with the DataPointPath class,
which is responsible for checking syntactical structure of the path.
This DataPointPath is passed to the methods of RuntimeRepoIf to identify the specific
datapoint to operate on.
Datapoint Creation
Datapoints need to be created before they can be used.
Attempting to write a datapoint that does not exist will throw an exception.
A datapoint can be created with the CreateDataPoint method, providing a default value:
RuntimeRepoIf& repo = ...
DataPointPath path = "/mycomp/static/param1";
int32_t default_value = 123;
repo.CreateDataPoint(path, default_value);
The datapoint type is deduced from the type of the default value. The currently supported types are indicated in the Supported Data Types section. Alternatively, a datapoint can be created without a default value by explicitly specifying the type as a template parameter:
RuntimeRepoIf& repo = ...
DataPointPath path = "/mycomp/static/param1";
repo.CreateDataPoint<int32_t>(path);
Datapoint Reading
Reading a datapoint can be done with the GetDataPoint method,
or the ReadDataPoint method to update an existing variable in place,
which may be useful for large vectors and matrices.
In addition, it is possible to pass a gsl::span or MatrixSpan to the ReadDataPoint
method instead of a reference to a container for numerical vectors and matrices.
This may be useful in situations where ownership of the buffer must be kept by the callers,
but the data buffer cannot be instantiated as a standard container object.
For example:
RuntimeRepoIf& repo = ...
int32_t param1 = repo.GetDataPoint<int32_t>("/mycomp/static/param1"_dppath);
std::vector<float> param2;
repo.ReadDataPoint("/mycomp/static/param2"_dppath, param2);
auto buffer1 = ...;
gsl::span<double> vector_span(buffer1);
repo.ReadDataPoint("/mycomp/static/param3"_dppath, vector_span);
auto buffer2 = ...;
MatrixSpan<double> matrix_span(buffer2);
repo.ReadDataPoint("/mycomp/static/param4"_dppath, matrix_span);
You will see that the _dppath suffix is added to the string representations of the datapoint paths
in the example above.
This is a shorthand to construct a DataPointPath object from a null terminated character string.
The GetDataPoint and ReadDataPoint methods are blocking,
and will only return to the caller once the data has been received.
In certain situations, it may be better to avoid blocking.
To support this, the API provides the SendRequest method that takes a BatchRequest object
as the input argument and returns a BatchResponse object, which can be used to eventually
synchronise.
The BatchRequest class is used to represent one or more read or write requests to be
sent to the Runtime Configuration Repository.
The BatchResponse object provides a Wait method that will block
until the request has been completed.
An optional timeout threshold can be given to the WaitFor method.
This pattern allows a request to be sent without blocking.
Some other work can then be performed while the request is completed in the background;
and finally the Wait method can be called to synchronise with the background request.
Ideally, by the time the Wait method is called, the request that was initially sent would have
completed and the Wait method would return immediately without blocking.
Otherwise it will block as long as is necessary for the request to complete.
Any processing which requires some or all of the datapoints sent as part of the request
must be performed after having invoked the Wait method and it returns without a timeout
condition.
Otherwise there will be a race condition between the request and processing code.
The following example code shows how a BatchRequest object is prepared,
how the request is sent and how the response is handled.
RuntimeRepoIf& repo = ...
int32_t param1;
std::vector<float> param2;
// An example callback lambda that will be invoked when the read request completes.
// The callback receives the datapoint path as its only argument.
auto handler = [](const DataPointPath& path) {
// Request completion notification for path ...
};
BatchRequest request;
request.ReadDataPoint("/mycomp/static/param1"_dppath, param1);
request.ReadDataPoint("/mycomp/static/param2"_dppath, param2, std::nullopt, handler);
auto response = repo.SendRequest(request);
// Other processing not requiring param1 or param2 can happen here ...
response.Wait();
// Any processing requiring access to param1 or param2 goes here ...
As can be seen, the ReadDataPoint method is used to add all the datapoints needed to the
request.
Two alternative invocations are shown, one with a callback handler function and one without.
The optional callback allows processing of a single datapoint’s data asynchronously as soon as it
arrives.
The callback is executed in a different thread than the one that invoked the SendRequest
method.
Therefore care should be taken when accessing any global variables to avoid race conditions.
Warning
Only the data explicitly passed to the callback’s argument should be accessed within the callback, since it is the only datapoint guaranteed to have been delivered when the callback is executed. No other data buffers for any other datapoints should be accessed within the callback function. Any such attempt will result in race conditions and likely data corruption.
In addition, the datapoint buffers that were added to the request with the ReadDataPoint
method must not be accessed outside of a callback function once SendRequest has been called.
Only after Wait returns successfully can the buffers for all these datapoints be accessed.
Datapoint Writing
Writing a new value to a datapoint can be done with the SetDataPoint method,
or the WriteDataPoint method to pass a reference to the data instead,
which is more efficient for large vectors or matrices.
Similarly to the read method, it is possible to pass a gsl::span or MatrixSpan to the
WriteDataPoint method instead of a reference to a container for numerical vectors and matrices.
This may be useful in situations where the data buffer cannot be instantiated easily as a standard
container object.
For example:
RuntimeRepoIf& repo = ...
repo.SetDataPoint<int32_t>("/mycomp/static/param1"_dppath, 123);
std::vector<float> value2 = {1.2, 3.4, 5.6, 7.8};
repo.WriteDataPoint("/mycomp/static/param2"_dppath, value2);
auto buffer1 = ...;
gsl::span<double> vector_span(buffer1);
repo.WriteDataPoint("/mycomp/static/param3"_dppath, vector_span);
auto buffer2 = ...;
MatrixSpan<double> matrix_span(buffer2);
repo.WriteDataPoint("/mycomp/static/param4"_dppath, matrix_span);
The SetDataPoint and WriteDataPoint methods are blocking,
and will only return to the caller once the data has been sent to the repository.
Similar to the reading methods, a non-blocking option exists with the SendRequest method.
It works in an analogous manner to the SendRequest method described in the previous
Datapoint Reading section.
The SendRequest method accepts a BatchRequest object and returns a BatchResponse object.
All datapoints that should be updated must be added to the BatchRequest object with the
WriteDataPoint method.
The Wait method of the BatchResponse object should be called to synchronise with the request
completion.
The call to Wait will block until the datapoints have been successfully sent to the repository.
The WaitFor method can optionally take a timeout argument.
Warning
The buffers of the datapoints added to the request with the WriteDataPoint method must not
be modified after SendRequest has been called.
Only after the Wait method returns successfully, can the datapoint buffers be modified.
Modifying the contents before a successful invocation of Wait will result in race conditions
and possible data corruption.
The following is an example of using SendRequest for writing:
RuntimeRepoIf& repo = ...
int32_t param1 = ...
std::vector<float> param2 = ...
BatchRequest request;
request.WriteDataPoint("/mycomp/static/param1"_dppath, param1);
request.WriteDataPoint("/mycomp/static/param2"_dppath, param2);
auto response = repo.SendRequest(request);
// Other processing can happen here, but param1 and param2 must not be
// changed ...
response.Wait();
// param1 and param2 can be modified again after the Wait call here ...
Datapoint Querying
To check the data type of a datapoint one can use the GetDataPointType method as follows:
RuntimeRepoIf& repo = ...
auto& type = repo.GetDataPointType("/mycomp/static/param1"_dppath);
This will return the std::type_info object corresponding to the data type as one would get from
the typeid operator.
See the possible C++ types in the Supported Data Types section.
The data size, or more specifically the number of elements for a datapoint,
is retrieved with the GetDataPointSize method.
Note that this will always return the value 1 for basic types such as int32_t or float.
For strings the number of characters is returned, i.e. the length of the string.
For vectors and matrices the total number of elements is returned.
The following is an example of using GetDataPointSize:
RuntimeRepoIf& repo = ...
size_t size = repo.GetDataPointSize("/mycomp/static/param1"_dppath);
It may be necessary to check for the existence of a datapoint.
This can be achieved with the DataPointExists method, which will return true if the
datapoint exists and false otherwise.
For example:
RuntimeRepoIf& repo = ...
if (repo.DataPointExists("/mycomp/static/param1"_dppath)) {
// Can operate on the datapoint here ...
}
There is also a mechanism to query the names of available datapoint paths using the GetChildren
method.
This method takes a datapoint path and lists all the child nodes under the path.
Specifically, it returns a pair of lists.
The first list contains all the datapoints found within the path and the second list contains
all the child paths, i.e. sub-folders.
An optional recurse flag can be set to true to query all paths recursively
under the given path, rather than just the immediate children.
To list only the immediate children of a path:
RuntimeRepoIf& repo = ...
auto [datapoints, child_paths] = repo.GetChildren("/mycomp"_dppath);
To retrieve all datapoints and child paths recursively under a path,
pass true for the recurse flag:
RuntimeRepoIf& repo = ...
auto [datapoints, child_paths] = repo.GetChildren("/mycomp"_dppath, true);
for (auto& dp_path: datapoints) {
std::cout << dp_path << std::endl;
}
The GetChildren method is also available in the BatchRequest API for asynchronous use,
where it accepts the same recurse flag and an optional callback.
The result is stored in a std::pair<PathList, PathList>, where PathList is a type alias
for std::vector<DataPointPath>.
For example:
RuntimeRepoIf& repo = ...
std::pair<PathList, PathList> result;
BatchRequest request;
request.GetChildren("/mycomp"_dppath, result, true);
repo.SendRequest(request).Wait();
for (auto& dp_path: result.first) {
std::cout << dp_path << std::endl;
}
Datapoint Deletion
Existing datapoints are deleted with the DeleteDataPoint method.
For example:
RuntimeRepoIf& repo = ...
repo.DeleteDataPoint("/mycomp/static/param1"_dppath);
An optional recurse flag can be set to true to recursively delete all datapoints
under the given path:
RuntimeRepoIf& repo = ...
repo.DeleteDataPoint("/mycomp/static"_dppath, true);
Command-line/Graphical Manipulation
Manipulating the datapoints in the Runtime Configuration Repository can be performed with the
rtctkConfigTool command line tool, which also supports a graphical user interface.
See section Configuration Tool for details.
This way of accessing the datapoints works for any repository adapter back-end implementation.
Datapoint Subscription
A simple API is available to support subscription to datapoints and registering callbacks for new data updates, datapoint creation and deletion notifications, and subscription error handling.
All subscription callbacks receive MetaData as an argument, providing access to datapoint
metadata such as type, shape, timestamp, and sequence ID.
Additionally, an error callback of type ErrorCallbackType (std::function<void(std::exception_ptr)>)
must be provided for all subscriptions to handle subscription errors
(e.g., lost samples, rejected samples, or deadline misses).
The Subscribe convenience method returns a Subscription RAII object that manages the
subscription’s lifetime.
The subscription is automatically cancelled when the Subscription object is destroyed,
or it can be explicitly cancelled by calling Unsubscribe on the object.
Subscribing to Value Changes
The Subscribe method can be used to register a callback that will be invoked whenever
a new datapoint value is received.
Two callback styles are supported: notification-only callbacks and value callbacks.
Notification-Only Callbacks
A notification-only callback receives the datapoint path and metadata but not the actual value. This is useful for large vector or matrix datapoints where only a change notification is needed and the value can be read on demand.
The callback signature is:
void(const DataPointPath& path, const MetaData& metadata)
Example:
RuntimeRepoIf& repo = ...
auto sub = repo.Subscribe(
"/mycomp/dynamic/large_matrix"_dppath,
[](const DataPointPath& path, const MetaData& metadata) {
// Notification that new data is available for path ...
},
[](std::exception_ptr eptr) {
// Handle subscription error ...
});
// Subscription remains active until 'sub' is destroyed or sub.Unsubscribe() is called.
Value Callbacks
A value callback receives the datapoint path, a const reference to a buffer containing the new value, and the metadata. The API allocates and manages the buffer internally, passing it to the callback by const reference. This is convenient for small scalar or vector datapoint types.
The callback signature is:
void(const DataPointPath& path, const T& buffer, const MetaData& metadata)
Where T is the datapoint type.
Example:
RuntimeRepoIf& repo = ...
auto sub = repo.Subscribe(
"/mycomp/static/param1"_dppath,
[](const DataPointPath& path, const int32_t& value, const MetaData& metadata) {
// New value available: value ...
},
[](std::exception_ptr eptr) {
// Handle subscription error ...
});
The C++ compiler will deduce the template argument T from the callback signature, so no explicit template argument is needed.
Warning
The callback is executed in a different thread than the one calling Subscribe.
Care must be taken when accessing shared state to avoid race conditions.
This applies from the moment the subscription is initiated until the Subscription
object is destroyed or Unsubscribe is called on it.
If other threads must also access shared data, it is up to the user to implement
appropriate synchronisation mechanisms.
Subscribing to Creation and Deletion Events
To receive notifications when datapoints are created or deleted (rather than value changes),
Subscribe can be called with creation and deletion callbacks:
RuntimeRepoIf& repo = ...
auto sub = repo.Subscribe(
[](const DataPointPath& path) {
// Datapoint created at path ...
},
[](const DataPointPath& path) {
// Datapoint deleted at path ...
},
[](std::exception_ptr eptr) {
// Handle subscription error ...
});
Batch Subscriptions
For subscribing to multiple datapoints, or for combining subscriptions with other repository
operations, use the BatchRequest class together with SendRequest.
The BatchRequest class provided by RuntimeRepoIf includes subscription methods
in addition to the read, write, and query operations described earlier.
The Subscribe method on BatchRequest returns a SubscriptionId that can later be
used with Unsubscribe to cancel the subscription.
Example of subscribing to multiple datapoints in a single batch request:
RuntimeRepoIf& repo = ...
auto OnNewMatrix = [](const DataPointPath& path, const MetaData& metadata) {
// Notification for matrix datapoint ...
};
auto OnNewVector = [](const DataPointPath& path, const std::vector<float>& value,
const MetaData& metadata) {
// New vector value available ...
};
auto OnError = [](std::exception_ptr eptr) {
// Handle subscription error ...
};
BatchRequest request;
auto id1 = request.Subscribe("/mycomp/dynamic/matrix"_dppath, OnNewMatrix, OnError);
auto id2 = request.Subscribe("/mycomp/static/vector"_dppath, OnNewVector, OnError);
auto response = repo.SendRequest(request);
response.Wait();
// To unsubscribe later:
BatchRequest unsubRequest;
unsubRequest.Unsubscribe(id1);
unsubRequest.Unsubscribe(id2);
repo.SendRequest(unsubRequest).Wait();
Warning
Callbacks are executed in different threads than the one that called SendRequest.
It is the user’s responsibility to implement synchronisation mechanisms to avoid race
conditions between these threads.
There is no guarantee of ordering if multiple datapoints are updated simultaneously.
Unsubscribing
Subscriptions managed by the Subscription RAII object are automatically cancelled when the
object goes out of scope or is explicitly unsubscribed:
auto sub = repo.Subscribe(
"/mycomp/static/param1"_dppath,
[](const DataPointPath& path, int32_t& value, const MetaData& metadata) {
// ...
},
[](std::exception_ptr eptr) {
// ...
});
// Explicitly unsubscribe before the object goes out of scope:
sub.Unsubscribe();
// Alternatively, let the destructor handle it when 'sub' goes out of scope.
For subscriptions created via BatchRequest, use the Unsubscribe method on
BatchRequest with the SubscriptionId returned by Subscribe;
see the example in the Datapoint Subscription Batch Subscriptions section above.
Supported Data Types
The following is a table of currently supported data types for the Runtime Configuration Repository:
C++ Type |
Internal Type Name |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The indicated C++ type should be used for declaring the data variables in source code and when
identifying the type to use in methods like CreateDataPoint.
The internal type name is the corresponding string representation of the type.
It is stored as meta-data inside the backend to identify the exact type of a
datapoint and also used inside the YAML files when using the file based repository adapters.
The rtctkConfigTool uses the internal type name for its command line arguments.
Composite Datapoints
A Composite Datapoint is the combination of multiple datapoints from a common root path that has been standardised. This section documents these standard Composite Datapoints.
NUMA Policies
This Composite Datapoint defines optional NUMA policies for CPU affinity, scheduling and memory policy.
The parameters are indicated in the following table relative to the Composite Datapoint root path.
For example, if the root path is /mycomp/static/thread_policy, the CPU parameter setting
cpu_affinity would be /mycomp/static/thread_policy/cpu_affinity.
Configuration Path |
Type |
Description |
|---|---|---|
|
|
Sets the optional CPU affinity for the thread.
This is a mask of CPUs on which the thread is allowed to be scheduled.
See the |
|
|
Specifies the optional scheduling policy to apply to the thread with priority as defined in
|
|
|
Indicates the priority or nice-value of the thread.
If |
|
|
Specifies the optional memory allocation policy to apply to the thread and if provided it
must be provided together with |
|
|
Indicates a mask of NUMA nodes to which the memory policy is applied.
See the |
Value |
System API Equivalent |
|---|---|
|
|
|
|
|
|
|
|
Value |
System API Equivalent |
|---|---|
|
|
|
|
|
|
|
|
The following is an example of the configuration parameters in a YAML file for NUMA policies
assuming /mycomp/static/example as root path:
mycomp:
static:
# Configure NUMA policies at Composite Datapoint "example"
example:
cpu_affinity: !cfg.type:string "1-4"
scheduler_policy: !cfg.type:string Fifo
scheduler_priority: !cfg.type:int32 10
memory_policy_mode: !cfg.type:string Bind
memory_policy_nodes: !cfg.type:string "1-4"
Limitations
The Runtime Repository currently supports a single rtctkRuntimeRepoServer process.
This design has proven sufficient to meet all performance requirements encountered so far,
and no need for multiple server processes has arisen.