fix(perf): eliminate the main-thread App Hangs (Sentry triage)#83
Open
caezium wants to merge 1 commit into
Open
fix(perf): eliminate the main-thread App Hangs (Sentry triage)#83caezium wants to merge 1 commit into
caezium wants to merge 1 commit into
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.
6 tasks
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.