Commit graph

60 commits

Author SHA1 Message Date
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
John Hawthorn
f483befd90 Add shape_id to RBasic under 32 bit
This makes `RBobject` `4B` larger on 32 bit systems
but simplifies the implementation a lot.

[Feature #21353]

Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
2025-05-26 10:31:54 +02:00
Peter Zhu
e00c46017b Drop unnecessary compiler guards for memory_sanitizer
We unpoison slots allocated out of the GC, so we don't need to disable
the assertions that read from the memory.
2025-05-23 14:35:56 -04:00
Peter Zhu
9130023cf5 Remove dependency on bits.h in default.c when BUILDING_MODULAR_GC
We can assume that the compiler will have __builtin_clzll so we can implement
nlz_int64 using that.
2025-05-23 14:35:56 -04:00
Peter Zhu
b043abc048 Only define RVALUE_OVERHEAD if undefined
This allows RVALUE_OVERHEAD to be defined elsewhere.
2025-05-20 12:57:23 -04:00
Jean Boussier
1e33a451bb gc: Execute run_final with the lock held
The finalizer table can't be read nor modified without the VM lock.
2025-05-16 20:16:52 +02:00
Jean Boussier
ec8900e3eb rb_gc_impl_copy_finalizer: generate a new object id
Fix a regression introduced by: https://github.com/ruby/ruby/pull/13155
2025-05-16 20:16:52 +02:00
Jean Boussier
a294427017 Add missing lock to rb_gc_impl_copy_finalizer 2025-05-16 20:16:52 +02:00
Peter Zhu
04f538c144 Remove dependency on sanitizers.h in default.c when BUILDING_MODULAR_GC 2025-05-15 14:13:53 -04:00
Jean Boussier
ed632cd0ba Add missing lock in rb_gc_impl_undefine_finalizer
The table is global so accesses must be synchronized.
2025-05-15 13:32:08 +02:00
Jean Boussier
3d1b8e7298 newobj_fill: don't assume RBasic size
The previous implementation assumed `RBasic` size is `2 * sizeof(VALUE)`,
might as well not make assumption and use a proper `sizeof`.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
2025-05-15 13:26:26 +02:00
John Hawthorn
3306d7d2f9 Only clear Ractor cache in child
This avoids a race condition where we were clearing the cache from
another ractor while it was in use. Oops!

Fixes this failure http://ci.rvm.jp/results/master@oci-aarch64/5750416
2025-05-09 16:15:54 -07:00
Jean Boussier
15e3675ecc Remove dead code in rb_gc_impl_ractor_cache_free
Followup: https://github.com/ruby/ruby/pull/13286
2025-05-09 11:13:52 +02:00
John Hawthorn
30ef0f180b Fix allocation count when forking with Ractors
After fork we reset to single ractor mode (which IMO we shouldn't do,
but it requires more work to fix) and so we need to add the pending
object counts back to the main heap.
2025-05-09 00:55:14 -07:00
Peter Zhu
c18bedcdbb Remove dependency on debug_counter.h when BUILDING_MODULAR_GC
This allows the default GC to not need debug_counter.h when building as a
modular GC.
2025-05-08 10:36:27 -04:00
Peter Zhu
3f5080e767 Stop checking for USE_DEBUG_COUNTER in default.c
We don't need to check for USE_DEBUG_COUNTER because the code is no-op
if USE_DEBUG_COUNTER is not enabled.
2025-05-08 10:36:27 -04:00
Jean Boussier
f48e45d1e9 Move object_id in object fields.
And get rid of the `obj_to_id_tbl`

It's no longer needed, the `object_id` is now stored inline
in the object alongside instance variables.

We still need the inverse table in case `_id2ref` is invoked, but
we lazily build it by walking the heap if that happens.

The `object_id` concern is also no longer a GC implementation
concern, but a generic implementation.

Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com>
2025-05-08 07:58:05 +02:00
Peter Zhu
3e94b5f9c0 Remove dependence on internal/hash.h for default GC 2025-05-07 11:47:13 -04:00
Étienne Barrié
cb772247e7 Improve correctness contention for allocated object counts
Currently the count of allocated object for a heap is incremented
without regards to parallelism which leads to incorrect counts.

By maintaining a local counter in the ractor newobj cache, and only
syncing atomically with some granularity, we can improve the correctness
without increasing contention.

The allocated object count is also synced when the ractor is freed.

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2025-05-06 19:13:59 +02:00
Nobuyoshi Nakada
c772d2691d
Count metadata entries automatically from the names list 2025-04-25 19:40:04 +09:00
Jean Boussier
62a7f17157 Update RB_GC_OBJECT_METADATA_ENTRY_COUNT
In cb1ea54bbf I added one more
metadata flag, but didn't notice `RB_GC_OBJECT_METADATA_ENTRY_COUNT`
had to be incremented.

This should fix ASAN builds.

Interestingly, bdb25959fb already
caused the count to be off by one, so I had to increment it by
2.
2025-04-25 11:17:31 +02:00
Jean Boussier
cb1ea54bbf objspace_dump: Include shareable flag
Given that the currently planned ractor local GC implementation
performance will heavilly be influenced by the number of shareable
objects it would be valuable to be able to know how many of them
are in the heap.
2025-04-24 10:14:29 +02:00
Jean Boussier
1048b162de Eagerly store a copy of object_id in finalizer table.
This makes the finalizer table fully self contained, so GC no
longer need to delay cleaning the `obj_to_id_tbl`.
2025-04-23 11:54:29 +02:00
Jean Boussier
6d0dd7d863 rb_gc_impl_define_finalizer: unlock on early return 2025-04-23 07:04:41 +02:00
Jean Boussier
7c30bd50df Add missing lock in rb_gc_impl_define_finalizer
`objspace->finalizer_table` must be synchronized,
otherwise concurrent insertion from multiple ractors
will cause a crash.

Repro:

```ruby
ractors = 5.times.map do |i|
  Ractor.new do
    100_000.times.map do
      o = Object.new
      ObjectSpace.define_finalizer(o, ->(id) {})
      o
    end
  end
end

ractors.each(&:take)
```
2025-04-22 23:23:35 +02:00
Jean Boussier
0606046c1a Lazily create objspace->id_to_obj_tbl
This inverse table is only useful if `ObjectSpace._id2ref` is used,
which is extremely rare. The only notable exception is the `drb` gem
and even then it has an option not to rely on `_id2ref`.

So if we assume this table will never be looked up, we can just
not maintain it, and if it turns out `_id2ref` is called, we
can lock the VM and re-build it.

```
compare-ruby: ruby 3.5.0dev (2025-04-10T09:44:40Z master 684cfa42d7) +YJIT +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-04-10T10:13:43Z lazy-id-to-obj d3aa9626cc) +YJIT +PRISM [arm64-darwin24]
warming up..

|           |compare-ruby|built-ruby|
|:----------|-----------:|---------:|
|baseline   |     26.364M|   25.974M|
|           |       1.01x|         -|
|object_id  |     10.293M|   14.202M|
|           |           -|     1.38x|
```
2025-04-15 07:57:39 +09:00
Peter Zhu
d4406f0627 Grow GC heaps independently
[Bug #21214]

If we allocate objects where one heap holds transient objects and another
holds long lived objects, then the heap with transient objects will grow
along the heap with long lived objects, causing higher memory usage.

For example, we can see this issue in this script:

    def allocate_small_object = []
    def allocate_large_object = Array.new(10)

    arys = Array.new(1_000_000) do
      # Allocate 10 small transient objects
      10.times { allocate_small_object }
      # Allocate 1 large object that is persistent
      allocate_large_object
    end

    pp GC.stat
    pp GC.stat_heap

Before this change:

    heap_live_slots: 2837243
    {0 =>
      {slot_size: 40,
       heap_eden_pages: 1123,
       heap_eden_slots: 1838807},
     2 =>
      {slot_size: 160,
       heap_eden_pages: 2449,
       heap_eden_slots: 1001149},
    }

After this change:

    heap_live_slots: 1094474
    {0 =>
      {slot_size: 40,
       heap_eden_pages: 58,
       heap_eden_slots: 94973},
     2 =>
      {slot_size: 160,
       heap_eden_pages: 2449,
       heap_eden_slots: 1001149},
    }
2025-04-07 09:41:11 -04:00
Jean Boussier
7db0e07134 Don't preserve object_id when moving object to another Ractor
That seemed like the logical thing to do to me, but ko1 disagree.
2025-03-31 12:01:55 +02:00
Jean Boussier
0350290262 Ractor: Fix moving embedded objects
[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.
2025-03-31 12:01:55 +02:00
Alan Wu
c576e83a24
Prefer FL_TEST_RAW() in GC on known on-heap objects
Was reading some assembly and noticed the dead branches generated for
FL_TEST(). Just a quick basic pass to change the obvious places; there
may be other opportunities.
2025-03-25 18:16:31 -04:00
Peter Zhu
6bb35a1de4 Make ruby_autocompact_compare_func static
It's not used outside of default.c.
2025-03-25 16:19:32 -04:00
Peter Zhu
e4c7eb1152 Make ruby_enable_autocompact static
It's not used outside of defaut.c
2025-03-25 16:19:32 -04:00
Peter Zhu
bdb25959fb Move object_id to flags for ObjectSpace dumps
Moving object_id dumping from ObjectSpace to the GC flags allows ObjectSpace
to not assume the FL_SEEN_OBJ_ID flag and instead move it to the responsibility
of the GC.
2025-03-13 10:12:24 -04:00
Peter Zhu
49e229b3fc Fix value of RB_GC_OBJECT_METADATA_ENTRY_COUNT
There are 7 entries in RB_GC_OBJECT_METADATA_ENTRY_COUNT.
2025-02-19 09:56:17 -05:00
Peter Zhu
5e45f2a0bc Add age to rb_gc_object_metadata
This will allow ObjectSpace.dump to output the age of the object.
2025-02-19 09:47:28 -05:00
Peter Zhu
7b6e07ea93 Add rb_gc_object_metadata API
This function replaces the internal rb_obj_gc_flags API. rb_gc_object_metadata
returns an array of name and value pairs, with the last element having
0 for the name.
2025-02-19 09:47:28 -05:00
Yuta Saito
eac35edfd1 [wasm] Stop using mprotect(PROT_NONE) on WASI
we had been using a stub weak definition of `mprotect` in wasm/missing.c
so far, but wasi-sdk 23 added mprotect emulation to wasi-libc[^1], so the
emulation is now linked instead. However, the emulation doesn't support
PROT_NONE and fails with ENOSYS, so we need to avoid calling mprotect
completely on WASI.

[^1]: 7528b13170
2025-02-19 11:46:12 +09:00
Daisuke Aritomo
c7e35e5534 gc.c: Remove no-op code
In this context, `vm_locked` is a argument variable, and is not used
later in the function.
2025-02-10 14:21:10 -05:00
Peter Zhu
de45755de8 Use an identity hash instead of array for stress_to_class 2025-01-29 13:22:04 -05:00
Peter Zhu
5e644e80e9 Fix GC.add_stress_to_class and GC.remove_stress_to_class
These methods were accidentally removed in [Feature #20470]. This commit
adds them back.
2025-01-29 13:22:04 -05:00
Nobuyoshi Nakada
be44d5677d
Suppress unused-value warnings 2025-01-29 16:57:16 +09:00
Peter Zhu
cb9aeb283b Fix gc_update_references_weak_table_i for ASAN
If the object is a T_MOVED, then it is poisoned in ASAN, so we need to
unpoison it before checking the type.
2025-01-27 13:26:26 -05:00
Peter Zhu
98b36f6f36 Use rb_gc_vm_weak_table_foreach for reference updating
We can use rb_gc_vm_weak_table_foreach for reference updating of weak tables
in the default GC.
2025-01-27 10:28:36 -05:00
Peter Zhu
89240eb2fb Add generic ivar reference updating step
Previously, generic ivars worked differently than the other global tables
during compaction. The other global tables had their references updated
through iteration during rb_gc_update_vm_references. Generic ivars updated
the keys when the object moved and updated the values while reference
updating the object. This is inefficient as this required one lookup for
every moved object and one lookup for every object with generic ivars.

Instead, this commit changes it to iterate over the generic ivar table to
update both the keys and values.
2025-01-22 08:54:52 -05:00
Jean Boussier
22e9fa81ca gc/default/default.c: don't call malloc_usable_size when hint is present
Depending on the allocator, `malloc_usable_size` may be very cheap or quite
expensive. On `macOS` for instance, it's about as expensive as `malloc`.

In many case we call `objspace_malloc_size` with as size we initially
requested as `hint`. The real usable size may be a few bytes bigger,
but since we only use that data to feed GC heuristics, I don't think
it's very important to be perfectly accurate.

It would make sense to call `malloc_usable_size` after growing a String
or Array to use the extra capacity, but here we don't do that, so
the call isn't worth its cost.
2025-01-05 17:04:54 +01:00
Peter Zhu
b8c4af24f9 Use rb_darray_insert_without_gc for heap_pages darray
rb_darray_insert could trigger a GC, which would cause problems if it
freed pages while a new page was being inserted.

For example, the following script fails:

    GC.stress = true
    GC.auto_compact = :empty

    10.times do
      GC.verify_compaction_references(expand_heap: true, toward: :empty)
    end

It errors out with:

    'GC.verify_compaction_references': malloc: possible integer overflow (8*18446744073709551603) (ArgumentError)
2025-01-02 11:03:04 -05:00
Peter Zhu
f9cd9a1b55 Revert "Remove with_gc functions in darray"
This reverts commit 24a7407960.
2025-01-02 11:03:04 -05:00
Peter Zhu
a58675386c Prefix asan_poison_object with rb 2024-12-19 09:14:34 -05:00
Peter Zhu
fcd44eee09 Fix compaction in ASAN with RGENGC_CHECK_MODE enabled 2024-12-19 09:14:34 -05:00
Peter Zhu
16750a47d0 Don't calculate the aligned slot when unlocking page
If we try to use GET_PAGE_HEADER, it can trigger the read barrier. If we
try to align on the slot then we end up unlocking the heap page of a
lower memory address.
2024-12-19 09:14:34 -05:00