Python subprocess.Popen "OSError: [Errno 12] Невозможно выделить память"

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

Python script запускает набор функций класса каждые 60 секунд с помощью модуля sched:

# sc is a sched.scheduler instance
sc.enter(60, 1, self.doChecks, (sc, False))

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

Несколько методов класса, которые называются частью doChecks, используют модуль subprocess для вызова системных функций, чтобы получить системную статистику

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

Это нормально работает в течение периода времени до полного сбоя script со следующей ошибкой:

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 -m на сервере после разбиения script:

$ free -m
                  total       used       free     shared     buffers    cached
Mem:                894        345        549          0          0          0
-/+ buffers/cache:  345        549
Swap:                 0          0          0

Сервер работает с CentOS 5.3. Я не могу воспроизвести свои собственные коробки CentOS, ни с каким-либо другим пользователем, сообщающим о той же проблеме.

Я пробовал несколько вещей, чтобы отладить это, как было предложено в исходном вопросе:

  • Запись выходного файла free -m до и после вызова Popen. Нет существенных изменений в использовании памяти, т.е. Память постепенно не используется, пока запускается script.

  • Я добавил close_fds = True для вызова Popen, но это не имело значения - script по-прежнему разбился с той же ошибкой. Здесь здесь и здесь.

  • Я проверил rlimits, которые показали (-1, -1) как на RLIMIT_DATA, так и на RLIMIT_AS, как предложено здесь.

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

  • Процессы закрываются, потому что это поведение использования .communicate() при резервном копировании исходного кода Python и комментариев здесь.

Все проверки можно найти на GitHub здесь с функцией getProcesses, определенной из строки 442. Это вызвано doChecks(), начиная с строка 520.

script запускался с strace со следующим выходом перед сбоем:

recv(4, "Total Accesses: 516662\nTotal kBy"..., 234, 0) = 234
gettimeofday({1250893252, 887805}, NULL) = 0
write(3, "2009-08-21 17:20:52,887 - checks"..., 91) = 91
gettimeofday({1250893252, 888362}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 74) = 74
gettimeofday({1250893252, 888897}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 67) = 67
gettimeofday({1250893252, 889184}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 81) = 81
close(4)                                = 0
gettimeofday({1250893252, 889591}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 63) = 63
pipe([4, 5])                            = 0
pipe([6, 7])                            = 0
fcntl64(7, F_GETFD)                     = 0
fcntl64(7, F_SETFD, FD_CLOEXEC)         = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory)
write(2, "Traceback (most recent call last"..., 35) = 35
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, "  File \"/usr/bin/sd-agent/agent."..., 52) = 52
open("/home/admin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, "  File \"/home/admin/sd-agent/dae"..., 60) = 60
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, "  File \"/usr/bin/sd-agent/agent."..., 54) = 54
open("/usr/lib/python2.4/sched.py", O_RDONLY|O_LARGEFILE) = 8
write(2, "  File \"/usr/lib/python2.4/sched"..., 55) = 55
fstat64(8, {st_mode=S_IFREG|0644, st_size=4054, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "\"\"\"A generally useful event sche"..., 4096) = 4054
write(2, "    ", 4)                     = 4
write(2, "void = action(*argument)\n", 25) = 25
close(8)                                = 0
munmap(0xb7d28000, 4096)                = 0
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, "  File \"/usr/bin/sd-agent/checks"..., 60) = 60
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, "  File \"/usr/bin/sd-agent/checks"..., 64) = 64
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, "  File \"/usr/lib/python2.4/subpr"..., 65) = 65
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n        print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n        # c2pread    <-"..., 4096) = 4096
write(2, "    ", 4)                     = 4
write(2, "errread, errwrite)\n", 19)    = 19
close(8)                                = 0
munmap(0xb7d28000, 4096)                = 0
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, "  File \"/usr/lib/python2.4/subpr"..., 71) = 71
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n        print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n        # c2pread    <-"..., 4096) = 4096
read(8, "table(self, handle):\n           "..., 4096) = 4096
read(8, "rrno using _sys_errlist (or siml"..., 4096) = 4096
read(8, " p2cwrite = None, None\n         "..., 4096) = 4096
write(2, "    ", 4)                     = 4
write(2, "self.pid = os.fork()\n", 21)  = 21
close(8)                                = 0
munmap(0xb7d28000, 4096)                = 0
write(2, "OSError", 7)                  = 7
write(2, ": ", 2)                       = 2
write(2, "[Errno 12] Cannot allocate memor"..., 33) = 33
write(2, "\n", 1)                       = 1
unlink("/var/run/sd-agent.pid")         = 0
close(3)                                = 0
munmap(0xb7e0d000, 4096)                = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x589978}, {0xb89a60, [], SA_RESTORER, 0x589978}, 8) = 0
brk(0xa022000)                          = 0xa022000
exit_group(1)                           = ?

Ответы

Ответ 1

Как правило (т.е. в ядрах ванили), fork/clone происходят сбои с ENOMEM из-за того, что честный по отношению к Богу недостаток памяти (dup_mm, dup_task_struct, alloc_pid, mpol_dup, mm_init и т.д. croak), или потому что security_vm_enough_memory_mm вам не удалось , а принудительно выполнить политика overcommit.

Начните с проверки vmsize процесса, который не был вилок, во время попытки fork, а затем сравните с объемом свободной памяти (физический и своп), относящейся к политике overcommit (подключите числа в.)

В вашем конкретном случае обратите внимание, что Virtuozzo имеет дополнительные проверки в overcommit правоприменительные. Более того, я не уверен, сколько у вас действительно контроля, от внутри вашего контейнера, свопинг и переназначение конфигурации (чтобы повлиять на результат принудительного исполнения.)

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

  • переключиться на более крупный экземпляр или
  • добавьте некоторые усилия по кодированию в , более эффективно контролируя объем

ПРИМЕЧАНИЕ, что усилия по кодированию могут быть все без изменений, если выяснится, что это не вы, но какой-то другой парень собрался в другом экземпляре на том же сервере, что и ваш amock.

>

В памяти мы уже знаем, что subprocess.Popen использует fork/clone под капотом, что означает, что каждый раз, когда вы его называете, вы запрашиваете еще раз столько памяти, сколько Python уже есть, то есть в сотнях дополнительных МБ, все для того, чтобы затем exec 10kB, например free или ps. В случае неблагоприятной политики overcommit вы скоро увидите ENOMEM.

Альтернативы fork, которые не имеют этой таблицы родительских страниц и т.д., являются vfork и posix_spawn. Но если вы не хотите переписывать куски subprocess.Popen в терминах vfork/posix_spawn, используйте suprocess.Popen только один раз, в начале вашего script (когда размер памяти Python минимален), чтобы создать оболочку script, которая затем запускает free/ps/sleep и все остальное в цикле, параллельное вашему script; опросить вывод script или прочитать его синхронно, возможно, из отдельного потока, если у вас есть другие вещи, которые будут асинхронно следить за вашими данными в Python, но оставляйте forking в подчиненном процессе.

ОДНАКО, в вашем конкретном случае вы можете вообще пропустить ps и free; что информация легко доступна вам на Python непосредственно из procfs, независимо от того, хотите ли вы получить к ней доступ самостоятельно или через существующие библиотеки и/или пакеты. Если ps и free были единственными утилитами, которые вы запускали, вы можете полностью отказаться от subprocess.Popen.

Наконец, что бы вы ни делали до subprocess.Popen, если ваша script утечка памяти вы все равно будете ударять по стене в конце концов. Следите за ней, а проверяйте утечку памяти.

Ответ 2

swap может не быть ранее предложенной красной селедкой. Насколько велика проблема python непосредственно перед ENOMEM?

В ядре 2.6, /proc/sys/vm/swappiness управляет тем, насколько агрессивно ядро ​​переключится на swap, а overcommit* сколько файлов и насколько точно ядро ​​может распределить память с помощью подмигивания и кивок. Как и ваш статус взаимоотношений с facebook, он был сложным.

... но swap фактически доступен по запросу (в соответствии с веб-хостом)...

но не в соответствии с выводом вашей команды free(1), которая не отображает пространство подкачки, распознанное вашим экземпляром сервера. Теперь ваш веб-узел, возможно, знает гораздо больше, чем я об этом, но виртуальные системы RHEL/CentOS, которые я использовал, сообщили о свопе, доступном для гостевой ОС.

Адаптация Red Hat KB Article 15252:

Система Red Hat Enterprise Linux 5 будет работать отлично, без подкачки на всех, если сумма анонимных памяти и системной V-памяти менее примерно 3/4 объема ОЗУ..... Системы с 4 ГБ оперативной памяти или менее [рекомендуется иметь] минимум 2 ГБ пространства подкачки.

Сравните ваши настройки /proc/sys/vm с простой установкой CentOS 5.3. Добавьте файл подкачки. Снимите swappiness и посмотрите, живете ли вы больше.

Ответ 3

Глядя на вывод free -m, мне кажется, что на самом деле у вас нет памяти подкачки. Я не уверен, что в Linux обмен всегда будет доступен автоматически по требованию, но у меня была такая же проблема, и ни один из ответов здесь мне не помог. Однако добавление некоторой памяти подкачки устраняет проблему в моем случае, так как это может помочь другим людям, столкнувшимся с одной и той же проблемой, я отправляю свой ответ о том, как добавить обмен на 1 ГБ (на Ubuntu 12.04, но он должен работать аналогично для других дистрибутивов).

Вы можете сначала проверить, есть ли сменная память.

$sudo swapon -s

если он пуст, это означает, что у вас нет смены. Чтобы добавить своп 1GB:

$sudo dd if=/dev/zero of=/swapfile bs=1024 count=1024k
$sudo mkswap /swapfile
$sudo swapon /swapfile

Добавьте следующую строку в fstab, чтобы сделать своп постоянным.

$sudo vim /etc/fstab

     /swapfile       none    swap    sw      0       0 

Источник и дополнительная информация можно найти здесь.

Ответ 4

Я продолжаю подозревать, что у вашего клиента/пользователя есть модуль ядра или драйвер, загруженный, который вмешивается в системный вызов clone() (возможно, некоторое неясное повышение безопасности, что-то вроде LIDS, но более неясное?) или каким-то образом заполняет некоторые данные ядра структуры, которые необходимы для работы fork()/clone() (таблица процессов, страница таблицы, таблицы дескрипторов файлов и т.д.).

Здесь соответствующая часть страницы fork(2):

ERRORS
       EAGAIN fork() cannot allocate sufficient memory to copy the parent page tables and allocate a task  structure  for  the
              child.

       EAGAIN It  was not possible to create a new process because the caller RLIMIT_NPROC resource limit was encountered.  To
              exceed this limit, the process must have either the CAP_SYS_ADMIN or the CAP_SYS_RESOURCE capability.

       ENOMEM fork() failed to allocate the necessary kernel structures because memory is tight.

Я предлагаю, чтобы пользователь попробовал это после загрузки в хранилище, общее ядро ​​и только с минимальным набором модулей и загруженных драйверов (что необходимо для запуска вашего приложения / script). Оттуда, предполагая, что он работает в этой конфигурации, они могут выполнять двоичный поиск между этим и конфигурацией, которая обнаруживает проблему. Это стандартный поиск неисправностей 101.

Соответствующая строка в strace:

clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory)

... Я знаю, что другие говорили о доступности подкачки и памяти (и я бы рекомендовал вам настроить хотя бы небольшой раздел подкачки, по иронии судьбы, даже если он находится на RAM-диске... пути кода через ядро ​​Linux когда у него есть даже небольшой доступ к swap, были реализованы гораздо шире, чем те (пути обработки исключений), в которых имеется нулевая свопа.

Однако я подозреваю, что это все еще красная селедка.

Тот факт, что free сообщает 0 (ZERO) память, используемая кешем, и буферы очень тревожат. Я подозреваю, что вывод free... и, возможно, проблема с вашим приложением, вызваны каким-то проприетарным модулем ядра, который каким-то образом мешает распределению памяти.

В соответствии с man-страницами fork()/clone() системный вызов fork() должен возвращать EAGAIN, если ваш вызов вызовет нарушение ограничения ресурса (RLIMIT_NPROC)... однако он не говорит, что если EAGAIN которые будут возвращены другими нарушениями RLIMIT *. В любом случае, если ваш целевой/хост имеет какие-то странные параметры Vormetric или другие параметры безопасности (или даже если ваш процесс запущен под какой-то странной политикой SELinux), это может привести к этому -ENOMEM-сбою.

Это вряд ли станет обычной проблемой Linux/UNIX. У вас там что-то нестандартное.

Ответ 5

Вы пытались использовать:

(status,output) = commands.getstatusoutput("ps aux")

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

После некоторого тестирования я обнаружил, что это произошло только в более старых версиях python: это происходит с 2.6.5, но не с 2.7.2

Мой поиск привел меня сюда python-close_fds-issue, но отключение shut_fds не решило проблему. Это все еще стоит прочитать.

Я обнаружил, что python пропускал файловые дескрипторы, просто следя за ним:

watch "ls /proc/$PYTHONPID/fd | wc -l"

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

Ответ 6

munmap (0xb7d28000, 4096) = 0
написать (2, "OSError", 7) = 7

Я видел неаккуратный код, который выглядит так:

serrno = errno;
some_Syscall(...)
if (serrno != errno)
/* sound alarm: CATROSTOPHIC ERROR !!! */

Вы должны проверить, является ли это то, что происходит в код python. Errno действует только в том случае, если системный вызов не удалось.

Отредактировано для добавления:

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

  • разветвленные процессы
  • неиспользуемые структуры данных
  • разделяемые библиотеки
  • файлы с отображением памяти