From: "luke-gru (Luke Gruber) via ruby-core" Date: 2025-09-23T21:08:41+00:00 Subject: [ruby-core:123318] [Ruby Bug#21614] thread_sched_wait_events race with timer_thread_wakeup Issue #21614 has been reported by luke-gru (Luke Gruber). ---------------------------------------- Bug #21614: thread_sched_wait_events race with timer_thread_wakeup https://bugs.ruby-lang.org/issues/21614 * Author: luke-gru (Luke Gruber) * Status: Open * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- `thread_sched_wait_events` checks th->sched.waiting_reason.flags under thread_sched_lock but not under timer_thread.waiting_lock. The timer thread sets it under the timer_thread.waiting_lock only. This race results in the assertion failure: `../ruby/thread_pthread.c:909: Assertion Failed: thread_sched_to_running_common:sched->running != th` Reproduction script (run it many times): ```ruby require "timeout" def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, stdout_filter: nil, stderr_filter: nil, ios: nil, signal: :TERM, rubybin: "ruby", precommand: nil, **opt) timeout = 60 in_c, in_p = IO.pipe out_p, out_c = IO.pipe if capture_stdout err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout opt[:in] = in_c opt[:out] = out_c if capture_stdout opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr if encoding out_p.set_encoding(encoding) if out_p err_p.set_encoding(encoding) if err_p end ios.each {|i, o = i|opt[i] = o} if ios c = "C" child_env = {} if Array === args and Hash === args.first child_env.update(args.shift) end args = [args] if args.kind_of?(String) # use the same parser as current ruby if args.none? { |arg| arg.start_with?("--parser=") } args = ["--parser=prism"] + args end pid = spawn(child_env, *precommand, rubybin, *args, opt) in_c.close out_c&.close out_c = nil err_c&.close err_c = nil if block_given? return yield in_p, out_p, err_p, pid else th_stdout = Thread.new { out_p.read } if capture_stdout th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout in_p.write stdin_data.to_str unless stdin_data.empty? in_p.close if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) timeout_error = nil else $stderr.puts "Error: timed out" status = terminate(pid, signal, opt[:pgroup]) terminated = Time.now end stdout = th_stdout.value if capture_stdout stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout out_p.close if capture_stdout err_p.close if capture_stderr && capture_stderr != :merge_to_stdout status ||= Process.wait2(pid)[1] stdout = stdout_filter.call(stdout) if stdout_filter stderr = stderr_filter.call(stderr) if stderr_filter if timeout_error bt = caller_locations msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" raise timeout_error, msg, bt.map(&:to_s) end return stdout, stderr, status end ensure [th_stdout, th_stderr].each do |th| th.kill if th end [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| io&.close end [th_stdout, th_stderr].each do |th| th.join if th end end def terminate(pid, signal = :TERM, pgroup = nil) case pgroup when 0, true pgroup = -pid when nil, false pgroup = pid end begin Process.kill signal, pgroup rescue Errno::EINVAL rescue Errno::ESRCH end $? end rs = [] 100.times do rs << Ractor.new do script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume' invoke_ruby([{}, '-e', script], "", true, true) end end rs.each(&:join) ``` This script is just taken directly from a failed test in the test suite that was run under multiple ractors at once (`test_vm_stack_size`). -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/