Skip to content

macOS 下 adb screencap 进程随机永久挂起,20 秒超时机制失效 #1364

Description

@cliveli

问题类型 / Issue Type

一般 Bug / General Bug

问题描述及复现步骤 / Problem Description & Reproduction Steps

环境

OS: macOS 26.5.1 (ARM64)
模拟器: MuMuPlayer
截图方式: ScreencapRawWithGzip(exec-out screencap | gzip -1)
复现概率: 随机,并发 adb 连接时更易触发

问题描述
使用 ScreencapRawWithGzip 截图时,子进程 adb 会随机永久挂起,始终不退出。startup_and_read_pipe 中设置的 20 秒超时完全不生效,导致任务永久卡死。

调试过程

  1. 确认挂起的 adb 进程
    PID 41936: adb -s 127.0.0.1:16448 exec-out screencap | gzip -1

已运行超过 9 分钟,无任何输出

  1. sample 确认 adb 阻塞在 read syscall
    4265 copy_to_file(int, int) (in adb)
    4265 read (in libsystem_kernel.dylib)
  2. lsof 确认阻塞位置是 TCP socket,而非 stdout pipe
    fd 1 PIPE → stdout 给父进程 (未满,不是瓶颈)
    fd 3 TCP localhost:54238 → localhost:5037 (ESTABLISHED) ← 阻塞在此
    结论: 设备端 screencap(或 MuMu 的 adbd)挂起后既不产生数据,也不关闭连接。TCP 连接维持 ESTABLISHED 状态,adb 的 copy_to_file → read(fd=3) 因此永久阻塞。这不是 pipe buffer 死锁——stdout pipe 并未写满。

超时机制为何失效

cpp// IOStream.cpp
while (is_open() && duration_since(start_time) < timeout) {
    auto data = read_once(...);   // ← 永久阻塞在 pin_.read() 内部
    ...
}

// 超时检查只在 read_once() 返回后才执行
// 如果 read_once() 永远不返回,超时永远不会触发
read_once 内部调用 pin_.read(buf, 128KB),这是一个阻塞调用。当 adb 子进程自身阻塞在 read(fd=3) 时,它永远不向 pipe 写数据,父进程的 pin_.read 因此永远不返回,超时循环没有机会执行。

建议修复方案
在独立线程中执行读取,使超时可以通过强杀子进程来真正中断阻塞:

cppstd::optional<std::string> UnitBase::startup_and_read_pipe(
    const ProcessArgv& argv, std::chrono::milliseconds timeout)
{
    ChildPipeIOStream ios(argv.exec, argv.args);

    auto future = std::async(std::launch::async, [&ios, timeout]() {
        return ios.read(timeout);
    });

    if (future.wait_for(timeout + std::chrono::milliseconds(500))
            == std::future_status::timeout) {
        LogError << "screencap timeout, killing child" << VAR(argv.exec);
        ios.release();  // SIGKILL adb → read(fd=3) 立即中断 → pipe EOF → 读取线程解除阻塞
    }

    std::string output = future.get();
    bool ret = ios.release();
    ...
}

ios.release() 向 adb 进程发送 SIGKILL,adb 退出后 stdout pipe 产生 EOF,父进程中阻塞的 pin_.read() 立即返回,读取线程正常结束。

MaaFramework 版本 / Version

5.10.5

日志文件 / Log Files

[2026-06-12 08:51:10.618][DBG][Px38921][Tx49310][ChildPipeIOStream.cpp][L84][MaaNS::ChildPipeIOStream::ChildPipeIOStream(const std::filesystem::path &, const std::vector<os_string> &, bool)] [exec_=/Applications/MuMuPlayer.app/Contents/MacOS/MuMuEmulator.app/Contents/MacOS/tools/adb] [args_=["-s","127.0.0.1:16384","exec-out","screencap | gzip -1"]] [child_.id()=40729]

Pipeline JSON

崩溃 Dump 文件 / Crash Dump (仅崩溃问题 / Crash only)

No response

Draw 调试图片 / Draw Debug Images (仅识别问题 / Recognition only)

No response

模板图片 / Template Images (仅识别问题 / Recognition only)

No response

其他信息 / Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions