1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_gc.rb
Aaron Patterson efcdf68e64 Guard callinfo
Callinfo was being written in to an array and the GC would not see the
reference on the stack.  `new_insn_send` creates a new callinfo object,
then it calls `new_insn_core`.  `new_insn_core` allocates a new INSN
linked list item, which can end up calling `xmalloc` which will trigger
a GC:

  70cd351c7c/compile.c (L968-L969)

Since the callinfo object isn't on the stack, the GC won't see it, and
it can get collected.  This patch just refactors `new_insn_send` to keep
the object on the stack

Co-authored-by: John Hawthorn <john@hawthorn.email>
2021-01-13 16:13:53 -08:00

497 lines
14 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
class TestGc < Test::Unit::TestCase
class S
def initialize(a)
@a = a
end
end
def test_gc
prev_stress = GC.stress
GC.stress = false
assert_nothing_raised do
tmp = nil
1.upto(10000) {
tmp = [0,1,2,3,4,5,6,7,8,9]
}
tmp
end
l=nil
100000.times {
l = S.new(l)
}
GC.start
assert true # reach here or dumps core
l = []
100000.times {
l.push([l])
}
GC.start
assert true # reach here or dumps core
GC.stress = prev_stress
end
def use_rgengc?
GC::OPTS.include? 'USE_RGENGC'.freeze
end
def test_enable_disable
GC.enable
assert_equal(false, GC.enable)
assert_equal(false, GC.disable)
assert_equal(true, GC.disable)
assert_equal(true, GC.disable)
assert_nil(GC.start)
assert_equal(true, GC.enable)
assert_equal(false, GC.enable)
ensure
GC.enable
end
def test_start_full_mark
return unless use_rgengc?
skip 'stress' if GC.stress
3.times { GC.start } # full mark and next time it should be minor mark
GC.start(full_mark: false)
assert_nil GC.latest_gc_info(:major_by)
GC.start(full_mark: true)
assert_not_nil GC.latest_gc_info(:major_by)
end
def test_start_immediate_sweep
skip 'stress' if GC.stress
GC.start(immediate_sweep: false)
assert_equal false, GC.latest_gc_info(:immediate_sweep)
GC.start(immediate_sweep: true)
assert_equal true, GC.latest_gc_info(:immediate_sweep)
end
def test_count
c = GC.count
GC.start
assert_operator(c, :<, GC.count)
end
def test_stat
res = GC.stat
assert_equal(false, res.empty?)
assert_kind_of(Integer, res[:count])
arg = Hash.new
res = GC.stat(arg)
assert_equal(arg, res)
assert_equal(false, res.empty?)
assert_kind_of(Integer, res[:count])
stat, count = {}, {}
2.times{ # to ignore const cache imemo creation
GC.start
GC.stat(stat)
ObjectSpace.count_objects(count)
# repeat same methods invocation for cache object creation.
GC.stat(stat)
ObjectSpace.count_objects(count)
}
assert_equal(count[:TOTAL]-count[:FREE], stat[:heap_live_slots])
assert_equal(count[:FREE], stat[:heap_free_slots])
# measure again without GC.start
2.times{ # to ignore const cache imemo creation
1000.times{ "a" + "b" }
GC.stat(stat)
ObjectSpace.count_objects(count)
}
assert_equal(count[:FREE], stat[:heap_free_slots])
end
def test_stat_argument
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.stat(:"\u{30eb 30d3 30fc}")}
end
def test_stat_single
skip 'stress' if GC.stress
stat = GC.stat
assert_equal stat[:count], GC.stat(:count)
assert_raise(ArgumentError){ GC.stat(:invalid) }
end
def test_stat_constraints
skip 'stress' if GC.stress
stat = GC.stat
assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages]
assert_operator stat[:heap_sorted_length], :>=, stat[:heap_eden_pages] + stat[:heap_allocatable_pages], "stat is: " + stat.inspect
assert_equal stat[:heap_available_slots], stat[:heap_live_slots] + stat[:heap_free_slots] + stat[:heap_final_slots]
assert_equal stat[:heap_live_slots], stat[:total_allocated_objects] - stat[:total_freed_objects] - stat[:heap_final_slots]
assert_equal stat[:heap_allocated_pages], stat[:heap_eden_pages] + stat[:heap_tomb_pages]
if use_rgengc?
assert_equal stat[:count], stat[:major_gc_count] + stat[:minor_gc_count]
end
end
def test_latest_gc_info
skip 'stress' if GC.stress
assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
GC.start
count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_pages) * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
count.times{ "a" + "b" }
assert_equal :newobj, GC.latest_gc_info[:gc_by]
eom
GC.latest_gc_info(h = {}) # allocate hash and rehearsal
GC.start
GC.start
GC.start
GC.latest_gc_info(h)
assert_equal :force, h[:major_by] if use_rgengc?
assert_equal :method, h[:gc_by]
assert_equal true, h[:immediate_sweep]
GC.stress = true
assert_equal :force, GC.latest_gc_info[:major_by]
ensure
GC.stress = false
end
def test_latest_gc_info_argument
info = {}
GC.latest_gc_info(info)
assert_not_empty info
assert_equal info[:gc_by], GC.latest_gc_info(:gc_by)
assert_raise(ArgumentError){ GC.latest_gc_info(:invalid) }
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.latest_gc_info(:"\u{30eb 30d3 30fc}")}
end
def test_stress_compile_send
assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "")
GC.stress = true
begin
eval("A::B.c(1, 1, d: 234)")
rescue
end
EOS
end
def test_singleton_method
assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:42832]")
GC.stress = true
10.times do
obj = Object.new
def obj.foo() end
def obj.bar() raise "obj.foo is called, but this is obj.bar" end
obj.foo
end
EOS
end
def test_singleton_method_added
assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:44436]")
class BasicObject
undef singleton_method_added
def singleton_method_added(mid)
raise
end
end
b = proc {}
class << b; end
b.clone rescue nil
GC.start
EOS
end
def test_gc_parameter
env = {
"RUBY_GC_MALLOC_LIMIT" => "60000000",
"RUBY_GC_HEAP_INIT_SLOTS" => "100000"
}
assert_normal_exit("exit", "[ruby-core:39777]", :child_env => env)
env = {
"RUBYOPT" => "",
"RUBY_GC_HEAP_INIT_SLOTS" => "100000"
}
assert_in_out_err([env, "-e", "exit"], "", [], [], "[ruby-core:39795]")
assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [], "[ruby-core:39795]")
assert_in_out_err([env, "-W1", "-e", "exit"], "", [], [], "[ruby-core:39795]")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_INIT_SLOTS=100000/, "[ruby-core:39795]")
env = {
"RUBY_GC_HEAP_GROWTH_FACTOR" => "2.0",
"RUBY_GC_HEAP_GROWTH_MAX_SLOTS" => "10000"
}
assert_normal_exit("exit", "", :child_env => env)
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_FACTOR=2.0/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_MAX_SLOTS=10000/, "[ruby-core:57928]")
env = {
"RUBY_GC_HEAP_INIT_SLOTS" => "100000",
"RUBY_GC_HEAP_FREE_SLOTS" => "10000",
"RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.9",
}
assert_normal_exit("exit", "", :child_env => env)
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=0\.9/, "")
# always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0
assert_in_out_err([env, "-e", "1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "") if use_rgengc?
env = {
"RUBY_GC_MALLOC_LIMIT" => "60000000",
"RUBY_GC_MALLOC_LIMIT_MAX" => "160000000",
"RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR" => "2.0"
}
assert_normal_exit("exit", "", :child_env => env)
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT=6000000/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT_MAX=16000000/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=2.0/, "")
if use_rgengc?
env = {
"RUBY_GC_OLDMALLOC_LIMIT" => "60000000",
"RUBY_GC_OLDMALLOC_LIMIT_MAX" => "160000000",
"RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR" => "2.0"
}
assert_normal_exit("exit", "", :child_env => env)
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT=6000000/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_MAX=16000000/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=2.0/, "")
end
end
def test_profiler_enabled
GC::Profiler.enable
assert_equal(true, GC::Profiler.enabled?)
GC::Profiler.disable
assert_equal(false, GC::Profiler.enabled?)
ensure
GC::Profiler.disable
end
def test_profiler_clear
skip "for now"
assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30
GC::Profiler.enable
GC.start
assert_equal(1, GC::Profiler.raw_data.size)
GC::Profiler.clear
assert_equal(0, GC::Profiler.raw_data.size)
200.times{ GC.start }
assert_equal(200, GC::Profiler.raw_data.size)
GC::Profiler.clear
assert_equal(0, GC::Profiler.raw_data.size)
eom
end
def test_profiler_total_time
GC::Profiler.enable
GC::Profiler.clear
GC.start
assert_operator(GC::Profiler.total_time, :>=, 0)
ensure
GC::Profiler.disable
end
def test_finalizing_main_thread
assert_in_out_err(%w[--disable-gems], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]")
ObjectSpace.define_finalizer(Thread.main) { p 'finalize' }
EOS
end
def test_expand_heap
assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
GC.start
base_length = GC.stat[:heap_eden_pages]
(base_length * 500).times{ 'a' }
GC.start
base_length = GC.stat[:heap_eden_pages]
(base_length * 500).times{ 'a' }
GC.start
assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r,
"invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})"
a = []
(base_length * 500).times{ a << 'a'; nil }
GC.start
assert_operator base_length, :<, GC.stat[:heap_eden_pages] + 1
eom
end
def test_gc_internals
assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
assert_not_nil GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
end
def test_sweep_in_finalizer
bug9205 = '[ruby-core:58833] [Bug #9205]'
2.times do
assert_ruby_status([], <<-'end;', bug9205, timeout: 120)
raise_proc = proc do |id|
GC.start
end
1000.times do
ObjectSpace.define_finalizer(Object.new, raise_proc)
end
end;
end
end
def test_exception_in_finalizer
bug9168 = '[ruby-core:58652] [Bug #9168]'
assert_normal_exit(<<-'end;', bug9168, encoding: Encoding::ASCII_8BIT)
raise_proc = proc {raise}
10000.times do
ObjectSpace.define_finalizer(Object.new, raise_proc)
Thread.handle_interrupt(RuntimeError => :immediate) {break}
Thread.handle_interrupt(RuntimeError => :on_blocking) {break}
Thread.handle_interrupt(RuntimeError => :never) {break}
end
end;
end
def test_interrupt_in_finalizer
bug10595 = '[ruby-core:66825] [Bug #10595]'
src = <<-'end;'
Signal.trap(:INT, 'DEFAULT')
pid = $$
Thread.start do
10.times {
sleep 0.1
Process.kill("INT", pid) rescue break
}
end
f = proc {1000.times {}}
loop do
ObjectSpace.define_finalizer(Object.new, f)
end
end;
out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV) do |*result|
break result
end
unless /mswin|mingw/ =~ RUBY_PLATFORM
assert_equal("INT", Signal.signame(status.termsig), bug10595)
end
assert_match(/Interrupt/, err.first, proc {err.join("\n")})
assert_empty(out)
end
def test_finalizer_passed_object_id
assert_in_out_err(%w[--disable-gems], <<-EOS, ["true"], [])
o = Object.new
obj_id = o.object_id
ObjectSpace.define_finalizer(o, ->(id){ p id == obj_id })
EOS
end
def test_verify_internal_consistency
assert_nil(GC.verify_internal_consistency)
end
def test_gc_stress_on_realloc
assert_normal_exit(<<-'end;', '[Bug #9859]')
class C
def initialize
@a = nil
@b = nil
@c = nil
@d = nil
@e = nil
@f = nil
end
end
GC.stress = true
C.new
end;
end
def test_gc_stress_at_startup
assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 60)
end
def test_gc_disabled_start
begin
disabled = GC.disable
c = GC.count
GC.start
assert_equal 1, GC.count - c
ensure
GC.enable unless disabled
end
end
def test_vm_object
assert_normal_exit <<-'end', '[Bug #12583]'
ObjectSpace.each_object{|o| o.singleton_class rescue 0}
ObjectSpace.each_object{|o| case o when Module then o.instance_methods end}
end
end
def test_exception_in_finalizer_procs
result = []
c1 = proc do
result << :c1
raise
end
c2 = proc do
result << :c2
raise
end
tap {
tap {
obj = Object.new
ObjectSpace.define_finalizer(obj, c1)
ObjectSpace.define_finalizer(obj, c2)
obj = nil
}
}
GC.start
skip "finalizers did not get run" if result.empty?
assert_equal([:c1, :c2], result)
end
def test_exception_in_finalizer_method
@result = []
def self.c1(x)
@result << :c1
raise
end
def self.c2(x)
@result << :c2
raise
end
tap {
tap {
obj = Object.new
ObjectSpace.define_finalizer(obj, method(:c1))
ObjectSpace.define_finalizer(obj, method(:c2))
obj = nil
}
}
GC.start
skip "finalizers did not get run" if @result.empty?
assert_equal([:c1, :c2], @result)
end
def test_object_ids_never_repeat
GC.start
a = 1000.times.map { Object.new.object_id }
GC.start
b = 1000.times.map { Object.new.object_id }
assert_empty(a & b)
end
end