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

Instance Method Summary collapse

Class Method Details

.usable?Bool

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

Returns:

  • (Bool)

    always true to indicate that this stragegy should be usable with any Ruby implementation which proivides an ObjectSpace::WeakMap.



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.

Examples:

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

Parameters:

  • obj (Object)

    an object

Returns:

  • (self)


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

#clearself

Removes all elements and returns self

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?

Note:

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.

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



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.

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.



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

Note:

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.

Parameters:

  • obj (Object)

    an object

Returns:

  • (Bool)

    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

#initializevoid

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

#sizeInteger

Returns the number of live elements in self.

Returns:

  • (Integer)

    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_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_secondary_keys.rb', line 118

def to_a
  @map.values.delete_if { |obj| DeletedEntry === obj }
end