ros2_control - rolling
Loading...
Searching...
No Matches
realtime_thread_safe_box.hpp
1// Copyright (c) 2009, Willow Garage, Inc.
2// Copyright (c) 2024, Lennart Nachtigall
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// * Redistributions of source code must retain the above copyright
8// notice, this list of conditions and the following disclaimer.
9//
10// * Redistributions in binary form must reproduce the above copyright
11// notice, this list of conditions and the following disclaimer in the
12// documentation and/or other materials provided with the distribution.
13//
14// * Neither the name of the Willow Garage, Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29
30// Author: Stuart Glaser
31// Author: Lennart Nachtigall
32
33#ifndef REALTIME_TOOLS__REALTIME_THREAD_SAFE_BOX_HPP_
34#define REALTIME_TOOLS__REALTIME_THREAD_SAFE_BOX_HPP_
35
36#include <functional>
37#include <initializer_list>
38#include <mutex>
39#include <optional>
40#include <utility>
41
42#include "rcpputils/pointer_traits.hpp"
43#ifndef _WIN32
44#include "realtime_tools/mutex.hpp"
45#define DEFAULT_MUTEX realtime_tools::prio_inherit_mutex
46#define RECURSIVE_MUTEX realtime_tools::prio_inherit_recursive_mutex
47#else
48#define DEFAULT_MUTEX std::mutex
49#define RECURSIVE_MUTEX std::recursive_mutex
50#endif
51
52namespace realtime_tools
53{
54
55template <typename T>
56constexpr auto is_ptr_or_smart_ptr = rcpputils::is_pointer<T>::value;
57
66template <class T, typename mutex_type = DEFAULT_MUTEX>
68{
69 static_assert(std::is_copy_constructible_v<T>, "Passed type must be copy constructible");
70
71public:
72 using mutex_t = mutex_type;
73 using type = T;
74 // Provide various constructors
75 constexpr explicit RealtimeThreadSafeBox(const T & init = T{}) : value_(init) {}
76 constexpr explicit RealtimeThreadSafeBox(const T && init) : value_(std::move(init)) {}
77
78 // Copy constructor
80 {
81 // Lock the other box mutex
82 std::unique_lock<mutex_t> lock(o.lock_);
83 // We do not need to lock our own mutex because we are currently in the process of being created
84 value_ = o.value_;
85 }
86
87 // Copy assignment constructor
88 constexpr RealtimeThreadSafeBox & operator=(const RealtimeThreadSafeBox & o)
89 {
90 // Check for self assignment (and a potential deadlock)
91 if (&o != this) {
92 // Lock the other box mutex
93 std::unique_lock<mutex_t> lock_other(o.lock_);
94 std::unique_lock<mutex_t> lock_self(lock_);
95
96 value_ = o.value_;
97 }
98 return *this;
99 }
100
102 {
103 // Lock the other box mutex
104 std::unique_lock<mutex_t> lock(o.lock_);
105 // We do not need to lock our own mutex because we are currently in the process of being created
106 value_ = std::move(o.value_);
107 }
108
109 // Only enabled for types that can be constructed from an initializer list
110 template <typename U = T>
111 constexpr RealtimeThreadSafeBox(
112 const std::initializer_list<U> & init,
113 std::enable_if_t<std::is_constructible_v<U, std::initializer_list<U>>>)
114 : value_(init)
115 {
116 }
117
118 constexpr RealtimeThreadSafeBox & operator=(RealtimeThreadSafeBox && o)
119 {
120 // Check for self assignment (and a potential deadlock)
121 if (&o != this) {
122 // Lock the other box mutex
123 std::unique_lock<mutex_t> lock_other(o.lock_);
124 std::unique_lock<mutex_t> lock_self(lock_);
125
126 value_ = std::move(o.value_);
127 }
128 return *this;
129 }
130
136 template <typename U = T>
137 typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, bool> try_set(const T & value)
138 {
139 std::unique_lock<mutex_t> guard(lock_, std::defer_lock);
140 if (!guard.try_lock()) {
141 return false;
142 }
143 value_ = value;
144 return true;
145 }
146
152 bool try_set(const std::function<void(T &)> & func)
153 {
154 std::unique_lock<mutex_t> guard(lock_, std::defer_lock);
155 if (!guard.try_lock()) {
156 return false;
157 }
158
159 func(value_);
160 return true;
161 }
162
167 template <typename U = T>
168 [[nodiscard]] typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, std::optional<U>> try_get() const
169 {
170 std::unique_lock<mutex_t> guard(lock_, std::defer_lock);
171 if (!guard.try_lock()) {
172 return std::nullopt;
173 }
174 return value_;
175 }
176
182 bool try_get(const std::function<void(const T &)> & func)
183 {
184 std::unique_lock<mutex_t> guard(lock_, std::defer_lock);
185 if (!guard.try_lock()) {
186 return false;
187 }
188
189 func(value_);
190 return true;
191 }
192
198 template <typename U = T>
199 typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, void> set(const T & value)
200 {
201 std::lock_guard<mutex_t> guard(lock_);
202 // cppcheck-suppress missingReturn
203 value_ = value;
204 }
205
210 template <
211 typename F,
212 typename = std::enable_if_t<std::is_invocable_v<F, T &> && !std::is_invocable_v<F, T>>>
213 void set(F && func)
214 {
215 std::lock_guard<mutex_t> guard(lock_);
216 std::forward<F>(func)(value_);
217 }
218
223 template <typename U = T, typename = std::enable_if_t<is_ptr_or_smart_ptr<U>>>
224 void set(std::nullptr_t)
225 {
226 std::lock_guard<mutex_t> guard(lock_);
227 value_ = nullptr;
228 }
229
234 template <typename U = T>
235 [[nodiscard]] typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, U> get() const
236 {
237 std::lock_guard<mutex_t> guard(lock_);
238 return value_;
239 }
240
245 template <typename U = T>
246 typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, void> get(T & in) const
247 {
248 std::lock_guard<mutex_t> guard(lock_);
249 // cppcheck-suppress missingReturn
250 in = value_;
251 }
252
258 void get(const std::function<void(const T &)> & func)
259 {
260 std::lock_guard<mutex_t> guard(lock_);
261 func(value_);
262 }
263
268 template <typename U = T>
269 typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, void> operator=(const T & value)
270 {
271 set(value);
272 }
273
278 template <typename U = T, typename = typename std::enable_if_t<!is_ptr_or_smart_ptr<U>>>
279 [[nodiscard]] operator T() const
280 {
281 // Only makes sense with the getNonRT method otherwise we would return an std::optional
282 return get();
283 }
284
289 template <typename U = T, typename = typename std::enable_if_t<!is_ptr_or_smart_ptr<U>>>
290 [[nodiscard]] operator std::optional<T>() const
291 {
292 return try_get();
293 }
294
295 // In case one wants to actually use a pointer
296 // in this implementation we allow accessing the lock directly.
297 // Note: Be careful with lock.unlock().
298 // It may only be called from the thread that locked the mutex!
299 [[nodiscard]] const mutex_t & get_mutex() const { return lock_; }
300 [[nodiscard]] mutex_t & get_mutex() { return lock_; }
301
302private:
303 T value_;
304
305 // Protects access to the thing in the box. This mutex is
306 // guaranteed to be locked for no longer than the duration of the
307 // copy, so as long as the copy is realtime safe and the OS has
308 // priority inheritance for mutexes, this lock can be safely locked
309 // from within realtime.
310 mutable mutex_t lock_;
311};
312
313// Provide specialisations for other mutex types
314
315template <typename T>
316using RealtimeThreadSafeBoxRecursive = RealtimeThreadSafeBox<T, RECURSIVE_MUTEX>;
317
318} // namespace realtime_tools
319
320#endif // REALTIME_TOOLS__REALTIME_THREAD_SAFE_BOX_HPP_
Definition realtime_thread_safe_box.hpp:68
bool try_set(const std::function< void(T &)> &func)
access the content readable with best effort
Definition realtime_thread_safe_box.hpp:152
std::enable_if_t<!is_ptr_or_smart_ptr< U >, void > operator=(const T &value)
provide a custom assignment operator for easier usage
Definition realtime_thread_safe_box.hpp:269
void set(F &&func)
wait until the mutex could be locked and access the content (rw)
Definition realtime_thread_safe_box.hpp:213
std::enable_if_t<!is_ptr_or_smart_ptr< U >, void > set(const T &value)
Wait until the mutex can be locked and set the content (RealtimeThreadSafeBox behavior)
Definition realtime_thread_safe_box.hpp:199
std::enable_if_t<!is_ptr_or_smart_ptr< U >, U > get() const
Wait until the mutex could be locked and get the content (RealtimeThreadSafeBox behaviour)
Definition realtime_thread_safe_box.hpp:235
void set(std::nullptr_t)
wait until the mutex could be locked and access the content (rw)
Definition realtime_thread_safe_box.hpp:224
void get(const std::function< void(const T &)> &func)
Wait until the mutex could be locked and access the content (r)
Definition realtime_thread_safe_box.hpp:258
bool try_get(const std::function< void(const T &)> &func)
access the content (r) with best effort
Definition realtime_thread_safe_box.hpp:182
std::enable_if_t<!is_ptr_or_smart_ptr< U >, std::optional< U > > try_get() const
get the content with best effort
Definition realtime_thread_safe_box.hpp:168
std::enable_if_t<!is_ptr_or_smart_ptr< U >, void > get(T &in) const
Wait until the mutex could be locked and get the content (r)
Definition realtime_thread_safe_box.hpp:246
std::enable_if_t<!is_ptr_or_smart_ptr< U >, bool > try_set(const T &value)
set a new content with best effort
Definition realtime_thread_safe_box.hpp:137
A pthread mutex wrapper that provides a mutex with the priority inheritance protocol and a priority c...
Definition async_function_handler.hpp:38