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.

Table 10.1 API Documentation for the SDK

Language API

Available Modules

RTI Routing Service C API

  • Library

  • Adapter

  • Processor

  • Transformation

RTI Routing Service C++ API

  • Library

  • Adapter

  • Processor

  • Transformation

RTI Routing Service Java API

  • Library

  • Adapter

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_RoutingServiceEnvironment parameter 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:

  1. Call RTI_RoutingServiceEnvironment_set_error() with a descriptive message.

  2. 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_shutdown callback 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_shutdown callback 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.3. External service unavailability

void MyStreamWriter::write(
        const std::vector<dds::core::xtypes::DynamicData>& samples,
        const std::vector<dds::sub::SampleInfo>& infos)
{
    if (!is_connected()) {
        throw std::runtime_error(
                "Cannot write samples: connection to external service lost. "
                "Will attempt to reconnect on next write.");
    }
    // Write logic...
}

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));
    }
}