Skip to content

Commit 7d4ed60

Browse files
authored
Fix executor timeout (#519)
* Examine timeout crash Signed-off-by: Michael X. Grey <[email protected]> * Fix executor timeout Signed-off-by: Michael X. Grey <[email protected]> * Add test assertions Signed-off-by: Michael X. Grey <[email protected]> * Skip readiness check if an error is reported Signed-off-by: Michael X. Grey <[email protected]> * fix formatting Signed-off-by: Michael X. Grey <[email protected]> --------- Signed-off-by: Michael X. Grey <[email protected]>
1 parent e61d81b commit 7d4ed60

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

rclrs/src/executor.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,3 +487,30 @@ impl CreateBasicExecutor for Context {
487487
self.create_executor(runtime)
488488
}
489489
}
490+
491+
#[cfg(test)]
492+
mod tests {
493+
use crate::*;
494+
use std::time::Duration;
495+
496+
#[test]
497+
fn test_timeout() {
498+
let context = Context::default();
499+
let mut executor = context.create_basic_executor();
500+
let _node = executor
501+
.create_node(&format!("test_timeout_{}", line!()))
502+
.unwrap();
503+
504+
for _ in 0..10 {
505+
let r = executor.spin(SpinOptions::default().timeout(Duration::from_millis(1)));
506+
assert_eq!(r.len(), 1);
507+
assert!(matches!(
508+
r[0],
509+
RclrsError::RclError {
510+
code: RclReturnCode::Timeout,
511+
..
512+
}
513+
));
514+
}
515+
}
516+
}

rclrs/src/wait_set.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,32 +114,39 @@ impl WaitSet {
114114
})
115115
}
116116
};
117+
117118
// SAFETY: The comments in rcl mention "This function cannot operate on the same wait set
118119
// in multiple threads, and the wait sets may not share content."
119-
// We cannot currently guarantee that the wait sets may not share content, but it is
120-
// mentioned in the doc comment for `add_subscription`.
121-
// Also, the rcl_wait_set is obviously valid.
122-
match unsafe { rcl_wait(&mut self.handle.rcl_wait_set, timeout_ns) }.ok() {
123-
Ok(_) => (),
120+
// * The we have exclusive access to rcl_wait_set because this is a
121+
// mutable borrow of WaitSet, which houses rcl_wait_set.
122+
// * We guarantee that the wait sets do not share content by funneling
123+
// the waitable of each primitive to one (and only one) WaitSet when
124+
// the primitive gets constructed. The waitables are never allowed to
125+
// move between wait sets.
126+
let r = match unsafe { rcl_wait(&mut self.handle.rcl_wait_set, timeout_ns) }.ok() {
127+
Ok(_) => Ok(()),
124128
Err(error) => match error {
125129
RclrsError::RclError { code, msg } => match code {
126-
RclReturnCode::WaitSetEmpty => (),
127-
_ => return Err(RclrsError::RclError { code, msg }),
130+
RclReturnCode::WaitSetEmpty => Ok(()),
131+
_ => Err(RclrsError::RclError { code, msg }),
128132
},
129-
_ => return Err(error),
133+
_ => Err(error),
130134
},
131-
}
135+
};
132136

133137
// Remove any waitables that are no longer being used
134138
for waitable in self.primitives.values_mut() {
135139
waitable.retain(|w| w.in_use());
136140
}
137141

138-
// For the remaining entities, check if they were activated and then run
139-
// the callback for those that were.
140-
for waiter in self.primitives.values_mut().flat_map(|v| v) {
141-
if waiter.is_ready(&self.handle.rcl_wait_set) {
142-
f(&mut *waiter.primitive)?;
142+
// Do not check the readiness if an error was reported.
143+
if !r.is_err() {
144+
// For the remaining entities, check if they were activated and then run
145+
// the callback for those that were.
146+
for waiter in self.primitives.values_mut().flat_map(|v| v) {
147+
if waiter.is_ready(&self.handle.rcl_wait_set) {
148+
f(&mut *waiter.primitive)?;
149+
}
143150
}
144151
}
145152

@@ -160,7 +167,7 @@ impl WaitSet {
160167
);
161168
}
162169

163-
Ok(())
170+
r
164171
}
165172

166173
/// Get a count of the different kinds of entities in the wait set.

0 commit comments

Comments
 (0)