Pure retry policy for transient HTTP failures in the Supabase Clojure SDK.
This namespace decides whether and how long to wait before retrying a
failed request. It performs no sleeping and no I/O: supabase.core.http
calls these functions from its retry loop and owns the actual waiting.
A failure is worth retrying when it is likely to resolve on its own:
transient-status?)transient-exception?, which also walks the cause chain)Delays combine two sources, with the server always winning:
Retry-After response header, when present and parseable
(retry-after-ms), either as delay-seconds or an RFC 1123 HTTP-date.backoff-ms), capped at
:max-delay-ms.next-delay-ms applies that precedence. merge-opts resolves client-level
and request-level retry options against default-opts.
(require '[supabase.core.retry :as retry])
(retry/transient-status? 503) ;; => true
(retry/retry-after-ms {"retry-after" "3"}) ;; => 3000
(retry/backoff-ms 2) ;; => long in [0, 400]
(retry/next-delay-ms 2 {"retry-after" "3"}) ;; => 3000
(retry/merge-opts {:max-attempts 5} true) ;; => {:max-attempts 5, ...}
Pure retry policy for transient HTTP failures in the Supabase Clojure SDK.
This namespace decides *whether* and *how long* to wait before retrying a
failed request. It performs no sleeping and no I/O: `supabase.core.http`
calls these functions from its retry loop and owns the actual waiting.
## Transient failures
A failure is worth retrying when it is likely to resolve on its own:
- HTTP statuses 429, 502, 503, 504 (`transient-status?`)
- Transport exceptions: connection refused, connect/read timeouts, no
route to host, reset or broken-pipe sockets, and unexpected EOF
(`transient-exception?`, which also walks the cause chain)
## Delay computation
Delays combine two sources, with the server always winning:
1. A `Retry-After` response header, when present and parseable
(`retry-after-ms`), either as delay-seconds or an RFC 1123 HTTP-date.
2. Exponential backoff with full jitter (`backoff-ms`), capped at
`:max-delay-ms`.
`next-delay-ms` applies that precedence. `merge-opts` resolves client-level
and request-level retry options against `default-opts`.
## Usage
(require '[supabase.core.retry :as retry])
(retry/transient-status? 503) ;; => true
(retry/retry-after-ms {"retry-after" "3"}) ;; => 3000
(retry/backoff-ms 2) ;; => long in [0, 400]
(retry/next-delay-ms 2 {"retry-after" "3"}) ;; => 3000
(retry/merge-opts {:max-attempts 5} true) ;; => {:max-attempts 5, ...}(backoff-ms attempt)(backoff-ms attempt opts)Exponential backoff with full jitter for the given 1-based attempt.
The delay cap is min(max-delay-ms, initial-delay-ms * multiplier^(attempt-1));
the result is a uniformly random long in [0, cap]. Missing option keys fall
back to default-opts.
(backoff-ms 1) ;; => long in [0, 200] with default opts
Exponential backoff with full jitter for the given 1-based `attempt`.
The delay cap is `min(max-delay-ms, initial-delay-ms * multiplier^(attempt-1))`;
the result is a uniformly random long in [0, cap]. Missing option keys fall
back to `default-opts`.
(backoff-ms 1) ;; => long in [0, 200] with default opts(merge-opts client-opts request-opts)Resolves retry options from defaults, client-level opts, and a request-level override.
The request override may be:
:max-attempts NClient opts may likewise be true (use defaults), an integer
(:max-attempts), or a map. Returns a resolved opts map containing every
key of default-opts, or nil when retries are disabled.
(merge-opts {:max-attempts 5} false) ;; => nil
(merge-opts true 5) ;; => (assoc default-opts :max-attempts 5)
Resolves retry options from defaults, client-level opts, and a
request-level override.
The request override may be:
- nil/absent: client opts win
- false: retry disabled (returns nil)
- true: client opts (or defaults) as-is
- integer N: client opts with `:max-attempts` N
- map: merged over client opts
Client opts may likewise be true (use defaults), an integer
(`:max-attempts`), or a map. Returns a resolved opts map containing every
key of `default-opts`, or nil when retries are disabled.
(merge-opts {:max-attempts 5} false) ;; => nil
(merge-opts true 5) ;; => (assoc default-opts :max-attempts 5)(next-delay-ms attempt headers)(next-delay-ms attempt headers opts)Delay in milliseconds before the next attempt.
An honored Retry-After header in headers wins over computed backoff;
otherwise returns backoff-ms for attempt. Returns a long.
(next-delay-ms 1 {"retry-after" "3"}) ;; => 3000
(next-delay-ms 1 {}) ;; => long in [0, 200]
Delay in milliseconds before the next attempt.
An honored Retry-After header in `headers` wins over computed backoff;
otherwise returns `backoff-ms` for `attempt`. Returns a long.
(next-delay-ms 1 {"retry-after" "3"}) ;; => 3000
(next-delay-ms 1 {}) ;; => long in [0, 200](retry-after-ms headers)Parses a Retry-After header value from a response headers map.
Headers arrive with lower-case string keys, so the lookup key is "retry-after". The value may be a non-negative integer (delay-seconds) or an HTTP-date (RFC 1123, e.g. "Tue, 18 Aug 2026 12:00:00 GMT"). Negative integer values are clamped to 0. Returns the delay in milliseconds as a long, or nil when the header is absent or unparseable. Never throws.
(retry-after-ms {"retry-after" "3"}) ;; => 3000
(retry-after-ms {}) ;; => nil
Parses a Retry-After header value from a response headers map.
Headers arrive with lower-case string keys, so the lookup key is
"retry-after". The value may be a non-negative integer (delay-seconds)
or an HTTP-date (RFC 1123, e.g. "Tue, 18 Aug 2026 12:00:00 GMT").
Negative integer values are clamped to 0. Returns the delay in
milliseconds as a long, or nil when the header is absent or unparseable.
Never throws.
(retry-after-ms {"retry-after" "3"}) ;; => 3000
(retry-after-ms {}) ;; => nil(transient-exception? ex)True for exceptions that indicate a transient transport failure:
java.net.ConnectException, java.net.http.HttpConnectTimeoutException,
java.net.http.HttpTimeoutException, java.net.NoRouteToHostException,
java.net.SocketException whose message contains "Connection reset" or
"Broken pipe" (case-insensitive), and java.io.EOFException.
Walks the cause chain, so a transient failure wrapped in another exception
(for example via ex-info) is still detected.
True for exceptions that indicate a transient transport failure: `java.net.ConnectException`, `java.net.http.HttpConnectTimeoutException`, `java.net.http.HttpTimeoutException`, `java.net.NoRouteToHostException`, `java.net.SocketException` whose message contains "Connection reset" or "Broken pipe" (case-insensitive), and `java.io.EOFException`. Walks the cause chain, so a transient failure wrapped in another exception (for example via `ex-info`) is still detected.
(transient-status? status)True for HTTP statuses worth retrying: 429, 502, 503, 504.
True for HTTP statuses worth retrying: 429, 502, 503, 504.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |