@@ -59,18 +59,72 @@ public final class RetryPolicyService {
5959 /// Optional maximum total duration allowed for all retry attempts.
6060 private let maxTotalDuration : DispatchTimeInterval ?
6161
62+ /// An optional logger used to record retry attempts and related events.
63+ private let logger : ILogger ?
64+
6265 // MARK: Initialization
6366
6467 /// Initializes a new instance of `RetryPolicyService`.
6568 ///
6669 /// - Parameters:
6770 /// - strategy: The strategy that determines how retries are performed.
6871 /// - maxTotalDuration: Optional maximum duration for all retries combined. If `nil`,
69- /// retries can continue indefinitely based on the
70- /// strategy.
71- public init ( strategy: RetryPolicyStrategy , maxTotalDuration: DispatchTimeInterval ? = nil ) {
72+ /// retries can continue indefinitely based on the strategy.
73+ /// - logger: An optional logger for capturing retry-related information.
74+ public init (
75+ strategy: RetryPolicyStrategy ,
76+ maxTotalDuration: DispatchTimeInterval ? = nil ,
77+ logger: ILogger ? = nil
78+ ) {
7279 self . strategy = strategy
7380 self . maxTotalDuration = maxTotalDuration
81+ self . logger = logger
82+ }
83+
84+ // MARK: Private
85+
86+ private func calculateDeadline( ) -> Date ? {
87+ maxTotalDuration? . nanoseconds. map {
88+ Date ( ) . addingTimeInterval ( TimeInterval ( $0) / 1_000_000_000 )
89+ }
90+ }
91+
92+ private func checkDeadline( _ deadline: Date ? , attempt: Int ) throws {
93+ if let deadline, Date ( ) > deadline {
94+ logger? . error ( " [RetryPolicy] Total duration exceeded after \( attempt) attempt(s). " )
95+ throw RetryPolicyError . totalDurationExceeded
96+ }
97+ }
98+
99+ private func handleRetryDecision(
100+ error: Error ,
101+ onFailure: ( @Sendable ( Error) async -> Bool ) ? ,
102+ iterator: inout some IteratorProtocol < UInt64 > ,
103+ attempt: Int
104+ ) async throws {
105+ if let onFailure, await !onFailure( error) {
106+ logger? . warning ( " [RetryPolicy] Stopped retrying after \( attempt) attempt(s) — onFailure returned false. " )
107+ throw error
108+ }
109+
110+ guard let duration = iterator. next ( ) else {
111+ logger? . error ( " [RetryPolicy] Retry limit exceeded after \( attempt) attempt(s). " )
112+ throw RetryPolicyError . retryLimitExceeded
113+ }
114+
115+ logger? . info ( " [RetryPolicy] Waiting \( duration) ns before attempt \( attempt + 1 ) ... " )
116+ try Task . checkCancellation ( )
117+ try await Task . sleep ( nanoseconds: duration)
118+ }
119+
120+ private func logSuccess( attempt: Int ) {
121+ if attempt > 0 {
122+ logger? . info ( " [RetryPolicy] Succeeded after \( attempt + 1 ) attempt(s). " )
123+ }
124+ }
125+
126+ private func logFailure( attempt: Int , error: Error ) {
127+ logger? . warning ( " [RetryPolicy] Attempt \( attempt) failed: \( error. localizedDescription) . " )
74128 }
75129}
76130
@@ -91,34 +145,27 @@ extension RetryPolicyService: IRetryPolicyService {
91145 _ closure: @Sendable ( ) async throws -> T
92146 ) async throws -> T {
93147 let effectiveStrategy = strategy ?? self . strategy
94-
95148 var iterator = RetrySequence ( strategy: effectiveStrategy) . makeIterator ( )
96-
97- let deadline = maxTotalDuration? . nanoseconds. map {
98- Date ( ) . addingTimeInterval ( TimeInterval ( $0) / 1_000_000_000 )
99- }
149+ let deadline = calculateDeadline ( )
150+ var attempt = 0
100151
101152 while true {
102- if let deadline, Date ( ) > deadline {
103- throw RetryPolicyError . totalDurationExceeded
104- }
153+ try checkDeadline ( deadline, attempt: attempt)
105154
106155 do {
107- return try await closure ( )
156+ let result = try await closure ( )
157+ logSuccess ( attempt: attempt)
158+ return result
108159 } catch {
109- let shouldContinue = await onFailure ? ( error) ?? true
110-
111- if !shouldContinue {
112- throw error
113- }
114-
115- guard let duration = iterator. next ( ) else {
116- throw RetryPolicyError . retryLimitExceeded
117- }
118-
119- try Task . checkCancellation ( )
120-
121- try await Task . sleep ( nanoseconds: duration)
160+ attempt += 1
161+ logFailure ( attempt: attempt, error: error)
162+
163+ try await handleRetryDecision (
164+ error: error,
165+ onFailure: onFailure,
166+ iterator: & iterator,
167+ attempt: attempt
168+ )
122169 }
123170 }
124171 }
0 commit comments