Actions
Bug #21614
openthread_sched_wait_events race with timer_thread_wakeup
Description
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):
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
).
No data to display
Actions
Like0