summaryrefslogtreecommitdiff
path: root/test/ruby
diff options
context:
space:
mode:
authorMatt Valentine-House <[email protected]>2024-07-04 15:21:09 +0100
committerMatt Valentine-House <[email protected]>2024-07-12 14:43:33 +0100
commitf543c68e1ce4abaafd535a4917129e55f89ae8f7 (patch)
tree8b1e319726f22d7033aca0979807da9cb3904cda /test/ruby
parent00d0ddd48aceb36326bd18d5a237071adfb69525 (diff)
Provide GC.config to disable major GC collections
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]
Diffstat (limited to 'test/ruby')
-rw-r--r--test/ruby/test_gc.rb75
1 files changed, 75 insertions, 0 deletions
diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb
index 25625c41cb..4ee8a20a3c 100644
--- a/test/ruby/test_gc.rb
+++ b/test/ruby/test_gc.rb
@@ -52,6 +52,81 @@ class TestGc < Test::Unit::TestCase
GC.enable
end
+ def test_gc_config_full_mark_by_default
+ omit "unsupoported platform/GC" unless defined?(GC.config)
+
+ config = GC.config
+ assert_not_empty(config)
+ assert_true(config[:full_mark])
+ end
+
+ def test_gc_config_invalid_args
+ omit "unsupoported platform/GC" unless defined?(GC.config)
+
+ assert_raise(ArgumentError) { GC.config(0) }
+ end
+
+ def test_gc_config_setting_returns_updated_config_hash
+ omit "unsupoported platform/GC" unless defined?(GC.config)
+
+ old_value = GC.config[:full_mark]
+ assert_true(old_value)
+
+ new_value = GC.config(full_mark: false)[:full_mark]
+ assert_false(new_value)
+ ensure
+ GC.config(full_mark: true)
+ GC.start
+ end
+
+ def test_gc_config_setting_returns_nil_for_missing_keys
+ omit "unsupoported platform/GC" unless defined?(GC.config)
+
+ missing_value = GC.config(no_such_key: true)[:no_such_key]
+ assert_nil(missing_value)
+ ensure
+ GC.config(full_mark: true)
+ GC.start
+ end
+
+ def test_gc_config_disable_major
+ omit "unsupoported platform/GC" unless defined?(GC.config)
+
+ GC.enable
+ GC.start
+
+ GC.config(full_mark: false)
+ major_count = GC.stat[:major_gc_count]
+ minor_count = GC.stat[:minor_gc_count]
+
+ arr = []
+ (GC.stat_heap[0][:heap_eden_slots] * 2).times do
+ arr << Object.new
+ Object.new
+ end
+
+ assert_equal(major_count, GC.stat[:major_gc_count])
+ assert_operator(minor_count, :<=, GC.stat[:minor_gc_count])
+ assert_nil(GC.start)
+ ensure
+ GC.config(full_mark: true)
+ GC.start
+ end
+
+ def test_gc_config_disable_major_gc_start_always_works
+ omit "unsupoported platform/GC" unless defined?(GC.config)
+
+ GC.config(full_mark: false)
+
+ major_count = GC.stat[:major_gc_count]
+ GC.start
+
+ assert_operator(major_count, :<, GC.stat[:major_gc_count])
+ ensure
+ GC.config(full_mark: true)
+ GC.start
+ end
+
def test_start_full_mark
return unless use_rgengc?
omit 'stress' if GC.stress