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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2524cd95e75d5fef01076f0bb4895a3d3aceeed233a2b68eabc596c7a731ebfa
4
- data.tar.gz: 7df5392a5796de69bf086cc47484cb5481f1e131099d3994062e5e3c308d4b06
3
+ metadata.gz: 829177cbd19c6b70ce4e32d03b824d80e0757d833f0f222ed7336156c2491975
4
+ data.tar.gz: 28a1317702057efb8effe2053a5c1b531bc7c13aff26dae835217a5da82d10b8
5
5
  SHA512:
6
- metadata.gz: 60adcdcd0b47caba95f1eb03c8be5e340e62aaf9fe2d9ed5ac85a702e1ac89d95e65c810a245bd5d9d0ddbd7fcbc68c493c46497a53298fe7721f90240107582
7
- data.tar.gz: 1657f7b9c9b358a58ca2aae35896374e5193a295e753d47054f662233237fef68148175d8e7e40a050bf102fb68f32cf790827f79f84b9450fea7d0be75ab8ac
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 pending counts. All counters are thread-safe and updated atomically after each task finishes.
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module TaskQueue
5
- VERSION = '0.3.0'
5
+ VERSION = '0.4.1'
6
6
  end
7
7
  end
@@ -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.3.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-05 00:00:00.000000000 Z
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.