Подпроцесс Python. Открытое сообщение об ошибке с OSError: [Errno 12] Не удается выделить память по истечении определенного периода времени

Примечание. Этот вопрос был повторен с кратким изложением всех попыток отладки здесь.


У меня есть сценарий Python, который выполняется в качестве фонового процесса, выполняемого каждые 60 секунд. Частично это вызов subprocess. Popen для получения вывода ps.

ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]

После нескольких дней работы вызов вызывает ошибку:

Файл "/home/admin/sd-agent/checks.py", строка 436, в getProcesses
Файл "/usr/lib/python2.4/subprocess.py", строка 533, в __init__
Файл "/usr/lib/python2.4/subprocess.py", строка 835, в _get_handles
OSError: [Errno 12] Невозможно выделить память

Однако вывод free на сервер:

$ free -m
                  общее количество использованных свободных общих буферов в кеше
Памяти:                894        345        549          0          0          0
-/+ буферы / кэш:  345        549
Обмен:                 0          0          0

Я искал проблему и нашел эту статью, в которой говорится:

Решение состоит в том, чтобы добавить больше пространства подкачки на ваш сервер. Когда ядро ​​пытается запустить моделлер или процесс обнаружения, оно сначала обеспечивает достаточно места в хранилище подкачки для нового процесса, если это необходимо.

Я отмечаю, что нет свободного свопа из свободного вывода выше. Может ли это быть проблемой и / или какие могут быть другие решения?

Обновление 13 августа 09 Код выше вызывается каждые 60 секунд, как часть серии функций мониторинга. Процесс демонизируется, и проверка запланирована с использованием sched. Конкретный код для вышеуказанной функции:

def getProcesses(self):
    self.checksLogger.debug('getProcesses: start')

    # Memory logging (case 27152)
    if self.agentConfig['debugMode'] and sys.platform == 'linux2':
        mem = subprocess.Popen(['free', '-m'], stdout=subprocess.PIPE).communicate()[0]
        self.checksLogger.debug('getProcesses: memory before Popen - ' + str(mem))

    # Get output from ps
    try:
        self.checksLogger.debug('getProcesses: attempting Popen')

        ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]

    except Exception, e:
        import traceback
        self.checksLogger.error('getProcesses: exception = ' + traceback.format_exc())
        return False

    self.checksLogger.debug('getProcesses: Popen success, parsing')

    # Memory logging (case 27152)
    if self.agentConfig['debugMode'] and sys.platform == 'linux2':
        mem = subprocess.Popen(['free', '-m'], stdout=subprocess.PIPE).communicate()[0]
        self.checksLogger.debug('getProcesses: memory after Popen - ' + str(mem))

    # Split out each process
    processLines = ps.split('\n')

    del processLines[0] # Removes the headers
    processLines.pop() # Removes a trailing empty line

    processes = []

    self.checksLogger.debug('getProcesses: Popen success, parsing, looping')

    for line in processLines:
        line = line.split(None, 10)
        processes.append(line)

    self.checksLogger.debug('getProcesses: completed, returning')

    return processes

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

Весь класс проверок можно найти по адресу http://github.com/dmytton/sd-agent/blob/82f5ff9203e54d2adeee8cfed704d09e3f00e8eb/checks.py с функцией getProcesses, определенной из строки 442. Это вызывается doChecks(), начиная со строки 520.

9 ответов

Решение

Когда вы используете popen, вам нужно передать close_fds=True, если вы хотите, чтобы он закрывал дополнительные файловые дескрипторы.

Создание нового канала, которое происходит в функции _get_handles из обратной трассировки, создает 2 файловых дескриптора, но ваш текущий код никогда не закрывает их, и вы в конечном итоге достигаете максимального предела fd в вашей системе.

Не уверен, почему полученная ошибка указывает на нехватку памяти: в качестве возвращаемого значения должна быть ошибка дескриптора файла. pipe() имеет код ошибки для этой проблемы.

Возможно, у вас есть утечка памяти, ограниченная некоторым ограничением ресурсов (RLIMIT_DATA, RLIMIT_AS?) унаследованный вашим скриптом python. Проверьте свои *ulimit(1)*s перед запуском скрипта и профилируйте использование памяти скриптом, как предлагали другие.

Что вы делаете с переменной ps после фрагмента кода вы показываете нам? Вы сохраняете ссылку на это, чтобы никогда не быть освобожденным? Цитируяsubprocess модульные документы:

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

... иps aux может быть многословным в загруженной системе...

Обновить

Вы можете проверить rlimits с помощью своего скрипта python, используя модуль ресурсов:

import resource
print resource.getrlimit(resource.RLIMIT_DATA) # => (soft_lim, hard_lim)
print resource.getrlimit(resource.RLIMIT_AS)

Если они возвращают "безлимитный" -(-1, -1)- тогда моя гипотеза неверна, и вы можете двигаться дальше!

Смотрите такжеresource.getrusageособенно ru_??rss поля, которые могут помочь вам использовать инструмент для потребления памяти с помощью скрипта Python, без выделения для внешней программы.

Ответ на этот вопрос - обман. Исторически системы Unix хотели, чтобы доступное пространство подкачки было таким, но они больше не работают таким образом (и Linux никогда не работал таким образом). Вы даже не близки к нехватке памяти, так что это вряд ли является реальной проблемой - у вас заканчивается какой-то другой ограниченный ресурс.

Учитывая, где происходит ошибка (_get_handles вызывает os.pipe() для создания каналов дочернему элементу), единственная реальная проблема, с которой вы можете столкнуться, - это нехватка свободных файловых дескрипторов. Вместо этого я бы искал незакрытые файлы (lsof -p для PID процесса, выполняющего popen). Если вашей программе действительно нужно держать много файлов открытыми одновременно, увеличьте пользовательский лимит и / или системный лимит для дескрипторов открытых файлов.

Если вы запускаете фоновый процесс, скорее всего, вы перенаправили ваши процессы stdin / stdout / stderr.

В этом случае добавьте параметр "close_fds=True" к вашему вызову Popen, который не позволит дочернему процессу наследовать ваш перенаправленный вывод. Это может быть пределом, в который вы врезаетесь.

Возможно, вы захотите дождаться завершения всех этих процессов PS, прежде чем добавлять пространство подкачки.

Не совсем понятно, что означает "запуск в качестве фонового процесса, выполняемого каждые 60 секунд".

Но ваш вызов subprocess.Popen каждый раз создает новый процесс.

Обновление

Я предполагаю, что вы как-то оставляете все эти процессы запущенными или зависшими в состоянии зомби. Тем не менее communicate Метод должен очистить порожденные подпроцессы.

Вы наблюдали за процессом с течением времени?

  • Lsof
  • ps -aux | grep -i pname
  • Топ

Все должны дать интересную информацию. Я думаю, что процесс связывает ресурсы, которые должны быть освобождены. Есть ли вероятность, что он связывает дескрипторы ресурсов (блоки памяти, потоки, файловые дескрипторы, потоки или дескрипторы процессов)? stdin, stdout, stderr из порожденных "пс". Память обрабатывает,... из множества небольших приращений. Мне было бы очень интересно посмотреть, что показывают вышеупомянутые команды для вашего процесса, когда он только что закончил запуск и запуск в первый раз и после 24 часов "сидения" там, регулярно запуская подпроцесс.

Поскольку он умирает через несколько дней, вы можете запустить его всего за несколько циклов, а затем перезапускать его один раз в день в качестве обходного пути. Это поможет вам в то же время.

Иаков

Виртуальная память имеет значение!!!

Я столкнулся с той же проблемой, прежде чем добавить своп в мою ОС. Формула для виртуальной памяти обычно выглядит так: SwapSize + 50% * PhysicalMemorySize. Я наконец-то решил эту проблему, либо добавив больше физической памяти, либо добавив диск подкачки. close_fds не будет работать в моем случае.

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

Еще одна вещь - если вы укажете shell=True в вызове Попен, вы видите другое поведение?

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

Вам нужно

ps = subprocess.Popen(["sleep", "1000"])
os.waitpid(ps.pid, 0)

освободить ресурсы.

Примечание: это не работает в Windows.

Другие вопросы по тегам