philiprehberger-task_queue 0.1.0 → 0.2.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: 0234cce054f1fc8e8923a25a414198c1863bfa035f9e9881ab38b48c484460d4
4
- data.tar.gz: e93358c9bec5ab194464dbdec66cdbf9ff414970d21e4539b578c9f01effd6ff
3
+ metadata.gz: e3cd82560d64b9754fc30641e980afce535bfeabbbc0fff39ecdd0a2032734b5
4
+ data.tar.gz: ef9c65fd500093f530cb0cf0f71e3b17d934682b8dafa881baf471354abcad2b
5
5
  SHA512:
6
- metadata.gz: 045c07963ff8f40a847eb375b2b04830f092fe5d0355fc3cf0db59865328083141f6859b2c18abbe46d844c8cd4af3b25143cfa79f4b01fabb28fc7e3acf0a2f
7
- data.tar.gz: f6d1b9a18e472fc17b717b65a076918319e83445d30f668df121833207fe7b70345d6e1b0264951b95e3f39f79643c4d6f8e3d0e575ee918175dc152cf2d9470
6
+ metadata.gz: 66bbada6cc0dd9c204d673bfa097943320ec7f12ba8be66b15acdaf9670d9f1fb9052de5d88b2255bb09b1c4eb155668799cde871769a70b45ff64e0c389f230
7
+ data.tar.gz: '019c01d08b8e2c72b4a987d98de7e2bd58d5c6c0a5fd5dd24c9e8de7ff70a1485e87b77f1368f433e38e80c31e479c632af72fdd76f0b1d2d62a17ebe2bd90e9'
data/CHANGELOG.md CHANGED
@@ -1,7 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - Add License badge to README
6
+ - Add bug_tracker_uri to gemspec
7
+ - Add Development section to README
8
+ - Add Requirements section to README
9
+
3
10
  All notable changes to this project will be documented in this file.
4
11
 
12
+ ## [0.2.0] - 2026-03-12
13
+
14
+ ### Added
15
+
16
+ - `on_error` callback for handling task failures
17
+ - `stats` method returning completed, failed, and pending task counts
18
+ - `drain(timeout:)` method to wait for task completion without shutting down
19
+
5
20
  ## [0.1.0] - 2026-03-10
6
21
 
7
22
  ### Added
data/README.md CHANGED
@@ -2,9 +2,14 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/philiprehberger-task_queue.svg)](https://rubygems.org/gems/philiprehberger-task_queue)
4
4
  [![CI](https://github.com/philiprehberger/rb-task-queue/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-task-queue/actions/workflows/ci.yml)
5
+ [![License](https://img.shields.io/github/license/philiprehberger/rb-task-queue)](LICENSE)
5
6
 
6
7
  In-process async job queue with concurrency control for Ruby.
7
8
 
9
+ ## Requirements
10
+
11
+ - Ruby >= 3.1
12
+
8
13
  ## Installation
9
14
 
10
15
  Add to your Gemfile:
@@ -42,6 +47,33 @@ queue.shutdown(timeout: 30)
42
47
  queue << -> { puts "Hello from a task!" }
43
48
  ```
44
49
 
50
+ ### Error handling
51
+
52
+ ```ruby
53
+ queue = Philiprehberger::TaskQueue.new
54
+
55
+ queue.on_error do |exception, task|
56
+ puts "Task failed: #{exception.message}"
57
+ end
58
+
59
+ queue.push { raise "oops" }
60
+ ```
61
+
62
+ ### Statistics
63
+
64
+ ```ruby
65
+ queue.stats
66
+ # => { completed: 5, failed: 1, pending: 2 }
67
+ ```
68
+
69
+ ### Draining
70
+
71
+ ```ruby
72
+ 10.times { |i| queue.push { process(i) } }
73
+ queue.drain(timeout: 10) # waits for all tasks to finish
74
+ # queue is still running and accepting new tasks
75
+ ```
76
+
45
77
  ## API
46
78
 
47
79
  | Method | Description |
@@ -52,6 +84,18 @@ queue << -> { puts "Hello from a task!" }
52
84
  | `#size` | Number of pending (not yet started) tasks |
53
85
  | `#running?` | Whether the queue is accepting new tasks |
54
86
  | `#shutdown(timeout: 30)` | Gracefully stop all workers, waiting up to `timeout` seconds |
87
+ | `#on_error(&block)` | Register error callback for failed tasks |
88
+ | `#stats` | Returns hash with `:completed`, `:failed`, `:pending` counts |
89
+ | `#drain(timeout: 30)` | Block until all pending tasks complete (without shutdown) |
90
+
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ bundle install
96
+ bundle exec rspec
97
+ bundle exec rubocop
98
+ ```
55
99
 
56
100
  ## License
57
101
 
@@ -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(@tasks, @mutex, @condition)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module TaskQueue
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.1"
6
6
  end
7
7
  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
- rescue StandardError
48
- # Swallow exceptions to keep the worker alive.
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.1.0
4
+ version: 0.2.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-03-10 00:00:00.000000000 Z
11
+ date: 2026-03-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.