35 Commits
Author SHA1 Message Date
Luke Gruber a00df45480 Add assertions that interrupts aren't checked when VM lock taken. (#18832) 2026-09-15 16:23:10 +12:00
Koichi SasadaandClaude Opus 5 4d764e62a3 Wait for terminating Ractors through the scheduler
rb_ractor_terminate_all() interrupts the other Ractors and waits on a
condvar of its own for them to finish, dropping the VM lock for the wait
and taking it back after.  That was rb_vm_cond_timedwait(), whose only
caller it was; rb_vm_cond_wait() had none at all.

Move the wait to rb_ractor_sched_wait_terminate() in thread_sched.c and
give the native thread back for its duration, as a blocking region does.
Waiting natively holds whatever native thread the caller runs on, and an
M:N caller runs on one from the shared pool -- so it would wait, holding
the pool, for Ractors that need the pool to finish.  It does not happen
today, the caller being the main Ractor's main thread with a native
thread of its own, but a scheduler-blind wait in the scheduler's way is
worth keeping out of vm_sync.c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:25:10 +09:00
Koichi SasadaandClaude Opus 5 90e729c9bc Share one thread/ractor scheduler between POSIX and Windows
Windows carried a second, much weaker scheduler: a single Win32 mutex as
the GVL, vm->clock for preemption, its own VM barrier over
vm->ractor.sync.barrier_*, its own Ractor wait/wakeup on a per-Ractor
condvar, no ready queue and no time slice. Keeping two made the rest of
the interpreter branch on which one was compiled in, and left Ractors on
Windows without real scheduling.

Move the scheduler out of thread_pthread.c into thread_sched.c and let
both platforms use it. thread.c now includes thread_sched.c, which
declares the scheduler <-> platform contract, includes THREAD_IMPL_SRC,
and then builds the scheduler on the primitives that file provides.
thread_pthread.c keeps only those primitives: synchronization, thread
local storage, native thread creation and stacks, the communication pipe,
the timer thread backend, the fork lock and the event hooks.

The data structures the scheduler works on move to a new thread_sched.h,
included from both platform headers, so struct rb_thread_sched,
struct rb_native_thread and struct rb_thread_sched_item are now shared.

thread_pthread_mn.c becomes thread_sched_mn.c: it is the M:N scheduler,
not a pthread detail. Most of it -- the coroutine threads, the native
thread stack pool, the timer wheel and the fd -> waiters map -- is
platform independent; only the readiness backend is not, and that section
is now marked as the seam where an epoll/kqueue split (or an IOCP backend,
which is what Windows would need for M:N) would go.

What Windows had to add is small: native_thread_interrupt(),
native_thread_default_max_cpu(), timer_thread_polling(),
timer_thread_wakeup_force(), RB_NATIVE_COND_MONOTONIC_P() and
RB_NATIVE_MUTEX_TRYLOCK_DETECTS_SELF() -- the last because a
CRITICAL_SECTION is recursive, so trylock cannot back a "somebody holds
this" assertion. It sets USE_MN_THREADS to 0 and supplies the stubs
thread_sched_mn.c provides elsewhere, so every thread there is dedicated,
as it already was.

With one scheduler the platform branches go away: all 16 uses of
RUBY_THREAD_PTHREAD_H / RUBY_THREAD_WIN32_H are removed from vm_core.h,
ractor.c, ractor_core.h, ractor_sync.c, vm_sync.c, vm.c and thread.c.
ractor_check_blocking() went with them -- it only ever fed the win32
scheduler's vm->ractor.blocking_cnt -- and so did USE_VM_CLOCK, now that
the common time slice drives preemption on Windows too.

Verified on both platforms. Linux: 11 rounds of make test-all (36236
tests) and btest (2066 tests) with no failures, plus 474 runs of a
scheduler and Ractor stress soak. Windows: no test fails that does not
also fail on the same tree without this change, btest passes, and two
40-minute soaks of 438 and 460 runs are clean. Ractors now run in
parallel there -- 3.6x on four Ractors, where before they could not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 04:04:48 +09:00
Koichi SasadaandClaude Fable 5 8d46d62fb0 thread: keep context switches off the scheduler lock
thread_sched_setup_running_threads runs on every context switch (park,
resume, and both ends of every blocking region) and took the global
ractor.sched.lock to maintain the running_threads and timeslice lists
and running_cnt.  With many ractors switching concurrently this lock is
the scaling ceiling.

Replace all three with ntlist, a per-location registration of who runs
where, and no shared counter:

- a thread on a shared nt registers in that nt's running_th, under the
  nt's own lock -- uncontended, no cross-core traffic
- a dedicated nt goes on ntlist.running_dnts while its thread runs (the
  switch rate of dedicated threads is what it always was; only the lock
  shrank), so the fast path writes no shared cache line at all

The consumers walk the registrations: the barrier's interrupt walk also
counts them into a snapshot (barrier_running_cnt) and stamps each nt
with the barrier serial; a stamped nt deregistering during that barrier
decrements the snapshot under sched.lock, so the completion check stays
O(1) however many runners there are.

The timeslice list becomes timeslice.scheds, the scheds whose readyq
holds waiters.  The enq that fills an empty readyq (and the add path
taking a turn with waiters already queued) links the sched; the timer
thread interrupts each listed sched's running thread and prunes entries
whose readyq drained.  A steadily contended sched stays linked, so the
switch path never touches the list, and idle scheds are not walked at
all.  The timer takes each sched's lock by trylock, because the
switchers nest sched.lock -> timeslice.lock; pruning is lazy, so
ractor_free delists the dying ractor's sched.

Barrier pairing needs no fences: the barrier sets barrier_is_waiting
and then walks the registrations under their locks; a switch moves its
registration under the same lock and then reads the flag.  A walk that
missed a registration ran before that registration's critical section,
so its flag store is visible to that switcher.  Nothing may start
running while the flag stands -- an interrupt flag alone would let a
resuming thread run, and allocate, inside the stop-the-world section --
so the add path takes its registration back and waits for
rb_ractor_sched_barrier_end's broadcast.  While a scan holds a
running_th lock the registered thread cannot finish parking, so it
cannot die under the scan.  An snt turned dedicated under
rb_thread_lock_native_thread has no creation-time running_thread, so
the running_dnts registration writes nt->running_thread itself.

R ractor pairs ping-ponging via ports (msgs/sec) and R ractors doing
pipe write/read round-trips (rt/sec), 16-HT machine (Ryzen 9 5900HX),
mean of 2 alternating same-tree runs:

                     before    after
    port pairs  8      503k     815k   +62%
    port pairs 32      453k     799k   +76%
    pipe self   4R     837k     940k   +12%
    pipe self  16R     820k     898k   +10%

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 02:52:02 +09:00
Matt Valentine-House c3e04e3bb6 MMTK: Allow debug cc checks on EC-less GC threads
MMTk marks call caches on GC worker threads. Those threads
have no execution context, so two debug helpers failed:

* vm_cc_check_cme took the VM lock, and the lock records
  the current ractor. Skip the lock when the thread has no
  execution context. We can assume this is safe because the only threads
  with no EC are MMTk GC worker threads, and they're only working when
  the mutator is stopped.

* RUBY_ASSERT_vm_locking and its variants assume the lock owner is the
  current ractor, but this is meaningless in a MMTk GC worker thread, so
  let's just pass the assertions.
2026-08-19 17:14:06 +01:00
Kunshan Wang 89e7b08a9f eBPF-based timeline visualization tool (#17190) 2026-07-07 19:07:22 +09:00
Luke Gruber 56b67f1684 ObjectSpace.{dump,dump_all,dump_shapes} needs vm barrier. (#15569)
This allows these methods to be called from ractors.

Add new exported function `rb_vm_lock_with_barrier()` that requires
users to include "vm_sync.h"
2025-12-17 12:17:30 -05:00
John HawthornandLuke Gruber 0a6cd03b3d Add ASSERT_vm_locking_with_barrier
Previously we just had a comment stating that the code required a
barrier. Turns out it's not too difficult to properly assert that.

Co-authored-by: Luke Gruber <luke.gru@gmail.com>
2025-10-10 11:09:34 -07:00
John Hawthorn 3ad2019259 Extract vm_locked_by_ractor_p
This introduces a new method to encapsulate checking whether the current
Ractor owns the vm->ractor.sync lock. This allows us to disable TSan on
it since that operation should be safe, and still get validation of
other uses.
2025-07-24 16:35:20 -07:00
Nobuyoshi Nakada ec20f7feb6 Suppress warnings
- `ractor_sync_terminate_atfork` is unused unless fork is working
- `cr` in `vm_lock_leave` is only for debugging
2025-06-22 01:08:38 +09:00
Koichi Sasada a62166e28e support nested VM barrier synchronization
on `RGENGC_CHECK_MODE > 1`, there are the following steps

1. gc_enter
2. vm_barrier
3. verify_internal_consistency
4. vm_barrier

and it causes nested vm_barrier synchronization.

This patch allows such cases.
2025-06-06 06:49:08 +09:00
Koichi Sasada ef2bb61018 Ractor::Port
* Added `Ractor::Port`
  * `Ractor::Port#receive` (support multi-threads)
  * `Rcator::Port#close`
  * `Ractor::Port#closed?`
* Added some methods
  * `Ractor#join`
  * `Ractor#value`
  * `Ractor#monitor`
  * `Ractor#unmonitor`
* Removed some methods
  * `Ractor#take`
  * `Ractor.yield`
* Change the spec
  * `Racotr.select`

You can wait for multiple sequences of messages with `Ractor::Port`.

```ruby
ports = 3.times.map{ Ractor::Port.new }
ports.map.with_index do |port, ri|
  Ractor.new port,ri do |port, ri|
    3.times{|i| port << "r#{ri}-#{i}"}
  end
end

p ports.each{|port| pp 3.times.map{port.receive}}

```

In this example, we use 3 ports, and 3 Ractors send messages to them respectively.
We can receive a series of messages from each port.

You can use `Ractor#value` to get the last value of a Ractor's block:

```ruby
result = Ractor.new do
  heavy_task()
end.value
```

You can wait for the termination of a Ractor with `Ractor#join` like this:

```ruby
Ractor.new do
  some_task()
end.join
```

`#value` and `#join` are similar to `Thread#value` and `Thread#join`.

To implement `#join`, `Ractor#monitor` (and `Ractor#unmonitor`) is introduced.

This commit changes `Ractor.select()` method.
It now only accepts ports or Ractors, and returns when a port receives a message or a Ractor terminates.

We removes `Ractor.yield` and `Ractor#take` because:
* `Ractor::Port` supports most of similar use cases in a simpler manner.
* Removing them significantly simplifies the code.

We also change the internal thread scheduler code (thread_pthread.c):
* During barrier synchronization, we keep the `ractor_sched` lock to avoid deadlocks.
  This lock is released by `rb_ractor_sched_barrier_end()`
  which is called at the end of operations that require the barrier.
* fix potential deadlock issues by checking interrupts just before setting UBF.

https://bugs.ruby-lang.org/issues/21262
2025-05-31 04:01:33 +09:00
Koichi Sasada 685a4e5be7 VM barrier needs to store GC root
On the VM barrier waiting, it needs to store machine context
as a GC root.

Also it needs to wait for barrier synchronization correctly
by `while` (for continuous barrier sync by another ractor).

This is why GC marking misses are occerred on ARM machines.
like: https://rubyci.s3.amazonaws.com/fedora40-arm/ruby-master/log/20240702T063002Z.fail.html.gz
2024-07-05 21:20:54 +09:00
Koichi Sasada be1bbd5b7d M:N thread scheduler for Ractors
This patch introduce M:N thread scheduler for Ractor system.

In general, M:N thread scheduler employs N native threads (OS threads)
to manage M user-level threads (Ruby threads in this case).
On the Ruby interpreter, 1 native thread is provided for 1 Ractor
and all Ruby threads are managed by the native thread.

From Ruby 1.9, the interpreter uses 1:1 thread scheduler which means
1 Ruby thread has 1 native thread. M:N scheduler change this strategy.

Because of compatibility issue (and stableness issue of the implementation)
main Ractor doesn't use M:N scheduler on default. On the other words,
threads on the main Ractor will be managed with 1:1 thread scheduler.

There are additional settings by environment variables:

`RUBY_MN_THREADS=1` enables M:N thread scheduler on the main ractor.
Note that non-main ractors use the M:N scheduler without this
configuration. With this configuration, single ractor applications
run threads on M:1 thread scheduler (green threads, user-level threads).

`RUBY_MAX_CPU=n` specifies maximum number of native threads for
M:N scheduler (default: 8).

This patch will be reverted soon if non-easy issues are found.

[Bug #19842]
2023-10-12 14:47:01 +09:00
Matt Valentine-House 4ae9c34a4e Move RB_VM_SAVE_MACHINE_CONTEXT to internal/thread.h 2023-03-15 21:26:26 +00:00
Matt Valentine-House 60b8c7d9fd Rename RB_GC_SAVE_MACHINE_CONTEXT -> RB_VM_SAVE_MACHINE_CONTEXT 2023-03-15 21:26:26 +00:00
Takashi Kokubun 233ddfac54 Stop exporting symbols for MJIT 2023-03-06 21:59:23 -08:00
Matt Valentine-House 72aba64fff Merge gc.h and internal/gc.h
[Feature #19425]
2023-02-09 10:32:29 -05:00
Nobuyoshi Nakada c38ad2aeb5 Fix format specifier for rb_ractor_id() 2022-07-28 23:46:06 +09:00
Nobuyoshi NakadaandSamuel Williams 42a0bed351 Prefix ccan headers (#4568)
* Prefixed ccan headers

* Remove unprefixed names in ccan/build_assert

* Remove unprefixed names in ccan/check_type

* Remove unprefixed names in ccan/container_of

* Remove unprefixed names in ccan/list

Co-authored-by: Samuel Williams <samuel.williams@oriontransfer.co.nz>
2022-03-30 20:36:31 +13:00
Kazuhiro NISHIYAMA 9b18f1bffe Supress warning: data argument not used by format string [-Wformat-extra-args] 2021-10-20 07:48:30 +09:00
Nobuyoshi Nakada 292230cbf9 Fixed leaked global symbols 2020-12-26 09:39:53 +09:00
Koichi Sasada 02d9524cda separate rb_ractor_pub from rb_ractor_t
separate some fields from rb_ractor_t to rb_ractor_pub and put it
at the beggining of rb_ractor_t and declare it in vm_core.h so
vm_core.h can access rb_ractor_pub fields.

Now rb_ec_ractor_hooks() is a complete inline function and no
MJIT related issue.
2020-12-22 00:03:00 +09:00
Koichi Sasada 0b678cc9e5 add vm_sync debug counters
* vm_sync_lock
* vm_sync_lock_enter
* vm_sync_lock_enter_nb
* vm_sync_lock_enter_cr
* vm_sync_barrier
2020-12-16 10:38:12 +09:00
Koichi Sasada ee194af2aa re-layout rb_ractor_t
separate synchronization data and ractor local data.
2020-12-09 01:40:38 +09:00
Koichi Sasada 0ebf6bd0a2 RB_VM_LOCK_ENTER_NO_BARRIER
Write barrier requires VM lock because it accesses VM global bitmap
but RB_VM_LOCK_ENTER() can invoke GC because another ractor can wait
to invoke GC and RB_VM_LOCK_ENTER() is barrier point. This means that
before protecting by a write barrier, GC can invoke.
To prevent such situation, RB_VM_LOCK_ENTER_NO_BARRIER() is introduced.
This lock primitive does not become GC barrier points.
2020-12-07 11:27:25 +09:00
Koichi Sasada 1d0bf3d8dd log for the beggining of vm_lock_enter
Before this patch, there is no information to start locking.
2020-12-07 08:28:36 +09:00
Koichi Sasada 554a7180a0 RB_VM_LOCK_ENTER_CR_LEV
This is variant of RB_VM_LOCK_ENTER_LEV, but accept current racotr's
pointer.
2020-12-07 08:28:36 +09:00
Koichi Sasada 5e3259ea74 fix public interface
To make some kind of Ractor related extensions, some functions
should be exposed.

* include/ruby/thread_native.h
  * rb_native_mutex_*
  * rb_native_cond_*
* include/ruby/ractor.h
  * RB_OBJ_SHAREABLE_P(obj)
  * rb_ractor_shareable_p(obj)
  * rb_ractor_std*()
  * rb_cRactor

and rm ractor_pub.h
and rename srcdir/ractor.h to srcdir/ractor_core.h
    (to avoid conflict with include/ruby/ractor.h)
2020-11-18 03:52:41 +09:00
Koichi Sasada f6661f5085 sync RClass::ext::iv_index_tbl
iv_index_tbl manages instance variable indexes (ID -> index).
This data structure should be synchronized with other ractors
so introduce some VM locks.

This patch also introduced atomic ivar cache used by
set/getinlinecache instructions. To make updating ivar cache (IVC),
we changed iv_index_tbl data structure to manage (ID -> entry)
and an entry points serial and index. IVC points to this entry so
that cache update becomes atomically.
2020-10-17 08:18:04 +09:00
Koichi Sasada ae693fff74 fix releasing timing.
(1) recorded_lock_rec > current_lock_rec should not be occurred
    on rb_ec_vm_lock_rec_release().
(2) should be release VM lock at EXEC_TAG(), not POP_TAG().
(3) some refactoring.
2020-10-14 16:36:55 +09:00
Koichi Sasada c3ba3fa8d0 support exception when lock_rec > 0
If a ractor getting a VM lock (monitor) raises an exception,
unlock can be skipped. To release VM lock correctly on exception
(or other jumps with JUMP_TAG), EC_POP_TAG() releases VM lock.
2020-10-14 14:02:06 +09:00
Koichi Sasada 74ddac1c82 relax dependency
vm_sync.h does not need to include vm_core.h and ractor_pub.h.
2020-09-15 00:04:59 +09:00
Nobuyoshi Nakada 169b1d1aca Initialize loop variables of list_for_each for MS VC 2020-09-04 11:46:50 +09:00
Koichi Sasada 79df14c04b Introduce Ractor mechanism for parallel execution
This commit introduces Ractor mechanism to run Ruby program in
parallel. See doc/ractor.md for more details about Ractor.
See ticket [Feature #17100] to see the implementation details
and discussions.

[Feature #17100]

This commit does not complete the implementation. You can find
many bugs on using Ractor. Also the specification will be changed
so that this feature is experimental. You will see a warning when
you make the first Ractor with `Ractor.new`.

I hope this feature can help programmers from thread-safety issues.
2020-09-03 21:11:06 +09:00