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

Parameters:

  • (defaults to: 3)

    Maximum number of retry attempts (default: 3)

  • (defaults to: 1.0)

    Initial delay in seconds (default: 1.0)

  • (defaults to: 60.0)

    Maximum delay in seconds (default: 60.0)

  • (defaults to: 2.0)

    Backoff multiplier (default: 2.0)

  • (defaults to: nil)

    List of retryable error classes



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

Parameters:

  • Current attempt number (1-based)

Returns:

  • Delay in seconds



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

Parameters:

  • The request object

  • Request context

  • Next middleware in chain

Returns:

  • Response from successful request



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

Parameters:

  • The error to check

Returns:

  • True if 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

Parameters:

  • The error that occurred

  • Current attempt number

Returns:

  • True if should 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

Returns:

  • Retry configuration and 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:



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