Actions
Bug #22265
closed`Thread#value` can return before the target thread completes after a spurious Fiber scheduler unblock
Bug #22265:
`Thread#value` can return before the target thread completes after a spurious Fiber scheduler unblock
Description
Ruby 3.3 and 3.4 can allow an untimed Thread#join to return after a spurious Fiber scheduler wakeup, even though the target thread is still alive.
Because Thread#value relies on Thread#join, it can consequently return nil before the thread completes.
This was encountered in io-event, where a stale scheduler unblock could resume a fiber waiting for a worker thread.
Reproducer¶
class Scheduler
def initialize
@root = Fiber.current
@ready = []
@blocking = {}
@lock = Thread::Mutex.new
end
def fiber(&block)
fiber = Fiber.new(blocking: false, &block)
fiber.transfer
fiber
end
def block(blocker, timeout = nil)
fiber = Fiber.current
@blocking[fiber] = true
@root.transfer
ensure
@blocking.delete(fiber)
end
def unblock(blocker, fiber)
@lock.synchronize {@ready << fiber}
end
def kernel_sleep(duration = nil)
unblock(:sleep, Fiber.current)
@root.transfer
true
end
def io_wait(io, events, duration = nil)
raise NotImplementedError
end
def fiber_interrupt(fiber, exception)
fiber.raise(exception)
end
def run
while @blocking.any? || @ready.any?
ready = @lock.synchronize do
ready, @ready = @ready, []
ready
end
ready.each do |fiber|
fiber.transfer if fiber.alive?
end
Thread.pass if ready.empty?
end
end
end
release = Thread::Queue.new
target = Thread.new do
release.pop
:success
end
Thread.pass until target.status == "sleep"
result = :unset
runner = Thread.new do
scheduler = Scheduler.new
Fiber.set_scheduler(scheduler)
joining_fiber = Fiber.schedule do
result = target.value
end
Fiber.schedule do
scheduler.unblock(:spurious_wakeup, joining_fiber)
sleep(0)
release << true
end
scheduler.run
ensure
Fiber.set_scheduler(nil)
end
runner.join
target.join
puts "ruby #{RUBY_VERSION}: result=#{result.inspect}"
abort "Thread#value returned before completion" unless result == :success
Actual result on Ruby 3.4.4¶
Tested with:
Expected result¶
An untimed Thread#join or Thread#value should continue waiting after a spurious wakeup:
Existing fix¶
This is fixed in Ruby 4.0 in PR #13532. The reproducer succeeds with Ruby 4.0.5.
- Commit:
f0cf4dce65ba7bf3dc6787e10b88e08b411e2c92 - PR: https://github.com/ruby/ruby/pull/13532
Proposed backports¶
Backport PRs have been prepared for both affected maintenance branches:
- Ruby 3.3: https://github.com/ruby/ruby/pull/18502
- Ruby 3.4: https://github.com/ruby/ruby/pull/18503
No data to display
Actions