Skip to content

Commit ea27c3f

Browse files
committed
Fix action cancellation pipeline
Signed-off-by: Michael X. Grey <[email protected]>
1 parent 6113ae1 commit ea27c3f

File tree

8 files changed

+3556
-2595
lines changed

8 files changed

+3556
-2595
lines changed

rclrs/src/action.rs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ impl From<&[u8; RCL_ACTION_UUID_SIZE]> for GoalUuid {
7373
}
7474

7575
/// The response returned by an [`ActionServer`]'s cancel callback when a goal is requested to be cancelled.
76+
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
77+
#[cfg_attr(feature = "serde", serde(rename = "snake_case"))]
7678
#[repr(i8)]
7779
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7880
pub enum CancelResponseCode {
@@ -118,6 +120,7 @@ impl From<i8> for CancelResponseCode {
118120
///
119121
/// When a cancellation request might cancel multiple goals, [`MultiCancelResponse`]
120122
/// will be used.
123+
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
121124
#[derive(Debug, Clone, PartialEq, PartialOrd)]
122125
pub struct CancelResponse {
123126
/// What kind of response was given.
@@ -154,6 +157,7 @@ impl MultiCancelResponse {
154157

155158
/// Values defined by `action_msgs/msg/GoalStatus`
156159
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
160+
#[cfg_attr(feature = "serde", serde(rename = "snake_case"))]
157161
#[repr(i8)]
158162
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
159163
pub enum GoalStatusCode {
@@ -240,15 +244,17 @@ mod tests {
240244
use tokio::sync::mpsc::unbounded_channel;
241245

242246
#[test]
243-
fn test_action_success() {
247+
fn test_action_success_streaming() {
244248
let mut executor = Context::default().create_basic_executor();
245249

246250
let node = executor
247251
.create_node(&format!("test_action_success_{}", line!()))
248252
.unwrap();
249253
let action_name = format!("test_action_success_{}_action", line!());
250254
let _action_server = node
251-
.create_action_server(&action_name, fibonacci_action)
255+
.create_action_server(&action_name, |handle| {
256+
fibonacci_action(handle, Duration::from_micros(10))
257+
})
252258
.unwrap();
253259

254260
let client = node
@@ -302,7 +308,41 @@ mod tests {
302308
executor.spin(SpinOptions::default().until_promise_resolved(promise));
303309
}
304310

305-
async fn fibonacci_action(handle: RequestedGoal<Fibonacci>) -> TerminatedGoal {
311+
#[test]
312+
fn test_action_cancel() {
313+
let mut executor = Context::default().create_basic_executor();
314+
315+
let node = executor
316+
.create_node(&format!("test_action_cancel_{}", line!()))
317+
.unwrap();
318+
let action_name = format!("test_action_cancel_{}_slow_action", line!());
319+
let _action_server = node
320+
.create_action_server(&action_name, |handle| {
321+
fibonacci_action(handle, Duration::from_secs(1))
322+
})
323+
.unwrap();
324+
325+
let client = node
326+
.create_action_client::<Fibonacci>(&action_name)
327+
.unwrap();
328+
329+
let request = client.request_goal(Fibonacci_Goal { order: 10 });
330+
331+
let promise = executor.commands().run(async move {
332+
let goal_client = request.await.unwrap();
333+
let cancellation = goal_client.cancellation.cancel().await;
334+
assert!(cancellation.is_accepted());
335+
let (status, _) = goal_client.result.await;
336+
assert_eq!(status, GoalStatusCode::Cancelled);
337+
});
338+
339+
executor.spin(SpinOptions::default().until_promise_resolved(promise));
340+
}
341+
342+
async fn fibonacci_action(
343+
handle: RequestedGoal<Fibonacci>,
344+
period: Duration,
345+
) -> TerminatedGoal {
306346
let goal_order = handle.goal().order;
307347
if goal_order < 0 {
308348
return handle.reject();
@@ -331,7 +371,7 @@ mod tests {
331371
let next = previous + current;
332372
previous = current;
333373
current = next;
334-
std::thread::sleep(Duration::from_micros(10));
374+
std::thread::sleep(period);
335375
}
336376
});
337377

rclrs/src/action/action_client.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,7 @@ use super::empty_goal_status_array;
22
use crate::{
33
log_warn,
44
rcl_bindings::*,
5-
vendor::{
6-
action_msgs::{
7-
msg::GoalInfo,
8-
srv::{CancelGoal_Request, CancelGoal_Response},
9-
},
10-
builtin_interfaces::msg::Time,
11-
unique_identifier_msgs::msg::UUID,
12-
},
5+
vendor::{action_msgs::srv::CancelGoal_Response, builtin_interfaces::msg::Time},
136
CancelResponse, CancelResponseCode, DropGuard, GoalStatus, GoalStatusCode, GoalUuid,
147
MultiCancelResponse, Node, NodeHandle, QoSProfile, RclPrimitive, RclPrimitiveHandle,
158
RclPrimitiveKind, RclrsError, ReadyKind, TakeFailedAsNone, ToResult, Waitable,
@@ -880,15 +873,15 @@ impl ActionClientHandle {
880873
}
881874

882875
fn send_cancel_goal(&self, goal_id: GoalUuid, stamp: Time) -> Result<i64, RclrsError> {
883-
let cancel_request = CancelGoal_Request {
884-
goal_info: GoalInfo {
885-
goal_id: UUID { uuid: *goal_id },
886-
stamp,
876+
let cancel_request_rmw = action_msgs__srv__CancelGoal_Request {
877+
goal_info: action_msgs__msg__GoalInfo {
878+
goal_id: unique_identifier_msgs__msg__UUID { uuid: *goal_id },
879+
stamp: builtin_interfaces__msg__Time {
880+
sec: stamp.sec,
881+
nanosec: stamp.nanosec,
882+
},
887883
},
888884
};
889-
890-
let cancel_request_rmw =
891-
<CancelGoal_Request as Message>::into_rmw_message(Cow::Owned(cancel_request));
892885
let mut seq: i64 = 0;
893886

894887
unsafe {

rclrs/src/action/action_client/cancellation_client.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use super::GoalClientLifecycle;
22
use crate::{ActionClient, CancelResponse, GoalUuid, MultiCancelResponse, RclrsError};
33
use rosidl_runtime_rs::Action;
44
use std::{
5+
future::Future,
56
ops::{Deref, DerefMut},
7+
pin::Pin,
68
sync::Arc,
9+
task::{Context, Poll},
710
};
811
use tokio::sync::oneshot::Receiver;
912

@@ -88,6 +91,18 @@ impl<A: Action> CancelResponseClient<A> {
8891
}
8992
}
9093

94+
impl<A: Action> Future for CancelResponseClient<A> {
95+
type Output = CancelResponse;
96+
97+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
98+
Future::poll(Pin::new(&mut self.get_mut().receiver), cx)
99+
// SAFETY: The Receiver returns an Err if the sender is dropped, but
100+
// the CancelResponseClient makes sure that the sender is alive in
101+
// the ActionClient, so we can always safely unwrap this.
102+
.map(|result| result.unwrap())
103+
}
104+
}
105+
91106
/// This will allow you to receive a response to a cancellation request that
92107
/// impacts multiple goals.
93108
///

rclrs/src/action/action_client/requested_goal_client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl<A: Action> Future for RequestedGoalClient<A> {
6565
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
6666
Future::poll(Pin::new(&mut self.get_mut().receiver), cx)
6767
// SAFETY: The Receiver returns an Err if the sender is dropped, but
68-
// the RegisteredGoalClient makes sure that the sender is alive in
68+
// the RequestedGoalClient makes sure that the sender is alive in
6969
// the ActionClient, so we can always safely unwrap this.
7070
.map(|result| result.unwrap())
7171
}

rclrs/src/action/action_client/result_client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl<A: Action> Future for ResultClient<A> {
6363
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
6464
Future::poll(Pin::new(&mut self.get_mut().receiver), cx)
6565
// SAFETY: The Receiver returns an Err if the sender is dropped, but
66-
// the RegisteredGoalClient makes sure that the sender is alive in
66+
// the ResultClient makes sure that the sender is alive in
6767
// the ActionClient, so we can always safely unwrap this.
6868
.map(|result| result.unwrap())
6969
}

rclrs/src/action/action_server/cancellation_state.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ impl<A: Action> CancellationState<A> {
7070
CancellationMode::None => {
7171
let requests = Vec::from_iter([request]);
7272
*mode = CancellationMode::CancelRequested(requests);
73+
self.change_cancel_requested_status(true);
7374
}
7475
CancellationMode::CancelRequested(requests) => {
7576
requests.push(request);
77+
self.change_cancel_requested_status(true);
7678
}
7779
CancellationMode::Cancelling => {
7880
request.accept(*uuid);
@@ -93,7 +95,7 @@ impl<A: Action> CancellationState<A> {
9395
*mode = CancellationMode::None;
9496
// We do not need to worry about errors from sending this state
9597
// since it is okay for the receiver to be dropped.
96-
let _ = self.sender.send(false);
98+
let _ = self.change_cancel_requested_status(false);
9799
}
98100
CancellationMode::None => {
99101
// Do nothing
@@ -120,21 +122,33 @@ impl<A: Action> CancellationState<A> {
120122
// a true value in the cancel requested channel. We can ignore
121123
// errors from this because it is okay for the receiver to be
122124
// dropped.
123-
let _ = self.sender.send(true);
125+
let _ = self.change_cancel_requested_status(true);
124126
}
125127
CancellationMode::None => {
126128
// Skip straight to cancellation mode since the user has accepted
127129
// a cancellation even though it wasn't requested externally.
128130
*mode = CancellationMode::Cancelling;
129131
// Make sure the cancellation is signalled. We can ignore errors
130132
// from this because it is okay for the receiver to be dropped.
131-
let _ = self.sender.send(true);
133+
let _ = self.change_cancel_requested_status(true);
132134
}
133135
CancellationMode::Cancelling => {
134136
// Do nothing
135137
}
136138
}
137139
}
140+
141+
fn change_cancel_requested_status(&self, cancel_requested: bool) {
142+
self.sender.send_if_modified(|status| {
143+
let previously_requested = *status;
144+
*status = cancel_requested;
145+
// Only notify the listeners if a cancellation has been requested
146+
// and was not previously requested. We do not need to notify when
147+
// a cancellation has been rejected (i.e. reverting back to no
148+
// cancellation requested).
149+
cancel_requested && !previously_requested
150+
});
151+
}
138152
}
139153

140154
impl<A: Action> Default for CancellationState<A> {

0 commit comments

Comments
 (0)