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

Instance Method Summary collapse

Class Method Details

.usable?Bool

Checks if this strategy is usable for the current Ruby version.

Returns:

  • (Bool)

    truethy for Ruby, TruffleRuby and modern JRuby, falsey otherwise


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.

Examples:

Weak::Set[1, 2].add(3)                #=> #<Weak::Set {1, 2, 3}>
Weak::Set[1, 2].add([3, 4])           #=> #<Weak::Set {1, 2, [3, 4]}>
Weak::Set[1, 2].add(2)                #=> #<Weak::Set {1, 2}>

Parameters:

  • obj (Object)

    an object

Returns:

  • (self)

50
51
52
53
# File 'lib/weak/set/strong_keys.rb', line 50

def add(obj)
  @map[obj.__id__] = obj
  self
end

#clearself

Removes all elements and returns ‘self`

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?

Note:

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`.

Parameters:

  • obj (Object)

Returns:

  • (self, nil)

    ‘self` if the given object was deleted from the set or `nil` if the object was not part of the set


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.

Yields:

  • (element)

    calls the given block once for each element in ‘self`

Yield Parameters:

  • element (Object)

    the yielded value

Returns:

  • (self, Enumerator)

    ‘self` if a block was given or an `Enumerator` if no block was given.


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

Note:

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.

Parameters:

  • obj (Object)

    an object

Returns:

  • (Bool)

    ‘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

#pruneself

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.

Returns:

  • (self)

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`.

Examples:

set = Weak::Set[1, :c, :s]        #=> #<Weak::Set {1, :c, :s}>
set.replace([1, 2])               #=> #<Weak::Set {1, 2}>
set                               #=> #<Weak::Set {1, 2}>

Parameters:

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

#sizeInteger

Returns the number of live elements in ‘self`.

Returns:

  • (Integer)

    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_aArray

Note:

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`.

Returns:

  • (Array)

    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