@@ -13,6 +13,8 @@ use std::time::Duration;
1313
1414use lightning:: util:: native_async:: FutureSpawner ;
1515use tokio:: task:: { JoinHandle , JoinSet } ;
16+ use tokio_util:: sync:: CancellationToken ;
17+ use tokio_util:: task:: TaskTracker ;
1618
1719use crate :: config:: {
1820 BACKGROUND_TASK_SHUTDOWN_TIMEOUT_SECS , LDK_EVENT_HANDLER_SHUTDOWN_TIMEOUT_SECS ,
@@ -28,13 +30,18 @@ pub(crate) struct Runtime {
2830}
2931
3032struct CancellableBackgroundTasks {
31- tasks : JoinSet < ( ) > ,
33+ tasks : TaskTracker ,
34+ cancellation_token : CancellationToken ,
3235 accepting_tasks : bool ,
3336}
3437
3538impl CancellableBackgroundTasks {
3639 fn new ( ) -> Self {
37- Self { tasks : JoinSet :: new ( ) , accepting_tasks : true }
40+ Self {
41+ tasks : TaskTracker :: new ( ) ,
42+ cancellation_token : CancellationToken :: new ( ) ,
43+ accepting_tasks : true ,
44+ }
3845 }
3946}
4047
@@ -109,8 +116,7 @@ impl Runtime {
109116 where
110117 F : Future < Output = ( ) > + Send + ' static ,
111118 {
112- let mut cancellable_background_tasks =
113- self . cancellable_background_tasks . lock ( ) . expect ( "lock" ) ;
119+ let cancellable_background_tasks = self . cancellable_background_tasks . lock ( ) . expect ( "lock" ) ;
114120 if !cancellable_background_tasks. accepting_tasks {
115121 log_trace ! (
116122 self . logger,
@@ -122,11 +128,32 @@ impl Runtime {
122128 // Since it seems to make a difference to `tokio` (see
123129 // https://docs.rs/tokio/latest/tokio/time/fn.timeout.html#panics) we make sure the futures
124130 // are always put in an `async` / `.await` closure.
125- cancellable_background_tasks. tasks . spawn_on ( async { future. await } , runtime_handle) ;
131+ let cancellation_token = cancellable_background_tasks. cancellation_token . clone ( ) ;
132+ // Detach the handle while the tracker continues tracking the task.
133+ let _ = cancellable_background_tasks. tasks . spawn_on (
134+ async move {
135+ tokio:: select! {
136+ biased;
137+ _ = cancellation_token. cancelled( ) => { } ,
138+ _ = future => { } ,
139+ }
140+ } ,
141+ runtime_handle,
142+ ) ;
126143 }
127144
128145 pub fn allow_cancellable_background_task_spawns ( & self ) {
129- self . cancellable_background_tasks . lock ( ) . expect ( "lock" ) . accepting_tasks = true ;
146+ let mut cancellable_background_tasks =
147+ self . cancellable_background_tasks . lock ( ) . expect ( "lock" ) ;
148+ if cancellable_background_tasks. cancellation_token . is_cancelled ( ) {
149+ debug_assert ! (
150+ cancellable_background_tasks. tasks. is_empty( ) ,
151+ "Expected all cancellable background tasks to be stopped"
152+ ) ;
153+ cancellable_background_tasks. cancellation_token = CancellationToken :: new ( ) ;
154+ }
155+ cancellable_background_tasks. tasks . reopen ( ) ;
156+ cancellable_background_tasks. accepting_tasks = true ;
130157 }
131158
132159 pub fn spawn_background_processor_task < F > ( & self , future : F )
@@ -164,15 +191,15 @@ impl Runtime {
164191 }
165192
166193 pub fn abort_cancellable_background_tasks ( & self ) {
167- let mut tasks = {
194+ let tasks = {
168195 let mut cancellable_background_tasks =
169196 self . cancellable_background_tasks . lock ( ) . expect ( "lock" ) ;
170197 cancellable_background_tasks. accepting_tasks = false ;
171- core:: mem:: take ( & mut cancellable_background_tasks. tasks )
198+ cancellable_background_tasks. tasks . close ( ) ;
199+ cancellable_background_tasks. cancellation_token . cancel ( ) ;
200+ cancellable_background_tasks. tasks . clone ( )
172201 } ;
173- debug_assert ! ( tasks. len( ) > 0 , "Expected some cancellable background_tasks" ) ;
174- tasks. abort_all ( ) ;
175- self . block_on ( async { while let Some ( _) = tasks. join_next ( ) . await { } } )
202+ self . block_on ( tasks. wait ( ) )
176203 }
177204
178205 pub fn wait_on_background_tasks ( & self ) {
@@ -381,19 +408,68 @@ impl FutureSpawner for RuntimeSpawner {
381408
382409#[ cfg( test) ]
383410mod tests {
384- use tokio:: sync:: oneshot;
411+ use tokio:: sync:: { mpsc , oneshot} ;
385412
386413 use super :: * ;
387414
415+ struct DropNotifier ( Option < oneshot:: Sender < ( ) > > ) ;
416+
417+ impl Drop for DropNotifier {
418+ fn drop ( & mut self ) {
419+ if let Some ( sender) = self . 0 . take ( ) {
420+ let _ = sender. send ( ( ) ) ;
421+ }
422+ }
423+ }
424+
388425 fn test_runtime ( ) -> Runtime {
389426 Runtime :: new ( Arc :: new ( Logger :: new_log_facade ( ) ) ) . unwrap ( )
390427 }
391428
429+ #[ test]
430+ fn completed_cancellable_tasks_are_released_before_shutdown ( ) {
431+ const TASK_COUNT : usize = 64 ;
432+
433+ let runtime = test_runtime ( ) ;
434+ let ( completion_sender, mut completion_receiver) = mpsc:: channel ( TASK_COUNT ) ;
435+ for _ in 0 ..TASK_COUNT {
436+ let completion_sender = completion_sender. clone ( ) ;
437+ runtime. spawn_cancellable_background_task ( async move {
438+ completion_sender. send ( ( ) ) . await . expect ( "completion receiver should be open" ) ;
439+ } ) ;
440+ }
441+ drop ( completion_sender) ;
442+
443+ let completed_tasks_are_released = runtime. block_on ( async {
444+ for _ in 0 ..TASK_COUNT {
445+ completion_receiver. recv ( ) . await . expect ( "cancellable task should complete" ) ;
446+ }
447+
448+ tokio:: time:: timeout ( Duration :: from_secs ( 1 ) , async {
449+ loop {
450+ if runtime. cancellable_background_tasks . lock ( ) . expect ( "lock" ) . tasks . is_empty ( ) {
451+ break ;
452+ }
453+ tokio:: task:: yield_now ( ) . await ;
454+ }
455+ } )
456+ . await
457+ . is_ok ( )
458+ } ) ;
459+
460+ assert ! (
461+ completed_tasks_are_released,
462+ "completed cancellable tasks should be released before shutdown"
463+ ) ;
464+ }
465+
392466 #[ test]
393467 fn late_cancellable_spawns_are_not_polled_after_abort ( ) {
394468 let runtime = test_runtime ( ) ;
395469 let ( started_sender, started_receiver) = oneshot:: channel ( ) ;
470+ let ( dropped_sender, dropped_receiver) = oneshot:: channel ( ) ;
396471 runtime. spawn_cancellable_background_task ( async move {
472+ let _drop_notifier = DropNotifier ( Some ( dropped_sender) ) ;
397473 let _ = started_sender. send ( ( ) ) ;
398474 std:: future:: pending :: < ( ) > ( ) . await ;
399475 } ) ;
@@ -402,6 +478,9 @@ mod tests {
402478 } ) ;
403479
404480 runtime. abort_cancellable_background_tasks ( ) ;
481+ runtime. block_on ( async {
482+ dropped_receiver. await . expect ( "aborted task should be dropped before abort returns" ) ;
483+ } ) ;
405484
406485 let ( late_spawn_sender, late_spawn_receiver) = oneshot:: channel ( ) ;
407486 runtime. spawn_cancellable_background_task ( async move {
0 commit comments