Module: Aidp::Concurrency::Backoff
- Defined in:
- lib/aidp/concurrency/backoff.rb
Overview
Retry logic with exponential backoff and jitter.
Replaces ad-hoc retry loops that use sleep() with a standardized, configurable retry mechanism that includes backoff strategies and jitter.
Class Method Summary collapse
-
.calculate_delay(attempt, strategy, base, max_delay, jitter) ⇒ Float
Calculate backoff delay for a given attempt.
-
.retry(max_attempts: nil, base: nil, max_delay: nil, jitter: nil, strategy: :exponential, on: [StandardError]) { ... } ⇒ Object
Retry a block with exponential backoff and jitter.
Class Method Details
.calculate_delay(attempt, strategy, base, max_delay, jitter) ⇒ Float
Calculate backoff delay for a given attempt.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/aidp/concurrency/backoff.rb', line 94 def calculate_delay(attempt, strategy, base, max_delay, jitter) delay = case strategy when :exponential base * (2**(attempt - 1)) when :linear base * attempt when :constant base else raise ArgumentError, "Unknown strategy: #{strategy}" end # Cap at max_delay delay = [delay, max_delay].min # Add jitter: randomize between (1-jitter)*delay and delay # e.g., with jitter=0.2, delay is reduced by 0-20% if jitter > 0 jitter_amount = delay * jitter * rand delay -= jitter_amount end delay end |
.retry(max_attempts: nil, base: nil, max_delay: nil, jitter: nil, strategy: :exponential, on: [StandardError]) { ... } ⇒ Object
Retry a block with exponential backoff and jitter.
42 43 44 45 46 47 48 49 50 51 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 78 79 80 81 82 83 84 |
# File 'lib/aidp/concurrency/backoff.rb', line 42 def retry(max_attempts: nil, base: nil, max_delay: nil, jitter: nil, strategy: :exponential, on: [StandardError], &block) max_attempts ||= Concurrency.configuration.default_max_attempts base ||= Concurrency.configuration.default_backoff_base max_delay ||= Concurrency.configuration.default_backoff_max jitter ||= Concurrency.configuration.default_jitter raise ArgumentError, "Block required" unless block_given? raise ArgumentError, "max_attempts must be >= 1" if max_attempts < 1 on = Array(on) last_error = nil attempt = 0 while attempt < max_attempts attempt += 1 begin result = block.call log_retry_success(attempt) if attempt > 1 return result rescue => e last_error = e # Re-raise if error is not in the retry list raise unless on.any? { |klass| e.is_a?(klass) } # Re-raise on last attempt if attempt >= max_attempts log_max_attempts_exceeded(attempt, e) raise Concurrency::MaxAttemptsError, "Max attempts (#{max_attempts}) exceeded: #{e.class} - #{e.}" end # Calculate delay and wait delay = calculate_delay(attempt, strategy, base, max_delay, jitter) log_retry_attempt(attempt, max_attempts, delay, e) sleep(delay) if delay > 0 end end # Should never reach here, but just in case raise Concurrency::MaxAttemptsError, "Max attempts (#{max_attempts}) exceeded: #{last_error&.class} - #{last_error&.}" end |