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.

Returns:

  • (Bool)

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



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

Parameters:

  • obj (Object)

    an object

Returns:

  • (self)


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

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.

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



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

Returns:

  • (self, Enumerator)

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



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.

Parameters:

  • obj (Object)

    an object

Returns:

  • (Bool)

    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.

Returns:

  • (self)


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

Parameters:

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

#sizeInteger

Returns the number of live elements in self.

Returns:

  • (Integer)

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



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

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