Bug Description
Stats crashes with EXC_BREAKPOINT (Swift runtime trap) in the CPU module after an extended system uptime.
This appears to be an incomplete fix of #3014. While #3014 prevented the overflow trap in the overall CPU load calculation (cpu_ticks) by using wrapping arithmetic (&-), the per-core load calculation in the same file still uses standard arithmetic operators.
When the underlying 32-bit CPU tick counters wrap around after a sufficiently long uptime, the plain subtraction can trigger Swift's integer overflow protection, causing the application to terminate with EXC_BREAKPOINT.
I also compared the behavior on another Mac with approximately 47 days of uptime, where the issue does not occur, which is consistent with the suspected long-uptime trigger.
Suspected Root Cause
In Modules/CPU/readers.swift, inside LoadReader.read(), the per-core CPU tick deltas are calculated using normal - and + operators:
if let prevCpuInfo = self.prevCpuInfo {
inUse = self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
+ self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
+ self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
total = inUse + (
self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
)
}
cpuInfo contains values of type integer_t (Int32). After sufficiently long uptime, these counters eventually wrap around. Once that happens, subtracting a large positive previous value from a wrapped current value using the normal - operator can overflow, which triggers Swift's runtime trap (EXC_BREAKPOINT).
This is similar to the issue previously fixed for the overall CPU load calculation in #3014.
Suggested Fix
As a minimal fix, the per-core delta calculation could also use Swift's wrapping arithmetic (&- / &+), matching the approach already used for the overall CPU load calculation:
if let prevCpuInfo = self.prevCpuInfo {
inUse = self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
&- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
&+ self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
&- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
&+ self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
&- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
total = inUse &+ (
self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
&- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
)
}
This should address the per-core overflow without changing the existing logic or behavior.
A more robust long-term solution would be to convert the kernel counters to unsigned values (for example via UInt32(bitPattern:)) and accumulate using a wider integer type such as UInt64. Since these kernel tick counters are effectively monotonically increasing unsigned counters, this would avoid relying on signed overflow semantics altogether. However, that would be a broader refactoring and is not required to resolve this crash.
I haven't been able to verify this patch myself. Reproducing the issue requires very long system uptime, and I don't currently have a local build environment for Stats. The proposed fix is based on comparing this code path with the equivalent fix introduced in #3014.
Crash Details
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Crashed Thread: 10 (Dispatch queue: com.apple.root.background-qos)
Thread 10 Crashed:
0 CPU 0x101129e80 0x101120000 + 40576
1 Kit 0x1015841f4 0x1014d8000 + 705012
2 Kit 0x1014eb35c 0x1014d8000 + 78684
3 libdispatch.dylib _dispatch_call_block_and_release
4 libdispatch.dylib _dispatch_client_callout
5 libdispatch.dylib <deduplicated_symbol>
6 libdispatch.dylib _dispatch_root_queue_drain
7 libdispatch.dylib _dispatch_worker_thread2
8 libsystem_pthread.dylib _pthread_wqthread
9 libsystem_pthread.dylib start_wqthread
Environment
- Stats version: 3.0.9
- macOS: 15.5 (24F74)
- Hardware: Mac mini (Apple Silicon)
- System uptime at crash: 277 days (
Time Awake Since Boot: 23,000,000 seconds)
- Usage scenario: Always-on home server / NAS, making long-running uptime and 32-bit counter wraparound realistic.
Bug Description
Stats crashes with
EXC_BREAKPOINT (Swift runtime trap)in the CPU module after an extended system uptime.This appears to be an incomplete fix of #3014. While #3014 prevented the overflow trap in the overall CPU load calculation (
cpu_ticks) by using wrapping arithmetic (&-), the per-core load calculation in the same file still uses standard arithmetic operators.When the underlying 32-bit CPU tick counters wrap around after a sufficiently long uptime, the plain subtraction can trigger Swift's integer overflow protection, causing the application to terminate with
EXC_BREAKPOINT.I also compared the behavior on another Mac with approximately 47 days of uptime, where the issue does not occur, which is consistent with the suspected long-uptime trigger.
Suspected Root Cause
In
Modules/CPU/readers.swift, insideLoadReader.read(), the per-core CPU tick deltas are calculated using normal-and+operators:cpuInfocontains values of typeinteger_t(Int32). After sufficiently long uptime, these counters eventually wrap around. Once that happens, subtracting a large positive previous value from a wrapped current value using the normal-operator can overflow, which triggers Swift's runtime trap (EXC_BREAKPOINT).This is similar to the issue previously fixed for the overall CPU load calculation in #3014.
Suggested Fix
As a minimal fix, the per-core delta calculation could also use Swift's wrapping arithmetic (
&-/&+), matching the approach already used for the overall CPU load calculation:This should address the per-core overflow without changing the existing logic or behavior.
A more robust long-term solution would be to convert the kernel counters to unsigned values (for example via
UInt32(bitPattern:)) and accumulate using a wider integer type such asUInt64. Since these kernel tick counters are effectively monotonically increasing unsigned counters, this would avoid relying on signed overflow semantics altogether. However, that would be a broader refactoring and is not required to resolve this crash.Crash Details
Environment
Time Awake Since Boot: 23,000,000 seconds)