@@ -405,12 +405,22 @@ class Client : public ClientBase
405405
406406 // / Send an action goal and asynchronously get the result.
407407 /* *
408- * If the goal is accepted by an action server, the returned future is set to a `ClientGoalHandle `.
408+ * If the goal is accepted by an action server, the returned future is set to a `GoalHandle::SharedPtr `.
409409 * If the goal is rejected by an action server, then the future is set to a `nullptr`.
410410 *
411- * The returned goal handle is used to monitor the status of the goal and get the final result.
412- * It is valid as long as you hold a reference to the shared pointer or until the
413- * rclcpp_action::Client is destroyed at which point the goal status will become UNKNOWN.
411+ * The goal handle in the future is used to monitor the status of the goal and get the final result.
412+ *
413+ * If callbacks were set in @param options, you will receive callbacks, as long as you hold a reference
414+ * to the shared pointer contained in the returned future, or rclcpp_action::Client is destroyed. Dropping
415+ * the shared pointer to the goal handle will not cancel the goal. In order to cancel it, you must explicitly
416+ * call async_cancel_goal.
417+ *
418+ * WARNING this method has inconsistent behaviour and a memory leak bug.
419+ * If you set the result callback in @param options, the handle will be self referencing, and you will receive
420+ * callbacks even though you do not hold a reference to the shared pointer. In this case, the self reference will
421+ * be deleted if the result callback was received. If there is no result callback, there will be a memory leak.
422+ *
423+ * To prevent the memory leak, you may call stop_callbacks() explicit. This will delete the self reference.
414424 *
415425 * \param[in] goal The goal request.
416426 * \param[in] options Options for sending the goal request. Contains references to callbacks for
@@ -448,7 +458,7 @@ class Client : public ClientBase
448458 std::shared_ptr<GoalHandle> goal_handle (
449459 new GoalHandle (goal_info, options.feedback_callback , options.result_callback ));
450460 {
451- std::lock_guard<std::mutex > guard (goal_handles_mutex_);
461+ std::lock_guard<std::recursive_mutex > guard (goal_handles_mutex_);
452462 goal_handles_[goal_handle->get_goal_id ()] = goal_handle;
453463 }
454464 promise->set_value (goal_handle);
@@ -466,7 +476,7 @@ class Client : public ClientBase
466476 // To prevent the list from growing out of control, forget about any goals
467477 // with no more user references
468478 {
469- std::lock_guard<std::mutex > guard (goal_handles_mutex_);
479+ std::lock_guard<std::recursive_mutex > guard (goal_handles_mutex_);
470480 auto goal_handle_it = goal_handles_.begin ();
471481 while (goal_handle_it != goal_handles_.end ()) {
472482 if (!goal_handle_it->second .lock ()) {
@@ -496,7 +506,7 @@ class Client : public ClientBase
496506 typename GoalHandle::SharedPtr goal_handle,
497507 ResultCallback result_callback = nullptr )
498508 {
499- std::lock_guard<std::mutex > lock (goal_handles_mutex_);
509+ std::lock_guard<std::recursive_mutex > lock (goal_handles_mutex_);
500510 if (goal_handles_.count (goal_handle->get_goal_id ()) == 0 ) {
501511 throw exceptions::UnknownGoalHandleError ();
502512 }
@@ -531,7 +541,7 @@ class Client : public ClientBase
531541 typename GoalHandle::SharedPtr goal_handle,
532542 CancelCallback cancel_callback = nullptr )
533543 {
534- std::lock_guard<std::mutex > lock (goal_handles_mutex_);
544+ std::lock_guard<std::recursive_mutex > lock (goal_handles_mutex_);
535545 if (goal_handles_.count (goal_handle->get_goal_id ()) == 0 ) {
536546 throw exceptions::UnknownGoalHandleError ();
537547 }
@@ -562,6 +572,63 @@ class Client : public ClientBase
562572 return async_cancel (cancel_request, cancel_callback);
563573 }
564574
575+ // / Stops the callbacks for the goal in a thread safe way
576+ /* *
577+ * This will NOT cancel the goal, it will only stop the callbacks.
578+ *
579+ * After the call to this function, it is guaranteed that there
580+ * will be no more callbacks from the goal. This is not guaranteed
581+ * if multiple threads are involved, and the goal_handle is just
582+ * dropped.
583+ *
584+ * \param[in] goal_handle The goal were the callbacks shall be stopped
585+ */
586+ void stop_callbacks (typename GoalHandle::SharedPtr goal_handle)
587+ {
588+ goal_handle->set_feedback_callback (typename GoalHandle::FeedbackCallback ());
589+ goal_handle->set_result_callback (typename GoalHandle::ResultCallback ());
590+
591+ std::lock_guard<std::recursive_mutex> guard (goal_handles_mutex_);
592+ const GoalUUID & goal_id = goal_handle->get_goal_id ();
593+ auto it = goal_handles_.find (goal_id);
594+ if (goal_handles_.end () == it) {
595+ // someone else already deleted the entry
596+ // e.g. the result callback
597+ RCLCPP_DEBUG (
598+ this ->get_logger (),
599+ " Given goal is unknown. Ignoring..." );
600+ return ;
601+ }
602+ goal_handles_.erase (it);
603+ }
604+
605+ // / Stops the callbacks for the goal in a thread safe way
606+ /* *
607+ * For futher information see stop_callbacks(typename GoalHandle::SharedPtr goal_handle)
608+ */
609+ void stop_callbacks (const GoalUUID & goal_id)
610+ {
611+ typename GoalHandle::SharedPtr goal_handle;
612+ {
613+ std::lock_guard<std::recursive_mutex> guard (goal_handles_mutex_);
614+ auto it = goal_handles_.find (goal_id);
615+ if (goal_handles_.end () == it) {
616+ // someone else already deleted the entry
617+ // e.g. the result callback
618+ RCLCPP_DEBUG (
619+ this ->get_logger (),
620+ " Given goal is unknown. Ignoring..." );
621+ return ;
622+ }
623+
624+ goal_handle = it->lock ();
625+ }
626+
627+ if (goal_handle) {
628+ stop_callbacks (goal_handle);
629+ }
630+ }
631+
565632 // / Asynchronously request all goals at or before a specified time be canceled.
566633 /* *
567634 * \param[in] stamp The timestamp for the cancel goal request.
@@ -590,7 +657,7 @@ class Client : public ClientBase
590657 virtual
591658 ~Client ()
592659 {
593- std::lock_guard<std::mutex > guard (goal_handles_mutex_);
660+ std::lock_guard<std::recursive_mutex > guard (goal_handles_mutex_);
594661 auto it = goal_handles_.begin ();
595662 while (it != goal_handles_.end ()) {
596663 typename GoalHandle::SharedPtr goal_handle = it->second .lock ();
@@ -637,7 +704,7 @@ class Client : public ClientBase
637704 void
638705 handle_feedback_message (std::shared_ptr<void > message) override
639706 {
640- std::lock_guard<std::mutex > guard (goal_handles_mutex_);
707+ std::lock_guard<std::recursive_mutex > guard (goal_handles_mutex_);
641708 using FeedbackMessage = typename ActionT::Impl::FeedbackMessage;
642709 typename FeedbackMessage::SharedPtr feedback_message =
643710 std::static_pointer_cast<FeedbackMessage>(message);
@@ -674,7 +741,7 @@ class Client : public ClientBase
674741 void
675742 handle_status_message (std::shared_ptr<void > message) override
676743 {
677- std::lock_guard<std::mutex > guard (goal_handles_mutex_);
744+ std::lock_guard<std::recursive_mutex > guard (goal_handles_mutex_);
678745 using GoalStatusMessage = typename ActionT::Impl::GoalStatusMessage;
679746 auto status_message = std::static_pointer_cast<GoalStatusMessage>(message);
680747 for (const GoalStatus & status : status_message->status_list ) {
@@ -723,7 +790,7 @@ class Client : public ClientBase
723790 wrapped_result.goal_id = goal_handle->get_goal_id ();
724791 wrapped_result.code = static_cast <ResultCode>(result_response->status );
725792 goal_handle->set_result (wrapped_result);
726- std::lock_guard<std::mutex > lock (goal_handles_mutex_);
793+ std::lock_guard<std::recursive_mutex > lock (goal_handles_mutex_);
727794 goal_handles_.erase (goal_handle->get_goal_id ());
728795 });
729796 } catch (rclcpp::exceptions::RCLError & ex) {
@@ -755,7 +822,7 @@ class Client : public ClientBase
755822 }
756823
757824 std::map<GoalUUID, typename GoalHandle::WeakPtr> goal_handles_;
758- std::mutex goal_handles_mutex_;
825+ std::recursive_mutex goal_handles_mutex_;
759826};
760827} // namespace rclcpp_action
761828
0 commit comments