fix: preserve GPUI lifetime and avoid WASAPI silence warnings
This commit is contained in:
+116
-29
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user