Facade: TIMEx.deadline, TIMEx.call¶
These two methods are the front door of the gem. Same story every time:
you bring a budget (seconds, nil, or a ready-made Deadline), TIMEx picks a
strategy, and your block receives the live Deadline so cooperative code can
check! as it goes.
TIMEx.deadline(2.0) { |d| work(d) }
# alias—nice for code review
TIMEx.deadline(2.0) { |d| work(d) }
# you already built the Deadline
TIMEx.deadline(my_deadline) { |d| work(d) }
Options¶
TIMEx.deadline(
deadline_or_seconds,
strategy: nil, # Symbol, callable strategy, or nil → default
auto_check: nil, # nil → use config default; true/false per call
on_timeout: :raise, # :raise | :raise_standard | :return_nil | :result | Proc
**strategy_specific_opts,
&block
)
| Option | What it does |
|---|---|
strategy: |
Override the default cooperative runner. Reach for IO, subprocess, or a composer instance when the problem is not “my own Ruby loop.” |
auto_check: |
Opt into TracePoint polling so legacy code gets check! without you rewriting it—trade CPU for safety. See Auto-check. |
on_timeout: |
What happens when time is up (Expired vs TimeoutError, nil, Result.timeout, or your proc). Per-call wins over global config. |
**strategy_specific_opts |
Passed through to the strategy you picked—each strategy documents its own extras. |
on_timeout: cheat sheet¶
| Value | What you get back |
|---|---|
:raise (default) |
Raises TIMEx::Expired (a bare Exception—survives rescue => e). |
:raise_standard |
Raises TIMEx::TimeoutError (a StandardError); original Expired is on #original. |
:return_nil |
Quietly returns nil so the caller can branch on truthiness. |
:result |
Returns a frozen TIMEx::Result.timeout(...) you pattern-match (result in [:timeout, _, _]) or value! to re-raise. |
Proc |
Your proc receives the Expired; whatever it returns becomes the call's return value. |
How the default strategy is chosen¶
TIMEx walks this list and stops at the first hit:
- You passed
strategy:explicitly—always wins. Registry.default_selectorsays otherwise (companion gems sometimes inject “use the scheduler when Async is active,” that kind of thing).- Fall back to
config.default_strategy, which is:cooperativeout of the box.
If that sounds like plumbing, it is—most apps only touch step 1 when they need to. Global wiring lives in Configuration and Internals.
Real-world: hand an SDK the remaining budget¶
The Rack middleware already minted a Deadline for this request, and your
Stripe (or any third-party) SDK accepts its own timeout: option. Use
deadline so you do not start a fresh clock, and feed
deadline.remaining into the SDK so its socket timeouts agree with what
the caller asked for:
def charge!(amount_cents:, customer_id:, deadline:)
TIMEx.deadline(deadline) do |d|
Stripe::PaymentIntent.create(
{ amount: amount_cents, currency: "usd", customer: customer_id, confirm: true },
{ open_timeout: d.remaining, read_timeout: d.remaining }
)
end
end
If d expires mid-call, Stripe’s own socket timeout fires with the same
number TIMEx is tracking—no “my timer says 0, yours says 30” mystery.