Skip to content

(terminal): switching to a hidden tab freezes the renderer for ~9 s #526

Closed

Description

@devsuitup

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:

  1. 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.
  2. 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.

Activity

  1. devsuitup commented on Oct 10, 2026

    @devsuitup
    OwnerAuthor

    History check, v0.0.51 → v0.0.91 (read-only, git and the trace):

    Discriminating checks:

    1. switch.step marks in showSession / fitAndScroll, plus a PerformanceObserver on long-animation-frame.
    2. An A/B without suspendTerminalWebgl(previous).
    3. 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.
  2. devsuitup commented on Oct 10, 2026

    @devsuitup
    OwnerAuthor

    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.

    build median switch
    v0.0.80 / v0.0.85 / v0.0.88 / v0.0.90 67–79 ms
    #508 head 70 ms
    #511 head (#506, tools moved to a right-hand bar) 1963 ms
    v0.0.91 2161 ms
    main (v0.0.92) 1889–2192 ms
    #532 37–42 ms

    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.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions