LocalAudioTrack.setProcessor acquires trackChangeLockbefore awaiting processor.init() (livekit-client.esm.mjs:19820-19840). Our init() does a wasm/ESM fetch, audioWorklet.addModule(), for DeepFilterNet a full core.initialize() model load, plus a resumeCtx() that can burn up to 3 s (resumeCtx default timeoutMs = 3_000). All of that holds the lock, and every mute(), unmute(), restartTrack() and device switch queues behind it. On a cold cache with DFN this is easily several seconds during which pressing mute does nothing.
How to trigger
Join a call with lotusDenoiseSource=1&lotusModel=deepfilternet on a cold cache and immediately hit mute.
Suggested fix
Warm the context/worklet/model before calling setProcessor (prefetch wasm and addModule at module start, keyed by model), so init() only wires up already-loaded pieces; and drop the resume timeout to a few hundred ms since the statechange watcher heals it anyway.
Filed from the September 2026 audit (branch lotus).
**Severity:** medium · **Type:** ux · **Confidence:** high
**Location:** `src/lotus/lotusDenoise.ts:100-107`, `src/lotus/lotusDenoiseProcessor.ts:160-172`, `210-251`
### Problem
`LocalAudioTrack.setProcessor` acquires `trackChangeLock` *before* awaiting `processor.init()` (`livekit-client.esm.mjs:19820-19840`). Our `init()` does a wasm/ESM fetch, `audioWorklet.addModule()`, for DeepFilterNet a full `core.initialize()` model load, plus a `resumeCtx()` that can burn up to 3 s (`resumeCtx` default `timeoutMs = 3_000`). All of that holds the lock, and every `mute()`, `unmute()`, `restartTrack()` and device switch queues behind it. On a cold cache with DFN this is easily several seconds during which pressing mute does nothing.
### How to trigger
Join a call with `lotusDenoiseSource=1&lotusModel=deepfilternet` on a cold cache and immediately hit mute.
### Suggested fix
Warm the context/worklet/model *before* calling `setProcessor` (prefetch wasm and `addModule` at module start, keyed by model), so `init()` only wires up already-loaded pieces; and drop the resume timeout to a few hundred ms since the `statechange` watcher heals it anyway.
---
_Filed from the September 2026 audit (branch `lotus`)._
jared
added this to the EC fork audit 2026-09 · Medium & Low milestone 2026-09-12 02:12:59 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: medium · Type: ux · Confidence: high
Location:
src/lotus/lotusDenoise.ts:100-107,src/lotus/lotusDenoiseProcessor.ts:160-172,210-251Problem
LocalAudioTrack.setProcessoracquirestrackChangeLockbefore awaitingprocessor.init()(livekit-client.esm.mjs:19820-19840). Ourinit()does a wasm/ESM fetch,audioWorklet.addModule(), for DeepFilterNet a fullcore.initialize()model load, plus aresumeCtx()that can burn up to 3 s (resumeCtxdefaulttimeoutMs = 3_000). All of that holds the lock, and everymute(),unmute(),restartTrack()and device switch queues behind it. On a cold cache with DFN this is easily several seconds during which pressing mute does nothing.How to trigger
Join a call with
lotusDenoiseSource=1&lotusModel=deepfilterneton a cold cache and immediately hit mute.Suggested fix
Warm the context/worklet/model before calling
setProcessor(prefetch wasm andaddModuleat module start, keyed by model), soinit()only wires up already-loaded pieces; and drop the resume timeout to a few hundred ms since thestatechangewatcher heals it anyway.Filed from the September 2026 audit (branch
lotus).