Skip to content

Unsafe

Seriously—read this twice

Unsafe uses Thread#raise to inject TIMEx::Expired at whatever moment Ruby next checks for async exceptions. That can leave mutexes half-locked, buffers half-written, and file handles dangling. It is the same bargain as stdlib Timeout—fast, familiar, and sharp enough to cut you.

Quick example

TIMEx.deadline(2.0, strategy: :unsafe) { legacy_block }

You get a timeout without rewriting the loop. You also accept the corruption lottery if the interrupted code was not written for this.

At a glance

Topic Plain English
CPU-heavy work Often yes—next interrupt check can be “soon-ish.”
Blocking IO Maybe—depends on whether the C extension cooperates with thread raises.
Mutexes / shared state No—assume the worst.
Runs everywhere Yes—no fork required.
How tight the timeout is Millisecond-ish, but where it lands is not your call.

The one respectable job for Unsafe

Almost never the first tool. The grown-up pattern is TwoPhase: be nice with Cooperative first, then Unsafe only after a grace window when soft cancellation cannot reach the work and you decide a wedged process is worse than a risky interrupt.

TIMEx::Composers::TwoPhase.new(
  soft: :cooperative, hard: :unsafe, grace: 0.5, hard_deadline: 1.0,
  idempotent: true
).call(deadline: 2.0) { work }

Untrusted code? Subprocess. Full stop.