The Windows host no longer blocks system sleep while idle #179

Merged
enricobuehler merged 2 commits from worktree-win-sleep-blockers into main 2026-08-12 16:30:13 +00:00
Owner

Field report (2026-08-12): a user's PC stopped sleeping after installing Punktfunk; powercfg -requests showed the Steam Streaming Microphone holding "An audio stream is currently in use."

Root cause

The host-lifetime mic pump (deliberately eager, so the virtual mic exists before games bind their capture device) kept a WASAPI render stream RUNNING on the virtual-mic device for the whole host lifetime, writing silence 24/7 — client connected or not. Any running stream makes the Windows audio stack hold a kernel power request attributed to that device, which vetoes system sleep indefinitely.

Fix

wasapi_mic.rs's render loop now idle-stops the stream after 10 s of silence-only output:

  • IAudioClient::Stop only — the client stays initialized and the microphone endpoint keeps existing for host apps to bind; just the power request is released.
  • The render thread parks on a condvar the producer notifies on the queue's empty→non-empty edge, so the next mic frame resumes (Start) within one device period — below the jitter buffer's prime depth, nothing audible changes.
  • The idle window keys on the queue length staying unchanged, not emptiness: it also covers a stale sub-prime tail a vanished client left behind, while a fresh burst landing exactly at the boundary moves the length and resets the window instead of being dropped.
  • A resume failure (endpoint died while parked) propagates as a thread death → the pump's existing reopen-with-backoff self-healing takes over; no new failure mode.
  • Streaming sessions keep the box awake via their session-scoped DisplayWakeRequest (pf-frame), unchanged.
  • Escape hatch: PUNKTFUNK_MIC_ALWAYS_ON=1 restores the always-running stream in case a third-party virtual audio driver misbehaves while its render side is paused.

Investigated and refuted en route: the desktop-audio loopback capturer does not persist between sessions on Windows (park_audio_capture drops it, restoring the default playback device) — the mic stream was the only no-client sleep blocker.

Verification

  • cargo clippy -p punktfunk-host --all-targets -- -D warnings green on the windows-amd64 runner (.133), after cargo clean -p punktfunk-host and confirming the shipped tree contained the change.
  • rustfmt gates via the repo hooks.
  • Owed (hardware): observe powercfg /requests clear ~10 s after idle on a real box, and confirm recording from the Steam Streaming Microphone yields silence (not a stall) while the render side is stopped — the reason the escape hatch exists.
Field report (2026-08-12): a user's PC stopped sleeping after installing Punktfunk; `powercfg -requests` showed the **Steam Streaming Microphone** holding "An audio stream is currently in use." ## Root cause The host-lifetime mic pump (deliberately eager, so the virtual mic exists before games bind their capture device) kept a WASAPI **render stream RUNNING** on the virtual-mic device for the whole host lifetime, writing silence 24/7 — client connected or not. Any running stream makes the Windows audio stack hold a kernel power request attributed to that device, which vetoes system sleep indefinitely. ## Fix `wasapi_mic.rs`'s render loop now **idle-stops the stream** after 10 s of silence-only output: - `IAudioClient::Stop` only — the client stays initialized and the microphone **endpoint keeps existing** for host apps to bind; just the power request is released. - The render thread parks on a condvar the producer notifies on the queue's empty→non-empty edge, so the next mic frame resumes (`Start`) within one device period — below the jitter buffer's prime depth, nothing audible changes. - The idle window keys on the queue **length staying unchanged**, not emptiness: it also covers a stale sub-prime tail a vanished client left behind, while a fresh burst landing exactly at the boundary moves the length and resets the window instead of being dropped. - A resume failure (endpoint died while parked) propagates as a thread death → the pump's existing reopen-with-backoff self-healing takes over; no new failure mode. - Streaming sessions keep the box awake via their session-scoped `DisplayWakeRequest` (pf-frame), unchanged. - Escape hatch: `PUNKTFUNK_MIC_ALWAYS_ON=1` restores the always-running stream in case a third-party virtual audio driver misbehaves while its render side is paused. Investigated and refuted en route: the desktop-audio loopback capturer does **not** persist between sessions on Windows (`park_audio_capture` drops it, restoring the default playback device) — the mic stream was the only no-client sleep blocker. ## Verification - `cargo clippy -p punktfunk-host --all-targets -- -D warnings` green on the windows-amd64 runner (.133), after `cargo clean -p punktfunk-host` and confirming the shipped tree contained the change. - rustfmt gates via the repo hooks. - Owed (hardware): observe `powercfg /requests` clear ~10 s after idle on a real box, and confirm recording from the Steam Streaming Microphone yields silence (not a stall) while the render side is stopped — the reason the escape hatch exists.
enricobuehler added 2 commits 2026-08-12 15:44:56 +00:00
The mic pump's WASAPI backend rendered silence into the virtual mic's
render endpoint (typically the Steam Streaming Microphone) for the whole
host lifetime. A RUNNING stream makes the Windows audio stack hold a
kernel power request ("An audio stream is currently in use", visible in
powercfg /requests), so every idle Punktfunk host blocked system sleep
forever — field-reported 2026-08-12 ("doesn't go to sleep anymore since
installing punktfunk; powercfg shows the Steam Streaming Microphone").

Stop the render stream (IAudioClient::Stop — client stays initialized,
the mic endpoint keeps existing) after 10 s of silence-only output at an
unchanged queue length, and park the render thread on a condvar the
producer notifies on the empty->non-empty edge, so the next mic frame
resumes the stream within one device period — well under the jitter
buffer's prime depth, so nothing audible changes. Keying the idle window
on the queue LENGTH (not emptiness) also covers a sub-prime tail a
vanished client left behind, while any fresh burst moves the length and
resets the window instead of being dropped.

During a session the box stays awake through the session's own
DisplayWakeRequest (pf-frame), never through this silence.
PUNKTFUNK_MIC_ALWAYS_ON=1 restores the old always-running stream in case
a virtual audio driver misbehaves while its render side is paused.
docs(changelog): the Windows host no longer blocks system sleep while idle
ci / rust (pull_request) Failing after 1m1s
ci / web (pull_request) Successful in 1m7s
ci / bun-nix (pull_request) Successful in 27s
ci / rust-arm64 (pull_request) Successful in 6m17s
ci / docs-site (pull_request) Successful in 1m35s
apple / swift (pull_request) Successful in 1m42s
apple / screenshots (pull_request) Skipped
android / android (pull_request) Successful in 9m51s
ef0af3b558
enricobuehler merged commit 90a3304c2b into main 2026-08-12 16:30:13 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#179