diff --git a/mobile/ios/Runner/PushSnapshotBridge.swift b/mobile/ios/Runner/PushSnapshotBridge.swift index b735539c8e..09f599cf0e 100644 --- a/mobile/ios/Runner/PushSnapshotBridge.swift +++ b/mobile/ios/Runner/PushSnapshotBridge.swift @@ -9,9 +9,14 @@ final class BuzzPushSnapshotBridge { private let endpointGrantStore: BuzzPushEndpointGrantKeychainStore private let interactionDeletionDeadline: BuzzInteractionDeletionDeadline private let keychainAccessGroup: String? + // Flutter awaits every call here, and the age-gate restore runs during app + // launch, when this process is busiest. At utility QoS these replies were the + // lowest-priority work in the process and stalled for over 10 s on saturated + // CI hosts while main-thread work kept moving; user-initiated is the class + // for work a caller is waiting on. private let queue = DispatchQueue( label: "xyz.block.buzz.push-snapshot", - qos: .utility + qos: .userInitiated ) private lazy var store: BuzzPushPresentationCacheStore? = { guard let container = containerURL() else { return nil } @@ -31,7 +36,7 @@ final class BuzzPushSnapshotBridge { INInteraction.deleteAll(completion: completion) }, scheduleTimeout: { delay, action in - DispatchQueue.global(qos: .utility).asyncAfter( + DispatchQueue.global(qos: .userInitiated).asyncAfter( deadline: .now() + delay, execute: action ) diff --git a/mobile/ios/RunnerTests/BuzzCommunicationNotificationTests.swift b/mobile/ios/RunnerTests/BuzzCommunicationNotificationTests.swift index a9960a70b5..8ade99d81f 100644 --- a/mobile/ios/RunnerTests/BuzzCommunicationNotificationTests.swift +++ b/mobile/ios/RunnerTests/BuzzCommunicationNotificationTests.swift @@ -331,6 +331,7 @@ final class BuzzPushSnapshotEnrichmentTests: XCTestCase { let snapshotURL = directory.appendingPathComponent(BuzzPushPresentationCacheStore.fileName) let original = try Data(contentsOf: snapshotURL) var deletionCalls = 0 + var deletionQoS: [qos_class_t] = [] let bridge = BuzzPushSnapshotBridge( appGroupIdentifier: nil, endpointGrantStore: BuzzPushEndpointGrantKeychainStore(accessGroup: nil), @@ -340,6 +341,7 @@ final class BuzzPushSnapshotEnrichmentTests: XCTestCase { timeout: 5, deleteAllInteractions: { completion in deletionCalls += 1 + deletionQoS.append(qos_class_self()) completion(deletionCalls == 1 ? NSError(domain: "InjectedCleanupFailure", code: 1) : nil) }, scheduleTimeout: { _, _ in } @@ -370,6 +372,8 @@ final class BuzzPushSnapshotEnrichmentTests: XCTestCase { XCTAssertNil(restoreResult) XCTAssertFalse(BuzzAgeRestrictionSession.isRestricted(containerURL: directory)) XCTAssertEqual(deletionCalls, 2) + // Flutter awaits these replies; a lower QoS left them starved on busy hosts. + XCTAssertEqual(deletionQoS, [QOS_CLASS_USER_INITIATED, QOS_CLASS_USER_INITIATED]) XCTAssertEqual(try Data(contentsOf: snapshotURL), original) }