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

Instance Method Summary collapse

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.

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}>


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

#clearself

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?

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.



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.

Yields:

  • (element)

    calls the given block once for each element in self

Yield Parameters:

  • element (Object)

    the yielded value



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

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.



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

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



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.

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}>


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

#sizeInteger



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



136
137
138
# File 'lib/weak/set/strong_secondary_keys.rb', line 136

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