如果失败,不会取消所有任务的三重奏托儿所

trio nursery that doesn't cancel all tasks if one fails

我想用 trio 实现一个服务器。单个客户端连接由托儿所生成的任务处理。但是,三重奏文档说 "If any task inside the nursery finishes with an unhandled exception, then the nursery immediately cancels all the tasks inside the nursery."。这对我的用例来说非常不幸。我宁愿在记录错误的同时继续为其他连接提供服务。有办法吗?

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

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(因为您按下了 control-C),或者 trio.Cancelled(因为您取消了它……也许是因为您按下了 control-C并且 parent 居住的托儿所被取消了),那么这些异常被允许传播出去并且仍然导致所有其他任务被取消,这几乎肯定是你想要的.

这是一些代码,但可以很容易地放入可重用的库中。 (如果我真的这样做,我可能会将异常处理代码作为传递给 open_exception_logging_nursery 的参数,而不是对 logger.error 的调用进行硬编码。)而且我希望看到一个库里面有这种 "smart supervisor" -- 基本的三重奏托儿所总是被设计成这些东西的基石。您还可以想象其他更有趣的政策,例如 "if a task exits with an unhandled exception, log something and then restart it, with an exponential backoff"。 (Erlang supervisors 窃取 灵感的良好创意来源。)