Deadline¶
If TIMEx were a board game, TIMEx::Deadline would be the money on the
table. Strategies, composers, and your own code all speak the same object: “how
much time is left, and when is that officially over?”
You can build one from seconds, from a wall-clock moment, or from nil /
Numeric shorthands—everything else in the gem is basically choreography around
this value.
Build one¶
TIMEx::Deadline.in(1.5) # 1.5s from “now” (monotonic clock)
TIMEx::Deadline.at_wall(Time.parse("2026-01-01T00:00Z"))
TIMEx::Deadline.infinite # never expires (identity for #min)
TIMEx::Deadline.coerce(2.0) # Numeric → Deadline; handy at boundaries
Mental model: in is “budget from now.” at_wall is “real-world calendar
time,” useful when another machine sent you a timestamp. infinite means “no
rush.” coerce is the polite adapter when someone hands you a raw number.
Read it¶
d.remaining # Float seconds left
d.remaining_ms # same idea, milliseconds
d.remaining_ns # integer nanoseconds (sharp elbows for hot paths)
d.initial_ms # original budget in ms (finite deadlines), handy for telemetry
d.expired? # true once monotonic “now” passes the anchor
d.infinite?
d.depth # how many hops this budget traveled (propagation)
d.origin # optional label for who started the clock
If you are new here: remaining is the human-friendly number;
expired? is the yes/no gate before you keep burning CPU.
Combine and enforce¶
inner.min(outer) # tighter deadline wins; infinite acts like “no opinion”
deadline.check! # raises TIMEx::Expired if you are already late
deadline.shield { ... } # run cleanup without check! ruining your day
min is how nested calls share one budget: whoever is stricter wins.
check! is the cooperative heartbeat—call it in loops you control.
shield is for “I know we are past the limit but I still need two lines of
cleanup.”
Real-world: caller budget vs local SLA¶
An edge handler might receive X-TIMEx-Deadline from a mobile client while
your service policy says “never more than 800 ms in this tier.” min
applies both caps so the tighter wins—users cannot accidentally grant
themselves infinite time, and a stingy gateway cannot starve you past what your
team promised:
inbound = TIMEx::Deadline.from_header(request.get_header("X-TIMEx-Deadline"))
local = TIMEx::Deadline.in(0.8)
deadline = inbound ? inbound.min(local) : local
TIMEx.deadline(deadline) { downstream.call(deadline: deadline) }
On the wire (headers)¶
deadline.to_header # "ms=1837;depth=1"
deadline.to_header(prefer: :wall) # "wall=2026-01-01T00:00:00.000Z;depth=1"
TIMEx::Deadline.from_header(str) # parse; nil if the string is nonsense
| Piece | Plain English |
|---|---|
ms= |
“Milliseconds left,” tied to monotonic time—great inside one data center because the wall clock cannot jump backward and confuse the math. |
wall= |
“Absolute stop time,” better when hosts disagree a little on “now” but you trust NTP-ish sync. The receiver re-anchors against its own monotonic clock. |
If wall skew looks ugly, TIMEx can warn through telemetry when drift beats
config.skew_tolerance_ms. See Configuration and
Telemetry.
Why monotonic?¶
Wall clock can jump backward (NTP fixes itself, leap shenanigans, someone moves
the system clock). CLOCK_MONOTONIC only moves forward, so a deadline built
on it does not accidentally gain or lose minutes because the OS “fixed” time.