Skip to content

Commit 1961d61

Browse files
authored
fix(DataStore): retry initial sync network failures from RemoteSyncEngine (#1773)
* fix(DataStore): retry initial sync network failures from RemoteSyncEngine * address PR comments * add functional test * address PR comment
1 parent 5c6d69d commit 1961d61

File tree

4 files changed

+64
-10
lines changed

4 files changed

+64
-10
lines changed

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/InitialSync/InitialSyncOrchestrator.swift

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,16 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator {
158158
return .successfulVoid
159159
}
160160

161+
var underlyingError: Error?
162+
if let error = syncErrors.first(where: isNetworkError(_:)) {
163+
underlyingError = getUnderlyingNetworkError(error)
164+
}
165+
161166
let allMessages = syncErrors.map { String(describing: $0) }
162167
let syncError = DataStoreError.sync(
163168
"One or more errors occurred syncing models. See below for detailed error description.",
164-
allMessages.joined(separator: "\n")
169+
allMessages.joined(separator: "\n"),
170+
underlyingError
165171
)
166172
return .failure(syncError)
167173
}
@@ -219,4 +225,36 @@ extension AWSInitialSyncOrchestrator {
219225
}
220226
return false
221227
}
228+
229+
private func isNetworkError(_ error: DataStoreError) -> Bool {
230+
guard case let .sync(_, _, underlyingError) = error,
231+
let datastoreError = underlyingError as? DataStoreError
232+
else {
233+
return false
234+
}
235+
236+
if case let .api(amplifyError, _) = datastoreError,
237+
let apiError = amplifyError as? APIError,
238+
case .networkError = apiError {
239+
return true
240+
}
241+
242+
return false
243+
}
244+
245+
private func getUnderlyingNetworkError(_ error: DataStoreError) -> Error? {
246+
guard case let .sync(_, _, underlyingError) = error,
247+
let datastoreError = underlyingError as? DataStoreError
248+
else {
249+
return nil
250+
}
251+
252+
if case let .api(amplifyError, _) = datastoreError,
253+
let apiError = amplifyError as? APIError,
254+
case let .networkError(_, _, underlyingError) = apiError {
255+
return underlyingError
256+
}
257+
258+
return nil
259+
}
222260
}

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Sync/RemoteSyncEngine+Retryable.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,15 @@ extension RemoteSyncEngine {
3030
}
3131

3232
private func getRetryAdvice(error: Error) -> RequestRetryAdvice {
33-
//TODO: Parse error from the receive completion to use as an input into getting retry advice.
34-
// For now, specifying not connected to internet to force a retry up to our maximum
35-
let urlError = URLError(.notConnectedToInternet)
36-
let advice = requestRetryablePolicy.retryRequestAdvice(urlError: urlError,
33+
var urlErrorOptional: URLError?
34+
if let dataStoreError = error as? DataStoreError,
35+
let underlyingError = dataStoreError.underlyingError as? URLError {
36+
urlErrorOptional = underlyingError
37+
} else if let urlError = error as? URLError {
38+
urlErrorOptional = urlError
39+
}
40+
41+
let advice = requestRetryablePolicy.retryRequestAdvice(urlError: urlErrorOptional,
3742
httpURLResponse: nil,
3843
attemptNumber: currentAttemptNumber)
3944
return advice

AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSyncEngineTests.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,15 @@ class RemoteSyncEngineTests: XCTestCase {
7272
let subscriptionsEstablishedReceived = expectation(description: "subscriptionsEstablished received")
7373
let cleanedup = expectation(description: "cleanedup")
7474
let failureOnInitialSync = expectation(description: "failureOnInitialSync")
75-
75+
let retryAdviceReceivedNetworkError = expectation(description: "retry advice received network error")
7676
var currCount = 1
7777

7878
let advice = RequestRetryAdvice.init(shouldRetry: false)
7979
mockRequestRetryablePolicy.pushOnRetryRequestAdvice(response: advice)
80+
mockRequestRetryablePolicy.setOnRetryRequestAdvice { urlError, httpURLResponse, attemptNumber in
81+
XCTAssertNotNil(urlError)
82+
retryAdviceReceivedNetworkError.fulfill()
83+
}
8084

8185
let filter = HubFilters.forEventName(HubPayload.EventName.DataStore.subscriptionsEstablished)
8286
let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { payload in
@@ -116,8 +120,8 @@ class RemoteSyncEngineTests: XCTestCase {
116120
XCTFail("Unexpected case gets hit")
117121
}
118122
})
119-
MockAWSInitialSyncOrchestrator.setResponseOnSync(result:
120-
.failure(DataStoreError.internalOperation("forceError", "none", nil)))
123+
MockAWSInitialSyncOrchestrator.setResponseOnSync(result: .failure(
124+
DataStoreError.internalOperation("forceError", "none", URLError(.notConnectedToInternet))))
121125

122126
remoteSyncEngine.start(api: apiPlugin)
123127

@@ -128,7 +132,8 @@ class RemoteSyncEngineTests: XCTestCase {
128132
subscriptionsInitialized,
129133
subscriptionsEstablishedReceived,
130134
cleanedup,
131-
failureOnInitialSync], timeout: defaultAsyncWaitTimeout)
135+
failureOnInitialSync,
136+
retryAdviceReceivedNetworkError], timeout: defaultAsyncWaitTimeout)
132137
remoteSyncEngineSink.cancel()
133138
Amplify.Hub.removeListener(hubListener)
134139
}

AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/Mocks/MockRequestRetryablePolicy.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@ import Foundation
1515
class MockRequestRetryablePolicy: RequestRetryablePolicy {
1616

1717
var responseQueue: [RequestRetryAdvice] = []
18+
var onRetryRequestAdvice: ((URLError?, HTTPURLResponse?, Int) -> Void)?
1819

1920
func pushOnRetryRequestAdvice(response: RequestRetryAdvice) {
2021
responseQueue.append(response)
2122
}
2223

24+
func setOnRetryRequestAdvice(onRetryRequestAdvice: @escaping (URLError?, HTTPURLResponse?, Int) -> Void) {
25+
self.onRetryRequestAdvice = onRetryRequestAdvice
26+
}
27+
2328
override func retryRequestAdvice(urlError: URLError?,
2429
httpURLResponse: HTTPURLResponse?,
2530
attemptNumber: Int) -> RequestRetryAdvice {
31+
onRetryRequestAdvice?(urlError, httpURLResponse, attemptNumber)
2632
// If this breaks, you didn't push anything onto the queue
27-
responseQueue.removeFirst()
33+
return responseQueue.removeFirst()
2834
}
2935
}

0 commit comments

Comments
 (0)