fix(perf): eliminate the main-thread App Hangs (Sentry triage)#83
Merged
Conversation
…ean reports Every unresolved Burrow Sentry issue is an AppHang (main thread blocked >=2s), not a crash. This addresses the structural root causes behind the top buckets: - App icons (BURROW-R/T): AppIcon walked NSWorkspace.runningApplications on the MAIN thread on every cache miss, once per process row, each 2s refresh - hundreds of O(running-apps) walks. Now resolved off-main in the existing process pass (AppIcon.resolve) which walks the app list at most once; views read a pure cache (cachedImage). The popup's few rows use a cache+async-fill path. The main thread never walks the app list. - Process table (BURROW-1, the regressed #1, + layout-frame tail): the table's ForEach iterated the FULL process set (hundreds), rebuilding/diffing every identity each tick and forcing repeated sizeThatFits over the nested ScrollViews. Cap the ForEach to the top 100 by the current sort (with a 'Show all' affordance), fix ProcRow at 30pt so LazyVStack skips child measurement and the size cache stays stable, and make ProcessInfo Equatable so unchanged rows don't re-render. - Clean/optimize reports (BURROW-1G/1F): OperationFlow is @mainactor and re-parsed the whole accumulated transcript (parseTaskReport/mergeSummaryFields) on the main actor for EVERY streamed line - O(n^2). Throttle the live re-parse to ~4x/s; terminal events still do a final authoritative reduce. Audited the rest: all NSAlert.runModal() already route through runModalQuiet() (pauses Sentry ANR tracking during user-paced modals); the disk-scan and CLI-process waits run off-main; sparklines are already capped at 120 drawn points (BURROW-1M is downstream of the main-thread pressure the above fix). The framework-frame singletons (CharacterSet, swift_slowAlloc, etc.) are symptoms that shrink as a side effect. Verified: xcodebuild Debug build succeeds with no new warnings. Do NOT auto-resolve the Sentry issues until a release soaks - the top two regressed after a prior resolve.
This was referenced Jun 16, 2026
pull Bot
pushed a commit
to CrazyForks/Burrow
that referenced
this pull request
Jun 17, 2026
The uninstall/software list rendered each AppRow by calling SoftwareIcons.icon()/version() in the row body on the main thread: NSWorkspace.icon(forFile:) hits the app bundle and version() reads Info.plist, so the first paint of a large /Applications list did O(rows) disk reads on the main thread during layout — an App Hang (Sentry BURROW-20, top frame "InstalledApp"). - SoftwareIcons is now NSLock-guarded and resolves icons + versions on a utility queue; icon()/version() are main-safe cache reads that return a placeholder/nil and fill asynchronously on a miss. - fetch() pre-warms the cache off-main (it always runs on a background queue), so rows render from a pure cache read. - InstalledApp is Equatable so unchanged rows diff cheaply. Mirrors the off-main app-icon resolution for the process table in caezium#83.
pull Bot
pushed a commit
to gclm/Burrow
that referenced
this pull request
Jun 17, 2026
…m#82) Replaces the fixed "42% · 5.2G" metrics string with a configurable, reorderable row of metric widgets, the way a desktop system monitor lets you pick what to glance at. - Store.menuBarItems: an ordered [MenuBarItem] (metric + style + colour), JSON-persisted. Defaults to the historical CPU + memory pair, and only ever shows in Metrics mode (which stays opt-in - the default is still the icon), so nothing changes for existing users until they customise. - MenuBarWidgets.swift (new): MenuBarMetric (CPU/RAM/GPU/disk/net/IO/fan/temp/battery), MenuBarWidgetStyle (value / label+value / bar / sparkline / speed-down-up / battery glyph), MenuBarColorMode (utilization/accent/mono/pressure), and MenuBarRenderer which draws the whole row into an NSImage via a drawing handler so monochrome text tracks the light/dark menu bar. Re-implemented in our own MIT Swift + Brand tokens. - StatusBarController renders the row from the live feed: a snapshot sink (CPU/RAM/GPU/disk/fan/temp/battery + cpu/mem/gpu sparkline rings) and a 1 Hz sink (live net/disk rates + their sparklines off the ring). Drawing is a few strings + shapes; it never walks the running-app list or does other blocking work on main - consistent with the hang fixes in caezium#83. - SettingsView > Menu Bar gains a metrics editor (add / remove / reorder, per-metric style + colour pickers) shown when Display = Metrics; edits persist and re-render the status item live. Build: xcodebuild Debug succeeds, no new warnings. Satisfies caezium#82.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Every unresolved Burrow Sentry issue is an AppHang (main thread blocked ≥2 s) — zero real crashes. The 40 issues are ~5 root causes wearing different top-frame hats; the two dominant buckets (BURROW-1 layout, BURROW-N generic
app.run()) both regressed after a prior 0.7.1 resolve. This PR fixes the structural root causes.Changes
A. App icons off the main thread —
StatusView.swift(BURROW-R, BURROW-T)AppIcon.image(for:)walkedNSWorkspace.runningApplicationson the main thread on every cache miss, once per process row, each 2 s refresh — hundreds of O(running-apps) walks. Now:AppIcon.resolve(for:)runs inside the existing off-main process pass, walking the app list at most once and indexing it (O(apps + procs), not O(apps × procs)).cachedImage(for:)— a pure, lock-guarded cache read; glyph fallback until the icon lands.image(for:)= cache read + async off-main fill.B. Bounded process table —
StatusView.swift,MoleStatus.swift(BURROW-1 + layout tail)The table's
ForEachiterated the full process set (hundreds), rebuilding/diffing every identity each tick and forcing repeatedsizeThatFitsover the nested ScrollViews (theScrollViewLayoutComputer → placeChildrenrecursion in the trace). Now:ForEachis capped to the top 100 rows by the current sort, with a "Show all (N more)" affordance.ProcRowhas a fixed.frame(height: 30)soLazyVStackskips per-child measurement and the ScrollView size cache stays stable.ProcessInfois nowEquatableso unchanged rows don't re-render.C. Throttled clean/optimize report —
OperationFlow.swift(BURROW-1G, BURROW-1F)OperationFlow(@MainActor) re-parsed the whole accumulated transcript (parseTaskReport/mergeSummaryFields) on the main actor for every streamed line — O(n²) over a long run. The live re-parse is now throttled to ~4×/s; terminal events still do a final, authoritative reduce, so the result screen is never stale.Audited — no change needed
NSAlert.runModal()already routes throughrunModalQuiet(), which pauses Sentry ANR tracking for user-paced modals.group.wait) and CLI-process (waitUntilExit) calls run off-main.MiniChartalready caps to 120 drawn points; this fingerprint is downstream of the main-thread pressure that A/B/C relieve.Test plan
xcodebuildDebug build succeeds, no new warnings.mo cleanand confirm the live report still updates and the final result is correct.Notes
origin/main. Companion PR (customizable menu-bar metrics, 关于菜单栏可以自定义指标的建议 #82) to follow.