philiprehberger-task_queue 0.2.10 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0e8b37d7bad85dce1fa4281f41df74991b9e6362d6d5bf7e1cddf1dc0612f9a
4
- data.tar.gz: 54a8725dea3cad2f9b8c8ec711dfa1c21522683d27d9d3bb84861d270a5b3f4a
3
+ metadata.gz: 6a1d028dda2118c9948cbec0cdff5f7215eeb8d76615acdeaf9a2e7b5f6c45ce
4
+ data.tar.gz: 94d56b9b9acb20add7a8e91dd1977f1a87a135e442dadc4c0b8acfc4eb5b5ac1
5
5
  SHA512:
6
- metadata.gz: ce894a8d984f95d2161d870ada0e0aef8f394cbbbd9f204173e5133eaa52acc0d6d59492dc4b4886fbcaa7461a360b588ba2e25fef908575a2f523aae216f610
7
- data.tar.gz: f945b9c98cf3e8f13572c07dd8709ca040f340a66ba64a57d2552f6c219b9f74b0ee59293ef7859d1670f268dbcf5a936198058262e8f6fb40ca028e135e8643
6
+ metadata.gz: 4c6f850324f487d516626884c44278bce4927b2419ce9b0036dd2d1c399b41a006c2ab0d48a3f18ef60a9ebcf00ef82fbf6a70e3bc486b78cdea4c397e9dc246
7
+ data.tar.gz: efb376ce60b1b9bfe43676ccefb5c0a2d03874e61d897337a6c0268555477478528583be36ab4763c5c0422b1e0fd4734db6bfa7dd738757a9e2f7616b8415f9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-04-05
11
+
12
+ ### Added
13
+ - `in_flight` count in `stats` hash for monitoring active task execution
14
+ - `Queue#pause` and `Queue#resume` for temporarily suspending task consumption
15
+ - `Queue#paused?` to check pause state
16
+ - `Queue#clear` to discard all pending tasks
17
+
18
+ ## [0.3.0] - 2026-04-04
19
+
20
+ ### Added
21
+ - `on_complete(&block)` callback that fires after each successful task completion with the task result
22
+
23
+ ## [0.2.11] - 2026-03-31
24
+
25
+ ### Added
26
+ - Add GitHub issue templates, dependabot config, and PR template
27
+
10
28
  ## [0.2.10] - 2026-03-31
11
29
 
12
30
  ### Changed
data/README.md CHANGED
@@ -64,12 +64,31 @@ 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
+ ```
69
+
70
+ ### Completion callback
71
+
72
+ Register a callback to run after each successful task completion. The callback receives the return value of the task.
73
+
74
+ ```ruby
75
+ queue = Philiprehberger::TaskQueue.new(concurrency: 2)
76
+
77
+ queue.on_complete do |result|
78
+ puts "Task finished with: #{result}"
79
+ end
80
+
81
+ queue.push { 42 }
82
+ queue.push { { status: "ok" } }
83
+
84
+ queue.drain(timeout: 5)
85
+ # Task finished with: 42
86
+ # Task finished with: {:status=>"ok"}
68
87
  ```
69
88
 
70
89
  ### Statistics
71
90
 
72
- `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.
73
92
 
74
93
  ```ruby
75
94
  queue = Philiprehberger::TaskQueue.new(concurrency: 4)
@@ -81,9 +100,44 @@ stats = queue.stats
81
100
  puts "Completed: #{stats[:completed]}"
82
101
  puts "Failed: #{stats[:failed]}"
83
102
  puts "Pending: #{stats[:pending]}"
103
+ puts "In-flight: #{stats[:in_flight]}"
84
104
  # Completed: 19
85
105
  # Failed: 1
86
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)
87
141
  ```
88
142
 
89
143
  ### FIFO ordering guarantees
@@ -140,9 +194,14 @@ queue.shutdown(timeout: 5)
140
194
  | `#size` | _(none)_ | `Integer` | Number of pending (not yet started) tasks |
141
195
  | `#running?` | _(none)_ | `Boolean` | Whether the queue is accepting new tasks |
142
196
  | `#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 |
197
+ | `#on_complete(&block)` | `&block` — callback receiving `(result)` | `self` | Register a callback invoked after each successful task completion with the task's return value |
143
198
  | `#on_error(&block)` | `&block` — callback receiving `(exception, task)` | `self` | Register an error callback invoked when a task raises a `StandardError` |
144
- | `#stats` | _(none)_ | `Hash` | Returns `{ completed:, failed:, pending: }` with Integer counts |
199
+ | `#stats` | _(none)_ | `Hash` | Returns `{ completed:, failed:, pending:, in_flight: }` with Integer counts |
145
200
  | `#drain(timeout:)` | `timeout` — seconds to wait (Numeric, default `30`) | `nil` | Block until all pending and in-flight tasks complete without shutting down |
201
+ | `#pause` | _(none)_ | `self` | Suspend task consumption; in-flight tasks finish but no new tasks are picked up |
202
+ | `#resume` | _(none)_ | `self` | Resume a paused queue, waking workers to continue processing |
203
+ | `#paused?` | _(none)_ | `Boolean` | Whether the queue is currently paused |
204
+ | `#clear` | _(none)_ | `Integer` | Remove all pending tasks and return the number cleared |
146
205
 
147
206
  ## Development
148
207
 
@@ -19,7 +19,10 @@ 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
25
+ @complete_handler = nil
23
26
  @stats = { completed: 0, failed: 0, in_flight: 0 }
24
27
  end
25
28
 
@@ -34,12 +37,66 @@ module Philiprehberger
34
37
  self
35
38
  end
36
39
 
40
+ # Register a callback invoked after each successful task completion.
41
+ #
42
+ # The callback receives the return value of the completed task.
43
+ #
44
+ # @yield [result] called on task success
45
+ # @return [self]
46
+ def on_complete(&block)
47
+ @mutex.synchronize { @complete_handler = block }
48
+ self
49
+ end
50
+
37
51
  # Return statistics about processed tasks.
38
52
  #
39
- # @return [Hash{Symbol => Integer}] counts for :completed, :failed, :pending
53
+ # @return [Hash{Symbol => Integer}] counts for :completed, :failed, :pending, :in_flight
40
54
  def stats
41
55
  @mutex.synchronize do
42
- { 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
43
100
  end
44
101
  end
45
102
 
@@ -116,8 +173,10 @@ module Philiprehberger
116
173
  return unless @running
117
174
 
118
175
  @running = false
176
+ @paused = false
119
177
  @workers.each(&:stop)
120
178
  @condition.broadcast
179
+ @pause_condition.broadcast
121
180
  end
122
181
  end
123
182
 
@@ -133,7 +192,9 @@ module Philiprehberger
133
192
  @concurrency.times do
134
193
  @workers << Worker.new(
135
194
  @tasks, @mutex, @condition,
136
- context: { stats: @stats, error_handler: @error_handler, drain_condition: @drain_condition }
195
+ context: { stats: @stats, error_handler: @error_handler,
196
+ complete_handler: @complete_handler, drain_condition: @drain_condition,
197
+ paused: -> { @paused }, pause_condition: @pause_condition }
137
198
  )
138
199
  end
139
200
  @started = true
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module TaskQueue
5
- VERSION = '0.2.10'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
@@ -12,7 +12,10 @@ module Philiprehberger
12
12
  @condition = condition
13
13
  @stats = context[:stats]
14
14
  @error_handler = context[:error_handler]
15
+ @complete_handler = context[:complete_handler]
15
16
  @drain_condition = context[:drain_condition]
17
+ @paused = context[:paused]
18
+ @pause_condition = context[:pause_condition]
16
19
  @running = true
17
20
  @thread = Thread.new { run }
18
21
  end
@@ -42,23 +45,27 @@ module Philiprehberger
42
45
  @condition.wait(@mutex) while @queue.empty? && @running
43
46
  return nil unless @running || !@queue.empty?
44
47
 
48
+ @pause_condition.wait(@mutex) while @paused&.call && @running
49
+ return nil unless @running || !@queue.empty?
50
+
45
51
  @stats[:in_flight] += 1
46
52
  @queue.shift
47
53
  end
48
54
  end
49
55
 
50
56
  def execute(task)
51
- task.call
52
- record_completion
57
+ result = task.call
58
+ record_completion(result)
53
59
  rescue StandardError => e
54
60
  record_failure(e, task)
55
61
  end
56
62
 
57
- def record_completion
63
+ def record_completion(result)
58
64
  @mutex.synchronize do
59
65
  @stats[:completed] += 1
60
66
  @stats[:in_flight] -= 1
61
67
  end
68
+ @complete_handler&.call(result)
62
69
  end
63
70
 
64
71
  def record_failure(error, task)
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.2.10
4
+ version: 0.4.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-03-31 00:00:00.000000000 Z
11
+ date: 2026-04-06 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.
@@ -25,11 +25,11 @@ files:
25
25
  - lib/philiprehberger/task_queue/queue.rb
26
26
  - lib/philiprehberger/task_queue/version.rb
27
27
  - lib/philiprehberger/task_queue/worker.rb
28
- homepage: https://github.com/philiprehberger/rb-task-queue
28
+ homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-task_queue
29
29
  licenses:
30
30
  - MIT
31
31
  metadata:
32
- homepage_uri: https://github.com/philiprehberger/rb-task-queue
32
+ homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-task_queue
33
33
  source_code_uri: https://github.com/philiprehberger/rb-task-queue
34
34
  changelog_uri: https://github.com/philiprehberger/rb-task-queue/blob/main/CHANGELOG.md
35
35
  bug_tracker_uri: https://github.com/philiprehberger/rb-task-queue/issues