que - script python ejemplo
Comience el proceso en segundo plano/daemon desde el script CGI (9)
No he intentado usar fork
pero he logrado lo que está pidiendo al ejecutar sys.stdout.flush()
después del mensaje original, antes de llamar al proceso en segundo plano.
es decir
print "Please wait..."
sys.stdout.flush()
output = some_processing() # put what you want to accomplish here
print output # in my case output was a redirect to a results page
Estoy intentando iniciar un proceso en segundo plano desde un script CGI. Básicamente, cuando se envía un formulario, el script CGI indicará al usuario que se está procesando su solicitud, mientras que el script de fondo procesará en sí mismo (porque el procesamiento suele llevar mucho tiempo). El problema al que me enfrento es que Apache no enviará la salida de la secuencia de comandos CGI principal al navegador hasta que finalice la secuencia de comandos hija.
Un colega me dijo que lo que quiero hacer es imposible porque no hay forma de evitar que Apache espere a que muera todo el árbol de procesos de un script CGI. Sin embargo, también he visto numerosas referencias en la web sobre un truco de "doble fork" que se supone que hace el trabajo. El truco se describe sucintamente en esta respuesta de desbordamiento de pila , pero he visto un código similar en otro lugar.
Aquí hay un pequeño script que escribí para probar el truco de doble tenedor en Python:
import os
import sys
if os.fork():
print ''Content-type: text/html/n/n Done''
sys.exit(0)
if os.fork():
os.setsid()
sys.exit(0)
# Second child
os.chdir("/")
sys.stdout.close()
sys.stderr.close()
sys.stdin.close()
f = open(''/tmp/lol.txt'', ''w'')
while 1:
f.write(''test/n'')
Si ejecuto esto desde el shell, hace exactamente lo que esperaba: el script original y el primer descendiente mueren, y el segundo descendiente sigue ejecutándose hasta que se elimine manualmente. Pero si accedo a él a través de CGI, la página no se cargará hasta que mate al segundo descendiente o Apache lo mate debido al tiempo de espera de CGI. También he intentado reemplazar el segundo sys.exit(0)
con os._exit(0)
, pero no hay diferencia.
¿Qué estoy haciendo mal?
Creo que hay dos problemas: setsid
está en el lugar equivocado y realiza operaciones de IO con búfer en uno de los niños transitorios:
if os.fork():
print "success"
sys.exit(0)
if os.fork():
os.setsid()
sys.exit()
Tienes el proceso original (abuelo, impresiones "éxito"), el padre intermedio y el nieto ("lol.txt").
La llamada a os.setsid()
se realiza en el padre intermedio después de que se haya generado el nieto . El padre del centro no puede influir en la sesión del nieto después de que se haya creado el nieto. Prueba esto:
print "success"
sys.stdout.flush()
if os.fork():
sys.exit(0)
os.setsid()
if os.fork():
sys.exit(0)
Esto crea una nueva sesión antes de engendrar al nieto. Luego el padre intermedio muere, dejando la sesión sin un líder de grupo de proceso, asegurando que cualquier llamada para abrir un terminal fallará, asegurándose de que nunca haya ningún bloqueo en la entrada o salida del terminal, o que envíe señales inesperadas al niño.
Tenga en cuenta que también he trasladado el success
al abuelo; no hay garantía de qué niño se ejecutará primero después de llamar al fork(2)
, y corre el riesgo de que el niño se engendre, y potencialmente intente escribir el resultado al error estándar o estándar, antes de que el padre del medio pudiera tener una oportunidad para escribir success
en el cliente remoto.
En este caso, las transmisiones se cierran rápidamente, pero aún así, la mezcla de flujos de E / S estándar entre múltiples procesos puede dificultar: consérvelo todo en un solo proceso, si puede.
Editar He encontrado un comportamiento extraño que no puedo explicar:
#!/usr/bin/python
import os
import sys
import time
print "Content-type: text/plain/r/n/r/npid: " + str(os.getpid()) + "/nppid: " + str(os.getppid())
sys.stdout.flush()
if os.fork():
print "/nfirst fork pid: " + str(os.getpid()) + "/nppid: " + str(os.getppid())
sys.exit(0)
os.setsid()
print "/nafter setsid pid: " + str(os.getpid()) + "/nppid: " + str(os.getppid())
sys.stdout.flush()
if os.fork():
print "/nsecond fork pid: " + str(os.getpid()) + "/nppid: " + str(os.getppid())
sys.exit(0)
#os.sleep(1) # comment me out, uncomment me, notice following line appear and dissapear
print "/nafter second fork pid: " + str(os.getpid()) + "/nppid: " + str(os.getppid())
La última línea, after second fork pid
, solo aparece cuando la os.sleep(1)
está comentada. Cuando la llamada se deja en su lugar, la última línea nunca aparece en el navegador. (De lo contrario, todo el contenido se imprime en el navegador).
Yo no recomendaría abordar el problema de esta manera. Si necesita ejecutar alguna tarea de forma asíncrona, ¿por qué no utilizar una cola de trabajo como beanstalkd en lugar de intentar separar las tareas de la solicitud? Hay bibliotecas de cliente para beanstalkd disponibles para python.
No tenedor: ejecuta el lote por separado
Este enfoque de doble bifurcación es una especie de truco, lo que para mí es una indicación de que no se debe hacer :). Para CGI de todos modos. Bajo el principio general de que si algo es demasiado difícil de lograr, es probable que te estés acercando al camino equivocado.
Afortunadamente, proporciona la información de contexto sobre lo que necesita: una llamada CGI para iniciar un procesamiento que se realiza de forma independiente y para regresar a la persona que llama. Bueno, seguro, hay comandos de Unix que hacen precisamente eso: programar el comando para que se ejecute en un momento específico ( at
) o cuando la CPU esté libre (por batch
). Así que haz esto en su lugar:
import os
os.system("batch <<< ''/home/some_user/do_the_due.py''")
# or if you don''t want to wait for system idle,
# os.system("at now <<< ''/home/some_user/do_the_due.py''")
print ''Content-type: text/html/n''
print ''Done!''
Y ahí lo tienes. Tenga en cuenta que si hay algún resultado para stdout / stderr, se enviará por correo al usuario (lo cual es bueno para la depuración, pero de lo contrario el script probablemente debería mantenerse en silencio).
PD. Acabo de recordar que Windows también tiene una versión de at
, por lo que con modificaciones menores de la invocación también puede tener ese trabajo en apache en Windows (frente al truco de fork que no funcionará en Windows).
PPS. asegúrese de que el proceso que ejecuta CGI no se excluya en /etc/at.deny
de la programación de trabajos por lotes
Como han señalado otras respuestas, es complicado iniciar un proceso persistente desde su script CGI porque el proceso debe desvincularse limpiamente del programa CGI. He descubierto que un gran programa de propósito general para esto es daemon . Se ocupa de los detalles desordenados que implican manejadores de archivos abiertos, grupos de procesos, directorio raíz, etc. para usted. Entonces, el patrón de un programa CGI de este tipo es:
#!/bin/sh
foo-service-ping || daemon --restart foo-service
# ... followed below by some CGI handler that uses the "foo" service
La publicación original describe el caso en el que desea que su programa CGI regrese rápidamente, mientras genera un proceso en segundo plano para finalizar el manejo de esa solicitud. Pero también existe el caso donde su aplicación web depende de un servicio en ejecución que debe mantenerse activo. (Otras personas han hablado sobre el uso de beanstalkd para manejar trabajos. Pero, ¿cómo se aseguran de que beanstalkd esté vivo?) Una forma de hacerlo es reiniciar el servicio (si está inactivo) desde el script CGI. Este enfoque tiene sentido en un entorno donde tienes un control limitado sobre el servidor y no puedes confiar en cosas como cron o un mecanismo init.d.
Aún me duele la cabeza con eso. Intenté todas las formas posibles de usar tu código con el cierre de fork y stdout, anulando o cualquier cosa, pero nada funcionó. La visualización de salida del proceso incompleta depende de la configuración del servidor web (Apache u otro), y en mi caso no fue una opción para cambiarla, por lo que prueba con "Transferencia de codificación: fragmentada; fragmento = CRLF" y "sys.stdout.flush () "tampoco funcionó. Aquí está la solución que finalmente funcionó.
En resumen, use algo como:
if len(sys.argv) == 1: # I''m in the parent process
childProcess = subprocess.Popen(''./myScript.py X'', bufsize=0, stdin=open("/dev/null", "r"), stdout=open("/dev/null", "w"), stderr=open("/dev/null", "w"), shell=True)
print "My HTML message that says to wait a long time"
else: # Here comes the child and his long process
# From here I cannot print to Webserver, but I can write in files that will be refreshed in my web page.
time.sleep(15) # To verify the parent completes rapidly.
Utilizo el parámetro "X" para hacer la distinción entre padre e hijo porque llamo el mismo script para ambos, pero podría hacerlo más simple llamando a otro script. Si un ejemplo completo sería útil, por favor pregunte.
De acuerdo, estoy agregando una solución más simple, si no necesita iniciar otro script, sino continuar en el mismo para hacer el proceso largo en segundo plano. Esto le permitirá dar un mensaje de espera visto instantáneamente por el cliente y continuar su procesamiento del servidor, incluso si el cliente mata la sesión del navegador:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import time
import datetime
print "Content-Type: text/html;charset=ISO-8859-1/n/n"
print "<html>Please wait...<html>/n"
sys.stdout.flush()
os.close(sys.stdout.fileno()) # Break web pipe
if os.fork(): # Get out parent process
sys.exit()
# Continue with new child process
time.sleep(1) # Be sure the parent process reach exit command.
os.setsid() # Become process group leader
# From here I cannot print to Webserver.
# But I can write in other files or do any long process.
f=open(''long_process.log'', ''a+'')
f.write( "Starting {0} .../n".format(datetime.datetime.now()) )
f.flush()
time.sleep(15)
f.write( "Still working {0} .../n".format(datetime.datetime.now()) )
f.flush()
time.sleep(300)
f.write( "Still alive - Apache didn''t scalped me!/n" )
f.flush()
time.sleep(150)
f.write( "Finishing {0} .../n".format(datetime.datetime.now()) )
f.flush()
f.close()
He leído la mitad de Internet durante una semana sin éxito en esta, finalmente traté de probar si hay una diferencia entre sys.stdout.close()
y os.close(sys.stdout.fileno())
y hay una enorme : el primero no hizo nada mientras que el segundo cerró el conducto del servidor web y se desconectó por completo del cliente. La horquilla solo es necesaria porque el servidor web matará sus procesos después de un tiempo y su largo proceso probablemente necesite más tiempo para completarse.
Necesitaba romper el stdout así como el stderr así:
sys.stdout.flush()
os.close(sys.stdout.fileno()) # Break web pipe
sys.sterr.flush()
os.close(sys.stderr.fileno()) # Break web pipe
if os.fork(): # Get out parent process
sys.exit()
#background processing follows here
Para miles que tienen "sh: 1: Syntax error: redirection unexpected"
con la solución at / batch intente utilizar algo como esto:
Asegúrese de que el comando at esté instalado y que el usuario ejecute la aplicación inst en /etc/at.deny
os.system("echo sudo /srv/scripts/myapp.py | /usr/bin/at now")