summaryrefslogtreecommitdiff
path: root/test/ruby/test_ractor.rb
AgeCommit message (Collapse)Author
2025-12-12Prevent ifunc procs from being made shareableÉtienne Barrié
[Bug #21775]
2025-12-05Method and UnboundMethod can be sharableKoichi Sasada
with `RUBY_TYPED_FROZEN_SHAREABLE_NO_REC`, if the receiver object is shareable on Method objects.
2025-11-11test_ractor.rb: Delete unnecessary GC.stress fiddlingAlan Wu
assert_ractor() runs in a subprocess.
2025-10-27Fix segfault when moving nested objects between ractors during GCJoshua Young
Fixes a segmentation fault when moving nested objects between ractors with GC stress enabled and YJIT. The issue is a timing problem: `move_enter` allocates new object shells but leaves their contents uninitialized until `move_leave` copies the actual data. If GC runs between these steps (which GC stress makes likely), it tries to follow what appear to be object pointers but are actually uninitialized memory, encountering null or invalid addresses. The fix zero-initializes the object contents immediately after allocation in `move_enter`, ensuring the GC finds safe null pointers instead of garbage data. The crash reproduced most consistently with nested hashes and YJIT, likely because nested structures create multiple uninitialized objects simultaneously while YJIT's memory usage increases the probability of GC triggering during moves.
2025-09-24Ractor.shareable_procKoichi Sasada
call-seq: Ractor.sharable_proc(self: nil){} -> sharable proc It returns shareable Proc object. The Proc object is shareable and the self in a block will be replaced with the value passed via `self:` keyword. In a shareable Proc, the outer variables should * (1) refer shareable objects * (2) be not be overwritten ```ruby a = 42 Ractor.shareable_proc{ p a } #=> OK b = 43 Ractor.shareable_proc{ p b; b = 44 } #=> Ractor::IsolationError because 'b' is reassigned in the block. c = 44 Ractor.shareable_proc{ p c } #=> Ractor::IsolationError because 'c' will be reassigned outside of the block. c = 45 d = 45 d = 46 if cond Ractor.shareable_proc{ p d } #=> Ractor::IsolationError because 'd' was reassigned outside of the block. ``` The last `d`'s case can be relaxed in a future version. The above check will be done in a static analysis at compile time, so the reflection feature such as `Binding#local_varaible_set` can not be detected. ```ruby e = 42 shpr = Ractor.shareable_proc{ p e } #=> OK binding.local_variable_set(:e, 43) shpr.call #=> 42 (returns captured timing value) ``` Ractor.sharaeble_lambda is also introduced. [Feature #21550] [Feature #21557]
2025-08-26Skip a Ractor test unstable on Windows for MinGWTakashi Kokubun
MinGW is also Windows, so it doesn't work for MinGW either. https://github.com/ruby/ruby/actions/runs/17250269899/job/48950567246
2025-08-21Adjust snt < max_cpu calculationJohn Hawthorn
[Bug #20146] Previously we dealt with the main Ractor not being enabled for M:N by incrementing snt_cnt++. This worked for comparing against ractor count, but meant that we always had one less SNT than was specified by RUBY_MAX_CPU. This was notably a problem for RUBY_MAX_CPU=1, which would cause Ractors to hang. This commit instead of adjusting snt, adjusts a "schedulable_ractor_cnt". This way snt_cnt will actually reach RUBY_MAX_CPU.
2025-08-15Skip an unstable Ractor test for windows (#14247)Takashi Kokubun
https://github.com/ruby/ruby/actions/runs/16995599804/job/48185434078?pr=14242
2025-08-14Increase timeout for a flaky test (#14233)Takashi Kokubun
https://github.com/ruby/ruby/actions/runs/16977882022/job/48131284556
2025-08-14Skip an unstable Ractor test for macOS (#14231)Takashi Kokubun
https://github.com/ruby/ruby/actions/runs/16977094733/job/48128667252?pr=14229
2025-08-08Fix lock ordering issue for rb_ractor_sched_wait() and rb_ractor_sched_wakeup()Luke Gruber
In rb_ractor_sched_wait() (ex: Ractor.receive), we acquire RACTOR_LOCK(cr) and then thread_sched_lock(cur_th). However, on wakeup if we're a dnt, in thread_sched_wait_running_turn() we acquire thread_sched_lock(cur_th) after condvar wakeup and then RACTOR_LOCK(cr). This lock inversion can cause a deadlock with rb_ractor_wakeup_all() (ex: port.send(obj)), where we acquire RACTOR_LOCK(other_r) and then thread_sched_lock(other_th). So, the error happens: nt 1: Ractor.receive rb_ractor_sched_wait() after condvar wakeup in thread_sched_wait_running_turn(): - thread_sched_lock(cur_th) (condvar) # acquires lock - rb_ractor_lock_self(cr) # deadlock here: tries to acquire, HANGS nt 2: port.send ractor_wakeup_all() - RACTOR_LOCK(port_r) # acquires lock - thread_sched_lock # tries to acquire, HANGS To fix it, we now unlock the thread_sched_lock before acquiring the ractor_lock in rb_ractor_sched_wait(). Script that reproduces issue: ```ruby require "async" class RactorWrapper def initialize @ractor = Ractor.new do Ractor.recv # Ractor doesn't start until explicitly told to # Do some calculations fib = ->(x) { x < 2 ? 1 : fib.call(x - 1) + fib.call(x - 2) } fib.call(20) end end def take_async @ractor.send(nil) Thread.new { @ractor.value }.value end end Async do |task| 10_000.times do |i| task.async do RactorWrapper.new.take_async puts i end end end exit 0 ``` Fixes [Bug #21398] Co-authored-by: John Hawthorn <john.hawthorn@shopify.com>
2025-08-06Struct: keep direct reference to IMEMO/fields when space allowsJean Boussier
It's not rare for structs to have additional ivars, hence are one of the most common, if not the most common type in the `gen_fields_tbl`. This can cause Ractor contention, but even in single ractor mode means having to do a hash lookup to access the ivars, and increase GC work. Instead, unless the struct is perfectly right sized, we can store a reference to the associated IMEMO/fields object right after the last struct member. ``` compare-ruby: ruby 3.5.0dev (2025-08-06T12:50:36Z struct-ivar-fields-2 9a30d141a1) +PRISM [arm64-darwin24] built-ruby: ruby 3.5.0dev (2025-08-06T12:57:59Z struct-ivar-fields-2 2ff3ec237f) +PRISM [arm64-darwin24] warming up..... | |compare-ruby|built-ruby| |:---------------------|-----------:|---------:| |member_reader | 590.317k| 579.246k| | | 1.02x| -| |member_writer | 543.963k| 527.104k| | | 1.03x| -| |member_reader_method | 213.540k| 213.004k| | | 1.00x| -| |member_writer_method | 192.657k| 191.491k| | | 1.01x| -| |ivar_reader | 403.993k| 569.915k| | | -| 1.41x| ``` Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>
2025-06-24Disallow forking from non-main ractorJean Boussier
[Bug #17516] `fork(2)` only leave the calling thread alive in the child. Because of this forking from the non-main ractor can easily leave the VM in a corrupted state. It may be possible in the future to carefully allow forking from non-main Ractor, but shot term it's preferable to add this restriction.
2025-06-13test/ruby/test_ractor.rb: avoid outputting anythingJean Boussier
Notes: Merged: https://github.com/ruby/ruby/pull/13611
2025-06-12Ensure crr->feature is an fstringJohn Hawthorn
Notes: Merged: https://github.com/ruby/ruby/pull/13531
2025-06-12Make setting and accessing class ivars lock-freeJean Boussier
Now that class fields have been deletated to a T_IMEMO/class_fields when we're in multi-ractor mode, we can read and write class instance variable in an atomic way using Read-Copy-Update (RCU). Note when in multi-ractor mode, we always use RCU. In theory we don't need to, instead if we ensured the field is written before the shape is updated it would be safe. Benchmark: ```ruby Warning[:experimental] = false class Foo @foo = 1 @bar = 2 @baz = 3 @egg = 4 @spam = 5 class << self attr_reader :foo, :bar, :baz, :egg, :spam end end ractors = 8.times.map do Ractor.new do 1_000_000.times do Foo.bar + Foo.baz * Foo.egg - Foo.spam end end end if Ractor.method_defined?(:value) ractors.each(&:value) else ractors.each(&:take) end ``` This branch vs Ruby 3.4: ```bash $ hyperfine -w 1 'ruby --disable-all ../test.rb' './miniruby ../test.rb' Benchmark 1: ruby --disable-all ../test.rb Time (mean ± σ): 3.162 s ± 0.071 s [User: 2.783 s, System: 10.809 s] Range (min … max): 3.093 s … 3.337 s 10 runs Benchmark 2: ./miniruby ../test.rb Time (mean ± σ): 208.7 ms ± 4.6 ms [User: 889.7 ms, System: 6.9 ms] Range (min … max): 202.8 ms … 222.0 ms 14 runs Summary ./miniruby ../test.rb ran 15.15 ± 0.47 times faster than ruby --disable-all ../test.rb ``` Notes: Merged: https://github.com/ruby/ruby/pull/13594
2025-05-31`Ractor::Port`Koichi Sasada
* 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 Notes: Merged: https://github.com/ruby/ruby/pull/13445
2025-05-23Fix 'require' from a ractor when the required file raises an errorLuke Gruber
If you catch an error that was raised from a file you required in a ractor, that error did not have its belonging reset from the main ractor to the current ractor, so you hit assertion errors in debug mode. Notes: Merged: https://github.com/ruby/ruby/pull/13428
2025-05-21Use rb_inspect for Ractor errorJohn Hawthorn
Previously the object was used directly, which calls `to_s` if defined. We should use rb_inspect to get a value suitable for display to the programmer. Notes: Merged: https://github.com/ruby/ruby/pull/13389
2025-03-31ractor: don't inherit the default thread groupJean Boussier
[Bug #17506] `Thread.current.group` isn't shareable so it shouldn't be inherited by the main thread of a new Ractor. This cause an extra allocation when spawning a ractor, which could be elided with a bit of extra work, but not sure if it's worth the effort. Notes: Merged: https://github.com/ruby/ruby/pull/13013
2025-03-26Add additional Ractor.make_shareable testsJohn Hawthorn
Notes: Merged: https://github.com/ruby/ruby/pull/12977