legionio 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f077a3f850a9690fe5fc70f6dd0ddb5ddf4192ffc0709dd6c254d873e4ccfcbc
4
- data.tar.gz: 2823a8a14bd3603bbaaf40356ae18cc95d6f11fc32b6335e3bfcbd69bcdef8b5
3
+ metadata.gz: 274f2876b6e344eba4a13f69464f8d2314184ac72b421758d8fbc54b40934fa7
4
+ data.tar.gz: 7fa9c97c435509b18b2f68cdc2a041f496b613a7cf163b053c5d286a2ae9870a
5
5
  SHA512:
6
- metadata.gz: 4be6ec4e8ce8c669a8b0793180508b68473a4e2bab97ecb355978150bdb9783b726719746dd493039276a97283263d122c9c5f9cc915cf056c395736466d2d83
7
- data.tar.gz: 7894f7a66b5648032d850ef8570db60b812fa1f65e7e4936d077003967e8183bb6a75ea363a715bff53c855e684f1d55c9d4936580be8f49be8ef4a361bc0473
6
+ metadata.gz: 64226b609440433ddf6db4bcfaf102318e50d22ed6a117ddf113086fb85988d4df4dfecd1d162c20d5e5b1d1b5e4a69dbc8a426b5b5a05e6f9a83fb7e4add5f8
7
+ data.tar.gz: 4ddd4175e2584e249e6e74e4101d6ed3ae15be7d60b3051e9025f8f8bb286e84cba73e00a1715443abd1cae216249b7328aed9daeb66ed8558282385f618dcf5
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.8.2] - 2026-04-13
6
+
7
+ ### Added
8
+ - `Legion::Extensions::Actors::RetryPolicy` — configurable retry threshold module with `should_retry?`, `extract_retry_count`, and `retry_threshold` helpers
9
+ - Subscription actor `reject_or_retry` — counts retries via `x-retry-count` header, republishes with incremented header and exponential backoff (`2^n * base_delay`, capped at `max_delay`), dead-letters to DLX when threshold exceeded
10
+ - Settings: `fleet.poison_message_threshold` (primary), `transport.retry_threshold` (fallback), `fleet.transport.retry_base_delay_seconds`, `fleet.transport.retry_max_delay_seconds`
11
+
5
12
  ## [1.7.37] - 2026-04-09
6
13
 
7
14
  ### Added
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Actors
6
+ module RetryPolicy
7
+ DEFAULT_THRESHOLD = 2
8
+ RETRY_COUNT_HEADER = 'x-retry-count'
9
+
10
+ module_function
11
+
12
+ def should_retry?(retry_count:, threshold:)
13
+ return true if threshold.nil?
14
+
15
+ retry_count < threshold
16
+ end
17
+
18
+ def extract_retry_count(headers)
19
+ return 0 if headers.nil?
20
+
21
+ count = headers[RETRY_COUNT_HEADER] || headers[RETRY_COUNT_HEADER.to_sym] || 0
22
+ count.to_i
23
+ end
24
+
25
+ def retry_threshold
26
+ threshold = nil
27
+ if defined?(Legion::Settings)
28
+ threshold = Legion::Settings.dig(:fleet, :poison_message_threshold)
29
+ threshold ||= Legion::Settings.dig(:transport, :retry_threshold)
30
+ end
31
+ threshold || DEFAULT_THRESHOLD
32
+ rescue StandardError
33
+ DEFAULT_THRESHOLD
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'base'
4
4
  require_relative 'dsl'
5
+ require_relative 'retry_policy'
5
6
  require 'date'
6
7
  require 'securerandom'
7
8
 
@@ -84,7 +85,7 @@ module Legion
84
85
  cancel if Legion::Settings[:client][:shutting_down]
85
86
  rescue StandardError => e
86
87
  handle_exception(e, lex: lex_name, fn: fn, routing_key: delivery_info.routing_key)
87
- @queue.reject(delivery_info.delivery_tag) if manual_ack
88
+ reject_or_retry(delivery_info, metadata, payload) if manual_ack
88
89
  end
89
90
  log.info "[Subscription] prepared: #{lex_name}/#{runner_name}"
90
91
  rescue StandardError => e
@@ -176,8 +177,8 @@ module Legion
176
177
  cancel if Legion::Settings[:client][:shutting_down]
177
178
  rescue StandardError => e
178
179
  handle_exception(e)
179
- log.warn "[Subscription] nacking message for #{lex_name}/#{fn}"
180
- @queue.reject(delivery_info.delivery_tag) if manual_ack
180
+ log.warn "[Subscription] retry-or-dlq for #{lex_name}/#{fn}"
181
+ reject_or_retry(delivery_info, metadata, payload) if manual_ack
181
182
  end
182
183
  log.info "[Subscription] subscribed: #{lex_name}/#{runner_name} (consumer registered)" if defined?(log)
183
184
  end
@@ -223,6 +224,47 @@ module Legion
223
224
  run_block.call
224
225
  end
225
226
  end
227
+
228
+ def reject_or_retry(delivery_info, metadata, payload)
229
+ headers = metadata&.headers || {}
230
+ retry_count = RetryPolicy.extract_retry_count(headers)
231
+ threshold = RetryPolicy.retry_threshold
232
+
233
+ if RetryPolicy.should_retry?(retry_count: retry_count, threshold: threshold)
234
+ base_delay = Legion::Settings.dig(:fleet, :transport, :retry_base_delay_seconds) || 1
235
+ max_delay = Legion::Settings.dig(:fleet, :transport, :retry_max_delay_seconds) || 30
236
+ delay = [base_delay * (2**retry_count), max_delay].min
237
+ log.info "[Subscription] retrying message in #{delay}s (attempt #{retry_count + 1}/#{threshold}) for #{lex_name}"
238
+ sleep(delay)
239
+ if republish_with_retry_count(delivery_info, metadata, payload, retry_count + 1)
240
+ @queue.acknowledge(delivery_info.delivery_tag)
241
+ else
242
+ @queue.reject(delivery_info.delivery_tag, requeue: false)
243
+ end
244
+ else
245
+ log.warn "[Subscription] dead-lettering message after #{retry_count} retries for #{lex_name}"
246
+ @queue.reject(delivery_info.delivery_tag, requeue: false)
247
+ end
248
+ end
249
+
250
+ def republish_with_retry_count(_delivery_info, metadata, payload, new_count)
251
+ headers = (metadata&.headers || {}).dup
252
+ headers[RetryPolicy::RETRY_COUNT_HEADER] = new_count
253
+
254
+ exchange = @queue.channel.default_exchange
255
+ exchange.publish(
256
+ payload,
257
+ routing_key: @queue.name,
258
+ headers: headers,
259
+ content_type: metadata&.content_type,
260
+ content_encoding: metadata&.content_encoding,
261
+ persistent: true
262
+ )
263
+ true
264
+ rescue StandardError => e
265
+ log.warn "[Subscription] republish failed, dead-lettering: #{e.message}"
266
+ false
267
+ end
226
268
  end
227
269
  end
228
270
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.8.1'
4
+ VERSION = '1.8.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legionio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.1
4
+ version: 1.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -809,6 +809,7 @@ files:
809
809
  - lib/legion/extensions/actors/nothing.rb
810
810
  - lib/legion/extensions/actors/once.rb
811
811
  - lib/legion/extensions/actors/poll.rb
812
+ - lib/legion/extensions/actors/retry_policy.rb
812
813
  - lib/legion/extensions/actors/singleton.rb
813
814
  - lib/legion/extensions/actors/subscription.rb
814
815
  - lib/legion/extensions/builders/absorbers.rb