summaryrefslogtreecommitdiff
path: root/ractor_sync.c
AgeCommit message (Collapse)Author
2025-12-18Check for NULL fields in TYPEDDATA memsize functions (#15633)Luke Gruber
Some TYPEDDATA objects allocate struct fields using the GC right after they get created, and in that case the VM can try to perform a GC and join a barrier if another ractor started one. If we're dumping the heap in another ractor, this acquires a barrier and it will call the `rb_obj_memsize` function on this object. We can't assume these struct fields are non-null. This also goes for C extensions, which may cause problems with heap dumping from a ractor if their memsize functions aren't coded correctly to check for NULL fields. Because dumping the heap from a ractor is likely a rare scenario and it has only recently been introduced, we'll have to see how this works in practice and if it causes bugs.
2025-12-17Fix heap dump with ractor barrierLuke Gruber
When a ractor was being initialized and it would join the heap dump barrier when allocating its queue or its ports, the heap dump code calls `rb_obj_memsize` on the ractor and this function assumed `ports` was never NULL. We need to check for the NULL case in case the ractor is still being initialized. Hopefully other T_DATA objects don't suffer from the same issue, otherwise we could revert the ractor barrier during heap dump or not use `rb_obj_memsize` on T_DATA during the heap dump.
2025-10-30allow Ractor::Port shareableKoichi Sasada
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-22Fix typo in function name ractor_port_initializePeter Zhu
2025-08-06Make Ractor::Selector write-barrier protectedPeter Zhu
2025-08-04[DOC] Fill undocumented documentsNobuyoshi Nakada
2025-08-01Remove dead rb_ractor_t in ractor_selectorPeter Zhu
2025-07-04ractor_sync.c: Optimize `ractor_set_successor_once` to be lock freeJean Boussier
2025-06-22Suppress warningsNobuyoshi Nakada
- `ractor_sync_terminate_atfork` is unused unless fork is working - `cr` in `vm_lock_leave` is only for debugging
2025-06-18Add write barriers from Ractor::Port to RactorJohn Hawthorn
Ractor::Port will mark the ractor, so we must issue a write barrier. This was detected by wbcheck, but we've also seen it in CI: verify_internal_consistency_reachable_i: WB miss (O->Y) 0x000071507d8bff80 ractor/port/Ractor::Port ractor/port -> 0x0000715097f5a470 ractor/Ractor r:1 <internal:kernel>:48: [BUG] gc_verify_internal_consistency: found internal inconsistency. Notes: Merged: https://github.com/ruby/ruby/pull/13644
2025-06-12Fix memory leak of Ractor basket when sending to closed RactorPeter Zhu
The following script leaks memory: r = Ractor.new { } r.value 10.times do 100_000.times do r.send(123) rescue Ractor::ClosedError end puts `ps -o rss= -p #{$$}` end Before: 18508 25420 32460 40012 47308 54092 61132 68300 75724 83020 After: 11432 11432 11432 11432 11432 11432 11432 11432 11432 11688 Notes: Merged: https://github.com/ruby/ruby/pull/13590
2025-06-03Fix memory leak of Ractor portsPeter Zhu
Memory leak reported: 3 miniruby 0x1044b6c1c ractor_init + 164 ractor.c:460 2 miniruby 0x1043fd6a0 ruby_xmalloc + 44 gc.c:5188 1 miniruby 0x104402840 rb_gc_impl_malloc + 148 default.c:8140 0 libsystem_malloc.dylib 0x19ab3912c _malloc_zone_malloc_instrumented_or_legacy + 152 Notes: Merged: https://github.com/ruby/ruby/pull/13504
2025-06-03Fix memory leak of Ractor recv_queuePeter Zhu
Memory leak reported: 3 miniruby 0x104702c1c ractor_init + 164 ractor.c:460 2 miniruby 0x1046496a0 ruby_xmalloc + 44 gc.c:5188 1 miniruby 0x10464e840 rb_gc_impl_malloc + 148 default.c:8140 0 libsystem_malloc.dylib 0x19ab3912c _malloc_zone_malloc_instrumented_or_legacy + 152 Notes: Merged: https://github.com/ruby/ruby/pull/13504
2025-05-31prepare IDs for `Ractor::monitor`Koichi Sasada
To prevent the following strange error, prepare IDs at first. ``` <internal:ractor>:596:in 'Ractor#monitor': symbol :exited is already registered with 98610c (fatal) from <internal:ractor>:550:in 'Ractor#join' from <internal:ractor>:574:in 'Ractor#value' from bootstraptest.test_ractor.rb_2013_1309.rb:12:in '<main>' ``` BTW, the error should be fixed on ID management system. Notes: Merged: https://github.com/ruby/ruby/pull/13481
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