2009-08-01 15 views
8

Nota: Esta pregunta se ha vuelto a solicitar con un resumen de todos los intentos de depuración here.Subproceso de Python. Error de error con OSError: [Errno 12] No se puede asignar memoria después del período de tiempo


Tengo un script de Python que se ejecuta como un proceso de fondo que se ejecuta cada 60 segundos. Parte de eso es una llamada al subprocess.Popen para obtener la salida de ps.

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

Después de correr durante unos días, la llamada se erroring con:

 
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 

Sin embargo la salida del free en el servidor es:

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

He buscado alrededor para el problema y encontró this article que dice:

La solución es para agregar más espacio de intercambio a su servidor. Cuando el núcleo se bifurca para iniciar el modelador o el proceso de descubrimiento, primero se asegura de que haya suficiente espacio disponible en el almacén de intercambio, si es necesario, del nuevo proceso.

Observo que no hay un intercambio disponible desde la salida gratuita anterior. ¿Es probable que este sea el problema y/o qué otras soluciones podría haber?

Actualización 13 de agosto de 09 El código anterior se llama cada 60 segundos como parte de una serie de funciones de supervisión. El proceso se demoniza y el control se programa usando sched. El código específico para la función anterior es:

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 

Esto es parte de una clase más grandes llamados controles que se inicializa una vez cuando se inicia el demonio.

toda la clase cheques se puede encontrar en http://github.com/dmytton/sd-agent/blob/82f5ff9203e54d2adeee8cfed704d09e3f00e8eb/checks.py con la función GetProcesses definida a partir de la línea 442. Esto es llamado por doChecks() a partir de la línea 520.

+0

Si ejecuta la parte superior, ¿ve que el proceso en segundo plano consume una mayor cantidad de memoria? Dado el código en el que falla, sospecho que me quedaré sin descriptores de archivo (aunque debería ser un error diferente). ¿Qué otro tipo de cosas haces cada 60 segundos? – bstpierre

+0

Al haber registrado la salida de -m libre antes y después de cada llamada de Popen, la memoria permanece igual. ¿Cómo puedo verificar los descriptores de archivos? También se están lanzando varios otros procesos, pero también se están registrando y la memoria no se está "agotando" con el tiempo. – DavidM

+0

Actualicé mi respuesta con otra sugerencia. –

Respuesta

3

cuando utiliza popen necesita entregar close_fds = True si desea que cierre los descriptores de archivos adicionales.

creando un nuevo conducto, que se produce en la función _get_handles desde el rastreo posterior, crea 2 descriptores de archivo, pero su código actual nunca los cierra y finalmente alcanza el límite máximo de fd de su sistema.

No estoy seguro de por qué el error que está recibiendo indica una condición de falta de memoria: debe ser un error de descriptor de archivo ya que el valor de retorno de pipe() tiene un código de error para este problema.

+0

Creo que es solo para cerrar las descripciones adicionales mientras el subproceso se está ejecutando. Cuando el subproceso finalice, cerrará todos sus descriptores de todos modos, ¿no? –

+0

@Vinay Sajip, sí, esta respuesta parece fuera de base. "close_fds" tiene que ver con los fds heredados de los subprocesos (como $^F de Perl), y el módulo de subproceso/comunicar() se encarga de cerrar el conducto entre padres e hijos de forma inteligente. También parece improbable que su ENOMEM sea realmente ENFILE/EMFILE disfrazado. – pilcrow

+0

examinó más a fondo el código y los FD de la tubería se cerraron correctamente. Cuando la horquilla ocurre con close_fds = False, todos los FD del proceso principal se copian en el niño, en este caso todos los FD del proceso de python, ya que este código es parte de un script más grande, podría haber muchos abiertos. Según POSIX, estos deben cerrarse cuando se cierra el proceso hijo, pero es bastante común que algo no haga que esto ocurra (la búsqueda rápida de Google para fd leak proporcionará referencias). Sigo pensando que los fd son el problema. ¿Podría OP confirmar si esto resolvió el problema? – Mark

1

Es posible que desee esperar en realidad para todos aquellos procesos PS para terminar antes de agregar el espacio de intercambio.

No está nada claro qué significa "ejecutar como proceso en segundo plano cada 60 segundos".

Pero su llamada al subproceso.Popen está bifurcando un nuevo proceso cada vez.

Actualización.

Supongo que de alguna manera estás dejando todos esos procesos ejecutándose o colgados en un estado zombie. Sin embargo, el communicate método debe limpiar los subprocesos generados.

+0

"ejecutándose como un proceso en segundo plano que se ejecuta cada 60 segundos" significa que el código se llama cada 60 segundos como parte de un proceso que se ejecuta continuamente. Si no llamo a communication(), entonces realmente no puedo obtener el resultado de ps. – DavidM

+0

'communicate()' espera a que el proceso generado termine y activa hilos que leen sus flujos stdout y stderr. –

+0

@DavidM: "el código"? "se llama"? ¿Qué código? El subproceso.Popen? ¿Tenedor de un nuevo proceso cada 60 segundos? ¿Es eso lo que estás diciendo? ¿Y nunca espera que termine un solo niño? –

0

No creo que las circunstancias dadas en el artículo de Zenoss con el que se vinculó sean la única causa de este mensaje, por lo que aún no está claro que el espacio de intercambio sea definitivamente el problema. Aconsejaría que ingrese más información incluso cuando las llamadas se realicen con éxito, para que pueda ver el estado de la memoria libre cada vez que haga la llamada ps.

Una cosa más: si especifica shell=True en la llamada de Popen, ¿observa un comportamiento diferente?

Actualización: Si no hay memoria, el siguiente posible culpable es de hecho los identificadores de archivos. Aconsejaría ejecutar el comando que falla bajo strace para ver exactamente qué llamadas al sistema están fallando.

+0

Puedo agregar el shell = True in. ¿Qué hace eso exactamente? La documentación dice "Si shell es verdadero, el comando especificado se ejecutará a través del shell". pero eso realmente no explica cuál es la diferencia. – DavidM

+0

Cuando especifica 'shell = True', se genera el programa de shell (por ejemplo,' bash' en Linux, 'cmd.exe' en Windows) que a su vez ejecuta el programa real que desea engendrar. Esto no se sugiere como una ruta para reducir el uso de memoria, sino como una herramienta de diagnóstico adicional para ver cómo cambia el comportamiento. Esperaría ver más información útil al registrar las condiciones de memoria en cada spawn y ver cómo las llamadas fallidas y las llamadas exitosas se correlacionan con el estado de la memoria, el intercambio, etc. –

+0

¿Tiene alguna sugerencia de cómo registrar el uso de memoria a medida que se ejecuta el script? He encontrado http://code.activestate.com/recipes/286222/ que parece hacer el trabajo. – DavidM

0

¿Ha visto su proceso a lo largo del tiempo?

  • lsof
  • -aux ps | grep -i pname
  • superior

Todos deben dar información interesante. Estoy pensando que el proceso está atando recursos que deberían ser liberados. ¿Existe la posibilidad de que esté atando los identificadores de recursos (bloques de memoria, secuencias, identificadores de archivos, subprocesos o identificadores de proceso)? stdin, stdout, stderr de los "ps" engendrados. Controles de memoria, ... de muchas pequeñas asignaciones incrementales. Me interesaría mucho ver qué muestran los comandos anteriores para su proceso cuando acaba de iniciar y ejecutar por primera vez y después de 24 horas de "estar sentado" iniciando el subproceso regularmente.

Dado que muere después de unos días, puede ejecutarlo solo por unos pocos bucles y luego reiniciarlo una vez al día para solucionarlo. Eso te ayudaría mientras tanto.

Jacob

3

Esa respuesta intercambiar espacio es falso. Históricamente, los sistemas Unix querían intercambiar espacio disponible de esa manera, pero ya no funcionan de esa manera (y Linux nunca funcionó de esa manera). Ni siquiera está cerca de quedarse sin memoria, por lo que ese no es el verdadero problema: se está quedando sin otro recurso limitado.

Dado que se produce el error (_get_handles llama a os.pipe() para crear canalizaciones al elemento secundario), el único problema real con el que podría encontrarse no es suficiente con los descriptores de archivos libres. En cambio, buscaría archivos no cerrados (lsof -p en el PID del proceso que hace el popen). Si su programa realmente necesita mantener una gran cantidad de archivos abiertos a la vez, entonces aumente el límite de usuario y/o el límite del sistema para los descriptores de archivos abiertos.

6

Quizás tenga una fuga de memoria limitada por resource limit (RLIMIT_DATA, ?) Heredada por su secuencia de comandos python. Compruebe su * ulimit (1) * s antes de ejecutar su secuencia de comandos, y el perfil de uso de memoria de la secuencia de comandos, como otros han sugerido.

¿Qué hace con la variable ps después del fragmento de código que nos muestra? ¿Mantiene una referencia, nunca para ser liberado? Citando al subprocess module docs:

Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited.

... y ps aux puede ser detallado en un sistema ocupado ...

actualización

Puede comprobar rlimits partir con su script en Python usando el módulo resource:

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

Si éstos retorno "ilimitada" - (-1, -1) - entonces mi hipótesis es incorrecta y se puede seguir adelante!

Véase también resource.getrusage, esp. los campos ru_??rss, que pueden ayudarlo a instrumentar el consumo de memoria con el script python, sin desembolsar a un programa externo.

+0

He actualizado la pregunta para incluir más detalles sobre la llamada a la función que finalmente llama al Popen. No se hace nada específico para la variable ps después del fragmento de código: la función regresa con el resultado procesado. – DavidM

+0

@DavidM, gracias por la actualización. Eso empuja mi pregunta a una capa: ¿qué pasa con los "procesos", alguna vez se destruye, etc.? En este momento actualizaré con una forma más pitónica para verificar los límites de recursos ... – pilcrow

+0

Los límites mostraron (-1, -1) tanto en RLIMIT_DATA como en RLIMIT_AS. procesos se devuelve y luego se utiliza para enviar esos datos a un sistema de monitoreo. No es destruido He actualizado la Q con más información sobre el daemon completo. – DavidM

0

Necesitas

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

para liberar recursos.

Nota: esto no funciona en Windows.

+1

Popen.communicate() llama a Popen.wait() que llama a os.waitpid() por usted. No es necesario llamar a os.waitpid() manualmente. – user9876

2

Si está ejecutando un proceso en segundo plano, es probable que haya redirigido sus procesos stdin/stdout/stderr.

En ese caso, agregue la opción "close_fds = True" a su llamada a Popen, lo que evitará que el proceso secundario herede su salida redirigida. Este puede ser el límite al que te encuentras.

0

¡La memoria virtual importa!

Me encontré con el mismo problema antes de agregar el intercambio a mi sistema operativo. La fórmula para la memoria virtual suele ser como: SwapSize + 50% * PhysicalMemorySize. Finalmente lo resuelvo agregando más memoria física o agregando un disco Swap. close_fds no funcionará en mi caso.

Cuestiones relacionadas