Skip to content

Commit 84048ba

Browse files
committed
[GR-59866] Fix WeakKeyMap
PullRequest: truffleruby/4482
2 parents 1fb4e7b + 7553d93 commit 84048ba

File tree

7 files changed

+85
-6
lines changed

7 files changed

+85
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Compatibility:
1010

1111
* Implement `StringScanner#{peek_byte,scan_byte,scan_integer,named_captures}` methods (#3788, @andrykonchin).
1212
* Support String patterns in `StringScanner#{exist?,scan_until,skip_until,check_until,search_full}` methods (@andrykonchin).
13+
* Implement `ObjectSpace::WeakKeyMap` (#3681, @andrykonchin).
1314

1415
Performance:
1516

spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818

1919
it "compares keys with #eql? semantics" do
2020
map = ObjectSpace::WeakKeyMap.new
21-
map[[1.0]] = "x"
21+
key = [1.0]
22+
map[key] = "x"
2223
map[[1]].should == nil
2324
map[[1.0]].should == "x"
25+
key.should == [1.0] # keep the key alive until here to keep the map entry
2426

2527
map = ObjectSpace::WeakKeyMap.new
26-
map[[1]] = "x"
28+
key = [1]
29+
map[key] = "x"
2730
map[[1.0]].should == nil
2831
map[[1]].should == "x"
32+
key.should == [1] # keep the key alive until here to keep the map entry
2933

3034
map = ObjectSpace::WeakKeyMap.new
3135
key1, key2 = %w[a a].map(&:upcase)
@@ -39,7 +43,8 @@
3943
x.should_receive(:hash).and_return(0)
4044

4145
map = ObjectSpace::WeakKeyMap.new
42-
map['foo'] = :bar
46+
key = 'foo'
47+
map[key] = :bar
4348
map[x].should == nil
4449
end
4550

spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def should_accept(map, key, value)
4343

4444
map.getkey("a").should.equal? key
4545
map.getkey("a").should_not.frozen?
46+
47+
key.should == "a" # keep the key alive until here to keep the map entry
4648
end
4749

4850
context "a key cannot be garbage collected" do

spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
map[key1] = true
1010
map.getkey(key2).should equal(key1)
1111
map.getkey("X").should == nil
12+
13+
key1.should == "A" # keep the key alive until here to keep the map entry
14+
key2.should == "A" # keep the key alive until here to keep the map entry
1215
end
1316

1417
it "returns nil when a key cannot be garbage collected" do

spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=2>\z/
1313
map[key3] = 3
1414
map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=2>\z/
15+
16+
key1.should == "foo" # keep the key alive until here to keep the map entry
17+
key2.should == "bar" # keep the key alive until here to keep the map entry
18+
key3.should == "bar" # keep the key alive until here to keep the map entry
1519
end
1620
end
1721
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# truffleruby_primitives: true
2+
# (to nil out references to make unreachable)
3+
4+
# Copyright (c) 2020, 2025 Oracle and/or its affiliates. All rights reserved. This
5+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
6+
# redistribute it and/or modify it under the terms of the:
7+
#
8+
# Eclipse Public License version 2.0, or
9+
# GNU General Public License version 2, or
10+
# GNU Lesser General Public License version 2.1.
11+
12+
require_relative '../../ruby/spec_helper'
13+
14+
describe "ObjectSpace::WeakKeyMap" do
15+
16+
it "gets rid of unreferenced keys" do
17+
map = ObjectSpace::WeakKeyMap.new
18+
key = "a".upcase
19+
value = "x"
20+
map[key] = value
21+
key = nil
22+
23+
Primitive.gc_force
24+
25+
map[key].should == nil
26+
map.key?(key).should == false
27+
map.getkey(key).should == nil
28+
end
29+
30+
it "has iterators methods that exclude unreferenced objects" do
31+
# This spec does not pass on MRI because the garbage collector is presumably too conservative and will not get rid
32+
# of the references eagerly enough.
33+
34+
map = ObjectSpace::WeakKeyMap.new
35+
k1, k2 = %w[a b].map(&:upcase)
36+
v1, v2 = %w[x y].map(&:upcase)
37+
map[k1] = v1
38+
map[k2] = v2
39+
k2 = nil
40+
41+
Primitive.gc_force
42+
43+
map.key?(k2).should == false
44+
map[k2].should == nil
45+
46+
map.key?(k1).should == true
47+
map[k1].should == v1
48+
end
49+
50+
it "supports frozen objects" do
51+
map = ObjectSpace::WeakKeyMap.new
52+
k = "x".freeze
53+
v = "y".freeze
54+
map[k] = v
55+
Primitive.gc_force
56+
map[k].should == v
57+
end
58+
end

src/main/java/org/truffleruby/collections/ConcurrentWeakKeysMap.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,14 @@ public Value remove(Key key) {
9494
return map.remove(buildWeakReference(key));
9595
}
9696

97+
/** It's important a WeakReference that is built returns true for {@code ref.equals(ref)} even if the reference is
98+
* cleared. */
9799
protected WeakReference<Key> buildWeakReference(Key key) {
98100
return new WeakKeyReference<>(key);
99101
}
100102

103+
/** It's important a WeakReference that is built returns true for {@code ref.equals(ref)} even if the reference is
104+
* cleared. */
101105
protected WeakReference<Key> buildWeakReference(Key key, ReferenceQueue<Key> referenceQueue) {
102106
return new WeakKeyReference<>(key, referenceQueue);
103107
}
@@ -108,14 +112,16 @@ protected WeakReference<Key> buildWeakReference(Key key, ReferenceQueue<Key> ref
108112
* It is possible that the map still contains {@link WeakReference} instances whose key has been nulled out after a
109113
* call to this method (the reference not having been enqueued yet)! */
110114
protected void removeStaleEntries() {
111-
WeakKeyReference<?> ref;
112-
while ((ref = (WeakKeyReference<?>) referenceQueue.poll()) != null) {
115+
WeakReference<?> ref;
116+
while ((ref = (WeakReference<?>) referenceQueue.poll()) != null) {
113117
// Here ref.get() is null, so it will not remove a new key-value pair with the same key
114-
// as that is a different WeakKeyReference instance.
118+
// as that is a different WeakReference instance.
115119
map.remove(ref);
116120
}
117121
}
118122

123+
/** A default implementation of a key that wraps in a user-provided key. Compares keys by
124+
* {@link Object#equals(Object)} */
119125
protected static final class WeakKeyReference<Key> extends WeakReference<Key> {
120126
private final int hashCode;
121127

0 commit comments

Comments
 (0)