Give a Ractor's main thread an ec serial from the new Ractor

Since ec serials became Ractor-local (4263f1d718), the root fiber of a
Ractor's main thread gets its serial from the creating Ractor, because it
is allocated there. The serial can then collide with one that the new
Ractor gives to its own threads. Mutex ownership is checked by ec serial,
so another thread could see a Mutex locked by the main thread as its own,
which led to "[BUG] invalid keeping_mutexes".

    Ractor.new {
      m = Mutex.new
      m.lock
      Thread.new {}.join
      Thread.new { m.unlock }.join # should raise ThreadError
    }.join
    #=> [BUG] invalid keeping_mutexes: Attempt to unlock a mutex which is not locked

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yusuke Endoh 2026-10-19 18:38:14 +09:00
parent 6fdd452087
commit bde63b65b7
Notes: git 2026-10-24 20:40:58 +00:00
2 changed files with 19 additions and 0 deletions

View File

@ -3371,3 +3371,21 @@ assert_equal 'ok', %q{
end.each(&:join)
:ok
}
# A Ractor's main thread must not share its ec serial with its threads
assert_equal 'false', %q{
rs = 4.times.map {
Ractor.new {
m = Thread::Mutex.new
m.lock
ok = 8.times.map { Thread.new { m.owned? } }.map(&:value)
begin
8.times.map { Thread.new { m.unlock rescue nil } }.each(&:join)
ensure
m.unlock
end
ok.any?
}
}
rs.map(&:value).any?
}

View File

@ -940,6 +940,7 @@ thread_create_core(VALUE thval, struct thread_create_params *params)
th->invoke_type = thread_invoke_type_ractor_proc;
th->ractor = params->g;
th->ec->ractor_id = rb_ractor_id(th->ractor);
th->ec->serial = th->ractor->next_ec_serial++;
th->ractor->threads.main = th;
th->invoke_arg.proc.proc = rb_proc_isolate_bang(params->proc, Qnil);
th->invoke_arg.proc.args = INT2FIX(RARRAY_LENINT(params->args));