@@ -27,6 +27,7 @@ use anyhow::{anyhow, Context};
2727use futures_core:: Stream ;
2828use std:: {
2929 collections:: HashMap ,
30+ ops:: ControlFlow ,
3031 pin:: Pin ,
3132 sync:: Arc ,
3233 task:: { Context as TaskContext , Poll } ,
@@ -51,6 +52,7 @@ pub struct Manager {
5152 event_in_tx : mpsc:: Sender < InputEvent > ,
5253 event_in_rx : mpsc:: Receiver < InputEvent > ,
5354 event_out_tx : mpsc:: Sender < OutputEvent > ,
55+ shutdown_rx : watch:: Receiver < bool > ,
5456 handle_allocator : packet:: HandleAllocator ,
5557 descriptors : HashMap < Handle , Descriptor > ,
5658}
@@ -67,13 +69,15 @@ impl Manager {
6769 pub fn new ( options : ManagerOptions ) -> ( Self , ManagerInput , ManagerOutput ) {
6870 let ( event_in_tx, event_in_rx) = mpsc:: channel ( Self :: EVENT_BUFFER_COUNT ) ;
6971 let ( event_out_tx, event_out_rx) = mpsc:: channel ( Self :: EVENT_BUFFER_COUNT ) ;
72+ let ( shutdown_tx, shutdown_rx) = watch:: channel ( false ) ;
7073
71- let event_in = ManagerInput :: new ( event_in_tx. clone ( ) ) ;
74+ let event_in = ManagerInput :: new ( event_in_tx. clone ( ) , shutdown_tx ) ;
7275 let manager = Manager {
7376 encryption_provider : options. encryption_provider ,
7477 event_in_tx,
7578 event_in_rx,
7679 event_out_tx,
80+ shutdown_rx,
7781 handle_allocator : packet:: HandleAllocator :: default ( ) ,
7882 descriptors : HashMap :: new ( ) ,
7983 } ;
@@ -88,25 +92,52 @@ impl Manager {
8892 ///
8993 pub async fn run ( mut self ) {
9094 log:: debug!( "Task started" ) ;
91- while let Some ( event) = self . event_in_rx . recv ( ) . await {
92- log:: debug!( "Input event: {:?}" , event) ;
93- match event {
94- InputEvent :: PublishRequest ( event) => self . on_publish_request ( event) . await ,
95- InputEvent :: PublishCancelled ( event) => self . on_publish_cancelled ( event) . await ,
96- InputEvent :: QueryPublished ( event) => self . on_query_published ( event) . await ,
97- InputEvent :: UnpublishRequest ( event) => self . on_unpublish_request ( event) . await ,
98- InputEvent :: SfuPublishResponse ( event) => self . on_sfu_publish_response ( event) . await ,
99- InputEvent :: SfuUnpublishResponse ( event) => {
100- self . on_sfu_unpublish_response ( event) . await
95+ loop {
96+ tokio:: select! {
97+ // Biased so queued events are still processed in order once
98+ // shutdown has been signalled out-of-band; see `ManagerInput::send`.
99+ biased;
100+ event = self . event_in_rx. recv( ) => {
101+ let Some ( event) = event else { break } ;
102+ if self . handle_event( event) . await . is_break( ) {
103+ break ;
104+ }
105+ }
106+ _ = self . shutdown_rx. changed( ) => {
107+ self . drain_pending( ) . await ;
108+ break ;
101109 }
102- InputEvent :: RepublishTracks => self . on_republish_tracks ( ) . await ,
103- InputEvent :: Shutdown => break ,
104110 }
105111 }
106112 self . shutdown ( ) . await ;
107113 log:: debug!( "Task ended" ) ;
108114 }
109115
116+ /// Drains events that were queued ahead of an out-of-band shutdown signal.
117+ async fn drain_pending ( & mut self ) {
118+ while let Ok ( event) = self . event_in_rx . try_recv ( ) {
119+ if self . handle_event ( event) . await . is_break ( ) {
120+ break ;
121+ }
122+ }
123+ }
124+
125+ /// Handles a single input event, reporting whether the task should stop.
126+ async fn handle_event ( & mut self , event : InputEvent ) -> ControlFlow < ( ) > {
127+ log:: debug!( "Input event: {:?}" , event) ;
128+ match event {
129+ InputEvent :: PublishRequest ( event) => self . on_publish_request ( event) . await ,
130+ InputEvent :: PublishCancelled ( event) => self . on_publish_cancelled ( event) . await ,
131+ InputEvent :: QueryPublished ( event) => self . on_query_published ( event) . await ,
132+ InputEvent :: UnpublishRequest ( event) => self . on_unpublish_request ( event) . await ,
133+ InputEvent :: SfuPublishResponse ( event) => self . on_sfu_publish_response ( event) . await ,
134+ InputEvent :: SfuUnpublishResponse ( event) => self . on_sfu_unpublish_response ( event) . await ,
135+ InputEvent :: RepublishTracks => self . on_republish_tracks ( ) . await ,
136+ InputEvent :: Shutdown => return ControlFlow :: Break ( ( ) ) ,
137+ }
138+ ControlFlow :: Continue ( ( ) )
139+ }
140+
110141 async fn on_publish_request ( & mut self , event : PublishRequest ) {
111142 if let Err ( error) = crate :: schema:: validate_schema (
112143 event. options . frame_encoding . as_ref ( ) ,
@@ -308,18 +339,37 @@ impl Manager {
308339 }
309340
310341 /// Performs cleanup before the task ends.
311- async fn shutdown ( self ) {
312- for ( _, descriptor) in self . descriptors {
342+ async fn shutdown ( mut self ) {
343+ let mut task_handles = Vec :: new ( ) ;
344+ for ( _, descriptor) in std:: mem:: take ( & mut self . descriptors ) {
313345 match descriptor {
314346 Descriptor :: Pending ( result_tx) => {
315347 _ = result_tx. send ( Err ( PublishError :: Disconnected ) )
316348 }
317349 Descriptor :: Active { state_tx, task_handle, .. } => {
318350 _ = state_tx. send ( PublishState :: Unpublished ) ;
319- task_handle . await ;
351+ task_handles . push ( task_handle ) ;
320352 }
321353 }
322354 }
355+
356+ // Track tasks emit a final unpublish request as they end, so the input
357+ // channel has to keep draining while they are joined. Joining without
358+ // draining deadlocks as soon as more tasks are ending than the channel
359+ // can buffer.
360+ let join_tasks = async {
361+ for task_handle in task_handles {
362+ task_handle. await ;
363+ }
364+ } ;
365+ tokio:: pin!( join_tasks) ;
366+ loop {
367+ tokio:: select! {
368+ _ = & mut join_tasks => break ,
369+ // Never yields `None`: the manager owns a sender for its own lifetime.
370+ _ = self . event_in_rx. recv( ) => { }
371+ }
372+ }
323373 }
324374
325375 /// Maximum number of outgoing frames to buffer per track.
@@ -415,7 +465,7 @@ pub(crate) enum PublishState {
415465#[ derive( Debug , Clone ) ]
416466pub struct ManagerInput {
417467 event_in_tx : mpsc:: Sender < InputEvent > ,
418- _drop_guard : Arc < DropGuard > ,
468+ drop_guard : Arc < DropGuard > ,
419469}
420470
421471/// Stream of [`OutputEvent`]s produced by [`Manager`].
@@ -430,25 +480,32 @@ impl Stream for ManagerOutput {
430480 }
431481}
432482
433- /// Guard that sends shutdown event when the last reference is dropped.
483+ /// Guard that signals shutdown when the last reference is dropped.
434484#[ derive( Debug ) ]
435485struct DropGuard {
436- event_in_tx : mpsc :: Sender < InputEvent > ,
486+ shutdown_tx : watch :: Sender < bool > ,
437487}
438488
439489impl Drop for DropGuard {
440490 fn drop ( & mut self ) {
441- _ = self . event_in_tx . try_send ( InputEvent :: Shutdown ) ;
491+ _ = self . shutdown_tx . send ( true ) ;
442492 }
443493}
444494
445495impl ManagerInput {
446- fn new ( event_in_tx : mpsc:: Sender < InputEvent > ) -> Self {
447- Self { event_in_tx : event_in_tx . clone ( ) , _drop_guard : DropGuard { event_in_tx } . into ( ) }
496+ fn new ( event_in_tx : mpsc:: Sender < InputEvent > , shutdown_tx : watch :: Sender < bool > ) -> Self {
497+ Self { event_in_tx, drop_guard : DropGuard { shutdown_tx } . into ( ) }
448498 }
449499
450500 /// Sends an input event to the manager's task to be processed.
451501 pub fn send ( & self , event : InputEvent ) -> Result < ( ) , InternalError > {
502+ // Shutdown bypasses the bounded event channel. In-flight track events
503+ // routinely saturate it, and a shutdown dropped for lack of capacity
504+ // strands the manager task along with everyone awaiting its completion.
505+ if matches ! ( event, InputEvent :: Shutdown ) {
506+ _ = self . drop_guard . shutdown_tx . send ( true ) ;
507+ return Ok ( ( ) ) ;
508+ }
452509 Ok ( self . event_in_tx . try_send ( event) . context ( "Failed to handle input event" ) ?)
453510 }
454511
@@ -527,6 +584,30 @@ mod tests {
527584 timeout ( Duration :: from_secs ( 1 ) , join_handle) . await . unwrap ( ) ;
528585 }
529586
587+ #[ tokio:: test]
588+ async fn test_task_shutdown_with_saturated_event_channel ( ) {
589+ let options = ManagerOptions { encryption_provider : None } ;
590+ let ( manager, input, _output) = Manager :: new ( options) ;
591+
592+ // Fill the event channel before the manager starts draining it so that
593+ // shutdown cannot depend on any remaining capacity.
594+ let mut result_rxs = Vec :: new ( ) ;
595+ for _ in 0 ..Manager :: EVENT_BUFFER_COUNT {
596+ let ( result_tx, result_rx) = oneshot:: channel ( ) ;
597+ input. send ( QueryPublished { result_tx } . into ( ) ) . unwrap ( ) ;
598+ result_rxs. push ( result_rx) ;
599+ }
600+ input. send ( InputEvent :: Shutdown ) . unwrap ( ) ;
601+
602+ let join_handle = livekit_runtime:: spawn ( manager. run ( ) ) ;
603+ timeout ( Duration :: from_secs ( 1 ) , join_handle) . await . unwrap ( ) ;
604+
605+ // Events queued ahead of the shutdown signal are still processed.
606+ for result_rx in result_rxs {
607+ assert ! ( result_rx. await . is_ok( ) ) ;
608+ }
609+ }
610+
530611 #[ tokio:: test]
531612 async fn test_publish ( ) {
532613 let payload_size = 256 ;
0 commit comments