@@ -573,6 +573,8 @@ where
573573 if num_expired > 0 {
574574 // Clean up the expired goal.
575575 let uuid = GoalUuid ( expired_goal. goal_id . uuid ) ;
576+ self . goal_results . lock ( ) . unwrap ( ) . remove ( & uuid) ;
577+ self . result_requests . lock ( ) . unwrap ( ) . remove ( & uuid) ;
576578 self . goal_handles . lock ( ) . unwrap ( ) . remove ( & uuid) ;
577579 } else {
578580 break ;
@@ -582,6 +584,29 @@ where
582584 Ok ( ( ) )
583585 }
584586
587+ // TODO(nwn): Replace `status` with a "properly typed" action_msgs::msg::GoalStatus enum.
588+ pub ( crate ) fn terminate_goal ( & self , goal_id : & GoalUuid , status : i8 , result : <T :: Result as Message >:: RmwMsg ) -> Result < ( ) , RclrsError > {
589+ let response_rmw = <T as ActionImpl >:: create_result_response ( status, result) ;
590+
591+ // Publish the result to anyone listening.
592+ self . publish_result ( goal_id, response_rmw) ;
593+
594+ // Publish the state change.
595+ self . publish_status ( ) ;
596+
597+ // Notify rcl that a goal has terminated and to therefore recalculate the expired goal timer.
598+ unsafe {
599+ // SAFETY: The action server is locked and valid. No other preconditions.
600+ rcl_action_notify_goal_done ( & * self . handle . lock ( ) )
601+ }
602+ . ok ( ) ?;
603+
604+ // Release ownership of the goal handle. It will persist until the user also drops it.
605+ self . goal_handles . lock ( ) . unwrap ( ) . remove ( & goal_id) ;
606+
607+ Ok ( ( ) )
608+ }
609+
585610 pub ( crate ) fn publish_status ( & self ) -> Result < ( ) , RclrsError > {
586611 let mut goal_statuses = DropGuard :: new (
587612 unsafe {
@@ -628,6 +653,37 @@ where
628653 }
629654 . ok ( )
630655 }
656+
657+ fn publish_result ( & self , goal_id : & GoalUuid , mut result : <<<T as ActionImpl >:: GetResultService as Service >:: Response as Message >:: RmwMsg ) -> Result < ( ) , RclrsError > {
658+ let goal_exists = unsafe {
659+ // SAFETY: No preconditions
660+ let mut goal_info = rcl_action_get_zero_initialized_goal_info ( ) ;
661+ goal_info. goal_id . uuid = goal_id. 0 ;
662+
663+ // SAFETY: The action server is locked through the handle. The `goal_info`
664+ // argument points to a rcl_action_goal_info_t with the desired UUID.
665+ rcl_action_server_goal_exists ( & * self . handle . lock ( ) , & goal_info)
666+ } ;
667+ if !goal_exists {
668+ panic ! ( "Cannot publish result for unknown goal" )
669+ }
670+
671+ // TODO(nwn): Fix synchronization problem between goal_results and result_requests.
672+ // Currently, there is a gap between the request queue being drained and the result being
673+ // stored for future requests. Any requests received during that gap would never receive a
674+ // response. Fixing this means we'll need combined locking over these two hash maps.
675+
676+ // Respond to all queued requests.
677+ if let Some ( result_requests) = self . result_requests . lock ( ) . unwrap ( ) . remove ( & goal_id) {
678+ for mut result_request in result_requests {
679+ self . send_result_response ( result_request, & mut result) ?;
680+ }
681+ }
682+
683+ self . goal_results . lock ( ) . unwrap ( ) . insert ( * goal_id, result) ;
684+
685+ Ok ( ( ) )
686+ }
631687}
632688
633689impl < T > ActionServerBase for ActionServer < T >
0 commit comments