recover the gui's D-Bus proxy when the service restarts underneath it #80

Merged
mysticalsoap merged 1 commit from fix/stale-dbus-proxy into trunk 2026-08-20 00:37:42 -04:00
Owner

Closes #73. Stacked on #79 (change/tunnel-state) — the recovery path re-syncs through its get_tunnel_state. Merge order: #79 first.

Problem

The GUI resolved its D-Bus proxy once at startup, and dbus-python pins such a proxy to the unique bus name of whoever owned org.aqomui.service at that moment. A service restart (package upgrade, crash recovery, manual systemctl restart) hands the well-known name to a new unique name, so the next GUI action failed with ServiceUnknown and got misread as "service is not available" — dialog plus a needless polkit-prompted restart of a service that was already up.

Fix

  • get_object(..., follow_name_owner_changes=True): the proxy (and its signal matches) now tracks the well-known name across restarts, so calls after a completed restart just work.
  • A call can still land inside the ownerless restart window. dbus_call now waits up to 5s for the name to have an owner again; if it does, it re-registers what the GUI owns (bypass network info), re-syncs the tunnel state, and retries the call once — silently. The dialog is reserved for a name nobody owns, and its Restart path now runs the same recovery.
  • create_dbus_object is idempotent: it removes its previous signal matches before reconnecting. This also fixes a pre-existing double-subscription — the service-down-at-startup path called create_dbus_object twice (once via initialize_service, once from __init__), leaving every log line handled twice.
  • The startup liveness probe is name_has_owner instead of a throwaway get_object, and initialize_service no longer builds a redundant proxy that create_dbus_object immediately rebuilds.

Verification

  • pytest: 101 passed — new cases cover the silent-recovery path, the one-recovery-per-call bound, the ownerless-name dialog, the owner-wait poll, and reconnects not doubling signal handlers.
  • ruff check --select E9,F63,F7,F82 and compileall -W error::SyntaxWarning clean.
  • Needs live verification before merge (per process): with the GUI running, systemctl restart aqomui → next GUI action should produce no dialog and no polkit prompt, log lines should appear once (not doubled), and with bypass enabled the cgroup rules should survive the restart via the automatic re-register.

🤖 Generated with Claude Code

Closes #73. Stacked on #79 (`change/tunnel-state`) — the recovery path re-syncs through its `get_tunnel_state`. Merge order: #79 first. ## Problem The GUI resolved its D-Bus proxy once at startup, and dbus-python pins such a proxy to the *unique* bus name of whoever owned `org.aqomui.service` at that moment. A service restart (package upgrade, crash recovery, manual `systemctl restart`) hands the well-known name to a new unique name, so the next GUI action failed with `ServiceUnknown` and got misread as "service is not available" — dialog plus a needless polkit-prompted restart of a service that was already up. ## Fix - `get_object(..., follow_name_owner_changes=True)`: the proxy (and its signal matches) now tracks the well-known name across restarts, so calls after a completed restart just work. - A call can still land inside the ownerless restart window. `dbus_call` now waits up to 5s for the name to have an owner again; if it does, it re-registers what the GUI owns (bypass network info), re-syncs the tunnel state, and retries the call once — silently. The dialog is reserved for a name nobody owns, and its Restart path now runs the same recovery. - `create_dbus_object` is idempotent: it removes its previous signal matches before reconnecting. This also fixes a pre-existing double-subscription — the service-down-at-startup path called `create_dbus_object` twice (once via `initialize_service`, once from `__init__`), leaving every log line handled twice. - The startup liveness probe is `name_has_owner` instead of a throwaway `get_object`, and `initialize_service` no longer builds a redundant proxy that `create_dbus_object` immediately rebuilds. ## Verification - pytest: 101 passed — new cases cover the silent-recovery path, the one-recovery-per-call bound, the ownerless-name dialog, the owner-wait poll, and reconnects not doubling signal handlers. - `ruff check --select E9,F63,F7,F82` and `compileall -W error::SyntaxWarning` clean. - **Needs live verification before merge** (per process): with the GUI running, `systemctl restart aqomui` → next GUI action should produce no dialog and no polkit prompt, log lines should appear once (not doubled), and with bypass enabled the cgroup rules should survive the restart via the automatic re-register. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
change: one typed tunnel-state object owned by the service, pushed over a single signal
All checks were successful
ci / test (pull_request) Successful in 28s
9ff0dddb16
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix: recover the gui's D-Bus proxy when the service restarts underneath it
All checks were successful
ci / test (pull_request) Successful in 38s
00ea8f1668
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mysticalsoap force-pushed fix/stale-dbus-proxy from 00ea8f1668
All checks were successful
ci / test (pull_request) Successful in 38s
to 8b79d40350
All checks were successful
ci / test (pull_request) Successful in 28s
ci / test (push) Successful in 26s
2026-08-20 00:20:31 -04:00
Compare
mysticalsoap deleted branch fix/stale-dbus-proxy 2026-08-20 00:37:42 -04:00
Sign in to join this conversation.
No description provided.