Skip to content

Commit 290a3ec

Browse files
Reset runtime and preempted in rb_fiber_atfork, add test
The child process inherits the parent's root fiber state at fork time. rb_fiber_atfork already reset quantum to the default but left runtime and preempted untouched. A child forked while a fiber had accumulated runtime or had preempted=1 would start with stale scheduling state, potentially triggering a spurious immediate preemption on the first back-edge check. Reset all three fields consistently, matching what rb_threadptr_root_fiber_setup does for a freshly created root fiber. Add test_fork_resets_fiber_scheduling_state to verify that a child forked from a fiber with accumulated runtime starts with runtime near zero and quantum at the default value. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d0e2242 commit 290a3ec

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

cont.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2998,7 +2998,6 @@ rb_fiber_runtime(VALUE self)
29982998
return UINT2NUM(fiber->cont.saved_ec.runtime);
29992999
}
30003000

3001-
30023001
/*
30033002
* call-seq: fiber.quantum -> integer
30043003
*

test/ruby/test_fiber.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,39 @@ def test_fork_from_fiber
427427
assert_predicate(status, :success?)
428428
end
429429

430+
def test_fork_resets_fiber_scheduling_state
431+
omit 'fork not supported' unless Process.respond_to?(:fork)
432+
omit "Fiber#runtime counter requires interpreter dispatch" if
433+
defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
434+
435+
# Accumulate runtime and set preempted in the parent so that the child
436+
# inherits a dirty scheduling state, then verify rb_fiber_atfork resets it.
437+
r, w = IO.pipe
438+
439+
f = Fiber.new(blocking: false) do
440+
10_000.times { } # accumulate runtime in parent
441+
pid = fork do
442+
r.close
443+
fiber = Fiber.current
444+
w.write("#{fiber.runtime},#{fiber.quantum}")
445+
w.close
446+
exit!(0)
447+
end
448+
w.close
449+
child_output = r.read
450+
Process.waitpid(pid)
451+
child_output
452+
end
453+
result = f.transfer
454+
r.close
455+
456+
child_runtime, child_quantum = result.split(",").map(&:to_i)
457+
assert_operator child_runtime, :<, 100,
458+
"child runtime=#{child_runtime} was not reset by rb_fiber_atfork (expected < 100)"
459+
assert_equal 50_000, child_quantum,
460+
"child quantum was not reset to default (got #{child_quantum})"
461+
end
462+
430463
def test_exit_in_fiber
431464
bug5993 = '[ruby-dev:45218]'
432465
assert_nothing_raised(bug5993) do

0 commit comments

Comments
 (0)