philiprehberger-timeout_kit 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 +14 -0
- data/README.md +67 -5
- data/lib/philiprehberger/timeout_kit/deadline.rb +65 -6
- data/lib/philiprehberger/timeout_kit/version.rb +1 -1
- data/lib/philiprehberger/timeout_kit.rb +11 -8
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 38648273212e92534eb7dceadb874fdff11a9f79537322caccce2b80265eb227
|
|
4
|
+
data.tar.gz: 630c54614f2001126fe6a4f251421245b83832513e1c7b7ac865866a4cf5c77a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21f72a37fd9180990963331e97658273e1eee1e5492ecab899fb5540c4843cd55fa2cfd2345920e6840c4b47446c711fb599e4b367a22dc4f7b04ae7de86704b
|
|
7
|
+
data.tar.gz: 1850ebc59963836b441862c3794151f225247604462f43eaa3252ac1efc46efa0b2f5eb25622e992fed1d8c200b430f89f4253e1fbd671a29bf00ca24b17944e
|
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.0] - 2026-03-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Deadline callbacks via `on_expire:` keyword argument or `Deadline#on_expire` block registration, fired once on first expiry detection
|
|
14
|
+
- Grace period via `grace:` keyword argument with `Deadline#in_grace?` and `Deadline#grace_remaining` accessors
|
|
15
|
+
- Deadline naming via `name:` keyword argument with `Deadline#name` accessor, included in error messages
|
|
16
|
+
- Full README compliance with 8 badges, Support section, and all standard sections
|
|
17
|
+
- GitHub issue templates (bug report, feature request), dependabot config, and PR template
|
|
18
|
+
|
|
19
|
+
## [0.1.1] - 2026-03-22
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Expanded test suite from 19 to 30+ examples covering edge cases, error hierarchy, zero/large timeouts, nested deadlines, and cooperative timeout loops
|
|
23
|
+
|
|
10
24
|
## [0.1.0] - 2026-03-22
|
|
11
25
|
|
|
12
26
|
### Added
|
data/README.md
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/philiprehberger/rb-timeout-kit/actions/workflows/ci.yml)
|
|
4
4
|
[](https://rubygems.org/gems/philiprehberger-timeout_kit)
|
|
5
|
+
[](https://github.com/philiprehberger/rb-timeout-kit/releases)
|
|
6
|
+
[](https://github.com/philiprehberger/rb-timeout-kit/commits/main)
|
|
5
7
|
[](LICENSE)
|
|
8
|
+
[](https://github.com/philiprehberger/rb-timeout-kit/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
|
9
|
+
[](https://github.com/philiprehberger/rb-timeout-kit/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
|
10
|
+
[](https://github.com/sponsors/philiprehberger)
|
|
6
11
|
|
|
7
12
|
Safe timeout patterns without Thread.raise
|
|
8
13
|
|
|
@@ -65,6 +70,52 @@ Philiprehberger::TimeoutKit.deadline(30) do |outer|
|
|
|
65
70
|
end
|
|
66
71
|
```
|
|
67
72
|
|
|
73
|
+
### Deadline Naming
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
Philiprehberger::TimeoutKit.deadline(10, name: 'db_query') do |d|
|
|
77
|
+
d.check! # raises "Deadline 'db_query' exceeded" if expired
|
|
78
|
+
puts d.name # => "db_query"
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Deadline Callbacks
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
Philiprehberger::TimeoutKit.deadline(10, on_expire: -> { cleanup() }) do |d|
|
|
86
|
+
loop do
|
|
87
|
+
d.check! # fires callback once on first expiry detection
|
|
88
|
+
process_next_item
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Or register via block
|
|
93
|
+
Philiprehberger::TimeoutKit.deadline(10) do |d|
|
|
94
|
+
d.on_expire { cleanup() }
|
|
95
|
+
loop do
|
|
96
|
+
d.check!
|
|
97
|
+
process_next_item
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Grace Period
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
Philiprehberger::TimeoutKit.deadline(10, grace: 2) do |d|
|
|
106
|
+
loop do
|
|
107
|
+
d.check! # does not raise during 2s grace period
|
|
108
|
+
break if d.expired?
|
|
109
|
+
|
|
110
|
+
process_next_item
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if d.in_grace?
|
|
114
|
+
puts "Grace period: #{d.grace_remaining}s left to wrap up"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
68
119
|
### Cooperative Timeout
|
|
69
120
|
|
|
70
121
|
```ruby
|
|
@@ -89,12 +140,16 @@ end
|
|
|
89
140
|
|
|
90
141
|
| Method | Description |
|
|
91
142
|
|--------|-------------|
|
|
92
|
-
| `.deadline(seconds) { \|d\| }` | Execute a block with a cooperative deadline |
|
|
143
|
+
| `.deadline(seconds, name:, grace:, on_expire:) { \|d\| }` | Execute a block with a cooperative deadline |
|
|
93
144
|
| `.cooperative(seconds) { \|t\| }` | Execute a block with a simple cooperative timeout |
|
|
94
145
|
| `.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 (
|
|
97
|
-
| `Deadline#expired?` | Whether the deadline has passed |
|
|
146
|
+
| `Deadline#check!` | Raise `DeadlineExceeded` if the deadline has passed (respects grace period) |
|
|
147
|
+
| `Deadline#remaining` | Seconds remaining until the primary deadline (negative during grace) |
|
|
148
|
+
| `Deadline#expired?` | Whether the primary deadline has passed |
|
|
149
|
+
| `Deadline#name` | The human-readable name for this deadline (nil if not set) |
|
|
150
|
+
| `Deadline#in_grace?` | Whether the deadline is in the grace period |
|
|
151
|
+
| `Deadline#grace_remaining` | Seconds remaining in the grace period (0.0 if none) |
|
|
152
|
+
| `Deadline#on_expire { }` | Register a callback that fires once on expiry detection |
|
|
98
153
|
| `DeadlineExceeded` | Raised when a deadline or timeout expires |
|
|
99
154
|
|
|
100
155
|
## Development
|
|
@@ -105,6 +160,13 @@ bundle exec rspec
|
|
|
105
160
|
bundle exec rubocop
|
|
106
161
|
```
|
|
107
162
|
|
|
163
|
+
## Support
|
|
164
|
+
|
|
165
|
+
If you find this package useful, consider giving it a star on GitHub — it helps motivate continued maintenance and development.
|
|
166
|
+
|
|
167
|
+
[](https://www.linkedin.com/in/philiprehberger)
|
|
168
|
+
[](https://philiprehberger.com/open-source-packages)
|
|
169
|
+
|
|
108
170
|
## License
|
|
109
171
|
|
|
110
|
-
MIT
|
|
172
|
+
[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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
81
|
-
require_relative
|
|
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.
|
|
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-30 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
|
|
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: []
|