2008-10-20 8 views
8

He heredado una aplicación django + fastcgi que debe modificarse para realizar un cálculo extenso (hasta media hora o más). Lo que quiero hacer es ejecutar el cálculo en segundo plano y devolver una respuesta de tipo "su trabajo ha sido iniciado". Mientras se está ejecutando el proceso, las visitas posteriores a la URL deben devolver "su trabajo aún se está ejecutando" hasta que el trabajo finalice y en ese momento se deben devolver los resultados del trabajo. Cualquier golpe posterior en la url debe devolver el resultado almacenado en caché.django, fastcgi: ¿cómo gestionar un proceso de larga ejecución?

Soy un completo novato en django y no he hecho ningún trabajo web importante en una década, así que no sé si hay una forma integrada de hacer lo que quiero. Intenté comenzar el proceso a través del subproceso.Popen(), y eso funciona bien, excepto por el hecho de que deja una entrada difunta en la tabla de proceso. Necesito una solución limpia que pueda eliminar los archivos temporales y cualquier rastro del proceso una vez que haya finalizado.

También he experimentado con fork() y subprocesos y todavía tengo que encontrar una solución viable. ¿Existe una solución canónica para lo que me parece un caso de uso bastante común? FWIW esto solo se usará en un servidor interno con muy poco tráfico.

+0

Sírvanse proporcionar el código que están haciendo para engendrar el procesamiento de fondo. Hay muchas formas de hacerlo, ¿cuál estás usando? –

Respuesta

3

Tal vez podría ver el problema al revés.

Quizás puedas probar DjangoQueueService, y tener un "daemon" escuchando la cola, viendo si hay algo nuevo y procesándolo.

+0

Eso es definitivamente cerca de lo que estoy buscando. Me topé con que antes, pero estoy con la esperanza de encontrar una solución que no me requiere para agregar las dependencias adicionales. Gracias. –

+0

Puede rodar un sistema de cola propio entonces. Quiero decir, no es muy difícil de hacer. – changelog

+0

como el creador de Django Queue Service, diría que mira hacia apio o uno de esos servicios de colas en su lugar. Era un hack ordenado en el día, pero ahora ha sido superado fácilmente. – heckj

4

Tengo que resolver un problema similar ahora. No será un sitio público, sino un servidor interno con poco tráfico.

limitaciones técnicas:

  • todos los datos de entrada al proceso de larga duración pueden ser suministrados en su inicio
  • proceso de larga duración no requiere interacción del usuario (excepto en la entrada inicial para comenzar un proceso)
  • el tiempo del cálculo es suficientemente largo para que los resultados no puedan ser notificados al cliente en una respuesta HTTP inmediata
  • se requiere algún tipo de comentario (tipo de barra de progreso) del proceso de larga ejecución.

Por lo tanto, necesitamos al menos dos "vistas" web: una para iniciar el proceso de larga ejecución, y la otra, para controlar su estado/recopilar los resultados.

También necesita algún tipo de comunicación entre procesos: enviar datos de usuario desde el iniciador (el servidor web a petición HTTP) a la largo proceso en ejecución, y luego enviar sus resultados a la receptor (de nuevo Web servidor, impulsado por solicitudes http). El primero es fácil, el último es menos obvio. A diferencia de la programación Unix normal, el receptor no se conoce inicialmente. El receptor puede ser un proceso diferente al del iniciador, y puede comenzar cuando el trabajo de larga ejecución aún está en progreso o ya ha finalizado. Entonces, las tuberías no funcionan y necesitamos una cierta permeabilidad de los resultados del largo proceso.

veo dos soluciones posibles:

  • lanzamientos de despacho de los largos procesos que se ejecutan en el gestor de trabajos de larga ejecución (esto es probablemente lo que el django-cola de servicio antes mencionado es);
  • guardar los resultados en forma permanente, ya sea en un archivo o en el PP.

Preferí usar archivos temporales y recordar su ubicación en los datos de la sesión. No creo que pueda hacerse más simple.

un script de trabajo (este es el proceso de larga duración), myjob.py:

import sys 
from time import sleep 

i = 0 
while i < 1000: 
    print 'myjob:', i 
    i=i+1 
    sleep(0.1) 
    sys.stdout.flush() 

Django urls.py mapeo:

urlpatterns = patterns('', 
(r'^startjob/$', 'mysite.myapp.views.startjob'), 
(r'^showjob/$', 'mysite.myapp.views.showjob'), 
(r'^rmjob/$', 'mysite.myapp.views.rmjob'), 
) 

las vistas de Django:

from tempfile import mkstemp 
from os import fdopen,unlink,kill 
from subprocess import Popen 
import signal 

def startjob(request): 
    """Start a new long running process unless already started.""" 
    if not request.session.has_key('job'): 
      # create a temporary file to save the resuls 
      outfd,outname=mkstemp() 
      request.session['jobfile']=outname 
      outfile=fdopen(outfd,'a+') 
      proc=Popen("python myjob.py",shell=True,stdout=outfile) 
      # remember pid to terminate the job later 
      request.session['job']=proc.pid 
    return HttpResponse('A <a href="/showjob/">new job</a> has started.') 

def showjob(request): 
    """Show the last result of the running job.""" 
    if not request.session.has_key('job'): 
      return HttpResponse('Not running a job.'+\ 
       '<a href="/startjob/">Start a new one?</a>') 
    else: 
      filename=request.session['jobfile'] 
      results=open(filename) 
      lines=results.readlines() 
      try: 
       return HttpResponse(lines[-1]+\ 
         '<p><a href="/rmjob/">Terminate?</a>') 
      except: 
       return HttpResponse('No results yet.'+\ 
         '<p><a href="/rmjob/">Terminate?</a>') 
    return response 

def rmjob(request): 
    """Terminate the runining job.""" 
    if request.session.has_key('job'): 
      job=request.session['job'] 
      filename=request.session['jobfile'] 
      try: 
       kill(job,signal.SIGKILL) # unix only 
       unlink(filename) 
      except OSError, e: 
       pass # probably the job has finished already 
      del request.session['job'] 
      del request.session['jobfile'] 
    return HttpResponseRedirect('/startjob/') # start a new one 
Cuestiones relacionadas