使用os.execl()重新加载时没有名为'x'的模块

Mar*_*tyn 14 python operating-system python-2.7

我有一个python脚本使用以下重新启动:

python = sys.executable
os.execl(python, python, * sys.argv)
Run Code Online (Sandbox Code Playgroud)

大部分时间这种方法工作正常,但偶尔重启失败,没有名为error的模块.例子:

Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
File "/usr/lib/python2.7/os.py", line 49, in <module>
import posixpath as path
File "/usr/lib/python2.7/posixpath.py", line 17, in <module>
import warnings
File "/usr/lib/python2.7/warnings.py", line 6, in <module>
import linecache
ImportError: No module named linecache
Run Code Online (Sandbox Code Playgroud)
Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
 File "/usr/lib/python2.7/os.py", line 49, in <module>
import posixpath as path
 File "/usr/lib/python2.7/posixpath.py", line 15, in <module>
import stat   
ImportError: No module named stat
Run Code Online (Sandbox Code Playgroud)

编辑:我按照andr0x的建议尝试了gc.collect(),但这没有用.我得到了同样的错误:

Traceback (most recent call last):
File "/usr/lib/python2.7/site.py", line 68, in <module>
import os
File "/usr/lib/python2.7/os.py", line 49, in <module>
import posixpath as path
ImportError: No module named posixpath
Run Code Online (Sandbox Code Playgroud)

编辑2:我试过sys.stdout.flush(),我仍然得到同样的错误.我注意到在发生错误之前,我只会在1-3次成功重启之间获得.

小智 8

我相信你遇到了以下错误:

http://bugs.python.org/issue16981

由于这些模块不太可能消失,因此必须存在另一个实际上存在错误的错误.错误报告列出了"太多打开的文件",因为它容易导致此问题,但我不确定是否还有其他错误也会触发此问题.

我会确保你在关闭重启代码之前关闭任何文件句柄.您还可以实际强制垃圾收集器手动运行:

import gc
gc.collect()
Run Code Online (Sandbox Code Playgroud)

http://docs.python.org/2/library/gc.html

您也可以在尝试重启代码之前尝试使用它


Lou*_*uis 3

如果问题是打开了太多文件,那么您必须FD_CLOEXEC在文件描述符上设置标志,以便在exec发生时关闭它们。这是一段模拟重新加载时达到文件描述符限制的代码,其中包含未达到限制的修复程序。如果您想模拟崩溃,请设置fixitFalse。当fixit是 时True,代码将遍历文件描述符列表并将它们设置为FD_CLOEXEC。这适用于 Linux。在没有该功能的系统上工作的人们/proc/<pid>/fd/必须找到一种适合系统的方法来列出打开的文件描述符。这个问题可能会有所帮助。

import os
import sys
import fcntl

pid = str(os.getpid())

def fds():
    return os.listdir(os.path.join("/proc", pid, "fd"))

files = []

print "Number of files open at start:", len(fds())

for i in xrange(0, 102):
    files.append(open("/dev/null", 'r'))

print "Number of files open after going crazy with open()", len(fds())

fixit = True
if fixit:
    # Cycle through all file descriptors opened by our process.
    for f in fds():
        fd = int(f)
        # Transmit the stds to future generations, mark the rest as close-on-exec.
        if fd > 2:  .
            try:
                fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
            except IOError:
                # Some files can be closed between the time we list
                # the file descriptors and now. Most notably,
                # os.listdir opens the dir and it will probably be
                # closed by the time we hit that fd.
                pass

print "reloading"
python = sys.executable
os.execl(python, python, *sys.argv)
Run Code Online (Sandbox Code Playgroud)

使用这段代码,我在标准输出上得到的是重复这三行,直到我终止进程:

Number of files open at start: 4
Number of files open after going crazy with open() 106
reloading
Run Code Online (Sandbox Code Playgroud)

代码如何工作

上面的代码通过函数获取打开的文件描述符列表fds()。在 Linux 系统上,特定进程打开的文件描述符列于:

/proc/<process id of the process we want>/fd
Run Code Online (Sandbox Code Playgroud)

因此,如果您的进程的进程 ID 是 100 并且您执行以下操作:

$ find /proc/100/fd
Run Code Online (Sandbox Code Playgroud)

你会得到一个类似的列表:

/proc/100/fd/0
/proc/100/fd/1
/proc/100/fd/2
[...]
Run Code Online (Sandbox Code Playgroud)

fds()函数只是获取所有这些文件的基本名称["0", "1", "2", ...]。(更通用的解决方案可能会立即将它们转换为整数。我选择不这样做。)

第二个关键部分是设置FD_CLOEXECstd{in,out,err}. 文件描述符上的设置FD_CLOEXEC告诉操作系统下次执行时,操作系统应在将控制权交给下一个可执行文件之前exec关闭文件描述符。该标志在fcntl的手册页上定义。

在使用打开文件的线程的应用程序中,如果线程在获取文件描述符列表的时间和调用时间之间执行并且该线程打开新的文件描述符,则上面的代码可能会错过FD_CLOEXEC某些文件描述符的设置文件。我相信确保这种情况不会发生的唯一方法是替换为调用股票的代码,然后立即在返回的文件描述符上进行设置。execos.openos.openFD_CLOEXEC