Skip to content

fix: 修复退出串流后客户端显示主机离线的问题#586

Merged
qiin2333 merged 1 commit intomasterfrom
fix/zombie-broadcast-and-vdd-topology
Apr 8, 2026
Merged

fix: 修复退出串流后客户端显示主机离线的问题#586
qiin2333 merged 1 commit intomasterfrom
fix/zombie-broadcast-and-vdd-topology

Conversation

@qiin2333
Copy link
Copy Markdown
Collaborator

@qiin2333 qiin2333 commented Apr 8, 2026

根因分析:

  1. 僵尸广播(zombie broadcast):延迟到达的 ABR HTTP 请求在 session 结束后 触发 change_dynamic_param_for_client(),该函数调用 broadcast_shared.ref() 创建了一个临时的广播上下文,随后 end_broadcast() 中的 control_thread.join() 阻塞了 HTTPS handler 线程,导致 HTTPS 服务器 无法接受新连接,客户端显示主机离线。

  2. VDD 模式下初始拓扑错误:apply_config 在 VDD 模式下调用 get_current_topology_metadata() 获取的是 VDD 启用后的当前拓扑 (只有 VDD 设备),忽略了 pre_saved_initial_topology(物理显示器拓扑)。 恢复时 remove_vdd_from_topology() 将初始拓扑清空,物理显示器无法 被重新启用。

修复:

  • stream.cpp: 在 change_dynamic_param_for_client 中添加 has_ref() 前置 检查,无活跃 session 时不触发僵尸广播
  • settings.cpp: VDD 模式下使用 pre_saved_initial_topology 替换 get_current_topology_metadata 返回的 VDD-only 初始拓扑

根因分析:
1. 僵尸广播(zombie broadcast):延迟到达的 ABR HTTP 请求在 session 结束后
   触发 change_dynamic_param_for_client(),该函数调用 broadcast_shared.ref()
   创建了一个临时的广播上下文,随后 end_broadcast() 中的
   control_thread.join() 阻塞了 HTTPS handler 线程,导致 HTTPS 服务器
   无法接受新连接,客户端显示主机离线。

2. VDD 模式下初始拓扑错误:apply_config 在 VDD 模式下调用
   get_current_topology_metadata() 获取的是 VDD 启用后的当前拓扑
   (只有 VDD 设备),忽略了 pre_saved_initial_topology(物理显示器拓扑)。
   恢复时 remove_vdd_from_topology() 将初始拓扑清空,物理显示器无法
   被重新启用。

修复:
- stream.cpp: 在 change_dynamic_param_for_client 中添加 has_ref() 前置
  检查,无活跃 session 时不触发僵尸广播
- settings.cpp: VDD 模式下使用 pre_saved_initial_topology 替换
  get_current_topology_metadata 返回的 VDD-only 初始拓扑
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Summary by CodeRabbit

Bug Fixes

  • 虚拟显示设备拓扑恢复优化 - 改进了VDD模式下显示配置的拓扑数据处理,确保物理显示的初始配置在还原操作时得到正确保留。

  • 流媒体参数更新稳定性提升 - 优化了动态参数变更逻辑,避免在没有活跃会话时执行不必要的广播控制操作。

概述

两个模块添加了防御性检查以优化特定场景。VDD模式中的拓扑处理现在有条件地恢复预存的初始拓扑元数据,防止物理显示器配置丢失。流广播代码新增早期退出检查,避免在无活跃会话时进行不必要处理。

变更

模块 / 文件 变更摘要
显示设备拓扑管理
src/platform/windows/display_device/settings.cpp
VDD模式下的apply_config函数在获取当前拓扑元数据后,现在会条件性地使用预保存的初始拓扑覆盖topology_result->pair.initial。防止还原过程中remove_vdd_from_topology清除物理显示器的初始拓扑配置。
流广播参数管理
src/stream.cpp
stream::change_dynamic_param_for_client函数开头新增broadcast_shared.has_ref()检查,当无活跃广播引用时立即返回false。避免在无现有会话时尝试获取广播上下文和启动控制流程。

代码审查工作量评估

🎯 2 (简单) | ⏱️ ~10 分钟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 拉取请求的标题准确反映了主要变更内容——修复客户端在退出串流后显示主机离线的问题,这正是本次代码变更的核心目标。
Description check ✅ Passed 拉取请求的描述详细说明了两个根本原因(僵尸广播和VDD模式下的拓扑问题)以及相应的修复措施,与代码变更内容完全相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/zombie-broadcast-and-vdd-topology

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/stream.cpp (1)

3055-3060: TOCTOU 竞态条件仍存在,虽改善常见场景但未完全消除问题

此修复有效改善了主要问题:在明确没有活跃引用时避免触发僵尸广播。然而,has_ref()ref() 各自独立加锁,存在竞态窗口:线程 A 调用 has_ref() 看到 _count > 0 后,线程 B 完全释放引用(_count = 0,调用 end_broadcast),随后线程 A 调用 ref()_count == 0,会再次调用 _construct()(即 start_broadcast),重新触发启停周期。

虽然此竞态窗口很小且实际触发概率低(特别是客户端发送动态参数变更时通常有活跃会话),但从线程安全角度讲仍不完美。建议在 shared_t 类中添加原子的 try_ref() 方法,仅当已有活跃引用时才增加计数,这样可完全消除竞态条件。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stream.cpp` around lines 3055 - 3060, The current check using
broadcast_shared.has_ref() followed by broadcast_shared.ref() has a TOCTOU race
where has_ref() and ref() lock separately allowing _count to drop to zero
between calls and re-trigger start_broadcast/end_broadcast; add an atomic
try_ref() method to shared_t that increments the reference count only if it is
already >0 and returns success/failure, then replace the has_ref()+ref() pattern
in the stream logic (where broadcast_shared is used) with a single call to
try_ref() to ensure no new reference is created when the count reached zero and
eliminate the race that recreates the broadcast cycle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/stream.cpp`:
- Around line 3055-3060: The current check using broadcast_shared.has_ref()
followed by broadcast_shared.ref() has a TOCTOU race where has_ref() and ref()
lock separately allowing _count to drop to zero between calls and re-trigger
start_broadcast/end_broadcast; add an atomic try_ref() method to shared_t that
increments the reference count only if it is already >0 and returns
success/failure, then replace the has_ref()+ref() pattern in the stream logic
(where broadcast_shared is used) with a single call to try_ref() to ensure no
new reference is created when the count reached zero and eliminate the race that
recreates the broadcast cycle.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f2d78b1b-3999-4b19-9d0b-f52e7bf91065

📥 Commits

Reviewing files that changed from the base of the PR and between f520396 and aafa965.

📒 Files selected for processing (2)
  • src/platform/windows/display_device/settings.cpp
  • src/stream.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/platform/windows/display_device/settings.cpp
  • src/stream.cpp
src/platform/**

⚙️ CodeRabbit configuration file

src/platform/**: 平台抽象层代码(Windows/Linux/macOS)。确保各平台实现一致, 注意 Windows API 调用的错误处理和资源释放。

Files:

  • src/platform/windows/display_device/settings.cpp
🔇 Additional comments (1)
src/platform/windows/display_device/settings.cpp (1)

797-805: VDD 初始拓扑覆盖逻辑实现正确。

这里在 pre_saved_initial_topology 有效且非空时覆盖 pair.initial,可避免将 VDD-only 拓扑误持久化为初始拓扑,修复恢复阶段物理显示器无法重新启用的问题。

@qiin2333 qiin2333 merged commit 33bc20d into master Apr 8, 2026
4 checks passed
@qiin2333 qiin2333 deleted the fix/zombie-broadcast-and-vdd-topology branch April 8, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant