我在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 错误报告,我认为我需要对服务器进程内部的所有Popen
调用设置close_fds=True
,以减轻文件描述符的泄漏。但是,我不熟悉围绕exec
-ing子进程和文件描述符继承的机制,因此我不清楚上述错误报告中的许多Popen
文档和注释。
听起来它实际上会在执行子进程之前关闭我进程中所有打开的文件描述符(包括活动请求套接字、日志文件句柄等)。这听起来比泄漏套接字要好,但仍然会导致错误。
但是,在实践中,当我在 Web 请求期间使用close_fds=True
时,它似乎工作正常,到目前为止,我无法构建它实际上关闭任何其他请求套接字、数据库请求等的场景。
文档指出:
如果 close_fds 为 true,则在执行子进程之前,将关闭除 0、1 和 2 之外的所有文件描述符。
所以我的问题是:在多线程 Python Web 服务器中将close_fds=True
传递给Popen
是否"安全"和"正确"?或者,如果其他请求同时执行文件/套接字 IO,我是否应该期望这会产生副作用?
我尝试了以下测试,使用 Python 3.2/3.3 的subprocess
的subprocess32
向后移植:
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
默认close_fds=True
POSIX系统AFAICT。这对我来说意味着它并不像听起来那么危险。
我怀疑close_fds
解决了文件描述符泄漏到子进程的问题。 想象一下打开一个文件,然后使用subprocess
运行某个任务。 如果没有close_fds
,文件描述符将复制到子进程,因此即使父进程关闭文件,文件也会由于子进程而保持打开状态。 现在,假设我们想使用shutil.rmtree
删除另一个线程中带有该文件的目录。 在常规文件系统上,这应该不是问题。 该目录刚刚按预期删除。 但是,当文件驻留在 NFS 上时,会发生以下情况:首先,Python 将尝试删除该文件。 由于该文件仍在使用中,因此将其重命名为.nfsXXX
,其中XXX
是一个长十六进制数。 接下来,Python 将尝试删除该目录,但这已经变得不可能,因为.nfsXXX
文件仍然驻留在其中。