exlab_wizard.tray.quit_coordinator#

Graceful tray shutdown coordinator. Backend Spec §4.3.2.

Steps documented in §4.3.2 (canonical):

  1. Send the FastAPI lifespan shutdown signal; the server stops accepting new requests and POST /api/v1/sessions returns 503 with error.code: "shutting_down".

  2. Wait up to 30 seconds (5 seconds for SIGTERM at logoff, since the OS will hard-kill the process anyway) for the predicate SessionStore.active_sessions == 0 AND NASSyncClient.in_flight_jobs == 0.

  3. If the predicate becomes true within the window, exit cleanly.

  4. If the timeout expires, prompt the operator with “1 operation still running. Force quit anyway?” via the open window if alive, otherwise via an OS notification. Operator picks Force quit (immediate shutdown; durable NAS-sync queue resumes on next launch) or Wait (resets the 30-second timer).

The coordinator is async because the predicate poll uses asyncio.sleep and the shutdown handoff to ServerRunner.stop naturally fits an async cleanup point. Tests can pass a 0-second timeout to exercise the timeout branch deterministically.

Classes

QuitCoordinator(*, server_runner, ...[, ...])

Drive the §4.3.2 graceful-shutdown protocol.

class exlab_wizard.tray.quit_coordinator.QuitCoordinator(*, server_runner, window_launcher, session_store, nas_sync, on_force_quit_prompt=None, timeout_seconds=30.0, sigterm_timeout_seconds=5.0, poll_interval_seconds=0.1)[source]#

Bases: object

Drive the §4.3.2 graceful-shutdown protocol.

Construction-time dependencies are kept loosely typed so the coordinator can integrate with whatever stub fixtures unit tests pass. The runtime contract is documented per parameter.

Parameters:
async quit(*, sigterm=False)[source]#

Run the graceful-shutdown protocol.

sigterm=True reduces the wait window to sigterm_timeout (5 s by default) because the OS will hard-kill the process shortly anyway.

Parameters:

sigterm (bool)

Return type:

None