Subproceso de Python. Error de PoEpen con OSError:[Errno 12] No se puede asignar memoria después de un período de tiempo
linux memory (9)
Nota : esta pregunta se ha vuelto a solicitar con un resumen de todos los intentos de eliminación de errores aquí .
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 subproceso. Pobre para obtener la salida de ps .
ps = subprocess.Popen([''ps'', ''aux''], stdout=subprocess.PIPE).communicate()[0]
Después de ejecutarse durante unos días, la llamada se está equivocando 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 de 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 el problema y encontré este artículo que dice:
La solución es 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 2009 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 grande llamada cheques que se inicializa una vez cuando se inicia el daemon.
La clase de cheques completa se puede encontrar en http://github.com/dmytton/sd-agent/blob/82f5ff9203e54d2adeee8cfed704d09e3f00e8eb/checks.py con la función getProcesses definida desde la línea 442. DoChecks () la llama desde la línea 520.
Es posible que desee esperar a que finalicen todos esos procesos de PS antes de agregar espacio de intercambio.
No está del todo claro qué significa "ejecutar como un proceso en segundo plano que se ejecuta 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 en ejecución o colgados en un estado zombie. Sin embargo, el método de communicate
debería limpiar los subprocesos generados.
¿Has visto tu proceso a lo largo del tiempo?
- lsof
- ps -aux | grep -i pname
- parte superior
Todos deberían 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 algunos bucles y luego reiniciarlo una vez al día como solución alternativa. Eso te ayudaría mientras tanto.
Jacob
Esa respuesta de espacio de intercambio es falsa. 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 está produciendo 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.
Necesitas
ps = subprocess.Popen(["sleep", "1000"])
os.waitpid(ps.pid, 0)
para liberar recursos.
Nota: esto no funciona en Windows.
Quizá tenga una fuga de memoria limitada por algún límite de recursos ( RLIMIT_DATA
, RLIMIT_AS
?) RLIMIT_AS
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é haces con la variable ps
después del fragmento de código que nos muestras? ¿Mantiene una referencia a eso, nunca para ser liberado? Citando los documentos del módulo de subprocess
:
Nota: La lectura de datos está almacenada en la memoria, por lo que no utilice este método si el tamaño de los datos es grande o ilimitado.
... y ps aux puede ser prolijo en un sistema ocupado ...
Actualizar
Puede verificar rlimits desde su script python usando el módulo de recursos :
import resource
print resource.getrlimit(resource.RLIMIT_DATA) # => (soft_lim, hard_lim)
print resource.getrlimit(resource.RLIMIT_AS)
Si estos devuelven "ilimitado" - (-1, -1)
- entonces mi hipótesis es incorrecta y ¡puede seguir adelante!
Ver 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.
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.
No creo que las circunstancias dadas en el artículo de Zenoss al 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 registre más información, incluso en llamadas exitosas, para que pueda ver el estado de la memoria libre cada vez antes de hacer la llamada ps
.
Una cosa más: si especifica shell=True
en la llamada de Popen, ¿ve un comportamiento diferente?
Actualización: si no es memoria, el siguiente culpable posible es de hecho manejadores de archivos. Aconsejaría ejecutar el comando que falla bajo strace
para ver exactamente qué llamadas al sistema están fallando.
¡La memoria virtual importa!
Me encontré con el mismo problema antes de agregar swap 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.
Cuando usa popen, necesita entregar close_fds = True si desea que cierre los descriptores de archivos adicionales.
la creación de un nuevo conducto, que se produce en la función _get_handles del rastreo posterior, crea 2 descriptores de archivos, pero su código actual nunca los cierra y finalmente llega al 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.