Skip to content

fix(streaming): move connection start off UI thread#49

Merged
qiin2333 merged 1 commit into
masterfrom
codex/fix-start-connection-freeze
Jun 22, 2026
Merged

fix(streaming): move connection start off UI thread#49
qiin2333 merged 1 commit into
masterfrom
codex/fix-start-connection-freeze

Conversation

@qiin2333

@qiin2333 qiin2333 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

改了啥呀

  • 新增 native startConnectionAsync,把 LiStartConnection 丢到 N-API async work 里跑,别再让主线程陪 ENet 等网络啦,冻屏小杂鱼退散。
  • StreamingSession 改成等待异步启动,并记录 pending start 状态。
  • 停止/取消时先 interruptConnection(),等启动返回后再 stopConnection(),贴合 moonlight-common-c 里 LiStartConnection / LiStopConnection 非线程安全的约束。
  • 补了主动取消、新会话复位、native 参数复制和 server info 释放这些边界。

为啥要改

冻屏日志里的主线程栈卡在 MoonBridge_StartConnection -> LiStartConnection -> startControlStream -> enet_socket_wait/poll,属于同步 native 网络等待把 ArkTS/UI 线程堵住了。把连接启动移出主线程后,即使控制流连接等待或超时,也不会触发 THREAD_BLOCK_6S

验证

  • git diff --check -- nativelib/src/main/cpp/moonlight_bridge.cpp nativelib/src/main/cpp/moonlight_bridge.h nativelib/src/main/cpp/napi_init.cpp entry/src/main/ets/service/streaming/StreamingSession.ets entry/src/main/ets/service/streaming/MoonBridge.ets
  • /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/build-tools/cmake/bin/ninja -C nativelib/.cxx/default/default/debug/arm64-v8a moonlight_nativelib
  • /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/build-tools/cmake/bin/ninja -C nativelib/.cxx/default/default/debug/x86_64 moonlight_nativelib
  • JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home PATH=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home/bin:$PATH NODE_PATH=/Users/mac/Program/moonlight-harmony/node_modules:/Applications/DevEco-Studio.app/Contents/tools/hvigor/hvigor/node_modules:/Applications/DevEco-Studio.app/Contents/tools/hvigor/hvigor-ohos-plugin/node_modules DEVECO_SDK_HOME=/Users/mac/ohos-sdk-cache/6.1-Release-mac/sdk-ci-shape node hvigorw.js assembleApp --mode project -p product=default --no-daemon --stacktrace

Summary by CodeRabbit

发布说明

  • 新功能

    • 新增异步连接启动接口(startConnectionAsync),以 Promise 方式返回启动结果,提升流媒体初始化期间的应用响应性。
  • 改进

    • 强化连接启动并发控制与忙碌反馈,避免启动互相干扰。
    • 优化启动/停止竞态处理与清理流程,提升停止过程的稳定性。
    • 连接终止时会额外触发桥接层通知,以完善启动保护的释放。

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@qiin2333, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 40 minutes and 15 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d5b7327f-61dd-40d6-93c6-6c5a365d8136

📥 Commits

Reviewing files that changed from the base of the PR and between f4b5927 and e3ae584.

📒 Files selected for processing (6)
  • entry/src/main/ets/service/streaming/MoonBridge.ets
  • entry/src/main/ets/service/streaming/StreamingSession.ets
  • nativelib/src/main/cpp/callbacks.cpp
  • nativelib/src/main/cpp/moonlight_bridge.cpp
  • nativelib/src/main/cpp/moonlight_bridge.h
  • nativelib/src/main/cpp/napi_init.cpp
📝 Walkthrough

Walkthrough

在 C++ 原生层引入连接启动状态机与并发控制,新增 PrepareStartConnectionStartConnectionWithPreparedInfo 等共享辅助函数,重构同步启动流程并通过 N-API Promise + async work 新增异步接口;ArkTS MoonBridge 层包装异步接口;StreamingSession 改用异步启动并添加启动状态跟踪、用户中断短路与清理竞态防护。

Changes

异步连接启动全栈实现

Layer / File(s) Summary
原生头文件声明与核心依赖
nativelib/src/main/cpp/moonlight_bridge.h, nativelib/src/main/cpp/moonlight_bridge.cpp
新增 MoonBridge_StartConnectionAsyncMoonBridge_OnConnectionTerminated 函数声明;cpp 补充 <cstdlib><cerrno><mutex> 头文件。
原生连接启动状态机与辅助函数基础
nativelib/src/main/cpp/moonlight_bridge.cpp
定义 ConnectionState 枚举、StartConnectionContext 结构体与全局互斥锁;新增 CreateResolvedInt32Promise 快速返回帮助函数;FreeServerInfo 统一释放字符串;PrepareStartConnection 解析参数、校验 AES Key/IV、推导 HDR 配置。
原生状态转移与启动执行
nativelib/src/main/cpp/moonlight_bridge.cpp
TryAcquireStartSlot/CompleteStartSlot/PromoteStartSlotAfterCleanup 管理并发与状态转移;BeginStopConnection/MarkConnectionIdle 处理停止与启动中断;StartConnectionWithPreparedInfo 回填全局配置、设置解码器并调用 LiStartConnection
原生同步与异步启动 API 实现
nativelib/src/main/cpp/moonlight_bridge.cpp
MoonBridge_StartConnection 改为调用共享辅助函数的完整流程;新增 ExecuteStartConnectionAsync/CompleteStartConnectionAsync 在工作线程中执行启动;MoonBridge_StartConnectionAsync 创建 promise 与 async work,包含失败路径的资源清理与状态恢复。
原生回调与模块导出
nativelib/src/main/cpp/callbacks.cpp, nativelib/src/main/cpp/napi_init.cpp
callbacks.cpp 新增 #include "moonlight_bridge.h" 并在 BridgeClConnectionTerminated 中调用 MoonBridge_OnConnectionTerminated;napi_init.cpp 注册 startConnectionAsync 导出项。
ArkTS 桥接与接口声明
entry/src/main/ets/service/streaming/MoonBridge.ets, entry/src/main/ets/service/streaming/StreamingSession.ets
MoonBridgeClass.startConnectionAsync 映射 StreamConfiguration 并调用原生异步接口,返回 Promise<number>StreamingSession.NativeModule 接口新增 startConnectionAsync 声明。
StreamingSession 异步启动状态跟踪与竞态防护
entry/src/main/ets/service/streaming/StreamingSession.ets
新增 isStartingConnection/connectionStartPromise 状态字段;start() 重置 userInitiatedStopconnectToServer() 改用 await callStart() 执行异步启动,finally 块清理状态;连接后与重试后各增加 userInitiatedStop 短路;cleanup() 启动中时先中断连接再等待 Promise,失败仅记告警。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • AlkaidLab/moonlight-harmony#28: 修改了 StreamingSession.etsconnectToServer() 同步启动流程,与本 PR 在同一代码位置的异步改造存在直接重叠。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了主要改进目标,清晰指出将同步的连接启动转移到异步执行以避免阻塞 UI 线程。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-start-connection-freeze

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
nativelib/src/main/cpp/moonlight_bridge.cpp (2)

389-415: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

线程安全依赖上层协调。

StartConnectionWithPreparedInfo 写入全局变量 g_streamConfigg_videoCapabilities。异步版本从工作线程调用此函数,同步版本从 UI 线程调用。线程安全依赖 ArkTS 层的 isStartingConnection 状态跟踪来防止并发调用。

当前设计是可行的,但建议在函数注释中说明此约束,避免后续维护时直接从原生层调用导致竞态。

Also applies to: 475-479

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nativelib/src/main/cpp/moonlight_bridge.cpp` around lines 389 - 415, The
function `StartConnectionWithPreparedInfo` modifies global variables
`g_streamConfig` and `g_videoCapabilities` which are accessed from multiple
threads (worker thread for async calls and UI thread for sync calls). Thread
safety currently depends on ArkTS layer coordination using
`isStartingConnection` state tracking to prevent concurrent calls. Add
documentation comments to the `StartConnectionWithPreparedInfo` function that
clearly explain this thread safety constraint and that concurrent calls must be
prevented at the ArkTS layer to avoid race conditions, helping future
maintainers understand why the function cannot be safely called directly from
multiple threads without proper synchronization.

364-373: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

AES Key/IV 验证失败时仍继续执行连接流程。

aesKeyDataaesIvData 长度不足时,仅记录错误日志但继续使用可能未初始化的密钥/IV 数据。这可能导致连接使用无效的加密参数。

建议在关键加密参数无效时返回 false,让调用方显式处理错误。

💡 建议的修改
     if (aesKeyData && aesKeyLength >= 16) {
         memcpy(streamConfig->remoteInputAesKey, aesKeyData, 16);
     } else {
         OH_LOG_ERROR(LOG_APP, "  riKey: INVALID (data=%{public}p, len=%{public}zu)", aesKeyData, aesKeyLength);
+        return false;
     }
     if (aesIvData && aesIvLength >= 16) {
         memcpy(streamConfig->remoteInputAesIv, aesIvData, 16);
     } else {
         OH_LOG_ERROR(LOG_APP, "  riIv: INVALID (data=%{public}p, len=%{public}zu)", aesIvData, aesIvLength);
+        FreeServerInfo(serverInfo);
+        return false;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nativelib/src/main/cpp/moonlight_bridge.cpp` around lines 364 - 373, The
validation checks for aesKeyData and aesIvData only log errors in the else
blocks but allow execution to continue with potentially invalid encryption
parameters. When the condition aesKeyLength < 16 or aesIvLength < 16 is detected
in the else blocks following the memcpy calls for
streamConfig->remoteInputAesKey and streamConfig->remoteInputAesIv, the function
should return false to halt the connection process instead of merely logging the
error. This ensures the caller can explicitly handle the failure rather than
proceeding with uninitialized encryption data.
entry/src/main/ets/service/streaming/StreamingSession.ets (1)

1529-1539: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

cleanup 对启动中断处理正确,但建议添加防御性检查。

当前逻辑:先取 connectionStartPromise 引用,再调用 interruptConnection(),最后等待。这正确处理了 Promise 可能在检查和使用之间被清空的情况。

interruptConnection() 调用应在 pendingStart !== null 检查内部,避免不必要的中断调用。

💡 建议的优化
     if (this.isStartingConnection) {
       const pendingStart = this.connectionStartPromise;
-      this.nativeModule.interruptConnection();
       if (pendingStart !== null) {
+        this.nativeModule.interruptConnection();
         try {
           await pendingStart;
         } catch (err) {
           console.warn(`等待启动中断完成失败: ${err}`);
         }
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@entry/src/main/ets/service/streaming/StreamingSession.ets` around lines 1529
- 1539, The `interruptConnection()` call on `this.nativeModule` is being
executed unconditionally outside the `pendingStart !== null` check, which can
trigger unnecessary interrupt operations. Refactor the code so that
`interruptConnection()` is only called when `pendingStart` is confirmed to be
not null, by moving the call inside the `if (pendingStart !== null)` conditional
block or adding an additional check before calling it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@entry/src/main/ets/service/streaming/StreamingSession.ets`:
- Around line 1529-1539: The `interruptConnection()` call on `this.nativeModule`
is being executed unconditionally outside the `pendingStart !== null` check,
which can trigger unnecessary interrupt operations. Refactor the code so that
`interruptConnection()` is only called when `pendingStart` is confirmed to be
not null, by moving the call inside the `if (pendingStart !== null)` conditional
block or adding an additional check before calling it.

In `@nativelib/src/main/cpp/moonlight_bridge.cpp`:
- Around line 389-415: The function `StartConnectionWithPreparedInfo` modifies
global variables `g_streamConfig` and `g_videoCapabilities` which are accessed
from multiple threads (worker thread for async calls and UI thread for sync
calls). Thread safety currently depends on ArkTS layer coordination using
`isStartingConnection` state tracking to prevent concurrent calls. Add
documentation comments to the `StartConnectionWithPreparedInfo` function that
clearly explain this thread safety constraint and that concurrent calls must be
prevented at the ArkTS layer to avoid race conditions, helping future
maintainers understand why the function cannot be safely called directly from
multiple threads without proper synchronization.
- Around line 364-373: The validation checks for aesKeyData and aesIvData only
log errors in the else blocks but allow execution to continue with potentially
invalid encryption parameters. When the condition aesKeyLength < 16 or
aesIvLength < 16 is detected in the else blocks following the memcpy calls for
streamConfig->remoteInputAesKey and streamConfig->remoteInputAesIv, the function
should return false to halt the connection process instead of merely logging the
error. This ensures the caller can explicitly handle the failure rather than
proceeding with uninitialized encryption data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40a6c638-88c6-4ec8-a133-74660e9ca9f6

📥 Commits

Reviewing files that changed from the base of the PR and between 1cb6899 and 64cf0b4.

📒 Files selected for processing (5)
  • entry/src/main/ets/service/streaming/MoonBridge.ets
  • entry/src/main/ets/service/streaming/StreamingSession.ets
  • nativelib/src/main/cpp/moonlight_bridge.cpp
  • nativelib/src/main/cpp/moonlight_bridge.h
  • nativelib/src/main/cpp/napi_init.cpp

@qiin2333 qiin2333 force-pushed the codex/fix-start-connection-freeze branch 2 times, most recently from 1c104a0 to f4b5927 Compare June 22, 2026 11:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
nativelib/src/main/cpp/moonlight_bridge.cpp (1)

656-664: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

参数准备失败时同时抛出异常并返回 -1,行为不一致。

PrepareStartConnection 失败时,代码调用 napi_throw_error 抛出异常,但随后仍创建并返回一个值为 -1napi_value。在 N-API 中,抛出异常后应直接返回 nullptr 让运行时处理异常,而不是返回正常值。

同步版本(line 589-592)有相同问题。建议统一处理方式。

🔧 建议修复
     if (!PrepareStartConnection(env, args, argc, &context->serverInfo, &context->streamConfig,
                                 &videoCapabilities, &hdrMode)) {
         MarkConnectionIdle();
         delete context;
         napi_throw_error(env, nullptr, "参数不足");
-        napi_value result;
-        napi_create_int32(env, -1, &result);
-        return result;
+        return nullptr;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nativelib/src/main/cpp/moonlight_bridge.cpp` around lines 656 - 664, In the
error handling block following the PrepareStartConnection call, after invoking
napi_throw_error to throw an exception, remove the napi_create_int32 call and
the return of the -1 value, and instead directly return nullptr to allow the
N-API runtime to properly handle the thrown exception. Apply the same fix to the
synchronous version at lines 589-592 where the same pattern exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nativelib/src/main/cpp/moonlight_bridge.cpp`:
- Around line 603-611: In the ExecuteStartConnectionAsync function, after the
successful call to StartConnectionWithPreparedInfo with context->serverInfo,
transfer ownership of context->serverInfo to the global g_serverInfo instead of
allowing it to be freed when the context is destroyed. This ensures the
lifecycle management is consistent with the synchronous path where g_serverInfo
is freed by DoStopConnectionCleanup, preventing use-after-free issues since
LiStartConnection may store pointers internally and continue using them on the
connection thread. Add code to copy context->serverInfo into g_serverInfo before
the context is cleaned up so that stopConnection will properly release the
serverInfo that was actually used by the connection.

---

Nitpick comments:
In `@nativelib/src/main/cpp/moonlight_bridge.cpp`:
- Around line 656-664: In the error handling block following the
PrepareStartConnection call, after invoking napi_throw_error to throw an
exception, remove the napi_create_int32 call and the return of the -1 value, and
instead directly return nullptr to allow the N-API runtime to properly handle
the thrown exception. Apply the same fix to the synchronous version at lines
589-592 where the same pattern exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb98a7e0-971b-4100-abc4-00d0355a152d

📥 Commits

Reviewing files that changed from the base of the PR and between 64cf0b4 and f4b5927.

📒 Files selected for processing (6)
  • entry/src/main/ets/service/streaming/MoonBridge.ets
  • entry/src/main/ets/service/streaming/StreamingSession.ets
  • nativelib/src/main/cpp/callbacks.cpp
  • nativelib/src/main/cpp/moonlight_bridge.cpp
  • nativelib/src/main/cpp/moonlight_bridge.h
  • nativelib/src/main/cpp/napi_init.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • entry/src/main/ets/service/streaming/MoonBridge.ets
  • nativelib/src/main/cpp/napi_init.cpp
  • entry/src/main/ets/service/streaming/StreamingSession.ets

Comment thread nativelib/src/main/cpp/moonlight_bridge.cpp
@qiin2333 qiin2333 force-pushed the codex/fix-start-connection-freeze branch from f4b5927 to e3ae584 Compare June 22, 2026 12:04
@qiin2333 qiin2333 merged commit b938ede into master Jun 22, 2026
2 checks passed
@qiin2333 qiin2333 deleted the codex/fix-start-connection-freeze branch June 22, 2026 12:20
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