You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
To enable Thread Safety Analysis, code must be annotated to let the compiler know more about the smantics of the code. These annotations are Clang-specific attributes - e.g. ``__atribute__(capability()))``. Instead of using those attributes directly, ROS2 provides preprocessor macros that are erased when using other compilers.
144
+
145
+
These macros can be found in `rcpputils/thread_safety_annotations.h <https://github.com/ros2/rcpputils/blob/master/include/rcpputils/thread_safety_annotations.h>`__
146
+
147
+
The Thread Safety Analysis documentation states
148
+
Thread safety analysis can be used with any threading library, but it does require that the threading API be wrapped in classes and methods which have the appropriate annotations
149
+
150
+
We have decided that we want ROS2 developers to be able to use ``std::`` threading primitives directly for their development. We do not want to provide our own wrapped types as is suggested above.
151
+
152
+
There are three C++ standard libraries to be aware of
153
+
* The GNU standard library ``libstdc++`` - default on Linux, explicitly via the compiler option ``-stdlib=libstdc++``
154
+
* The LLVM standard library ``libc++`` (also called ``libcxx`` ) - default on macOS, explicitly set by the compiler option ``-stdlib=libc++``
155
+
* The Windows C++ Standard Library - not relevant to this use case
156
+
157
+
``libcxx`` annotates its ``std::mutex`` and ``std::lock_guard`` implementations for Thread Safety Analysis. When using GNU ``libstdc++`` , those annotations are not present, so Thread Safety Analysis cannot be used on non-wrapped ``std::`` types.
158
+
159
+
*Therefore, to use Thread Safety Analysis directly with* ``std::`` *types, we must use* ``libcxx``
160
+
161
+
162
+
**Implementation:**
163
+
164
+
165
+
The code migration suggestions here are by no means complete - when writing (or annotating existing) threaded code, you are encouraged to utilize as many of the annotations as is logical for your use case. However, this step-by-step is a great place to start!
166
+
167
+
* Enabling Analysis for Package/Target
168
+
169
+
When the C++ compiler is Clang, enable the ``-Wthread-safety`` flag. Example below for CMake-based projects
170
+
171
+
.. code-block:: cmake
172
+
173
+
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
174
+
add_compile_options(-Wthread-safety) # for your whole package
175
+
target_compile_options(${MY_TARGET} PUBLIC -Wthread-safety) # for a single library or executable
176
+
endif()
177
+
178
+
* Annotating Code
179
+
180
+
* Step 1 - Annotate data members
181
+
182
+
* Find anywhere that ``std::mutex`` is used to protect some member data
183
+
* Add the ``RCPPUTILS_TSA_GUARDED_BY(mutex_name)`` annotation to the data that is protected by the mutex
184
+
185
+
.. code-block:: cpp
186
+
:emphasize-lines: 14
187
+
188
+
class Foo {
189
+
public:
190
+
void incr(int amount) {
191
+
std::lock_guard<std::mutex> lock(mutex_);
192
+
bar += amount;
193
+
}
194
+
195
+
void get() const {
196
+
return bar;
197
+
}
198
+
199
+
private:
200
+
mutable std::mutex mutex_;
201
+
int bar RCPPUTILS_TSA_GUARDED_BY(mutex_) = 0;
202
+
};
203
+
204
+
* Step 2 - Fix Warnings
205
+
206
+
* In the above example - ``Foo::get`` will produce a compiler warning! To fix it, lock before returning bar
207
+
208
+
.. code-block:: cpp
209
+
:emphasize-lines: 2
210
+
211
+
void get() const {
212
+
std::lock_guard<std::mutex> lock(mutex_);
213
+
return bar;
214
+
}
215
+
216
+
* Step 3 - (Optional but Recommended) Refactor Existing Code to Private-Mutex Pattern
217
+
218
+
A recommended pattern in threaded C++ code is to always keep your ``mutex`` as a ``private:`` member of the data structure. This makes data safety the concern of the containing structure, offloading that responsibility from users of the structure and minimizing the surface area of affected code.
219
+
220
+
Making your locks private may require rethinking the interfaces to your data. This is a great exercise - here are a few things to consider
221
+
222
+
* You may want to provide specialized interfaces for performing analysis that requires complex locking logic, e.g. counting members in a filtered set of a mutex-guarded map structure, instead of actually returning the underlying structure to consumers
223
+
* Consider copying to avoid blocking, where the amount of data is small. This can let other threads get on with accessing the shared data, which can potentially lead to better overall performance.
Negative Capability Analysis lets you specify “this lock must not be held when calling this function”. It can reveal potential deadlock cases that other annotations cannot.
230
+
231
+
* Where you specified ``-Wthread-safety``, add the additional flag ``-Wthread-safety-negative``
232
+
* On any function that acquires a lock, use the ``RCPPUTILS_TSA_REQUIRES(!mutex)`` pattern
233
+
234
+
235
+
236
+
* How to run the analysis
237
+
238
+
* The ROS CI build farm runs a nightly job with ``libcxx``, which will surface any issues in the ROS2 core stack by being marked "Unstable" when Thread Safety Analysis raises warnings
239
+
* For local runs, you have the following options, all equivalent
240
+
241
+
* Use the colcon `clang-libcxx mixin <https://github.com/colcon/colcon-mixin-repository/blob/master/clang-libcxx.mixin>`__
242
+
243
+
* ``colcon build --mixin clang-libcxx``
244
+
* You may only use this if you have `configured mixins for your colcon installation <https://github.com/colcon/colcon-mixin-repository/blob/master/README.md>`__
0 commit comments