-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keep task references while running #87970
Changes from all commits
1d6633b
9ab70db
d977673
a14b11c
83ef675
c79cd3e
062ab08
8aec73e
df48c8a
9483b5e
16af8f3
4d47fd3
6fe9569
1e92276
3cfe0e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -276,8 +276,7 @@ def __new__(cls) -> HomeAssistant: | |
def __init__(self) -> None: | ||
"""Initialize new Home Assistant object.""" | ||
self.loop = asyncio.get_running_loop() | ||
self._pending_tasks: list[asyncio.Future[Any]] = [] | ||
self._track_task = True | ||
self._tasks: set[asyncio.Future[Any]] = set() | ||
self.bus = EventBus(self) | ||
self.services = ServiceRegistry(self) | ||
self.states = StateMachine(self.bus, self.loop) | ||
|
@@ -353,12 +352,14 @@ async def async_start(self) -> None: | |
self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE) | ||
self.bus.async_fire(EVENT_HOMEASSISTANT_START) | ||
|
||
try: | ||
# Only block for EVENT_HOMEASSISTANT_START listener | ||
self.async_stop_track_tasks() | ||
async with self.timeout.async_timeout(TIMEOUT_EVENT_START): | ||
await self.async_block_till_done() | ||
except asyncio.TimeoutError: | ||
if not self._tasks: | ||
pending: set[asyncio.Future[Any]] | None = None | ||
else: | ||
_done, pending = await asyncio.wait( | ||
self._tasks, timeout=TIMEOUT_EVENT_START | ||
) | ||
|
||
if pending: | ||
_LOGGER.warning( | ||
( | ||
"Something is blocking Home Assistant from wrapping up the start up" | ||
|
@@ -494,9 +495,8 @@ def async_add_hass_job( | |
hassjob.target = cast(Callable[..., _R], hassjob.target) | ||
task = self.loop.run_in_executor(None, hassjob.target, *args) | ||
|
||
# If a task is scheduled | ||
if self._track_task: | ||
self._pending_tasks.append(task) | ||
self._tasks.add(task) | ||
task.add_done_callback(self._tasks.remove) | ||
|
||
return task | ||
|
||
|
@@ -516,9 +516,8 @@ def async_create_task(self, target: Coroutine[Any, Any, _R]) -> asyncio.Task[_R] | |
target: target to call. | ||
""" | ||
task = self.loop.create_task(target) | ||
|
||
if self._track_task: | ||
self._pending_tasks.append(task) | ||
self._tasks.add(task) | ||
task.add_done_callback(self._tasks.remove) | ||
|
||
return task | ||
|
||
|
@@ -528,23 +527,11 @@ def async_add_executor_job( | |
) -> asyncio.Future[_T]: | ||
"""Add an executor job from within the event loop.""" | ||
task = self.loop.run_in_executor(None, target, *args) | ||
|
||
# If a task is scheduled | ||
if self._track_task: | ||
self._pending_tasks.append(task) | ||
self._tasks.add(task) | ||
task.add_done_callback(self._tasks.remove) | ||
|
||
return task | ||
|
||
@callback | ||
def async_track_tasks(self) -> None: | ||
"""Track tasks so you can wait for all tasks to be done.""" | ||
self._track_task = True | ||
|
||
@callback | ||
def async_stop_track_tasks(self) -> None: | ||
"""Stop track tasks so you can't wait for all tasks to be done.""" | ||
self._track_task = False | ||
|
||
@overload | ||
@callback | ||
def async_run_hass_job( | ||
|
@@ -638,29 +625,25 @@ async def async_block_till_done(self) -> None: | |
# To flush out any call_soon_threadsafe | ||
await asyncio.sleep(0) | ||
start_time: float | None = None | ||
|
||
while self._pending_tasks: | ||
pending = [task for task in self._pending_tasks if not task.done()] | ||
self._pending_tasks.clear() | ||
Comment on lines
-642
to
-644
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old approach would clear all the tasks so it would only track new tasks. The new approach is to just track all tasks. Old + new. It doesn't matter because if the old one blocks, it's also blocking. It was also necessary because each task will try to remove itself from this set once it's done. |
||
if pending: | ||
await self._await_and_log_pending(pending) | ||
|
||
if start_time is None: | ||
# Avoid calling monotonic() until we know | ||
# we may need to start logging blocked tasks. | ||
start_time = 0 | ||
elif start_time == 0: | ||
# If we have waited twice then we set the start | ||
# time | ||
start_time = monotonic() | ||
elif monotonic() - start_time > BLOCK_LOG_TIMEOUT: | ||
# We have waited at least three loops and new tasks | ||
# continue to block. At this point we start | ||
# logging all waiting tasks. | ||
for task in pending: | ||
_LOGGER.debug("Waiting for task: %s", task) | ||
else: | ||
await asyncio.sleep(0) | ||
current_task = asyncio.current_task() | ||
|
||
while tasks := [task for task in self._tasks if task is not current_task]: | ||
await self._await_and_log_pending(tasks) | ||
|
||
if start_time is None: | ||
# Avoid calling monotonic() until we know | ||
# we may need to start logging blocked tasks. | ||
start_time = 0 | ||
elif start_time == 0: | ||
# If we have waited twice then we set the start | ||
# time | ||
start_time = monotonic() | ||
elif monotonic() - start_time > BLOCK_LOG_TIMEOUT: | ||
# We have waited at least three loops and new tasks | ||
# continue to block. At this point we start | ||
# logging all waiting tasks. | ||
for task in tasks: | ||
_LOGGER.debug("Waiting for task: %s", task) | ||
|
||
async def _await_and_log_pending(self, pending: Collection[Awaitable[Any]]) -> None: | ||
"""Await and log tasks that take a long time.""" | ||
|
@@ -704,7 +687,6 @@ async def async_stop(self, exit_code: int = 0, *, force: bool = False) -> None: | |
|
||
# stage 1 | ||
self.state = CoreState.stopping | ||
self.async_track_tasks() | ||
self.bus.async_fire(EVENT_HOMEASSISTANT_STOP) | ||
try: | ||
async with self.timeout.async_timeout(STAGE_1_SHUTDOWN_TIMEOUT): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously the test relied on race conditions about when the thread was done with work. Now the test just wait for the thread to be done.