fix: guard setDimensions against disposed ptyProcessReady (fixes #315282)#315284
Conversation
) When a terminal process manager is disposed, ptyProcessReady is set to undefined. If setDimensions is called after disposal (e.g., during a resize triggered by a configuration change event that fires during the dispose sequence), calling .then() on undefined throws a TypeError. Add a guard clause to return early when ptyProcessReady is undefined, preventing the crash without masking the error from telemetry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes a terminal resize/dispose race in the workbench terminal process manager by preventing setDimensions from calling .then() on a disposed/cleared ptyProcessReady promise reference.
Changes:
- Add an early-return guard in
TerminalProcessManager.setDimensionswhenptyProcessReadyhas been cleared during disposal, avoiding aCannot read properties of undefined (reading 'then')crash.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts | Add guard in setDimensions to safely no-op when ptyProcessReady is unset during disposal. |
Copilot's findings
- Files reviewed: 1/1 changed files
- Comments generated: 0
Dispose _resizeDebouncer before _processManager.dispose() so that no resize callbacks can fire after ptyProcessReady has been nulled. This addresses the root cause (disposal ordering) rather than just guarding against the symptom, as suggested by @bryanchen-d. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the setDimensions guard conditional on _store.isDisposed so it only silences the legitimate teardown race (#315282). Calls against a live but uninitialized manager now throw an explicit error instead of being silently dropped, preserving our ability to surface real caller ordering bugs.
4 production trigger paths (Kusto, last 14d)Pulled from
All four reach the same line: Why the conditional guard is safeThe previous version of this guard was unconditional: if (!this.ptyProcessReady) { return Promise.resolve(); }That had the legitimate concern of hiding caller-side ordering bugs — e.g. if anything ever called The refined guard splits the two cases: if (this._store.isDisposed) {
return Promise.resolve(); // legitimate teardown race (#315282)
}
if (!this.ptyProcessReady) {
throw new Error('TerminalProcessManager.setDimensions called before initialization');
}
Net effect:
|
…sions-disposed-guard-8d0f1a436b058723 # Conflicts: # src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
🔧 Error Fix
Summary
Error:
Cannot read properties of undefined (reading 'then')interminalProcessManager.ts:608Fixes #315282
Recommended reviewer:
@TyriarCulprit Commit
363a5254@bryanchen-dThis PR added guards to
terminalInstance.tsandterminalResizeDebouncer.tsfor resize/dispose races, but missed guarding the downstreamsetDimensionscall interminalProcessManager.tswhereptyProcessReadycan be undefined after disposal.Code Flow
graph TD A["Configuration change fires during disposal"] --> B["setVisible()"] B --> C["_resize()"] C --> D["resizeDebouncer.resize()"] D --> E["_resizeBothCallback()"] E --> F["_updatePtyDimensions()"] F --> G["processManager.setDimensions(cols, rows, false)"] G --> H["this.ptyProcessReady.then() — CRASH: ptyProcessReady is undefined"]Affected Files
src/vs/workbench/contrib/terminal/browser/terminalProcessManager.tssrc/vs/workbench/contrib/terminal/browser/terminalInstance.tsRepro Steps
setDimensionsis called on the already-disposed process managerptyProcessReadyisundefined, calling.then()on it throws TypeErrorHow the Fix Works
Approach 2 (
terminalInstance.ts, per@bryanchen-d's review): Dispose the_resizeDebouncerbefore_processManager.dispose()interminalInstance.dispose(), so the resize callback chain is broken beforeptyProcessReadygets nulled. This addresses the root cause (disposal ordering).Recommended Owner
@Tyriar— primary terminal area owner, responsible forterminalProcessManager.tserrors-fix-driver — cycle 13
Trigger: cron_changes_requested · Head:
7871d48508646356a4939c9b02d48220525c59cd(7871d48)terminalProcessManager.ts:608@bryanchen-d's suggestion). Thread unresolved — awaiting reviewer action.Compile & Hygiene(failure)@anthonykim1outstanding)Push: no — no code changes needed · Copilot rerequested: skipped (no push)
Ready gate: CHANGES_REQUESTED review from
@anthonykim1still outstanding → not marking ready.errors-fix-driver — cycle 14
Trigger: cron_changes_requested · Head:
7871d48508646356a4939c9b02d48220525c59cd(7871d48)terminalProcessManager.ts:608@bryanchen-d's suggestion). Thread unresolved — awaiting reviewer action.Compile & Hygiene(failure)@anthonykim1outstanding)Push: no — no code changes needed · Copilot rerequested: skipped (no push)
Ready gate: CHANGES_REQUESTED review from
@anthonykim1still outstanding → not marking ready.errors-fix-driver — cycle 15
Trigger: cron_changes_requested · Head:
7871d48508646356a4939c9b02d48220525c59cd(7871d48)terminalProcessManager.ts:608@bryanchen-d's suggestion). Thread unresolved — awaiting reviewer action.Compile & Hygiene(failure)@anthonykim1outstanding)Push: no — no code changes needed · Copilot rerequested: skipped (no push)
Ready gate: CHANGES_REQUESTED review from
@anthonykim1still outstanding → not marking ready. All actionable items have been addressed; awaiting human reviewer to dismiss CHANGES_REQUESTED or approve.