OpenStack neutron中AsyncProcess类

在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到现在都未解决。

发布了48 篇原创文章 · 获赞 4 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/m0_37313888/article/details/84572061
今日推荐