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
|