philiprehberger-task_queue 0.7.1 → 0.8.0
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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +52 -6
- data/lib/philiprehberger/task_queue/queue.rb +52 -11
- data/lib/philiprehberger/task_queue/version.rb +1 -1
- data/lib/philiprehberger/task_queue/worker.rb +82 -15
- data/lib/philiprehberger/task_queue.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5017b6c1de6e4dfabd256db6ce882673c7c6feb06b9f77d379120f73ca488332
|
|
4
|
+
data.tar.gz: 62178c519c689ec28af19af6bbb2fa5bf5e54095bcb9cb22da0ceb5ffc413d58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82445514d31e2af3423bcb480bb14c0dfb20a410bdf745137f2f646c6412e791bec548bbdd46e32bb52d238eb6931b3812fdb7137f007bed061e5c93feafb713
|
|
7
|
+
data.tar.gz: 15e97b694846cf81d93183e257ae9dd8b12f4d0d43f6a71309a43f2ebefcd7dfb0624055d0a564b3052ad4e32a50b2f18c0173ea5c228fb7d9ec303da852528b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-07-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Automatic retries with backoff — `max_retries:`, `retry_backoff:` (`:none`/`:fixed`/`:exponential`), and `retry_base_delay:` constructor options; failing tasks are requeued up to `max_retries` times before counting as `failed`
|
|
14
|
+
- `retried` counter in `stats` reporting the total number of retry attempts made
|
|
15
|
+
- `on_error` now receives the 1-based attempt number as its third argument
|
|
16
|
+
- Task priorities — `push(priority:)` dequeues higher-priority tasks first while preserving FIFO order within the same priority (default priority `0` keeps pure FIFO behavior)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Callbacks registered after the first `push` (via `on_error`/`on_complete`) now fire; handlers are read live at execution time instead of being snapshotted when workers start
|
|
20
|
+
- User callbacks are isolated from stats accounting and the worker loop — a raising completion or error callback can no longer corrupt the `in_flight`/`completed`/`failed` counters or kill a worker thread
|
|
21
|
+
|
|
10
22
|
## [0.7.1] - 2026-06-14
|
|
11
23
|
|
|
12
24
|
### Changed
|
|
@@ -128,3 +140,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
128
140
|
- Thread-safe task enqueuing with `push` / `<<`
|
|
129
141
|
- Graceful shutdown with timeout support
|
|
130
142
|
- Auto-starting worker threads on first push
|
|
143
|
+
|
|
144
|
+
[Unreleased]: https://github.com/philiprehberger/rb-task-queue/compare/v0.8.0...HEAD
|
|
145
|
+
[0.8.0]: https://github.com/philiprehberger/rb-task-queue/compare/v0.7.1...v0.8.0
|
data/README.md
CHANGED
|
@@ -66,7 +66,34 @@ queue.push { File.read("/nonexistent") }
|
|
|
66
66
|
|
|
67
67
|
queue.drain(timeout: 5)
|
|
68
68
|
puts queue.stats
|
|
69
|
-
# => { completed: 0, failed: 2, pending: 0, in_flight: 0 }
|
|
69
|
+
# => { completed: 0, failed: 2, pending: 0, in_flight: 0, retried: 0 }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Retries and backoff
|
|
73
|
+
|
|
74
|
+
Failing tasks can be retried automatically. Pass `max_retries:` (default `0`, no retries) and a `retry_backoff:` policy (`:none`, `:fixed`, or `:exponential`) with a `retry_base_delay:` in seconds. A task that raises a `StandardError` is requeued up to `max_retries` times before being counted as `failed`. The `on_error` callback fires on every failed attempt and receives the attempt number as its third argument. The number of retry attempts is tracked in `stats[:retried]`.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
queue = Philiprehberger::TaskQueue.new(
|
|
78
|
+
concurrency: 4,
|
|
79
|
+
max_retries: 3,
|
|
80
|
+
retry_backoff: :exponential, # :none | :fixed | :exponential
|
|
81
|
+
retry_base_delay: 0.5 # seconds; grows 0.5, 1.0, 2.0 for :exponential
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
queue.on_error do |exception, task, attempt|
|
|
85
|
+
warn "[TaskQueue] attempt #{attempt} failed: #{exception.message}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
attempts = 0
|
|
89
|
+
queue.push do
|
|
90
|
+
attempts += 1
|
|
91
|
+
raise "transient failure" if attempts < 3
|
|
92
|
+
puts "succeeded on attempt #{attempts}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
queue.drain(timeout: 30)
|
|
96
|
+
puts queue.stats[:retried] # number of retry attempts made
|
|
70
97
|
```
|
|
71
98
|
|
|
72
99
|
### Completion callback
|
|
@@ -154,9 +181,28 @@ queue.stats_reset!
|
|
|
154
181
|
queue.stats[:completed] # => 0
|
|
155
182
|
```
|
|
156
183
|
|
|
184
|
+
### Task priorities
|
|
185
|
+
|
|
186
|
+
Give tasks a `priority:` (default `0`) to have them dequeued ahead of lower-priority work. Higher priorities run first; tasks that share a priority preserve FIFO (insertion) order. The `<<` alias always enqueues at priority `0`.
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
queue = Philiprehberger::TaskQueue.new(concurrency: 1)
|
|
190
|
+
queue.pause
|
|
191
|
+
|
|
192
|
+
queue.push(priority: 0) { puts "low" }
|
|
193
|
+
queue.push(priority: 10) { puts "high" }
|
|
194
|
+
queue.push(priority: 5) { puts "medium" }
|
|
195
|
+
|
|
196
|
+
queue.resume
|
|
197
|
+
queue.drain(timeout: 5)
|
|
198
|
+
# high
|
|
199
|
+
# medium
|
|
200
|
+
# low
|
|
201
|
+
```
|
|
202
|
+
|
|
157
203
|
### FIFO ordering guarantees
|
|
158
204
|
|
|
159
|
-
Tasks are stored in an internal array and dequeued in FIFO order. When `concurrency` is `1`, tasks execute strictly in the order they were pushed. With higher concurrency, dequeue order is still FIFO but tasks may complete out of order depending on individual execution time.
|
|
205
|
+
Tasks are stored in an internal array and dequeued in priority-then-FIFO order. With the default priority of `0`, ordering is pure FIFO. When `concurrency` is `1`, equal-priority tasks execute strictly in the order they were pushed. With higher concurrency, dequeue order is still priority-then-FIFO but tasks may complete out of order depending on individual execution time.
|
|
160
206
|
|
|
161
207
|
```ruby
|
|
162
208
|
results = Queue.new # stdlib thread-safe queue for collecting output
|
|
@@ -202,8 +248,8 @@ queue.shutdown(timeout: 5)
|
|
|
202
248
|
|
|
203
249
|
| Method | Parameters | Returns | Description |
|
|
204
250
|
|---|---|---|---|
|
|
205
|
-
| `.new(concurrency:)` | `concurrency` — max worker threads (Integer, default `4`) | `Queue` | Create a new queue
|
|
206
|
-
| `#push(&block)` | `&block` — the task to execute | `self` | Enqueue a block for async execution; raises `ArgumentError` if no block given, raises `RuntimeError` if the queue is shut down |
|
|
251
|
+
| `.new(concurrency:, max_retries:, retry_backoff:, retry_base_delay:)` | `concurrency` — max worker threads (Integer, default `4`); `max_retries` — retries before a task counts as failed (Integer, default `0`); `retry_backoff` — `:none`/`:fixed`/`:exponential` (default `:none`); `retry_base_delay` — base delay in seconds (Numeric, default `0.1`) | `Queue` | Create a new queue; raises `ArgumentError` on a negative `max_retries` or unknown `retry_backoff` |
|
|
252
|
+
| `#push(&block)` | `priority` — dequeue priority, higher runs first (Integer, default `0`); `&block` — the task to execute | `self` | Enqueue a block for async execution; raises `ArgumentError` if no block given, raises `RuntimeError` if the queue is shut down |
|
|
207
253
|
| `#<<(callable)` | `callable` — any object responding to `#call` | `self` | Alias for `#push`; convenient for lambdas and procs |
|
|
208
254
|
| `#size` | _(none)_ | `Integer` | Number of pending (not yet started) tasks |
|
|
209
255
|
| `#empty?` | _(none)_ | `Boolean` | Whether there are no pending tasks waiting to be started |
|
|
@@ -211,8 +257,8 @@ queue.shutdown(timeout: 5)
|
|
|
211
257
|
| `#running?` | _(none)_ | `Boolean` | Whether the queue is accepting new tasks |
|
|
212
258
|
| `#shutdown(timeout:)` | `timeout` — seconds to wait for workers (Numeric, default `30`) | `nil` | Signal workers to stop, drain remaining tasks, join threads up to `timeout` seconds |
|
|
213
259
|
| `#on_complete(&block)` | `&block` — callback receiving `(result)` | `self` | Register a callback invoked after each successful task completion with the task's return value |
|
|
214
|
-
| `#on_error(&block)` | `&block` — callback receiving `(exception, task)` | `self` | Register an error callback invoked when a task raises a `StandardError` |
|
|
215
|
-
| `#stats` | _(none)_ | `Hash` | Returns `{ completed:, failed:, pending:, in_flight: }` with Integer counts |
|
|
260
|
+
| `#on_error(&block)` | `&block` — callback receiving `(exception, task, attempt)` | `self` | Register an error callback invoked on every failed attempt when a task raises a `StandardError`; `attempt` is the 1-based attempt number |
|
|
261
|
+
| `#stats` | _(none)_ | `Hash` | Returns `{ completed:, failed:, pending:, in_flight:, retried: }` with Integer counts (`retried` is the total number of retry attempts made) |
|
|
216
262
|
| `#drain(timeout:)` | `timeout` — seconds to wait (Numeric, default `30`) | `nil` | Block until all pending and in-flight tasks complete without shutting down |
|
|
217
263
|
| `#pause` | _(none)_ | `self` | Suspend task consumption; in-flight tasks finish but no new tasks are picked up |
|
|
218
264
|
| `#resume` | _(none)_ | `self` | Resume a paused queue, waking workers to continue processing |
|
|
@@ -4,17 +4,36 @@ require_relative 'worker'
|
|
|
4
4
|
|
|
5
5
|
module Philiprehberger
|
|
6
6
|
module TaskQueue
|
|
7
|
+
# Internal representation of an enqueued task, carrying its priority and the
|
|
8
|
+
# number of attempts made so far (for retry accounting).
|
|
9
|
+
Entry = Struct.new(:callable, :priority, :attempts)
|
|
10
|
+
|
|
7
11
|
# In-process async job queue with concurrency control.
|
|
8
12
|
#
|
|
9
13
|
# Tasks are enqueued as blocks or callable objects and executed by a pool of
|
|
10
14
|
# worker threads. The queue is fully thread-safe.
|
|
11
15
|
class Queue
|
|
16
|
+
# Supported retry backoff policies.
|
|
17
|
+
BACKOFF_POLICIES = %i[none fixed exponential].freeze
|
|
18
|
+
|
|
12
19
|
# @return [Integer] the maximum number of concurrent worker threads
|
|
13
20
|
attr_reader :concurrency
|
|
14
21
|
|
|
15
22
|
# @param concurrency [Integer] maximum number of concurrent worker threads
|
|
16
|
-
|
|
23
|
+
# @param max_retries [Integer] how many times a failing task is requeued
|
|
24
|
+
# before it is counted as failed (default +0+, i.e. no retries)
|
|
25
|
+
# @param retry_backoff [Symbol] delay policy between retries — one of
|
|
26
|
+
# +:none+, +:fixed+, or +:exponential+
|
|
27
|
+
# @param retry_base_delay [Numeric] base delay in seconds used by the
|
|
28
|
+
# +:fixed+ and +:exponential+ backoff policies
|
|
29
|
+
# @raise [ArgumentError] if +max_retries+ is negative or +retry_backoff+
|
|
30
|
+
# is not a recognized policy
|
|
31
|
+
def initialize(concurrency: 4, max_retries: 0, retry_backoff: :none, retry_base_delay: 0.1)
|
|
17
32
|
@concurrency = concurrency
|
|
33
|
+
@max_retries = max_retries
|
|
34
|
+
@retry_backoff = retry_backoff
|
|
35
|
+
@retry_base_delay = retry_base_delay
|
|
36
|
+
validate_retry_options!
|
|
18
37
|
@tasks = []
|
|
19
38
|
@mutex = Mutex.new
|
|
20
39
|
@condition = ConditionVariable.new
|
|
@@ -26,14 +45,19 @@ module Philiprehberger
|
|
|
26
45
|
@pause_condition = ConditionVariable.new
|
|
27
46
|
@error_handler = nil
|
|
28
47
|
@complete_handler = nil
|
|
29
|
-
@stats = { completed: 0, failed: 0, in_flight: 0 }
|
|
48
|
+
@stats = { completed: 0, failed: 0, in_flight: 0, retried: 0 }
|
|
30
49
|
end
|
|
31
50
|
|
|
32
51
|
# Register a callback invoked when a task raises an exception.
|
|
33
52
|
#
|
|
34
|
-
# The callback receives the exception
|
|
53
|
+
# The callback receives the exception, the task that raised it, and the
|
|
54
|
+
# attempt number (1 for the first try, incrementing on each retry). It
|
|
55
|
+
# fires on every failed attempt, including the ones that are retried.
|
|
35
56
|
#
|
|
36
|
-
#
|
|
57
|
+
# The callback may be registered at any time — even after tasks have been
|
|
58
|
+
# pushed — and is read live when the task runs.
|
|
59
|
+
#
|
|
60
|
+
# @yield [exception, task, attempt] called on each task failure
|
|
37
61
|
# @return [self]
|
|
38
62
|
def on_error(&block)
|
|
39
63
|
@mutex.synchronize { @error_handler = block }
|
|
@@ -53,11 +77,12 @@ module Philiprehberger
|
|
|
53
77
|
|
|
54
78
|
# Return statistics about processed tasks.
|
|
55
79
|
#
|
|
56
|
-
# @return [Hash{Symbol => Integer}] counts for :completed, :failed,
|
|
80
|
+
# @return [Hash{Symbol => Integer}] counts for :completed, :failed,
|
|
81
|
+
# :pending, :in_flight, and :retried (total retry attempts made)
|
|
57
82
|
def stats
|
|
58
83
|
@mutex.synchronize do
|
|
59
84
|
{ completed: @stats[:completed], failed: @stats[:failed], pending: @tasks.size,
|
|
60
|
-
in_flight: @stats[:in_flight] }
|
|
85
|
+
in_flight: @stats[:in_flight], retried: @stats[:retried] }
|
|
61
86
|
end
|
|
62
87
|
end
|
|
63
88
|
|
|
@@ -137,10 +162,15 @@ module Philiprehberger
|
|
|
137
162
|
|
|
138
163
|
# Enqueue a task to be processed asynchronously.
|
|
139
164
|
#
|
|
165
|
+
# Higher-priority tasks are dequeued before lower-priority ones; tasks
|
|
166
|
+
# that share a priority run in FIFO (insertion) order. The +<<+ alias
|
|
167
|
+
# always enqueues at the default priority of +0+.
|
|
168
|
+
#
|
|
140
169
|
# @param callable [#call, nil] a callable object (used by +<<+)
|
|
170
|
+
# @param priority [Integer] dequeue priority; higher runs first (default +0+)
|
|
141
171
|
# @yield the block to execute (takes precedence over +callable+)
|
|
142
172
|
# @return [self]
|
|
143
|
-
def push(callable = nil, &block)
|
|
173
|
+
def push(callable = nil, priority: 0, &block)
|
|
144
174
|
task = block || callable
|
|
145
175
|
raise ArgumentError, 'a block is required' unless task
|
|
146
176
|
|
|
@@ -148,7 +178,7 @@ module Philiprehberger
|
|
|
148
178
|
raise 'queue is shut down' unless @running
|
|
149
179
|
|
|
150
180
|
start_workers unless @started
|
|
151
|
-
@tasks << task
|
|
181
|
+
@tasks << Entry.new(task, priority, 0)
|
|
152
182
|
@condition.signal
|
|
153
183
|
end
|
|
154
184
|
|
|
@@ -229,13 +259,24 @@ module Philiprehberger
|
|
|
229
259
|
@concurrency.times do
|
|
230
260
|
@workers << Worker.new(
|
|
231
261
|
@tasks, @mutex, @condition,
|
|
232
|
-
context: { stats: @stats,
|
|
233
|
-
|
|
234
|
-
|
|
262
|
+
context: { stats: @stats,
|
|
263
|
+
error_handler: -> { @error_handler },
|
|
264
|
+
complete_handler: -> { @complete_handler },
|
|
265
|
+
drain_condition: @drain_condition,
|
|
266
|
+
paused: -> { @paused }, pause_condition: @pause_condition,
|
|
267
|
+
max_retries: @max_retries, retry_backoff: @retry_backoff,
|
|
268
|
+
retry_base_delay: @retry_base_delay }
|
|
235
269
|
)
|
|
236
270
|
end
|
|
237
271
|
@started = true
|
|
238
272
|
end
|
|
273
|
+
|
|
274
|
+
def validate_retry_options!
|
|
275
|
+
raise ArgumentError, 'max_retries must be >= 0' if @max_retries.negative?
|
|
276
|
+
return if BACKOFF_POLICIES.include?(@retry_backoff)
|
|
277
|
+
|
|
278
|
+
raise ArgumentError, "unknown retry_backoff: #{@retry_backoff.inspect} (expected one of #{BACKOFF_POLICIES})"
|
|
279
|
+
end
|
|
239
280
|
end
|
|
240
281
|
end
|
|
241
282
|
end
|
|
@@ -16,6 +16,9 @@ module Philiprehberger
|
|
|
16
16
|
@drain_condition = context[:drain_condition]
|
|
17
17
|
@paused = context[:paused]
|
|
18
18
|
@pause_condition = context[:pause_condition]
|
|
19
|
+
@max_retries = context[:max_retries]
|
|
20
|
+
@retry_backoff = context[:retry_backoff]
|
|
21
|
+
@retry_base_delay = context[:retry_base_delay]
|
|
19
22
|
@running = true
|
|
20
23
|
@thread = Thread.new { run }
|
|
21
24
|
end
|
|
@@ -39,32 +42,49 @@ module Philiprehberger
|
|
|
39
42
|
|
|
40
43
|
def run
|
|
41
44
|
loop do
|
|
42
|
-
|
|
43
|
-
break unless
|
|
45
|
+
entry = next_task
|
|
46
|
+
break unless entry
|
|
44
47
|
|
|
45
|
-
execute(
|
|
48
|
+
execute(entry)
|
|
46
49
|
@mutex.synchronize { @drain_condition.broadcast }
|
|
47
50
|
end
|
|
48
51
|
end
|
|
49
52
|
|
|
50
53
|
def next_task
|
|
51
54
|
@mutex.synchronize do
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
loop do
|
|
56
|
+
@condition.wait(@mutex) while @queue.empty? && @running
|
|
57
|
+
return nil unless @running || !@queue.empty?
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
@pause_condition.wait(@mutex) while @paused&.call && @running
|
|
60
|
+
return nil unless @running || !@queue.empty?
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
# Another worker may have taken the last task while we waited.
|
|
63
|
+
next if @queue.empty?
|
|
64
|
+
|
|
65
|
+
@stats[:in_flight] += 1
|
|
66
|
+
return dequeue
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Remove and return the highest-priority pending entry. Ties are broken by
|
|
72
|
+
# insertion order (FIFO), so equal-priority tasks keep their arrival order.
|
|
73
|
+
def dequeue
|
|
74
|
+
best = 0
|
|
75
|
+
index = 1
|
|
76
|
+
while index < @queue.size
|
|
77
|
+
best = index if @queue[index].priority > @queue[best].priority
|
|
78
|
+
index += 1
|
|
60
79
|
end
|
|
80
|
+
@queue.delete_at(best)
|
|
61
81
|
end
|
|
62
82
|
|
|
63
|
-
def execute(
|
|
64
|
-
result =
|
|
83
|
+
def execute(entry)
|
|
84
|
+
result = entry.callable.call
|
|
65
85
|
record_completion(result)
|
|
66
86
|
rescue StandardError => e
|
|
67
|
-
|
|
87
|
+
handle_failure(entry, e)
|
|
68
88
|
end
|
|
69
89
|
|
|
70
90
|
def record_completion(result)
|
|
@@ -72,15 +92,62 @@ module Philiprehberger
|
|
|
72
92
|
@stats[:completed] += 1
|
|
73
93
|
@stats[:in_flight] -= 1
|
|
74
94
|
end
|
|
75
|
-
|
|
95
|
+
invoke_complete(result)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def handle_failure(entry, error)
|
|
99
|
+
entry.attempts += 1
|
|
100
|
+
attempt = entry.attempts
|
|
101
|
+
invoke_error(error, entry.callable, attempt)
|
|
102
|
+
|
|
103
|
+
if attempt <= @max_retries
|
|
104
|
+
requeue(entry, attempt)
|
|
105
|
+
else
|
|
106
|
+
record_failure
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def requeue(entry, attempt)
|
|
111
|
+
delay = backoff_delay(attempt)
|
|
112
|
+
sleep(delay) if delay.positive?
|
|
113
|
+
@mutex.synchronize do
|
|
114
|
+
@stats[:in_flight] -= 1
|
|
115
|
+
@stats[:retried] += 1
|
|
116
|
+
@queue << entry
|
|
117
|
+
@condition.signal
|
|
118
|
+
end
|
|
76
119
|
end
|
|
77
120
|
|
|
78
|
-
def record_failure
|
|
121
|
+
def record_failure
|
|
79
122
|
@mutex.synchronize do
|
|
80
123
|
@stats[:failed] += 1
|
|
81
124
|
@stats[:in_flight] -= 1
|
|
82
125
|
end
|
|
83
|
-
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def backoff_delay(attempt)
|
|
129
|
+
case @retry_backoff
|
|
130
|
+
when :fixed then @retry_base_delay
|
|
131
|
+
when :exponential then @retry_base_delay * (2**(attempt - 1))
|
|
132
|
+
else 0
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# User callbacks run outside the stats-accounting path and are each
|
|
137
|
+
# isolated in their own rescue, so a raising callback can neither corrupt
|
|
138
|
+
# the counters nor unwind the worker thread.
|
|
139
|
+
def invoke_complete(result)
|
|
140
|
+
handler = @complete_handler.call
|
|
141
|
+
handler&.call(result)
|
|
142
|
+
rescue StandardError
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def invoke_error(error, task, attempt)
|
|
147
|
+
handler = @error_handler.call
|
|
148
|
+
handler&.call(error, task, attempt)
|
|
149
|
+
rescue StandardError
|
|
150
|
+
nil
|
|
84
151
|
end
|
|
85
152
|
end
|
|
86
153
|
end
|
|
@@ -5,6 +5,9 @@ require_relative 'task_queue/queue'
|
|
|
5
5
|
|
|
6
6
|
module Philiprehberger
|
|
7
7
|
module TaskQueue
|
|
8
|
+
# Base error class for all TaskQueue-specific errors.
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
|
|
8
11
|
# Convenience constructor.
|
|
9
12
|
#
|
|
10
13
|
# @param options [Hash] forwarded to {Queue#initialize}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-task_queue
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A lightweight, zero-dependency, thread-safe in-process async job queue
|
|
14
14
|
with configurable concurrency for Ruby applications.
|