Skip to content

Commit 58d8c52

Browse files
authored
Fix memory leak on RDF::Util::Cache (#438)
* Add cache tests * Fix cache finalizers
1 parent ac80b2f commit 58d8c52

2 files changed

Lines changed: 87 additions & 1 deletion

File tree

lib/rdf/util/cache.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def []=(key, value)
8585
id = value.__id__
8686
@cache[key] = id
8787
@index[id] = key
88-
ObjectSpace.define_finalizer(value, proc {|id| @cache.delete(@index.delete(id))})
88+
ObjectSpace.define_finalizer(value, finalizer_proc)
8989
end
9090
value
9191
end
@@ -100,6 +100,12 @@ def delete(key)
100100
@cache.delete(key)
101101
@index.delete(id) if id
102102
end
103+
104+
private
105+
106+
def finalizer_proc
107+
proc { |id| @cache.delete(@index.delete(id)) }
108+
end
103109
end # ObjectSpaceCache
104110

105111
##

spec/util/cache_spec.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
require_relative '../spec_helper'
2+
3+
describe RDF::Util::Cache do
4+
subject(:cache) do
5+
described_class.new(10)
6+
end
7+
8+
describe '#capacity' do
9+
it 'returns the cache size' do
10+
expect(cache.capacity).to eq 10
11+
end
12+
end
13+
14+
describe '#size' do
15+
it 'returns the cache size' do
16+
cache[:key] = {}
17+
expect(cache.size).to eq 1
18+
end
19+
end
20+
21+
describe '#[]' do
22+
it 'returns the value' do
23+
cache[:key] = {}
24+
expect(cache[:key]).to eq({})
25+
end
26+
end
27+
28+
describe '#[]=' do
29+
context 'when the cache is not full' do
30+
it 'stores the value' do
31+
expect {
32+
cache[:key] = {}
33+
}.to change(cache, :size).by(1)
34+
end
35+
36+
it 'returns the value' do
37+
expect(cache[:key] = {}).to eq({})
38+
end
39+
end
40+
41+
context 'when the cache is full' do
42+
before do
43+
10.times { |i| cache[i] = {} }
44+
end
45+
46+
it 'does not store the value' do
47+
expect {
48+
cache[:key] = {}
49+
}.not_to change(cache, :size)
50+
end
51+
52+
it 'returns the value' do
53+
expect(cache[:key] = {}).to eq({})
54+
end
55+
end
56+
end
57+
58+
context 'when the GC starts' do
59+
before do
60+
100.times { |i| cache[i] = {}; nil }
61+
end
62+
63+
# Sometimes the last reference is not gc
64+
it 'cleans the unused references' do
65+
expect {
66+
GC.start
67+
}.to change(cache, :size).by_at_most(-9)
68+
end
69+
end
70+
71+
describe '#delete' do
72+
before do
73+
cache[:key] = {}
74+
end
75+
76+
it 'delete the value' do
77+
expect { cache.delete(:key) }.to change(cache, :size).to(0)
78+
end
79+
end
80+
end

0 commit comments

Comments
 (0)