我们的 CI 在多台机器上的 Docker 容器中运行 Teamcity Agents。每次运行时,我都会启动 100 个测试运行器进程 (Ruby/Cucumber),通过 100 个 Appium 实例运行测试。
几周前,我们将 100 个测试运行器进程和 100 个 Appium 实例移至子 docker 容器中,但有一台特定的机器开始出错:时不时地,所有(或几乎所有)100 个 Appium 进程在启动时读取“require”文件时挂起 - 有时挂在 fs.closeSync() 上,有时挂在从 fs.readFileSync fs.js:615:40 调用的 toString buffer.js:634:45 上
所有 Appium 进程肯定都被楔住了(我使用不同的调试器实例进行了两次堆栈跟踪,间隔一秒),并且堆栈跟踪分为几组,所有堆栈跟踪都在“需要”文件时卡住,在任何端口打开之前,在服务器打开其日志之前,尽管每次需要的文件并不总是相同的。
一旦发生这种情况,它似乎会在所有后续运行中继续发生,直到我手动删除 node_modules 目录,但过了一会儿它又开始锁定。node_modules 目录位于 Teamcity Agent 和运行 Appium 进程的测试容器之间共享的卷中,因此我们不必每次都重新执行 git checkout,也不必执行 npm install。
这似乎暗示了文件锁定,但是当我在容器中以 root 身份运行 lslock 时,无论是在我启动 Appium 之前还是在我获得它的堆栈跟踪之后,我都看不到任何内容(除了所有 docker 容器都显示的锁,这似乎与顶层/主机级别的 /dev 上的锁有关):
$ lslocks
COMMAND PID TYPE SIZE MODE M START END PATH
file_lock 54314 POSIX 0B WRITE 0 0 0 /
(unknown) -1 OFDLCK 0B READ 0 0 0
file_lock 是我的测试,以确保 lslock 正常工作;我还检查了该卷是否未启用强制锁定。
我无法从命令行触发挂起,但我没有捕获所有实例的环境变量,所以我无法尝试启动所有 100 个实例。下次发生挂起时我会这样做。
话虽如此,我没有合理的理由去研究:我可以升级节点和 appium,但如果不理解为什么这些进程显然相互死锁,我就不能保证这是一个永久的解决方案。
进程进入磁盘等待状态,并且脚本echo 3 > /proc/sys/vmdrop_caches
没有清除它(尽管有迹象表明它可能在几分之一秒内疏通了一切)。
这似乎与使用共享 Docker 卷有关,甚至在仅启动 4 个 Appium 时也会出现挂起,因此这不仅仅是大量进程用读取请求冲击覆盖 fs。
在过去的几天里,将 node_modules 更改为指向非共享区域的软链接似乎已经解决了问题。
主机:SLES 12 SP 4 - 4.14.75-0.2-default #1 SMP PREEMPT 2017 年 11 月 13 日星期一 14:53:06 UTC(c152297)x86_64 x86_64 x86_64 GNU/Linux
/proc/version:Linux 版本 4.14.75-0.2-default (geeko@buildhost) (gcc 版本 4.8.5 (SUSE Linux)) #1 SMP PREEMPT 2017 年 11 月 13 日星期一 14:53:06 UTC (c152297)
Docker 版本 18.09.6,内部版本 481bc7715621
容器:Debian GNU/Linux 9 - 4.14.75-0.2-default #1 SMP PREEMPT 2017 年 11 月 13 日星期一 14:53:06 UTC (c152297) x86_64 GNU/Linux
节点:v9.11.2
答案1
部分答案:导致此问题的原因似乎是尝试从两个容器之间共享的 Docker 卷中的 node_modules 目录运行 appium:将 node_modules 移动到非卷区域可停止挂起。
到底是什么导致了实际问题 - 不知道,但看起来像是文件系统逻辑中的一个错误。我不知道 NodeJS/Appium 是如何触发它的,而 100 个进程至少读取了同样多的 Ruby 代码,但后来却没有触发,我不知道。