Module: WeakSet::StrongSecondaryKeys
- Defined in:
- lib/weak_set/strong_secondary_keys.rb
Overview
This WeakSet strategy targets JRuby < 9.4.6.0.
These JRuby versions have a similar WeakMap as newer JRubies with strong keys and weak values. Thus, only the value object can be garbage collected to remove the entry while the key defines a strong object reference which prevents the key object from being garbage collected.
Additionally, Integer values (including object_ids) can have multiple different object representations in JRuby, making them not strictly equal. Thus, we can not use the object_id as a key in a WeakMap as we do in StrongKeys for newer JRuby versions.
As a workaround we use a more indirect implementation with a secondary lookup table for the keys which is inspired by Google::Protobuf::Internal::LegacyObjectCache
This secondary key map is a regular Hash which stores a mapping from an element's object_id to a separate Object which in turn is used as the key in the WeakMap.
Being a regular Hash, the keys and values of the secondary key map are not automatically garbage collected as elements in the WeakMap are removed. However, its entries are rather cheap with Integer keys and "empty" objects as values. We perform manual garbage collection of this secondary key map during #include? if required.
As this strategy is the most conservative with the fewest requirements to the WeakMap, we use it as a default or fallback if there is no better strategy.
Class Method Summary collapse
-
.usable? ⇒ Bool
Checks if this strategy is usable for the current Ruby version.
Instance Method Summary collapse
-
#add(obj) ⇒ self
Adds the given object to the weak set and return
self
. -
#clear ⇒ self
Removes all elements and returns
self
. -
#delete?(obj) ⇒ self?
Deletes the given object from
self
and returnsself
if it was present in the set. -
#each {|element| ... } ⇒ self, Enumerator
Calls the given block once for each live element in
self
, passing that element as a parameter. -
#include?(obj) ⇒ Bool
true
if the given object is included inself
,false
otherwise. -
#initialize ⇒ void
Initialize the weak map.
-
#size ⇒ Integer
The number of live elements in
self
. -
#to_a ⇒ Array
The live elements contained in
self
as anArray
.
Class Method Details
.usable? ⇒ Bool
Checks if this strategy is usable for the current Ruby version.
48 49 50 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 48 def self.usable? true end |
Instance Method Details
#add(obj) ⇒ self
Adds the given object to the weak set and return self
. Use WeakSet#merge to
add many elements at once.
In contrast to other "regular" objects, we will not retain a strong reference to the added object. Unless some other live objects still references the object, it will eventually be garbage-collected.
60 61 62 63 64 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 60 def add(obj) key = @key_map[obj.__id__] ||= Object.new.freeze @map[key] = obj self end |
#clear ⇒ self
Removes all elements and returns self
67 68 69 70 71 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 67 def clear @map = ObjectSpace::WeakMap.new @key_map.clear self end |
#delete?(obj) ⇒ self?
WeakSet does not test member equality with ==
or eql?
. Instead,
it always checks strict object equality, so that, e.g., different
strings are not considered equal, even if they may contain the same
string content.
Deletes the given object from self
and returns self
if it was present
in the set. If the object was not in the set, returns nil
.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 74 def delete?(obj) # When deleting, we still retain the key to avoid having to re-create it # when `obj` is re-added to the WeakSet again before the next GC. # # If `obj` is not added again, the key is eventually removed with our next # GC of the `@key_map`. key = @key_map[obj.__id__] if key && @map.key?(key) && @map[key].equal?(obj) # If there is a valid value in the WeakMap (with a strong object_id # key), we replace the value of the strong key with a DeletedEntry # marker object. This will cause the key/value entry to vanish from the # WeakMap when the DeletedEntry object is eventually garbage collected. @map[key] = DeletedEntry.new self end end |
#each {|element| ... } ⇒ self, Enumerator
Calls the given block once for each live element in self
, passing that
element as a parameter. Returns the weak set itself.
If no block is given, an Enumerator
is returned instead.
92 93 94 95 96 97 98 99 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 92 def each return enum_for(__method__) { size } unless block_given? @map.values.each do |obj| yield(obj) unless DeletedEntry === obj end self end |
#include?(obj) ⇒ Bool
WeakSet does not test member equality with ==
or eql?
. Instead,
it always checks strict object equality, so that, e.g., different
strings are not considered equal, even if they may contain the same
string content.
Returns true
if the given object is included in self
, false
otherwise.
102 103 104 105 106 107 108 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 102 def include?(obj) key = @key_map[obj.__id__] value = !!(key && @map.key?(key) && @map[key].equal?(obj)) collect_garbage value end |
#initialize ⇒ void
Initialize the weak map
54 55 56 57 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 54 def initialize @map = ObjectSpace::WeakMap.new @key_map = {} end |
#size ⇒ Integer
Returns the number of live elements in self
.
111 112 113 114 115 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 111 def size # Compared to using WeakMap#each_value like we do in WeakKeys, this # version is ~12% faster on JRuby < 9.4.6.0 @map.values.delete_if { |obj| DeletedEntry === obj }.size end |
#to_a ⇒ Array
The order of elements on the returned Array
is non-deterministic.
We do not preserve preserve insertion order.
Returns the live elements contained in self
as an Array
.
118 119 120 |
# File 'lib/weak_set/strong_secondary_keys.rb', line 118 def to_a @map.values.delete_if { |obj| DeletedEntry === obj } end |