Version
版本:@midscene/android 1.10.6(含 2807 的1.10.5-beta 起即存在)
Details
当 AndroidDevice.connect()(或 retryScrcpy())触发的 scrcpy 连接在初始化中途失败时,连接错误本身能被正确捕获并通过
getScrcpyStatus().lastError 暴露;但同一次失败还会额外泄漏一个未处理的 promise rejection,冒泡到进程级触发 Node 默认的
--unhandled-rejections=throw,直接以 exit code 1 杀掉进程。
结果:调用方即使正确地用 getScrcpyStatus() 检测到失败并准备重试/降级,也会在重试跑起来之前被这个逃逸的 rejection
杀死——无法在库外捕获。
逃逸 rejection 的堆栈
Error: ExactReadable ended
at BufferedReadableStream.#readSource (@yume-chan/stream-extra/src/buffered.ts:57:19)
at AdbServerStream.readOkay (@yume-chan/adb/src/server/stream.ts:59:26)
at AdbServerClient.#waitForUnchecked (@yume-chan/adb/src/server/client.ts:380:13)
根因分析(依据 dist/es/index.mjs 打包产物推断,烦请对照 src)
- 链路:AndroidDevice.connect() → ScrcpyDeviceAdapter.initialize() → ensureManager() + manager 的 ensureConnected()(内部
AdbScrcpyClient.start())。
- 当 AdbScrcpyClient.start() 中途失败(如设备媒体栈未就绪 → ExactReadable ended),this.scrcpyClient = await
AdbScrcpyClient.start(...) 的赋值未完成,this.scrcpyClient 仍为 null。
- connect 的 catch 里执行 await this.disconnect(),但 disconnect() 读到 this.scrcpyClient === null,无法 close
那个半初始化的 client → 它内部一个仍 pending 的读(AdbServerClient.#waitForUnchecked)成为悬垂 promise。
- 连接错误经 createConnectionError 正常抛出并暴露;但那个悬垂读单独 reject、无人 catch → unhandledRejection → 进程 exit 1。
- 可观测特征:同一次连接失败产生两个 ExactReadable ended——一个被捕获(getScrcpyStatus().lastError),一个裸奔逃逸。
Reproduce link
const device = new AndroidDevice(deviceId, { scrcpyConfig: { enabled: true } }); await device.connect(); // scrcpy 首建失败被吞成 warning const s = device.getScrcpyStatus(); // { enabled:true, connected:false, lastError:'...ExactReadable ended' } // 此处已正确检测失败、准备重试;但数百 ms 后 // AdbScrcpyClient.start() 遗留的悬垂读 reject → unhandledRejection → 进程 exit 1
Reproduce Steps
云手机重装系统后,媒体栈(SurfaceFlinger 显示捕获 + MediaCodec H.264 编码器)就绪晚于
sys.boot_completed,该窗口内首次 scrcpy
连接必失败,稳定触发。一次三台设备同时连接的运行中,三台各留一个悬垂 rejection、三个 worker 同时崩溃。
Version
Details
当 AndroidDevice.connect()(或 retryScrcpy())触发的 scrcpy 连接在初始化中途失败时,连接错误本身能被正确捕获并通过
getScrcpyStatus().lastError 暴露;但同一次失败还会额外泄漏一个未处理的 promise rejection,冒泡到进程级触发 Node 默认的
--unhandled-rejections=throw,直接以 exit code 1 杀掉进程。
结果:调用方即使正确地用 getScrcpyStatus() 检测到失败并准备重试/降级,也会在重试跑起来之前被这个逃逸的 rejection
杀死——无法在库外捕获。
逃逸 rejection 的堆栈
Error: ExactReadable ended
at BufferedReadableStream.#readSource (@yume-chan/stream-extra/src/buffered.ts:57:19)
at AdbServerStream.readOkay (@yume-chan/adb/src/server/stream.ts:59:26)
at AdbServerClient.#waitForUnchecked (@yume-chan/adb/src/server/client.ts:380:13)
根因分析(依据 dist/es/index.mjs 打包产物推断,烦请对照 src)
AdbScrcpyClient.start())。
AdbScrcpyClient.start(...) 的赋值未完成,this.scrcpyClient 仍为 null。
那个半初始化的 client → 它内部一个仍 pending 的读(AdbServerClient.#waitForUnchecked)成为悬垂 promise。
Reproduce link
const device = new AndroidDevice(deviceId, { scrcpyConfig: { enabled: true } }); await device.connect(); // scrcpy 首建失败被吞成 warning const s = device.getScrcpyStatus(); // { enabled:true, connected:false, lastError:'...ExactReadable ended' } // 此处已正确检测失败、准备重试;但数百 ms 后 // AdbScrcpyClient.start() 遗留的悬垂读 reject → unhandledRejection → 进程 exit 1
Reproduce Steps
云手机重装系统后,媒体栈(SurfaceFlinger 显示捕获 + MediaCodec H.264 编码器)就绪晚于
sys.boot_completed,该窗口内首次 scrcpy
连接必失败,稳定触发。一次三台设备同时连接的运行中,三台各留一个悬垂 rejection、三个 worker 同时崩溃。