Skip to content

Commit 64aa941

Browse files
authored
Use realtime mutex and also try-lock while setting feedback (backport ros-controls#490) (ros-controls#494)
1 parent f66ddb6 commit 64aa941

File tree

1 file changed

+52
-28
lines changed

1 file changed

+52
-28
lines changed

realtime_tools/include/realtime_tools/realtime_server_goal_handle.hpp

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,20 @@
3838
#include "rclcpp/logging.hpp"
3939
#include "rclcpp_action/server_goal_handle.hpp"
4040

41+
// prio_inherit_mutex uses pthread APIs not available on Windows
42+
#ifndef _WIN32
43+
#include "realtime_tools/mutex.hpp"
44+
#else
45+
#include <mutex>
46+
#endif
47+
4148
namespace realtime_tools
4249
{
50+
#ifndef _WIN32
51+
using rt_server_goal_handle_mutex = prio_inherit_mutex;
52+
#else
53+
using rt_server_goal_handle_mutex = std::mutex;
54+
#endif
4355
template <class Action>
4456
class RealtimeServerGoalHandle
4557
{
@@ -53,10 +65,8 @@ class RealtimeServerGoalHandle
5365
std::atomic<bool> req_succeed_;
5466
std::atomic<bool> req_execute_;
5567

56-
std::mutex mutex_;
68+
rt_server_goal_handle_mutex mutex_;
5769
ResultSharedPtr req_result_;
58-
// Use raw pointer for lock-free feedback exchange from RT thread
59-
std::atomic<typename Action::Feedback *> req_feedback_ptr_;
6070
FeedbackSharedPtr req_feedback_;
6171
rclcpp::Logger logger_;
6272

@@ -80,7 +90,6 @@ class RealtimeServerGoalHandle
8090
req_cancel_(false),
8191
req_succeed_(false),
8292
req_execute_(false),
83-
req_feedback_ptr_(nullptr),
8493
logger_(logger),
8594
gh_(gh),
8695
preallocated_result_(preallocated_result),
@@ -100,7 +109,7 @@ class RealtimeServerGoalHandle
100109
req_execute_.load(std::memory_order_acquire) &&
101110
!req_succeed_.load(std::memory_order_acquire) &&
102111
!req_abort_.load(std::memory_order_acquire) && !req_cancel_.load(std::memory_order_acquire)) {
103-
std::lock_guard<std::mutex> guard(mutex_);
112+
std::lock_guard<rt_server_goal_handle_mutex> guard(mutex_);
104113

105114
req_result_ = result;
106115
req_abort_.store(true, std::memory_order_release);
@@ -113,7 +122,7 @@ class RealtimeServerGoalHandle
113122
req_execute_.load(std::memory_order_acquire) &&
114123
!req_succeed_.load(std::memory_order_acquire) &&
115124
!req_abort_.load(std::memory_order_acquire) && !req_cancel_.load(std::memory_order_acquire)) {
116-
std::lock_guard<std::mutex> guard(mutex_);
125+
std::lock_guard<rt_server_goal_handle_mutex> guard(mutex_);
117126

118127
req_result_ = result;
119128
req_cancel_.store(true, std::memory_order_release);
@@ -126,7 +135,7 @@ class RealtimeServerGoalHandle
126135
req_execute_.load(std::memory_order_acquire) &&
127136
!req_succeed_.load(std::memory_order_acquire) &&
128137
!req_abort_.load(std::memory_order_acquire) && !req_cancel_.load(std::memory_order_acquire)) {
129-
std::lock_guard<std::mutex> guard(mutex_);
138+
std::lock_guard<rt_server_goal_handle_mutex> guard(mutex_);
130139

131140
req_result_ = result;
132141
req_succeed_.store(true, std::memory_order_release);
@@ -136,27 +145,49 @@ class RealtimeServerGoalHandle
136145
/**
137146
* @brief Set feedback to be published by runNonRealtime().
138147
*
139-
* This method is lock-free and safe to call from real-time context.
140-
* The feedback pointer is atomically exchanged, and the non-RT thread
141-
* will pick it up on the next runNonRealtime() call.
148+
* This method uses a non-blocking lock acquisition with try_to_lock() and is safe
149+
* to call from real-time context without causing thread blocking or priority inversion.
150+
* If the lock cannot be acquired (because runNonRealtime() is holding it), the update
151+
* is silently skipped to avoid blocking the real-time thread.
152+
*
153+
* The feedback pointer is stored and the non-RT thread will publish it on the next
154+
* runNonRealtime() call, provided the goal handle is in executing state.
155+
*
156+
* @param feedback Shared pointer to feedback message. Can be nullptr to clear pending
157+
* feedback. If a valid pointer is provided, the caller must ensure the feedback
158+
* object remains valid until runNonRealtime() processes it. Using a
159+
* preallocated feedback message (stored in preallocated_feedback_) is
160+
* recommended to avoid dynamic allocations in the real-time thread.
161+
*
162+
* @return true if the lock was acquired and feedback was successfully set,
163+
* false if the lock could not be acquired (feedback update was dropped).
142164
*
143-
* @param feedback Shared pointer to feedback message. The caller must ensure
144-
* the feedback object remains valid until runNonRealtime() processes it.
145-
* Using a preallocated feedback message is recommended.
165+
* @note If the mutex lock cannot be acquired, the feedback update is dropped without
166+
* notification. This is intentional to preserve real-time guarantees. Check the
167+
* return value if you need to know whether the update succeeded.
168+
*
169+
* @note Feedback is only published if the goal is currently executing. If feedback
170+
* is set after the goal transitions out of executing state, it will be discarded.
171+
*
172+
* @see runNonRealtime() for the counterpart that publishes the feedback.
173+
* @see preallocated_feedback_ for recommended pre-allocated feedback usage.
146174
*/
147-
void setFeedback(FeedbackSharedPtr feedback = nullptr)
175+
bool setFeedback(FeedbackSharedPtr feedback = nullptr)
148176
{
149-
// Lock-free exchange: store raw pointer for non-RT thread to pick up
150-
// The shared_ptr ensures the object stays alive
151-
req_feedback_ptr_.store(feedback.get(), std::memory_order_release);
177+
std::unique_lock<rt_server_goal_handle_mutex> lock(mutex_, std::try_to_lock);
178+
if (lock.owns_lock()) {
179+
req_feedback_ = feedback;
180+
return true;
181+
}
182+
return false;
152183
}
153184

154185
void execute()
155186
{
156187
if (
157188
!req_succeed_.load(std::memory_order_acquire) &&
158189
!req_abort_.load(std::memory_order_acquire) && !req_cancel_.load(std::memory_order_acquire)) {
159-
std::lock_guard<std::mutex> guard(mutex_);
190+
std::lock_guard<rt_server_goal_handle_mutex> guard(mutex_);
160191
req_execute_.store(true, std::memory_order_release);
161192
}
162193
}
@@ -169,10 +200,7 @@ class RealtimeServerGoalHandle
169200
return;
170201
}
171202

172-
// Read feedback pointer atomically (lock-free read from RT thread's write)
173-
auto * feedback_ptr = req_feedback_ptr_.exchange(nullptr, std::memory_order_acq_rel);
174-
175-
std::lock_guard<std::mutex> guard(mutex_);
203+
std::lock_guard<rt_server_goal_handle_mutex> guard(mutex_);
176204

177205
try {
178206
if (
@@ -192,12 +220,8 @@ class RealtimeServerGoalHandle
192220
gh_->succeed(req_result_);
193221
req_succeed_.store(false, std::memory_order_release);
194222
}
195-
if (feedback_ptr && gh_->is_executing()) {
196-
// Create a non-owning shared_ptr that points to the feedback object
197-
// The original shared_ptr in the caller keeps the object alive
198-
gh_->publish_feedback(
199-
std::shared_ptr<typename Action::Feedback>(
200-
std::shared_ptr<typename Action::Feedback>{}, feedback_ptr));
223+
if (req_feedback_ && gh_->is_executing()) {
224+
gh_->publish_feedback(req_feedback_);
201225
}
202226
} catch (const rclcpp::exceptions::RCLErrorBase & e) {
203227
// Likely invalid state transition

0 commit comments

Comments
 (0)