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

Posted

技术标签:

【中文标题】使用 os.execl() 重新加载时没有名为“x”的模块【英文标题】:No module named 'x' when reloading with os.execl() 【发布时间】:2013-12-03 04:10:48 【问题描述】:

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

python = sys.executable
os.execl(python, python, * sys.argv)

大多数情况下这工作正常,但偶尔重启会失败并出现 no module named 错误。例子:

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

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

编辑:我按照 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

编辑 2:我尝试了sys.stdout.flush(),但我仍然遇到同样的错误。我注意到在发生错误之前,我每次只能成功重启 1-3 次。

【问题讨论】:

您能提供更多信息吗?“偶尔”是什么意思?您是否在循环中尝试过您的示例?它多久失败一次?您的脚本中是否存在可能导致此行为的内容? 脚本不断运行,2天左右后失败。脚本大约每 8-15 小时重新启动一次 什么操作系统和版本? 2.7.x 的小版本是什么? Python 是 2.7.2+,操作系统是 Linux Mint 12 Lisa 3.0.0-14-generic 【参考方案1】:

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

http://bugs.python.org/issue16981

由于这些模块不太可能消失,因此肯定是另一个错误实际上是错误的。错误报告将“打开的文件过多”列为容易导致此问题的原因,但我不确定是否还有其他错误也会触发此问题。

我会确保您在点击重新启动代码之前关闭所有文件句柄。您实际上也可以强制垃圾收集器手动运行:

import gc
gc.collect()

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

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

【讨论】:

我在重启期间看不到任何打开的文件句柄。我已将 gc 代码添加到我的重启功能中,看看是否有帮助。 不,这不起作用。星期五又犯了一个错误。我编辑了原帖 正如Python doc 所说,当前进程立即被替换。打开的文件对象和描述符不会被刷新。那么,在调用 exec* 函数之前使用 sys.stdout.flush() 或 os.fsync() 刷新它们怎么样? 试过 sys.stdout.flush。这对我没有帮助。【参考方案2】:

不是一个真正的答案,只是针对您的实际问题的一种解决方法:您是否考虑过启动一个子进程,如果它立即终止,然后尝试启动另一个?这有一些含义,比如不断变化的 PID,但也许你可以忍受。

代替

python = sys.executable
os.execl(python, python, * sys.argv)

你可以使用

import time, os

MONITOR_DURATION = 3.0
# ^^^ time in seconds we monitor our child for terminating too soon

python = sys.executable
while True:  # until we have a child which survived the monitor duration
  pid = os.fork()  # splice this process into two
  if pid == 0:  # are we the child process?
    os.execl(python, python, *sys.argv)  # start this program anew
  else:  # we are the father process
    startTime = time.time()
    while startTime + MONITOR_DURATION > time.time():
      exitedPid, status = os.waitpid(pid, os.WNOHANG)
      # ^^^ check our child for being terminted yet
      #     (without really waiting for it, due to WNOHANG)
      if exitedPid == pid:  # did our child terminate too soon?
        break
      else:  # no, nothing terminated yet
        time.sleep(0.2)  # wait a little before testing child again
    else:  # we survived the monitor duration without reaching a "break"
      break  # so we have a good running child, leave the outer loop

【讨论】:

我从来没有研究过这个,你能指点我一些文档/示例代码来做到这一点吗?谢谢 我草拟了一段代码来实现这一点。与您的版本所做的主要区别在于,我的版本在重新启动后终止。然后另一个进程(子进程)接管。如果其他人正在等待您的进程并且应该永远不会停止等待,这可能会导致问题。 谢谢,代码看起来不错。我已将其集成到我的重新启动功能中,并将看看它是如何进行的。干杯。 请记住,如果您的问题源于继承的资源问题(例如打开的文件过多),则此方法根本无济于事。子进程继承所有打开的文件句柄,然后遇到与execed(替换)进程相同的问题。 这修复了我的非重启错误,但导致我的虚拟机崩溃,我认为这与打开的文件过多有关。【参考方案3】:

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

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)

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

Number of files open at start: 4
Number of files open after going crazy with open() 106
reloading

代码的工作原理

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

/proc/<process id of the process we want>/fd

因此,如果您的进程的进程 ID 为 100,而您这样做:

$ find /proc/100/fd

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

/proc/100/fd/0
/proc/100/fd/1
/proc/100/fd/2
[...]

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

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

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

【讨论】:

我想就是这样,谢谢。我使用 fixit false 运行您的代码并得到 ImportError: No module named os.你能解释一下这实际上是如何工作的,或者指点我读一些书来理解这一点吗?再次感谢您

以上是关于使用 os.execl() 重新加载时没有名为“x”的模块的主要内容,如果未能解决你的问题,请参考以下文章

加载自定义模板标签时出现问题(错误:没有名为 x 的模块)

单击另一个表单中的按钮提交时如何在c#中重新加载表单?

Django:重新启动后出现“没有名为 context_processors 的模块”错误

python之os.exec*族用法简结

Userform在通过命令VBA.Userform.Add(“UF”)加载时重新初始化

UITableView 无法从 CustomCell 重新加载数据