Module: Weak::Map::StrongKeys

Includes:
AbstractStrongKeys
Defined in:
lib/weak/map/strong_keys.rb

Overview

This Weak::Map 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 in an ObjectSpace::WeakMap 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.

As we need to store both a key and value object for each key-value pair in our Weak::Map, we use two separate ObjectSpace::WeakMap objects for storing those. This allows keys and values to be independently garbage collected. When accessing a logical key in the Weak::Map, we need to manually check if we have a valid entry for both the stored key and the associated value.

The ObjectSpace::WeakMap 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

Methods included from AbstractStrongKeys

#keys, #size, #values

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



51
52
53
54
55
56
57
58
# File 'lib/weak/map/strong_keys.rb', line 51

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

#[](key) ⇒ Object

Note:

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 the value associated with the given key, if found. If key is not found, returns the default value, i.e. the value returned by the default proc (if defined) or the default value (which is initially nil.).

Parameters:

  • key (Object)

    the key for the requested value

Returns:

  • (Object)

    the value associated with the given key, if found. If key is not found, returns the default value, i.e. the value returned by the default proc (if defined) or the default value (which is initially nil.)



61
62
63
# File 'lib/weak/map/strong_keys.rb', line 61

def [](key)
  _get(key.__id__) { _default(key) }
end

#[]=(key, value) ⇒ Object

Note:

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.

Associates the given value with the given key; returns value. If the given key exists, replaces its value with the given value.

Parameters:

  • key (Object)

    the key for the set key-value pair

  • value (Object)

    the value of the set key-value pair

Returns:

  • (Object)

    the given value



66
67
68
69
70
71
72
# File 'lib/weak/map/strong_keys.rb', line 66

def []=(key, value)
  id = key.__id__

  @keys[id] = key.nil? ? NIL : key
  @values[id] = value.nil? ? NIL : value
  value
end

#clearself

Removes all elements and returns self

Returns:

  • (self)


75
76
77
78
79
# File 'lib/weak/map/strong_keys.rb', line 75

def clear
  @keys = ObjectSpace::WeakMap.new
  @values = ObjectSpace::WeakMap.new
  self
end

#delete(key) {|key| ... } ⇒ Object?

Note:

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 key-value pair and returns the value from self whose key is equal to key. If the key is not found, it returns nil. If the optional block is given and the key is not found, pass in the key and return the result of the block.

Parameters:

  • key (Object)

    the key to delete

Yields:

  • (key)

Yield Parameters:

  • key (Object)

    the given key if it was not part of the map

Returns:

  • (Object, nil)

    the value associated with the given key, or the result of the optional block if given the key was not found, or nil if the key was not found and no block was given.



82
83
84
# File 'lib/weak/map/strong_keys.rb', line 82

def delete(key)
  _delete(key.__id__) { yield(key) if block_given? }
end

#each_key {|key| ... } ⇒ self, Enumerator

Calls the given block once for each live key in self, passing the key as a parameter. Returns the weak map itself.

If no block is given, an Enumerator is returned instead.

Yields:

  • (key)

    calls the given block once for each key in self

Yield Parameters:

  • key (Object)

    the key of the current key-value pair

Returns:

  • (self, Enumerator)

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



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/weak/map/strong_keys.rb', line 87

def each_key
  return enum_for(__method__) { size } unless block_given?

  @keys.values.each do |raw_key|
    next if DeletedEntry === raw_key

    key = value!(raw_key)
    id = key.__id__
    if missing?(@values[id])
      @keys[id] = DeletedEntry.new
    else
      yield key
    end
  end

  self
end

#each_pair {|key, value| ... } ⇒ self, Enumerator

Calls the given block once for each live key in self, passing the key and value as parameters. Returns the weak map itself.

If no block is given, an Enumerator is returned instead.

Yields:

  • (key, value)

    calls the given block once for each key in self

Yield Parameters:

  • key (Object)

    the key of the current key-value pair

  • value (Object)

    the value of the current key-value pair

Returns:

  • (self, Enumerator)

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



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/weak/map/strong_keys.rb', line 106

def each_pair
  return enum_for(__method__) { size } unless block_given?

  @keys.values.each do |raw_key|
    next if DeletedEntry === raw_key

    key = value!(raw_key)
    id = key.__id__

    raw_value = @values[id]
    if missing?(raw_value)
      @keys[id] = DeletedEntry.new
    else
      yield [key, value!(raw_value)]
    end
  end

  self
end

#each_value {|value| ... } ⇒ self, Enumerator

Calls the given block once for each live key self, passing the live value associated with the key as a parameter. Returns the weak map itself.

If no block is given, an Enumerator is returned instead.

Yields:

  • (value)

    calls the given block once for each key in self

Yield Parameters:

  • value (Object)

    the value of the current key-value pair

Returns:

  • (self, Enumerator)

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



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/weak/map/strong_keys.rb', line 127

def each_value
  return enum_for(__method__) { size } unless block_given?

  @keys.values.each do |raw_key|
    next if DeletedEntry === raw_key

    key = value!(raw_key)
    id = key.__id__

    raw_value = @values[id]
    if missing?(raw_value)
      @keys[id] = DeletedEntry.new
    else
      yield value!(raw_value)
    end
  end

  self
end

#fetch(key, default = UNDEFINED) {|key| ... } ⇒ Object

Note:

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 a value from the hash for the given key. If the key can't be found, there are several options: With no other arguments, it will raise a KeyError exception; if default is given, then that value will be returned; if the optional code block is specified, then it will be called and its result returned.

Parameters:

  • key (Object)

    the key for the requested value

  • default (Object) (defaults to: UNDEFINED)

    a value to return if there is no value at key in the hash

Yields:

  • (key)

    if no value was set at key, no default value was given, and a block was given, we call the block and return its value

Yield Parameters:

  • key (String)

    the given key

Returns:

  • (Object)

    the value for the given key if present in the map. If the key was not found, we return the default value or the value of the given block.

Raises:

  • (KeyError)

    if the key can not be found and no block or default value was provided



148
149
150
# File 'lib/weak/map/strong_keys.rb', line 148

def fetch(key, default = UNDEFINED, &block)
  _get(key.__id__) { _fetch_default(key, default, &block) }
end

#include?(key) ⇒ Bool

Note:

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 key is included in self and has an associated live value, false otherwise.

Parameters:

  • key (Object)

    a possible key

Returns:

  • (Bool)

    true if the given key is included in self and has an associated live value, false otherwise



153
154
155
156
# File 'lib/weak/map/strong_keys.rb', line 153

def include?(key)
  _get(key.__id__) { return false }
  true
end

#pruneself

Cleanup data structures from the map to remove data associated with deleted or garbage collected keys and/or values. This method may be called automatically for some Weak::Map operations.

Returns:

  • (self)


159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/weak/map/strong_keys.rb', line 159

def prune
  value_keys = ::Set.new(@values.keys)

  @keys.keys.each do |id|
    next if value_keys.delete?(id)
    @keys[id] = DeletedEntry.new
  end

  value_keys.each do |id|
    @values[id] = DeletedEntry.new
  end

  self
end