Nota: Esta pregunta se hizo originalmente aquí, pero el tiempo de recompensa expiró aunque no se encontró una respuesta aceptable. Estoy volviendo a hacer esta pregunta, incluidos todos los detalles proporcionados en la pregunta original.
Un script de Python ejecuta un conjunto de funciones de clase cada 60 segundos usando el módulo sched :
# sc is a sched.scheduler instance
sc.enter(60, 1, self.doChecks, (sc, False))
El script se ejecuta como un proceso demonizado usando el código aquí .
Varios métodos de clase que se llaman como parte de doChecks usan el módulo de subproceso para llamar a funciones del sistema con el fin de obtener estadísticas del sistema:
ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]
Esto funciona bien durante un período de tiempo antes de que todo el script se bloquee con el siguiente error:
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
La salida de free -m en el servidor una vez que el script se ha bloqueado es:
$ free -m
total used free shared buffers cached
Mem: 894 345 549 0 0 0
-/+ buffers/cache: 345 549
Swap: 0 0 0
El servidor está ejecutando CentOS 5.3. No puedo reproducir en mis propias cajas CentOS ni con ningún otro usuario que informe el mismo problema.
He intentado varias cosas para depurar esto como se sugiere en la pregunta original:
Registrar la salida de free -m antes y después de la llamada de Popen. No hay ningún cambio significativo en el uso de la memoria, es decir, la memoria no se utiliza gradualmente a medida que se ejecuta el script.
Agregué close_fds = True a la llamada de Popen, pero esto no hizo ninguna diferencia: el script aún se bloqueó con el mismo error. Sugerido aquí y aquí .
Revisé los rlimits que mostraban (-1, -1) tanto en RLIMIT_DATA como en RLIMIT_AS como se sugiere aquí .
Un artículo sugirió que no tener espacio de intercambio podría ser la causa, pero el intercambio está disponible bajo demanda (según el proveedor de alojamiento web) y esto también se sugirió como una causa falsa aquí. .
Los procesos se están cerrando porque ese es el comportamiento de usar .communicate () respaldado por el código fuente de Python y los comentarios aquí .
Las comprobaciones completas se pueden encontrar en GitHub aquíTodas las con la función getProcesses definida en la línea 442. Esto es llamado por doChecks () comenzando en la línea 520.
El script se ejecutó con strace con el siguiente resultado antes del bloqueo:
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) = ?
/var/log/messages
odmesg
mandar.Respuestas:
Como regla general (es decir, en los núcleos de vainilla),
fork
/clone
fracasos conENOMEM
ocurren específicamente ya sea por un honesto a Dios fuera del estado de memoria (dup_mm
,dup_task_struct
,alloc_pid
,mpol_dup
,mm_init
etc. graznido), o debido asecurity_vm_enough_memory_mm
que fallaron mientras que la aplicación de la política de sobreasignación .Comience verificando el vmsize del proceso que no se pudo bifurcar, en el momento del intento de bifurcación, y luego compárelo con la cantidad de memoria libre (física y de intercambio) en lo que respecta a la política de sobreasignación (ingrese los números).
En su caso particular, tenga en cuenta que Virtuozzo tiene comprobaciones adicionales en la aplicación de compromisos excesivos . Por otra parte, no estoy seguro de cuánto control tiene verdad, desde dentro de su contenedor, a lo largo de intercambio y configuración overcommit (con el fin de influir en el resultado de la aplicación.)
Ahora, para poder avanzar, diría que te quedan dos opciones :
TENGA EN CUENTA que el esfuerzo de codificación puede ser en vano si resulta que no es usted, sino algún otro tipo ubicado en una instancia diferente en el mismo servidor que usted ejecuta amock.
En cuanto a la memoria, ya sabemos que
subprocess.Popen
usafork
/clone
under the hood , lo que significa que cada vez que lo llamas, estás solicitando una vez más tanta memoria como Python ya está consumiendo , es decir, en los cientos de MB adicionales, todo para luegoexec
un ejecutable insignificante de 10kB comofree
ops
. En el caso de una política de sobrecompromiso desfavorable, pronto veráENOMEM
.Alternativas a las
fork
que no tienen este problema de copia de tablas de página principal, etc. sonvfork
yposix_spawn
. Pero si no tiene ganas de reescribir fragmentos desubprocess.Popen
en términos devfork
/posix_spawn
, considere usarsuprocess.Popen
solo una vez, al comienzo de su script (cuando la huella de memoria de Python es mínima), para generar un script de shell que luego se ejecutafree
/ps
/sleep
y cualquier otra cosa en un lazo paralelo a su secuencia de comandos; sondee la salida del script o léalo de forma sincrónica, posiblemente desde un hilo separado si tiene otras cosas de las que ocuparse de forma asincrónica: procese sus datos en Python pero deje la bifurcación al proceso subordinado.SIN EMBARGO , en su caso particular, puede omitir la invocación
ps
y porfree
completo; esa información está disponible para usted en Python directamente desdeprocfs
, ya sea que elija acceder a ella usted mismo oa través de bibliotecas y / o paquetes existentes . Sips
yfree
fueran las únicas utilidades que estaba ejecutando, entonces puede eliminarlas porsubprocess.Popen
completo .Finalmente, hagas lo que hagas en lo que a ti
subprocess.Popen
respecta, si tu secuencia de comandos pierde memoria, eventualmente acabarás golpeando la pared. Vigílelo y verifique que no haya pérdidas de memoria .fuente
gc.collect()
justo antessubprocess.Popen
ayuda en los casos en que el recolector de basura no se ha ejecutado durante un tiempo./proc/fd/maps
para determinar si la memoria comprometida en exceso es de hecho el problemaMirando la salida de
free -m
me parece que en realidad no tiene memoria de intercambio disponible. No estoy seguro de si en Linux el intercambio siempre estará disponible automáticamente a pedido, pero estaba teniendo el mismo problema y ninguna de las respuestas aquí realmente me ayudó. Sin embargo, al agregar algo de memoria de intercambio, se solucionó el problema en mi caso, por lo que, dado que esto podría ayudar a otras personas que enfrentan el mismo problema, publico mi respuesta sobre cómo agregar un intercambio de 1 GB (en Ubuntu 12.04, pero debería funcionar de manera similar para otras distribuciones).Primero puede verificar si hay alguna memoria de intercambio habilitada.
si está vacío, significa que no tiene ningún intercambio habilitado. Para agregar un intercambio de 1GB:
Agregue la siguiente línea
fstab
para que el intercambio sea permanente.La fuente y más información se pueden encontrar aquí .
fuente
El intercambio puede no ser la pista falsa sugerida anteriormente. ¿Qué tan grande es el proceso de Python en cuestión justo antes del
ENOMEM
?Bajo el kernel 2.6,
/proc/sys/vm/swappiness
controla qué tan agresivamente se convertirá el kernel para intercambiar yovercommit*
archiva cuánto y con qué precisión el kernel puede distribuir memoria con un guiño y un asentimiento. Al igual que el estado de su relación en Facebook, es complicado .pero no de acuerdo con la salida de su
free(1)
comando, que muestra ningún espacio de intercambio reconocido por su instancia de servidor. Ahora bien, es posible que su proveedor de alojamiento web sepa mucho más que yo sobre este tema, pero los sistemas virtuales RHEL / CentOS que he usado han informado que hay intercambio disponible para el sistema operativo invitado.Adaptación del artículo 15252 de la base de conocimiento de Red Hat :
Compare su
/proc/sys/vm
configuración con una instalación simple de CentOS 5.3. Agrega un archivo de intercambio. Mueva el trinqueteswappiness
y vea si vive más.fuente
ps -o user,pid,vsz="Mem(Kb)" -o cmd $PYTHON_PID
, o top (1), debería hacerlo.Para una solución fácil, podría
si está seguro de que su sistema tiene suficiente memoria. Vea Linux sobre la heurística de confirmación .
fuente
Sigo sospechando que su cliente / usuario tiene algún módulo del kernel o controlador cargado que está interfiriendo con la
clone()
llamada al sistema (¿quizás alguna mejora de seguridad oscura, algo como LIDS pero más oscuro?) O que de alguna manera está llenando algunas de las estructuras de datos del kernel que son necesarios parafork()
/clone()
para operar (tabla de procesos, tablas de páginas, tablas de descriptores de archivos, etc.).Aquí está la parte relevante de la
fork(2)
página de manual:Sugiero que el usuario intente esto después de arrancar en un kernel genérico y con solo un conjunto mínimo de módulos y controladores cargados (mínimo necesario para ejecutar su aplicación / script). A partir de ahí, asumiendo que funciona en esa configuración, pueden realizar una búsqueda binaria entre eso y la configuración que presenta el problema. Esta es la solución de problemas estándar de administradores de sistemas 101.
La línea relevante en tu
strace
es:... Sé que otros han hablado sobre el intercambio y la disponibilidad de memoria (y le recomendaría que configure al menos una pequeña partición de intercambio, irónicamente incluso si está en un disco RAM ... las rutas del código a través del kernel de Linux cuando tiene incluso una pequeña cantidad de intercambio disponible se ha ejercido mucho más ampliamente que aquellos (rutas de manejo de excepciones) en los que no hay intercambio disponible.
Sin embargo, sospecho que esto sigue siendo una pista falsa.
El hecho de que
free
informe 0 (CERO) memoria en uso por el caché y los búferes es muy perturbador. Sospecho que lafree
salida ... y posiblemente el problema de su aplicación aquí, son causados por algún módulo del kernel propietario que está interfiriendo con la asignación de memoria de alguna manera.De acuerdo con las páginas de manual para fork () / clone (), la llamada al sistema fork () debería devolver EAGAIN si su llamada causaría una violación del límite de recursos (RLIMIT_NPROC) ... sin embargo, no dice si se debe devolver EAGAIN por otras infracciones de RLIMIT *. En cualquier caso, si su objetivo / host tiene algún tipo de Vormetric extraño u otra configuración de seguridad (o incluso si su proceso se está ejecutando bajo alguna política de SELinux extraña), entonces podría estar causando esta falla -ENOMEM.
Es bastante improbable que sea un problema normal de Linux / UNIX. Tiene algo fuera de lo normal.
fuente
¿Ha intentado usar:
Pensé que esto me había solucionado exactamente el mismo problema. Pero luego mi proceso terminó siendo asesinado en lugar de fallar al engendrar, lo cual es aún peor ...
Después de algunas pruebas, descubrí que esto solo ocurría en versiones anteriores de Python: sucede con 2.6.5 pero no con 2.7.2
Mi búsqueda me había llevado aquí python-close_fds-issue , pero desarmar closed_fds no había resuelto el problema. Todavía vale la pena leerlo.
Descubrí que Python estaba filtrando descriptores de archivos con solo vigilarlo:
Como tú, quiero capturar la salida del comando y quiero evitar errores OOM ... pero parece que la única forma es que la gente use una versión de Python con menos errores. No es ideal...
fuente
He visto un código descuidado que se ve así:
Debe verificar si esto es lo que está sucediendo en el código de Python. Errno solo es válido si la llamada al sistema en curso falló.
Editado para agregar:
No dice cuánto tiempo dura este proceso. Posibles consumidores de memoria
fuente
errno
se restablece muchas veces a lo largo del camino.Tal vez puedas simplemente
Funciona para mi caso.
Referencia: https://github.com/openai/gym/issues/110#issuecomment-220672405
fuente