从python子进程作业获取输出到龙卷风

时间:2013-07-18 22:24:10

标签: python asynchronous subprocess tornado

我搜索了很多,但没有找到如何将正在运行的python子进程的输出转换为Tornado。我想要的是Travis CI之类的东西。在管理页面中,我将启动作业,服务器将接收请求并启动子进程。此子进程将执行一些数据挖掘并使用一些日志提供字符串缓冲区。我将通过settimeout或websocket获取带有一些ajax的日志,并将此日志输出到页面中。即使用户关闭页面并稍后返回该页面,也会有日志,通常会更新。嗯,与Travis非常相似。

1 个答案:

答案 0 :(得分:3)

此博文显示了执行此操作的方法:http://stefaanlippens.net/python-asynchronous-subprocess-pipe-reading

基本上这篇文章展示了如何通过异步读取stdout和stderr来读取进程输出时如何防止死锁。您可以从producer替换__main__命令以运行您喜欢的任何命令和带有代码的print语句来处理Tornado中的输出。

更新:如果博客被删除,我已经包含以下内容:

  

...如果你想逐行阅读标准输出和错误怎么办,   例如,因为您想监视更长时间运行的进程?上   在网络上你可以找到很多不同程度的解决方案   复杂性,抽​​象和依赖性。一个解决方案(有限的   代码和标准库之外没有依赖关系)是读取   管道在不同的螺纹中,因此一个管道不能阻挡另一个管道。

     

下面的代码显示了一个示例实现。脚本已设置完毕   以这种方式用于父进程作为子进程。

     

对于子进程:当使用'produce'参数调用时,它运行的produce()函数只是随机渲染一些行   标准输出和标准错误。在线之间有一个触摸   延迟模拟较长时间运行的过程。   在consume()函数中实现的父进程(不带参数调用的脚本)在“子模式”中调用相同的脚本   子进程并逐行监视其输出,而不知道   推进每条管道的管道。

     

AsynchronousFileReader类用于将读取的线程   标准输出和错误管道异步并将每一行放在一个   队列。然后主线程可以通过观察来监视子进程   他们排队等候。

import sys
import subprocess
import random
import time
import threading
import Queue

class AsynchronousFileReader(threading.Thread):
    '''
    Helper class to implement asynchronous reading of a file
    in a separate thread. Pushes read lines on a queue to
    be consumed in another thread.
    '''

    def __init__(self, fd, queue):
        assert isinstance(queue, Queue.Queue)
        assert callable(fd.readline)
        threading.Thread.__init__(self)
        self._fd = fd
        self._queue = queue

    def run(self):
        '''The body of the tread: read lines and put them on the queue.'''
        for line in iter(self._fd.readline, ''):
            self._queue.put(line)

    def eof(self):
        '''Check whether there is no more content to expect.'''
        return not self.is_alive() and self._queue.empty()

def consume(command):
    '''
    Example of how to consume standard output and standard error of
    a subprocess asynchronously without risk on deadlocking.
    '''

    # Launch the command as subprocess.
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # Launch the asynchronous readers of the process' stdout and stderr.
    stdout_queue = Queue.Queue()
    stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue)
    stdout_reader.start()
    stderr_queue = Queue.Queue()
    stderr_reader = AsynchronousFileReader(process.stderr, stderr_queue)
    stderr_reader.start()

    # Check the queues if we received some output (until there is nothing more to get).
    while not stdout_reader.eof() or not stderr_reader.eof():
        # Show what we received from standard output.
        while not stdout_queue.empty():
            line = stdout_queue.get()
            print 'Received line on standard output: ' + repr(line)

        # Show what we received from standard error.
        while not stderr_queue.empty():
            line = stderr_queue.get()
            print 'Received line on standard error: ' + repr(line)

        # Sleep a bit before asking the readers again.
        time.sleep(.1)

    # Let's be tidy and join the threads we've started.
    stdout_reader.join()
    stderr_reader.join()

    # Close subprocess' file descriptors.
    process.stdout.close()
    process.stderr.close()

def produce(items=10):
    '''
    Dummy function to randomly render a couple of lines
    on standard output and standard error.
    '''
    for i in range(items):
        output = random.choice([sys.stdout, sys.stderr])
        output.write('Line %d on %s\n' % (i, output))
        output.flush()
        time.sleep(random.uniform(.1, 1))

if __name__ == '__main__':
    # The main flow:
    # if there is an command line argument 'produce', act as a producer
    # otherwise be a consumer (which launches a producer as subprocess).
    if len(sys.argv) == 2 and sys.argv[1] == 'produce':
        produce(10)
    else:
        consume(['python', sys.argv[0], 'produce'])