Module: Weak::Set::StrongKeys
- Defined in:
- lib/weak/set/strong_keys.rb
Overview
This Weak::Set strategy targets JRuby >= 9.4.6.0 and TruffleRuby >= 22. Older versions require additional indirections implemented in StrongSecondaryKeys:
The ‘ObjectSpace::WeakMap` on JRuby and TruffleRuby has 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.
As a workaround, we use the element’s object_id as a key. Being an ‘Integer`, the object_id is generally is not garbage collected anyway but allows to uniquely identity the object.
The ‘ObjectSpace::WeakMap` class does not allow to explicitly delete entries. We emulate this by setting the garbage-collectible value of a deleted entry to a simple new object. This value will be garbage collected on the next GC run which will then remove the entry. When accessing elements, we delete and filter out these recently deleted entries.
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 returns `self` 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 in `self`, `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 returns `self`.
-
#size ⇒ Integer
The number of live elements in ‘self`.
-
#to_a ⇒ Array
The live elements contained in ‘self` as an `Array`.
Class Method Details
.usable? ⇒ Bool
Checks if this strategy is usable for the current Ruby version.
40 41 42 43 44 45 46 47 |
# File 'lib/weak/set/strong_keys.rb', line 40 def self.usable? case RUBY_ENGINE when "ruby", "truffleruby" true when "jruby" Gem::Version.new(RUBY_ENGINE_VERSION) >= Gem::Version.new("9.4.6.0") end 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.
50 51 52 53 |
# File 'lib/weak/set/strong_keys.rb', line 50 def add(obj) @map[obj.__id__] = obj self end |
#clear ⇒ self
Removes all elements and returns ‘self`
56 57 58 59 |
# File 'lib/weak/set/strong_keys.rb', line 56 def clear @map = ObjectSpace::WeakMap.new 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`.
62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/weak/set/strong_keys.rb', line 62 def delete?(obj) key = obj.__id__ return unless @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 # temporary DeletedEntry object. As we do not keep any strong reference # to this object, this will cause the key/value entry to vanish from the # `Objectpace::WeakMap when the DeletedEntry object is eventually # garbage collected. @map[key] = DeletedEntry.new self 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.
77 78 79 80 81 82 83 84 |
# File 'lib/weak/set/strong_keys.rb', line 77 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.
87 88 89 90 |
# File 'lib/weak/set/strong_keys.rb', line 87 def include?(obj) key = obj.__id__ !!(@map.key?(key) && @map[key].equal?(obj)) 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.
93 94 95 |
# File 'lib/weak/set/strong_keys.rb', line 93 def prune self end |
#replace(enum) ⇒ self
Replaces the contents of ‘self` with the contents of the given enumerable object and returns `self`.
98 99 100 101 102 103 104 105 106 |
# File 'lib/weak/set/strong_keys.rb', line 98 def replace(enum) map = ObjectSpace::WeakMap.new do_with_enum(enum) do |obj| map[obj.__id__] = obj end @map = map self end |
#size ⇒ Integer
Returns the number of live elements in ‘self`.
109 110 111 112 113 114 115 |
# File 'lib/weak/set/strong_keys.rb', line 109 def size # Compared to using `ObjectSpace::WeakMap#each_value` like we do in # `Weak::Set::WeakKeys`, this version is # * ~12% faster on JRuby >= 9.4.6.0 # * sam-ish on TruffleRuby 24 with a slight advantage to this version @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_keys.rb', line 118 def to_a @map.values.delete_if { |obj| DeletedEntry === obj } end |