@@ -7,6 +7,7 @@ import com.lithic.api.core.RequestOptions
77import com.lithic.api.errors.LithicIoException
88import java.io.IOException
99import java.time.Clock
10+ import java.time.Duration
1011import java.time.OffsetDateTime
1112import java.time.format.DateTimeFormatter
1213import java.time.format.DateTimeParseException
@@ -59,7 +60,7 @@ private constructor(
5960 }
6061
6162 val backoffMillis = getRetryBackoffMillis(retries, response)
62- Thread .sleep(backoffMillis)
63+ Thread .sleep(backoffMillis.toMillis() )
6364 }
6465 }
6566
@@ -95,7 +96,7 @@ private constructor(
9596 }
9697
9798 val backoffMillis = getRetryBackoffMillis(retries, response)
98- return sleepAsync(backoffMillis).thenCompose {
99+ return sleepAsync(backoffMillis.toMillis() ).thenCompose {
99100 wrap(httpClient.executeAsync(request, requestOptions))
100101 }
101102 },
@@ -113,8 +114,7 @@ private constructor(
113114
114115 private fun isRetryable (request : HttpRequest ): Boolean {
115116 // Some requests, such as when a request body is being streamed, cannot be retried because
116- // the body data aren't
117- // available on subsequent attempts.
117+ // the body data aren't available on subsequent attempts.
118118 return request.body?.repeatable() ? : true
119119 }
120120
@@ -151,39 +151,53 @@ private constructor(
151151
152152 private fun shouldRetry (throwable : Throwable ): Boolean {
153153 // Only retry IOException and LithicIoException, other exceptions are not intended to be
154- // retried
154+ // retried.
155155 return throwable is IOException || throwable is LithicIoException
156156 }
157157
158- private fun getRetryBackoffMillis (retries : Int , response : HttpResponse ? ): Long {
158+ private fun getRetryBackoffMillis (retries : Int , response : HttpResponse ? ): Duration {
159159 // About the Retry-After header:
160160 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
161- val retryAfter =
162- response?.headers()?.get(" Retry-After" )?.getOrNull(0 )?.let { retryAfter ->
163- retryAfter.toLongOrNull()
164- ? : try {
165- ChronoUnit .SECONDS .between(
166- OffsetDateTime .now(clock),
167- OffsetDateTime .parse(retryAfter, DateTimeFormatter .RFC_1123_DATE_TIME )
168- )
169- } catch (e: DateTimeParseException ) {
170- null
161+ response
162+ ?.headers()
163+ ?.let { headers ->
164+ headers
165+ .get(" Retry-After-Ms" )
166+ .getOrNull(0 )
167+ ?.toFloatOrNull()
168+ ?.times(TimeUnit .MILLISECONDS .toNanos(1 ))
169+ ? : headers.get(" Retry-After" ).getOrNull(0 )?.let { retryAfter ->
170+ retryAfter.toFloatOrNull()?.times(TimeUnit .SECONDS .toNanos(1 ))
171+ ? : try {
172+ ChronoUnit .MILLIS .between(
173+ OffsetDateTime .now(clock),
174+ OffsetDateTime .parse(
175+ retryAfter,
176+ DateTimeFormatter .RFC_1123_DATE_TIME
177+ )
178+ )
179+ } catch (e: DateTimeParseException ) {
180+ null
181+ }
171182 }
172183 }
173-
174- // If the API asks us to wait a certain amount of time (and it's a reasonable amount), just
175- // do what it says.
176- if (retryAfter != null && retryAfter in 1 .. 60 ) {
177- return TimeUnit .SECONDS .toMillis(retryAfter)
178- }
184+ ?.let { retryAfterNanos ->
185+ // If the API asks us to wait a certain amount of time (and it's a reasonable
186+ // amount), just
187+ // do what it says.
188+ val retryAfter = Duration .ofNanos(retryAfterNanos.toLong())
189+ if (retryAfter in Duration .ofNanos(0 ).. Duration .ofMinutes(1 )) {
190+ return retryAfter
191+ }
192+ }
179193
180194 // Apply exponential backoff, but not more than the max.
181195 val backoffSeconds = min(0.5 * 2.0 .pow(retries - 1 ), 8.0 )
182196
183197 // Apply some jitter
184198 val jitter = 1.0 - 0.25 * ThreadLocalRandom .current().nextDouble()
185199
186- return ( TimeUnit .SECONDS .toMillis (1 ) * backoffSeconds * jitter).toLong()
200+ return Duration .ofNanos(( TimeUnit .SECONDS .toNanos (1 ) * backoffSeconds * jitter).toLong() )
187201 }
188202
189203 private fun sleepAsync (millis : Long ): CompletableFuture <Void > {
0 commit comments