Skip to content

Conversation

@melekr
Copy link
Collaborator

@melekr melekr commented Jul 25, 2025

WHY

To fix an issue where app‑launch stalls when OOM detection is enabled. Caused by heavy plcr snapshots and sync operations.

This PR:

  • Offloads all OOM and PLCR I/O to a dedicated serial DispatchQueue.
  • Provides a 3 options self‑documenting explicit switch (.none | .light | .full) for OOM reporting.
  • Eliminates launch stalls by moving all OOM work to a dedicated serial queue and introducing a lightweight snapshot mode that skips symbolication.
  • Provides automatic fallback (light → full) to guarantee that an OOM is still reported even if the lightweight path fails.
  • Maintains backwards compatibility and adds deprecation & compiler notices.
  • Adds resident memory footprint at the moment of the low‑memory warning for easier triage.

WHAT CHANGED

API

  • BacktraceClientConfiguration gains oomMode: BacktraceOomMode.
  • Legacy detectOom Bool is deprecated but still functional.
  • No other public signatures changed.

Runtime

  • BacktraceOomWatcher executes on DispatchQueue(label: "com.backtrace.oom").
  • New lightweight report path (current thread, no symbolication).
  • Normal termination now early‑exits when mode == .none.

Tests

  • Added flushQueue() helper and updated specs.
  • New coverage for .none and .light modes.

Sample Reports

WHAT'S NEXT [Swift 6 Concurrency 🙌]

Grand Central Dispatch (DispatchQueue) is the established concurrency framework that predates the modern Swift concurrency. It solves the immediate problem and is fully compatible & safe in a Swift 6 environment.
A more idiomatic Swift 6 approach however, would use async/await and Actors.

actor BacktraceOomWatcher {

    // actor guarantees concurrency
    internal var state: ApplicationInfo
    var quietTimeInMillis: Int = 60 * 1000

    // oomQueue is no longer needed.

    init(...) {
        // standard initialization
    }

    // This function is now 'async'. The 'actor' ensures it runs serially.
    func start() async {
        await sendPendingOomReports()
        ...
    }

    // Callers from outside the actor must 'await' this call.
    func handleLowMemoryWarning() async {
        ...
        await saveState()
    }

    // private methods are also isolated to the actor.
    private func saveState() {
        ...
    }
    
    private func reportOom(appState: ApplicationInfo) async {
        // generate report
        try? await backtraceApi.send(report)
    }
 }

Usage:

Task {
     await self.backtraceOomWatcher.start()
  }

ref: BT-5903

melekr added 3 commits July 25, 2025 18:29
… and improve oom handling

Perform BacktraceOomWatcher operations on DispatchQueue
Add `flushQueue()` internal helper for unit tests
Add early‑return in `start()` when oomMode == .none
Add and persist resident memory footprint attribute at warning time
Introduce @objc enum BacktraceOomMode [ .none, .light, .full ]
Add property `oomMode` to BacktraceClientConfiguration
Deprecate legacy `detectOom` Bool with @available(renamed)
Provide deprecated convenience init that maps detectOOM to oomMode
Call `flushQueue()` after each async method before asserting state
Add new context for .none and .light oom modes
Ensure no state file is written in .none mode
@melekr melekr self-assigned this Jul 25, 2025
@melekr melekr requested review from konraddysput and rick-bt July 25, 2025 23:21
@melekr melekr requested a review from konraddysput July 26, 2025 14:14
@melekr melekr merged commit ad1f6e6 into master Jul 30, 2025
7 of 8 checks passed
@melekr melekr deleted the oom_handling branch July 30, 2025 00:58
@melekr melekr mentioned this pull request Aug 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants