Skip to content

Bug: Per-core CPU load overflow after long uptime (incomplete fix of #3014) #3476

Description

@SkywingJam

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions