Подпроцесс Python заканчивается файловыми дескрипторами - PullRequest
6 голосов
/ 12 июля 2011

У меня есть долго работающий проект на Python, который использует модуль подпроцесса для запуска различных других программ.Он ожидает завершения каждой программы, затем завершает функцию-обертку и возвращается в свой цикл ожидания.

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

Я не могу найти нигде в документации подпроцесса , что происходит с файловыми дескрипторами при закрытии дочернего процесса.Сначала я думал, что они закроются автоматически, поскольку команда subprocess.call () ждет, пока дочерний процесс не завершится.

Но если бы это было так, у меня не было бы проблемы.Я также подумал, что если что-нибудь останется, Python соберет мусор, когда функция завершится, и файловые дескрипторы выйдут из области видимости.Но, похоже, это тоже не так.

Как получить доступ к этим файловым дескрипторам?функция subprocess.call () возвращает только код завершения, но не дескрипторы открытых файлов.Есть ли что-то еще, что я здесь упускаю?

Этот проект служит связующим звеном между различными корпоративными приложениями.Указанные приложения не могут быть переданы по конвейеру, и они являются графическими системами.Итак, единственное, что я могу сделать, - это запустить их с помощью встроенных макросов.Эти макросы выводят текстовые файлы, которые я использую для следующей программы в конвейере.

Да, это так плохо, как кажется.К счастью, все файлы имеют уникальные имена.Итак, здесь, в следующие несколько дней, я буду использовать инструмент sys internals, предложенный ниже, чтобы попытаться отследить файл.Я дам вам знать, как это получается.

Большинство файлов, которые я не открываю, я просто перемещаю их с помощью функции win32file.CopyFile ().

Ответы [ 4 ]

4 голосов
/ 20 мая 2014

У меня была такая же проблема.

Мы постоянно используем subprocess.Popen () для вызова внешних инструментов в среде Windows. В какой-то момент у нас возникла проблема, когда больше не было файловых дескрипторов. Мы углубились в проблему и обнаружили, что экземпляры subprocess.Popen ведут себя иначе в Windows, чем в Linux.

Если экземпляр Popen не уничтожен (например, путем сохранения какой-либо ссылки и, следовательно, не позволяя сборщику мусора уничтожить объект), каналы, созданные во время вызова, остаются открытыми в Windows, тогда как в Linux они были автоматически закрыто после вызова Popen.communicate (). Если это будет продолжено при дальнейших вызовах, дескрипторы файлов «зомби» из каналов будут накапливаться и в конечном итоге вызовут исключение Python IOError: [Errno 24] Too many open files.

Как получить дескрипторы открытых файлов в Python

Для того чтобы мы могли решать наши проблемы, нам нужен был способ получить действительные файловые дескрипторы в скрипте Python. Итак, мы создали следующий скрипт. Обратите внимание, что мы проверяем дескрипторы файлов только от 0 до 100, так как мы не открываем столько файлов одновременно.

fd_table_status.py :

import os
import stat

_fd_types = (
    ('REG', stat.S_ISREG),
    ('FIFO', stat.S_ISFIFO),
    ('DIR', stat.S_ISDIR),
    ('CHR', stat.S_ISCHR),
    ('BLK', stat.S_ISBLK),
    ('LNK', stat.S_ISLNK),
    ('SOCK', stat.S_ISSOCK)
)

def fd_table_status():
    result = []
    for fd in range(100):
        try:
            s = os.fstat(fd)
        except:
            continue
        for fd_type, func in _fd_types:
            if func(s.st_mode):
                break
        else:
            fd_type = str(s.st_mode)
        result.append((fd, fd_type))
    return result

def fd_table_status_logify(fd_table_result):
    return ('Open file handles: ' +
            ', '.join(['{0}: {1}'.format(*i) for i in fd_table_result]))

def fd_table_status_str():
    return fd_table_status_logify(fd_table_status())

if __name__=='__main__':
    print fd_table_status_str()

При простом запуске он покажет все открытые дескрипторы файлов и их соответствующий тип:

$> python fd_table_status.py
Open file handles: 0: CHR, 1: CHR, 2: CHR
$>

Вывод такой же, вызывая fd_table_status_str () через код Python. Подробнее о значении «CHR» и значении «коротких кодов» см. Документация Python по stat .

.

Проверка поведения дескриптора файла

Попробуйте запустить следующий скрипт в Linux и Windows:

test_fd_handling.py :

import fd_table_status
import subprocess
import platform

fds = fd_table_status.fd_table_status_str

if platform.system()=='Windows':
    python_exe = r'C:\Python27\python.exe'
else:
    python_exe = 'python'

print '1) Initial file descriptors:\n' + fds()
f = open('fd_table_status.py', 'r')
print '2) After file open, before Popen:\n' + fds()
p = subprocess.Popen(['python', 'fd_table_status.py'],
                     stdin=subprocess.PIPE,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE)
print '3) After Popen, before reading piped output:\n' + fds()
result = p.communicate()
print '4) After Popen.communicate():\n' + fds()
del p
print '5) After deleting reference to Popen instance:\n' + fds()
del f
print '6) After deleting reference to file instance:\n' + fds()
print '7) child process had the following file descriptors:'
print result[0][:-1]

Вывод Linux

1) Initial file descriptors:
Open file handles: 0: CHR, 1: CHR, 2: CHR
2) After file open, before Popen:
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG
3) After Popen, before reading piped output:
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG, 5: FIFO, 6: FIFO, 8: FIFO
4) After Popen.communicate():
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG
5) After deleting reference to Popen instance:
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG
6) After deleting reference to file instance:
Open file handles: 0: CHR, 1: CHR, 2: CHR
7) child process had the following file descriptors:
Open file handles: 0: FIFO, 1: FIFO, 2: FIFO, 3: REG

Вывод Windows

1) Initial file descriptors:
Open file handles: 0: CHR, 1: CHR, 2: CHR
2) After file open, before Popen:
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG
3) After Popen, before reading piped output:
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG, 4: FIFO, 5: FIFO, 6: FIFO
4) After Popen.communicate():
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG, 5: FIFO, 6: FIFO
5) After deleting reference to Popen instance:
Open file handles: 0: CHR, 1: CHR, 2: CHR, 3: REG
6) After deleting reference to file instance:
Open file handles: 0: CHR, 1: CHR, 2: CHR
7) child process had the following file descriptors:
Open file handles: 0: FIFO, 1: FIFO, 2: FIFO

Как видно на шаге 4, Windows не ведет себя так же, как Linux. Экземпляр Popen должен быть уничтожен, чтобы трубы были закрыты.

Кстати, различие в шаге 7 показывает другую проблему, касающуюся поведения интерпретатора Python в Windows, вы можете увидеть более подробную информацию об обеих проблемах здесь .

2 голосов
/ 13 июля 2011

Какую версию Python вы используете?Существует известная утечка файловых дескрипторов с subprocess.Popen (), которая также может влиять на subprocess.call ()

http://bugs.python.org/issue6274

Как вы можете видеть, это было исправлено только в python-2,6

1 голос
/ 23 июля 2012

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

С тех пор я нашел heapy .

0 голосов
/ 12 июля 2011

Файловые дескрипторы исчезают, когда происходит процесс, поэтому это должен быть родитель, удерживающий файловые дескрипторы (вы можете проверить это с помощью lsof). Что делает код в родительском?

...