feat: wire OS sleep/wake power events (suspend-edge session snapshot)#97
Open
pmaxhogan wants to merge 1 commit into
Open
feat: wire OS sleep/wake power events (suspend-edge session snapshot)#97pmaxhogan wants to merge 1 commit into
pmaxhogan wants to merge 1 commit into
Conversation
Closes #33. Adds per-OS suspend/resume notifications and wires the orchestrator to run the DESIGN s5.10.2 / s5.10.3 sequences at the suspend/ resume edge instead of waiting up to 30s for the power poll. driven-power: - SleepWakeEvent edge type + PowerSource::subscribe_sleep_wake (default inert receiver) + a uniform SleepWakeMonitor RAII teardown handle. - Windows: PowerRegisterSuspendResumeNotification + DEVICE_NOTIFY_CALLBACK (no window / message pump); maps PBT_APMSUSPEND/RESUME* to edges. - macOS: IOKit IORegisterForSystemPower on a dedicated CFRunLoop thread (kIOMessageSystemWillSleep / HasPoweredOn; acks CanSystemSleep). - Linux: logind PrepareForSleep DBus signal via zbus #[proxy] stream (zbus on the tokio reactor). driven-core: the run loop consumes the sleep/wake edge stream, calls on_power_event, and fires the deferred complete_resume after the 30s defer. app-shell: assembly starts the monitor per account; AccountHandle stores it and tears it down on quit (no orphaned OS handle / thread / task). Tested: Windows backend verified on-host (real registration + callback -> broadcast path); a run-loop integration test drives suspend -> resume -> Wake cycle via FakePowerSource. macOS/Linux backends are compile-only here (implementer on Windows) but cross-checked + clippy-clean via `cargo check/clippy --target {x86_64-apple-darwin,x86_64-unknown-linux-gnu}`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014fLmkjpFkiuky72eLBijL4
Contributor
Coverage
Gate: passed - no coverage regression (epsilon 0.1 pp). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #33.
What
Wires the OS suspend/resume power events that DESIGN s5.10 assumed but that were left as per-OS TODO seams. The orchestrator already had the
on_power_event/complete_resumelogic (and unit tests); nothing produced the edges in production, so the app relied on the 30s power poll (which never runs the resume sequence). Now the suspend/resume edge is detected at the edge and the s5.10.2 / s5.10.3 sequences run.Per-platform backend choices
PowerRegisterSuspendResumeNotificationwithDEVICE_NOTIFY_CALLBACK. The system calls our callback directly - no hidden window or message pump. MapsPBT_APMSUSPEND-> Suspending,PBT_APMRESUMEAUTOMATIC/PBT_APMRESUMESUSPEND-> Resumed.IORegisterForSystemPoweron a dedicatedCFRunLoopthread;kIOMessageSystemWillSleep-> Suspending,kIOMessageSystemHasPoweredOn-> Resumed, and we ackkIOMessageCanSystemSleep/ will-sleep viaIOAllowPowerChangeso the system never blocks on us. Chosen overNSWorkspacebecause it is the documented C sleep/wake API, matches the existing IOKit power reader + the Windows callback shape, and needs no AppKit main-thread run loop (structurally identical to the tested Windows path).org.freedesktop.login1.Manager.PrepareForSleep(bool)via azbus#[proxy]-generated signal stream (start=true-> Suspending,false-> Resumed).zbusis pinned to the tokio reactor to match the app runtime.Plumbing
driven-power: newSleepWakeEventedge type,PowerSource::subscribe_sleep_wake(default inert receiver so no impl breaks), and a single uniformSleepWakeMonitorRAII handle whose per-OS teardown unregisters / stops-and-joins / aborts on drop orstop().driven-core: the run loop subscribes to the edge stream, maps toPowerEvent, callson_power_event, and fires the deferredcomplete_resumeafter the 30s defer via a fixed-instant timer arm (inline, so it respects the single-in-flight guard). A suspend supersedes a stale pending resume.assembly::build_accountstarts the monitor per account;AccountHandleholds it and tears it down on quit (no orphaned OS handle / thread / task), mirroring the existing power-poller bookkeeping.FakePowerSource::push_sleep_wakelets tests drive edges deterministically.Empirically tested vs compile-only
PDEVICE_NOTIFY_CALLBACK_ROUTINEmapping suspend/resume codes to a broadcast a subscriber receives (a real machine-suspend cannot run in an automated test, so the callback is invoked directly with the OS codes); the purePBT_*mapping; and adriven-corerun-loop integration test that pushes Suspending -> Resumed throughFakePowerSourceand asserts the loop broadcasts the Power events and completes a from-scratch Wake cycle to Idle after the defer.cargo check/cargo clippy --target x86_64-apple-darwinand--target x86_64-unknown-linux-gnu(type-check without linking); CI's 3-OS matrix is the runtime gate. Their pure mapping fns + the subscribe/broadcast plumbing are unit-tested on every OS.Tests added
driven-power: Windows PBT mapping, callback->broadcast, real register/teardown lifecycle; macOS IOKit-message mapping + subscribe delivery; Linux PrepareForSleep mapping + subscribe delivery.driven-test-fixtures:push_sleep_wakereaches a subscriber.driven-core: run loop consumes sleep/wake edges and completes the deferred resume.Notes / deferred
pending_ops.payload_json(PersistedResumable{session,acked_offset}) and stale sessions are discarded by the executor's existing 6-day age check, so the "session snapshot" requirement is satisfied by that continuous persistence; this PR adds the missing edge detection + resume sequence that drives it.RefreshingTokenSourcerefreshes on-demand on the first post-wake request. The "Suspending..." tray tooltip (s5.10.2 cosmetic) is not wired (the event bridge still ignoresPowerevents) - deferred as a nicety.🤖 Generated with Claude Code
https://claude.ai/code/session_014fLmkjpFkiuky72eLBijL4