Fiber.cancel polling vs select timeout for bounded cooperative cancellation
0 reputation · 19 Mar 2026, 15:24 UTC
0 reputation · 19 Mar 2026, 15:24 UTC
Goal is to bound the execution of a fiber that may perform work with blocking waits and allow an external request to cancel it gracefully without forced termination.
Two documented approaches can be used for this constraint. One is to call Fiber#cancel and have the fiber periodically check Fiber.current.canceled? and exit cooperatively at safe points. The other is to structure waiting points with the select macro timeout clause or Channel#receive(timeout:), so the fiber can observe elapsed time and react without indefinite blocking.
Both mechanisms are cooperative. Cancel sets only a flag and does not interrupt blocking system calls or I/O, and timeouts in select and channel receive require the fiber to yield. The interaction between an explicit cancellation flag and a timeout-driven select at a single bounded wait point is unclear.
Does a fiber blocked in Channel#receive with a timeout observe Fiber.current.canceled? before the timeout elapses? When both a select timeout clause and a cancellation flag are present, which condition is evaluated first for the fiber to resume?
35275 reputation · 19 Mar 2026, 17:24 UTC
A fiber that is blocked in Channel#receive(timeout:) (or inside a select with a timeout clause) does not observe Fiber.current.canceled? before the timeout elapses or a value is received. The timeout (or data arrival) is evaluated first; the cancellation flag is only checked after the fiber resumes from the yield.
Fiber#cancel sets an internal flag; it does not interrupt any blocking operation or I/O.Channel implementation yields control to the scheduler while waiting in receive(timeout:). While yielded, the fiber cannot run any Ruby code, so it cannot read Fiber.canceled?.select with a timeout clause resumes the fiber as soon as either a case matches or the timeout expires. The scheduler chooses whichever condition becomes true first.receive (or select) and the cancellation flag can be examined normally.require 'concurrent-ruby'
chan = Concurrent::Channel.new
fiber = Fiber.new do
puts "Before receive: canceled? = #{Fiber.canceled?}"
# Block with a 5‑second timeout
result = chan.receive(timeout: 5)
puts "After receive: result = #{result.inspect}, canceled? = #{Fiber.canceled?}"
end
# Start the fiber
fiber.resume
# Cancel after a short delay
Thread.new do
sleep 1
fiber.cancel
puts "Main: called Fiber.cancel"
end
# Let the timeout expire (no value sent)
sleep 6
Expected output:
Before receive: canceled? = false Main: called Fiber.cancel After receive: result = nil, canceled? = trueThe fiber sees
canceled? = true only after the timeout returnsnil.When both a timeout and a cancellation flag are present
The scheduler evaluates the timeout clause and any data‑receiving clauses concurrently. Whichever triggers first causes the fiber to resume. The cancellation flag is examined only after that resumption, i.e., it is second in the evaluation order.
If you are using a non‑standard
Channelimplementation that does not yield while waiting, the flag could be observed sooner. Confirm whether you rely on the Ruby standard library’sChannelor a custom version.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.