mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-10-10 00:39:38 +01:00
Consume the exception of a finished detached job task (#7217)
* Consume the exception of a finished detached job task With detach, errors raised by the job body surface on the returned task rather than to the caller. A caller that starts a detached job without awaiting the task, such as a scheduled reload starting the Supervisor auto update, would then leave an unretrieved exception that asyncio reports with a full traceback when the task is garbage collected. The wrapper has already captured the error on the job and logged it, so that report only adds noise. Retrieve the exception in the done callback the decorator already registers on every detached task. This only marks it as retrieved: callers that await the task still receive the exception. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Log a consumed detached job failure A HassioError is only logged when it is raised with a logger, so consuming the exception of an unawaited detached task could leave a failure without any trace once the job record is cleaned up. Log one warning line naming the job. A JobException is skipped because the wrapper already logged it with its traceback. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
fb84e69f29
commit
24abbd2eba
2 files changed
+45
-2
No files matched your search
@@ -541,10 +541,19 @@ class Job(CoreSysAttributes):
|
||||
)
|
||||
|
||||
def _clear_detached_task(self, task: asyncio.Task[Any]) -> None:
|
||||
"""Drop the reference to a finished detached task.
|
||||
"""Drop the reference to a finished detached task and consume its error.
|
||||
|
||||
Guarded by identity so an older task cannot clear a newer one.
|
||||
A caller that does not await the task must not trigger asyncio's
|
||||
"Task exception was never retrieved" report, so retrieve the error
|
||||
here. Callers that do await the task still receive it. A HassioError
|
||||
is only logged if it was raised with a logger, so log one line naming
|
||||
the job to keep the failure observable. A JobException was already
|
||||
logged with its traceback by the wrapper. Guarded by identity so an
|
||||
older task cannot clear a newer one.
|
||||
"""
|
||||
if not task.cancelled() and (err := task.exception()) is not None:
|
||||
if not isinstance(err, JobException):
|
||||
_LOGGER.warning("Detached job %s failed: %s", self.name, err)
|
||||
if self._detached_task is task:
|
||||
self._detached_task = None
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import gc
|
||||
from unittest.mock import ANY, AsyncMock, Mock, PropertyMock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -1835,3 +1836,36 @@ async def test_detach_drops_finished_task_reference(coresys: CoreSys):
|
||||
await asyncio.sleep(0)
|
||||
# pylint: disable-next=protected-access
|
||||
assert TestClass.job._detached_task is None
|
||||
|
||||
|
||||
async def test_detach_unawaited_error_is_retrieved(
|
||||
coresys: CoreSys, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test a failed detached task nobody awaits is logged once, not reported by asyncio."""
|
||||
|
||||
class TestClass:
|
||||
"""Test class."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize the test class."""
|
||||
self.coresys = coresys
|
||||
|
||||
@Job(name="test_detach_unawaited_error_is_retrieved_execute", detach=True)
|
||||
async def execute(self) -> None:
|
||||
"""Execute the class method."""
|
||||
raise HassioError("boom")
|
||||
|
||||
test = TestClass(coresys)
|
||||
with patch("asyncio.base_events.logger") as asyncio_logger:
|
||||
task = await test.execute()
|
||||
assert task is not None
|
||||
await asyncio.sleep(0)
|
||||
assert task.done()
|
||||
del task
|
||||
gc.collect()
|
||||
|
||||
asyncio_logger.error.assert_not_called()
|
||||
assert (
|
||||
"Detached job test_detach_unawaited_error_is_retrieved_execute failed: boom"
|
||||
in caplog.text
|
||||
)
|
||||
Reference in new issue
Block a user