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+
4148namespace 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
4355template <class Action >
4456class 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