mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-11 00:54:10 +00:00
fix(renderer): roll back the mute-suppression latch when the IPC fails
CodeRabbit caught a real bug in the previous commit's fix. The latch mirrors the NATIVE refcount, but it was flipped before the invoke resolved: a rejected release left it reading "released" while the engine still held the suppression, so every later release short-circuited and monitor mute stayed suppressed for good — the same stuck-suppression bug the latch exists to prevent, just one level up. The latch now only stays flipped if the call actually landed, and rolls back otherwise (guarded so a newer call can't be clobbered by a stale rejection). A downlevel addon with no arbiter leaves the latch untouched instead of recording a hold it never acquired. Pins the whole contract with a vm-extracted unit test on the real screen.js function: unpaired acquires hold at most one native suppression, cycles stay balanced across 25 song loads, a rejected release retries, and both the downlevel and sync-throw paths are clean. Fails 3/5 against the original branch (the refcount leak) and 2/5 against the pre-rollback version.
This commit is contained in:
+18
-7
@@ -4390,16 +4390,27 @@ window.__feedBackDesktopAudioHooks = window.__feedBackDesktopAudioHooks || {};
|
||||
function aeSetMonitorMuteSuppressed(suppressed) {
|
||||
const want = !!suppressed;
|
||||
if (want === aeMonitorMuteSuppressionHeld) return; // idempotent, like the old bool
|
||||
aeMonitorMuteSuppressionHeld = want;
|
||||
const api = window.feedBackDesktop?.audio;
|
||||
// Optional-chained: a downlevel native addon simply ignores this.
|
||||
// Downlevel addon (no arbiter): nothing is ever acquired, so leave the
|
||||
// latch alone rather than recording a hold we don't have.
|
||||
if (typeof api?.setMonitorMuteSuppressed !== 'function') return;
|
||||
aeMonitorMuteSuppressionHeld = want;
|
||||
// The latch mirrors the NATIVE refcount, so it may only stay flipped if
|
||||
// the call actually landed. A rejected release that left the latch at
|
||||
// "released" would short-circuit every later release while the native
|
||||
// count stayed held — the same stuck-suppression bug, one level up. Roll
|
||||
// back on failure so the next call retries (and only if no newer call
|
||||
// has moved the latch on in the meantime).
|
||||
const rollback = () => {
|
||||
if (aeMonitorMuteSuppressionHeld === want) aeMonitorMuteSuppressionHeld = !want;
|
||||
};
|
||||
// setMonitorMuteSuppressed is async (ipcRenderer.invoke) — the sync
|
||||
// try/catch only covers a missing method, so also swallow the
|
||||
// returned promise's rejection to avoid an unhandled rejection.
|
||||
// try/catch only covers a throwing call, so handle the returned
|
||||
// promise's rejection too (which also avoids an unhandled rejection).
|
||||
try {
|
||||
const r = api?.setMonitorMuteSuppressed?.(want);
|
||||
if (r && typeof r.catch === 'function') r.catch(() => {});
|
||||
} catch (_) { /* downlevel */ }
|
||||
const r = api.setMonitorMuteSuppressed(want);
|
||||
if (r && typeof r.catch === 'function') r.catch(rollback);
|
||||
} catch (_) { rollback(); }
|
||||
}
|
||||
// Called by clearChainForNewSong (IIFE 1) and the preload below.
|
||||
window._aeBeginChainRebuildGuard = function () { aeSetMonitorMuteSuppressed(true); };
|
||||
|
||||
Reference in New Issue
Block a user