Class: GraphWeaver::Retry
- Inherits:
-
Object
- Object
- GraphWeaver::Retry
- Defined in:
- lib/graph_weaver/retry.rb
Overview
Wraps any client/transport with configurable retries — it satisfies the same execute contract, so it layers over HTTP, Faraday, or anything else:
client = GraphWeaver::Retry.new(
GraphWeaver::Transport::HTTP.new(url),
tries: 5, # total attempts, first included
on: [GraphWeaver::TransportError, GraphWeaver::ServerError],
backoff: :exponential, # or :linear, or ->(attempt) { seconds }
base: 0.5, max: 30, # seconds; delays clamp at max:
jitter: true, # randomize each delay by 50-100%
retry_codes: ["THROTTLED"], # also retry GraphQL errors by code
)
What retries, by default:
- TransportError: always (the request never arrived)
- ServerError: only 5xx — a 4xx is a bug in the request, retrying
won't fix it. Override with retry_if: ->(error) { ... }
- responses whose GraphQL error codes intersect retry_codes: (off by
default — pass the codes your API uses for transient failures)
Exhausting tries re-raises the last error (or returns the last code-matched response).
Constant Summary collapse
- BACKOFFS =
{ exponential: ->(base, attempt) { base * (2**(attempt - 1)) }, linear: ->(base, attempt) { base * attempt }, }.freeze
- DEFAULT_RETRY_IF =
retry 5xx, not 4xx; everything else listed in on: retries
lambda do |error| !error.is_a?(GraphWeaver::ServerError) || error.status >= 500 end
Instance Method Summary collapse
- #execute(query, variables: {}) ⇒ Object
-
#initialize(client, tries: 3, on: [GraphWeaver::TransportError, GraphWeaver::ServerError], backoff: :exponential, base: 0.5, max: 30, jitter: true, retry_if: DEFAULT_RETRY_IF, retry_codes: [], sleeper: nil) ⇒ Retry
constructor
A new instance of Retry.
-
#url ⇒ Object
surface the wrapped transport's endpoint (schema-dump provenance).
Constructor Details
#initialize(client, tries: 3, on: [GraphWeaver::TransportError, GraphWeaver::ServerError], backoff: :exponential, base: 0.5, max: 30, jitter: true, retry_if: DEFAULT_RETRY_IF, retry_codes: [], sleeper: nil) ⇒ Retry
Returns a new instance of Retry.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/graph_weaver/retry.rb', line 40 def initialize(client, tries: 3, on: [GraphWeaver::TransportError, GraphWeaver::ServerError], backoff: :exponential, base: 0.5, max: 30, jitter: true, retry_if: DEFAULT_RETRY_IF, retry_codes: [], sleeper: nil) raise ArgumentError, "tries: must be >= 1" unless tries >= 1 @client = client @tries = tries @on = on @backoff = if backoff.is_a?(Proc) ->(_base, attempt) { backoff.call(attempt) } # custom: ->(attempt) { seconds } else BACKOFFS.fetch(backoff) { raise ArgumentError, "backoff: must be :exponential, :linear, or a Proc, got #{backoff.inspect}" } end @base = base @max = max @jitter = jitter @retry_if = retry_if @retry_codes = retry_codes @sleeper = sleeper || ->(seconds) { sleep(seconds) } end |
Instance Method Details
#execute(query, variables: {}) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/graph_weaver/retry.rb', line 68 def execute(query, variables: {}) attempt = 0 loop do attempt += 1 begin response = @client.execute(query, variables:) return response unless attempt < @tries && retryable_response?(response) rescue *@on => e raise if attempt >= @tries || !@retry_if.call(e) end @sleeper.call(delay(attempt)) end end |
#url ⇒ Object
surface the wrapped transport's endpoint (schema-dump provenance)
64 65 66 |
# File 'lib/graph_weaver/retry.rb', line 64 def url @client.url if @client.respond_to?(:url) end |