Stabilize beta release with warning cleanup and edge-case fixes
This commit is contained in:
@@ -13,7 +13,7 @@ actor ItineraryItemService {
|
||||
// Debounce tracking
|
||||
private var pendingUpdates: [UUID: ItineraryItem] = [:]
|
||||
private var retryCount: [UUID: Int] = [:]
|
||||
private var debounceTask: Task<Void, Never>?
|
||||
private var flushTask: Task<Void, Never>?
|
||||
|
||||
private let maxRetries = 3
|
||||
private let debounceInterval: Duration = .seconds(1.5)
|
||||
@@ -22,8 +22,8 @@ actor ItineraryItemService {
|
||||
|
||||
/// Cancel any pending debounce task (for cleanup)
|
||||
func cancelPendingSync() {
|
||||
debounceTask?.cancel()
|
||||
debounceTask = nil
|
||||
flushTask?.cancel()
|
||||
flushTask = nil
|
||||
}
|
||||
|
||||
// MARK: - CRUD Operations
|
||||
@@ -52,26 +52,7 @@ actor ItineraryItemService {
|
||||
func updateItem(_ item: ItineraryItem) async {
|
||||
pendingUpdates[item.id] = item
|
||||
|
||||
// Cancel existing debounce task
|
||||
debounceTask?.cancel()
|
||||
|
||||
// Start new debounce task with proper cancellation handling
|
||||
debounceTask = Task {
|
||||
// Check cancellation before sleeping
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
do {
|
||||
try await Task.sleep(for: debounceInterval)
|
||||
} catch {
|
||||
// Task was cancelled during sleep
|
||||
return
|
||||
}
|
||||
|
||||
// Check cancellation after sleeping (belt and suspenders)
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
await flushPendingUpdates()
|
||||
}
|
||||
scheduleFlush(after: debounceInterval)
|
||||
}
|
||||
|
||||
/// Force immediate sync of pending updates
|
||||
@@ -92,15 +73,47 @@ actor ItineraryItemService {
|
||||
retryCount[item.id] = nil
|
||||
}
|
||||
} catch {
|
||||
// Add back to pending for retry, respecting max retry limit
|
||||
// Keep failed updates queued and retry with backoff; never drop user edits.
|
||||
var highestRetry = 0
|
||||
for item in updates.values {
|
||||
let currentRetries = retryCount[item.id] ?? 0
|
||||
if currentRetries < maxRetries {
|
||||
pendingUpdates[item.id] = item
|
||||
retryCount[item.id] = currentRetries + 1
|
||||
}
|
||||
// If max retries exceeded, silently drop the update to prevent infinite loop
|
||||
let nextRetry = min(currentRetries + 1, maxRetries)
|
||||
retryCount[item.id] = nextRetry
|
||||
highestRetry = max(highestRetry, nextRetry)
|
||||
pendingUpdates[item.id] = item
|
||||
}
|
||||
|
||||
let retryDelay = retryDelay(for: highestRetry)
|
||||
scheduleFlush(after: retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Flush Scheduling
|
||||
|
||||
private func scheduleFlush(after delay: Duration) {
|
||||
flushTask?.cancel()
|
||||
flushTask = Task {
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
do {
|
||||
try await Task.sleep(for: delay)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
guard !Task.isCancelled else { return }
|
||||
await flushPendingUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
private func retryDelay(for retryLevel: Int) -> Duration {
|
||||
switch retryLevel {
|
||||
case 0, 1:
|
||||
return .seconds(5)
|
||||
case 2:
|
||||
return .seconds(15)
|
||||
default:
|
||||
return .seconds(30)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +129,7 @@ actor ItineraryItemService {
|
||||
|
||||
for item in items {
|
||||
let recordId = CKRecord.ID(recordName: item.id.uuidString)
|
||||
try? await database.deleteRecord(withID: recordId)
|
||||
_ = try? await database.deleteRecord(withID: recordId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user