Module: Weak::Set::StrongSecondaryKeys
- Defined in:
- lib/weak/set/strong_secondary_keys.rb
Overview
This Weak::Set strategy targets JRuby < 9.4.6.0.
These JRuby versions have a similar ObjectSpace::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 an ObjectSpace::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 ObjectSpace::WeakMap
.
Being a regular Hash, the keys and values of the secondary key map are not
automatically garbage collected as elements in the ObjectSpace::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 ObjectSpace::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. -
#prune ⇒ self
Cleanup data structures from the set to remove data associated with deleted or garbage collected elements.
-
#replace(enum) ⇒ self
Replaces the contents of
self
with the contents of the given enumerable object and returnsself
. -
#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.
50 51 52 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 50 def self.usable? true end |
Instance Method Details
#add(obj) ⇒ self
Adds the given object to the weak set and return self
. Use Weak::Set#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.
55 56 57 58 59 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 55 def add(obj) key = @key_map[obj.__id__] ||= Object.new.freeze @map[key] = obj self end |
#clear ⇒ self
Removes all elements and returns self
62 63 64 65 66 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 62 def clear @map = ObjectSpace::WeakMap.new @key_map = {} self end |
#delete?(obj) ⇒ self?
Weak::Set 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
.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 69 def delete?(obj) # When deleting, we still retain the key to avoid having to re-create it # when `obj` is re-added to the {Weak::Set} 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 `ObjectSpace::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 `ObjectSpace::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.
88 89 90 91 92 93 94 95 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 88 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
Weak::Set 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.
98 99 100 101 102 103 104 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 98 def include?(obj) key = @key_map[obj.__id__] value = !!(key && @map.key?(key) && @map[key].equal?(obj)) auto_prune value end |
#prune ⇒ self
Cleanup data structures from the set to remove data associated with deleted or garbage collected elements. This method may be called automatically for some Weak::Set operations.
107 108 109 110 111 112 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 107 def prune @key_map.each do |id, key| @key_map.delete(id) unless @map.key?(key) end self end |
#replace(enum) ⇒ self
Replaces the contents of self
with the contents of the given
enumerable object and returns self
.
115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 115 def replace(enum) map = ObjectSpace::WeakMap.new key_map = {} do_with_enum(enum) do |obj| key = key_map[obj.__id__] ||= Object.new.freeze map[key] = obj end @map = map @key_map = key_map self end |
#size ⇒ Integer
Returns the number of live elements in self
.
129 130 131 132 133 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 129 def size # Compared to using `ObjectSpace::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
.
136 137 138 |
# File 'lib/weak/set/strong_secondary_keys.rb', line 136 def to_a @map.values.delete_if { |obj| DeletedEntry === obj } end |