4.11. Threading Model

4.11.1. Introduction

This section describes the threading model, the use of critical sections, and how to configure thread parameters in RTI Connext Micro. Please note that the information contained in this document applies to application development using Connext Micro. For information regarding porting the Connext Micro thread API to a new OS, please refer to Porting RTI Connext Micro.

This section includes:

4.11.2. Architectural Overview

RTI Connext Micro consists of a core library and a number of components. The core library provides a porting layer, frequently used data-structures and abstractions, and the DDS API. Components provide additional functionality such as UDP communication, DDS discovery plugins, DDS history caches, etc.

+-------+                                      \
| DDS_C |                                       }  C API
+-------+                                      /

+-------+ +-------+ +------+ +------+          \
| DPSE  | | DPDE  | | WHSM | | RHSM |          |
+-------+ +-------+ +------+ +------+          |
+-------+ +-------+ +------+ +------+ +-----+   } Optional components
| LOOP  | | UDP(*)| | RTPS | | DRI  | | DWI |  |  (platform independent)
+-------+ +-------+ +------+ +------+ +-----+  |
                                               /

+-------+ +-------+ +------+ +------+          \  Core Services (always
| REDA  | |  CDR  | | DB   | | RT   |           } present, platform
+-------+ +-------+ +------+ +------+          /  independent)

+-----------------------------------+          \
|               OSAPI               |           } Platform dependent module
+-----------------------------------+          /

(*) The UDP transport relies on a BSD socket API

4.11.3. Threading Model

RTI Connext Micro is architected in a way that makes it possible to create a port of Connext Micro that uses no threads, for example on platforms with no operating system or where the OS itself creates the threads which calls into Connext Micro. Thus, the following discussion is only applicable to Connext Micro libraries from RTI that runs on operating systems where Connext Micro is allowed to create threads. The platform specific notes provide additional information regarding integration with specific environments.

4.11.3.1. OSAPI Threads

The Connext Micro OSAPI layer creates one thread per OS process. This thread manages all the Connext Micro timers, such as deadline and liveliness timers. This thread is created by the Connext Micro OSAPI System when the OSAPI_System_initialize() function is called. When the Connext Micro DDS API is used DomainParticipantFactory_get_instance() calls this function once.

4.11.3.1.1. Configuring OSAPI Threads

The timer thread is configured through the OSAPI_SystemProperty structure and any changes must be made before OSAPI_System_initialize() is called. In Connext Micro, DomainParticipantFactory_get_instance() calls OSAPI_System_initialize(). Thus, if it is necessary to change the system timer thread settings, it must be done before DomainParticipantFactory_get_instance() is called the first time.

Please refer to OSAPI_Thread for supported thread options. Note that not all options are supported by all platforms.

struct OSAPI_SystemProperty sys_property = OSAPI_SystemProperty_INITIALIZER;

if (!OSAPI_System_get_property(&sys_property))
{
    /* ERROR */
}

/* Please refer to OSAPI_ThreadOptions for possible options */
sys.property.timer_property.thread.options = ....;

/* The stack-size is platform dependent, it is passed directly to the OS */
sys.property.timer_property.thread.stack_size = ....

/* The priority is platform dependent, it is passed directly to the OS */
sys.property.timer_property.thread.priority = ....

if (!OSAPI_System_set_property(&sys_property))
{
    /* ERROR */
}

4.11.3.2. UDP Transport Threads

Of the components that RTI provides, only the UDP component creates threads. The UDP transport creates one receive thread for each unique UDP receive resource. Thus, two UDP threads are created by default:

  • A unicast receive thread for discovery data

  • A unicast receive thread for user-data

Additional threads may be created depending on the transport configuration for a DomainParticipant, DataReader and DataWriter. The UDP transport creates threads based on the following criteria:

  • Each unique unicast port creates a new thread

  • Each unique multicast address and port creates a new thread

For example, if a DataReader specifies its own multicast receive address a new receive thread will be created.

4.11.3.2.1. Configuring UDP Receive Threads

All threads in the UDP transport share the same thread settings. It is important to note that all the UDP properties must be set before the UDP transport is registered. Connext Micro pre-registers the UDP transport with default settings when the DomainParticipantFactory is initialized. To change the UDP thread settings, use the following code.

RT_Registry_T *registry = NULL;
DDS_DomainParticipantFactory *factory = NULL;
struct UDP_InterfaceFactoryProperty *udp_property = NULL;

factory = DDS_DomainParticipantFactory_get_instance();

udp_property = (struct UDP_InterfaceFactoryProperty *)
                    malloc(sizeof(struct UDP_InterfaceFactoryProperty));
*udp_property = UDP_INTERFACE_FACTORY_PROPERTY_DEFAULT;

registry = DDS_DomainParticipantFactory_get_registry(factory);

if (!RT_Registry_unregister(registry, "_udp", NULL, NULL))
{
    /* ERROR */
}

/* Please refer to OSAPI_ThreadOptions for possible options */
udp_property->recv_thread.options = ...;

/* The stack-size is platform dependent, it is passed directly to the OS */
udp_property->recv_thread.stack_size = ....

/* The priority is platform dependent, it is passed directly to the OS */
udp_property->recv_thread.priority = ....

if (!RT_Registry_register(registry, "_udp",
                          UDP_InterfaceFactory_get_interface(),
                          (struct RT_ComponentFactoryProperty*)udp_property,
                          NULL))
{
    /* ERROR */
}

4.11.3.3. General Thread Configuration

The Connext Micro architecture consists of a number of components and layers, and each layer and component has its own properties. It is important to remember that the layers and components are configured independently of each other, as opposed to configuring everything through DDS. This design makes it possible to relatively easily swap out one part of the library for another.

All threads created based on Connext Micro OSAPI APIs use the same OSAPI_ThreadProperty structure.

4.11.4. Thread-Safety

All public APIs have a note about thread-safety included in the API reference manuals. It is important that an application does not violate thread-safety guidelines.

RTI Connext Micro may create multiple threads, but from an application point of view, there is only a single critical section protecting all DDS resources within a DomainParticipant.

Note

Although Connext Micro may create multiple mutexes, these are used to protect resources in the OSAPI layer, and are thus not relevant when using the public DDS APIs.

4.11.4.1. Calling DDS APIs from listeners and callbacks

When DDS is executing in a listener, it holds a critical section. Thus it is important to return as quickly as possible to avoid stalling network I/O.

There are no deadlock scenarios when a listener calls Connext Micro DDS APIs from the same DomainParticipant (and contained entities) that the listener was called from, as shown in the diagram below:

../_images/ThreadSafety-OK.png

Warning

It is not safe to call DDS APIs from a different DomainParticipant than the one listener was called from, as shown in the diagram below. This may result in a deadlock situation.

../_images/ThreadSafety-NOK.png

Warning

There are no checks on whether or not an API call will cause problems, such as deleting a participant when processing data in on_data_available from a reader within the same participant.

4.11.4.2. Calling DDS APIs from a type-plugin

A user type-plugin that is registered with the DomainParticipant is subject to the following rules:

  • The key kind is constant.

  • The plugin is constant for a given DDS entity (Topic, DataWriter, or DataReader).

  • The plugin data must be protected if thread safety is a concern, as it is user data and not protected by Connext Micro.

Note

A type-plugin generated from an IDL file with the rtiddsgen IDL compiler included with Connext Micro will satisfy these rules.