Нет модуля с именем "x" при перезагрузке с помощью os.execl()

У меня есть python script, который использует следующее для перезапуска:

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

В большинстве случаев это работает нормально, но иногда перезапуск завершается с ошибкой без модуля с ошибкой. Примеры:

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

Изменить: я попытался gc.collect(), как было предложено andr0x, и это не сработало. Я получил ту же ошибку:

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 успешных перезапуска до возникновения ошибки.

Ответы

Ответ 1

Если проблема в том, что слишком много файлов открывается, вам нужно установить флаг FD_CLOEXEC в дескрипторах файла, чтобы закрыть их, когда произойдет exec. Вот фрагмент кода, который имитирует удар по ограничению дескриптора файла при перезагрузке и содержит исправление, не допускающее ограничение. Если вы хотите симулировать сбои, установите fixit на False. Когда fixit True, код проходит через список дескрипторов файлов и устанавливает их как FD_CLOEXEC. Это работает в Linux. Люди, работающие над системами, не имеющими /proc/<pid>/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)

С помощью этого кода, что я получаю на stdout, эти три строки повторяются, пока я не убью процесс:

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

Итак, если ваш идентификатор процесса вашего процесса равен 100, и вы делаете:

$ find /proc/100/fd

Вы получите список, например:

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

Функция fds() просто получает базовое имя всех этих файлов ["0", "1", "2", ...]. (Более общее решение может сразу преобразовать их в целые числа. Я решил не делать этого.)

Вторая ключевая часть - установка FD_CLOEXEC во всех дескрипторах файлов, кроме std{in,out,err}. Установка FD_CLOEXEC в дескрипторе файла сообщает операционной системе, что в следующий раз exec, ОС должна закрыть дескриптор файла, прежде чем давать управление следующему исполняемому файлу. Этот флаг определен на странице руководства для fcntl.

В приложении, использующем потоки, открывающие файлы, возможно, что код, который у меня выше, пропустил настройку FD_CLOEXEC для некоторых файловых дескрипторов, если поток выполняется между тем, когда получен список файловых дескрипторов, и время exec, и этот поток открывает новые файлы. Я считаю, что единственный способ гарантировать, что этого не произойдет, - заменить os.open кодом, вызывающим запас os.open, а затем установить FD_CLOEXEC сразу после возврата дескриптора файла.

Ответ 2

Я считаю, что вы попадаете в следующую ошибку:

http://bugs.python.org/issue16981

Поскольку маловероятно, что эти модули исчезают, должна быть другая ошибка, которая действительно виновата. В отчете об ошибках содержится список "слишком много открытых файлов", подверженных этой проблеме, однако я не уверен, есть ли другие ошибки, которые также могут вызвать это.

Я хочу убедиться, что вы закрываете любые дескрипторы файлов, прежде чем нажать на код перезагрузки. Вы также можете принудительно запустить сборщик мусора вручную:

import gc
gc.collect()

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

Вы можете попробовать использовать это, прежде чем удалять код перезагрузки.

Ответ 3

Не настоящий ответ, просто обходной путь для вашей реальной проблемы: считаете ли вы, что начинаете дочерний процесс, и если это немедленно прекратится, попробуйте запустить другое? Это имеет некоторые последствия, как постоянно меняющийся 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