@@ -10,7 +10,7 @@ use crate::{
1010 error:: { RclReturnCode , ToResult } ,
1111 qos:: QoSProfile ,
1212 rcl_bindings:: * ,
13- NodeHandle , RclrsError , ENTITY_LIFECYCLE_MUTEX ,
13+ Node , NodeHandle , RclrsError , ENTITY_LIFECYCLE_MUTEX ,
1414} ;
1515
1616mod callback;
@@ -31,7 +31,7 @@ unsafe impl Send for rcl_subscription_t {}
3131/// [1]: <https://doc.rust-lang.org/reference/destructors.html>
3232pub struct SubscriptionHandle {
3333 rcl_subscription : Mutex < rcl_subscription_t > ,
34- node_handle : Arc < NodeHandle > ,
34+ node_handle : Arc < Node > ,
3535 pub ( crate ) in_use_by_wait_set : Arc < AtomicBool > ,
3636}
3737
8484 pub ( crate ) handle : Arc < SubscriptionHandle > ,
8585 /// The callback function that runs when a message was received.
8686 pub callback : Mutex < AnySubscriptionCallback < T > > ,
87+ /// Ensure the parent node remains alive as long as the subscription is held.
88+ /// This implementation will change in the future.
89+ node : Arc < Node > ,
8790 message : PhantomData < T > ,
8891}
8992
9396{
9497 /// Creates a new subscription.
9598 pub ( crate ) fn new < Args > (
96- node_handle : Arc < NodeHandle > ,
99+ node : & Arc < Node > ,
97100 topic : & str ,
98101 qos : QoSProfile ,
99102 callback : impl SubscriptionCallback < T , Args > ,
@@ -117,7 +120,7 @@ where
117120 subscription_options. qos = qos. into ( ) ;
118121
119122 {
120- let rcl_node = node_handle . rcl_node . lock ( ) . unwrap ( ) ;
123+ let rcl_node = node . handle . rcl_node . lock ( ) . unwrap ( ) ;
121124 let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX . lock ( ) . unwrap ( ) ;
122125 unsafe {
123126 // SAFETY:
@@ -146,6 +149,7 @@ where
146149 Ok ( Self {
147150 handle,
148151 callback : Mutex :: new ( callback. into_callback ( ) ) ,
152+ node : Arc :: clone ( node) ,
149153 message : PhantomData ,
150154 } )
151155 }
@@ -396,4 +400,36 @@ mod tests {
396400 ) ;
397401 Ok ( ( ) )
398402 }
403+
404+ #[ test]
405+ fn test_node_subscription_raii ( ) {
406+ use rclrs:: * ;
407+ use std:: sync:: atomic:: Ordering ;
408+
409+ let executor = Context :: default ( ) . create_basic_executor ( ) ;
410+
411+ let triggered = Arc :: new ( AtomicBool :: new ( false ) ) ;
412+ let callback = |_| {
413+ triggered. store ( true , Ordering :: AcqRel ) ;
414+ } ;
415+
416+ let ( subscription, publisher) = {
417+ let node = executor
418+ . create_node ( & format ! ( "test_node_subscription_raii_{}" , line!( ) ) )
419+ . unwrap ( ) ;
420+
421+ let qos = QoSProfile :: default ( ) . keep_all ( ) . reliable ( ) ;
422+ let subscription = node. create_subscription :: < msg:: Empty > ( "test_topic" , qos, callback) . unwrap ( ) ;
423+ let publisher = node. create_publisher :: < msg:: Empty > ( "test_topic" , qos) . unwrap ( ) ;
424+
425+ ( subscription, publisher)
426+ } ;
427+
428+ publisher. publish ( msg:: Empty { } ) ;
429+ let start_time = std:: time:: Instant :: now ( ) ;
430+ while !triggered. load ( Ordering :: AcqRel ) {
431+ assert ! ( executor. spin( SpinOptions :: spin_once( ) ) . is_empty( ) ) ;
432+ assert ! ( start_time. elapsed( ) < std:: time:: Duration :: from_secs( 10 ) ) ;
433+ }
434+ }
399435}
0 commit comments