django, fastcgi: как управлять длительным процессом? - PullRequest
8 голосов
/ 20 октября 2008

Я унаследовал приложение django + fastcgi, которое необходимо изменить для выполнения длительных вычислений (до получаса или более). Что я хочу сделать, так это запустить вычисления в фоновом режиме и вернуть ответ типа «ваша работа была запущена». Пока процесс запущен, дальнейшие обращения к URL должны возвращать «ваша работа еще выполняется», пока работа не завершится, и в этот момент результаты работы должны быть возвращены. Любое последующее попадание по URL должно возвращать кешированный результат.

Я абсолютный новичок в django и не занимался какой-либо значительной веб-работой в течение десятилетия, поэтому я не знаю, есть ли встроенный способ делать то, что я хочу. Я попытался запустить процесс через subprocess.Popen (), и это работает нормально, за исключением того факта, что он оставляет несуществующую запись в таблице процесса. Мне нужно чистое решение, которое может удалить временные файлы и любые следы процесса после его завершения.

Я также экспериментировал с fork () и потоками и пока не нашел жизнеспособного решения. Есть ли каноническое решение того, что мне кажется довольно распространенным вариантом использования? FWIW это будет использоваться только на внутреннем сервере с очень низким трафиком.

Ответы [ 2 ]

4 голосов
/ 29 декабря 2008

Я должен решить подобную проблему сейчас. Это будет не публичный сайт, а аналогичный внутренний сервер с низким трафиком.

Технические ограничения:

  • все входные данные для длительного процесса могут быть предоставлены при его запуске
  • длительный процесс не требует взаимодействия с пользователем (за исключением начального ввода для запуска процесса)
  • время вычислений достаточно велико, чтобы результаты не могли быть переданы клиенту в виде немедленного HTTP-ответа
  • Требуется какая-то обратная связь (вроде индикатора выполнения) от длительного процесса.

Следовательно, нам нужно по крайней мере два веб-представления: одно для запуска длительного процесса, а другое для контроля его состояния / сбора результатов.

Нам также необходим некоторый тип межпроцессного взаимодействия: отправьте пользовательские данные из инициатора (веб-сервер по запросу http) в длительный процесс , а затем отправьте его результаты Reciever (снова веб-сервер, управляемый http-запросами). Первое легко, последнее менее очевидно. В отличие от обычного программирования Unix, приемник изначально не известен. Получатель может отличаться от инициатора, и он может начаться, когда долгосрочное задание еще выполняется или уже завершено. Таким образом, трубы не работают, и нам нужны некоторые результаты длительного процесса.

Я вижу два возможных решения:

  • диспетчеризация запускающих долго процессов в диспетчере долго выполняющихся заданий (вероятно, это и есть вышеупомянутая служба django-queue-service);
  • сохранить результаты постоянно, либо в файл или в БД.

Я предпочел использовать временные файлы и запоминать их расположение в данных сеанса. Я не думаю, что это можно сделать проще.

Сценарий задания (это длительный процесс), 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()

Джанго urls.py Отображение:

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

django views:

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
3 голосов
/ 20 октября 2008

Может быть, вы могли бы взглянуть на проблему с другой стороны.

Может быть, вы могли бы попробовать DjangoQueueService , и "демон" прослушивал очередь, проверяя, есть ли что-то новое, и обрабатывал бы его.

...