summaryrefslogtreecommitdiff
path: root/test/ruby/test_gc_compact.rb
blob: 94acbb3a4dd84ce6acc37bd97dbdd673997063c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true
require 'test/unit'
require 'fiddle'

class TestGCCompact < Test::Unit::TestCase
  def memory_location(obj)
    (Fiddle.dlwrap(obj) >> 1)
  end

  def assert_object_ids(list)
    same_count = list.find_all { |obj|
      memory_location(obj) == obj.object_id
    }.count
    list.count - same_count
  end

  def big_list(level = 10)
    if level > 0
      big_list(level - 1)
    else
      1000.times.map {
        # try to make some empty slots by allocating an object and discarding
        Object.new
        Object.new
      } # likely next to each other
    end
  end

  # Find an object that's allocated in a slot that had a previous
  # tenant, and that tenant moved and is still alive
  def find_object_in_recycled_slot(addresses)
    new_object = nil

    100_000.times do
      new_object = Object.new
      if addresses.index memory_location(new_object)
        break
      end
    end

    new_object
  end

  def try_to_move_objects
    10.times do
      list_of_objects = big_list

      ids       = list_of_objects.map(&:object_id) # store id in map
      addresses = list_of_objects.map(&self.:memory_location)

      assert_equal ids, addresses

      # All object ids should be equal
      assert_equal 0, assert_object_ids(list_of_objects) # should be 0

      GC.verify_compaction_references(toward: :empty)

      # Some should have moved
      id_count = assert_object_ids(list_of_objects)
      skip "couldn't get objects to move" if id_count == 0
      assert_operator id_count, :>, 0

      new_ids = list_of_objects.map(&:object_id)

      # Object ids should not change after compaction
      assert_equal ids, new_ids

      new_tenant = find_object_in_recycled_slot(addresses)
      return [list_of_objects, addresses, new_tenant] if new_tenant
    end

    flunk "Couldn't get objects to move"
  end

  def test_find_collided_object
    skip "figure out how to guarantee move"

    list_of_objects, addresses, new_tenant = try_to_move_objects

    # This is the object that used to be in new_object's position
    loc = memory_location(new_tenant)
    assert loc, "should have a memory location"

    if (ENV['TRAVIS'] && RUBY_PLATFORM =~ /darwin/)
      skip "tests are failing on Travis osx / Wercker from here"
    end

    address_idx = addresses.index(loc)
    assert address_idx, "should have an address index"

    previous_tenant = list_of_objects[address_idx]
    assert previous_tenant, "should have a previous tenant"

    assert_not_equal previous_tenant.object_id, new_tenant.object_id

    # Should be able to look up object by object_id
    assert_equal new_tenant, ObjectSpace._id2ref(new_tenant.object_id)

    # Should be able to look up object by object_id
    assert_equal previous_tenant, ObjectSpace._id2ref(previous_tenant.object_id)

    int = (new_tenant.object_id >> 1)
    # These two should be the same! but they are not :(
    assert_equal int, ObjectSpace._id2ref(int.object_id)
  end

  def test_many_collisions
    list_of_objects = big_list
    ids       = list_of_objects.map(&:object_id)
    addresses = list_of_objects.map(&self.:memory_location)

    GC.verify_compaction_references(toward: :empty)

    return
    new_tenants = 10.times.map {
      find_object_in_recycled_slot(addresses)
    }

    collisions = GC.stat(:object_id_collisions)
    skip "couldn't get objects to collide" if collisions == 0
    assert_operator collisions, :>, 0
  end

  def test_complex_hash_keys
    list_of_objects = big_list
    hash = list_of_objects.hash
    GC.verify_compaction_references(toward: :empty)
    assert_equal hash, list_of_objects.hash
  end
end