fix(desktop): throttle devicechange-driven media enumeration - #4171
ludo-trentuna wants to merge 1 commit into
Conversation
WebKitGTK's libcamera backend can retry a failing camera manager in a
tight loop, firing devicechange on every cycle. Each cycle spawns a new
libcamera manager and leaks file descriptors in the web process; on
machines where the camera fails to enumerate (libcamera: 'No such
device'), this escalates until EMFILE aborts the app ('Too many open
files', GLib: 'Creating pipes for GWakeup').
useAudioDevices listened to devicechange with no throttle, so the
frontend amplified the loop: enumerate -> probe -> fail -> devicechange
-> enumerate...
Throttle refreshes to at most one per 2s, and back off for 30s after 3
consecutive failures. Breaks the feedback loop while keeping hotplug
responsiveness.
Signed-off-by: Ludo <ludo@trentuna.com>
|
Hi @ludo-trentuna — I independently hit this crash (v0.5.24, AppImage) and Two findings from a falsification experiment on the affected machine:
I have a fix PR incoming that removes the amplifier instead: enumeration No duplication intended — your report and FD measurements (144 → 403 over 40 s, Upstream note for whoever picks this up: the per-enumeration re-announcement + |
|
Update to the upstream note above: filed as WebKitGTK Bug 325151 — https://bugs.webkit.org/show_bug.cgi?id=325151 (full diagnosis, a runnable repro attachment, and suspected fix directions; happy to test patches on the affected machine). |
Summary
On Linux, Buzz Desktop crashes on its own a few seconds after launch on machines where the webcam fails to enumerate:
GLib-ERROR **: Creating pipes for GWakeup: Too many open filesaborts theWebKitWebProcess.This PR breaks the frontend half of the feedback loop that drives the crash:
useAudioDeviceslistened todevicechangewith no throttle, so each failed media probe re-triggeredenumerateDevices().Root cause
HuddleProvideris mounted at the app root (desktop/src/app/AppShell.tsx), souseAudioDevicescallsnavigator.mediaDevices.enumerateDevices()on mount and registers adevicechangelistener (desktop/src/features/huddle/lib/useAudioDevices.ts).Failed to start camera manager: No such device(reproduced with an integrated UVC camera, 2 interfaces, 4/dev/video*nodes).camera_managerprocess and leaks file descriptors in the web process.devicechange, and the frontend listener calledenumerateDevices()again without any throttle — amplifying the loop.EMFILE→ GLib abort inWebKitWebProcess→ the whole app dies.Measured on the affected machine (idle app, no user interaction, 40 s):
~6.5 leaked FDs/s (linear, never released), plus ~16
camera_managerspawns per second (new PID on every log line).ulimit -nwas 524288, so this is a genuine leak, not a low system limit.Fix
Throttle
devicechange-driven refreshes to at most one per 2 s, and back off for 30 s after 3 consecutive failures. This breaks the feedback loop while keeping hotplug responsiveness (a healthy device still gets picked up on the next refresh).Scope / honesty
The frontend throttle stops the amplification, but the internal WebKitGTK/libcamera probe loop and FD leak remain — on a broken-camera machine the app would still crash, just later. The real fix belongs upstream in WebKitGTK (stop retrying / close FDs when the camera manager fails to start) and should be reported to bugs.webkit.org. This PR is the Buzz-side mitigation.
Duplicate search
repo:block/buzz type:pr "open files"→ 15 PRs, none about this FD leak / camera retry loop.repo:block/buzz type:pr devicechange/enumerateDevices→ one open PR: fix(huddle): guard mediaDevices access when the API is unavailable #3817fix(huddle): guard mediaDevices access when the API is unavailable.navigator.mediaDevicesbeingundefined(non-secure context → TypeError crash on mount). This PR handles the case where the API exists but the camera fails to enumerate (EMFILE crash). Complementary.useAudioDevices.ts(samerefreshDevices); if fix(huddle): guard mediaDevices access when the API is unavailable #3817 merges first this branch will need a rebase.Test plan
pnpm lint/tsc --noEmitpass.