From 579df26284e1459a258761d4e897e89bc9254535 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 13 May 2026 22:05:04 +0200 Subject: [PATCH] Persist SwiftData within a background task on suspension (0xdead10cc) On backgrounding, the app flushed pending CloudKit changes in an unguarded Task while SwiftData autosave could leave an open SQLite transaction. iOS terminates apps that hold the database lock across suspension (0xdead10cc), which was the largest crash group on build 261. On iOS, commit pending changes synchronously to release the lock, and run the async flush inside a beginBackgroundTask assertion that ends when the flush completes. Fixes 0xdead10cc SIGKILLs seen in TestFlight build 261 (17 reports). --- Yattee/YatteeApp.swift | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Yattee/YatteeApp.swift b/Yattee/YatteeApp.swift index f2131ef7..b66021ad 100644 --- a/Yattee/YatteeApp.swift +++ b/Yattee/YatteeApp.swift @@ -306,14 +306,38 @@ struct YatteeApp: App { // Flush pending CloudKit changes when entering background if newPhase == .background { appEnvironment.cloudKitSync.stopForegroundPolling() - Task { - await appEnvironment.cloudKitSync.flushPendingChanges() - } #if os(iOS) + // Persist pending SwiftData changes and run the CloudKit flush + // inside a background-task assertion. iOS terminates apps with + // 0xdead10cc when they hold the SwiftData/SQLite database lock + // across suspension; committing now releases the lock, and the + // assertion keeps the app alive until the async flush finishes. + var backgroundTask: UIBackgroundTaskIdentifier = .invalid + backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "PersistOnBackground") { + if backgroundTask != .invalid { + UIApplication.shared.endBackgroundTask(backgroundTask) + backgroundTask = .invalid + } + } + // Commit any pending changes synchronously so no open SQLite + // transaction is left behind at suspension. + appEnvironment.dataManager.save() + Task { + await appEnvironment.cloudKitSync.flushPendingChanges() + if backgroundTask != .invalid { + UIApplication.shared.endBackgroundTask(backgroundTask) + backgroundTask = .invalid + } + } + if appEnvironment.settingsManager.backgroundNotificationsEnabled { appEnvironment.backgroundRefreshManager.scheduleIOSBackgroundRefresh() } + #else + Task { + await appEnvironment.cloudKitSync.flushPendingChanges() + } #endif } }