Skip to content

Commit f3bdba6

Browse files
Shutdown no longer travels through event channel, resolving data tracks deadlock on shutdown under high load
1 parent 06371a3 commit f3bdba6

3 files changed

Lines changed: 193 additions & 44 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
livekit-datatrack: patch
3+
livekit-ffi: patch
4+
livekit-uniffi: patch
5+
livekit: patch
6+
---
7+
8+
Fix a data track manager deadlock during room disconnect. Shutdown was delivered
9+
through the bounded event channel with `try_send`, so it was silently dropped
10+
whenever in-flight track events had saturated the channel, leaving the manager
11+
task and every caller awaiting disconnect stranded.

livekit-datatrack/src/local/manager.rs

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use anyhow::{anyhow, Context};
2727
use futures_core::Stream;
2828
use 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)]
416466
pub 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)]
435485
struct DropGuard {
436-
event_in_tx: mpsc::Sender<InputEvent>,
486+
shutdown_tx: watch::Sender<bool>,
437487
}
438488

439489
impl 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

445495
impl 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

Comments
 (0)