Skip to content

fix(desktop): make media device enumeration demand-driven to break WebKitGTK FD-leak loop - #7866

Open
jandado wants to merge 3 commits into
block:mainfrom
jandado:fix/linux-devicechange-enumeration-loop
Open

jandado wants to merge 3 commits into
block:mainfrom
jandado:fix/linux-devicechange-enumeration-loop

Conversation

@jandado

@jandado jandado commented Sep 24, 2026 •

Copy link
Copy Markdown

Summary

On Linux, Buzz Desktop crashes on its own after launch on machines with a
webcam: the WebKitWebProcess leaks file descriptors until EMFILE and GLib
aborts it (Creating pipes for GWakeup: Too many open files). The driver is a
feedback loop: useAudioDevices (mounted app-wide via HuddleProvider) and
AnimatedAvatarCapture re-enumerate navigator.mediaDevices on every
devicechange event, and WebKitGTK starts a fresh GStreamer GstDeviceMonitor
per enumerateDevices() call — whose startup re-announces already-known
devices as devicechange — so the loop feeds itself while each cycle leaks
FDs in the web process.

This PR makes enumeration strictly demand-driven:

  • useAudioDevices.ts — no mount-time enumeration and no devicechange
    listener; a single serialized refreshAudioDevices() (in-flight guard makes
    overlapping calls await; an unchanged audioinput list does not update
    React state).
  • Triggers replace event-driven freshness: the mic picker refreshes on open
    (MicControls onOpenChange → onPickerOpen, wired through HuddleBar
    and HuddleProfileControl), and one refresh runs after getUserMedia
    succeeds in huddle start/join (device labels only become readable once
    capture permission is granted). Companion windows mirror the audio-owning
    window's list, so their refresh is a no-op.
  • AnimatedAvatarCapture.tsx — devicechange listener removed; enumerates
    once when the capture UI opens and on camera-source switches.
  • The loop-safe cpal-backed output-device listener in HuddleContext keeps
    its devicechange handler but gains a ~1 s trailing debounce and an
    identical-list skip so residual re-announcement storms cannot spam the Rust
    backend (hardware-validated to produce no enumeration churn; kept defensive).

Full diagnosis, falsification experiment (libcamera plugin removed → FD leak
persists → engine is WebKitGTK, libcamera was the amplifier), stopgap, and
hardware validation: docs/linux-media-device-enumeration-loop.md.

Related issue

Fixes #7865.

Alternative to #4171, which throttles the same loop (2 s throttle + 30 s
backoff) but keeps it event-driven; its own description concedes the internal
WebKitGTK probe loop and per-enumeration FD leak remain ("on a broken-camera
machine the app would still crash, just later"). This change removes the
app-side amplifier entirely — no recurring trigger of any kind — and adds a
regression test that fails if event-driven enumeration returns. Happy to
coordinate with #4171's author; the surviving pieces there (throttle) become
unnecessary under this approach.

Upstream: the per-enumeration re-announcement and FD leak themselves are
WebKitGTK behavior — filed as Bug 325151: https://bugs.webkit.org/show_bug.cgi?id=325151.

Testing

  • New regression test desktop/src/features/huddle/lib/useAudioDevices.test.mjs
    binds the production hook and reproduces the pathology (every
    enumerateDevices() schedules a devicechange after resolving). Asserts:
    zero enumerations at mount, zero enumerations from simulated
    re-announcement storms, overlapping refreshes serialize into one
    enumeration, unchanged lists do not re-render, failed enumeration keeps the
    previous list. Reintroducing a devicechange-fed enumeration fails it.

  • cd desktop && pnpm check && pnpm typecheck && pnpm test — green.

  • just ci — green.

  • Hardware validation on the affected machine (2026-09-23, v0.5.24 AppImage,
    direct launch so firejail's --novideo cannot mask the bug):

    Artifact Adding camera lines WebKitWebProcess FDs
    pre-fix 1687 in 75 s 91 → 915 in 60 s, then EMFILE
    fixed build 6, in a 240 ms startup burst, then silence flat 71 for 150 s
  • Behavior change to flag for review: device lists refresh when the mic
    picker opens / on huddle start-join instead of on hotplug events — on all
    platforms, not just Linux. Chosen deliberately: the loop hazard is
    WebKitGTK-specific, platform-sniffing adds complexity, and WKWebView's
    mediaDevices is unreliable anyway (Huddle mediaDevices access crashes the whole app when navigator.mediaDevices is undefined #3118). Interactive checks (mic picker
    lists devices, huddle join works, avatar capture enumerates when opened)
    pass on the validated machine.

…bKitGTK FD-leak loop

On Linux/WebKitGTK, event-driven enumerateDevices() re-enumeration feeds a self-sustaining loop: every enumeration starts a fresh GStreamer device monitor whose startup re-announces known devices as devicechange, and each cycle leaks FDs in the WebKitWebProcess until EMFILE kills it. Remove both amplifiers: input enumeration becomes demand-driven (mic picker open, once after getUserMedia), the avatar-capture listener is gone, and the loop-safe cpal output listener is debounced with an identical-list skip. Adds a falsifiable regression test and docs/linux-media-device-enumeration-loop.md (diagnosis, falsification, stopgap, hardware validation).

Signed-off-by: Jan Dado <jan@dado.cz>
@jandado
jandado requested a review from a team as a code owner September 24, 2026 13:29
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 797012ff01a6d499959b45ed2e56f7927c6a4d6b...16013f6757cacebc0d2737b6a511fedaf9b8eaf4.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review 16013f6757cacebc0d2737b6a511fedaf9b8eaf4 to authorize a new review.
Any previous review applies only to its recorded range.

Signed-off-by: Jan Dado <jan@dado.cz>
…n\n\nStandalone MiniBrowser testing (WebKitGTK 2.52.6) shows enumeration-only and\nenumerate-with-live-capture loops are leak-free with single-init capture\nmachinery; the reproducible defect is per-getUserMedia-cycle FD leakage\n(~1 fd/cycle). Updates the root-cause attribution and falsification\ninterpretation, and adds the Bug 325151 amendment note.

Signed-off-by: Jan Dado <jan@dado.cz>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linux: WebKitWebProcess crashes with "Too many open files" (enumerateDevices <-> devicechange FD-leak loop)

1 participant