Skip to content

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

TIMEx.deadline(1.0) do |d|
  begin
    work
  ensure
    d.shield { release_resources }
  end
end

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

bin/timex-lint app lib

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