Exemple #1
0
async def background_asyncio_service(
        service: ServiceAPI,
        loop: asyncio.AbstractEventLoop = None) -> AsyncIterator[ManagerAPI]:
    """
    Run a service in the background.

    The service is running within the context block and will be properly
    cleaned up upon exiting the context block.
    """
    manager = AsyncioManager(service, loop=loop)
    task = asyncio.ensure_future(manager.run(), loop=loop)

    try:
        async with cleanup_tasks(task):
            await manager.wait_started()

            try:
                yield manager
            finally:
                await manager.stop()
    finally:
        if manager.did_error:
            # TODO: better place for this.
            raise MultiError(
                tuple(
                    exc_value.with_traceback(exc_tb)
                    for _, exc_value, exc_tb in manager._errors))
Exemple #2
0
 async def _exit(
     self,
     exc_type: Optional[Type[BaseException]],
     exc_value: Optional[BaseException],
     traceback: Optional[TracebackType],
 ) -> None:
     if not self.cms_to_exit:
         return
     # don't use gather() to ensure that we wait for all __aexit__s
     # to complete even if one of them raises
     done, _pending = await asyncio.wait([
         cm.__aexit__(exc_type, exc_value, traceback)
         for cm in self.cms_to_exit
     ])
     # This is to ensure we re-raise any exceptions our coroutines raise when exiting.
     errors: List[Tuple[Type[BaseException], BaseException,
                        TracebackType]] = []
     for d in done:
         try:
             d.result()
         except BaseException:
             errors.append(sys.exc_info())
     if errors:
         raise MultiError(
             tuple(
                 exc_value.with_traceback(exc_tb)
                 for _, exc_value, exc_tb in errors))
Exemple #3
0
async def cancel_tasks(tasks: Iterable[asyncio.Task[Any]]) -> None:
    """
    Cancel and await for the given tasks, ignoring any asyncio.CancelledErrors.
    """
    for task in tasks:
        task.cancel()

    errors: List[BaseException] = []
    # Wait for all tasks in parallel so if any of them catches CancelledError and performs a
    # slow cleanup the othders don't have to wait for it. The timeout is long as our component
    # tasks can do a lot of stuff during their cleanup.
    done, pending = await asyncio.wait(tasks, timeout=5)
    if pending:
        errors.append(
            asyncio.TimeoutError(
                "Tasks never returned after being cancelled: %s", pending))
    # We use future as the variable name here because that's what asyncio.wait returns above.
    for future in done:
        # future.exception() will raise a CancelledError if the future was cancelled by us above, so
        # we must suppress that here.
        with contextlib.suppress(asyncio.CancelledError):
            if future.exception():
                errors.append(future.exception())
    if errors:
        raise MultiError(errors)
Exemple #4
0
async def cancel_pending_tasks(*tasks: asyncio.Task[Any],
                               timeout: int) -> AsyncIterator[None]:
    """
    Cancel and await for all of the given tasks that are still pending, in no specific order.

    If all cancelled tasks have not completed after the given timeout, raise a TimeoutError.

    Ignores any asyncio.CancelledErrors.
    """
    try:
        yield
    finally:
        logger = get_logger('p2p.asyncio_utils.cancel_pending_tasks')
        cancelled: List[asyncio.Task[Any]] = []
        for task in tasks:
            if not task.done():
                task.cancel()
                cancelled.append(task)

        # It'd save us one indentation level on the block of code below if we had an early return
        # in case there are no cancelled tasks, but it turns out an early return inside a finally:
        # block silently cancels an active exception being raised, so we use an if/else to avoid
        # having to check if there is an active exception and re-raising it.
        if cancelled:
            logger.debug("Cancelled tasks %s, now waiting for them to return",
                         task)
            errors: List[BaseException] = []
            # Wait for all tasks in parallel so if any of them catches CancelledError and performs a
            # slow cleanup the othders don't have to wait for it.
            done, pending = await asyncio.wait(cancelled, timeout=timeout)
            if pending:
                errors.append(
                    asyncio.TimeoutError(
                        "Tasks never returned after being cancelled: %s",
                        pending))
            # We use future as the variable name here because that's what asyncio.wait returns
            # above.
            for future in done:
                # future.exception() will raise a CancelledError if the future was cancelled by us
                # above, so we must suppress that here.
                with contextlib.suppress(asyncio.CancelledError):
                    if future.exception():
                        errors.append(future.exception())
            if errors:
                raise MultiError(errors)
        else:
            logger.debug("No pending tasks in %s, returning", task)
Exemple #5
0
async def cancel_futures(futures: Iterable[asyncio.Future[None]]) -> None:
    """
    Cancel and await for the given futures, ignoring any asyncio.CancelledErrors.
    """
    for fut in futures:
        fut.cancel()

    errors: List[BaseException] = []
    # Wait for all futures in parallel so if any of them catches CancelledError and performs a
    # slow cleanup the othders don't have to wait for it. The timeout is long as our component
    # tasks can do a lot of stuff during their cleanup.
    done, pending = await asyncio.wait(futures, timeout=5)
    if pending:
        errors.append(
            asyncio.TimeoutError(
                "Tasks never returned after being cancelled: %s", pending))
    for task in done:
        with contextlib.suppress(asyncio.CancelledError):
            if task.exception():
                errors.append(task.exception())
    if errors:
        raise MultiError(errors)
Exemple #6
0
    async def run(self) -> None:
        if self._run_lock.locked():
            raise LifecycleError(
                "Cannot run a service with the run lock already engaged.  Already started?"
            )
        elif self.is_started:
            raise LifecycleError(
                "Cannot run a service which is already started.")

        try:
            async with self._run_lock:
                handle_cancelled_task = asyncio.ensure_future(
                    self._handle_cancelled(), loop=self._loop)

                async with cleanup_tasks(handle_cancelled_task):
                    self._started.set()

                    self.run_task(self._service.run)

                    # This is hack to get the task stats correct.  We don't want to
                    # count the `Service.run` method as a task.  This is still
                    # imperfect as it will still count as a completed task when it
                    # finishes.
                    self._total_task_count = 0

                    await self._wait_all_tasks_done()
        finally:
            self._finished.set()
            self.logger.debug("%s: finished", self)

        # Above we rely on run_task() and handle_cancelled() to run the
        # service/tasks and swallow/collect exceptions so that they can be
        # reported all together here.
        if self.did_error:
            raise MultiError(
                tuple(
                    exc_value.with_traceback(exc_tb)
                    for _, exc_value, exc_tb in self._errors))
Exemple #7
0
 async def raise_multi():
     raise MultiError([ClickException("err1"), ClickException("err2")])