10. Software Development Kit
You can extend the out-of-the-box behavior of Routing Service through its Software Development Kit (SDK). The SDK provides a set of public interfaces that allow you to control Routing Service execution as well as extend its capabilities.
10.1. Routing Service SDK Modules
The SDK is divided in the following modules:
RTI Routing Service Library API:
This module offers a set of APIs that allow you to instantiate Routing Service instances in your application. This allows you to run Routing Service as a library, as described in Routing Service Library.RTI Routing Service Adapter API:
Adapters are pluggable components that allow Routing Service to consume and produce data for different data domains (e.g. Connext, MQTT, raw Socket, etc.). This module offers a set of pluggable APIs to develop custom Adapters, which you can use through shared libraries or through the Library API. By default, Routing Service is distributed with a builtin DDS adapter that is part of the service library.RTI Routing Service Processor API:
Processors are event-oriented pluggable components that allow you to control the forwarding process that occurs within a Route. This module offers a set of pluggable APIs to develop custom Processors, which you can use through shared libraries or through the Library API.RTI Routing Service Transformation API:
Transformations are data-oriented pluggable components that allow you to perform conversions of the representation and content of the data that goes through Routing Service. This module offers a set of pluggable APIs to develop custom Transformations, which you can use through shared libraries or through the Library API.
Table 10.1 shows which modules are available for each API, along with links to the API documentation.
Language API |
Available Modules |
|---|---|
|
|
|
|
|
10.2. Error Handling in SDK Plugins
This section describes error handling patterns and best practices for developing custom Adapters, Processors, and Transformations. Proper error handling ensures that Routing Service can respond appropriately to failures and provide meaningful diagnostics.
10.2.1. Overview
SDK plugins (Adapters, Processors, and Transformations) can encounter errors during their lifecycle, such as:
Resource allocation failures (memory, file handles, network connections)
Invalid configuration parameters
Runtime errors during data processing
External service unavailability
Internal state inconsistencies
The SDK provides different error handling mechanisms depending on the language API:
C API uses the
RTI_RoutingServiceEnvironmentparameter to report errors.C++ API uses C++ exceptions that are automatically caught and converted.
Java API uses Java exceptions that are automatically caught and converted.
10.2.2. Error handling in C plugins
C-based plugins receive an RTI_RoutingServiceEnvironment pointer parameter
in most callback functions. This environment object is used to report errors
back to Routing Service.
10.2.2.1. Setting errors
To report an error from a C plugin callback:
Call
RTI_RoutingServiceEnvironment_set_error()with a descriptive message.Return
NULL(for creation functions) or an error value as appropriate.
struct RTI_RoutingServiceAdapterPlugin *
MyAdapter_create_adapter_plugin(
const struct RTI_RoutingServiceProperties *properties,
RTI_RoutingServiceEnvironment *env)
{
struct MyAdapterPlugin *adapter = NULL;
adapter = (struct MyAdapterPlugin*) calloc(1, sizeof(struct MyAdapterPlugin));
if (adapter == NULL) {
RTI_RoutingServiceEnvironment_set_error(
env,
"Memory allocation failed for adapter plugin");
return NULL;
}
/* Initialize adapter... */
if (!MyAdapter_initialize(adapter, properties)) {
RTI_RoutingServiceEnvironment_set_error(
env,
"Failed to initialize adapter with provided properties");
free(adapter);
return NULL;
}
return (struct RTI_RoutingServiceAdapterPlugin*) adapter;
}
10.2.2.2. Fatal errors
In rare cases where a plugin encounters an unrecoverable error that should
terminate Routing Service, use RTI_RoutingServiceEnvironment_fatal_error():
void MyStreamWriter_write(
RTI_RoutingServiceStreamWriter stream_writer,
const RTI_RoutingServiceSample *sample_list,
const RTI_RoutingServiceSampleInfo *info_list,
int count,
RTI_RoutingServiceEnvironment *env)
{
/* Critical corruption detected */
if (detect_data_corruption()) {
RTI_RoutingServiceEnvironment_fatal_error(
env,
"Critical data corruption detected in stream writer");
return;
}
/* Normal write logic... */
}
Warning
Use fatal_error() only for truly unrecoverable situations. Regular
errors should use set_error() and allow Routing Service to handle the failure
gracefully.
Note
When a fatal error occurs, Routing Service invokes the on_shutdown callback
function of the registered shutdown hook.
When using Routing Service as an application: The default shutdown hook terminates the application, stopping all Routing Service execution immediately.
When using Routing Service as a library: The behavior depends on what is programmed in the
on_shutdowncallback function. The developer using the library is responsible for implementing appropriate shutdown logic (e.g., setting a flag, cleaning up resources, or stopping the service).
See Routing Service Library for more information on using Routing Service as a library and configuring shutdown hooks.
10.2.2.3. Checking for errors
Utility functions allow plugins to check if an error has been set:
if (RTI_RoutingServiceEnvironment_error_occurred(env)) {
const char *error_msg = RTI_RoutingServiceEnvironment_get_error_message(env);
/* Log or handle error... */
}
To clear an error (use with caution):
RTI_RoutingServiceEnvironment_clear_error(env);
10.2.3. Error handling in C++ plugins
C++ plugins use standard C++ exception handling. The SDK automatically catches exceptions thrown by plugin code and converts them to appropriate error indications for Routing Service.
10.2.3.1. Throwing exceptions
C++ plugins should throw exceptions derived from std::exception to report
errors:
class MyAdapter : public rti::routing::adapter::AdapterPlugin {
public:
MyAdapter(const rti::routing::PropertySet& properties)
{
// Validate required properties
if (properties.find("server_url") == properties.end()) {
throw std::invalid_argument(
"Missing required property 'server_url'");
}
// Attempt connection
if (!connect(properties.at("server_url"))) {
throw std::runtime_error(
"Failed to connect to server: " +
properties.at("server_url"));
}
}
// Other methods...
};
10.2.3.2. Fatal exceptions
For unrecoverable errors that should terminate Routing Service, throw
rti::apputils::FatalServiceException:
void MyStreamWriter::write(
const std::vector<dds::core::xtypes::DynamicData>& samples,
const std::vector<dds::sub::SampleInfo>& infos)
{
if (detect_critical_failure()) {
throw rti::apputils::FatalServiceException(
"Critical internal state corruption detected");
}
// Normal write logic...
}
Note
FatalServiceException is converted by the SDK into a fatal error that
causes Routing Service to invoke the shutdown hook’s on_shutdown callback.
When using Routing Service as an application: The default shutdown hook terminates the application, stopping all Routing Service execution immediately.
When using Routing Service as a library: The behavior depends on what is programmed in the
on_shutdowncallback function. The developer using the library is responsible for implementing appropriate shutdown logic (e.g., setting a flag, cleaning up resources, or stopping the service).
Reserve FatalServiceException strictly for cases where continuing
operation is unsafe or impossible.
See Routing Service Library for more information on using Routing Service as a library and configuring shutdown hooks.
10.2.3.3. Exception safety
Plugin implementations should follow C++ exception safety guidelines:
Constructors can throw exceptions to indicate creation failure.
Destructors must not throw exceptions (
noexcept).Resource Management use RAII to ensure resources are cleaned up even when exceptions occur.
class MyConnection : public rti::routing::adapter::Connection {
std::unique_ptr<NetworkSocket> socket_;
public:
MyConnection(const std::string& address)
{
socket_ = std::make_unique<NetworkSocket>();
if (!socket_->connect(address)) {
// RAII ensures socket_ is automatically cleaned up
throw std::runtime_error("Failed to connect to " + address);
}
}
~MyConnection() noexcept
{
// Must not throw
try {
if (socket_) {
socket_->close();
}
} catch (...) {
// Log but don't propagate
}
}
};
10.2.4. Error handling in different plugin phases
10.2.4.1. Plugin creation (entry point)
Errors during plugin creation (the entry point function) prevent the plugin from being loaded:
C API:
struct RTI_RoutingServiceAdapterPlugin *
MyAdapter_create_adapter_plugin(
const struct RTI_RoutingServiceProperties *properties,
RTI_RoutingServiceEnvironment *env)
{
if (properties == NULL || properties->count == 0) {
RTI_RoutingServiceEnvironment_set_error(
env,
"Adapter requires configuration properties");
return NULL; /* Plugin will not be loaded */
}
/* ... */
}
C++ API:
RTI_ADAPTER_PLUGIN_CREATE_FUNCTION_DEF(MyAdapter)
// Exception thrown in constructor will prevent plugin from loading
10.2.4.2. Connection and Session creation
Errors during connection or session creation prevent those entities from being created, but do not affect the plugin itself:
C++ Example:
rti::routing::adapter::Connection* MyAdapter::create_connection(
const rti::routing::PropertySet& properties)
{
try {
return new MyConnection(properties);
} catch (const std::exception& ex) {
// Error is propagated; connection will not be created
throw;
}
}
10.2.4.3. Stream creation
Errors during stream reader/writer creation prevent those streams from being created:
rti::routing::adapter::StreamReader* MyConnection::create_stream_reader(
const rti::routing::StreamInfo& info,
const rti::routing::PropertySet& properties)
{
if (!validate_stream_info(info)) {
throw std::invalid_argument(
"Invalid stream configuration: " + info.stream_name());
}
return new MyStreamReader(info, properties);
}
10.2.4.4. Runtime operations
Errors during runtime operations (read, write, process, transform) are logged but generally do not terminate the route or session:
void MyStreamWriter::write(
const std::vector<dds::core::xtypes::DynamicData>& samples,
const std::vector<dds::sub::SampleInfo>& infos)
{
for (const auto& sample : samples) {
try {
write_sample(sample);
} catch (const TransientNetworkError& ex) {
// Log error but continue processing remaining samples
log_warning("Failed to write sample: " + ex.what());
// Error is reported but processing continues
throw; // SDK will log and continue with next write
}
}
}
10.2.4.5. Update operations
Errors during update operations (when configuration changes are applied) prevent the update from taking effect:
void MyStreamWriter::update(const rti::routing::PropertySet& properties)
{
// Validate new properties before applying
if (!validate_properties(properties)) {
throw std::invalid_argument(
"Invalid properties for update: missing 'timeout'");
// Update will not be applied; previous configuration remains
}
apply_properties(properties);
}
10.2.5. Best practices
10.2.5.1. Error messages
Provide clear, actionable error messages that help diagnose the problem:
Good:
throw std::runtime_error(
"Failed to connect to MQTT broker at 'mqtt://localhost:1883': "
"Connection refused (errno: 111). "
"Verify broker is running and address is correct.");
Poor:
throw std::runtime_error("Connection failed");
10.2.5.2. Error context
Include relevant context in error messages:
void MyProcessor::on_data_available(rti::routing::processor::Route& route)
{
try {
process_data(route);
} catch (const std::exception& ex) {
std::string error_msg =
"Error processing data in route '" + route.name() +
"': " + ex.what();
throw std::runtime_error(error_msg);
}
}
10.2.5.3. Resource cleanup
Ensure resources are properly cleaned up even when errors occur:
C API:
RTI_RoutingServiceConnection MyAdapter_create_connection(
struct RTI_RoutingServiceAdapterPlugin *plugin,
/* ... */
RTI_RoutingServiceEnvironment *env)
{
struct MyConnection *connection = NULL;
struct NetworkHandle *network = NULL;
connection = calloc(1, sizeof(struct MyConnection));
if (connection == NULL) {
RTI_RoutingServiceEnvironment_set_error(env, "Out of memory");
return NULL;
}
network = network_create();
if (network == NULL) {
RTI_RoutingServiceEnvironment_set_error(
env,
"Failed to create network handle");
free(connection); /* Clean up already-allocated resource */
return NULL;
}
connection->network = network;
return connection;
}
C++ API (using RAII):
class MyConnection : public rti::routing::adapter::Connection {
std::unique_ptr<NetworkHandle> network_;
std::unique_ptr<CacheManager> cache_;
public:
MyConnection()
{
network_ = std::make_unique<NetworkHandle>();
// If next line throws, network_ is automatically cleaned up
cache_ = std::make_unique<CacheManager>(network_.get());
}
};
10.2.5.4. Validation
Validate configuration early (during creation) rather than deferring to runtime:
MyAdapter::MyAdapter(const rti::routing::PropertySet& properties)
{
// Validate all required properties up front
const std::vector<std::string> required = {
"server_url", "port", "protocol"
};
for (const auto& prop : required) {
if (properties.find(prop) == properties.end()) {
throw std::invalid_argument(
"Missing required property: " + prop);
}
}
// Validate property values
int port = std::stoi(properties.at("port"));
if (port < 1 || port > 65535) {
throw std::out_of_range(
"Invalid port number: " + std::to_string(port));
}
}
10.2.5.5. Transient vs fatal errors
Use regular errors for transient failures that Routing Service can recover from, and fatal errors only for unrecoverable situations:
Transient (Regular) Errors:
Network timeouts
Temporary resource unavailability
Invalid individual samples
Recoverable I/O errors
Fatal Errors:
Memory corruption detected
Critical internal state inconsistency
Unrecoverable plugin state
System-level resource exhaustion
10.2.5.6. Debugging
During development, enable Routing Service verbosity to see detailed error information:
rtiroutingservice -cfgFile my_config.xml -cfgName MyService -verbosity 4
When enabled, Routing Service displays detailed error messages from your plugin, including the full exception messages and stack traces where available.
10.2.6. Common error scenarios
10.2.6.1. Configuration errors
MyAdapter::MyAdapter(const rti::routing::PropertySet& properties)
{
if (properties.find("required_param") == properties.end()) {
throw std::invalid_argument(
"Configuration error: 'required_param' is mandatory. "
"Add <property><name>required_param</name>"
"<value>...</value></property> to your adapter configuration.");
}
}
10.2.6.2. Resource allocation failures
rti::routing::adapter::StreamReader* MyConnection::create_stream_reader(
const rti::routing::StreamInfo& info,
const rti::routing::PropertySet& properties)
{
try {
return new MyStreamReader(info, properties);
} catch (const std::bad_alloc&) {
throw std::runtime_error(
"Failed to allocate memory for stream reader '" +
info.stream_name() + "'. System may be out of memory.");
}
}
10.2.6.4. Invalid data
void MyTransformation::transform(
std::vector<dds::core::xtypes::DynamicData>& output_samples,
const std::vector<dds::core::xtypes::DynamicData>& input_samples)
{
for (const auto& input : input_samples) {
if (!validate_sample(input)) {
// Skip invalid samples with warning, don't fail entire batch
log_warning("Skipping invalid input sample");
continue;
}
output_samples.push_back(transform_sample(input));
}
}