我打算用gevent 1.0.1在Python 2.7.10中编写一个子进程管理器。这似乎是相关的,所以我还要注意到我正在使用桌面x64 Windows 10。
子进程管理器的核心功能之一是将子进程的输出重定向到旋转文件。我假设使用标准RotatingFileLogger
会使这很容易实现。通过gevent可以实现非阻塞I / O.
我的第一步是写一个概念证明来展示这种方法,但我正在努力学习我认为是基本的gevent概念。
当我运行PoC时,日志文件显得很好,并且在进程运行时它们会被填充,但是当进程结束时,一切都会停止。我想有些事情会陷入僵局。
我编写了以下gevent.Greenlet
子类LogPipe
,它将从stdout
和stderr
中提取并将其提供给标准记录器:
class LogPipe(gevent.Greenlet):
"""
inspiration: http://codereview.stackexchange.com/questions/6567/redirecting-subprocesses-output-stdout-and-stderr-to-the-logging-module/17959#17959
"""
def __init__(self, logger, level):
gevent.Greenlet.__init__(self)
self._logger = logger
self._level = level
self._fd_read, self._fd_write = os.pipe()
self._pipe_reader = os.fdopen(self._fd_read)
def fileno(self):
return self._fd_write
def _run(self):
for line in iter(self._pipe_reader.readline, b''):
self._logger.log(self._level, line.strip())
self._pipe_reader.close()
def close(self):
os.close(self._fd_write)
def __str__(self):
return "LogPipe for logger {} ({})".format(self._logger.name, gevent.Greenlet.__str__(self))
这里是SubprocessManager
,也是gevent.Greenlet
的子类,底部是测试用例:
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
import os
import logging
import logging.handlers
import gevent
from gevent.subprocess import Popen
class SubprocessManager(gevent.Greenlet):
def __init__(self, name, command, log_dir=""):
gevent.Greenlet.__init__(self)
self._name = name
self._args = command
log_dir = log_dir if os.path.exists(log_dir) else os.getcwd()
formatter = logging.Formatter('%(message)s')
self._stdout_logger = logging.getLogger("{}.stdout".format(self._name))
self._stdout_logger.setLevel(logging.INFO)
stdout_log_file = os.path.join(log_dir, self._stdout_logger.name)
stdout_handler = logging.handlers.RotatingFileHandler(stdout_log_file, maxBytes=4096, backupCount=5)
stdout_handler.setFormatter(formatter)
self._stdout_logger.addHandler(stdout_handler)
self._stderr_logger = logging.getLogger("{}.stderr".format(self._name))
self._stderr_logger.setLevel(logging.INFO)
stderr_log_file = os.path.join(log_dir, self._stderr_logger.name)
stderr_handler = logging.handlers.RotatingFileHandler(stderr_log_file, maxBytes=4096, backupCount=5)
stderr_handler.setFormatter(formatter)
self._stderr_logger.addHandler(stderr_handler)
def _run(self):
stdout_pipe = logutil.LogPipe(self._stdout_logger, logging.INFO)
stdout_pipe.start()
stderr_pipe = logutil.LogPipe(self._stderr_logger, logging.INFO)
stderr_pipe.start()
try:
sp = Popen(self._args, stdout=stdout_pipe, stderr=stderr_pipe, bufsize=1)
return_code = sp.wait()
finally:
stdout_pipe.close()
stderr_pipe.close()
return return_code
if __name__ == "__main__":
m = SubprocessManager("test", "test.bat")
m.start()
gevent.wait()
test.bat
:
echo "starting"
ping 127.0.0.1 -n 5 -w 1000 > NUL
echo "bla blasdfasdf"
ping 127.0.0.1 -n 5 -w 1000 > NUL
echo "bla blasdfas df"
ping 127.0.0.1 -n 5 -w 1000 > NUL
echo "done!"
requirements.txt
:
future
gevent==1.0.1
所以我的问题是,为什么这段代码会死锁?当我使用调试器时,似乎就像readline
中对LogPipe
的最终调用永远不会返回。