| Age | Commit message (Collapse) | Author |
|
|
|
Before this patch, Ractor::IsolationError reported an incorrect constant
path when constant was found through `rb_const_get_0()`.
In this code, Ractor::IsolationError reported illegal access against
`M::TOPLEVEL`, where it should be `Object::TOPLEVEL`.
```ruby
TOPLEVEL = [1]
module M
def self.f
TOPLEVEL
end
end
Ractor.new { M.f }.value
```
This was because `rb_const_get_0()` built the "path" part referring to
the module/class passed to it in the first place. When a constant was
found through recursive search upwards, the module/class which the
constant was found should be reported.
This patch fixes this issue by modifying rb_const_search() to take a
VALUE pointer to be filled with the module/class where the constant was
found.
[Bug #21782]
|
|
|
|
|
|
|
|
Although the Ractor API is still experimental and may change, and there
may be some implementation issues, we should no longer say that there
are many.
Hopefully we can remove this warning entirely for Ruby 4.1
|
|
This refactors the concurrent set to examine and reserve a slot via CAS
with the hash, before then doing the same with the key.
This allows us to use an extra bit from the hash as a "continuation bit"
which marks whether we have ever probed past this key while inserting.
When that bit isn't set on deletion we can clear the field instead of
placing a tombstone.
|
|
|
|
|
|
|
|
|
|
on resource limited environment.
```
stderr output is not empty
bootstraptest.test_ractor.rb_2446_1412.rb:23:in 'Ractor.new': can't create Thread: Cannot allocate memory (ThreadError)
```
|
|
The id2ref table needs to be under a VM lock to ensure there are no race
conditions. The following script crashes:
o = Object.new
ObjectSpace._id2ref(o.object_id)
10.times.map do
Ractor.new do
10_000.times do
a = Object.new
a.object_id
end
end
end.map(&:value)
With:
[BUG] Object ID seen, but not in _id2ref table: object_id=2800 object=T_OBJECT
ruby 4.0.0dev (2025-12-06T15:15:43Z ractor-id2ref-fix e7f9abdc91) +PRISM [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0001 p:---- s:0003 e:000002 l:y b:---- DUMMY [FINISH]
-- Threading information ---------------------------------------------------
Total ractor count: 5
Ruby thread count for this ractor: 1
-- C level backtrace information -------------------------------------------
miniruby(rb_print_backtrace+0x14) [0x6047d09b2dff] vm_dump.c:1105
miniruby(rb_vm_bugreport) vm_dump.c:1450
miniruby(rb_bug_without_die_internal+0x5f) [0x6047d066bf57] error.c:1098
miniruby(rb_bug) error.c:1116
miniruby(rb_gc_get_ractor_newobj_cache+0x0) [0x6047d066c8dd] gc.c:2052
miniruby(gc_sweep_plane+0xad) [0x6047d079276d] gc/default/default.c:3513
miniruby(gc_sweep_page) gc/default/default.c:3605
miniruby(gc_sweep_step) gc/default/default.c:3886
miniruby(gc_sweep+0x1ba) [0x6047d0794cfa] gc/default/default.c:4154
miniruby(gc_start+0xbf2) [0x6047d0796742] gc/default/default.c:6519
miniruby(heap_prepare+0xcc) [0x6047d079748c] gc/default/default.c:2090
miniruby(heap_next_free_page) gc/default/default.c:2305
miniruby(newobj_cache_miss) gc/default/default.c:2412
miniruby(newobj_alloc+0xd) [0x6047d0798ff5] gc/default/default.c:2436
miniruby(rb_gc_impl_new_obj) gc/default/default.c:2515
miniruby(newobj_of) gc.c:996
miniruby(rb_wb_protected_newobj_of) gc.c:1046
miniruby(str_alloc_embed+0x28) [0x6047d08fda18] string.c:1019
miniruby(str_enc_new) string.c:1069
miniruby(prep_io+0x5) [0x6047d07cda14] io.c:9305
miniruby(prep_stdio) io.c:9347
miniruby(rb_io_prep_stdin) io.c:9365
miniruby(thread_start_func_2+0x77c) [0x6047d093a55c] thread.c:679
miniruby(thread_sched_lock_+0x0) [0x6047d093aacd] thread_pthread.c:2241
miniruby(co_start) thread_pthread_mn.c:469
|
|
`pr` should not change on this method.
|
|
|
|
When defining a bmethod, we recorded the current Ractor's object in the
method. However that was never marked and so could be GC'd and reused by
a future Ractor. Instead we can use the Ractor's id, which we expect to
be unique forever.
Co-authored-by: Luke Gruber <luke.gru@gmail.com>
|
|
|
|
with `RUBY_TYPED_FROZEN_SHAREABLE_NO_REC`,
if the receiver object is shareable on Method objects.
|
|
Since we do not run a Ractor barrier before forking, it's possible that
another other Ractor is halfway through allocating an object during forking.
This may lead to allocated_objects_count being off by one.
For example, the following script reproduces the bug:
100.times do |i|
Ractor.new(i) do |j|
10000.times do |i|
"#{j}-#{i}"
end
Ractor.receive
end
pid = fork { GC.verify_internal_consistency }
_, status = Process.waitpid2 pid
raise unless status.success?
end
We need to run with `taskset -c 1` to force it to use a single CPU core
to more consistenly reproduce the bug:
heap_pages_final_slots: 1, total_freed_objects: 16628
test.rb:8: [BUG] inconsistent live slot number: expect 19589, but 19588.
ruby 4.0.0dev (2025-11-25T03:06:55Z master 55892f5994) +PRISM [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0007 p:---- s:0029 e:000028 l:y b:---- CFUNC :verify_internal_consistency
c:0006 p:0004 s:0025 e:000024 l:n b:---- BLOCK test.rb:8 [FINISH]
c:0005 p:---- s:0022 e:000021 l:y b:---- CFUNC :fork
c:0004 p:0012 s:0018 E:0014c0 l:n b:---- BLOCK test.rb:8
c:0003 p:0024 s:0011 e:000010 l:y b:0001 METHOD <internal:numeric>:257
c:0002 p:0005 s:0006 E:001730 l:n b:---- EVAL test.rb:1 [FINISH]
c:0001 p:0000 s:0003 E:001d20 l:y b:---- DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
test.rb:1:in '<main>'
<internal:numeric>:257:in 'times'
test.rb:8:in 'block in <main>'
test.rb:8:in 'fork'
test.rb:8:in 'block (2 levels) in <main>'
test.rb:8:in 'verify_internal_consistency'
-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1
-- C level backtrace information -------------------------------------------
ruby(rb_print_backtrace+0x14) [0x61b67ac48b60] vm_dump.c:1105
ruby(rb_vm_bugreport) vm_dump.c:1450
ruby(rb_bug_without_die_internal+0x5f) [0x61b67a818a28] error.c:1098
ruby(rb_bug) error.c:1116
ruby(gc_verify_internal_consistency_+0xbdd) [0x61b67a83d8ed] gc/default/default.c:5186
ruby(gc_verify_internal_consistency+0x2d) [0x61b67a83d960] gc/default/default.c:5241
ruby(rb_gc_verify_internal_consistency) gc/default/default.c:8950
ruby(gc_verify_internal_consistency_m) gc/default/default.c:8966
ruby(vm_call_cfunc_with_frame_+0x10d) [0x61b67a9e50fd] vm_insnhelper.c:3902
ruby(vm_sendish+0x111) [0x61b67a9eeaf1] vm_insnhelper.c:6124
ruby(vm_exec_core+0x84) [0x61b67aa07434] insns.def:903
ruby(vm_exec_loop+0xa) [0x61b67a9f8155] vm.c:2811
ruby(rb_vm_exec) vm.c:2787
ruby(vm_yield_with_cref+0x90) [0x61b67a9fd2ea] vm.c:1865
ruby(vm_yield) vm.c:1873
ruby(rb_yield) vm_eval.c:1362
ruby(rb_protect+0xef) [0x61b67a81fe6f] eval.c:1154
ruby(rb_f_fork+0x16) [0x61b67a8e98ab] process.c:4293
ruby(rb_f_fork) process.c:4284
|
|
This was a test case for Ractors discovered that causes MMTk to deadlock.
There is a fix for it in https://github.com/ruby/mmtk/pull/49.
|
|
This bug was happening only when the `id2ref` table exists. We need
to replace the generic fields before replacing the object id of the
newly moved object.
Fixes [Bug #21664]
|
|
|
|
|
|
|
|
|
|
because ISeq is shareable now.
|
|
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]
|
|
ZJIT: Unskip Ractor bootstrap test
|
|
[Feature #21262]
|
|
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
|
|
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.
|
|
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.
|
|
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;
```
|
|
It could also potentially lead to an out of memory error.
|
|
[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>'
|
|
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)
|
|
|
|
`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
|
|
* 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
[Bug #19367]
Notes:
Merged: https://github.com/ruby/ruby/pull/7174
|
|
[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
|
|
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
|
|
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
|
|
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
|
|
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
|