Skip to content

Commit

Permalink
Fix shutdown being delayed for cancelling tasks (#93287)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed May 21, 2023
1 parent 72c76d3 commit 780f9bc
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 4 deletions.
14 changes: 11 additions & 3 deletions homeassistant/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@
from .helpers.aiohttp_compat import restore_original_aiohttp_cancel_behavior
from .helpers.json import json_dumps
from .util import dt as dt_util, location, ulid as ulid_util
from .util.async_ import run_callback_threadsafe, shutdown_run_callback_threadsafe
from .util.async_ import (
cancelling,
run_callback_threadsafe,
shutdown_run_callback_threadsafe,
)
from .util.read_only_dict import ReadOnlyDict
from .util.timeout import TimeoutManager
from .util.unit_system import (
Expand Down Expand Up @@ -678,7 +682,11 @@ async def async_block_till_done(self) -> None:
start_time: float | None = None
current_task = asyncio.current_task()

while tasks := [task for task in self._tasks if task is not current_task]:
while tasks := [
task
for task in self._tasks
if task is not current_task and not cancelling(task)
]:
await self._await_and_log_pending(tasks)

if start_time is None:
Expand Down Expand Up @@ -791,7 +799,7 @@ async def async_stop(self, exit_code: int = 0, *, force: bool = False) -> None:
# while we are awaiting canceled tasks to get their result
# which will result in the set size changing during iteration
for task in list(running_tasks):
if task.done():
if task.done() or cancelling(task):
# Since we made a copy we need to check
# to see if the task finished while we
# were awaiting another task
Expand Down
9 changes: 8 additions & 1 deletion homeassistant/util/async_.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Asyncio utilities."""
from __future__ import annotations

from asyncio import Semaphore, gather, get_running_loop
from asyncio import Future, Semaphore, gather, get_running_loop
from asyncio.events import AbstractEventLoop
from collections.abc import Awaitable, Callable
import concurrent.futures
Expand All @@ -20,6 +20,13 @@
_P = ParamSpec("_P")


def cancelling(task: Future[Any]) -> bool:
"""Return True if task is done or cancelling."""
# https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancelling
# is new in Python 3.11
return bool((cancelling_ := getattr(task, "cancelling", None)) and cancelling_())


def run_callback_threadsafe(
loop: AbstractEventLoop, callback: Callable[..., _T], *args: Any
) -> concurrent.futures.Future[_T]:
Expand Down

0 comments on commit 780f9bc

Please sign in to comment.