Class: A2A::Client::Middleware::RetryInterceptor

Inherits:
Object
  • Object
show all
Defined in:
lib/a2a/client/middleware/retry_interceptor.rb

Constant Summary collapse

DEFAULT_RETRYABLE_ERRORS =

Default retryable error classes

[
  A2A::Errors::TimeoutError,
  A2A::Errors::HTTPError,
  A2A::Errors::TransportError,
  A2A::Errors::AgentUnavailable,
  A2A::Errors::ResourceExhausted,
  Faraday::TimeoutError,
  Faraday::ConnectionFailed
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_attempts: 3, initial_delay: 1.0, max_delay: 60.0, backoff_multiplier: 2.0, retryable_errors: nil) ⇒ RetryInterceptor

Initialize retry interceptor



34
35
36
37
38
39
40
41
42
43
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 34

def initialize(max_attempts: 3, initial_delay: 1.0, max_delay: 60.0,
               backoff_multiplier: 2.0, retryable_errors: nil)
  @max_attempts = max_attempts
  @initial_delay = initial_delay
  @max_delay = max_delay
  @backoff_multiplier = backoff_multiplier
  @retryable_errors = retryable_errors || DEFAULT_RETRYABLE_ERRORS

  validate_configuration!
end

Instance Attribute Details

#backoff_multiplierObject (readonly)

Returns the value of attribute backoff_multiplier.



13
14
15
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 13

def backoff_multiplier
  @backoff_multiplier
end

#initial_delayObject (readonly)

Returns the value of attribute initial_delay.



13
14
15
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 13

def initial_delay
  @initial_delay
end

#max_attemptsObject (readonly)

Returns the value of attribute max_attempts.



13
14
15
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 13

def max_attempts
  @max_attempts
end

#max_delayObject (readonly)

Returns the value of attribute max_delay.



13
14
15
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 13

def max_delay
  @max_delay
end

#retryable_errorsObject (readonly)

Returns the value of attribute retryable_errors.



13
14
15
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 13

def retryable_errors
  @retryable_errors
end

Instance Method Details

#calculate_delay(attempt) ⇒ Float

Calculate delay for the given attempt



118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 118

def calculate_delay(attempt)
  return 0 if attempt <= 1

  # Exponential backoff: initial_delay * (backoff_multiplier ^ (attempt - 2))
  delay = @initial_delay * (@backoff_multiplier**(attempt - 2))

  # Add jitter to prevent thundering herd
  jitter = delay * 0.1 * rand
  delay += jitter

  # Cap at max_delay
  [delay, @max_delay].min
end

#call(request, context, next_middleware) ⇒ Object

Execute request with retry logic



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 52

def call(request, context, next_middleware)
  attempt = 0
  last_error = nil

  loop do
    attempt += 1

    begin
      return next_middleware.call(request, context)
    rescue StandardError => e
      last_error = e

      # Check if we should retry
      raise e unless should_retry?(e, attempt)

      delay = calculate_delay(attempt)
      context[:retry_attempt] = attempt
      context[:retry_delay] = delay

      sleep(delay) if delay.positive?
      next

      # Re-raise the error if we shouldn't retry or max attempts reached
    end
  end
end

#retryable_error?(error) ⇒ Boolean

Check if an error is retryable



109
110
111
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 109

def retryable_error?(error)
  @retryable_errors.any? { |error_class| error.is_a?(error_class) }
end

#should_retry?(error, attempt) ⇒ Boolean

Check if an error should trigger a retry



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 85

def should_retry?(error, attempt)
  return false if attempt >= @max_attempts
  return false unless retryable_error?(error)

  # Check for specific HTTP status codes that shouldn't be retried
  if error.respond_to?(:status_code)
    case error.status_code
    when 400, 401, 403, 404, 422 # Client errors - don't retry
      return false
    when 429 # Rate limited - should retry
      return true
    when 500..599 # Server errors - should retry
      return true
    end
  end

  true
end

#statsHash

Get retry statistics



136
137
138
139
140
141
142
143
144
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 136

def stats
  {
    max_attempts: @max_attempts,
    initial_delay: @initial_delay,
    max_delay: @max_delay,
    backoff_multiplier: @backoff_multiplier,
    retryable_errors: @retryable_errors.map(&:name)
  }
end

#validate_configuration!Object (private)

Validate configuration parameters

Raises:

  • (ArgumentError)


150
151
152
153
154
155
156
157
# File 'lib/a2a/client/middleware/retry_interceptor.rb', line 150

def validate_configuration!
  raise ArgumentError, "max_attempts must be positive" if @max_attempts <= 0
  raise ArgumentError, "initial_delay must be non-negative" if @initial_delay.negative?
  raise ArgumentError, "max_delay must be positive" if @max_delay <= 0
  raise ArgumentError, "backoff_multiplier must be positive" if @backoff_multiplier <= 0

  raise ArgumentError, "initial_delay cannot be greater than max_delay" if @initial_delay > @max_delay
end