Skip to content

bug: TMWebDriver master stdout 被 revoke → print() 抛 BrokenPipeError,导致扩展 WS 连接秒断、web_scan 读取历史脏数据 #672

Description

@double-tu

问题现象

TMWebDriver 的 web_scan / web_execute_js 能正常返回数据,但返回的是历史脏数据(陈旧缓存),而非当前真实页面内容。例如:用户浏览器实际只有 2 个标签页,却读到 16 个早已关闭的历史标签页。重启浏览器、重载扩展均无效。

根本原因

TMWebDriver master 进程的 stdout(fd 1/2)被系统吊销(revoke)变成坏管道。master 在处理每条 WS 消息时会执行 print() 输出日志,而向坏管道写入会抛出 BrokenPipeError,导致当前 WebSocket 连接被关闭。

因此 Chrome 扩展每次连接 master 后约 1 秒就被断开(扩展发送 ext_ready 触发了 master 的 print),无法获取新数据,只能回退到缓存的脏数据。

最迷惑的点

此时 端口仍在 LISTEN、WS 握手仍返回 101 成功,从表面看 master 完全正常,极难定位。

证据链

  1. master 进程的 fd 0/1/2 全部显示 (revoked)
    lsof -p <master_PID> -a -d 0,1,2   # 显示 (revoked) 即为坏管道
  2. 本地复现:向坏管道 print() 会抛 BrokenPipeError
  3. lsof -nP -iTCP:18765 中 Chrome 的 ESTABLISHED 连接每隔约 1 秒被重置(重连)

诊断方法

# 1. 检查 master stdout 是否被 revoke(关键)
lsof -p <master_PID> -a -d 0,1,2
# 2. 检查 Chrome 扩展的 WS 连接是否秒断
lsof -nP -iTCP:18765

建议修复

1. 代码层面(推荐,根治):master 的日志输出不要直接写 stdout,而是写入真实文件;或对 stdout 写入做容错:

import functools, sys
def safe_print(*a, **k):
    try:
        print(*a, **k)
    except BrokenPipeError:
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())  # 重定向到 /dev/null,避免反复崩溃

并建议在 read-loop 的 except 分支中区分 BrokenPipeError,避免因日志输出失败而关闭正常的数据连接。

2. 启动层面:用 stdout 重定向到真实文件的方式启动,避免被系统 revoke:

nohup python -c "from TMWebDriver import TMWebDriver; import time; TMWebDriver(); [time.sleep(3600) for _ in iter(int,1)]" > /tmp/tmwd.log 2>&1 &

⚠ 注意:当前 TMWebDriver.__main__ 仅实例化而无保活循环,daemon WS 线程会随主进程退出而死亡,主进程必须加 while True: time.sleep(3600) 保活。

环境

  • macOS(stdout revoke 现象在 macOS 上观察到)
  • TMWebDriver 通过 Chrome 扩展接管用户浏览器(端口 18765 WS / 18766 HTTP)

排查这个隐藏 bug 花了不少时间,希望根因分析对项目有帮助 🙏

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions