Safe timeout patterns without Thread.raise
gem install philiprehberger-timeout_kitSafe timeout patterns without Thread.raise
Add to your Gemfile:
gem "philiprehberger-timeout_kit"
Or install directly:
gem install philiprehberger-timeout_kit
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
Philiprehberger::TimeoutKit.deadline(10) do |d|
while d.remaining > 1
do_work
d.check!
end
puts "Only #{d.remaining}s left, wrapping up"
end
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
deadline = Philiprehberger::TimeoutKit::Deadline.new(10)
sleep 5
deadline.progress # => 0.5 (approximately)
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
Philiprehberger::TimeoutKit.deadline(10, name: 'db_query') do |d|
d.check! # raises "Deadline 'db_query' exceeded" if expired
puts d.name # => "db_query"
end
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
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
Philiprehberger::TimeoutKit.cooperative(5) do |t|
items.each do |item|
t.check!
process(item)
end
end
Philiprehberger::TimeoutKit.deadline(10) do |_d|
current = Philiprehberger::TimeoutKit.current_deadline
puts current.remaining
end
| Method | Description |
|---|---|
.deadline(seconds, name:, grace:, on_expire:) { |d| } | Execute a block with a cooperative deadline |
.cooperative(seconds) { |t| } | Execute a block with a simple cooperative timeout |
.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#duration | The original budget passed to Deadline.new (Float) |
Deadline#progress | Fraction of the budget that has elapsed (elapsed / duration); exceeds 1.0 after expiry |
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 |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: