Skip to content

Commit e38144a

Browse files
committed
Fix lifecycles of Clock and Timer to guarantee safe usage
Signed-off-by: Michael X. Grey <[email protected]>
1 parent 37d5e77 commit e38144a

File tree

4 files changed

+50
-47
lines changed

4 files changed

+50
-47
lines changed

rclrs/src/clock.rs

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,26 @@ impl Clock {
6666
}
6767

6868
fn make(kind: ClockType) -> Self {
69-
let mut rcl_clock;
69+
let rcl_clock;
7070
unsafe {
7171
// SAFETY: Getting a default value is always safe.
72-
rcl_clock = Self::init_generic_clock();
72+
let allocator = rcutils_get_default_allocator();
73+
rcl_clock = Arc::new(Mutex::new(rcl_clock_t {
74+
type_: rcl_clock_type_t::RCL_CLOCK_UNINITIALIZED,
75+
jump_callbacks: std::ptr::null_mut(),
76+
num_jump_callbacks: 0,
77+
get_now: None,
78+
data: std::ptr::null_mut::<std::os::raw::c_void>(),
79+
allocator,
80+
}));
7381
let mut allocator = rcutils_get_default_allocator();
7482
// Function will return Err(_) only if there isn't enough memory to allocate a clock
7583
// object.
76-
rcl_clock_init(kind.into(), &mut rcl_clock, &mut allocator)
84+
rcl_clock_init(kind.into(), &mut *rcl_clock.lock().unwrap(), &mut allocator)
7785
.ok()
7886
.unwrap();
7987
}
80-
Self {
81-
kind,
82-
rcl_clock: Arc::new(Mutex::new(rcl_clock)),
83-
}
88+
Self { kind, rcl_clock }
8489
}
8590

8691
/// Returns the clock's `rcl_clock_t`.
@@ -106,22 +111,6 @@ impl Clock {
106111
clock: Arc::downgrade(&self.rcl_clock),
107112
}
108113
}
109-
110-
/// Helper function to privately initialize a default clock, with the same behavior as
111-
/// `rcl_init_generic_clock`. By defining a private function instead of implementing
112-
/// `Default`, we avoid exposing a public API to create an invalid clock.
113-
// SAFETY: Getting a default value is always safe.
114-
unsafe fn init_generic_clock() -> rcl_clock_t {
115-
let allocator = rcutils_get_default_allocator();
116-
rcl_clock_t {
117-
type_: rcl_clock_type_t::RCL_CLOCK_UNINITIALIZED,
118-
jump_callbacks: std::ptr::null_mut::<rcl_jump_callback_info_t>(),
119-
num_jump_callbacks: 0,
120-
get_now: None,
121-
data: std::ptr::null_mut::<std::os::raw::c_void>(),
122-
allocator,
123-
}
124-
}
125114
}
126115

127116
impl Drop for ClockSource {

rclrs/src/timer.rs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,30 @@ pub use timer_options::*;
3333
// TODO: callback type prevents us from making the Timer implement the Debug trait.
3434
// #[derive(Debug)]
3535
pub struct Timer {
36-
pub(crate) rcl_timer: Arc<Mutex<rcl_timer_t>>,
36+
pub(crate) handle: TimerHandle,
3737
/// The callback function that runs when the timer is due.
3838
callback: Arc<Mutex<Option<AnyTimerCallback>>>,
3939
/// We hold onto the Timer's clock for the whole lifespan of the Timer to
4040
/// make sure the underlying `rcl_clock_t` remains valid.
41-
clock: Clock,
4241
pub(crate) in_use_by_wait_set: Arc<AtomicBool>,
4342
}
4443

44+
/// Manage the lifecycle of an `rcl_timer_t`, including managing its dependency
45+
/// on `rcl_clock_t` by ensuring that this dependency are [dropped after][1]
46+
/// the `rcl_timer_t`.
47+
///
48+
/// [1]: <https://doc.rust-lang.org/reference/destructors.html>
49+
pub(crate) struct TimerHandle {
50+
pub(crate) rcl_timer: Arc<Mutex<rcl_timer_t>>,
51+
clock: Clock,
52+
}
53+
4554
impl Timer {
4655
/// Gets the period of the timer
4756
pub fn get_timer_period(&self) -> Result<Duration, RclrsError> {
4857
let mut timer_period_ns = 0;
4958
unsafe {
50-
let rcl_timer = self.rcl_timer.lock().unwrap();
59+
let rcl_timer = self.handle.rcl_timer.lock().unwrap();
5160
rcl_timer_get_period(&*rcl_timer, &mut timer_period_ns)
5261
}.ok()?;
5362

@@ -56,7 +65,7 @@ impl Timer {
5665

5766
/// Cancels the timer, stopping the execution of the callback
5867
pub fn cancel(&self) -> Result<(), RclrsError> {
59-
let mut rcl_timer = self.rcl_timer.lock().unwrap();
68+
let mut rcl_timer = self.handle.rcl_timer.lock().unwrap();
6069
let cancel_result = unsafe { rcl_timer_cancel(&mut *rcl_timer) }.ok()?;
6170
Ok(cancel_result)
6271
}
@@ -65,7 +74,7 @@ impl Timer {
6574
pub fn is_canceled(&self) -> Result<bool, RclrsError> {
6675
let mut is_canceled = false;
6776
unsafe {
68-
let rcl_timer = self.rcl_timer.lock().unwrap();
77+
let rcl_timer = self.handle.rcl_timer.lock().unwrap();
6978
rcl_timer_is_canceled(&*rcl_timer, &mut is_canceled)
7079
}.ok()?;
7180
Ok(is_canceled)
@@ -75,7 +84,7 @@ impl Timer {
7584
pub fn time_since_last_call(&self) -> Result<Duration, RclrsError> {
7685
let mut time_value_ns: i64 = 0;
7786
unsafe {
78-
let rcl_timer = self.rcl_timer.lock().unwrap();
87+
let rcl_timer = self.handle.rcl_timer.lock().unwrap();
7988
rcl_timer_get_time_since_last_call(&*rcl_timer, &mut time_value_ns)
8089
}.ok()?;
8190

@@ -86,7 +95,7 @@ impl Timer {
8695
pub fn time_until_next_call(&self) -> Result<Duration, RclrsError> {
8796
let mut time_value_ns: i64 = 0;
8897
unsafe {
89-
let rcl_timer = self.rcl_timer.lock().unwrap();
98+
let rcl_timer = self.handle.rcl_timer.lock().unwrap();
9099
rcl_timer_get_time_until_next_call(&*rcl_timer, &mut time_value_ns)
91100
}.ok()?;
92101

@@ -95,15 +104,15 @@ impl Timer {
95104

96105
/// Resets the timer.
97106
pub fn reset(&self) -> Result<(), RclrsError> {
98-
let mut rcl_timer = self.rcl_timer.lock().unwrap();
107+
let mut rcl_timer = self.handle.rcl_timer.lock().unwrap();
99108
unsafe { rcl_timer_reset(&mut *rcl_timer) }.ok()
100109
}
101110

102111
/// Checks if the timer is ready (not canceled)
103112
pub fn is_ready(&self) -> Result<bool, RclrsError> {
104113
let is_ready = unsafe {
105114
let mut is_ready: bool = false;
106-
let rcl_timer = self.rcl_timer.lock().unwrap();
115+
let rcl_timer = self.handle.rcl_timer.lock().unwrap();
107116
rcl_timer_is_ready(&*rcl_timer, &mut is_ready).ok()?;
108117
is_ready
109118
};
@@ -173,23 +182,27 @@ impl Timer {
173182
callback: AnyTimerCallback,
174183
) -> Result<Timer, RclrsError> {
175184
let period = period.as_nanos() as i64;
176-
let mut rcl_clock = clock.get_rcl_clock().lock().unwrap();
177-
let mut rcl_context = context.rcl_context.lock().unwrap();
178185

179186
// Callbacks will be handled at the rclrs layer.
180187
let rcl_timer_callback: rcl_timer_callback_t = None;
181188

182-
let mut rcl_timer;
189+
let rcl_timer = Arc::new(Mutex::new(
190+
// SAFETY: Zero-initializing a timer is always safe
191+
unsafe { rcl_get_zero_initialized_timer() }
192+
));
193+
183194
unsafe {
195+
let mut rcl_clock = clock.get_rcl_clock().lock().unwrap();
196+
let mut rcl_context = context.rcl_context.lock().unwrap();
197+
184198
// SAFETY: Getting a default value is always safe.
185-
rcl_timer = rcl_get_zero_initialized_timer();
186199
let allocator = rcutils_get_default_allocator();
187200

188201
let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
189202
// SAFETY: We lock the lifecycle mutex since rcl_timer_init is not
190203
// thread-safe.
191204
rcl_timer_init(
192-
&mut rcl_timer,
205+
&mut *rcl_timer.lock().unwrap(),
193206
&mut *rcl_clock,
194207
&mut *rcl_context,
195208
period,
@@ -199,9 +212,8 @@ impl Timer {
199212
}.ok()?;
200213

201214
let timer = Timer {
202-
rcl_timer: Arc::new(Mutex::new(rcl_timer)),
215+
handle: TimerHandle { rcl_timer, clock },
203216
callback: Arc::new(Mutex::new(Some(callback))),
204-
clock: clock.clone(),
205217
in_use_by_wait_set: Arc::new(AtomicBool::new(false)),
206218
};
207219
Ok(timer)
@@ -253,7 +265,7 @@ impl Timer {
253265
/// in the [`Timer`] struct. This means there are no side-effects to this
254266
/// except to keep track of when the timer has been called.
255267
fn rcl_call(&self) -> Result<(), RclrsError> {
256-
let mut rcl_timer = self.rcl_timer.lock().unwrap();
268+
let mut rcl_timer = self.handle.rcl_timer.lock().unwrap();
257269
unsafe { rcl_timer_call(&mut *rcl_timer) }.ok()
258270
}
259271

@@ -268,16 +280,18 @@ impl Timer {
268280
}
269281

270282
/// 'Drop' trait implementation to be able to release the resources
271-
impl Drop for rcl_timer_t {
283+
impl Drop for TimerHandle {
272284
fn drop(&mut self) {
273-
// SAFETY: No preconditions for this function
274-
unsafe { rcl_timer_fini(&mut *self) };
285+
let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
286+
// SAFETY: The lifecycle mutex is locked and the clock for the timer
287+
// must still be valid because TimerHandle keeps it alive.
288+
unsafe { rcl_timer_fini(&mut *self.rcl_timer.lock().unwrap()) };
275289
}
276290
}
277291

278292
impl PartialEq for Timer {
279293
fn eq(&self, other: &Self) -> bool {
280-
Arc::ptr_eq(&self.rcl_timer, &other.rcl_timer)
294+
Arc::ptr_eq(&self.handle.rcl_timer, &other.handle.rcl_timer)
281295
}
282296
}
283297

@@ -334,7 +348,7 @@ mod tests {
334348
fn test_new_with_source_clock() {
335349
let (clock, source) = Clock::with_source();
336350
// No manual time set, it should default to 0
337-
assert!(clock.now().nsec == 0);
351+
assert_eq!(clock.now().nsec, 0);
338352
let set_time = 1234i64;
339353
source.set_ros_time_override(set_time);
340354

rclrs/src/timer/timer_callback.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@ where
6868
Func: FnOnce(Time) + Send + 'static,
6969
{
7070
fn into_oneshot_timer_callback(self) -> AnyTimerCallback {
71-
AnyTimerCallback::OneShot(Box::new(move |t| self(t.clock.now())))
71+
AnyTimerCallback::OneShot(Box::new(move |t| self(t.handle.clock.now())))
7272
}
7373
}

rclrs/src/wait.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ impl WaitSet {
340340
// Passing in a null pointer for the third argument is explicitly allowed.
341341
rcl_wait_set_add_timer(
342342
&mut self.handle.rcl_wait_set,
343-
&*timer.rcl_timer.lock().unwrap() as *const _,
343+
&*timer.handle.rcl_timer.lock().unwrap() as *const _,
344344
core::ptr::null_mut(),
345345
)
346346
}

0 commit comments

Comments
 (0)