fix: preserve GPUI lifetime and avoid WASAPI silence warnings

This commit is contained in:
2026-10-13 18:58:37 +02:00
parent d065ad7775
commit 072a4ab7ca
17 changed files with 470 additions and 188 deletions
+116 -29
View File
@@ -8,7 +8,7 @@ pass/fail results.
| Where | Script | Status |
| --- | --- | --- |
| Linux, headless rig (Wayland portal + Xwayland) | `scripts/validate/linux-rig.sh` | All 10 scenarios pass on devbox (Intel UHD 770, VA-API), 2026-10-13 |
| Linux, headless rig (Wayland portal + Xwayland) | `scripts/validate/linux-rig.sh` | All 12 scenarios pass on devbox (Intel UHD 770, VA-API), 2026-10-13 |
| Linux, real session (CachyOS, Hyprland, RX 9070 XT, 1440p60) | `scripts/validate/linux-hardware.sh` | Passed 2026-10-08 (run 4, dc9cbd6) |
| Linux desktop automatic sessions (headless Wayland) | `scripts/validate/linux-session.sh` | Passed 2026-10-13: recording sidebar, close-to-tray, game exit, second-launch reopen and active Quit |
| Windows (Game Capture, NVENC/AMF/QSV) | `scripts/validate/windows.ps1` | Not run yet; script untested |
@@ -175,9 +175,10 @@ Windows status:
record portrait). If the window is resized mid-session the size stays and
the picture is scaled to fit.
- **Audio delivery health.** The configured system/microphone capture sources
are monitored directly through OBS audio capture callbacks. After five seconds
without delivery, recording is degraded with “audio capture lost”; video
continues. The silent muxer track cannot mask absent source callbacks. See
are monitored through OBS audio capture callbacks on Linux (five seconds
without delivery) and source-scoped WASAPI device errors on Windows. Either
degrades recording with “audio capture lost”; video continues. The silent
muxer track cannot mask capture failure. See
Background operation below for validation and limits.
- **Linux runtime dependencies.** The Linux OBS build links the system's
FFmpeg, PipeWire and other libraries, so it is tied to the distribution it
@@ -258,15 +259,16 @@ manifest ("Escape from Duckov") and recorded the session. Audio delivery loss
is now detected as described below; the new warning still needs real hardware
and Windows validation.
GPUI 0.2.2 ends its Linux and Windows event loops when the last window is
removed. Clipbin keeps the recorder, tray and single-instance endpoint alive
outside each GPUI run, and constructs a fresh Application on the main thread
when reopened. The previous route is restored and the library/settings are
reloaded; unavailable sessions fall back to the library. This also releases
playback resources while the interface is closed. No fake or hidden GPUI window
is kept alive: GPUI's Linux backends ignore WindowOptions::show. Window bounds,
scroll offsets and unsaved editor selection are not restored. Explicit Quit
waits for the existing worker's clean session finish before joining/exiting.
GPUI 0.2.2 normally ends its Linux and Windows event loops when the last
window is removed. Clipbin now keeps one Application and native loop alive
until explicit Quit, with a small vendored backend policy patch (see
[vendor/gpui/CLIPBIN.md](../vendor/gpui/CLIPBIN.md)). The visible window is
removed on close and recreated on Open, restoring its previous route and
reloading the library/settings; unavailable sessions fall back to the library.
Playback resources are released while closed. No invisible GPUI window is
needed. Bounds, scroll offsets and unsaved editor selection are not restored.
Quit waits for the existing worker's clean session finish before joining/exiting.
## Clips, bookmarks and storage cleanup
@@ -547,22 +549,42 @@ on every configured system/microphone source, independently of the encoder,
mixed audio counters and silent output track. A boxed callback context stores
an atomic monotonic delivery timestamp; it performs no allocation or locking.
The context is removed before source release: OBS's audio_cb_mutex serialises
callback removal with callbacks. Five seconds without delivery (including a
source that never starts) is a warning-level RecordingStatus::Degraded,
callback removal with callbacks. On Linux, five seconds without delivery
(including a source that never starts) is a warning-level RecordingStatus::Degraded,
with “audio capture lost” in the sidebar and tray. A new callback clears the
warning; video/source/output/disk failures still take precedence and stop
recording. Intentionally disabled audio has no callback and no warning. This
checks delivery, not amplitude: valid silent samples are healthy. It cannot
detect a driver delivering endless synthetic silence, and WASAPI silence or
idle-device callback behaviour needs a Windows runtime check. No automatic
source restart is added; video continues and audio can recover if OBS resumes
its source. Source identity is not displayed separately for system vs mic loss.
recording. Disabled audio has no callback and no warning. Valid silent samples
are healthy; an endless synthetic silence stream cannot reveal a driver failure.
Windows does not infer loss from absent buffers. In the pinned
[OBS 32.0.4 win-wasapi source](https://github.com/obsproject/obs-studio/blob/32.0.4/plugins/win-wasapi/win-wasapi.cpp),
WASAPISource::Initialize calls ClearBuffer before InitCapture for DeviceOutput. ClearBuffer
creates a temporary shared renderer and fills its buffer with silence, but
neither starts nor retains that renderer. ProcessCaptureData accepts silent
packets and exits normally when GetNextPacketSize reports no packets. This is
not evidence of uninterrupted callbacks during idle loopback capture. Instead,
Clipbin tracks the plugin's source-scoped “failed to start” and device
“invalidated. Retrying” logs; successful initialization or a delivered capture
buffer clears the warning. The callback remains allocation/lock free; the log
handler separately updates weak registered fault flags. Flags are registered
before source creation and unregistered on cleanup, so unrelated/stale sources
cannot affect a recording. The parser has a unit test. Windows failures that
neither deliver buffers nor produce these pinned log messages remain undetected;
this is preferable to reporting ordinary silence as lost capture. Actual WASAPI
silence, mute, unplug/recovery and changed-device behavior require Windows runtime
validation. No automatic source restart is added; OBS's recovery can clear the
warning. Source identity is not displayed separately for system vs mic loss.
The rig's audio failure injection kills its private pipewire-pulse daemon,
leaving PipeWire video and the portal alive. Unloading the null sink and
requesting kill-source-output did not reliably stop delivery in PipeWire 1.6.8;
killing that private audio server deterministically stops source callbacks.
The original nine scenarios are retained alongside this tenth warning case.
The original nine scenarios are retained alongside audio loss, idle silence
and muted-microphone cases (twelve total). Silence cases omit the ffplay tone,
assert there are no playback streams, wait twelve seconds and require the null
sink to be SUSPENDED before capture. They then record for sixteen seconds with
no degraded/failed status. The muted-mic case selects the null sink monitor as
the default input and mutes it; physical microphone hardware remains untested.
The rig now uses an explicit private SWAYSOCK and waits for it; discovery by
the previous sway-ipc glob did not locate the installed Sway 1.12 endpoint.
CLIPBIN_RIG_KEEP_LOGS also preserves startup-failure logs.
@@ -573,10 +595,11 @@ callback registration/removal. Nothing new has been linked or run on Windows.
GPUI desktop and clipbin-media remain excluded from that lint set because the
MSVC C toolchain/FFmpeg development import libraries are unavailable here.
Unvalidated: tray rendering/menu focus/Explorer restart, icon/tooltips, singleton
startup races and multi-user/desktop behaviour, foreground activation, repeated
GPUI Application creation after close, clean Quit/file closure, --background,
registry writes and real login launch, WASAPI delivery-loss/recovery/idle
semantics, and the Clips layout on Windows. Real Linux desktop trays (Hyprland
startup races and multi-user/desktop behaviour, foreground activation,
GPUI persistent-loop policy (including the vendored Windows backend, which is
not cross-linted), window recreation, clean Quit/file closure, --background,
registry writes and real login launch, WASAPI error-log/recovery/idle semantics, and the Clips layout on Windows.
Real Linux desktop trays (Hyprland
Waybar, KDE, GNOME extensions), login launch, SNI host replacement/re-registration
and native X11 tray lifecycle still need runtime validation.
@@ -646,6 +669,70 @@ commit: removed the obsolete window-close shutdown mode, reused existing clip,
bookmark and clean-finish paths, avoided copying the full library for tray
polling, limited icon updates to state changes, kept callbacks allocation/lock
free, and made native tray/thread cleanup explicit. The text helper is limited
to the demonstrated GPUI 0.2.2 rendering bug; no dependency fork or general UI
framework was added. Native Windows runtime and real desktop limits above
remain unchanged.
to the demonstrated GPUI 0.2.2 rendering bug. The subsequent lifecycle review
required the small vendored GPUI policy patch described below. Native Windows
runtime and real desktop limits above remain unchanged.
## Background review follow-up (2026-10-13)
The first five-cycle close/Open test exposed a leak in repeated Application
creation: process thread counts grew 99, 114, 129, 144, 159, 174. GPUI's
LinuxDispatcher creates a worker pool and Timer per platform; the Timer ignores
channel closure and keeps running. This invalidates repeated-run ownership even
though each reopened window worked. Keeping one Application removes the repeated
platform construction. The final five-cycle test measured 71 threads initially
and 69 after every reopen; RSS was 243228, 242580, 247344, 243032, 243136,
243364 KiB. The rig saves reopen-metrics.tsv and asserts bounded thread/RSS growth.
The pinned Windows source was also examined: WindowsPlatform::new calls
OleInitialize; Drop destroys the message-only platform window and calls
OleUninitialize. The platform RegisterClassW result is ignored; the visible
window class uses Once, so repeated class registration itself is not a panic.
The VSync thread uses weak platform state and can exit on drop, and Windows
uses the OS thread pool rather than Linux's owned worker pool. Both dispatchers,
however, forget scheduled runnables when the main channel has closed, assuming
process shutdown. Repeated Application lifetimes are therefore not established
as safe on Windows. Clipbin avoids them entirely: fonts, dispatcher, OLE,
classes, DirectX state and native loop initialize once. The Windows last-window
policy is patched in the same backend decision as Linux; its runtime/build
remains unvalidated because the complete GPUI desktop cannot be cross-checked
here. The unmodified published dependency is retained with its Apache license;
clipbin-background.patch records the complete source delta. The only additional
compatibility change gives two taffy float literals explicit f32 types for
Rust 1.98's strict lint.
Follow-up artifacts live under target/background-review; reopen-before and
reopen-after contain lifecycle metrics. The silent-monitor experiment also
passed with the sink initially suspended. An initial muted-mic experiment used
an absent default input and correctly degraded; the committed fixture explicitly
selects a real monitor source before muting it.
Follow-up validation (all exit 0, 2026-10-13):
```sh
env PATH=/usr/bin:/bin:$HOME/.cargo/bin cargo fmt --all -- --check
env PATH=/usr/bin:/bin:$HOME/.cargo/bin cargo clippy --locked --workspace --all-targets -- -D warnings
env PATH=/usr/bin:/bin:$HOME/.cargo/bin cargo test --locked --workspace
env RUSTUP_HOME=$HOME/.cache/clipbin/rustup CARGO_HOME=$HOME/.cache/clipbin/cargo-home PATH=$HOME/.cache/clipbin/cargo-home/bin:$PATH CARGO_TARGET_DIR=target/windows-check cargo clippy --locked --target x86_64-pc-windows-msvc -p clipbin-core -p clipbin-obs -p clipbin-probe --all-targets -- -D warnings
env PATH=/usr/bin:/bin:$HOME/.cargo/bin TMPDIR=/home/harry/Dev/clipbin-desktop/target/rig-tmp scripts/validate/linux-background.sh /home/harry/Dev/clipbin-desktop/target/background-review/reopen-after
env PATH=/usr/bin:/bin:$HOME/.cargo/bin TMPDIR=/home/harry/Dev/clipbin-desktop/target/rig-tmp scripts/validate/linux-rig.sh /home/harry/Dev/clipbin-desktop/target/background-review/capture
env PATH=/usr/bin:/bin:$HOME/.cargo/bin TMPDIR=/home/harry/Dev/clipbin-desktop/target/rig-tmp bash -e -c 'for suite in linux-session linux-clips linux-playback; do scripts/validate/${suite}.sh /home/harry/Dev/clipbin-desktop/target/background-review/${suite}; done'
```
All 58 workspace tests passed (fixture-dependent media tests retain their
existing opt-in behavior). All twelve capture scenarios passed; silence and
muted-mic logs show the initially suspended sink and respectively 650 and
650/645 captured packets, with no degradation. Audio-server loss still degrades
while video continues. Lifecycle passed SNI registration/menu actions,
--background, autostart write/remove, close survival, second launch, all five
reopen cycles, audio warning, clean Quit, watcher loss and no-host fallback.
Session finalization/library reload, clips/bookmarks/protected cleanup, and
playback all passed after the persistent-loop change. Playback changed 6.4% of
preview pixels, audio RMS was 0.08044; decode median/p95 was 4.951/11.467 ms and
GPUI upload median/p95 was 0.186/0.334 ms. The simplify quick self-review checked
reuse, clarity and efficiency; it retained existing actions/clean finish, one
application/dispatcher, weak fault registration with explicit cleanup, and no
new callback locks or allocations. The Windows policy flag uses a separate
Cell to avoid adding native state borrows during close handling.