66//
77
88import struct Foundation. Date
9+ import struct Foundation. TimeInterval
910import class Smithy. Context
1011import enum Smithy. ClientError
1112import protocol Smithy. RequestMessage
@@ -76,6 +77,7 @@ public struct Orchestrator<
7677 private let deserialize : ( ResponseType , Context ) async throws -> OutputType
7778 private let retryStrategy : ( any RetryStrategy ) ?
7879 private let retryErrorInfoProvider : ( Error ) -> RetryErrorInfo ?
80+ private let clockSkewProvider : ClockSkewProvider < RequestType , ResponseType >
7981 private let telemetry : OrchestratorTelemetry
8082 private let selectAuthScheme : SelectAuthScheme
8183 private let applyEndpoint : any ApplyEndpoint < RequestType >
@@ -96,6 +98,12 @@ public struct Orchestrator<
9698 self . retryErrorInfoProvider = { _ in nil }
9799 }
98100
101+ if let clockSkewProvider = builder. clockSkewProvider {
102+ self . clockSkewProvider = clockSkewProvider
103+ } else {
104+ self . clockSkewProvider = { ( _, _, _, _) in nil }
105+ }
106+
99107 if let selectAuthScheme = builder. selectAuthScheme {
100108 self . selectAuthScheme = selectAuthScheme
101109 } else {
@@ -262,9 +270,29 @@ public struct Orchestrator<
262270 do {
263271 _ = try context. getOutput ( )
264272 await strategy. recordSuccess ( token: token)
265- } catch let error {
266- // If we can't get errorInfo, we definitely can't retry
267- guard let errorInfo = retryErrorInfoProvider ( error) else { return }
273+ } catch {
274+ let clockSkewStore = ClockSkewStore . shared
275+ var clockSkewErrorInfo : RetryErrorInfo ?
276+
277+ // Clock skew can't be calculated when there is no request/response, so safe-unwrap them
278+ if let request = context. getRequest ( ) , let response = context. getResponse ( ) {
279+ // Assign clock skew to local var to prevent capturing self in block below
280+ let clockSkewProvider = self . clockSkewProvider
281+ // Check for clock skew, and if found, store in the shared map of hosts to clock skews
282+ let clockSkewDidChange = await clockSkewStore. setClockSkew ( host: request. host) { @Sendable previous in
283+ clockSkewProvider ( request, response, error, previous)
284+ }
285+ // Retry only if the new clock skew is different than previous.
286+ // If clock skew was unchanged on this errored request, then clock skew is likely not the
287+ // cause of the error
288+ if clockSkewDidChange {
289+ clockSkewErrorInfo = . clockSkewErrorInfo
290+ }
291+ }
292+
293+ // If clock skew was found or has substantially changed, then retry on that
294+ // Else get errorInfo on the error
295+ guard let errorInfo = clockSkewErrorInfo ?? retryErrorInfoProvider ( error) else { return }
268296
269297 // If the body is a nonseekable stream, we also can't retry
270298 do {
@@ -459,3 +487,11 @@ public struct Orchestrator<
459487 }
460488 }
461489}
490+
491+ private extension RetryErrorInfo {
492+
493+ /// `RetryErrorInfo` value used to signal that a retry should be performed due to clock skew.
494+ static var clockSkewErrorInfo : RetryErrorInfo {
495+ RetryErrorInfo ( errorType: . clientError, retryAfterHint: nil , isTimeout: false )
496+ }
497+ }
0 commit comments