commit f1b12ffae6f3ee74572bf538ceb23149b3b333cc
parent ccf6c5adbaec793931f41ddd45b1fa748b4a51ef
Author: Michael Camilleri <[email protected]>
Date: Wed, 13 May 2026 04:44:33 +0900
Parallelise fetchPushPingsDirect across zones
The ping fast-path looped serially over every known zone in the database scope,
issuing one CKQuery per zone and appending the results. For a user with sixteen
active games that meant sixteen sequential round-trips on every silent-push
wake — the work blocking a collaborator's 'I just joined' or 'I just solved'
toast from reaching the screen.
The per-zone Ping queries are now dispatched through a TaskGroup, mirroring the
pattern fetchKnownZoneUpdatesDirect already uses. The actor's await points
release isolation between round-trips, so the per-zone CK requests actually
overlap. Per-zone errors are caught and traced rather than propagated, so one
transient failure doesn't suppress notifications from healthy zones.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Diffstat:
1 file changed, 39 insertions(+), 17 deletions(-)
diff --git a/Crossmate/Sync/SyncEngine.swift b/Crossmate/Sync/SyncEngine.swift
@@ -586,24 +586,46 @@ actor SyncEngine {
let scopeCheckpoint = pingPushCheckpoints[scopeValue]?
.addingTimeInterval(-pingPushCheckpointOverlap)
- var collected: [CKRecord] = []
- for (zoneID, createdAt) in zones {
- // Scope checkpoint (if present) wins — it's forward-moving across
- // all zones. On first run for a given scope we fall back to the
- // game's createdAt floor so the ping that triggered this wake is
- // still in range, but pings older than the device's first
- // knowledge of the game are not.
- let since = scopeCheckpoint
- ?? createdAt.addingTimeInterval(-pingPushCheckpointOverlap)
- let recs = try await queryLiveRecords(
- type: "Ping",
- database: database,
- zoneID: zoneID,
- since: since,
- desiredKeys: ["authorID", "playerName", "puzzleTitle", "kind", "scope"]
- )
- collected.append(contentsOf: recs)
+ // Fan the per-zone Ping queries out concurrently. The actor's await
+ // points release isolation between round-trips, so the per-zone CK
+ // requests overlap; a serial N-zone scan becomes a single parallel
+ // batch. Per-zone errors are caught and traced so one transient
+ // failure doesn't suppress notifications from healthy zones.
+ let perZoneRecords = await withTaskGroup(of: [CKRecord].self) { group in
+ for (zoneID, createdAt) in zones {
+ // Scope checkpoint (if present) wins — it's forward-moving
+ // across all zones. On first run for a given scope we fall
+ // back to the game's createdAt floor so the ping that
+ // triggered this wake is still in range, but pings older
+ // than the device's first knowledge of the game are not.
+ let since = scopeCheckpoint
+ ?? createdAt.addingTimeInterval(-pingPushCheckpointOverlap)
+ group.addTask { [weak self] in
+ guard let self else { return [] }
+ do {
+ return try await self.queryLiveRecords(
+ type: "Ping",
+ database: database,
+ zoneID: zoneID,
+ since: since,
+ desiredKeys: ["authorID", "playerName", "puzzleTitle", "kind", "scope"]
+ )
+ } catch {
+ await self.trace(
+ "\(label) ping fast-path: zone \(zoneID.zoneName) failed: " +
+ "\(error.localizedDescription)"
+ )
+ return []
+ }
+ }
+ }
+ var all: [[CKRecord]] = []
+ for await batch in group {
+ all.append(batch)
+ }
+ return all
}
+ let collected: [CKRecord] = perZoneRecords.flatMap { $0 }
let pings = collected.compactMap(Self.parsePingRecord)
if let latest = collected.compactMap(\.modificationDate).max() {