You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
(terminal): switching to a hidden tab freezes the renderer for ~9 s #526
Switching to a hidden session tab freezes the renderer for about 8-10 s every time, whatever the amount of output replayed.
Measured 2026-10-10 in the live activity trace (v0.0.91; public/terminal-manager.js is identical on d14b035):
Reveal (UTC)
session
writeChars replayed
stall
23:56:07
6577a487
429 275
9.0 s
23:59:44
7615734f
26 118
8.5 s
00:03:05
6577a487
152 194
9.8 s
Every reveal in the window stalls, and the duration does not depend on volume (×16 in volume, same duration).
The main process stays alive meanwhile (osc.title events about every second); only the renderer is blocked.
Ruled out:
Replay write: xterm 6.0.0 parses 26 KB in 29 ms and 2 MB in 194 ms, measured under jsdom.
Scrollback reflow: resizing with 10k-50k scrollback lines takes 40-133 ms.
Click path JS:clearNotifications and the sidebar class.apply events land in the same millisecond.
Synchronous IPC: there is no sendSync.
Main suspect (plausible, not proven): the render path of showSession (~1291-1311).
suspendTerminalWebgl(previous) disposes the previous tab's WebGL addon.
restoreTerminalWebgl creates a new WebglAddon and context before the container gets .visible, i.e. on a display:none element.
forceRepaint then calls clearTextureAtlas and refresh.
atlasChanges and atlasCanvases stay 0, so this is not a repaint storm, but they do not measure context creation.
Next:
Instrument each step of showSession and its rAF callback under window.ATRACE as a reveal.timing event, so one tab switch shows which step takes the time.
Then fix:
preferred: keep the WebGL context across switches, within the existing LRU cap;
or load it after the container is visible, sized, one frame later.
Earlier, possibly related: xterm "task queue exceeded allotted deadline" overruns of 41-82 s on 2026-10-09 18:06-18:41. That warning means a single idle task overran its slot, so it is not explained by a chunked write. Its cause is not identified.
Only dependency change in the window: node-pty 1.1.0 → 1.2.0-beta.15 ((terminal): move node-pty to 1.2.0-beta.15 (#409) #431, v0.0.88, 2026-10-03), which bundles OpenConsole. Its cursor-position queries after a resize are a plausible source of stray input or wrong content. Nothing links it to the renderer stall.
More work around each switch:
per-session panel visibility and width, so a switch can resize and send a ConPTY resize;
Stall meter:render.stats.ms comes from a 1 s setTimeout. In .005, the stalls are 9042, 8460, 9841, 8637 and 9766 ms; earlier files have about 20 stalls of 3-17 s. atlasChanges stays 0 everywhere.
forceRepaint right after creation:clearTextureAtlas() runs right after the addon is created, which resets the atlas warm-up (93 idle glyph tasks, TextureAtlas.ts:122-146). The xterm "task queue exceeded allotted deadline" warnings come from that same IdleTaskQueue, probably as a symptom of the stall seen from inside the queue.
Discriminating checks:
switch.step marks in showSession / fitAndScroll, plus a PerformanceObserver on long-animation-frame.
An A/B without suspendTerminalWebgl(previous).
With the trace off, compare tags v0.0.87 / v0.0.88 / v0.0.91, and v0.0.51 as the environment control: if v0.0.51 also stalls, the cause is the environment (GPU driver, session count), not a regression.
Bisected live, same bench for every build: isolated test-pr, 5 plain terminals each streaming 30,000 lines, 15 switches, timed from showSession to two animation frames, window visible.
So the regression came in v0.0.91 with #511. The cost is one long animation frame of ~1.6 s per switch on main. #532 removes it, but the mechanism #511 added has not been identified: the WebGL dispose/recreate per switch already existed at 75 ms. Worth a look once #532 is in, in case something else in the same path is still expensive.
Activity
devsuitup commentedon Oct 10, 2026
History check, v0.0.51 → v0.0.91 (read-only, git and the trace):
suspendTerminalWebgl(previous),restoreTerminalWebgl(next),replayHiddenBufferandforceRepainthave been unchanged since fix(terminal): clear stale WebGL glyph atlas on reveal and on atlas rebuild #103/perf(terminal): suspend rendering work for hidden sessions in single view #115/perf(terminal): fully suspend xterm for hidden sessions (buffer-and-replay) #122 (v0.0.45-v0.0.51, mid-August). Electron 41.0.3, xterm 6.0.0, addon-webgl 0.19.0 and addon-fit 0.11.0 are identical from v0.0.51 to v0.0.91.render.stats.mscomes from a 1 ssetTimeout. In.005, the stalls are 9042, 8460, 9841, 8637 and 9766 ms; earlier files have about 20 stalls of 3-17 s.atlasChangesstays 0 everywhere.forceRepaintright after creation:clearTextureAtlas()runs right after the addon is created, which resets the atlas warm-up (93 idle glyph tasks,TextureAtlas.ts:122-146). The xterm "task queue exceeded allotted deadline" warnings come from that sameIdleTaskQueue, probably as a symptom of the stall seen from inside the queue.Discriminating checks:
switch.stepmarks inshowSession/fitAndScroll, plus aPerformanceObserveronlong-animation-frame.suspendTerminalWebgl(previous).test(terminal): pin that switching tabs keeps recent WebGL contexts a…
fix(terminal): keep recent WebGL contexts across tab switches and tim…
Merge main into wt/526, keep the #526 entry under Unreleased
devsuitup commentedon Oct 10, 2026
Bisected live, same bench for every build: isolated
test-pr, 5 plain terminals each streaming 30,000 lines, 15 switches, timed fromshowSessionto two animation frames, window visible.So the regression came in v0.0.91 with #511. The cost is one long animation frame of ~1.6 s per switch on main. #532 removes it, but the mechanism #511 added has not been identified: the WebGL dispose/recreate per switch already existed at 75 ms. Worth a look once #532 is in, in case something else in the same path is still expensive.
(terminal): keep recent WebGL contexts across tab switches and time e…