usbmonkey
@usbmonkey

Как правильно форкнуть процесс в flask?

Привет.
Стоит задача сделать API, под которым будет запускаться скриптик. Скрипт в свою очередь может выполняться очень и очень долго - от 90 секунд. API, как можно догадаться, должен отвечать моментально.
В качестве веб-севера будет выступать nginx с proxy на flask апликацию.
Как удалось выяснить, flask совсем не асинхронный.
Сразу возникала идея форкнуться и выполнять задачу в фоне.

Накидал для пробы:
def start():
	pid1 = os.fork()
	if pid1 == 0:
		os.setsid()
		x = 30
		while x > 0:
			sleep(1)
			with open('/tmp/sleep.log', 'a') as fd:
				pid = str(os.getpid())
				print "child pid = ", pid 
				fd.write(pid  + ' PID \n')
			x -= 1
		os._exit(0)
	else:
		print os.getpid()
		while True:
			pass

start()


В этом примере все замечательно работает. Создается новый процесс, задачка выполнятся в фоне. После завершения дочерний процесс умирает, родительский продолжает работать.

Попробовал перенести под flask:

@app.route('/api/start', methods=['POST'])
def start():
	d1 = "DONE\n"
	pid1 = os.fork()
	if pid1 == 0:
		os.setsid()
		closer() #здесь закрываю все файловые дескрипторы унаследованные от родителя
		x = 30
		while x > 0:
			sleep(1)
			with open('/tmp/sleep.log', 'a') as fd:
				pid = str(os.getpid())
				print "child pid = ", pid 
				fd.write(pid  + ' PID \n')
			x -= 1
		os._exit(0)
	else:
		print os.getpid()
	return d1


Вот что происходит в этом примере:
username    6158  0.0  0.4 106528 26192 ?        S    19:34   0:00 python -u /home/username/VCS/username/seek/lui/tcpdumper/dumper_api.py
username    6165  0.3  0.4 182876 26816 ?        Sl   19:34   0:05 /usr/bin/python /home/username/VCS/username/seek/lui/tcpdumper/dumper_api.py
username    6262  0.0  0.0      0     0 ?        Zs   19:34   0:00 [python] <defunct>


Дочерний процесс сразу становится зомби. Соответственно, в файл ничего не записывается.

Собственно, главный вопрос в том, почему так происходит.
Подскажите, какие еще варианты решения проблемы могут мне подойти.
Смотрел в сторону Tornado, subprocess ( его использовать нежелательно ).
Спасибо.
  • Вопрос задан
  • 506 просмотров
Решения вопроса 1
usbmonkey
@usbmonkey Автор вопроса
Удалось разобраться в проблеме. Может быть кому-нибудь поможет.

Дело в том, что родительский процесс ждет завершения дочернего ( код возврата ).
Я думал, что вызова os._exit() достаточно и это можно увидеть запустив strace:
16059 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=16060, si_status=0, si_utime=0, si_stime=0} ---

Оказалось, что есть нюансы.
Нюанс заключается в следующем. Для родительского процесса нужно установить обработчик сигнала. Сделать это можно использовав библиотеку signal.
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
Первый аргумент - int'вая константа, а второй - действия для сигнала.
После завершения дочернего процесса он уходит в состоянии зомби ( это нормально ), как было в моем случаем. После установки обработчика начинает работать механизм reaping и OS самостоятельно очищает таблицу процессов.

Кстати, на stackoverflow предложили достаточно хорошее решение.
Идея заключается, в том, чтобы сделать демона, который будет смотреть в очередь задач. При этом веб-сервер будет отвечать 202 кодом. Задачки будут разбираться по мере поступления, а статут задачи всегда можно будет узнать по другому URL.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
@thenno
Проектирую, разрабатываю, преподаю.
Для начала, в Python есть более вменяемые способы управления процессами - посмотрите модуль multiprocess.

API, как можно догадаться, должен отвечать моментально.

Догадаться не получается, на самом деле:) Очень непонятно, зачем нужна такая логика - сначала ответить клиенту 'ok', а только потом закончить выполнять операцию. Это логически неверно и сбивает с толку в 99% случаев. По-хорошему api должно принять запрос, выполнить действие (хоть меньше секунды, хоть все 90 секунд), а затем ответить клиенту с успехом или нет.

Главное здесь - грамотно настроить nginx (worker_connections и timeout'ы как минимум, возможно, в официальной документации найдется что-то более годное), чтобы в случае множества медленных запросов сервис продолжал нормально работать.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы