三人托儿所,如果一个人失败,不会取消所有任务

时间:2018-01-20 10:18:47

标签: python python-trio

我想用trio实现一个服务器。单个客户端连接由托儿所生成的任务处理。然而,三人文档说"如果托儿所里面的任何任务以未处理的异常结束,那么托儿所立即取消托儿所内的所有任务。"。这对我的用例非常不幸。我更愿意在记录错误时继续为其他连接提供服务。有没有办法做到这一点?

1 个答案:

答案 0 :(得分:2)

您可以自己实现托儿所界面:

class ExceptionLoggingNursery:
    def __init__(self, nursery):
        self.nursery = nursery

    @property
    def cancel_scope(self):
        return self.nursery.cancel_scope

    async def _run_and_log_errors(self, async_fn, *args):
        # This is more cumbersome than it should be
        # See https://github.com/python-trio/trio/issues/408
        def handler(exc):
            if not isinstance(exc, Exception):
                return exc
            logger.error("Unhandled exception!", exc_info=exc)
        with trio.MultiError.catch(handler):
            return await async_fn(*args)

    def start_soon(self, async_fn, *args, **kwargs):
        self.nursery.start_soon(self._run_and_log_errors, async_fn, *args, **kwargs)

    async def start(self, async_fn, *args, **kwargs):
        return await self.nursery.start(self._run_and_log_errors, async_fn, *args, **kwargs)

@asynccontextmanager
async def open_exception_logging_nursery():
    async with trio.open_nursery() as nursery:
        yield ExceptionLoggingNursery(nursery)

请注意,我们只捕获Exception个子类,并允许其他异常继续传播。这意味着,如果你的一个子任务提出KeyboardInterrupt(因为你点击了控制-C),或者trio.Cancelled(因为你,好吧,取消它...也许是因为你点击了控制-C并且所居住的托儿所被取消了),然后这些例外被允许传播出去并且仍然导致所有其他任务被取消,这几乎肯定是你想要的。

它有点代码,但可以轻松放入可重用的库中。 (如果我真的这样做,我可能会将异常处理代码作为参数传递给open_exception_logging_nursery,而不是硬编码调用logger.error。)我很乐意看到一个拥有这种"智能主管的图书馆"在它 - 基本的三重托儿所总是作为这样的事情的组成部分。您可以想象其他更有趣的策略,例如"如果任务以未处理的异常退出,则记录某些内容然后重新启动它,并使用指数退避"。 (Erlang supervisors窃取受到启发的好主意。)