问题类型 / 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 秒超时完全不生效,导致任务永久卡死。
调试过程
- 确认挂起的 adb 进程
PID 41936: adb -s 127.0.0.1:16448 exec-out screencap | gzip -1
已运行超过 9 分钟,无任何输出
- sample 确认 adb 阻塞在 read syscall
4265 copy_to_file(int, int) (in adb)
4265 read (in libsystem_kernel.dylib)
- 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
问题类型 / 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 秒超时完全不生效,导致任务永久卡死。
调试过程
PID 41936: adb -s 127.0.0.1:16448 exec-out screencap | gzip -1
已运行超过 9 分钟,无任何输出
4265 copy_to_file(int, int) (in adb)
4265 read (in libsystem_kernel.dylib)
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 并未写满。
超时机制为何失效
// 超时检查只在 read_once() 返回后才执行
// 如果 read_once() 永远不返回,超时永远不会触发
read_once 内部调用 pin_.read(buf, 128KB),这是一个阻塞调用。当 adb 子进程自身阻塞在 read(fd=3) 时,它永远不向 pipe 写数据,父进程的 pin_.read 因此永远不返回,超时循环没有机会执行。
建议修复方案
在独立线程中执行读取,使超时可以通过强杀子进程来真正中断阻塞:
ios.release() 向 adb 进程发送 SIGKILL,adb 退出后 stdout pipe 产生 EOF,父进程中阻塞的 pin_.read() 立即返回,读取线程正常结束。
MaaFramework 版本 / Version
5.10.5
日志文件 / Log Files
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