Bug: shutdown() 中 tx.send() 阻塞导致潜在死锁 (state_portal.rs) #8

Closed
opened 2026-06-04 20:59:06 +08:00 by dailz · 2 comments
Owner

位置

src/state_portal.rs:287-306

严重性

🔴

问题描述

shutdown() 调用 enc.flush()drain_encoder() 时,会通过 tx.send()(阻塞调用)向 webrtc channel 发送编码帧。但 shutdown() 中并未调用 poll_webrtc() 来消费 webrtc_rx,如果 channel 缓冲区(容量 32)已满,send() 将无限阻塞,导致 Drop 期间死锁。

shutdown()Drop::drop 中被调用,在析构期间阻塞特别危险——可能导致进程无法正常退出。

建议修复

shutdown() 中,先调用 poll_webrtc() 排空 channel,再进行 enc.flush(),并在 flush 后再次 drain:

pub fn shutdown(&mut self) {
    // 先排空 WebRTC channel,防止后续 flush 时 send 阻塞
    if let Err(e) = self.poll_webrtc() {
        tracing::error!("WebRTC drain before shutdown error: {e}");
    }
    if let Some(mut enc) = self.enc.take() {
        if let Err(e) = enc.flush() {
            tracing::error!("Flush error during shutdown: {e}");
        }
        // flush 后可能又有新帧入 channel,再次排空
        if let Err(e) = self.poll_webrtc() {
            tracing::error!("WebRTC drain after shutdown error: {e}");
        }
    }
    // ... stats logging ...
}
## 位置 `src/state_portal.rs:287-306` ## 严重性 🔴 高 ## 问题描述 `shutdown()` 调用 `enc.flush()` → `drain_encoder()` 时,会通过 `tx.send()`(阻塞调用)向 webrtc channel 发送编码帧。但 `shutdown()` 中并未调用 `poll_webrtc()` 来消费 `webrtc_rx`,如果 channel 缓冲区(容量 32)已满,`send()` 将无限阻塞,导致 Drop 期间死锁。 `shutdown()` 在 `Drop::drop` 中被调用,在析构期间阻塞特别危险——可能导致进程无法正常退出。 ## 建议修复 在 `shutdown()` 中,先调用 `poll_webrtc()` 排空 channel,再进行 `enc.flush()`,并在 flush 后再次 drain: ```rust pub fn shutdown(&mut self) { // 先排空 WebRTC channel,防止后续 flush 时 send 阻塞 if let Err(e) = self.poll_webrtc() { tracing::error!("WebRTC drain before shutdown error: {e}"); } if let Some(mut enc) = self.enc.take() { if let Err(e) = enc.flush() { tracing::error!("Flush error during shutdown: {e}"); } // flush 后可能又有新帧入 channel,再次排空 if let Err(e) = self.poll_webrtc() { tracing::error!("WebRTC drain after shutdown error: {e}"); } } // ... stats logging ... } ```
Author
Owner

test

test
Author
Owner

修复方案 (commit 36f07c9)

Bug 已确认并修复,双重防护:

根因分析

shutdown()enc.flush()drain_encoder()tx.send() 在满的 bounded(32) 通道上阻塞。由于 webrtc_rx 由同一个 StatePortal 持有且 shutdown() 中不会调用 poll_webrtc() 消费 receiver,导致自死锁。

修复内容

  1. avhw.rs: tx.send()tx.try_send(),区分 Full(丢帧+warn)和 Disconnected(标记断连)两种失败
  2. state_portal.rs: shutdown() 中 flush 前 self.webrtc_rx = None,使 try_send 立即返回 Disconnected

回归测试

新增 3 个测试验证通道语义:

  • try_send_full_channel_returns_full_not_block — 满通道不阻塞
  • try_send_after_rx_dropped_returns_disconnected — receiver drop 后立即返回
  • shutdown_rx_drop_prevents_deadlock_on_full_channel — 满通道+drop receiver 组合场景

审核过程

  • 初始分析误判为 false positive
  • Oracle (GPT-5.5) 重新审核确认为真实 bug,高置信度
  • 已验证 state.rs 不存在相同问题(无 shutdown/flush 路径)
## 修复方案 (commit 36f07c9) Bug 已确认并修复,双重防护: ### 根因分析 `shutdown()` → `enc.flush()` → `drain_encoder()` → `tx.send()` 在满的 `bounded(32)` 通道上阻塞。由于 `webrtc_rx` 由同一个 `StatePortal` 持有且 `shutdown()` 中不会调用 `poll_webrtc()` 消费 receiver,导致自死锁。 ### 修复内容 1. **`avhw.rs`**: `tx.send()` → `tx.try_send()`,区分 `Full`(丢帧+warn)和 `Disconnected`(标记断连)两种失败 2. **`state_portal.rs`**: `shutdown()` 中 flush 前 `self.webrtc_rx = None`,使 try_send 立即返回 `Disconnected` ### 回归测试 新增 3 个测试验证通道语义: - `try_send_full_channel_returns_full_not_block` — 满通道不阻塞 - `try_send_after_rx_dropped_returns_disconnected` — receiver drop 后立即返回 - `shutdown_rx_drop_prevents_deadlock_on_full_channel` — 满通道+drop receiver 组合场景 ### 审核过程 - 初始分析误判为 false positive - Oracle (GPT-5.5) 重新审核确认为真实 bug,高置信度 - 已验证 `state.rs` 不存在相同问题(无 shutdown/flush 路径)
dailz closed this issue 2026-06-06 20:03:34 +08:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dailz/wl-webrtc#8