philiprehberger-task_queue 0.1.0 → 0.2.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 +8 -0
- data/README.md +30 -0
- data/lib/philiprehberger/task_queue/queue.rb +44 -1
- data/lib/philiprehberger/task_queue/version.rb +1 -1
- data/lib/philiprehberger/task_queue/worker.rb +24 -3
- 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: 34263f9a3370dc191d5f7f2ec9744e9c92b9e50b80e305e9916d097a070d6b8b
|
|
4
|
+
data.tar.gz: 230bf370d52a1b02eb928ac0a53b3f0abbb519cd5396903c622f831691f6128e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f96333a34f87690f6ada381f4eec29e419aaf1a72aaf2237f7fd31d3af252aa6d11f2851613afcf56040f4c79bd50d12d397adf949b7eac2a01ee2d10f15b886
|
|
7
|
+
data.tar.gz: a607205711f5647aceb92c6eec0c19103681a7c074ddfd1de7b3dbda30956f356403770bb36721c9ea5a2171d9282a84117f239c66b9a99b8c1a47a73875119b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.0] - 2026-03-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `on_error` callback for handling task failures
|
|
10
|
+
- `stats` method returning completed, failed, and pending task counts
|
|
11
|
+
- `drain(timeout:)` method to wait for task completion without shutting down
|
|
12
|
+
|
|
5
13
|
## [0.1.0] - 2026-03-10
|
|
6
14
|
|
|
7
15
|
### Added
|
data/README.md
CHANGED
|
@@ -42,6 +42,33 @@ queue.shutdown(timeout: 30)
|
|
|
42
42
|
queue << -> { puts "Hello from a task!" }
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
### Error handling
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
queue = Philiprehberger::TaskQueue.new
|
|
49
|
+
|
|
50
|
+
queue.on_error do |exception, task|
|
|
51
|
+
puts "Task failed: #{exception.message}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
queue.push { raise "oops" }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Statistics
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
queue.stats
|
|
61
|
+
# => { completed: 5, failed: 1, pending: 2 }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Draining
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
10.times { |i| queue.push { process(i) } }
|
|
68
|
+
queue.drain(timeout: 10) # waits for all tasks to finish
|
|
69
|
+
# queue is still running and accepting new tasks
|
|
70
|
+
```
|
|
71
|
+
|
|
45
72
|
## API
|
|
46
73
|
|
|
47
74
|
| Method | Description |
|
|
@@ -52,6 +79,9 @@ queue << -> { puts "Hello from a task!" }
|
|
|
52
79
|
| `#size` | Number of pending (not yet started) tasks |
|
|
53
80
|
| `#running?` | Whether the queue is accepting new tasks |
|
|
54
81
|
| `#shutdown(timeout: 30)` | Gracefully stop all workers, waiting up to `timeout` seconds |
|
|
82
|
+
| `#on_error(&block)` | Register error callback for failed tasks |
|
|
83
|
+
| `#stats` | Returns hash with `:completed`, `:failed`, `:pending` counts |
|
|
84
|
+
| `#drain(timeout: 30)` | Block until all pending tasks complete (without shutdown) |
|
|
55
85
|
|
|
56
86
|
## License
|
|
57
87
|
|
|
@@ -15,9 +15,49 @@ module Philiprehberger
|
|
|
15
15
|
@tasks = []
|
|
16
16
|
@mutex = Mutex.new
|
|
17
17
|
@condition = ConditionVariable.new
|
|
18
|
+
@drain_condition = ConditionVariable.new
|
|
18
19
|
@workers = []
|
|
19
20
|
@running = true
|
|
20
21
|
@started = false
|
|
22
|
+
@error_handler = nil
|
|
23
|
+
@stats = { completed: 0, failed: 0, in_flight: 0 }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Register a callback invoked when a task raises an exception.
|
|
27
|
+
#
|
|
28
|
+
# The callback receives the exception and the task that raised it.
|
|
29
|
+
#
|
|
30
|
+
# @yield [exception, task] called on task failure
|
|
31
|
+
# @return [self]
|
|
32
|
+
def on_error(&block)
|
|
33
|
+
@mutex.synchronize { @error_handler = block }
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Return statistics about processed tasks.
|
|
38
|
+
#
|
|
39
|
+
# @return [Hash{Symbol => Integer}] counts for :completed, :failed, :pending
|
|
40
|
+
def stats
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
{ completed: @stats[:completed], failed: @stats[:failed], pending: @tasks.size }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Block until all pending tasks are complete without shutting down.
|
|
47
|
+
#
|
|
48
|
+
# @param timeout [Numeric] seconds to wait before returning
|
|
49
|
+
# @return [void]
|
|
50
|
+
def drain(timeout: 30)
|
|
51
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
52
|
+
@mutex.synchronize do
|
|
53
|
+
while !@tasks.empty? || @stats[:in_flight].positive?
|
|
54
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
55
|
+
break if remaining <= 0
|
|
56
|
+
|
|
57
|
+
@drain_condition.wait(@mutex, remaining)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
nil
|
|
21
61
|
end
|
|
22
62
|
|
|
23
63
|
# Enqueue a task to be processed asynchronously.
|
|
@@ -91,7 +131,10 @@ module Philiprehberger
|
|
|
91
131
|
|
|
92
132
|
def start_workers
|
|
93
133
|
@concurrency.times do
|
|
94
|
-
@workers << Worker.new(
|
|
134
|
+
@workers << Worker.new(
|
|
135
|
+
@tasks, @mutex, @condition,
|
|
136
|
+
context: { stats: @stats, error_handler: @error_handler, drain_condition: @drain_condition }
|
|
137
|
+
)
|
|
95
138
|
end
|
|
96
139
|
@started = true
|
|
97
140
|
end
|
|
@@ -6,10 +6,13 @@ module Philiprehberger
|
|
|
6
6
|
class Worker
|
|
7
7
|
attr_reader :thread
|
|
8
8
|
|
|
9
|
-
def initialize(queue, mutex, condition)
|
|
9
|
+
def initialize(queue, mutex, condition, context:)
|
|
10
10
|
@queue = queue
|
|
11
11
|
@mutex = mutex
|
|
12
12
|
@condition = condition
|
|
13
|
+
@stats = context[:stats]
|
|
14
|
+
@error_handler = context[:error_handler]
|
|
15
|
+
@drain_condition = context[:drain_condition]
|
|
13
16
|
@running = true
|
|
14
17
|
@thread = Thread.new { run }
|
|
15
18
|
end
|
|
@@ -30,6 +33,7 @@ module Philiprehberger
|
|
|
30
33
|
break unless task
|
|
31
34
|
|
|
32
35
|
execute(task)
|
|
36
|
+
@mutex.synchronize { @drain_condition.broadcast }
|
|
33
37
|
end
|
|
34
38
|
end
|
|
35
39
|
|
|
@@ -38,14 +42,31 @@ module Philiprehberger
|
|
|
38
42
|
@condition.wait(@mutex) while @queue.empty? && @running
|
|
39
43
|
return nil unless @running || !@queue.empty?
|
|
40
44
|
|
|
45
|
+
@stats[:in_flight] += 1
|
|
41
46
|
@queue.shift
|
|
42
47
|
end
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
def execute(task)
|
|
46
51
|
task.call
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
record_completion
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
record_failure(e, task)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def record_completion
|
|
58
|
+
@mutex.synchronize do
|
|
59
|
+
@stats[:completed] += 1
|
|
60
|
+
@stats[:in_flight] -= 1
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def record_failure(error, task)
|
|
65
|
+
@mutex.synchronize do
|
|
66
|
+
@stats[:failed] += 1
|
|
67
|
+
@stats[:in_flight] -= 1
|
|
68
|
+
end
|
|
69
|
+
@error_handler&.call(error, task)
|
|
49
70
|
end
|
|
50
71
|
end
|
|
51
72
|
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.2.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-
|
|
11
|
+
date: 2026-03-13 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.
|