Python 2.7 Popen:`close_fds` 参数是什么意思?

3

我有一个使用Python(2.7)编写的Web服务器,它使用Popen将一些工作委托给子进程:

url_arg = "http://localhost/index.html?someparam=somevalue"
call = ('phantomjs', 'some/phantom/script.js', url_arg)

imageB64data = tempfile.TemporaryFile()
errordata = tempfile.TemporaryFile()

p = Popen(call, stdout=imageB64data, stderr=errordata, stdin=PIPE)
p.communicate(input="")

我发现有间歇性问题,大约在执行了一定数量的 Popen 后(大约为 64),进程会用尽文件描述符而无法正常工作——完全无响应,如果线程尝试打开任何文件或套接字,则所有线程似乎都会永久阻塞。

(可能相关:子进程 phantomjs 加载一个 URL 并回调到生成它的服务器。)

根据 此 Python bug 报告,我认为我需要在服务器进程内的所有 Popen 调用上设置 close_fds=True,以减轻文件描述符泄漏。然而,我对围绕 exec 子进程和文件描述符继承的机制并不熟悉,因此 Popen 文档和前面提到的 bug 报告中的注释对我来说不太清楚。

听起来实际上会关闭我的进程中所有打开的文件描述符(包括活动请求套接字、日志文件句柄等),然后再执行子进程。这听起来比泄漏套接字要好得多,但仍会导致错误。

然而,在实践中,当我在Web请求期间使用close_fds=True时,它似乎运行良好,到目前为止我尚未能够构建一个场景,在这种情况下它实际上会关闭任何其他请求套接字、数据库请求等。
文档说明如下:
如果close_fds为true,则在执行子进程之前,所有文件描述符(除了0、1和2)都将关闭。
因此,我的问题是:在多线程Python Web服务器中,在调用Popen时传递close_fds=True是否“安全”和“正确”?或者,如果其他请求同时进行文件/套接字IO,那么我应该预计会有副作用吗?

我不确定事情是否如此。相反,这似乎类似于 POSIX 的 CLOSE_ON_EXEC 标志。 - cs95
我不确定这些内容,但CLOSE_ON_EXEC似乎是相关的。虽然我也不明白它的作用... - Ben Mosher
基本上,这个想法是当你执行一个子进程时,它会覆盖父进程,清除所有上下文数据,除了打开的文件描述符。为了防止子进程对这些文件做任何不必要的操作,你需要将CLOSE_ON_EXEC指定为1来关闭所有这些打开的文件描述符,以便子进程无法使用它们。 - cs95
从经验上看,在子进程执行后,它们似乎会在父进程中保持打开状态。这是否一致? - Ben Mosher
2个回答

3

我尝试使用Python 3.2/3.3的subprocesssubprocess32后移版本进行以下测试:

import tempfile
import subprocess32 as subprocess

fp = open('test.txt', 'w')

fp.write("some stuff")

echoed = tempfile.TemporaryFile()
p = subprocess.Popen(("echo", "this", "stuff"), stdout=echoed, close_fds=True)
p.wait()
echoed.seek(0)

fp.write("whatevs")
fp.write(echoed.read())
fp.close()

我在 test.txt 中得到了预期的结果:some stuffwhatevsecho this stuff

因此,close_fds 中的 close 并不意味着执行子进程后父进程中打开的文件(套接字等)将无法使用。

另外值得注意的是:subprocess32 在 POSIX 系统上默认为 close_fds=True。据我所知,这意味着它并不像听起来那么危险。


1
我怀疑close_fds解决了文件描述符泄漏到子进程的问题。 想象一下打开一个文件,然后使用subprocess运行某个任务。 如果没有使用close_fds,文件描述符会被复制到子进程中,因此即使父进程关闭该文件,由于子进程的存在,文件仍然保持打开状态。现在,假设我们想要在另一个线程中使用shutil.rmtree删除包含该文件的目录。 在常规文件系统上,这不应该是一个问题。 目录会按预期被删除。 但是,当文件驻留在NFS上时,会发生以下情况:首先,Python将尝试删除该文件。 由于该文件仍在使用中,它将被重命名为.nfsXXX,其中XXX是一个很长的十六进制数。接下来,Python将尝试删除该目录,但由于.nfsXXX文件仍驻留在其中,这变得不可能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接