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

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

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

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

Сценарий выполняется как демонизированный процесс с использованием приведенного здесь кода.

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

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

$ 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, ни на других пользователях, сообщающих об этой проблеме.

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

  1. Регистрация вывода free -m до и после вызова Popen. Значительного изменения в использовании памяти нет, т. Е. Память постепенно не используется во время выполнения скрипта.

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

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

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

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

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

Сценарий был запущен с помощью 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)                           = ?

7 ответов

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

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

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

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

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

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

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

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

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

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

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

Сначала вы можете проверить, включена ли какая-либо подкачка памяти.

$sudo swapon -s

если он пуст, это означает, что у вас не включен своп. Чтобы добавить 1 ГБ подкачки:

$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 

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

Для простого исправления вы могли бы

echo 1 > /proc/sys/vm/overcommit_memory

если вы уверены, что в вашей системе достаточно памяти. См. Linux более совершенной эвристики.

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

Под ядром 2.6 /proc/sys/vm/swappiness контролирует, насколько агрессивно ядро ​​превратится в своп, и overcommit* Файлы, сколько и как точно ядро ​​может распределить память с подмигиванием и кивком. Как и ваш статус в Facebook, это сложно.

... но своп на самом деле доступен по запросу (по данным веб-хостинга)...

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

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

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

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

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

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

ОШИБКИ
       EAGAIN fork() не может выделить достаточно памяти, чтобы скопировать таблицы страниц родителя и выделить структуру задачи для
              ребенок.

       EAGAIN Не удалось создать новый процесс, так как был обнаружен предел ресурса RLIMIT_NPROC вызывающей стороны. к
              превышение этого предела, процесс должен иметь возможность CAP_SYS_ADMIN или CAP_SYS_RESOURCE.

       ENOMEM fork () не удалось выделить необходимые структуры ядра из-за нехватки памяти.

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

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

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

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

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

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

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

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

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

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

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

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

Мой поиск привел меня сюда http://bramp.net/blog/python-close_fds-issue, но удаление closed_fds не решило проблему. Это все еще стоит прочитать.

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

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

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

Может ты просто можешь

$ sudo bash -c "echo vm.overcommit_memory=1 >> /etc/sysctl.conf"
$ sudo sysctl -p

В моем случае это работает.

Ссылка: https://github.com/openai/gym/issues/110

munmap (0xb7d28000, 4096) = 0
write (2, "OSError", 7) = 7

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

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

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

Отредактировано, чтобы добавить:

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

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