Skip to content

Listener exception permanently wedges EventEmitter, deadlocking the debuggee at startup #2401

Description

Filed by GitHub Copilot on behalf of Joaquín Ruales (@jruales). Root cause verified in source; fix validated by a controlled A/B in a real VS Code window.

Summary

A single listener exception permanently wedges EventEmitter, which silently kills the CDP message pump for that connection. When it happens during target attach, js-debug never sends Runtime.runIfWaitingForDebugger, so the debuggee stays paused at startup forever.

Symptom when debugging an Electron app (the microsoft/vscode workbench): the window renders but accepts no input — nothing clickable, keyboard dead, command palette opens but neither responds nor dismisses.

Root cause

1. EventEmitter.fire doesn't restore _deliveryQueue when a listener throwssrc/common/events.ts#L55-L65

if (!dispatch) return;
for (let index = 0; index < this._deliveryQueue.length; index++) {
  const { data, event } = this._deliveryQueue[index];
  data.listener.call(data.thisArg, event);   // throws here
}
this._deliveryQueue = undefined;             // never reached

_deliveryQueue stays truthy, so every later fire() computes dispatch === false, enqueues, and returns without dispatching. The emitter is permanently deaf. Since Connection consumes its transport via this._transport.onMessage(...) (connection.ts#L59), one bad message permanently stops all CDP traffic on that connection.

2. Connection._onMessage throws on an unknown session idsrc/cdp/connection.ts#L104-L110

Messages for disposed sessions are ignored, but messages for unknown sessions throw — which is what enters the wedge above.

Why an unknown session id occurs

Inspector.workerScriptLoaded is a Blink event fired from WorkerGlobalScope::RunWorkerScript() when a web worker finishes evaluating its script. It is never handled by js-debug at runtime (it appears only in the generated src/cdp/api.d.ts), so it arrives unsolicited.

Session registration appears to be deferred: Target.setAutoAttach({ flatten: true }) delivers worker targets, but the targetCreated handler is wrapped in enqueueLifecycleFn (browserTargetManager.ts#L171-L174), so createSession(sessionId) runs on a later microtask. A workerScriptLoaded landing in that window finds no registered session.

This matches the observed behaviour: it is intermittent, and it reproduces with workbench web workers such as editorWorkerService.

Evidence

Error at the exact moment of the freeze, with EventEmitter.fire directly beneath the throw:

[error] Error: Unknown session id: B9878282765DBAAB165E4A5851627507 while processing: Inspector.workerScriptLoaded
    at ut._onMessage (.../src/extension.js:58:8166)
    at U.fire        (.../src/extension.js:39:11654)

The debuggee was confirmed waiting for the debugger, not paused at a breakpoint:

  • Runtime.evaluate of synchronous code succeeded
  • setTimeout / requestAnimationFrame never fired; event handlers never ran
  • Debugger.resume returned Can only perform operation while paused
  • Runtime.runIfWaitingForDebugger immediately restored the window

Minimal repro against the unmodified EventEmitter:

const emitter = new EventEmitter<string>();
const delivered: string[] = [];
emitter.event(v => { if (v === 'poison') throw new Error('boom'); delivered.push(v); });

emitter.fire('before');
try { emitter.fire('poison'); } catch {}
emitter.fire('after-1');

console.log(delivered); // ["before"]  <-- 'after-1' is never delivered

Fix

#2402 restores _deliveryQueue in a finally block, and treats unknown sessions the way disposed ones are already treated (warn and ignore).

Validated by a controlled A/B: the extension built both ways, loaded into a real VS Code Insiders window via --extensionDevelopmentPath, same machine/repo/profile/launch config, only the diff varying.

Build Outcome
Unpatched Workbench freezes. Reproduced.
Patched Works every time, across repeated runs.

Environment

js-debug 1.117.0 (bundled with VS Code Insiders)
VS Code 1.136.0-insider
Electron (debuggee) 42.8.1
OS macOS (arm64)
Scenario Debugging microsoft/vscode via its Renderer and Main processes compound

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions