Skip to content

Commit e027247

Browse files
committed
fix(updater): fail health check fast, signal busy during boot provisioning, keep overlay through UI restart
1 parent 84f6379 commit e027247

4 files changed

Lines changed: 43 additions & 12 deletions

File tree

‎BlocksScreen/lib/panels/widgets/MainWindow/updatePage.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def __init__(self) -> None:
7272
self._update_avail: bool = False
7373
self._post_update_status_pending: bool = False
7474
self._overlay_shown: bool = False
75+
self._restart_pending: bool = False
7576
self._elapsed_time_seconds: int = 0
7677
self._elapsed_timer: QtCore.QTimer = QtCore.QTimer(self)
7778
self._elapsed_timer.setSingleShot(False)
@@ -335,7 +336,7 @@ def handle_status_ready(self, json_str: str) -> None:
335336
self._update_avail = _update_avail
336337
if not self._busy:
337338
self.show_loading(False)
338-
if self._post_update_status_pending:
339+
if self._post_update_status_pending and not self._restart_pending:
339340
_log.debug("status_ready: emitting call_load_panel(False)")
340341
self.call_load_panel.emit(False, "", False)
341342
self._post_update_status_pending = False
@@ -350,6 +351,7 @@ def handle_busy_changed(self, busy: bool) -> None:
350351
self._busy = busy
351352
self.show_loading(busy)
352353
if busy:
354+
self._restart_pending = False
353355
self._elapsed_time_seconds = 0
354356
self._elapsed_timer.start()
355357
self._busy_timeout_timer.start()
@@ -364,11 +366,21 @@ def handle_busy_changed(self, busy: bool) -> None:
364366
self._progress_label.hide()
365367
self._cancel_btn.hide()
366368
self.update_all_btn.setEnabled(True)
367-
if self._overlay_shown:
369+
if self._restart_pending:
370+
# Keep the overlay up: SIGTERM is imminent, MainWindow would flash.
371+
QtCore.QTimer.singleShot(15000, self._dismiss_after_restart_grace)
372+
elif self._overlay_shown:
368373
self._overlay_shown = False
369374
self.call_load_panel.emit(False, "", False)
370375
self._request_status_debounced()
371376

377+
def _dismiss_after_restart_grace(self) -> None:
378+
"""Drop the overlay if the expected UI restart never came."""
379+
if self._restart_pending and not self._busy:
380+
self._restart_pending = False
381+
self._overlay_shown = False
382+
self.call_load_panel.emit(False, "", False)
383+
372384
@QtCore.pyqtSlot(name="on-update-all-clicked")
373385
def on_update_all_clicked(self) -> None:
374386
"""Guard against updates during a print or with hot heaters; otherwise show confirm dialog."""
@@ -425,6 +437,8 @@ def handle_step_complete(self, name: str, step: int, total: int) -> None:
425437
if self._busy_timeout_timer.isActive():
426438
self._busy_timeout_timer.start()
427439
self._overlay_shown = True
440+
# BlocksScreen's last step restarts this very process.
441+
self._restart_pending = name == "BlocksScreen" and step == total
428442
overlay_msg = f"{name}: {label}"
429443
self._progress_label.setText(f"Step {step}/{total}")
430444
self.call_load_panel.emit(True, overlay_msg, False)

‎updater/dbus_service.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ async def _periodic_status_check(self) -> None:
195195
while True:
196196
try:
197197
await self._emit_status()
198-
if await self._svc.provision_missing():
198+
if await self._svc.provision_missing(self._set_busy):
199199
await self._emit_status() # reflect freshly-installed components
200200
except Exception as exc: # noqa: BLE001
201201
_log.error("periodic_check failed: %s", exc)

‎updater/executor.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,13 +1034,22 @@ def _http_probe(url: str) -> bool:
10341034
conn.close()
10351035

10361036

1037-
async def wait_for_http_ready(url: str, timeout: float = 120.0) -> bool:
1038-
"""Poll a component's loopback health URL until it returns 2xx or timeout."""
1037+
async def wait_for_http_ready(
1038+
url: str, timeout: float = 120.0, *, service: str | None = None
1039+
) -> bool:
1040+
"""Poll a health URL until 2xx or timeout; fail fast if `service` leaves active."""
10391041
deadline = asyncio.get_running_loop().time() + timeout
10401042
while True:
10411043
if await asyncio.to_thread(_http_probe, url):
10421044
logger.info("health check ok: %s", url)
10431045
return True
1046+
# A crash-looping unit is 'activating', never 'active': don't wait out the timeout.
1047+
if (
1048+
service
1049+
and not (await _run([SYSTEMCTL, "is-active", service], timeout=10.0))[0]
1050+
):
1051+
logger.warning("service %r left active during health check", service)
1052+
return False
10441053
if asyncio.get_running_loop().time() >= deadline:
10451054
logger.warning("health check timed out after %.0fs: %s", timeout, url)
10461055
return False

‎updater/service.py‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,10 @@ async def _filter_dead_branch_batch(self, batch: list[ComponentConfig]) -> bool:
476476
)
477477
return ok
478478

479-
async def provision_missing(self) -> bool:
480-
"""Clone absent install_if_missing components at boot (no manual update)."""
479+
async def provision_missing(
480+
self, on_busy: Callable[[bool], None] | None = None
481+
) -> bool:
482+
"""Clone absent install_if_missing components at boot; on_busy brackets the work."""
481483
missing = [
482484
c
483485
for c in self._components
@@ -492,10 +494,16 @@ async def provision_missing(self) -> bool:
492494
if not acquired:
493495
self._log.info("provision_missing: update in progress, deferring")
494496
return False
495-
for c in missing:
496-
if c.path is None or not c.path.exists(): # recheck under lock
497-
await self._provision_component(c)
498-
provisioned = True
497+
if on_busy:
498+
on_busy(True) # UI shows step_complete only while busy
499+
try:
500+
for c in missing:
501+
if c.path is None or not c.path.exists(): # recheck under lock
502+
await self._provision_component(c)
503+
provisioned = True
504+
finally:
505+
if on_busy:
506+
on_busy(False)
499507
return provisioned
500508

501509
async def _preflight_fetch(
@@ -1982,7 +1990,7 @@ async def _restart_one(self, service: str, health_url: str | None = None) -> boo
19821990
if not await wait_for_service_active(service, timeout=90.0):
19831991
self._log.error("%s did not become active after restart", service)
19841992
return False
1985-
if health_url and not await wait_for_http_ready(health_url):
1993+
if health_url and not await wait_for_http_ready(health_url, service=service):
19861994
self._log.error("%s active but health check failed", service)
19871995
return False
19881996
self._log.info("%s active after restart", service)

0 commit comments

Comments
 (0)