summaryrefslogtreecommitdiff
path: root/test/ruby/test_gc.rb
AgeCommit message (Collapse)Author
2025-12-26Remove `RUBY_GC_HEAP_INIT_SLOTS` environment variableNobuyoshi Nakada
2025-12-25Implement declaring weak referencesPeter Zhu
[Feature #21084] # Summary The current way of marking weak references uses `rb_gc_mark_weak(VALUE *ptr)`. This presents challenges because Ruby's GC is incremental, meaning that if the `ptr` changes (e.g. realloc'd or free'd), then we could have an invalid memory access. This also overwrites `*ptr = Qundef` if `*ptr` is dead, which prevents any cleanup to be run (e.g. freeing memory or deleting entries from hash tables). This ticket proposes `rb_gc_declare_weak_references` which declares that an object has weak references and calls a cleanup function after marking, allowing the object to clean up any memory for dead objects. # Introduction In [[Feature #19783]](https://bugs.ruby-lang.org/issues/19783), I introduced an API allowing objects to mark weak references, the function signature looks like this: ```c void rb_gc_mark_weak(VALUE *ptr); ``` `rb_gc_mark_weak` is called during the marking phase of the GC to specify that the memory at `ptr` holds a pointer to a Ruby object that is weakly referenced. `rb_gc_mark_weak` appends this pointer to a list that is processed after the marking phase of the GC. If the object at `*ptr` is no longer alive, then it overwrites the object reference with a special value (`*ptr = Qundef`). However, this API resulted in two challenges: 1. Ruby's default GC is incremental, which means that the GC is not ran in one phase, but rather split into chunks of work that interleaves with Ruby execution. The `ptr` passed into `rb_gc_mark_weak` could be on the malloc heap, and that memory could be realloc'd or even free'd. We had to use workarounds such as `rb_gc_remove_weak` to ensure that there were no illegal memory accesses. This made `rb_gc_mark_weak` difficult to use, impacted runtime performance, and increased memory usage. 2. When an object dies, `rb_gc_mark_weak` only overwites the reference with `Qundef`. This means that if we want to do any cleanup (e.g. free a piece of memory or delete a hash table entry), we could not do that and had to defer this process elsewhere (e.g. during marking or runtime). In this ticket, I'm proposing a new API for weak references. Instead of an object marking its weak references during the marking phase, the object declares that it has weak references using the `rb_gc_declare_weak_references` function. This declaration occurs during runtime (e.g. after the object has been created) rather than during GC. After an object declares that it has weak references, it will have its callback function called after marking as long as that object is alive. This callback function can then call a special function `rb_gc_handle_weak_references_alive_p` to determine whether its references are alive. This will allow the callback function to do whatever it wants on the object, allowing it to perform any cleanup work it needs. This significantly simplifies the code for `ObjectSpace::WeakMap` and `ObjectSpace::WeakKeyMap` because it no longer needs to have the workarounds for the limitations of `rb_gc_mark_weak`. # Performance The performance results below demonstrate that `ObjectSpace::WeakMap#[]=` is now about 60% faster because the implementation has been simplified and the number of allocations has been reduced. We can see that there is not a significant impact on the performance of `ObjectSpace::WeakMap#[]`. Base: ``` ObjectSpace::WeakMap#[]= 4.620M (± 6.4%) i/s (216.44 ns/i) - 23.342M in 5.072149s ObjectSpace::WeakMap#[] 30.967M (± 1.9%) i/s (32.29 ns/i) - 154.998M in 5.007157s ``` Branch: ``` ObjectSpace::WeakMap#[]= 7.336M (± 2.8%) i/s (136.31 ns/i) - 36.755M in 5.013983s ObjectSpace::WeakMap#[] 30.902M (± 5.4%) i/s (32.36 ns/i) - 155.901M in 5.064060s ``` Code: ``` require "bundler/inline" gemfile do source "https://rubygems.org" gem "benchmark-ips" end wmap = ObjectSpace::WeakMap.new key = Object.new val = Object.new wmap[key] = val Benchmark.ips do |x| x.report("ObjectSpace::WeakMap#[]=") do |times| i = 0 while i < times wmap[Object.new] = Object.new i += 1 end end x.report("ObjectSpace::WeakMap#[]") do |times| i = 0 while i < times wmap[key] wmap[val] # does not exist i += 1 end end end ``` # Alternative designs Currently, `rb_gc_declare_weak_references` is designed to be an internal-only API. This allows us to assume the object types that call `rb_gc_declare_weak_references`. In the future, if we want to open up this API to third parties, we may want to change this function to something like: ```c void rb_gc_add_cleaner(VALUE obj, void (*callback)(VALUE obj)); ``` This will allow the third party to implement a custom `callback` that gets called after the marking phase of GC to clean up any dead references. I chose not to implement this design because it is less efficient as we would need to store a mapping from `obj` to `callback`, which requires extra memory.
2025-11-19Implement heap_final_slots in GC.stat_heapPeter Zhu
[Feature #20408]
2025-11-19Implement heap_free_slots in GC.stat_heapPeter Zhu
[Feature #20408]
2025-11-19Implement heap_live_slots in GC.stat_heapPeter Zhu
[Feature #20408]
2025-11-04Release VM lock before running finalizers (#15050)Luke Gruber
We shouldn't run any ruby code with the VM lock held.
2025-09-21Exclude failing GC finalizer tests with ASANBenoit Daloze
* See https://bugs.ruby-lang.org/issues/21613
2025-09-03Revert "test_gc.rb: Attempt to stabilize test_interrupt_in_finalizer"Takashi Kokubun
This reverts commit c1c0b32445c66e343c136faa28d7a0f0f46d96a2. This test is clearly not reliable: https://github.com/ruby/ruby/actions/runs/17446920961/job/49543543423 I want to skip this unstable test on GitHub Actions to make sure this test doesn't prevent PRs from getting merged. We can continue to monitor the state of this test on RubyCI and ci.rvm.jp.
2025-09-02test_gc.rb: Attempt to stabilize test_interrupt_in_finalizerTakashi Kokubun
This fails way too often across many environments. I don't think this test is healthy. https://github.com/ruby/ruby/actions/runs/17343611722/job/49240735401 Before we give up on it, let me see if this helps.
2025-08-18Increase timeout on slow tests (#14264)Max Bernstein
2025-08-14Increase timeout for failing testsTakashi Kokubun
https://github.com/ruby/ruby/actions/runs/16969921157/job/48103809963 https://github.com/ruby/ruby/actions/runs/16969655024/job/48102876839
2025-08-11Fix return value of setting in GC.configPeter Zhu
gc_config_set returned rb_gc_impl_config_get, but gc_config_get also added the implementation key to the return value. This caused the return value of GC.config to differ depending on whether the optional hash argument is provided or not.
2025-06-26Fix flaky TestGc#test_heaps_grow_independentlyPeter Zhu
The test sometimes fails with "Expected 2062788 to be < 2000000" because heap 0 has not been cleared yet by GC. This commit fixes it to run GC before the assertion to ensure that it does not flake.
2025-06-15Relax the criteria of flaky weak_references count testNobuyoshi Nakada
2025-06-15Simplify weak_references count test initializationNobuyoshi Nakada
Using an enumerator does not resolve the intermittent failures: 100+ failures in 10,000 iterations.
2025-06-08increase timeout for high load envKoichi Sasada
I can reproduce timeout failure with the high load machine: ``` $ RUBY_MAX_CPU=100 ruby -e '100.times{Ractor.new{loop{}}}; sleep' & $ while make test-all -o exts -o encs TESTS='ruby/gc -n /test_interrupt_in_finalizer/ --repeat-count=100'; do date; done .... Finished(93/100) tests in 0.653434s, 1.5304 tests/s, 7.6519 assertions/s. Finished(94/100) tests in 0.614422s, 1.6275 tests/s, 8.1377 assertions/s. [1/1] TestGc#test_interrupt_in_finalizer = 11.08 s 1) Timeout: TestGc#test_interrupt_in_finalizer ```
2025-06-05Add debug message to test_heaps_grow_independentlyPeter Zhu
To debug flaky failures on i686 that look like: 1) Failure: TestGc#test_heaps_grow_independently [test/ruby/test_gc.rb:704]: Expected 2061929 to be < 2000000. Notes: Merged: https://github.com/ruby/ruby/pull/13140
2025-06-05mark main Ractor objectKoichi Sasada
`RUBY_DEBUG=gc_stress ./miniruby -e0` crashes because of this marking miss. BTW, to use `RUBY_DEBUG=gc_stress` we need to specify `--enable-debug-env` configure option. This is why I couldn't repro on my environments. see c0c94ab183d0d428595ccb74ae71ee945f1afd45 Notes: Merged: https://github.com/ruby/ruby/pull/13506
2025-05-31skip failing test in a weekKoichi Sasada
``` 1) Failure: TestGc#test_gc_stress_at_startup [/home/chkbuild/chkbuild/tmp/build/20250530T213003Z/ruby/test/ruby/test_gc.rb:799]: [Bug #15784] pid 1748790 killed by SIGSEGV (signal 11) (core dumped). 1. [3/3] Assertion for "success?" | Expected #<Process::Status: pid 1748790 SIGSEGV (signal 11) (core dumped)> to be success?. ```
2025-04-28Fix nondeterministic failure in test_latest_gc_info_weak_references_countJeremy Evans
Clear the ary variable before setting it to nil. Otherwise, if the previous ary value was somewhere on the stack, all references in it would be considered live, and the wmap size would be 10000.
2025-04-07Grow GC heaps independentlyPeter Zhu
[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}, } Notes: Merged: https://github.com/ruby/ruby/pull/13061
2025-04-01[Bug #21203] Skip TestGc#test_gc_parameter_init_slots since it is flaky (#13025)Naoto Ono
https://bugs.ruby-lang.org/issues/21203 TestGc#test_gc_parameter_init_slots is a flaky test that fails intermittently. Until the issue with flakiness is resolved, I will skip it. Notes: Merged-By: ono-max <onoto1998@gmail.com>
2025-03-11Fix flaky test_latest_gc_info_need_major_byPeter Zhu
The test could flake because a major GC could be triggered due to allocation for caches or other things, which would cause the test to fail.
2025-03-10Bump tolerance for weak reference test from 1 to 2Peter Zhu
The test fails sometimes with: TestGc#test_latest_gc_info_weak_references_count [test/ruby/test_gc.rb:421]: Expected 2 to be <= 1. Notes: Merged: https://github.com/ruby/ruby/pull/12894
2025-02-13[Feature #21116] Extract RJIT as a third-party gemNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/12740
2024-11-14rb_raise when attempting to set the GC implementation nameMatt Valentine-House
Instead of silently ignoring the key, we should raise to clearly tell the user that this key is read-only. Notes: Merged: https://github.com/ruby/ruby/pull/11872
2024-11-14Expose GC.config[:implementation], to query the currently active GCMatt Valentine-House
And a default and readonly key to the GC.config hash that names the current GC implementation. This is provided by each implementation by the API function rb_gc_impl_active_gc_name Notes: Merged: https://github.com/ruby/ruby/pull/11872
2024-11-06Fix flakiness in TestGc#test_thrashing_for_young_objectsKJ Tsanaktsidis
I caught a reproduction of this test failing under rr, and was able to replay it to isolate the failure. The call to `before_stat_heap = GC.stat_heap` is itself allocating a hash, which in unlucky circumstances can result in a new page being allocated and thus `before_stats[:heap_allocated_pages]` no longer equals `after_stats[:heap_allocated_pages]`. The solution is to use the form of GC.stat/stat_heap which takes a hash as an argument, and thus needs to perform no Ruby allocations itself. Notes: Merged: https://github.com/ruby/ruby/pull/11997
2024-10-11Remove defined check for GC.config in test_gc.rbPeter Zhu
GC.config is always defined. Notes: Merged: https://github.com/ruby/ruby/pull/11867
2024-10-10Fix typo in test_gc.rb [ci skip]Peter Zhu
2024-10-03Rename size_pool -> heapMatt Valentine-House
Now that we've inlined the eden_heap into the size_pool, we should rename the size_pool to heap. So that Ruby contains multiple heaps, with different sized objects. The term heap as a collection of memory pages is more in memory management nomenclature, whereas size_pool was a name chosen out of necessity during the development of the Variable Width Allocation features of Ruby. The concept of size pools was introduced in order to facilitate different sized objects (other than the default 40 bytes). They wrapped the eden heap and the tomb heap, and some related state, and provided a reasonably simple way of duplicating all related concerns, to provide multiple pools that all shared the same structure but held different objects. Since then various changes have happend in Ruby's memory layout: * The concept of tomb heaps has been replaced by a global free pages list, with each page having it's slot size reconfigured at the point when it is resurrected * the eden heap has been inlined into the size pool itself, so that now the size pool directly controls the free_pages list, the sweeping page, the compaction cursor and the other state that was previously being managed by the eden heap. Now that there is no need for a heap wrapper, we should refer to the collection of pages containing Ruby objects as a heap again rather than a size pool Notes: Merged: https://github.com/ruby/ruby/pull/11771
2024-09-17Replace all GC.disable with EnvUtil.without_gcPeter Zhu
Notes: Merged: https://github.com/ruby/ruby/pull/11635
2024-09-09Add keys to GC.stat and fix testsPeter Zhu
This adds keys heap_empty_pages and heap_allocatable_slots to GC.stat.
2024-09-09Switch sorted list of pages in the GC to a darrayPeter Zhu
2024-09-02Fix flaky test_latest_gc_info_need_major_byPeter Zhu
It's possible for a GC to run between the calls of GC.latest_gc_info, which would cause the test to fail. We can disable GC so that GC only triggers manually.
2024-08-19Make assertions allow incremental GC when disabledPeter Zhu
When assertions are enabled, the following code triggers an assertion error: GC.disable GC.start(immediate_mark: false, immediate_sweep: false) 10_000_000.times { Object.new } This is because the GC.start ignores that the GC is disabled and will start incremental marking and lazy sweeping. But the assertions in gc_marks_continue and gc_sweep_continue assert that GC is not disabled. This commit changes it for the assertion to pass if the GC was triggered from a method. Notes: Merged: https://github.com/ruby/ruby/pull/11391
2024-08-11Fix flag test macroNobuyoshi Nakada
`RBOOL` is a macro to convert C boolean to Ruby boolean. Notes: Merged: https://github.com/ruby/ruby/pull/11351
2024-07-12Rename full_mark -> rgengc_allow_full_markMatt Valentine-House
2024-07-12Provide GC.config to disable major GC collectionsMatt Valentine-House
This feature provides a new method `GC.config` that configures internal GC configuration variables provided by an individual GC implementation. Implemented in this PR is the option `full_mark`: a boolean value that will determine whether the Ruby GC is allowed to run a major collection while the process is running. It has the following semantics This feature configures Ruby's GC to only run minor GC's. It's designed to give users relying on Out of Band GC complete control over when a major GC is run. Configuring `full_mark: false` does two main things: * Never runs a Major GC. When the heap runs out of space during a minor and when a major would traditionally be run, instead we allocate more heap pages, and mark objspace as needing a major GC. * Don't increment object ages. We don't promote objects during GC, this will cause every object to be scanned on every minor. This is an intentional trade-off between minor GC's doing more work every time, and potentially promoting objects that will then never be GC'd. The intention behind not aging objects is that users of this feature should use a preforking web server, or some other method of pre-warming the oldgen (like Nakayoshi fork)before disabling Majors. That way most objects that are going to be old will have already been promoted. This will interleave major and minor GC collections in exactly the same what that the Ruby GC runs in versions previously to this. This is the default behaviour. * This new method has the following extra semantics: - `GC.config` with no arguments returns a hash of the keys of the currently configured GC - `GC.config` with a key pair (eg. `GC.config(full_mark: true)` sets the matching config key to the corresponding value and returns the entire known config hash, including the new values. If the key does not exist, `nil` is returned * When a minor GC is run, Ruby sets an internal status flag to determine whether the next GC will be a major or a minor. When `full_mark: false` this flag is ignored and every GC will be a minor. This status flag can be accessed at `GC.latest_gc_info(:needs_major_by)`. Any value other than `nil` means that the next collection would have been a major. Thus it's possible to use this feature to check at a predetermined time, whether a major GC is necessary and run one if it is. eg. After a request has finished processing. ```ruby if GC.latest_gc_info(:needs_major_by) GC.start(full_mark: true) end ``` [Feature #20443]
2024-07-05Fix flaky test_stat_heap_allPeter Zhu
We only collect GC.stat_heap(nil, stat_heap_all) once, outside of the loop, but assert_equal could allocate objects which can cause a GC to run and cause stat_heap_all to be out-of-sync.
2024-07-03[Feature #20470] Split GC into gc_impl.cPeter Zhu
This commit splits gc.c into two files: - gc.c now only contains code not specific to Ruby GC. This includes code to mark objects (which the GC implementation may choose not to use) and wrappers for internal APIs that the implementation may need to use (e.g. locking the VM). - gc_impl.c now contains the implementation of Ruby's GC. This includes marking, sweeping, compaction, and statistics. Most importantly, gc_impl.c only uses public APIs in Ruby and a limited set of functions exposed in gc.c. This allows us to build gc_impl.c independently of Ruby and plug Ruby's GC into itself.
2024-06-07TestGc#test_thrashing_for_young_objects: extend the timeout limitYusuke Endoh
2024-03-27Revert "skip `test_gc_stress_at_startup`"Peter Zhu
This reverts commit 3680981c7b71df8c3a426164787ccefe5296bb25.
2024-03-26skip `test_gc_stress_at_startup`Koichi Sasada
(maybe) from 9cf754b the test fails on some environments: https://rubyci.s3.amazonaws.com/icc-x64/ruby-master/log/20240325T200004Z.fail.html.gz ``` 1) Failure: TestGc#test_gc_stress_at_startup [/home/chkbuild/chkbuild/tmp/build/20240325T200004Z/ruby/test/ruby/test_gc.rb:771]: [Bug #15784] pid 1087168 killed by SIGSEGV (signal 11) (core dumped). 1. [3/3] Assertion for "success?" | Expected #<Process::Status: pid 1087168 SIGSEGV (signal 11) (core dumped)> to be success?. ``` https://rubyci.s3.amazonaws.com/freebsd14/ruby-master/log/20240325T203002Z.fail.html.gz https://rubyci.s3.amazonaws.com/osx1200arm-no-yjit/ruby-master/log/20240325T195003Z.fail.html.gz https://rubyci.s3.amazonaws.com/s390x/ruby-master/log/20240325T210003Z.fail.html.gz ... so just skipt it until it works.
2024-01-06Add test case for GC.measure_total_timeRian McGuire
2023-12-19restore the stack pointer on finalizerKoichi Sasada
When error on finalizer, the exception will be ignored. To restart the code, we need to restore the stack pointer. fix [Bug #20042]
2023-12-18Fix flaky test test_stat_heapPeter Zhu
The test sometimes fails with: 1) Failure: TestGc#test_stat_heap [/tmp/ruby/src/trunk-repeat50/test/ruby/test_gc.rb:169]: Expected 33434403 to be <= 33434354.
2023-12-12Skip a GC test for RJITTakashi Kokubun
It randomly fails like this: https://github.com/ruby/ruby/actions/runs/7191443542/job/19586164973
2023-12-07Fix SEGV caused by `GC::Profiler.raw_data` (#9122)Soutaro Matsumoto
2023-10-18Loosen assertion for flaky weak references testPeter Zhu