Class: NatsWork::RetryHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/natswork/retry_handler.rb

Constant Summary collapse

DEFAULT_MAX_RETRIES =
3
DEFAULT_BASE_DELAY =
1
DEFAULT_MAX_DELAY =

5 minutes

300

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ RetryHandler

Returns a new instance of RetryHandler.



13
14
15
16
17
18
19
20
21
# File 'lib/natswork/retry_handler.rb', line 13

def initialize(options = {})
  @base_delay = options[:base_delay] || DEFAULT_BASE_DELAY
  @max_delay = options[:max_delay] || DEFAULT_MAX_DELAY
  @jitter = options[:jitter] || false
  @strategy = options[:strategy] || :exponential

  @retry_callbacks = []
  @failure_callbacks = []
end

Instance Attribute Details

#base_delayObject (readonly)

Returns the value of attribute base_delay.



11
12
13
# File 'lib/natswork/retry_handler.rb', line 11

def base_delay
  @base_delay
end

#jitterObject (readonly)

Returns the value of attribute jitter.



11
12
13
# File 'lib/natswork/retry_handler.rb', line 11

def jitter
  @jitter
end

#max_delayObject (readonly)

Returns the value of attribute max_delay.



11
12
13
# File 'lib/natswork/retry_handler.rb', line 11

def max_delay
  @max_delay
end

#strategyObject (readonly)

Returns the value of attribute strategy.



11
12
13
# File 'lib/natswork/retry_handler.rb', line 11

def strategy
  @strategy
end

Instance Method Details

#calculate_delay(attempt) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/natswork/retry_handler.rb', line 30

def calculate_delay(attempt)
  delay = case @strategy
          when :exponential
            @base_delay * (2**attempt)
          when :linear
            @base_delay * (attempt + 1)
          when :constant
            @base_delay
          when Proc
            @strategy.call(attempt)
          else
            @base_delay * (2**attempt)
          end

  # Apply max delay cap
  delay = [delay, @max_delay].min

  # Apply jitter if enabled (±10% randomness)
  if @jitter && delay.positive?
    jitter_amount = delay * 0.1
    delay += (rand * 2 - 1) * jitter_amount
  end

  delay
end

#handle_failure(connection, job_message, error) ⇒ Object



117
118
119
120
121
122
123
# File 'lib/natswork/retry_handler.rb', line 117

def handle_failure(connection, job_message, error)
  if should_retry?(job_message)
    schedule_retry(connection, job_message, error)
  else
    send_to_dead_letter(connection, job_message, error)
  end
end

#on_failure(&block) ⇒ Object



113
114
115
# File 'lib/natswork/retry_handler.rb', line 113

def on_failure(&block)
  @failure_callbacks << block
end

#on_retry(&block) ⇒ Object



109
110
111
# File 'lib/natswork/retry_handler.rb', line 109

def on_retry(&block)
  @retry_callbacks << block
end

#schedule_retry(connection, job_message, error) ⇒ Object



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
85
86
87
# File 'lib/natswork/retry_handler.rb', line 56

def schedule_retry(connection, job_message, error)
  retry_count = (job_message['retry_count'] || 0) + 1
  delay = calculate_delay(retry_count - 1)

  retry_message = job_message.merge(
    'retry_count' => retry_count,
    'retry_at' => (Time.now + delay).iso8601,
    'last_error' => {
      'type' => error.class.name,
      'message' => error.message,
      'backtrace' => error.backtrace&.first(10) || []
    }
  )

  # Track retry history
  retry_history = retry_message['retry_history'] || []
  retry_history << {
    'attempt' => retry_count,
    'error' => error.message,
    'retried_at' => Time.now.iso8601
  }
  retry_message['retry_history'] = retry_history

  # Publish to retry queue
  retry_queue = "natswork.queue.retry.#{job_message['queue'] || 'default'}"
  connection.publish(retry_queue, retry_message)

  # Call retry callbacks
  @retry_callbacks.each do |callback|
    callback.call(job_message, error)
  end
end

#send_to_dead_letter(connection, job_message, error) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/natswork/retry_handler.rb', line 89

def send_to_dead_letter(connection, job_message, error)
  dead_letter_message = job_message.merge(
    'final_error' => {
      'type' => error.class.name,
      'message' => error.message,
      'backtrace' => error.backtrace || []
    },
    'failed_at' => Time.now.iso8601,
    'exhausted_retries' => true,
    'total_attempts' => (job_message['retry_count'] || 0) + 1
  )

  connection.publish('natswork.queue.dead_letter', dead_letter_message)

  # Call failure callbacks
  @failure_callbacks.each do |callback|
    callback.call(job_message, error)
  end
end

#should_retry?(job_message) ⇒ Boolean

Returns:

  • (Boolean)


23
24
25
26
27
28
# File 'lib/natswork/retry_handler.rb', line 23

def should_retry?(job_message)
  retry_count = job_message['retry_count'] || 0
  max_retries = job_message['max_retries'] || DEFAULT_MAX_RETRIES

  retry_count < max_retries
end