Skip to content

fix: guard terminal resize against disposal to prevent xterm dimensions error (fixes #316419)#316424

Open
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/terminal-resize-dispose-race-316419-d05fcf06a84c3868
Open

fix: guard terminal resize against disposal to prevent xterm dimensions error (fixes #316419)#316424
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/terminal-resize-dispose-race-316419-d05fcf06a84c3868

Conversation

@vs-code-engineering
Copy link
Copy Markdown
Contributor

🔧 Error Fix

Summary

Error: Cannot read properties of undefined (reading 'dimensions') — a TypeError thrown by xterm.js's Terminal.dimensions getter when its internal RenderService._renderer.value is undefined post-disposal.

Impact: 48.6M hits across 4.9M users. Affects all platforms (Linux, Mac, Windows). Bucket 1e83a096-ed50-2ba1-2dd8-46381020bd02.

Root cause: During TerminalInstance.dispose(), the _onDisposed event fires at line 1327 before super.dispose() is called at line 1345. This means isDisposed is still false during the event chain. Listeners triggered by _onDisposed can dispose timers/schedulers whose callbacks fire configuration change events. These config changes reach setVisible() on the disposing terminal, which calls _resize() → debouncer → _resizeBothCallback_updatePtyDimensions(). The existing isDisposed guards in the callback and _updatePtyDimensions pass (returning false), and the xterm dimensions getter throws because the renderer is in a bad state during the disposal process.

Fixes #316419
Recommended reviewer: @Tyriar

Culprit Commit

This is a long-standing issue (bucket first seen at v1.109.5, March 2026) exacerbated by the xterm.js beta.213 bump (644c7175 by @bryanchen-d). The prior fix attempt added isDisposed guards and a try/catch (644c7175), then the try/catch was removed (5e647b29) on the assumption that isDisposed guards were sufficient. They are not sufficient for the _onDisposed.fire() path where isDisposed is still false.

Code Flow

sequenceDiagram
    participant PE as _onProcessExit
    participant D as dispose()
    participant EV as _onDisposed listeners
    participant CC as Config Change
    participant SV as setVisible
    participant R as _resize
    participant DB as ResizeDebouncer
    participant UP as _updatePtyDimensions
    participant XT as xterm.dimensions getter

    PE->>D: dispose(TerminalExitReason.Process)
    Note over D: isDisposed = false (super.dispose() not yet called)
    D->>EV: _onDisposed.fire(this)
    EV->>CC: listener disposes timer, timer callback fires
    CC->>SV: config change triggers setVisible(true)
    SV->>R: _resize()
    R->>DB: resize(cols, rows, immediate)
    DB->>UP: _resizeBothCallback, _updatePtyDimensions(xterm.raw)
    UP->>XT: rawXterm.dimensions
    Note over XT: renderer undefined, TypeError
Loading

Affected Files

File Role
src/vs/workbench/contrib/terminal/browser/terminalInstance.ts Terminal instance with dispose/resize logic

How the Fix Works

Chosen approach (terminalInstance.ts): Added a _isDisposing flag that is set to true at the start of dispose() (before _onDisposed.fire()) and checked in _resize() alongside isDisposed. This guards against resize operations during the entire disposal process, not just after super.dispose() completes. The fix is at the data producer level — preventing the resize call from being made when the terminal is mid-disposal, rather than catching the error at the crash site (the xterm getter).

Alternatives considered:

  • Adding a try/catch around the dimensions access: wraps the crash site, hides the bug.
  • Only checking isDisposed in _resize: insufficient because isDisposed is false during _onDisposed.fire().
  • Moving super.dispose() before _onDisposed.fire(): breaks contribution addon cleanup.

Recommended Owner

@Tyriar — primary owner of the terminal component. @bryanchen-d previously worked on this exact bucket.

Generated by errors-fix · ● 95.2M ·

…ns error (fixes #316419)

During _onProcessExit → dispose(), the _onDisposed event fires before
super.dispose() is called, so isDisposed is still false. Event listeners
triggered during _onDisposed can reach setVisible → _resize →
_updatePtyDimensions, which accesses xterm's dimensions getter. The
getter throws because xterm's internal renderer is in a bad state during
the disposal process.

Add a _isDisposing flag set at the start of dispose() and check it in
_resize() to prevent resize operations during the entire disposal
process, not just after it completes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 14, 2026 15:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@vs-code-engineering vs-code-engineering Bot requested a review from Copilot May 14, 2026 15:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@vs-code-engineering vs-code-engineering Bot marked this pull request as ready for review May 14, 2026 15:16
@vs-code-engineering vs-code-engineering Bot enabled auto-merge (squash) May 14, 2026 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Regression] Cannot read properties of undefined (reading 'dimensions') — xterm

2 participants