philiprehberger-timeout_kit
Safe timeout patterns without Thread.raise
Requirements
- Ruby >= 3.1
Installation
Add to your Gemfile:
gem "philiprehberger-timeout_kit"
Or install directly:
gem install philiprehberger-timeout_kit
Usage
require "philiprehberger/timeout_kit"
Philiprehberger::TimeoutKit.deadline(5) do |d|
loop do
d.check! # raises DeadlineExceeded if time is up
process_next_item
end
end
Remaining Time
Philiprehberger::TimeoutKit.deadline(10) do |d|
while d.remaining > 1
do_work
d.check!
end
puts "Only #{d.remaining}s left, wrapping up"
end
Measuring elapsed time
Philiprehberger::TimeoutKit.deadline(10) do |d|
do_work
puts "Work took #{d.elapsed}s so far"
# elapsed continues to grow past the budget even after expiration
end
Nested Deadlines
Philiprehberger::TimeoutKit.deadline(30) do |outer|
# Inner deadline is tighter, so it takes precedence
Philiprehberger::TimeoutKit.deadline(5) do |inner|
inner.check!
puts inner.remaining # <= 5
end
# After inner block, outer deadline is restored
outer.check!
puts outer.remaining # <= 30
end
Deadline Naming
Philiprehberger::TimeoutKit.deadline(10, name: 'db_query') do |d|
d.check! # raises "Deadline 'db_query' exceeded" if expired
puts d.name # => "db_query"
end
Deadline Callbacks
Philiprehberger::TimeoutKit.deadline(10, on_expire: -> { cleanup() }) do |d|
loop do
d.check! # fires callback once on first expiry detection
process_next_item
end
end
# Or register via block
Philiprehberger::TimeoutKit.deadline(10) do |d|
d.on_expire { cleanup() }
loop do
d.check!
process_next_item
end
end
Grace Period
Philiprehberger::TimeoutKit.deadline(10, grace: 2) do |d|
loop do
d.check! # does not raise during 2s grace period
break if d.expired?
process_next_item
end
if d.in_grace?
puts "Grace period: #{d.grace_remaining}s left to wrap up"
end
end
Cooperative Timeout
Philiprehberger::TimeoutKit.cooperative(5) do |t|
items.each do |item|
t.check!
process(item)
end
end
Current Deadline
Philiprehberger::TimeoutKit.deadline(10) do |_d|
current = Philiprehberger::TimeoutKit.current_deadline
puts current.remaining
end
API
| Method | Description |
|---|---|
| `.deadline(seconds, name:, grace:, on_expire:) { \ | d\ |
| `.cooperative(seconds) { \ | t\ |
.current_deadline |
Return the current active deadline or nil |
Deadline#check! |
Raise DeadlineExceeded if the deadline has passed (respects grace period) |
Deadline#remaining |
Seconds remaining until the primary deadline (negative during grace) |
Deadline#elapsed |
Seconds elapsed since the deadline was created (continues past the budget after expiration) |
Deadline#expired? |
Whether the primary deadline has passed |
Deadline#name |
The human-readable name for this deadline (nil if not set) |
Deadline#in_grace? |
Whether the deadline is in the grace period |
Deadline#grace_remaining |
Seconds remaining in the grace period (0.0 if none) |
Deadline#on_expire { } |
Register a callback that fires once on expiry detection |
DeadlineExceeded |
Raised when a deadline or timeout expires |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful: