philiprehberger-task_queue 0.3.0 → 0.4.1
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 +13 -0
- data/README.md +43 -3
- data/lib/philiprehberger/task_queue/queue.rb +60 -3
- data/lib/philiprehberger/task_queue/version.rb +1 -1
- data/lib/philiprehberger/task_queue/worker.rb +5 -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: 829177cbd19c6b70ce4e32d03b824d80e0757d833f0f222ed7336156c2491975
|
|
4
|
+
data.tar.gz: 28a1317702057efb8effe2053a5c1b531bc7c13aff26dae835217a5da82d10b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e5ce7cda54494f0756e05a5bd56dbf722553f34890c5b9868b522ad70e6ae587e01afee2ae035b2a61bc7baec4b6d4dd34dde2549d1c93757b08ca88f630932
|
|
7
|
+
data.tar.gz: 8e380685e1a026d5344a754e27c5c74989f17509492e75683ff69ad2ccd2e457707fc272814e228da5c05bfe3cf8c50b4c32599270bb9eadbeb3025611d113ad
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2026-04-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Queue#empty?` to check whether any pending tasks are waiting
|
|
14
|
+
|
|
15
|
+
## [0.4.0] - 2026-04-05
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `in_flight` count in `stats` hash for monitoring active task execution
|
|
19
|
+
- `Queue#pause` and `Queue#resume` for temporarily suspending task consumption
|
|
20
|
+
- `Queue#paused?` to check pause state
|
|
21
|
+
- `Queue#clear` to discard all pending tasks
|
|
22
|
+
|
|
10
23
|
## [0.3.0] - 2026-04-04
|
|
11
24
|
|
|
12
25
|
### Added
|
data/README.md
CHANGED
|
@@ -64,7 +64,7 @@ queue.push { File.read("/nonexistent") }
|
|
|
64
64
|
|
|
65
65
|
queue.drain(timeout: 5)
|
|
66
66
|
puts queue.stats
|
|
67
|
-
# => { completed: 0, failed: 2, pending: 0 }
|
|
67
|
+
# => { completed: 0, failed: 2, pending: 0, in_flight: 0 }
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
### Completion callback
|
|
@@ -88,7 +88,7 @@ queue.drain(timeout: 5)
|
|
|
88
88
|
|
|
89
89
|
### Statistics
|
|
90
90
|
|
|
91
|
-
`stats` returns a snapshot of completed, failed, and
|
|
91
|
+
`stats` returns a snapshot of completed, failed, pending, and in-flight counts. All counters are thread-safe and updated atomically after each task finishes.
|
|
92
92
|
|
|
93
93
|
```ruby
|
|
94
94
|
queue = Philiprehberger::TaskQueue.new(concurrency: 4)
|
|
@@ -100,9 +100,44 @@ stats = queue.stats
|
|
|
100
100
|
puts "Completed: #{stats[:completed]}"
|
|
101
101
|
puts "Failed: #{stats[:failed]}"
|
|
102
102
|
puts "Pending: #{stats[:pending]}"
|
|
103
|
+
puts "In-flight: #{stats[:in_flight]}"
|
|
103
104
|
# Completed: 19
|
|
104
105
|
# Failed: 1
|
|
105
106
|
# Pending: 0
|
|
107
|
+
# In-flight: 0
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Pause and resume
|
|
111
|
+
|
|
112
|
+
Temporarily suspend task consumption without shutting down. In-flight tasks will finish, but no new tasks are picked up until the queue is resumed.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
queue = Philiprehberger::TaskQueue.new(concurrency: 4)
|
|
116
|
+
|
|
117
|
+
10.times { |i| queue.push { process(i) } }
|
|
118
|
+
|
|
119
|
+
queue.pause
|
|
120
|
+
puts queue.paused? # => true
|
|
121
|
+
|
|
122
|
+
# Tasks already in flight will complete, but pending tasks wait.
|
|
123
|
+
queue.resume
|
|
124
|
+
puts queue.paused? # => false
|
|
125
|
+
|
|
126
|
+
queue.shutdown(timeout: 10)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Clear pending tasks
|
|
130
|
+
|
|
131
|
+
Discard all pending tasks from the queue. Returns the number of tasks removed.
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
queue = Philiprehberger::TaskQueue.new(concurrency: 2)
|
|
135
|
+
|
|
136
|
+
100.times { |i| queue.push { process(i) } }
|
|
137
|
+
cleared = queue.clear
|
|
138
|
+
puts "Cleared #{cleared} tasks"
|
|
139
|
+
|
|
140
|
+
queue.shutdown(timeout: 5)
|
|
106
141
|
```
|
|
107
142
|
|
|
108
143
|
### FIFO ordering guarantees
|
|
@@ -157,12 +192,17 @@ queue.shutdown(timeout: 5)
|
|
|
157
192
|
| `#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 |
|
|
158
193
|
| `#<<(callable)` | `callable` — any object responding to `#call` | `self` | Alias for `#push`; convenient for lambdas and procs |
|
|
159
194
|
| `#size` | _(none)_ | `Integer` | Number of pending (not yet started) tasks |
|
|
195
|
+
| `#empty?` | _(none)_ | `Boolean` | Whether there are no pending tasks waiting to be started |
|
|
160
196
|
| `#running?` | _(none)_ | `Boolean` | Whether the queue is accepting new tasks |
|
|
161
197
|
| `#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 |
|
|
162
198
|
| `#on_complete(&block)` | `&block` — callback receiving `(result)` | `self` | Register a callback invoked after each successful task completion with the task's return value |
|
|
163
199
|
| `#on_error(&block)` | `&block` — callback receiving `(exception, task)` | `self` | Register an error callback invoked when a task raises a `StandardError` |
|
|
164
|
-
| `#stats` | _(none)_ | `Hash` | Returns `{ completed:, failed:, pending: }` with Integer counts |
|
|
200
|
+
| `#stats` | _(none)_ | `Hash` | Returns `{ completed:, failed:, pending:, in_flight: }` with Integer counts |
|
|
165
201
|
| `#drain(timeout:)` | `timeout` — seconds to wait (Numeric, default `30`) | `nil` | Block until all pending and in-flight tasks complete without shutting down |
|
|
202
|
+
| `#pause` | _(none)_ | `self` | Suspend task consumption; in-flight tasks finish but no new tasks are picked up |
|
|
203
|
+
| `#resume` | _(none)_ | `self` | Resume a paused queue, waking workers to continue processing |
|
|
204
|
+
| `#paused?` | _(none)_ | `Boolean` | Whether the queue is currently paused |
|
|
205
|
+
| `#clear` | _(none)_ | `Integer` | Remove all pending tasks and return the number cleared |
|
|
166
206
|
|
|
167
207
|
## Development
|
|
168
208
|
|
|
@@ -19,6 +19,8 @@ module Philiprehberger
|
|
|
19
19
|
@workers = []
|
|
20
20
|
@running = true
|
|
21
21
|
@started = false
|
|
22
|
+
@paused = false
|
|
23
|
+
@pause_condition = ConditionVariable.new
|
|
22
24
|
@error_handler = nil
|
|
23
25
|
@complete_handler = nil
|
|
24
26
|
@stats = { completed: 0, failed: 0, in_flight: 0 }
|
|
@@ -48,10 +50,53 @@ module Philiprehberger
|
|
|
48
50
|
|
|
49
51
|
# Return statistics about processed tasks.
|
|
50
52
|
#
|
|
51
|
-
# @return [Hash{Symbol => Integer}] counts for :completed, :failed, :pending
|
|
53
|
+
# @return [Hash{Symbol => Integer}] counts for :completed, :failed, :pending, :in_flight
|
|
52
54
|
def stats
|
|
53
55
|
@mutex.synchronize do
|
|
54
|
-
{ completed: @stats[:completed], failed: @stats[:failed], pending: @tasks.size
|
|
56
|
+
{ completed: @stats[:completed], failed: @stats[:failed], pending: @tasks.size,
|
|
57
|
+
in_flight: @stats[:in_flight] }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Pause the queue so workers stop dequeuing new tasks.
|
|
62
|
+
#
|
|
63
|
+
# In-flight tasks will finish, but no new tasks will be picked up until
|
|
64
|
+
# +resume+ is called.
|
|
65
|
+
#
|
|
66
|
+
# @return [self]
|
|
67
|
+
def pause
|
|
68
|
+
@mutex.synchronize do
|
|
69
|
+
@paused = true
|
|
70
|
+
end
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Resume a paused queue, waking workers to continue processing.
|
|
75
|
+
#
|
|
76
|
+
# @return [self]
|
|
77
|
+
def resume
|
|
78
|
+
@mutex.synchronize do
|
|
79
|
+
@paused = false
|
|
80
|
+
@pause_condition.broadcast
|
|
81
|
+
end
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Whether the queue is currently paused.
|
|
86
|
+
#
|
|
87
|
+
# @return [Boolean]
|
|
88
|
+
def paused?
|
|
89
|
+
@mutex.synchronize { @paused }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Remove all pending tasks from the queue.
|
|
93
|
+
#
|
|
94
|
+
# @return [Integer] number of tasks cleared
|
|
95
|
+
def clear
|
|
96
|
+
@mutex.synchronize do
|
|
97
|
+
count = @tasks.size
|
|
98
|
+
@tasks.clear
|
|
99
|
+
count
|
|
55
100
|
end
|
|
56
101
|
end
|
|
57
102
|
|
|
@@ -101,6 +146,15 @@ module Philiprehberger
|
|
|
101
146
|
@mutex.synchronize { @tasks.size }
|
|
102
147
|
end
|
|
103
148
|
|
|
149
|
+
# Whether there are no pending tasks waiting to be started.
|
|
150
|
+
#
|
|
151
|
+
# In-flight tasks are not considered; use +drain+ to wait for them.
|
|
152
|
+
#
|
|
153
|
+
# @return [Boolean]
|
|
154
|
+
def empty?
|
|
155
|
+
@mutex.synchronize { @tasks.empty? }
|
|
156
|
+
end
|
|
157
|
+
|
|
104
158
|
# Whether the queue is accepting new tasks.
|
|
105
159
|
#
|
|
106
160
|
# @return [Boolean]
|
|
@@ -128,8 +182,10 @@ module Philiprehberger
|
|
|
128
182
|
return unless @running
|
|
129
183
|
|
|
130
184
|
@running = false
|
|
185
|
+
@paused = false
|
|
131
186
|
@workers.each(&:stop)
|
|
132
187
|
@condition.broadcast
|
|
188
|
+
@pause_condition.broadcast
|
|
133
189
|
end
|
|
134
190
|
end
|
|
135
191
|
|
|
@@ -146,7 +202,8 @@ module Philiprehberger
|
|
|
146
202
|
@workers << Worker.new(
|
|
147
203
|
@tasks, @mutex, @condition,
|
|
148
204
|
context: { stats: @stats, error_handler: @error_handler,
|
|
149
|
-
complete_handler: @complete_handler, drain_condition: @drain_condition
|
|
205
|
+
complete_handler: @complete_handler, drain_condition: @drain_condition,
|
|
206
|
+
paused: -> { @paused }, pause_condition: @pause_condition }
|
|
150
207
|
)
|
|
151
208
|
end
|
|
152
209
|
@started = true
|
|
@@ -14,6 +14,8 @@ module Philiprehberger
|
|
|
14
14
|
@error_handler = context[:error_handler]
|
|
15
15
|
@complete_handler = context[:complete_handler]
|
|
16
16
|
@drain_condition = context[:drain_condition]
|
|
17
|
+
@paused = context[:paused]
|
|
18
|
+
@pause_condition = context[:pause_condition]
|
|
17
19
|
@running = true
|
|
18
20
|
@thread = Thread.new { run }
|
|
19
21
|
end
|
|
@@ -43,6 +45,9 @@ module Philiprehberger
|
|
|
43
45
|
@condition.wait(@mutex) while @queue.empty? && @running
|
|
44
46
|
return nil unless @running || !@queue.empty?
|
|
45
47
|
|
|
48
|
+
@pause_condition.wait(@mutex) while @paused&.call && @running
|
|
49
|
+
return nil unless @running || !@queue.empty?
|
|
50
|
+
|
|
46
51
|
@stats[:in_flight] += 1
|
|
47
52
|
@queue.shift
|
|
48
53
|
end
|
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.4.1
|
|
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-04-
|
|
11
|
+
date: 2026-04-07 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.
|