在openstack/neutron的源代码中,在neutron-openvswitch-agent中有个函数daemon_loop():
def daemon_loop(self):
..........
with polling.get_polling_manager(
self.minimize_polling,
self.ovsdb_monitor_respawn_interval) as pm,\
ovsdb_monitor.get_bridges_monitor(
br_names,
self.ovsdb_monitor_respawn_interval) as bm:
self.rpc_loop(polling_manager=pm, bridges_monitor=bm)
最后有个叫做polling_manager与bridges_monitor,这两个东西从表面意思上来看,应该是状态监视器。
如果慢慢的看源代码,这两个东西最后都与一个类有关,即
neutron/agent/linux/async_process.py中的AsyncProcess
这个类是做什么的呢?
我看了看源代码中的注释,大致意思如下
这个类是用来管理一个子进程的。这个类通过subprocess产生一个子进程,并且生成一个额外的greenthread来将子进程执行过程中产生的输出与错误存储到队列中。
如respawn_interval这个参数非零,那么如果子进程执行中发生了错误,子进程与greenthread将直接被停止并且子进程将在特定间隔后重启
下面还给了一个使用用例
import time
proc = AsyncProcess(['ping', "127.0.0.1"])
proc.start()
time.sleep(5)
proc.stop()
for line in proc.iter_stdout():
print(line)
大意是通过异步调用的方式执行ping子进程,然后当前线程阻塞5s把cpu使用权让给监听greenthread,然后从子进程的执行结果中逐行读出结果
先看看构造函数
def __init__(self, cmd, run_as_root=False, respawn_interval=None,
namespace=None, log_output=False, die_on_error=False):
"""Constructor.
:param cmd: 需要调用的外部linux命令行参数列表,这里是shell=False,表示接受一个列表,列表的第一个为命令,后面的为参数
:param run_as_root: 外部linux命令以root权限运行
:param respawn_interval: 在子进程挂掉后的respawn_interval秒后重新启动该进程
:param namespace: 在特定的namespace中启动该子进程
:param log_output: 把标准输出输出到日志文件中
:param die_on_error: 有错误输出时,终止子进程
"""
self.cmd_without_namespace = cmd
self._cmd = ip_lib.add_namespace_to_cmd(cmd, namespace)
self.run_as_root = run_as_root
if respawn_interval is not None and respawn_interval < 0:
raise ValueError(_('respawn_interval must be >= 0 if provided.'))
self.respawn_interval = respawn_interval
"""子进程对象"""
self._process = None
"""子进程的pid"""
self._pid = None
"""表示子进程运行状态"""
self._is_running = False
"""用于终止子进程与监控greenthread的信号"""
self._kill_event = None
self._reset_queues()
self._watchers = []
self.log_output = log_output
self.die_on_error = die_on_error
着重看start()与stop()方法
def start(self):
"""启动一个子进程"""
"""这个时候子进程状态应该是not running的"""
LOG.debug('Launching async process [%s].', self.cmd)
if self._is_running:
raise AsyncProcessException(_('Process is already started'))
else:
self._spawn()
"""如果block==True,表示主进程一直阻塞直到这个子进程启动"""
if block:
common_utils.wait_until_true(self.is_active)
self._spawn():
def _spawn(self):
"""创建一个异步执行的进程与负责读取这个子进程输出的greenthread"""
self._is_running = True
self._pid = None
"""利用eventlet.event.Event()进行流程控制"""
self._kill_event = eventlet.event.Event()
"""利用subprocess.Popen()创建子进程"""
self._process, cmd = utils.create_process(self.cmd,
root_helper=self.root_helper)
self._watchers = []
for reader in (self._read_stdout, self._read_stderr):
"""这里是把进程的监视greenthread放到了greenthread里面。"""
"""有两种greenthread:标准输出greenthread,标准错误greenthread"""
watcher = eventlet.spawn(self._watch_process,
reader, self._kill_event)
self._watchers.append(watcher)
重点是self._watch_process()
def _watch_process(self, callback, kill_event):
while not kill_event.ready():
try:
"""kill_event为False,说明被人为退出了"""
output = callback()
"""结果为None而不是空字符,退出"""
if not output and output != "":
break
except Exception:
LOG.exception('An error occurred while communicating '
'with async process [%s].', self.cmd)
break
"""每次读完结果,挂起这个监视greenthread"""
eventlet.sleep()
"""关闭子进程响应的状态描述符"""
if self._is_running:
self._is_running = False
self._handle_process_error()
回调函数有两个,self._read_stdout与self._read_stderr
def _read(self, stream, queue):
"""将子进程产生的输出放入queue"""
data = stream.readline()
if data:
data = helpers.safe_decode_utf8(data.strip())
queue.put(data)
return data
def _read_stdout(self):
"""将标准输出放到stdout_lines队列里面"""
data = self._read(self._process.stdout, self._stdout_lines)
if self.log_output:
LOG.debug('Output received from [%(cmd)s]: %(data)s',
{'cmd': self.cmd,
'data': data})
return data
def _read_stderr(self):
"""将错误放到_stderr_lines队列里面"""
data = self._read(self._process.stderr, self._stderr_lines)
if self.log_output:
LOG.error('Error received from [%(cmd)s]: %(err)s',
{'cmd': self.cmd,
'err': data})
if self.die_on_error:
LOG.error("Process [%(cmd)s] dies due to the error: %(err)s",
{'cmd': self.cmd,
'err': data})
return None
return data
self._handle_process_error()
def _handle_process_error(self):
"""重启这个子进程"""
stdout = list(self.iter_stdout())
stderr = list(self.iter_stderr())
LOG.debug('Halting async process [%s] in response to an error. stdout:'
' [%s] - stderr: [%s]', self.cmd, stdout, stderr)
self._kill(getattr(signal, 'SIGKILL', signal.SIGTERM))
if self.respawn_interval is not None and self.respawn_interval >= 0:
eventlet.sleep(self.respawn_interval)
LOG.debug('Respawning async process [%s].', self.cmd)
try:
self.start()
except AsyncProcessException:
# 子进程已经被启动了
pass
所以,self.start()的作用可以总结为:
异步调用一个子进程,并且设置两个greenthread实现的监控线程不断地读取输出结果并把结果放到队列中。
之后我们来看看self.stop():
def stop(self, block=False, kill_signal=None):
"""停止异步进程与监听greenthread
:param kill_signal: 终止进程时向进程发送的信号标号
"""
kill_signal = kill_signal or getattr(signal, 'SIGKILL', signal.SIGTERM)
if self._is_running:
LOG.debug('Halting async process [%s].', self.cmd)
self._kill(kill_signal)
else:
raise AsyncProcessException(_('Process is not running.'))
if block:
common_utils.wait_until_true(lambda: not self.is_active())
self.kill:
def _kill(self, kill_signal):
"""终止子进程与绑定的greenthread"""
pid = self.pid
if pid:
self._is_running = False
self._pid = None
self._kill_process(pid, kill_signal)
""" 终止greenthread"""
if self._kill_event:
self._kill_event.send()
self._kill_event = None
def _kill_process(self, pid, kill_signal):
try:
utils.kill_process(pid, kill_signal, self.run_as_root)
except Exception:
LOG.exception('An error occurred while killing [%s].',
self.cmd)
return False
if self._process:
self._process.wait()
return True
现在回到开头时的示例代码
import time
proc = AsyncProcess(['ping', '127.0.0.1'])
proc.start()
time.sleep(5)
proc.stop()
for line in proc.iter_stdout():
print(line)
现在就很清楚了,执行过程是这样的
1.异步的生成与调用一个ping命令的子进程
2.创建两个greenthread负责监听这个子进程的标准输出与错误输出
3.主线程挂起,这时候开始执行greenthread,greenthread每次读出结果并存入数列后切换到其他线程
4.这时候主线程还是sleep(挂起)的,继续执行greenthread
5.5s后主线程恢复,打印greenthread所输出的结果
简而言之,这个类提供了一种异步调用了一个子进程,并且在主进程中提供了greenthread用于接收子进程的标准输出与错误输出
这个类存在的问题:
由于每次协程调度到读的时候,仅仅读取一行
def _read(self, stream, queue):
"""将子进程产生的输出放入queue"""
data = stream.readline()
if data:
data = helpers.safe_decode_utf8(data.strip())
queue.put(data)
return data
所以需要打印实时输出的子进程,能打印多少行完全取决于进程多少次执行这个read函数,而这完全是由协程调度器决定的。所以这种方法无法读取实时输出,这个问题neutron到现在都未解决。