Skip to content

Main-thread stall in SwiftUI DisplayList removeRemaining starves control socket; quit during stall restores a stale session snapshot #7861

Description

@azooz2003-bit

Observed once during PiP verification on #7798 (build a6badf5, tag pip) under extreme host load (loadavg 70-160, ~78 users, dozens of agents): after an ~18s CLI timeout on a stateful verb, every MainActor-bound socket verb (tree, identify, rename-tab, surface pip) timed out for 15+ minutes while ping kept answering and terminals kept rendering (Metal threads). sample shows the main thread pinned for the whole window inside NSHostingView.layout → ViewGraph.renderDisplayList → DisplayList.ViewUpdater.Container.removeRemaining(viewCache:) → CoreViewLayerView → objc_loadWeak → weak_entry_for_referent (21/22 samples in the objc weak-table walk). No cmux frames below the hosting view — this is the SwiftUI huge-view-cache teardown family (cf. #2586). Sample: /tmp/fable-feat-surface-pip/evidence-3/10-sample-during-hang.txt, log: 10a-socket-wedge.txt (machine-local).

Downstream effect: SIGTERM during the stall cannot write the quit-time session snapshot, so relaunch restores the last periodic snapshot; in the observed case that resurrected panes/surfaces closed long before quit (looked like "phantom panes with fresh shells"). A clean pop→SIGTERM→relaunch at normal conditions round-trips correctly, so the restore path itself is sound.

Two directions worth separating: (1) whatever makes removeRemaining walk an enormous weak table on one display-cycle flush; (2) socket-side, consider a watchdog that fails stateful verbs fast with a distinct main_thread_stalled error instead of generic client timeouts, so callers and quit paths can distinguish a stall from slowness.

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