[转]python执行系统命令,怎么就卡死了?

最近很忙,术后恢复也很久没有去更新博客了。

来自陌陌安全团队的文章,但是python 执行系统命令popen方法设置了stdout或者stderr,创建子进程把pipe size填满之前没有具体了解,终于知道原因了,也顺手记录一下。

====================================================

title: python执行系统命令,怎么就卡死了?

发生背景
本次问题的发现,主要来源于最近的一个需求,调用第三方工具扫描从gitlab中拉下来项目的依赖信息。其实这个需求要实现的功能比较简单,核心就是在拉下来的项目目录下执行第三方工具,然后读取该工具产生的结果文件,处理后写到回数据库中。

发生过程

脚本开发完成后,在测试机中进行单元测试和完成过程测试后,在生产环境中上线。但上线时发现扫描任务队列的数据并未如期消耗,于是开始查看日志,但未发现任何异常信息。

[En]

After the script has been developed, it is put online in the production environment after unit testing and complete process testing in the test machine. However, when it was launched, it was found that the data of the scanning task queue had not been consumed as expected, so we began to check the log, but did not find any abnormal information.

接着尝试记录更加详细日志进行排错,并在观察中有如下发现:

①并不是所有的任务都会卡死;

②同一个任务第一次执行卡死,第二次重新执行就正常了。

我在这里排除的第一个原因是当前代码中的逻辑问题。在再次仔细查看日志后,我终于发现了一个线索:每次执行系统命令时都会发生拥塞,所以我开始专注于命令调用逻辑的代码。

[En]

The first reason I ruled out here is the logic problem in the current code. After looking closely at the log again, I finally found a clue: every time the jam occurred on the execution of system commands, so I began to focus on the code of command calling logic.

问题分析

下面是问题代码:

[转]python执行系统命令,怎么就卡死了?

由于在之前的脚本中写过不少类似的功能,但大多使用的都是io模块中的os.popen()方法,在新版的python中推荐使用subprocess模块来替代一些旧的模块,于是这次也改成了新的方式,但问题也随之被引入了。

我们先手动调用下这个工具来分析下这个项目的依赖:

https://github.com/momosecurity/mosec-maven-plugin

这个工具的使用也比较简单,具体使用方式可以参考github上的说明,先看看它的输出:

[转]python执行系统命令,怎么就卡死了?

这里需要注意的是,在执行过程中会输出大量的下载信息。

然后我们回到上面的问题代码中,当popen方法设置了stdout或者stderr参数后,系统会创建一个管道用于和子进程进行通信,而上面输出的大量下载信息会把创建的管道填满,进而使得后续的内容无法继续写入管道中,这样整个程序就卡死了。此外这里也没及时读取管道的数据,而是一直等待子进程完成,于是就发生了我们最常见的死锁情况,所以最后的表现就是脚本卡死了。

但当我们第二次重启脚本时,由于缓存的存在,大量的下载信息没有被打印出来,所以这次程序输出要少得多,上面的管道不会被填满。

[En]

But when we restart the script for the second time, due to the existence of the cache, a large amount of download information has not been printed, so this time the program output is much less, and the above pipeline will not be filled.

通过查询我们机器的信息,可以看到当前Linux系统管道的默认大小是4kB(8 * 512B = 4kB),而在第二次调用的时候因为输出的数据明显小于4kB,这才有了第一次卡死,第二次却执行成功的现象。

[转]python执行系统命令,怎么就卡死了?

此外在Python的官方文档*中也在wait方法和stderr属性中做了明确的标注:

stdout=PIPE 或者 stderr=PIPE 并且子进程产生了足以阻塞 OS 管道缓冲区接收更多数据的输出到管道时,将会发生死锁。当使用管道时用 Popen.communicate() 来规避它。

警告:
使用 communicate() 而非 .stdin.write, .stdout.read 或者 .stderr.read 来避免由于任意其他 OS 管道缓冲区被子进程填满阻塞而导致的死锁。

问题解决

这里是我们修改后的代码:

[转]python执行系统命令,怎么就卡死了?

虽然官方推荐是使用communicate方法来解决上面的问题,但我们只是为了获得输出,并尽可能的避免其他可能发生的问题,就直接使用check_output方法来替代。此外我们还可以看到,在check_output方法中也确实使用了communicate:

[转]python执行系统命令,怎么就卡死了?

总结

为了实现一个功能,最常见的方式是在网上找到一些代码,然后不假思索地粘贴复制到我们的项目中,但这可能隐藏了很多难以发现的问题。此时,最好的办法是确定代码中的每个方法是否都有问题,特别是对于不熟悉的方法,好好看看官方文档可能会出乎意料。

[En]

In order to achieve a function, the most common way is to find some code on the Internet, and then paste and copy it into our project without thinking, but this may hide a lot of hard-to-find problems. At this time, the best way is to determine whether there is something wrong with every method in the code, especially for unfamiliar methods, it may be unexpected to take a good look at the official documentation.

参考
[*] Python官方文档 https://docs.python.org/zh-cn/3.7/library/subprocess.html

来源:

https://mp.weixin.qq.com/s/CJK-_yMo_VN1_yIUiO787w

Original: https://www.cnblogs.com/sevck/p/16494222.html
Author: sevck
Title: [转]python执行系统命令,怎么就卡死了?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/10646/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

免费咨询
免费咨询
扫码关注
扫码关注
联系站长

站长Johngo!

大数据和算法重度研究者!

持续产出大数据、算法、LeetCode干货,以及业界好资源!

2022012703491714

微信来撩,免费咨询:xiaozhu_tec

分享本页
返回顶部