diff --git a/homeassistant/core.py b/homeassistant/core.py index 8e88820bf70..b2a00cce038 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -82,7 +82,11 @@ from .exceptions import ( 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 ( @@ -678,7 +682,11 @@ class HomeAssistant: 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: @@ -791,7 +799,7 @@ class HomeAssistant: # 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 diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 5b119a58c22..4caf074b879 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -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 @@ -20,6 +20,13 @@ _R = TypeVar("_R") _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]: