Skip to content

Commit b12d676

Browse files
committed
Use a validation_complete event rather then looking for bus events
1 parent 54e6732 commit b12d676

File tree

9 files changed

+67
-79
lines changed

9 files changed

+67
-79
lines changed

supervisor/addons/manager.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ async def shutdown(self, stage: AddonStartup) -> None:
184184
on_condition=AddonsJobError,
185185
concurrency=JobConcurrency.QUEUE,
186186
)
187-
async def install(self, slug: str) -> None:
187+
async def install(
188+
self, slug: str, *, validation_complete: asyncio.Event | None = None
189+
) -> None:
188190
"""Install an add-on."""
189191
self.sys_jobs.current.reference = slug
190192

@@ -197,6 +199,10 @@ async def install(self, slug: str) -> None:
197199

198200
store.validate_availability()
199201

202+
# If being run in the background, notify caller that validation has completed
203+
if validation_complete:
204+
validation_complete.set()
205+
200206
await Addon(self.coresys, slug).install()
201207

202208
_LOGGER.info("Add-on '%s' successfully installed", slug)
@@ -226,7 +232,11 @@ async def uninstall(self, slug: str, *, remove_config: bool = False) -> None:
226232
on_condition=AddonsJobError,
227233
)
228234
async def update(
229-
self, slug: str, backup: bool | None = False
235+
self,
236+
slug: str,
237+
backup: bool | None = False,
238+
*,
239+
validation_complete: asyncio.Event | None = None,
230240
) -> asyncio.Task | None:
231241
"""Update add-on.
232242
@@ -251,6 +261,10 @@ async def update(
251261
# Check if available, Maybe something have changed
252262
store.validate_availability()
253263

264+
# If being run in the background, notify caller that validation has completed
265+
if validation_complete:
266+
validation_complete.set()
267+
254268
if backup:
255269
await self.sys_backups.do_backup_partial(
256270
name=f"addon_{addon.slug}_{addon.version}",

supervisor/api/backups.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@
4545
ATTR_TYPE,
4646
ATTR_VERSION,
4747
REQUEST_FROM,
48-
BusEvent,
49-
CoreState,
5048
)
5149
from ..coresys import CoreSysAttributes
5250
from ..exceptions import APIError, APIForbidden, APINotFound
@@ -74,8 +72,6 @@
7472
# Remove: 2022.08
7573
_ALL_FOLDERS = ALL_FOLDERS + [FOLDER_HOMEASSISTANT]
7674

77-
_freeze_state_filter = lambda new_state: new_state == CoreState.FREEZE
78-
7975

8076
def _ensure_list(item: Any) -> list:
8177
"""Ensure value is a list."""
@@ -308,11 +304,7 @@ async def backup_full(self, request: web.Request):
308304

309305
background = body.pop(ATTR_BACKGROUND)
310306
backup_task, job_id = await background_task(
311-
self,
312-
self.sys_backups.do_backup_full,
313-
bus_event=BusEvent.SUPERVISOR_STATE_CHANGE,
314-
event_filter=_freeze_state_filter,
315-
**body,
307+
self, self.sys_backups.do_backup_full, **body
316308
)
317309

318310
if background and not backup_task.done():
@@ -348,11 +340,7 @@ async def backup_partial(self, request: web.Request):
348340

349341
background = body.pop(ATTR_BACKGROUND)
350342
backup_task, job_id = await background_task(
351-
self,
352-
self.sys_backups.do_backup_partial,
353-
bus_event=BusEvent.SUPERVISOR_STATE_CHANGE,
354-
event_filter=_freeze_state_filter,
355-
**body,
343+
self, self.sys_backups.do_backup_partial, **body
356344
)
357345

358346
if background and not backup_task.done():
@@ -376,12 +364,7 @@ async def restore_full(self, request: web.Request):
376364
)
377365
background = body.pop(ATTR_BACKGROUND)
378366
restore_task, job_id = await background_task(
379-
self,
380-
self.sys_backups.do_restore_full,
381-
backup,
382-
bus_event=BusEvent.SUPERVISOR_STATE_CHANGE,
383-
event_filter=_freeze_state_filter,
384-
**body,
367+
self, self.sys_backups.do_restore_full, backup, **body
385368
)
386369

387370
if background and not restore_task.done() or await restore_task:
@@ -401,12 +384,7 @@ async def restore_partial(self, request: web.Request):
401384
)
402385
background = body.pop(ATTR_BACKGROUND)
403386
restore_task, job_id = await background_task(
404-
self,
405-
self.sys_backups.do_restore_partial,
406-
backup,
407-
bus_event=BusEvent.SUPERVISOR_STATE_CHANGE,
408-
event_filter=_freeze_state_filter,
409-
**body,
387+
self, self.sys_backups.do_restore_partial, backup, **body
410388
)
411389

412390
if background and not restore_task.done() or await restore_task:

supervisor/api/homeassistant.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ async def update(self, request: web.Request) -> dict[str, str] | None:
181181
update_task, job_id = await background_task(
182182
self,
183183
self.sys_homeassistant.core.update,
184-
job_names={"docker_interface_update", "backup_manager_partial_backup"},
185184
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
186185
backup=body.get(ATTR_BACKUP),
187186
)

supervisor/api/store.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ async def addons_addon_install(self, request: web.Request) -> dict[str, str] | N
233233
background = body[ATTR_BACKGROUND]
234234

235235
install_task, job_id = await background_task(
236-
self, self.sys_addons.install, addon.slug, job_names={"addon_install"}
236+
self, self.sys_addons.install, addon.slug
237237
)
238238

239239
if background and not install_task.done():
@@ -255,7 +255,6 @@ async def addons_addon_update(self, request: web.Request) -> dict[str, str] | No
255255
self,
256256
self.sys_addons.update,
257257
addon.slug,
258-
job_names={"backup_manager_partial_backup", "addon_update"},
259258
backup=body.get(ATTR_BACKUP),
260259
)
261260

supervisor/api/utils.py

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
REQUEST_FROM,
2323
RESULT_ERROR,
2424
RESULT_OK,
25-
BusEvent,
2625
)
2726
from ..coresys import CoreSys, CoreSysAttributes
2827
from ..exceptions import APIError, BackupFileNotFoundError, DockerAPIError, HassioError
@@ -208,19 +207,13 @@ async def background_task(
208207
coresys_obj: CoreSysAttributes,
209208
task_method: Callable,
210209
*args,
211-
bus_event: BusEvent = BusEvent.SUPERVISOR_JOB_START,
212-
event_filter: Callable[[Any], bool] | None = None,
213-
job_names: set[str] | None = None,
214210
**kwargs,
215211
) -> tuple[asyncio.Task, str]:
216212
"""Start task in background and return task and job ID.
217213
218214
Args:
219215
coresys_obj: Instance that accesses coresys data using CoreSysAttributes
220-
task_method: The method to execute in the background
221-
bus_event: Event type to listen for which any initial validation has passed
222-
event_filter: Function to determine if the event is the one specific to the job by the data
223-
job_names: Alternative bus_event and event_filter. Validation considered passed when a named child job of primary one starts
216+
task_method: The method to execute in the background. Must include a keyword arg 'validation_complete' of type asyncio.Event. Should set it after any initial validation has completed
224217
*args: Arguments to pass to task_method
225218
**kwargs: Keyword arguments to pass to task_method
226219
@@ -232,36 +225,23 @@ async def background_task(
232225
job, task = cast(
233226
tuple[SupervisorJob, asyncio.Task],
234227
coresys_obj.sys_jobs.schedule_job(
235-
task_method, JobSchedulerOptions(), *args, **kwargs
228+
task_method,
229+
JobSchedulerOptions(),
230+
*args,
231+
validation_complete=event,
232+
**kwargs,
236233
),
237234
)
238235

239-
if job_names:
240-
241-
def child_job_filter(job_event: SupervisorJob) -> bool:
242-
"""Return true if job is a child of main job and name is in set."""
243-
return job_event.parent_id == job.uuid and job_event.name in job_names
244-
245-
event_filter = child_job_filter
246-
247-
async def release_on_job_start(event_data: Any):
248-
"""Release if filter passes or no filter is provided."""
249-
if not event_filter or event_filter(event_data):
250-
event.set()
251-
252236
# Wait for provided event before returning
253237
# If the task fails validation it should raise before getting there
254-
listener = coresys_obj.sys_bus.register_event(bus_event, release_on_job_start)
255-
try:
256-
event_task = coresys_obj.sys_create_task(event.wait())
257-
_, pending = await asyncio.wait(
258-
(task, event_task),
259-
return_when=asyncio.FIRST_COMPLETED,
260-
)
261-
# It seems task returned early (error or something), make sure to cancel
262-
# the event task to avoid "Task was destroyed but it is pending!" errors.
263-
if event_task in pending:
264-
event_task.cancel()
265-
return (task, job.uuid)
266-
finally:
267-
coresys_obj.sys_bus.remove_listener(listener)
238+
event_task = coresys_obj.sys_create_task(event.wait())
239+
_, pending = await asyncio.wait(
240+
(task, event_task),
241+
return_when=asyncio.FIRST_COMPLETED,
242+
)
243+
# It seems task returned early (error or something), make sure to cancel
244+
# the event task to avoid "Task was destroyed but it is pending!" errors.
245+
if event_task in pending:
246+
event_task.cancel()
247+
return (task, job.uuid)

supervisor/backups/manager.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ async def do_backup_full(
598598
homeassistant_exclude_database: bool | None = None,
599599
extra: dict | None = None,
600600
additional_locations: list[LOCATION_TYPE] | None = None,
601+
validation_complete: asyncio.Event | None = None,
601602
) -> Backup | None:
602603
"""Create a full backup."""
603604
await self._check_location(location)
@@ -614,6 +615,10 @@ async def do_backup_full(
614615
name, filename, BackupType.FULL, password, compressed, location, extra
615616
)
616617

618+
# If being run in the background, notify caller that validation has completed
619+
if validation_complete:
620+
validation_complete.set()
621+
617622
_LOGGER.info("Creating new full backup with slug %s", new_backup.slug)
618623
backup = await self._do_backup(
619624
new_backup,
@@ -648,6 +653,7 @@ async def do_backup_partial(
648653
homeassistant_exclude_database: bool | None = None,
649654
extra: dict | None = None,
650655
additional_locations: list[LOCATION_TYPE] | None = None,
656+
validation_complete: asyncio.Event | None = None,
651657
) -> Backup | None:
652658
"""Create a partial backup."""
653659
await self._check_location(location)
@@ -684,6 +690,10 @@ async def do_backup_partial(
684690
continue
685691
_LOGGER.warning("Add-on %s not found/installed", addon_slug)
686692

693+
# If being run in the background, notify caller that validation has completed
694+
if validation_complete:
695+
validation_complete.set()
696+
687697
backup = await self._do_backup(
688698
new_backup,
689699
addon_list,
@@ -817,8 +827,10 @@ async def _validate_backup_location(
817827
async def do_restore_full(
818828
self,
819829
backup: Backup,
830+
*,
820831
password: str | None = None,
821832
location: str | None | type[DEFAULT] = DEFAULT,
833+
validation_complete: asyncio.Event | None = None,
822834
) -> bool:
823835
"""Restore a backup."""
824836
# Add backup ID to job
@@ -838,6 +850,10 @@ async def do_restore_full(
838850
_LOGGER.error,
839851
)
840852

853+
# If being run in the background, notify caller that validation has completed
854+
if validation_complete:
855+
validation_complete.set()
856+
841857
_LOGGER.info("Full-Restore %s start", backup.slug)
842858
await self.sys_core.set_state(CoreState.FREEZE)
843859

@@ -876,11 +892,13 @@ async def do_restore_full(
876892
async def do_restore_partial(
877893
self,
878894
backup: Backup,
895+
*,
879896
homeassistant: bool = False,
880897
addons: list[str] | None = None,
881898
folders: list[str] | None = None,
882899
password: str | None = None,
883900
location: str | None | type[DEFAULT] = DEFAULT,
901+
validation_complete: asyncio.Event | None = None,
884902
) -> bool:
885903
"""Restore a backup."""
886904
# Add backup ID to job
@@ -908,6 +926,10 @@ async def do_restore_partial(
908926
_LOGGER.error,
909927
)
910928

929+
# If being run in the background, notify caller that validation has completed
930+
if validation_complete:
931+
validation_complete.set()
932+
911933
_LOGGER.info("Partial-Restore %s start", backup.slug)
912934
await self.sys_core.set_state(CoreState.FREEZE)
913935

supervisor/homeassistant/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ async def update(
229229
self,
230230
version: AwesomeVersion | None = None,
231231
backup: bool | None = False,
232+
validation_complete: asyncio.Event | None = None,
232233
) -> None:
233234
"""Update HomeAssistant version."""
234235
to_version = version or self.sys_homeassistant.latest_version
@@ -248,6 +249,10 @@ async def update(
248249
f"Version {to_version!s} is already installed", _LOGGER.warning
249250
)
250251

252+
# If being run in the background, notify caller that validation has completed
253+
if validation_complete:
254+
validation_complete.set()
255+
251256
if backup:
252257
await self.sys_backups.do_backup_partial(
253258
name=f"core_{self.instance.version}",

tests/api/test_homeassistant.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from supervisor.homeassistant.api import APIState
1515
from supervisor.homeassistant.core import HomeAssistantCore
1616
from supervisor.homeassistant.module import HomeAssistant
17-
from supervisor.jobs.decorator import _JOB_NAMES, Job
1817

1918
from tests.api import common_test_api_advanced_logs
2019
from tests.common import load_json_fixture
@@ -207,18 +206,15 @@ async def test_home_assistant_background_update(
207206
):
208207
"""Test background update of Home Assistant."""
209208
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
210-
_JOB_NAMES.remove("docker_interface_update")
211-
_JOB_NAMES.remove("backup_manager_partial_backup")
212209
event = asyncio.Event()
213210
mock_update_called = mock_backup_called = False
214211

215-
@Job(name="docker_interface_update")
212+
# Mock backup/update as long-running tasks
216213
async def mock_docker_interface_update(*args, **kwargs):
217214
nonlocal mock_update_called
218215
mock_update_called = True
219216
await event.wait()
220217

221-
@Job(name="backup_manager_partial_backup")
222218
async def mock_partial_backup(*args, **kwargs):
223219
nonlocal mock_backup_called
224220
mock_backup_called = True

tests/api/test_store.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from supervisor.docker.const import ContainerState
1919
from supervisor.docker.interface import DockerInterface
2020
from supervisor.docker.monitor import DockerContainerStateEvent
21-
from supervisor.jobs.decorator import _JOB_NAMES, Job
2221
from supervisor.store.addon import AddonStore
2322
from supervisor.store.repository import Repository
2423

@@ -398,10 +397,9 @@ async def test_api_store_addons_changelog_corrupted(
398397
async def test_addon_install_in_background(api_client: TestClient, coresys: CoreSys):
399398
"""Test installing an addon in the background."""
400399
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
401-
_JOB_NAMES.remove("addon_install")
402400
event = asyncio.Event()
403401

404-
@Job(name="addon_install")
402+
# Mock a long-running install task
405403
async def mock_addon_install(*args, **kwargs):
406404
await event.wait()
407405

@@ -448,18 +446,15 @@ async def test_addon_update_in_background(
448446
"""Test updating an addon in the background."""
449447
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
450448
install_addon_ssh.data_store["version"] = "10.0.0"
451-
_JOB_NAMES.remove("addon_update")
452-
_JOB_NAMES.remove("backup_manager_partial_backup")
453449
event = asyncio.Event()
454450
mock_update_called = mock_backup_called = False
455451

456-
@Job(name="addon_update")
452+
# Mock backup/update as long-running tasks
457453
async def mock_addon_update(*args, **kwargs):
458454
nonlocal mock_update_called
459455
mock_update_called = True
460456
await event.wait()
461457

462-
@Job(name="backup_manager_partial_backup")
463458
async def mock_partial_backup(*args, **kwargs):
464459
nonlocal mock_backup_called
465460
mock_backup_called = True

0 commit comments

Comments
 (0)