MacOS SFM 永久无响应挂起
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 38.1k
- Forks
- 4.6k
- Avg merge
- 19d 15h
- Merged PRs (30d)
- 1
Description
操作系统
macOS
系统版本
Darwin Kernel Version 25.5.0
安装类型
sing-box for macOS 图形客户端程序
如果您使用图形客户端程序,请提供该程序版本。
版本1.14.0-beta.7 (1)
版本
描述
摘要
SFM 出现永久性无响应(转圈)。macOS spindump hang 报告显示:主线程以约 91% CPU 占用卡在 TextKit 2 布局中(-[NSTextLayoutManager _estimatedTextLocationForVerticalOffset:...]),并且被每一次 CoreAnimation 显示周期重复触发。采样时进程已无响应 11.4 小时,内存 footprint 达 3661 MB。
这不是崩溃 —— 进程仍然存活,只是彻底卡死。
环境
| 项 | 值 |
|---|---|
| 应用 | SFM 1.14.0-beta.7 (1),io.nekohasekai.sfavt.standalone |
| 路径 | /Applications/SFM.app/Contents/MacOS/SFM |
| macOS | 26.5.2 (Build 25F84) |
| 硬件 | Mac16,5 (arm64e),14 核,36 GB 内存 |
| 报告类型 | spindump,Event: hang,Report Version 72 |
| Incident ID | 2699724E-FF28-4B2F-8479-6EDA44F1940A |
| 语言环境 | zh-Hans-CN |
报告中的关键指标
| 指标 | 值 |
|---|---|
Event |
hang |
Duration |
41116.29 s |
| 采样前已无响应时长 | 41115 s(约 11.4 小时) |
| 采样窗口 | 1.10 s / 11 次采样,间隔 100 ms |
| 采样窗口内主线程 CPU | 0.999 s / 1.10 s(约 91%) —— 在真跑 CPU,不是阻塞 |
| 进程 footprint | 3661.00 MB |
Time Since Fork |
119522 s(约 33 小时) |
| Jetsam 优先级 / 内存上限 | 100 / 0 MB(无上限) |
| 系统内存压力 | 无(ThermalPressure -> 0,724 GB 空闲磁盘,整份报告无任何 jetsam kill) |
| 线程数 | 26(另有 3 个空闲 work queue 线程被省略) |
即:这是主线程用户态 CPU 密集型死循环,不是死锁、不是锁竞争、不是 I/O 等待,也与内存压力无关。
主线程调用栈(11/11 次采样完全一致)
11 start + 6992 (dyld)
11 ??? (SFM + 9956) ← 整条栈中唯一的 SFM 帧(main,未符号化)
11 static App.main() (SwiftUI)
11 runApp<A>(_:) (SwiftUI)
11 specialized runApp(_:) (SwiftUI)
11 NSApplicationMain (AppKit)
11 -[NSApplication run]
11 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]
11 _DPSNextEvent
11 _DPSBlockUntilNextEventMatchingListInMode
11 ReceiveNextEventCommon (HIToolbox)
11 RunCurrentEventLoopInMode
11 __CFRunLoopRun
11 __CFRunLoopDoSource1
11 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
11 __CFMachPortPerform
11 UC::DriverCore::continueProcessing() (UpdateCycle)
11 stepTransactionFlush (AppKit)
11 CA::Transaction::flush_as_runloop_observer(bool) (QuartzCore)
11 CA::Transaction::commit()
11 CA::Transaction::run_commit_handlers(CATransactionPhase)
11 NSDisplayCycleFlush
11 NSDisplayCycleObserverInvoke
11 __NSWindowGetDisplayCycleObserverForLayout_block_invoke
11 -[NSWindow(NSConstraintBasedLayoutInternal) layoutIfNeeded]
11 -[NSWindow(NSConstraintBasedLayoutInternal) _layoutViewTree]
11 -[NSView layoutSubtreeIfNeeded]
11 -[NSView _layoutSubtreeIfNeededAndAllowTemporaryEngine:]
11 -[NSView _layoutSubtreeWithOldSize:] ┐
...(_layoutSubtreeWithOldSize: 共 12 层嵌套) │ 视图层级很深
11 -[NSView _layoutSubtreeWithOldSize:] ┘
11 _NSViewLayout
11 ___NSViewLayout_block_invoke
11 ??? (AppKit + 5414264)
11 ??? (AppKit + 5412652)
11 ??? (AppKit + 5653696)
11 ??? (AppKit + 5653656)
11 -[NSTextView layout] + 496
11 -[NSTextViewportLayoutController layoutViewport] + 828
11 -[NSTextView locationForPositionInViewport:offset:] + 192
11 -[NSTextLayoutManager textLayoutFragmentForPosition:inTextContainerAtIndex:avoidsAdditionalLayout:] + 208
11 -[NSTextLayoutManager ensureLayoutForBounds:] + 196
8 -[NSTextLayoutManager _estimatedTextLocationForVerticalOffset:originLocation:originOffset:deltaFromOffset:] + 1556 ← 8/11 次采样,偏移量相同
8 -[_NSTextLayoutFragmentStorage enumerateTextLayoutFragmentFromLocation:options:usingBlock:] + 216
6 -[_NSTextRunStorage enumerateObjectsFromLocation:options:usingBlock:] + 220
5 -[_NSTextRunStorage _performCountableRunStorageOperation:] + 84
5 __69-[_NSTextRunStorage enumerateObjectsFromLocation:options:usingBlock:]_block_invoke.26 + 328
2 -[NSCountableTextLocation initWithIndex:] + 120
2 -[NSConcreteHashTable getItem:] (Foundation)
2 hashProbe + 324 (Foundation)
2 objc_loadWeak (libobjc.A.dylib)
1 objc_loadWeakRetained + 96
调用栈解读
-[NSTextLayoutManager _estimatedTextLocationForVerticalOffset:...]的作用是把垂直滚动偏移量反查为文本位置,其实现方式是线性枚举 layout fragment storage。- 11 次采样中有 8 次停在该函数同一条指令偏移(
+1556),且始终位于枚举回调内部。说明该枚举在一个采样间隔内无法收敛。 - 每次迭代都要分配一个
NSCountableTextLocation,并执行一次弱引用哈希查找(objc_loadWeak→hashProbe)。单次迭代成本很高,而它被执行了极大量次数。 - 整条路径挂在
CA::Transaction::commit()下,意味着它会被每一次显示周期重新触发 —— 应用永远无法完成一帧绘制,因此表现为永久转圈。 - 3661 MB 的 footprint 与「单个
NSTextView/NSTextContentStorage中持有了海量文本」这一形态高度一致。
这份报告无法说明的事
SFM 二进制在整条栈中只贡献了一帧:??? (SFM + 9956),即 main。整条挂起路径 100% 位于 AppKit / UIFoundation 内部,且应用二进制在本报告中未符号化。
因此仅凭这份日志无法定位到 SFM 源码的任何具体位置。 要归因到某个具体视图,需要 dSYM 符号化的捕获,或带复现步骤的 Instruments Time Profiler 追踪。
未经证实的推断
涉事文本视图最可能是应用内的日志面板,依据是:(a) 3.6 GB 的 footprint;(b) 33 小时运行时长下的无界增长;(c) 它是 SFM 中唯一有可能持有该量级文本的 UI。这是基于 footprint 与应用形态的推断,并非来自调用栈的证据。如果 SFM 还存在其他大文本视图(例如配置编辑器),其嫌疑同等成立。
已排除的无关项
- 进程内存在 WebKit 线程(
WebCore: Scrolling、Log work queue→ JavaScriptCore),但全部停在semaphore_wait_trap,最后运行时间为约 47276 秒前。与本问题无关。 - 子进程
RootHelper [47390]正常阻塞于read()→lck_mtx_sleep。无关。 MTLCompilerService(responsible process 为 SFM)空闲于__sigsuspend_nocancel。无关。
修复建议
- 给日志缓冲区加上限。 日志视图必须限制保留的内容量(例如仅保留最近 N 行 / M 字节),而不是无界增长。这是唯一能彻底消除该失效模式的改动。
- 重新考虑日志视图的实现方式。 单个巨型
NSTextView在 TextKit 2 下于该量级会严重退化。改为按行渲染(NSTableView/ SwiftUIList,每条日志一行,或LazyVStack)可完全绕开NSTextLayoutManager的视口位置估算。 - 降低视图嵌套深度。 文本视图外围有 12 层
_layoutSubtreeWithOldSize:嵌套,会成倍放大每一次布局的开销。
受影响用户的临时规避方案
- 关闭 / 禁用应用内日志面板。
- 降低日志级别,减少文本累积量。
- 长时间使用时定期重启 SFM。
重现方式
运行SFM GUI
日志
支持我们
- 我已经 赞助
完整性要求
- 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
- 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
- 我保证提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。
- 我保证提供了完整的配置文件与日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the SFM macOS GUI entry point and the attached spindump; the report identifies SwiftUI, AppKit, and TextKit 2 but no repository file or symbolized SFM frame. Reproduce with the GUI, then use a dSYM-symbolized capture or Instruments Time Profiler to identify the affected view; done means the hang is reproducible and the relevant text view no longer grows without bounds.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, swift
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100