philiprehberger-timeout_kit 0.1.1 → 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: cb17ea7722ec6fc8ea24e1a8a0893646325c69b699ca72540316246805883ae9
4
- data.tar.gz: 4e0f67f146bf729e7b168f663ee8487a750a7c2db2b41dea0ba33b21b851a440
3
+ metadata.gz: 7a8adc873e824dbd74917d235e838e7471d7e8d5171dced00b3c551d6cd122c7
4
+ data.tar.gz: c32d783deeb26085d274dd7e802f0127848ad14292d25bb90edc72f8dbe1112b
5
5
  SHA512:
6
- metadata.gz: fc7a2917806f244243e6c48cfd0a9fec9a86494257317242b766bbeb42a0a9748ab1810ef38834c6e2d82bd6c9909dfcfdb8eca5a627cbdc8b5bcc1db75141dc
7
- data.tar.gz: 397b475df8ea8cc6b3c1139e28d2e84ec00548b49caeade1187981c1eca99daa040203e946a64d1531a22cdcb8ac6afaa0f2791fe661dd707173f70dd517c0e9
6
+ metadata.gz: 41d90f277f055add991dede2a122ba767b109cc1d112526ef4aab0a217b935ad16df0e38ddf78ec78b9cc54de7e4d5c8b542b2b93d6f7c95908ed0deb3c7804a
7
+ data.tar.gz: 39b256c071b1b339f37b02bb13c07ed76fe486b78006961178c6d6e56d0046c1f04dab90d102b0fd14aa3cfb5b6a343f239c92e654de13fa9ce79658e1c56f64
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2026-03-31
11
+
12
+ ### Changed
13
+ - Standardize README badges, support section, and license format
14
+
15
+ ## [0.2.0] - 2026-03-29
16
+
17
+ ### Added
18
+ - Deadline callbacks via `on_expire:` keyword argument or `Deadline#on_expire` block registration, fired once on first expiry detection
19
+ - Grace period via `grace:` keyword argument with `Deadline#in_grace?` and `Deadline#grace_remaining` accessors
20
+ - Deadline naming via `name:` keyword argument with `Deadline#name` accessor, included in error messages
21
+ - Full README compliance with 8 badges, Support section, and all standard sections
22
+ - GitHub issue templates (bug report, feature request), dependabot config, and PR template
23
+
10
24
  ## [0.1.1] - 2026-03-22
11
25
 
12
26
  ### Added
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Tests](https://github.com/philiprehberger/rb-timeout-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-timeout-kit/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://badge.fury.io/rb/philiprehberger-timeout_kit.svg)](https://rubygems.org/gems/philiprehberger-timeout_kit)
5
- [![License](https://img.shields.io/github/license/philiprehberger/rb-timeout-kit)](LICENSE)
5
+ [![Last updated](https://img.shields.io/github/last-commit/philiprehberger/rb-timeout-kit)](https://github.com/philiprehberger/rb-timeout-kit/commits/main)
6
6
 
7
7
  Safe timeout patterns without Thread.raise
8
8
 
@@ -65,6 +65,52 @@ Philiprehberger::TimeoutKit.deadline(30) do |outer|
65
65
  end
66
66
  ```
67
67
 
68
+ ### Deadline Naming
69
+
70
+ ```ruby
71
+ Philiprehberger::TimeoutKit.deadline(10, name: 'db_query') do |d|
72
+ d.check! # raises "Deadline 'db_query' exceeded" if expired
73
+ puts d.name # => "db_query"
74
+ end
75
+ ```
76
+
77
+ ### Deadline Callbacks
78
+
79
+ ```ruby
80
+ Philiprehberger::TimeoutKit.deadline(10, on_expire: -> { cleanup() }) do |d|
81
+ loop do
82
+ d.check! # fires callback once on first expiry detection
83
+ process_next_item
84
+ end
85
+ end
86
+
87
+ # Or register via block
88
+ Philiprehberger::TimeoutKit.deadline(10) do |d|
89
+ d.on_expire { cleanup() }
90
+ loop do
91
+ d.check!
92
+ process_next_item
93
+ end
94
+ end
95
+ ```
96
+
97
+ ### Grace Period
98
+
99
+ ```ruby
100
+ Philiprehberger::TimeoutKit.deadline(10, grace: 2) do |d|
101
+ loop do
102
+ d.check! # does not raise during 2s grace period
103
+ break if d.expired?
104
+
105
+ process_next_item
106
+ end
107
+
108
+ if d.in_grace?
109
+ puts "Grace period: #{d.grace_remaining}s left to wrap up"
110
+ end
111
+ end
112
+ ```
113
+
68
114
  ### Cooperative Timeout
69
115
 
70
116
  ```ruby
@@ -89,12 +135,16 @@ end
89
135
 
90
136
  | Method | Description |
91
137
  |--------|-------------|
92
- | `.deadline(seconds) { \|d\| }` | Execute a block with a cooperative deadline |
138
+ | `.deadline(seconds, name:, grace:, on_expire:) { \|d\| }` | Execute a block with a cooperative deadline |
93
139
  | `.cooperative(seconds) { \|t\| }` | Execute a block with a simple cooperative timeout |
94
140
  | `.current_deadline` | Return the current active deadline or nil |
95
- | `Deadline#check!` | Raise `DeadlineExceeded` if the deadline has passed |
96
- | `Deadline#remaining` | Seconds remaining until the deadline (0.0 if expired) |
97
- | `Deadline#expired?` | Whether the deadline has passed |
141
+ | `Deadline#check!` | Raise `DeadlineExceeded` if the deadline has passed (respects grace period) |
142
+ | `Deadline#remaining` | Seconds remaining until the primary deadline (negative during grace) |
143
+ | `Deadline#expired?` | Whether the primary deadline has passed |
144
+ | `Deadline#name` | The human-readable name for this deadline (nil if not set) |
145
+ | `Deadline#in_grace?` | Whether the deadline is in the grace period |
146
+ | `Deadline#grace_remaining` | Seconds remaining in the grace period (0.0 if none) |
147
+ | `Deadline#on_expire { }` | Register a callback that fires once on expiry detection |
98
148
  | `DeadlineExceeded` | Raised when a deadline or timeout expires |
99
149
 
100
150
  ## Development
@@ -105,6 +155,24 @@ bundle exec rspec
105
155
  bundle exec rubocop
106
156
  ```
107
157
 
158
+ ## Support
159
+
160
+ If you find this project useful:
161
+
162
+ ⭐ [Star the repo](https://github.com/philiprehberger/rb-timeout-kit)
163
+
164
+ 🐛 [Report issues](https://github.com/philiprehberger/rb-timeout-kit/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
165
+
166
+ 💡 [Suggest features](https://github.com/philiprehberger/rb-timeout-kit/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
167
+
168
+ ❤️ [Sponsor development](https://github.com/sponsors/philiprehberger)
169
+
170
+ 🌐 [All Open Source Projects](https://philiprehberger.com/open-source-packages)
171
+
172
+ 💻 [GitHub Profile](https://github.com/philiprehberger)
173
+
174
+ 🔗 [LinkedIn Profile](https://www.linkedin.com/in/philiprehberger)
175
+
108
176
  ## License
109
177
 
110
- MIT
178
+ [MIT](LICENSE)
@@ -10,38 +10,97 @@ module Philiprehberger
10
10
  # @return [Float] the absolute monotonic time when the deadline expires
11
11
  attr_reader :expires_at
12
12
 
13
+ # @return [String, nil] the human-readable name for this deadline
14
+ attr_reader :name
15
+
13
16
  # Create a new deadline.
14
17
  #
15
18
  # @param seconds [Numeric] the number of seconds until the deadline expires
16
- def initialize(seconds)
19
+ # @param name [String, nil] optional human-readable name for the deadline
20
+ # @param grace [Numeric, nil] optional grace period in seconds after the primary deadline
21
+ # @param on_expire [Proc, nil] optional callback that fires once when expiry is detected
22
+ def initialize(seconds, name: nil, grace: nil, on_expire: nil)
17
23
  @expires_at = now + seconds
24
+ @name = name
25
+ @grace_seconds = grace
26
+ @grace_expires_at = @grace_seconds ? @expires_at + @grace_seconds : nil
27
+ @on_expire = on_expire
28
+ @expire_callback_fired = false
29
+ end
30
+
31
+ # Register a callback that fires once when expiry is detected.
32
+ #
33
+ # @yield the block to call on expiry
34
+ # @return [void]
35
+ def on_expire(&block)
36
+ @on_expire = block
18
37
  end
19
38
 
20
39
  # Check whether the deadline has expired.
21
40
  #
22
- # @raise [DeadlineExceeded] if the deadline has passed
41
+ # @raise [DeadlineExceeded] if the deadline has passed (and grace period, if any, has also passed)
23
42
  # @return [void]
24
43
  def check!
25
- raise DeadlineExceeded, "Deadline exceeded" if expired?
44
+ return unless expired?
45
+
46
+ fire_expire_callback
47
+
48
+ # If we have a grace period and are still within it, don't raise
49
+ return if in_grace?
50
+
51
+ message = @name ? "Deadline '#{@name}' exceeded" : 'Deadline exceeded'
52
+ raise DeadlineExceeded, message
26
53
  end
27
54
 
28
- # Return the remaining time in seconds.
55
+ # Return the remaining time in seconds until the primary deadline.
56
+ # Can be negative during the grace period.
29
57
  #
30
- # @return [Float] seconds remaining (0.0 if expired)
58
+ # @return [Float] seconds remaining (negative if past primary deadline)
31
59
  def remaining
32
60
  r = @expires_at - now
61
+ if @grace_seconds
62
+ r
63
+ else
64
+ r.negative? ? 0.0 : r
65
+ end
66
+ end
67
+
68
+ # Return the remaining time in the grace period.
69
+ #
70
+ # @return [Float] seconds remaining in grace period (0.0 if no grace period or grace expired)
71
+ def grace_remaining
72
+ return 0.0 unless @grace_expires_at
73
+
74
+ r = @grace_expires_at - now
33
75
  r.negative? ? 0.0 : r
34
76
  end
35
77
 
36
- # Whether the deadline has expired.
78
+ # Whether the primary deadline has expired.
37
79
  #
38
80
  # @return [Boolean]
39
81
  def expired?
40
82
  now >= @expires_at
41
83
  end
42
84
 
85
+ # Whether the deadline is currently in the grace period.
86
+ # True only when the primary deadline has expired but the grace period has not.
87
+ #
88
+ # @return [Boolean]
89
+ def in_grace?
90
+ return false unless @grace_expires_at
91
+
92
+ expired? && now < @grace_expires_at
93
+ end
94
+
43
95
  private
44
96
 
97
+ def fire_expire_callback
98
+ return if @expire_callback_fired || @on_expire.nil?
99
+
100
+ @expire_callback_fired = true
101
+ @on_expire.call
102
+ end
103
+
45
104
  def now
46
105
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
47
106
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module TimeoutKit
5
- VERSION = "0.1.1"
5
+ VERSION = '0.2.1'
6
6
  end
7
7
  end
@@ -14,20 +14,23 @@ module Philiprehberger
14
14
  # constraint, but outer deadlines are also checked.
15
15
  #
16
16
  # @param seconds [Numeric] the number of seconds for the deadline
17
+ # @param name [String, nil] optional human-readable name for the deadline
18
+ # @param grace [Numeric, nil] optional grace period in seconds after the primary deadline
19
+ # @param on_expire [Proc, nil] optional callback that fires once when expiry is detected
17
20
  # @yield [deadline] the block to execute within the deadline
18
21
  # @yieldparam deadline [Deadline] the deadline context
19
22
  # @return the block's return value
20
23
  # @raise [DeadlineExceeded] if the deadline is exceeded and {Deadline#check!} is called
21
- def self.deadline(seconds, &block)
22
- dl = Deadline.new(seconds)
24
+ def self.deadline(seconds, name: nil, grace: nil, on_expire: nil, &block)
25
+ dl = Deadline.new(seconds, name: name, grace: grace, on_expire: on_expire)
23
26
 
24
27
  # Support nested deadlines: use the tightest constraint
25
28
  parent = current_deadline
26
29
  effective = if parent && parent.expires_at < dl.expires_at
27
- parent
28
- else
29
- dl
30
- end
30
+ parent
31
+ else
32
+ dl
33
+ end
31
34
 
32
35
  push_deadline(effective)
33
36
  begin
@@ -77,5 +80,5 @@ module Philiprehberger
77
80
  end
78
81
  end
79
82
 
80
- require_relative "timeout_kit/version"
81
- require_relative "timeout_kit/deadline"
83
+ require_relative 'timeout_kit/version'
84
+ require_relative 'timeout_kit/deadline'
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-timeout_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
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-22 00:00:00.000000000 Z
11
+ date: 2026-03-31 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A cooperative timeout library providing deadline and timeout patterns
14
- that avoid Thread.raise, with nested deadline support and explicit cancellation
15
- checks.
14
+ that avoid Thread.raise, with nested deadline support, grace periods, callbacks,
15
+ and explicit cancellation checks.
16
16
  email:
17
17
  - me@philiprehberger.com
18
18
  executables: []