Skip to content

Unexplained: single renderer thread sustained 14% of a core for 3 days (investigation record) #104

Description

@daiverd

Summary

A client tab left open ~3 days sustained 17.8% of a core on average — about 13.7 hours of CPU. A single renderer thread accounted for 79% of it. The condition stopped partway through the investigation and the trigger was never identified.

Filing to preserve the measurements and the eliminations before the context is lost. This is an open question, not a solved bug.

Measurements

Chrome renderer, uptime 77.6 h:

Measure Value
Total CPU 49,709 s
Lifetime average 17.8% of a core
Working set 190–268 MB, flat over 3 days
Threads 29, stable

Per-thread breakdown (Windows exposes per-thread CPU, so this was measurable from outside the browser):

Thread Lifetime CPU Share Priority
TID 47712 39,169 s 79% Normal
TID 34980 2,376 s 5% Normal
TID 42620 1,566 s 3% AboveNormal (compositor)
TID 44172 678 s 1% AboveNormal
TID 52852 477 s 1% TimeCritical (WebAudio render thread)

TID 47712 averaged 14% of a core sustained across the entire three days.

Behavior under conditions

Scenario Renderer CPU TID 47712
Affected tab, foreground 24.1% 15.2%
Affected tab, backgrounded 1.6–4.2% 0.2%
Affected tab, foreground after the condition ended 5.8% 0.3%
Fresh tab, visible, logged out 3.5%
Fresh tab, visible, logged in, idle 2.6%

A healthy logged-in idle client costs 2.6% of a core with zero rAF callbacks and zero long tasks. The affected tab was roughly 9× that.

What has been eliminated

  • Not a memory leak. Working set flat over 3 days; thread and handle counts stable. Scrollback is capped in three places (output.tsx splice, outputStore 500, channelHistoryStore 1000), which is why memory stays flat.
  • Not a Web Worker. The thread throttled hard when the tab was backgrounded; workers are exempt from background throttling.
  • Not the WebAudio thread. That is a separate thread (TID 52852, TimeCritical) and accounts for only ~2% — tracked separately in Audio: suspend idle AudioContext to release 1 ms platform timer and OS audio stream #99.
  • Not a rAF loop in this codebase. All three requestAnimationFrame call sites in src/ are one-shot: input.tsx:167, input.tsx:215 (cursor position), output.tsx:549 (scroll to bottom). None self-perpetuates.
  • Not inherent to the client. A clean logged-in session does not reproduce it.
  • Not a CSS animation. The only infinite animation on a connected client is .status-dot.connecting (statusbar.css:53), which applies solely while connecting and animates opacity, a compositor-only property.

What is known

  • Main-thread work — it responds to Chrome's background-tab throttling.
  • Requires a live session — absent from a fresh logged-in idle client, so it depends on accumulated session state or an enabled feature.
  • Ends spontaneously — it stopped during the investigation and did not return, including when the tab was brought back to the foreground. So it is state-triggered, not steady-state, and not visibility-driven.

Remaining suspects

Untested, roughly in order:

  1. An enabled subsystem a fresh session lacks: MIDI panel open (MidiStatus: 2s poll calls loadDevices() unconditionally and double-stringifies to compare #101), voice chat, haptics/gamepad, an open editor window.
  2. Ambient room traffic driving continuous re-renders through useClientEvent — amplified by the O(n)-per-message store rebuilds in outputStore/channelHistoryStore: O(n) array rebuild on every message #102. The room in question describes "a terminal in the corner glows with scrolling output," which is at least suggestive of a periodic emitter.
  3. Accumulated session state that degrades over days.

Next step

This is unlikely to be solved by re-deriving it from outside the browser. The productive path is #103 (performance watchdog) plus #100 (diagnostics export), so that the next occurrence identifies itself instead of requiring process-level archaeology.

Related

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