Wakeup¶
You already have an IO.select loop juggling real sockets. Wakeup hands
you an extra pipe—backed by a CancellationToken—so the select set can also
react when your deadline fires, not only when bytes arrive.
Mental model: add a tiny doorbell next to the mailbox. Mail still matters, but now “time’s up” rings too.
Quick example¶
wake = TIMEx::Strategies::Wakeup.new(2.0)
begin
ready, = ::IO.select([sock, wake.read_io], nil, nil)
if ready.include?(wake.read_io)
# deadline fired—handle cancellation gracefully
end
ensure
wake.close
end
Each Wakeup is single-use: always close in an ensure block, and build
a fresh instance for the next operation. The pipe and watcher thread leak if
you forget.
At a glance¶
| Topic | Plain English |
|---|---|
| CPU-heavy Ruby | Does not interrupt tight loops—you still need checkpoints elsewhere. |
Blocking IO inside select |
Yes: select returns when the wakeup side is readable. |
| Mutexes / shared state | Gentle pattern: you choose what happens after select wakes. |
| Runs everywhere | Plain MRI. |
| How tight the timeout is | Millisecond-ish in practice. |
| Cost | One pipe plus a small watcher thread doing the bookkeeping. |
Manual “ring the bell”¶
Use this when you want to abort early—user clicked cancel, upstream told you to stop, etc.
Real-world: long-poll endpoint that returns 204 instead of hanging¶
A mobile client long-polls /inbox/wait for up to 25 s. You subscribe to a
Redis pub/sub channel and want select to wake on either a new message
or the deadline—never both clients sitting on dead sockets:
get "/inbox/wait" do
sub = redis.subscribe_socket("inbox:#{current_user.id}")
wake = TIMEx::Strategies::Wakeup.new(25.0)
begin
ready, = ::IO.select([sub, wake.read_io], nil, nil)
if ready.include?(wake.read_io)
halt 204
else
[200, { "Content-Type" => "application/json" }, [sub.read_message]]
end
ensure
wake.close
sub.close
end
end
No per-request thread killing, no Timeout.timeout wrapping a Redis read—the
kernel’s own select handles the wait and the doorbell rings on schedule.