2016-12-17 3 views
0

Я пытаюсь написать веб-приложение Tornado, которое выполняет локальную команду асинхронно, в качестве сопрограммы. Это урезанная пример кода:Запуск asyncio.subprocess.Process from Tornado RequestHandler

#! /usr/bin/env python3 

import shlex 
import asyncio 
import logging 

from tornado.web import Application, url, RequestHandler 
from tornado.httpserver import HTTPServer 
from tornado.ioloop import IOLoop 

logging.getLogger('asyncio').setLevel(logging.DEBUG) 


async def run(): 
    command = "python3 /path/to/my/script.py" 
    logging.debug('Calling command: {}'.format(command)) 
    process = asyncio.create_subprocess_exec(
     *shlex.split(command), 
     stdout=asyncio.subprocess.PIPE, 
     stderr=asyncio.subprocess.STDOUT 
    ) 
    logging.debug(' - process created') 

    result = await process 
    stdout, stderr = result.communicate() 
    output = stdout.decode() 
    return output 

def run_sync(self, path): 
    command = "python3 /path/to/my/script.py" 
    logging.debug('Calling command: {}'.format(command)) 
    try: 
     result = subprocess.run(
      *shlex.split(command), 
      stdout=subprocess.PIPE, 
      stderr=subprocess.STDOUT, 
      check=True 
     ) 
    except subprocess.CalledProcessError as ex: 
     raise RunnerError(ex.output) 
    else: 
     return result.stdout 


class TestRunner(RequestHandler): 

    async def get(self): 
     result = await run() 
     self.write(result) 

url_list = [ 
    url(r"/test", TestRunner), 
] 
HTTPServer(Application(url_list, debug=True)).listen(8080) 
logging.debug("Tornado server started at port {}.".format(8080)) 
IOLoop.configure('tornado.platform.asyncio.AsyncIOLoop') 
IOLoop.instance().start() 

Когда /path/to/my/script.py вызывается непосредственно он выполняет, как ожидалось. Кроме того, когда у меня есть TestHandler.get, реализованный как обычный синхронный метод (см. run_sync), он выполняется правильно. Однако, при запуске приложения выше и вызова /test, журнал показывает:

DEBUG:asyncio:Using selector: EpollSelector 
DEBUG:asyncio:execute program 'python3' stdout=stderr=<pipe> 
DEBUG:asyncio:process 'python3' created: pid 21835 

Однако ps показывает, что процесс повешен:

$ ps -ef | grep 21835 
berislav 21835 21834 0 19:19 pts/2 00:00:00 [python3] <defunct> 

У меня есть ощущение, что я не реализующее право цикл, или я делаю это неправильно, но все examples Я видел, как использовать asyncio.get_event_loop().run_until_complete(your_coro()), и я не мог найти многого о объединении асинчо и торнадо. Все предложения приветствуются!

ответ

2

Подпроцессы сложны из-за одноточечного обработчика SIGCHLD. В asyncio это означает, что они работают только с «основным» циклом событий. Если вы меняете tornado.ioloop.IOLoop.configure('tornado.platform.asyncio.AsyncIOLoop') на tornado.platform.asyncio.AsyncIOMainLoop().install(), то пример работает. Также необходимы несколько других очищений; вот полный код:

#! /usr/bin/env python3 

import shlex 
import asyncio 
import logging 

import tornado.platform.asyncio 
from tornado.web import Application, url, RequestHandler 
from tornado.httpserver import HTTPServer 
from tornado.ioloop import IOLoop 

logging.getLogger('asyncio').setLevel(logging.DEBUG) 

async def run(): 
    command = "python3 /path/to/my/script.py" 
    logging.debug('Calling command: {}'.format(command)) 
    process = await asyncio.create_subprocess_exec(
     *shlex.split(command), 
     stdout=asyncio.subprocess.PIPE, 
     stderr=asyncio.subprocess.STDOUT 
    ) 
    logging.debug(' - process created') 

    result = await process.wait() 
    stdout, stderr = await process.communicate() 
    output = stdout.decode() 
    return output 

tornado.platform.asyncio.AsyncIOMainLoop().install() 
IOLoop.instance().run_sync(run) 

Также отметим, что смерч имеет свой собственный интерфейс подпроцесс в tornado.process.Subprocess, так что если это единственное, что вам нужно asyncio для, рекомендуется использовать версию Tornado вместо этого. Имейте в виду, что объединение интерфейсов подпроцессов Tornado и asyncio в одном и том же процессе может привести к конфликтам с обработчиком SIGCHLD, поэтому вы должны выбрать тот или другой или использовать библиотеки таким образом, чтобы обработчик SIGCHLD не был необходим (например, полагаясь исключительно на на stdout/stderr вместо статуса выхода процесса).

+0

Привет, Бен, спасибо за ответ! Вы указали мне не в одном, а в двух направлениях с вашим ответом, и оба, похоже, работают нормально - один использует «AsyncIOMainLoop» (хотя он должен быть явно закрыт с помощью 'asyncio.get_event_loop(). Close()' at конец), а другой - «Subprocess» Tornado, о котором я не знал и вам нужно изучить немного больше. Документация по последнему не очень полна, хотя, я понимаю, что можно связываться с подпроцессом, используя опцию 'STREAM' из' stdin'/'stdout', это правильно? –

+0

Да, используйте опцию потока для связи с подпроцессом. –

Смежные вопросы