Skip to content

Commit c1b9943

Browse files
authored
Add Node to Participant mapping article (#250)
Signed-off-by: ivanpauno <[email protected]>
1 parent fca6752 commit c1b9943

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
layout: default
3+
title: Node to Participant mapping
4+
permalink: articles/Node_to_Participant_mapping.html
5+
abstract: This article analyzes the performance implications of enforcing a one-to-one mapping between ROS Nodes and DDS Participants, and proposes alternative implementation approaches.
6+
author: '[Ivan Paunovic](https://github.com/ivanpauno)'
7+
published: true
8+
categories: Middleware
9+
---
10+
11+
{:toc}
12+
13+
# {{ page.title }}
14+
15+
<div class="abstract" markdown="1">
16+
{{ page.abstract }}
17+
</div>
18+
19+
Original Author: {{ page.author }}
20+
21+
## Background
22+
23+
### Node
24+
25+
In ROS, a Node is an entity used to group other entities.
26+
For example: Publishers, Subscriptions, Servers, Clients.
27+
Nodes ease organization and code reuse, as they can be composed in different ways.
28+
29+
### Domain Participant
30+
31+
A Participant is a type of DDS entity.
32+
Participant also group other entities, like Publishers, Subscribers, Data Writters, Data Readers, etc.
33+
Creating more Participants adds overhead to an application:
34+
35+
- Each Participant participates in discovery.
36+
Creating more than one Participant usually increases CPU usage and network IO load.
37+
- Each Participant keeps track of other DDS entities.
38+
Using more than one within a single process may result in data duplication.
39+
- Each Participant may create multiple threads for event handling, discovery, etc.
40+
The number of threads created per Participant depends on the DDS vendor (e.g.: [RTI Connext](https://community.rti.com/best-practices/create-few-domainParticipants-possible)).
41+
42+
For those reasons, a Participant is a heavyweight entity.
43+
44+
Note: This might actually depend on the DDS vendor, some of them share these resources between Participants (e.g. `OpenSplice`).
45+
Many DDS vendors, however, do not perform this kind of optimization (e.g.: `RTI Connext` and `Fast-RTPS`), and actually recommend creating just one Participant per process.
46+
47+
### Context
48+
49+
In ROS, a Context is the non-global state of an init-shutdown cycle.
50+
It also encapsulates shared state between Nodes and other entities.
51+
In most applications, there is only one ROS Context in a process.
52+
53+
## Behavior pre-Foxy
54+
55+
There is a one-to-one mapping between Nodes and DDS Participants.
56+
This simplified the original implementation, as DDS Participants provide many features equivalent to the ones of ROS Nodes.
57+
The drawback of this approach is the overhead that comes with creating many Participants.
58+
Furthermore, the maximum number of Domain Participants is rather small.
59+
For example, in [RTI Connext](https://community.rti.com/kb/what-maximum-number-Participants-domain) it is limited to 120 Participants per Domain.
60+
61+
## Proposed approach
62+
63+
The goal of this proposal is to improve overall performance by avoiding the creation of one Participant per Node.
64+
API changes will be avoided, if possible.
65+
66+
### Mapping of DDS Participant to a ROS entity
67+
68+
There are two alternatives, besides the one-to-one Node to Participant mapping used pre-Foxy:
69+
- Using one Participant per process.
70+
- Using one Participant per Context.
71+
72+
The second approach is much more flexible, allowing more than one Participant in a single application for those that need it e.g. domain bridge applications.
73+
Thus, a one-to-one Participant to Context mapping was chosen.
74+
75+
When multiple Nodes are running in a single process, there are different options for grouping them by - ranging from a separate context for each Node, over grouping a few Nodes in the same context, to using a single context for all Nodes.
76+
For most applications, only one Context is created.
77+
78+
### Discovery information
79+
80+
If a one-to-one Node to Participant mapping is not used, extra discovery information is needed to be able to match other entities to a Node e.g. Publishers, Subscriptions, etc.
81+
Several approaches can be used to share this information.
82+
The proposed approach uses a topic for it.
83+
Each Participant publishes a message with all the information needed to match an entity to a Node.
84+
The message structure is the following:
85+
86+
* ParticipantInfo
87+
* gid
88+
* NodeInfo
89+
* Node namespace
90+
* Node name
91+
* Reader gid
92+
* writed gid
93+
94+
When one entity is updated (e.g.: a Publisher is created or destroyed), a new message is sent.
95+
96+
Identification of Clients and Servers happens according to the ROS conventions for their topic names (see [
97+
Topic and Service name mapping to DDS](140_topic_and_service_name_mapping.md)).
98+
99+
This topic is considered an implementation detail, and not all `rmw` implementations have to use it.
100+
Thus, all the necessary logic has to be in the rmw implementation itself or in an upstream package.
101+
Implementing this logic in `rcl` would make it part of the API, and not an implementation detail.
102+
103+
To avoid code repetition, a common implementation of this logic is provided by the [rmw_dds_common](https://github.com/ros2/rmw_dds_common/) package.
104+
105+
#### Details of the ROS discovery topic
106+
107+
- topic name: `ros_discovery_info`
108+
- Writer qos:
109+
- durability: transient local
110+
- history: keep last
111+
- history depth: 1
112+
- reliability: reliable
113+
- Reader qos:
114+
- durability: transient local
115+
- history: keep all
116+
- history depth: 1
117+
- reliability: reliable
118+
119+
## Other implications
120+
121+
#### Security
122+
123+
Previously, each Node could have different security artifacts.
124+
That was possible because each Node was mapped to one Participant.
125+
The new approach allows to specify different security artifacts for each process.
126+
For more details, see [ROS 2 Security Enclaves](ros2_security_enclaves.md).
127+
128+
#### Ignore local publications option
129+
130+
There is an `ignore_local_publications` option that can be set when [creating a Subscription](https://github.com/ros2/rmw/blob/2250b3eee645d90f9e9d6c96d71ce3aada9944f3/rmw/include/rmw/rmw.h#L517).
131+
That option avoids receiving messages from Publishers within the same Node.
132+
This wasn't implemented in all the rmw implementations (e.g.: [FastRTPS](https://github.com/ros2/rmw_fastrtps/blob/099f9eed9a0f581447405fbd877c6d3b15f1f26e/rmw_fastrtps_cpp/src/rmw_Subscription.cpp#L118)).
133+
134+
After this change, implementing this feature will be less direct.
135+
Some extra logic needs to be added in order to identify from which Node a Publisher was created.
136+
137+
## Alternative implementations
138+
139+
### Using keyed topics
140+
141+
Keyed topics could be used, with the Participant gid as the key.
142+
That would allow the Reader side of the topic to use a keep last, depth 1 history.
143+
144+
### Using Participant/Writer/Reader userData QoS
145+
146+
Instead of using a custom topic to share the extra ROS specific discovery information, a combination of Participant/Reader/Writer `userData` could be used:
147+
148+
- Participant `userData`: list of Nodes created by the Participant (updated when Node created destroyed).
149+
- Reader user `userData`: Node name/namespace.
150+
- Writer user `userData`: Node name/namespace.
151+
152+
That information would be enough to satisfy ROS required graph API.
153+
This mechanism would also preclude having a topic that has to be read and written by all Nodes, which is better from a security perspective.
154+
155+
This alternative wasn't implemented because of lack of support in some of the currently supported DDS vendors.
156+
157+
## Further work
158+
159+
This optimization has been applied to `rmw_cyclonedds`, `rmw_fastrtps_cpp` and `rmw_fastrtps_dynamic_cpp`.
160+
Other DDS based `rmw` implementations, like `rmw_connext_cpp` could use the same approach.

0 commit comments

Comments
 (0)