Подпроцесс Python.Popen с помощью OSError: [Errno 12] Невозможно выделить память после периода времени

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


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

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

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

File "/home/admin/sd-agent/checks.py", line 436, in getProcesses
File "/usr/lib/python2.4/subprocess.py", line 533, in __init__
File "/usr/lib/python2.4/subprocess.py", line 835, in _get_handles
OSError: [Errno 12] Cannot allocate memory

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

$ free -m
                  total       used       free     shared     buffers    cached
Mem:                894        345        549          0          0          0
-/+ buffers/cache:  345        549
Swap:                 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.

Ответы

Ответ 1

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

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

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

Ответ 2

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

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

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

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

Обновление

С помощью python script вы можете проверить rlimits с помощью resource:

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 script без обхода внешней программы.

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

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

Обновление.

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

Ответ 6

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

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

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

Ответ 7

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

  • lsof
  • ps -aux | grep -i pname
  • верхний

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

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

Jacob

Ответ 8

Вам нужно

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

для освобождения ресурсов.

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

Ответ 9

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

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