summaryrefslogtreecommitdiff
path: root/bootstraptest
AgeCommit message (Collapse)Author
2025-09-01remove `Ractor#take`Koichi Sasada
[Feature #21262]
2025-08-26Follow up cleanup for `opt_(aref|aset)_with` removalStan Lo
Just removing some unneeded tests, outdated comments...etc.
2025-08-25Fix deadlock when malloc in Ractor lockPeter Zhu
If we malloc when the current Ractor is locked, we can deadlock because GC requires VM lock and Ractor barrier. If another Ractor is waiting on this Ractor lock, then it will deadlock because the other Ractor will never join the barrier. For example, this script deadlocks: r = Ractor.new do loop do Ractor::Port.new end end 100000.times do |i| r.send(nil) puts i end On debug builds, it fails with this assertion error: vm_sync.c:75: Assertion Failed: vm_lock_enter:cr->sync.locked_by != rb_ractor_self(cr) On non-debug builds, we can see that it deadlocks in the debugger: Main Ractor: frame #3: 0x000000010021fdc4 miniruby`rb_native_mutex_lock(lock=<unavailable>) at thread_pthread.c:115:14 frame #4: 0x0000000100193eb8 miniruby`ractor_send0 [inlined] ractor_lock(r=<unavailable>, file=<unavailable>, line=1180) at ractor.c:73:5 frame #5: 0x0000000100193eb0 miniruby`ractor_send0 [inlined] ractor_send_basket(ec=<unavailable>, rp=0x0000000131092840, b=0x000000011c63de80, raise_on_error=true) at ractor_sync.c:1180:5 frame #6: 0x0000000100193eac miniruby`ractor_send0(ec=<unavailable>, rp=0x0000000131092840, obj=4, move=<unavailable>, raise_on_error=true) at ractor_sync.c:1211:5 Second Ractor: frame #2: 0x00000001002208d0 miniruby`rb_ractor_sched_barrier_start [inlined] rb_native_cond_wait(cond=<unavailable>, mutex=<unavailable>) at thread_pthread.c:221:13 frame #3: 0x00000001002208cc miniruby`rb_ractor_sched_barrier_start(vm=0x000000013180d600, cr=0x0000000131093460) at thread_pthread.c:1438:13 frame #4: 0x000000010028a328 miniruby`rb_vm_barrier at vm_sync.c:262:13 [artificial] frame #5: 0x00000001000dfa6c miniruby`gc_start [inlined] rb_gc_vm_barrier at gc.c:179:5 frame #6: 0x00000001000dfa68 miniruby`gc_start [inlined] gc_enter(objspace=0x000000013180fc00, event=gc_enter_event_start, lock_lev=<unavailable>) at default.c:6636:9 frame #7: 0x00000001000dfa48 miniruby`gc_start(objspace=0x000000013180fc00, reason=<unavailable>) at default.c:6361:5 frame #8: 0x00000001000e3fd8 miniruby`objspace_malloc_increase_body [inlined] garbage_collect(objspace=0x000000013180fc00, reason=512) at default.c:6341:15 frame #9: 0x00000001000e3fa4 miniruby`objspace_malloc_increase_body [inlined] garbage_collect_with_gvl(objspace=0x000000013180fc00, reason=512) at default.c:6741:16 frame #10: 0x00000001000e3f88 miniruby`objspace_malloc_increase_body(objspace=0x000000013180fc00, mem=<unavailable>, new_size=<unavailable>, old_size=<unavailable>, type=<unavailable>) at default.c:8007:13 frame #11: 0x00000001000e3c44 miniruby`rb_gc_impl_malloc [inlined] objspace_malloc_fixup(objspace=0x000000013180fc00, mem=0x000000011c700000, size=12582912) at default.c:8085:5 frame #12: 0x00000001000e3c30 miniruby`rb_gc_impl_malloc(objspace_ptr=0x000000013180fc00, size=12582912) at default.c:8182:12 frame #13: 0x00000001000d4584 miniruby`ruby_xmalloc [inlined] ruby_xmalloc_body(size=<unavailable>) at gc.c:5128:12 frame #14: 0x00000001000d4568 miniruby`ruby_xmalloc(size=<unavailable>) at gc.c:5118:34 frame #15: 0x00000001001eb184 miniruby`rb_st_init_existing_table_with_size(tab=0x000000011c2b4b40, type=<unavailable>, size=<unavailable>) at st.c:559:39 frame #16: 0x00000001001ebc74 miniruby`rebuild_table_if_necessary [inlined] rb_st_init_table_with_size(type=0x00000001004f4a78, size=524287) at st.c:585:5 frame #17: 0x00000001001ebc5c miniruby`rebuild_table_if_necessary [inlined] rebuild_table(tab=0x000000013108e2f0) at st.c:753:19 frame #18: 0x00000001001ebbfc miniruby`rebuild_table_if_necessary(tab=0x000000013108e2f0) at st.c:1125:9 frame #19: 0x00000001001eba08 miniruby`rb_st_insert(tab=0x000000013108e2f0, key=262144, value=4767566624) at st.c:1143:5 frame #20: 0x0000000100194b84 miniruby`ractor_port_initialzie [inlined] ractor_add_port(r=0x0000000131093460, id=262144) at ractor_sync.c:399:9 frame #21: 0x0000000100194b58 miniruby`ractor_port_initialzie [inlined] ractor_port_init(rpv=4750065560, r=0x0000000131093460) at ractor_sync.c:87:5 frame #22: 0x0000000100194b34 miniruby`ractor_port_initialzie(self=4750065560) at ractor_sync.c:103:12
2025-08-20Fix race condition in method invalidation for RactorsPeter Zhu
We lock the VM to invalidate method entries. However, we do not lock the VM to call methods, so it's possible that during a method call the method entry gets invalidated. We only check that the method entry in the callcache is not invalidated at the beginning of the method call, which makes it possible to have race conditions. This causes crashes like: vm_callinfo.h:421: Assertion Failed: vm_cc_cme:cc->klass != Qundef || !vm_cc_markable(cc) vm_insnhelper.c:2200: Assertion Failed: vm_lookup_cc:!METHOD_ENTRY_INVALIDATED(vm_cc_cme(ccs_cc)) This commit adds a VM barrier to method cache invalidation to ensure that other Ractors are stopped at a safe-point before invalidating the method entry.
2025-08-14YJIT: Fix `defined?(yield)` and `block_given?` at top levelAlan Wu
Previously, YJIT returned truthy for the block given query at the top level. That's incorrect because the top level script never receives a block, and `yield` is a syntax error there. Inside methods, the number of hops to get from `iseq` to `iseq->body->local_iseq` is the same as the number of `VM_ENV_PREV_EP(ep)` hops to get to an environment with `VM_ENV_FLAG_LOCAL`. YJIT and the interpreter both rely on this as can be seen in get_lvar_level(). However, this identity does not hold for the top level frame because of vm_set_eval_stack(), which sets up `TOPLEVEL_BINDING`. Since only methods can take a block that `yield` goes to, have ISEQs that are the child of a non-method ISEQ return falsy for the block given query. This fixes the issue for the top level script and is an optimization for non-method contexts such as inside `ISEQ_TYPE_CLASS`.
2025-08-01Refactor `vm_lookup_cc` to allow lock-free lookups in `RClass.cc_tbl`Jean Boussier
In multi-ractor mode, the `cc_tbl` mutations use the RCU pattern, which allow lock-less reads. Based on the assumption that invalidations and misses should be increasingly rare as the process ages, locking on modification isn't a big concern.
2025-07-30YJIT: Remove a dead test for getinlinecaches (#14033)Stan Lo
The test was added in #5221 4 years ago but: 1. The insn it targets was removed in 2022 in #6187 2. The YJIT API `blocks_for` seems to be dropped in 2022 when it switched to use Rust in #5826 So this test has not been run in more than 3 years and can't be run anymore. I think we can remove it.
2025-07-21Ractor: lock around global variable get/setLuke Gruber
There's a global id_table `rb_global_tbl` that needs a lock (I used VM lock). In the future, we might use a lock-free rb_id_table if we create such a data structure. Reproduction script that might crash or behave strangely: ```ruby 100.times do Ractor.new do 1_000_000.times do $stderr $stdout $stdin $VERBOSE $stderr $stdout $stdin $VERBOSE $stderr $stdout $stdin $VERBOSE end end end $myglobal0 = nil; $myglobal1 = nil; # ... vim macros to the rescue $myglobal100000 = nil; ```
2025-07-17Fix btest in ractor_test.rb that can lead timeout of the testLuke Gruber
It could also potentially lead to an out of memory error.
2025-07-11Add Timeout message when bootstraptest times outJohn Hawthorn
2025-07-10Fix rb_eSystemExit raised in RactorJohn Hawthorn
[Bug #21505] Previously `Ractor.new { exit }.join` would hang because SystemExit was special cased. This commit updates this to take the same path as other exceptions, which wraps the exception in a Ractor::RemoteError and does not end up exiting the main Ractor. I don't know if that's what this should do, but I think it's a reasonable behaviour as calling exit() in a Ractor is odd. in 'Ractor#join': thrown by remote Ractor. (Ractor::RemoteError) from -e:1:in '<main>' in 'Kernel#exit': exit (SystemExit) from -e:1:in 'block in <main>'
2025-07-04Fix wrong GENIV WB on too_complex Ractor traversalJohn Hawthorn
WBCHECK ERROR: Missed write barrier detected! Parent object: 0x7c4a5f1f66c0 (wb_protected: true) rb_obj_info_dump: 0x00007c4a5f1f66c0 T_IMEMO/<fields> Reference counts - snapshot: 2, writebarrier: 0, current: 2, missed: 1 Missing reference to: 0x7b6a5f2f7010 rb_obj_info_dump: 0x00007b6a5f2f7010 T_ARRAY/Array [E ] len: 1 (embed)
2025-07-03ZJIT: Skip a hanging ractor test (#13774)Takashi Kokubun
2025-06-13Get rid of FL_EXIVARJean Boussier
Now that the shape_id gives us all the same information, it's no longer needed. Notes: Merged: https://github.com/ruby/ruby/pull/13612
2025-06-06[Bug #21400] Fix rb_bug() when killing current root fiber in non-main thread ↵Luke Gruber
(#13526) Fixes the following: ```ruby Thread.new { Fiber.current.kill }.join ``` Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2025-06-04`Ractor#take` and warnKoichi Sasada
`Ractor#take` was deprecated but some libraries can use it as an alias for `Ractor#value` (i.e., to wait for a Ractor's temrination and retrieve its result). Therefore `Ractor#take` is simply an alias for `Ractor#value`. This method will remain available until the end of August 2025, unless there is further discussion. Notes: Merged: https://github.com/ruby/ruby/pull/13512
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-26Don't copy FL_PROMOTED to new object in Ractor movePeter Zhu
We should not copy the FL_PROMOTED flag when we move an object in Ractor#send(move: true) because the newly created object may not be old. Notes: Merged: https://github.com/ruby/ruby/pull/13442
2025-05-23Fix moving old objects between RactorsPeter Zhu
The FL_PROMOTED flag was not copied when moving objects, causing assertions to fail when an old object is moved: gc/default/default.c:834: Assertion Failed: RVALUE_AGE_SET:age <= RVALUE_OLD_AGE Co-Authored-By: Luke Gruber <luke.gruber@shopify.com> Notes: Merged: https://github.com/ruby/ruby/pull/13415
2025-05-23lock vm around `rb_free_generic_ivar`Luke Gruber
Currently, this can be reproduced by: r = Ractor.new do a = [1, 2, 3] a.object_id a.dup # this frees the generic ivar for `object_id` on the copied object :done end r.take In debug builds, this hits an assertion failure without this fix. Notes: Merged: https://github.com/ruby/ruby/pull/13401
2025-05-21Fix Symbol#to_proc (rb_sym_to_proc) to be ractor safeLuke Gruber
In non-main ractors, don't use `sym_proc_cache`. It is not thread-safe to add to this array without a lock and also it leaks procs from one ractor to another. Instead, we create a new proc each time. If this results in poor performance we can come up with a solution later. Fixes [Bug #21354] Notes: Merged: https://github.com/ruby/ruby/pull/13380
2025-05-13Get ractor message passing working with > 1 thread sending/receiving values ↵Luke Gruber
in same ractor Rework ractors so that any ractor action (Ractor.receive, Ractor#send, Ractor.yield, Ractor#take, Ractor.select) will operate on the thread that called the action. It will put that thread to sleep if it's a blocking function and it needs to put it to sleep, and the awakening action (Ractor.yield, Ractor#send) will wake up the blocked thread. Before this change every blocking ractor action was associated with the ractor struct and its fields. If a ractor called Ractor.receive, its wait status was wait_receiving, and when another ractor calls r.send on it, it will look for that status in the ractor struct fields and wake it up. The problem was that what if 2 threads call blocking ractor actions in the same ractor. Imagine if 1 thread has called Ractor.receive and another r.take. Then, when a different ractor calls r.send on it, it doesn't know which ruby thread is associated to which ractor action, so what ruby thread should it schedule? This change moves some fields onto the ruby thread itself so that ruby threads are the ones that have ractor blocking statuses, and threads are then specifically scheduled when unblocked. Fixes [#17624] Fixes [#21037] Notes: Merged: https://github.com/ruby/ruby/pull/12633
2025-05-13Throw RuntimeError if getting/setting ractor local storage for non-main ractorlukeg
[Bug #19367] Notes: Merged: https://github.com/ruby/ruby/pull/7174
2025-05-09Fix crash when instantiating classes in RactorsPeter Zhu
[Bug #18119] When we create classes, it pushes the class to the subclass list of the superclass. This access needs to be synchronized because multiple Ractors may be creating classes with the same superclass, which would cause race conditions and cause the linked list to be corrupted. For example, we can reproduce with this script crashing: workers = (0...8).map do Ractor.new do loop do 100.times.map { Class.new } Ractor.yield nil end end end 100.times { Ractor.select(*workers) } With ASAN enabled, we can see that there are use-after-free errors: ==176013==ERROR: AddressSanitizer: heap-use-after-free on address 0x5030000974f0 at pc 0x62f9e56f892d bp 0x7a503f1ffd90 sp 0x7a503f1ffd88 WRITE of size 8 at 0x5030000974f0 thread T4 #0 0x62f9e56f892c in rb_class_remove_from_super_subclasses class.c:149:24 #1 0x62f9e58c9dd2 in rb_gc_obj_free gc.c:1262:9 #2 0x62f9e58f6e19 in gc_sweep_plane gc/default/default.c:3450:21 #3 0x62f9e58f686a in gc_sweep_page gc/default/default.c:3535:13 #4 0x62f9e58f12b4 in gc_sweep_step gc/default/default.c:3810:9 #5 0x62f9e58ed2a7 in gc_sweep gc/default/default.c:4058:13 #6 0x62f9e58fac93 in gc_start gc/default/default.c:6402:13 #7 0x62f9e58e8b69 in heap_prepare gc/default/default.c:2032:13 #8 0x62f9e58e8b69 in heap_next_free_page gc/default/default.c:2255:9 #9 0x62f9e58e8b69 in newobj_cache_miss gc/default/default.c:2362:38 ... 0x5030000974f0 is located 16 bytes inside of 24-byte region [0x5030000974e0,0x5030000974f8) freed by thread T4 here: #0 0x62f9e562f28a in free (miniruby+0x1fd28a) (BuildId: 5ad6d9e7cec8318df6726ea5ce34d3c76d0d0233) #1 0x62f9e58ca2ab in rb_gc_impl_free gc/default/default.c:8102:9 #2 0x62f9e58ca2ab in ruby_sized_xfree gc.c:5029:13 #3 0x62f9e58ca2ab in ruby_xfree gc.c:5040:5 #4 0x62f9e56f88e6 in rb_class_remove_from_super_subclasses class.c:152:9 #5 0x62f9e58c9dd2 in rb_gc_obj_free gc.c:1262:9 #6 0x62f9e58f6e19 in gc_sweep_plane gc/default/default.c:3450:21 #7 0x62f9e58f686a in gc_sweep_page gc/default/default.c:3535:13 #8 0x62f9e58f12b4 in gc_sweep_step gc/default/default.c:3810:9 #9 0x62f9e58ed2a7 in gc_sweep gc/default/default.c:4058:13 ... previously allocated by thread T5 here: #0 0x62f9e562f70d in calloc (miniruby+0x1fd70d) (BuildId: 5ad6d9e7cec8318df6726ea5ce34d3c76d0d0233) #1 0x62f9e58c8e1a in calloc1 gc/default/default.c:1472:12 #2 0x62f9e58c8e1a in rb_gc_impl_calloc gc/default/default.c:8138:5 #3 0x62f9e58c8e1a in ruby_xcalloc_body gc.c:4964:12 #4 0x62f9e58c8e1a in ruby_xcalloc gc.c:4958:34 #5 0x62f9e56f906e in push_subclass_entry_to_list class.c:88:13 #6 0x62f9e56f906e in rb_class_subclass_add class.c:111:38 #7 0x62f9e56f906e in RCLASS_SET_SUPER internal/class.h:257:9 #8 0x62f9e56fca7a in make_metaclass class.c:786:5 #9 0x62f9e59db982 in rb_class_initialize object.c:2101:5 Notes: Merged: https://github.com/ruby/ruby/pull/13284
2025-05-08Raise error on take/send for Ractors in child processesAaron Patterson
Ractor objects that are available in a child process should raise a `Ractor::ClosedError` exception when called with `send` or `take` Co-authored-by: John Hawthorn <john@hawthorn.email> Notes: Merged: https://github.com/ruby/ruby/pull/12982
2025-05-08Clean up Ractor cache after forkAaron Patterson
Ractors created in a parent process should be properly shut down in the child process. They need their cache cleared and status set to "terminated" Co-authored-by: John Hawthorn <john@hawthorn.email> Notes: Merged: https://github.com/ruby/ruby/pull/12982
2025-05-08Rename `ivptr` -> `fields`, `next_iv_index` -> `next_field_index`Jean Boussier
Ivars will longer be the only thing stored inline via shapes, so keeping the `iv_index` and `ivptr` names would be confusing. Instance variables won't be the only thing stored inline via shapes, so keeping the `ivptr` name would be confusing. `field` encompass anything that can be stored in a VALUE array. Similarly, `gen_ivtbl` becomes `gen_fields_tbl`. Notes: Merged: https://github.com/ruby/ruby/pull/13159
2025-04-28YJIT: Fix potential infinite loop when OOM (GH-13186)Rian McGuire
Avoid generating an infinite loop in the case where: 1. Block `first` is adjacent to block `second`, and the branch from `first` to `second` is a fallthrough, and 2. Block `second` immediately exits to the interpreter, and 3. Block `second` is invalidated and YJIT is OOM While pondering how to fix this, I think I've stumbled on another related edge case: 1. Block `incoming_one` and `incoming_two` both branch to block `second`. Block `incoming_one` has a fallthrough 2. Block `second` immediately exits to the interpreter (so it starts with its exit) 3. When Block `second` is invalidated, the incoming fallthrough branch from `incoming_one` might be rewritten first, which overwrites the start of block `second` with a jump to a new branch stub. 4. YJIT runs of out memory 5. The incoming branch from `incoming_two` is then rewritten, but because we're OOM we can't generate a new stub, so we use `second`'s exit as the branch target. However `second`'s exit was already overwritten with a jump to the branch stub for `incoming_one`, so `incoming_two` will end up jumping to `incoming_one`'s branch stub. Fixes [Bug #21257] Notes: Merged: https://github.com/ruby/ruby/pull/13186 Merged-By: XrXr
2025-04-18Assert everything is compiled in test_zjit ↵Takashi Kokubun
(https://github.com/Shopify/zjit/pull/40) * Assert everything is compiled in test_zjit * Update a comment on rb_zjit_assert_compiles Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> * Add a comment about assert_compiles * Actually use pipe_fd --------- Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> Notes: Merged: https://github.com/ruby/ruby/pull/13131
2025-04-18Add two more small btestsMaxime Chevalier-Boisvert
Notes: Merged: https://github.com/ruby/ruby/pull/13131
2025-04-18Load Param off of cfp->ep (https://github.com/Shopify/zjit/pull/31)Takashi Kokubun
* Load Param off of cfp->ep * Test with --zjit-call-threshold=1 as well * Fix get_opnd's debug output * Return Mem operand from gen_param * Test both first and second calls * Spell out the namespace for Opnd returns * Update a comment about gen_param * Explain why we take a lock * Fix a typo Notes: Merged: https://github.com/ruby/ruby/pull/13131
2025-04-18Implement FixnumAdd and stub PatchPoint/GuardType ↵Takashi Kokubun
(https://github.com/Shopify/zjit/pull/30) * Implement FixnumAdd and stub PatchPoint/GuardType Co-authored-by: Max Bernstein <max.bernstein@shopify.com> Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> * Clone Target for arm64 * Use $create instead of use create Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> * Fix misindentation from suggested changes * Drop an unneeded variable for mut * Load operand into a register only if necessary --------- Co-authored-by: Max Bernstein <max.bernstein@shopify.com> Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> Notes: Merged: https://github.com/ruby/ruby/pull/13131
2025-04-18Recommend the same task as what CI usesTakashi Kokubun
Notes: Merged: https://github.com/ruby/ruby/pull/13131
2025-04-18Start testing the actual JIT code on CITakashi Kokubun
Notes: Merged: https://github.com/ruby/ruby/pull/13131
2025-04-18Lock-free hash set for fstrings [Feature #21268]John Hawthorn
This implements a hash set which is wait-free for lookup and lock-free for insert (unless resizing) to use for fstring de-duplication. As highlighted in https://bugs.ruby-lang.org/issues/19288, heavy use of fstrings (frozen interned strings) can significantly reduce the parallelism of Ractors. I tried a few other approaches first: using an RWLock, striping a series of RWlocks (partitioning the hash N-ways to reduce lock contention), and putting a cache in front of it. All of these improved the situation, but were unsatisfying as all still required locks for writes (and granular locks are awkward, since we run the risk of needing to reach a vm barrier) and this table is somewhat write-heavy. My main reference for this was Cliff Click's talk on a lock free hash-table for java https://www.youtube.com/watch?v=HJ-719EGIts. It turns out this lock-free hash set is made easier to implement by a few properties: * We only need a hash set rather than a hash table (we only need keys, not values), and so the full entry can be written as a single VALUE * As a set we only need lookup/insert/delete, no update * Delete is only run inside GC so does not need to be atomic (It could be made concurrent) * I use rb_vm_barrier for the (rare) table rebuilds (It could be made concurrent) We VM lock (but don't require other threads to stop) for table rebuilds, as those are rare * The conservative garbage collector makes deferred replication easy, using a T_DATA object Another benefits of having a table specific to fstrings is that we compare by value on lookup/insert, but by identity on delete, as we only want to remove the exact string which is being freed. This is faster and provides a second way to avoid the race condition in https://bugs.ruby-lang.org/issues/21172. This is a pretty standard open-addressing hash table with quadratic probing. Similar to our existing st_table or id_table. Deletes (which happen on GC) replace existing keys with a tombstone, which is the only type of update which can occur. Tombstones are only cleared out on resize. Unlike st_table, the VALUEs are stored in the hash table itself (st_table's bins) rather than as a compact index. This avoids an extra pointer dereference and is possible because we don't need to preserve insertion order. The table targets a load factor of 2 (it is enlarged once it is half full). Notes: Merged: https://github.com/ruby/ruby/pull/12921
2025-04-15Add a test for moving composite object partsLuke Gruber
2025-04-07Fixed wrong condition to avoid flaky ractor_test.rbHiroshi SHIBATA
Notes: Merged: https://github.com/ruby/ruby/pull/13078
2025-04-06Fix the if condition to skip test_ractor.rb correctly (#13067)Naoto Ono
Follow-up for https://github.com/ruby/ruby/commit/a2b03ba7cb721d698bebee74c535dea4583a9c28 Notes: Merged-By: ono-max <onoto1998@gmail.com>
2025-04-04Ractor: revert to moving object bytes, but size pool awareJean Boussier
Using `rb_obj_clone` introduce other problems, such as `initialize_*` callbacks invocation in the context of the parent ractor. So we can revert back to copy the content of the object slots, but in a way that is aware of size pools. Notes: Merged: https://github.com/ruby/ruby/pull/13070
2025-04-02Fix assertion failure with anonymous splatsJeremy Evans
When calling a method that accepts an anonymous splat and literal keywords without any arguments, an assertion failure was previously raised. Set rest_index to 0 when setting rest to the frozen hash, so the args_argc calculation is accurate. While here, add more tests for methods with anonymous splats with and without keywords and keyword splats to confirm behavior is correct. Also add a basic bootstrap test that would hit the previous assertion failure. Co-authored-by: Jean Boussier <jean.boussier@gmail.com> Notes: Merged: https://github.com/ruby/ruby/pull/13046
2025-03-31Don't preserve `object_id` when moving object to another RactorJean Boussier
That seemed like the logical thing to do to me, but ko1 disagree. Notes: Merged: https://github.com/ruby/ruby/pull/13008
2025-03-31Ractor: Fix moving embedded objectsJean Boussier
[Bug #20271] [Bug #20267] [Bug #20255] `rb_obj_alloc(RBASIC_CLASS(obj))` will always allocate from the basic 40B pool, so if `obj` is larger than `40B`, we'll create a corrupted object when we later copy the shape_id. Instead we can use the same logic than ractor copy, which is to use `rb_obj_clone`, and later ask the GC to free the original object. We then must turn it into a `T_OBJECT`, because otherwise just changing its class to `RactorMoved` leaves a lot of ways to keep using the object, e.g.: ``` a = [1, 2, 3] Ractor.new{}.send(a, move: true) [].concat(a) # Should raise, but wasn't. ``` If it turns out that `rb_obj_clone` isn't performant enough for some uses, we can always have carefully crafted specialized paths for the types that would benefit from it. Notes: Merged: https://github.com/ruby/ruby/pull/13008
2025-03-31Skip test_ractor.rb with ModGC workflow because this test is flakyHiroshi SHIBATA
Notes: Merged: https://github.com/ruby/ruby/pull/13015
2025-03-27Avoid array allocation for *nil, by not calling nil.to_aJeremy Evans
The following method call: ```ruby a(*nil) ``` A method call such as `a(*nil)` previously allocated an array, because it calls `nil.to_a`, but I have determined this array allocation is unnecessary. The instructions in this case are: ``` 0000 putself ( 1)[Li] 0001 putnil 0002 splatarray false 0004 opt_send_without_block <calldata!mid:a, argc:1, ARGS_SPLAT|FCALL> 0006 leave ``` The method call uses `ARGS_SPLAT` without `ARGS_SPLAT_MUT`, so the returned array doesn't need to be mutable. I believe all cases where `splatarray false` are used allow the returned object to be frozen, since the `false` means to not duplicate the array. The optimization in this case is to have `splatarray false` push a shared empty frozen array, instead of calling `nil.to_a` to return a newly allocated array. There is a slightly backwards incompatibility with this optimization, in that `nil.to_a` is not called. However, I believe the new behavior of `*nil` not calling `nil.to_a` is more consistent with how `**nil` does not call `nil.to_hash`. Also, so much Ruby code would break if `nil.to_a` returned something different from the empty hash, that it's difficult to imagine anyone actually doing that in real code, though we have a few tests/specs for that. I think it would be bad for consistency if `*nil` called `nil.to_a` in some cases and not others, so this changes other cases to not call `nil.to_a`: For `[*nil]`, this uses `splatarray true`, which now allocates a new array for a `nil` argument without calling `nil.to_a`. For `[1, *nil]`, this uses `concattoarray`, which now returns the first array if the second array is `nil`. This updates the allocation tests to check that the array allocations are avoided where possible. Implements [Feature #21047] Notes: Merged: https://github.com/ruby/ruby/pull/12597
2025-03-26Ractor.make_shareable(proc_obj) makes inner structure shareablelukeg
Proc objects are now traversed like other objects when making them shareable. Fixes [Bug #19372] Fixes [Bug #19374] Notes: Merged: https://github.com/ruby/ruby/pull/12977
2025-03-25Fail test if child process exists non-zeroJohn Hawthorn
Notes: Merged: https://github.com/ruby/ruby/pull/12981
2025-03-24Revert "Fix broken CI. (#12963)"Hiroshi SHIBATA
This reverts commit eb91c664dc0b4d69db09ae913f2d7a5ef3490d74. Notes: Merged: https://github.com/ruby/ruby/pull/12968
2025-03-23Fix broken CI. (#12963)Samuel Williams
* Increase timeout to fix flaky tests? * Fix bundler test - wording changed. expect "fatal: Remote branch deadbeef not found in upstream origin" to include "Revision deadbeef does not exist in the repository" Notes: Merged-By: ioquatix <samuel@codeotaku.com>
2025-03-21FCALL shouldn't be forwarded from callerAaron Patterson
When we forward an FCALL (a method call with an implicit self), we shouldn't forward the FCALL flag because it ignores method visibility checks. This patch removes the FCALL flag from callers. [Bug #21196]
2025-03-20Use atomic for method reference count [Bug #20934]John Hawthorn
This changes reference_count on rb_method_definition_struct into an atomic. Ractors can create additional references as part of `bind_call` or (presumably) similar. Because this can be done inside Ractors, we should use a lock or atomics so that we don't race and avoid incrementing. Co-authored-by: wanabe <s.wanabe@gmail.com> Notes: Merged: https://github.com/ruby/ruby/pull/12951