Tips and Tricks¶
Little habits that make TIMEx feel obvious in code review. None of these are secret features—just the stuff we reach for after the first week of wiring deadlines for real.
Set the client’s own timeout first
TIMEx caps how long Ruby waits. The HTTP client, DB driver, or RPC stub
is what actually stops the IO. Always configure their native timeouts
(read_timeout, statement_timeout, gRPC deadline:, etc.) — then wrap
them in TIMEx.deadline to share one budget across hops.
Real-world: one checkout flow, many IO calls¶
During payment capture you might read Redis, call a card gateway, then enqueue a
receipt—all under one Rack-derived deadline. Thread Deadline through plain
Ruby methods (no global timer per hop) so a slow fraud check does not leave the
card client zero time:
def capture!(deadline:)
fraud_client.verify!(deadline: deadline)
charge = gateway.capture(deadline: deadline.min(2.0))
enqueue_receipt(charge.id, deadline: deadline)
end
Pass the deadline down the stack¶
def fetch(deadline:)
TIMEx::Strategies::IO.read(socket, 4096, deadline: deadline)
end
TIMEx.deadline(2.0) { |d| fetch(deadline: d) }
Treat Deadline like a permission slip: hand it to helpers so every layer
knows how much time is left. Nested work can shrink the budget with
Deadline#min:
def call_external(deadline:)
TIMEx.deadline(deadline.min(0.5)) { real_call } # never more than 500 ms here
end
Use shield only for cleanup¶
shield says “do not cancel this tiny block for cooperative deadlines.”
Perfect for releasing handles; not a hiding place for more slow work.
Let telemetry be your flight recorder¶
Every strategy emits a finish event with outcome, elapsed_ms,
strategy, and deadline_ms. Plug an adapter once—see
Telemetry—and you get a straight answer to “what timed out,
where, and how long did we burn?”
Test time without sleep¶
around { |ex| TIMEx::Test.with_virtual_clock { ex.run } }
it "expires" do
d = TIMEx::Deadline.in(1.0)
TIMEx::Test.advance(2.0)
expect(d).to be_expired
end
Full tour: Testing.
Lint the scary rescues¶
That helper nags about rescue Exception and bare rescue inside
TIMEx.deadline blocks—patterns that swallow cooperative timeouts and pretend
everything succeeded.
Useful examples¶
End-to-end recipes for common scenarios. Each is a single-page, self-contained snippet you can copy-paste.
| Recipe | Strategies / Composers |
|---|---|
| LLM calls with RubyLLM + TIMEx | Faraday request_timeout, propagation, Result |
| Net::HTTP request with deadline | IO, propagation |
| PG query with deadline | Closeable, IO |
| Redis with deadline | IO |
| Faraday middleware | IO, propagation |
| Sidekiq job deadline | Cooperative, propagation |
| Rack request deadline | RackMiddleware |
| gRPC deadline propagation | Propagation |
| CLI long-running command | TwoPhase, Subprocess |
| Untrusted user code | Subprocess |
| Hedged RPC call | Hedged |
| Two-phase graceful shutdown | TwoPhase |
| Adaptive timeout from history | Adaptive |
| Lease-based distributed job | Lease (placeholder) |
| OpenTelemetry spans | Telemetry |
| ActiveSupport instrumentation | Telemetry |
Migrating a legacy Timeout.timeout |
Cooperative, TwoPhase |