Class: Farce::Vector

Inherits:
Abstract::Vector show all
Includes:
Shareable::Delegated
Defined in:
lib/farce/vector.rb

Overview

A Ractor-shareable vector with transfer modes for mutable values. Values stored through this vector's mode manager are automatically unwrapped.

Examples:

Atomically updating a collection

vector = Farce::Vector.new([[]], mode: :make_shareable)
vector.update(0) { |jobs| jobs + [:finished] } # => [:finished]

Instance Method Summary collapse

Methods included from Shareable

#ractor_shareable?

Methods inherited from Abstract::Vector

#&, #*, #+, #-, #<<, #<=>, #as_extended_json, #as_json, #assoc, #at, #bsearch, #bsearch_index, #bson_type, #chunk, #chunk_while, #clear, #combination, #compact, #compact!, #compact_blank, #concat, #deconstruct, #deep_dup, #delete, #delete_at, #delete_if, #difference, #dig, #drop, #drop_while, #each, #each_cons, #each_index, #each_slice, #entries, #excluding, #fetch, #fetch_values, #fifth, #fill, #filter_map, #first, #flat_map, #flatten, #flatten!, #forty_two, #fourth, #from, #grep, #grep_v, #group_by, #in_groups, #in_groups_of, #in_order_of, #include?, #including, #index, #inquiry, #insert, #intersect?, #intersection, #keep_if, #last, #map, #map!, #max, #max_by, #maximum, #min, #min_by, #minimum, #minmax, #minmax_by, #pack, #partition, #permutation, #pick, #pluck, #product, #rassoc, #reject, #reject!, #repeated_combination, #repeated_permutation, #replace, #reverse, #reverse!, #reverse_each, #rfind, #rindex, #rotate, #rotate!, #sample, #second, #second_to_last, #select, #select!, #shift, #shuffle, #shuffle!, #size, #slice, #slice!, #slice_after, #slice_before, #slice_when, #sort, #sort!, #sort_by, #sort_by!, #split, #take, #take_while, #third, #third_to_last, #to, #to_a, #to_ary, #to_bson, #to_bson_normalized_value, #to_cbor, #to_fs, #to_h, #to_json, #to_msgpack, #to_param, #to_query, #to_sentence, #to_xml, #transpose, #union, #uniq, #uniq!, #unshift, #values_at, #wait_until_match, #wait_until_value, #wait_while, #wait_while_match, #wait_while_value, #zip, #|

Methods inherited from Abstract::Collection

#<<, [], #clear, #count, #each, #empty?, #filter, #include?, #join, #length, #member?, #reject, #select, #size, #to_a, #to_s

Methods included from Internal::Copyable

#duplicable?

Constructor Details

#initialize(source = nil, mode: :smart, compare_by_identity: false) ⇒ Vector

Valid modes are:

  • :smart - Applies smart rules or a dedicated proxy. Raises for unsupported values. This is the default for most mode-backed classes.
  • :smart_raise - Applies smart rules or a dedicated proxy, with a raising fallback.
  • :smart_copy - Applies smart rules or a dedicated proxy, then falls back to copying or Marshal. This is the default for Port.
  • :smart_move - Applies smart rules or a dedicated proxy, then falls back to moving.
  • :copy - The value will be copied between Ractors.
  • :marshal - The value will be serialized immediately and reconstructed once per Ractor with Ruby's Marshal protocol. Contents must support Marshal. Nested shareable values may be reconstructed too.
  • :make_shareable - The value will be made Ractor-shareable using Ractor.make_shareable.
  • :move - The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.
  • :mutable - A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.
  • :local - The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.
  • :proxy - The value will be wrapped in a Farce::Proxy that executes calls in the original Ractor.
  • :raise - An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.
  • :dedup - The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.
  • :shareable_copy - The value will be copied and the copy will be made Ractor-shareable.

Parameters:

  • source (Array, Integer, nil) (defaults to: nil) —

    Initial values or a size filled with nil. The source array is not retained.

  • mode (Symbol) (defaults to: :smart) —

    The default value transfer mode.

  • compare_by_identity (Boolean) (defaults to: false) —

    Whether values are compared by identity.

Raises:

  • (ArgumentError) —

    If the size is negative.



27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/farce/vector.rb', line 27

def initialize(source = nil, mode: :smart, compare_by_identity: false)
  source = normalize_vector_source(source)
  raise TypeError, "source must be an Array" unless source.nil? || Integer === source || source.is_a?(Array)

  @compare_by_identity = compare_by_identity ? true : false
  @manager = ModeManager.new(mode:)
  if source && !(Integer === source)
    source = source.map { @manager.wrap(it) }
    raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
  end
  @vector = Internal::Vector.new_for_modes(source)
  super()
end

Instance Method Details

#[](index, length = UNDEFINED) ⇒ BasicObject, ...

Read an index without waiting for atomic-update access, or return a slice snapshot.

Parameters:

  • index (Integer, Range) —

    The index, start index, or range. Negative indexes count from the end.

  • length (Integer) (defaults to: UNDEFINED) —

    The slice length. Omit for a single index or range.

Returns:

  • (BasicObject, Vector, nil) —

    The logical value, a slice Vector, or nil if absent.



70
71
72
73
# File 'lib/farce/vector.rb', line 70

def [](index, length = UNDEFINED)
  return @manager.unwrap(@vector[index]) if length.equal?(UNDEFINED) && !index.is_a?(Range)
  slice(index, length)
end

#[]=(index, value, replacement = UNDEFINED) ⇒ BasicObject

Store a value or replace a slice atomically, growing the vector if necessary. Slice replacements accept a Vector, Array-like object, or one scalar value.

Parameters:

  • index (Integer, Range) —

    The index, start index, or range.

  • value (BasicObject, Integer) —

    The assigned value, or the slice length for three arguments.

  • replacement (BasicObject) (defaults to: UNDEFINED) —

    The replacement for the three-argument form.

Returns:

  • (BasicObject) —

    The assigned value or replacement sequence.



76
77
78
79
80
81
82
83
84
# File 'lib/farce/vector.rb', line 76

def []=(index, value, replacement = UNDEFINED)
  if replacement.equal?(UNDEFINED) && !index.is_a?(Range)
    check_frozen!
    @vector[index] = @manager.wrap(value)
    value
  else
    assign_vector_slice(index, value, replacement)
  end
end

#append(*values, mode: nil, timeout: nil) ⇒ self, false

Append values using the same options as #push.

Parameters:

  • values (Array<BasicObject>) —

    The values to append.

  • mode (Symbol, nil) (defaults to: nil) —

    The transfer mode, or nil to use the default.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

Returns:

  • (self, false) —

    Self on success, or false on timeout.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/farce/vector.rb', line 19

class Vector < Farce::Abstract::Vector
  include Shareable::Delegated

  # @!macro modes
  # @param source [Array, Integer, nil] Initial values or a size filled with nil. The source array is not retained.
  # @param mode [Symbol] The default value transfer mode.
  # @param compare_by_identity [Boolean] Whether values are compared by identity.
  # @raise [ArgumentError] If the size is negative.
  def initialize(source = nil, mode: :smart, compare_by_identity: false)
    source = normalize_vector_source(source)
    raise TypeError, "source must be an Array" unless source.nil? || Integer === source || source.is_a?(Array)

    @compare_by_identity = compare_by_identity ? true : false
    @manager = ModeManager.new(mode:)
    if source && !(Integer === source)
      source = source.map { @manager.wrap(it) }
      raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
    end
    @vector = Internal::Vector.new_for_modes(source)
    super()
  end

  # @api private
  def transaction_wrapper(transaction) = Transaction::Vector.new(transaction, self, @vector, manager: @manager)

  # @return [Symbol] The default transfer mode.
  def mode = @manager.mode

  # @api private
  def marshal_dump
    entries = @vector.snapshot.map { Internal::MarshalSupport.value(it) }
    [1, entries, @manager, compare_by_identity?, frozen?]
  end

  # @api private
  def marshal_load(data)
    entries, @manager, identity, frozen = Internal::MarshalSupport.payload(data, 4)
    @compare_by_identity = identity
    @vector = Internal::Vector.new_for_modes(nil)
    publish_shareable
    entries.each { @vector.push(Internal::MarshalSupport.restore_value(it)) }
    Internal::MarshalSupport.freeze(self, frozen)
  end

  # (see Farce::Abstract::Vector#compare_by_identity?)
  def compare_by_identity? = @compare_by_identity

  # (see Farce::Abstract::Vector#shareable_values?)
  def shareable_values? = true

  # (see Farce::Abstract::Vector#[])
  def [](index, length = UNDEFINED)
    return @manager.unwrap(@vector[index]) if length.equal?(UNDEFINED) && !index.is_a?(Range)
    slice(index, length)
  end

  # (see Farce::Abstract::Vector#[]=)
  def []=(index, value, replacement = UNDEFINED)
    if replacement.equal?(UNDEFINED) && !index.is_a?(Range)
      check_frozen!
      @vector[index] = @manager.wrap(value)
      value
    else
      assign_vector_slice(index, value, replacement)
    end
  end

  # (see Farce::Abstract::Vector#get)
  def get(index, timeout: nil) = @manager.unwrap(@vector.get(index, timeout:))

  # (see Farce::Abstract::Vector#store)
  # @param mode [Symbol, nil] The transfer mode, or nil to use the default.
  def store(index, value, mode: nil, timeout: nil)
    check_frozen!
    @manager.unwrap(@vector.store(index, @manager.wrap(value, mode:), timeout:))
  end

  # (see Farce::Abstract::Vector#push)
  # @param mode [Symbol, nil] The transfer mode, or nil to use the default.
  def push(value = UNDEFINED, *rest, mode: nil, timeout: nil)
    if rest.empty? && !value.equal?(UNDEFINED)
      check_frozen!
      stored = mode.nil? ? @manager.wrap(value) : @manager.wrap(value, mode:)
      completed = timeout.nil? ? @vector.push(stored) : @vector.push(stored, timeout:)
      return completed ? self : false
    end
    push_values(value.equal?(UNDEFINED) ? rest : [value, *rest], mode:, timeout:)
  end

  # (see Farce::Abstract::Vector#pop)
  def pop(count = UNDEFINED, timeout: nil)
    return @manager.unwrap(timeout.nil? ? @vector.pop : @vector.pop(timeout:)) if count.equal?(UNDEFINED)
    pop_values(count, timeout:)
  end

  # (see Farce::Abstract::Vector#swap)
  # @!macro modes
  # @param mode [Symbol, nil] The replacement's transfer mode, or nil to use the default.
  def swap(index, replacement, mode: nil, timeout: nil)
    check_frozen!
    @manager.unwrap(@vector.swap(index, @manager.wrap(replacement, mode:), timeout:))
  end

  # (see Farce::Abstract::Vector#store_if_absent)
  # @param mode [Symbol, nil] The result's transfer mode, or nil to use the default.
  def store_if_absent(index, mode: nil, timeout: nil)
    raise LocalJumpError, "no block given" unless block_given?
    check_frozen!
    @manager.unwrap(@vector.store_if_absent(index, timeout:) do
      value = yield
      check_frozen!
      @manager.wrap(value, mode:)
    end)
  end

  # (see Farce::Abstract::Vector#update)
  # @!macro modes
  # @param mode [Symbol, nil] The result's transfer mode, or nil to use the default.
  def update(index, mode: nil, timeout: nil)
    raise LocalJumpError, "no block given" unless block_given?
    check_frozen!
    @manager.unwrap(@vector.update(index, timeout:) do |current|
      value = yield(@manager.unwrap(current))
      check_frozen!
      @manager.wrap(value, mode:)
    end)
  end

  # (see Farce::Abstract::Vector#upsert)
  # @!macro modes
  # @param mode [Symbol, nil] The value transfer mode, or nil to use the default.
  def upsert(index, initial, mode: nil, timeout: nil)
    raise LocalJumpError, "no block given" unless block_given?
    check_frozen!
    @manager.unwrap(@vector.update(index, timeout:) do |current|
      value = if nil.equal?(current)
                initial
              else
                yielded = yield(@manager.unwrap(current))
                check_frozen!
                yielded
              end
      @manager.wrap(value, mode:)
    end)
  end

  # (see Farce::Abstract::Vector#compare_and_set)
  # @!macro modes
  # @param mode [Symbol, nil] The replacement's transfer mode, or nil to use the default.
  def compare_and_set(index, expected, replacement, mode: nil, timeout: nil)
    check_frozen!
    deadline = timeout_deadline(timeout)
    expected = wrap_comparison(expected)
    wrapped = false
    loop do
      current = @vector[index]
      equal   = values_equal?(current, expected)
      check_frozen!
      return false unless equal
      unless wrapped
        replacement = @manager.wrap(replacement, mode:)
        wrapped = true
      end
      return true if @vector.compare_and_set(index, current, replacement, timeout: Clock.remaining(deadline))
      return false if nil.equal?(current) || (deadline && Clock.now >= deadline)
    end
  end

  # (see Farce::Abstract::Vector#wait_until)
  def wait_until(index, timeout: nil)
    raise LocalJumpError, "no block given" unless block_given?
    result = Internal.wait_until(@vector, index, timeout:) { |current| yield(@manager.unwrap(current)) }
    @manager.unwrap(result)
  end

  # (see Farce::Abstract::Vector#wait_until_changed)
  def wait_until_changed(index, expected, timeout: nil)
    deadline = timeout_deadline(timeout)
    expected = wrap_comparison(expected)
    loop do
      current = @vector[index]
      return @manager.unwrap(current) unless values_equal?(current, expected)
      return if deadline && Clock.now >= deadline
      @vector.wait_until_changed(index, current, timeout: Clock.remaining(deadline))
    end
  end

  # (see Farce::Abstract::Vector#wait_until_non_nil)
  def wait_until_non_nil(index, timeout: nil) = @manager.unwrap(@vector.wait_until_non_nil(index, timeout:))

  protected

  def logical_value(value)              = @manager.unwrap(value)
  def deletion_comparison(value)        = wrap_comparison(value)
  def deletion_match?(stored, expected) = values_equal?(stored, expected)

  def build_derived_vector(values)
    self.class.allocate.initialize_derived_vector(values, @manager, @compare_by_identity)
  end

  def initialize_derived_vector(values, manager, compare_by_identity)
    @manager = manager
    @compare_by_identity = compare_by_identity
    @vector = Internal::Vector.new(values, compare_by_identity: true)
    publish_shareable
  end

  attr_reader :manager

  def reusable_operand_snapshot(other)
    return unless other.is_a?(Farce::Vector)
    return unless other.manager.equal?(@manager)
    other.internal_vector.snapshot
  end

  private

  def inspect_value(inspector, value)        = super(inspector, value, @manager)
  def derived_storage(value)                 = @manager.wrap(value)
  def prepare_vector_value(value, mode: nil) = @manager.wrap(value, mode:)

  # Expanded descendants have no independent envelope to reuse. Copy them rather than
  # moving them out of a retained parent Array or another sequence's storage.
  def prepare_sequence_value(value)
    @manager.wrap(value, mode: mode == :move ? :copy : nil)
  end

  def freeze_backend             = @vector
  def wrap_comparison(value)     = @manager.wrap(value, mode: compare_by_identity? ? :local : :copy)
  def values_equal?(left, right) = @manager.same_value?(left, right, identity: compare_by_identity?)

  def timeout_deadline(timeout)
    return if timeout.nil?
    timeout = Float(timeout)
    raise ArgumentError, "timeout must be finite and non-negative" unless timeout.finite? && !timeout.negative?
    Clock.now + timeout
  end
end

#compare_and_set(index, expected, replacement, mode: nil, timeout: nil) ⇒ Boolean

Replace an existing index only if its value matches the expected value. This never grows the vector. Matching uses the configured comparison mode. Valid modes are:

  • :smart - Applies smart rules or a dedicated proxy. Raises for unsupported values. This is the default for most mode-backed classes.
  • :smart_raise - Applies smart rules or a dedicated proxy, with a raising fallback.
  • :smart_copy - Applies smart rules or a dedicated proxy, then falls back to copying or Marshal. This is the default for Port.
  • :smart_move - Applies smart rules or a dedicated proxy, then falls back to moving.
  • :copy - The value will be copied between Ractors.
  • :marshal - The value will be serialized immediately and reconstructed once per Ractor with Ruby's Marshal protocol. Contents must support Marshal. Nested shareable values may be reconstructed too.
  • :make_shareable - The value will be made Ractor-shareable using Ractor.make_shareable.
  • :move - The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.
  • :mutable - A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.
  • :local - The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.
  • :proxy - The value will be wrapped in a Farce::Proxy that executes calls in the original Ractor.
  • :raise - An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.
  • :dedup - The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.
  • :shareable_copy - The value will be copied and the copy will be made Ractor-shareable.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • expected (BasicObject) —

    The value that must match the current entry.

  • replacement (BasicObject) —

    The value to store on a match.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

  • mode (Symbol, nil) (defaults to: nil) —

    The replacement's transfer mode, or nil to use the default.

Returns:

  • (Boolean) —

    Whether the replacement succeeded. False on timeout.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/farce/vector.rb', line 168

def compare_and_set(index, expected, replacement, mode: nil, timeout: nil)
  check_frozen!
  deadline = timeout_deadline(timeout)
  expected = wrap_comparison(expected)
  wrapped = false
  loop do
    current = @vector[index]
    equal   = values_equal?(current, expected)
    check_frozen!
    return false unless equal
    unless wrapped
      replacement = @manager.wrap(replacement, mode:)
      wrapped = true
    end
    return true if @vector.compare_and_set(index, current, replacement, timeout: Clock.remaining(deadline))
    return false if nil.equal?(current) || (deadline && Clock.now >= deadline)
  end
end

#compare_by_identity? ⇒ Boolean

Returns Whether values are compared by identity.

Returns:

  • (Boolean) —

    Whether values are compared by identity.



64
# File 'lib/farce/vector.rb', line 64

def compare_by_identity? = @compare_by_identity

#get(index, timeout: nil) ⇒ BasicObject?

Read an index after acquiring atomic-update access.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

Returns:

  • (BasicObject, nil) —

    The value, or nil if absent or timed out.



87
# File 'lib/farce/vector.rb', line 87

def get(index, timeout: nil) = @manager.unwrap(@vector.get(index, timeout:))

#mode ⇒ Symbol

Returns The default transfer mode.

Returns:

  • (Symbol) —

    The default transfer mode.



45
# File 'lib/farce/vector.rb', line 45

def mode = @manager.mode

#pop(count = UNDEFINED, timeout: nil) ⇒ BasicObject, ...

Remove and return the last value, or a Vector of the last count values, atomically. This does not wait for an empty vector to become nonempty.

Parameters:

  • count (Integer) (defaults to: UNDEFINED) —

    The maximum number of values. Omit to return one value.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

Returns:

  • (BasicObject, Vector, nil) —

    Removed values, or nil if empty without a count or timed out.



109
110
111
112
# File 'lib/farce/vector.rb', line 109

def pop(count = UNDEFINED, timeout: nil)
  return @manager.unwrap(timeout.nil? ? @vector.pop : @vector.pop(timeout:)) if count.equal?(UNDEFINED)
  pop_values(count, timeout:)
end

#push(value = UNDEFINED, *rest, mode: nil, timeout: nil) ⇒ self, false

Append values atomically. Calls with one value use the scalar backend path.

Parameters:

  • value (BasicObject) (defaults to: UNDEFINED) —

    The first value. Omit to leave contents unchanged.

  • rest (Array<BasicObject>) —

    Additional values to append.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

  • mode (Symbol, nil) (defaults to: nil) —

    The transfer mode, or nil to use the default.

Returns:

  • (self, false) —

    Self on success, or false on timeout.



98
99
100
101
102
103
104
105
106
# File 'lib/farce/vector.rb', line 98

def push(value = UNDEFINED, *rest, mode: nil, timeout: nil)
  if rest.empty? && !value.equal?(UNDEFINED)
    check_frozen!
    stored = mode.nil? ? @manager.wrap(value) : @manager.wrap(value, mode:)
    completed = timeout.nil? ? @vector.push(stored) : @vector.push(stored, timeout:)
    return completed ? self : false
  end
  push_values(value.equal?(UNDEFINED) ? rest : [value, *rest], mode:, timeout:)
end

#shareable_values? ⇒ Boolean

Returns Whether stored values must be Ractor-shareable.

Returns:

  • (Boolean) —

    Whether stored values must be Ractor-shareable.



67
# File 'lib/farce/vector.rb', line 67

def shareable_values? = true

#store(index, value, mode: nil, timeout: nil) ⇒ BasicObject, false

Store a value after acquiring atomic-update access.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • value (BasicObject) —

    The value to store.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

  • mode (Symbol, nil) (defaults to: nil) —

    The transfer mode, or nil to use the default.

Returns:

  • (BasicObject, false) —

    The value, or false on timeout.



91
92
93
94
# File 'lib/farce/vector.rb', line 91

def store(index, value, mode: nil, timeout: nil)
  check_frozen!
  @manager.unwrap(@vector.store(index, @manager.wrap(value, mode:), timeout:))
end

#store_if_absent(index, mode: nil, timeout: nil) { ... } ⇒ BasicObject?

Compute and store a value only when the index is absent or contains nil.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

  • mode (Symbol, nil) (defaults to: nil) —

    The result's transfer mode, or nil to use the default.

Yields:

  • Called without arguments to compute a value when the slot is absent or nil.

Yield Returns:

  • (BasicObject) —

    The value to store.

Returns:

  • (BasicObject, nil) —

    The existing or computed value, or nil on timeout.



124
125
126
127
128
129
130
131
132
# File 'lib/farce/vector.rb', line 124

def store_if_absent(index, mode: nil, timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  check_frozen!
  @manager.unwrap(@vector.store_if_absent(index, timeout:) do
    value = yield
    check_frozen!
    @manager.wrap(value, mode:)
  end)
end

#swap(index, replacement, mode: nil, timeout: nil) ⇒ BasicObject?

Replace an index and return its previous value, growing the vector if necessary. Valid modes are:

  • :smart - Applies smart rules or a dedicated proxy. Raises for unsupported values. This is the default for most mode-backed classes.
  • :smart_raise - Applies smart rules or a dedicated proxy, with a raising fallback.
  • :smart_copy - Applies smart rules or a dedicated proxy, then falls back to copying or Marshal. This is the default for Port.
  • :smart_move - Applies smart rules or a dedicated proxy, then falls back to moving.
  • :copy - The value will be copied between Ractors.
  • :marshal - The value will be serialized immediately and reconstructed once per Ractor with Ruby's Marshal protocol. Contents must support Marshal. Nested shareable values may be reconstructed too.
  • :make_shareable - The value will be made Ractor-shareable using Ractor.make_shareable.
  • :move - The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.
  • :mutable - A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.
  • :local - The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.
  • :proxy - The value will be wrapped in a Farce::Proxy that executes calls in the original Ractor.
  • :raise - An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.
  • :dedup - The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.
  • :shareable_copy - The value will be copied and the copy will be made Ractor-shareable.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • replacement (BasicObject) —

    The new value.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

  • mode (Symbol, nil) (defaults to: nil) —

    The replacement's transfer mode, or nil to use the default.

Returns:

  • (BasicObject, nil) —

    The previous value, or nil if absent or timed out.



117
118
119
120
# File 'lib/farce/vector.rb', line 117

def swap(index, replacement, mode: nil, timeout: nil)
  check_frozen!
  @manager.unwrap(@vector.swap(index, @manager.wrap(replacement, mode:), timeout:))
end

#update(index, mode: nil, timeout: nil) {|value| ... } ⇒ BasicObject?

Atomically replace an index with the block result, growing the vector if necessary. Valid modes are:

  • :smart - Applies smart rules or a dedicated proxy. Raises for unsupported values. This is the default for most mode-backed classes.
  • :smart_raise - Applies smart rules or a dedicated proxy, with a raising fallback.
  • :smart_copy - Applies smart rules or a dedicated proxy, then falls back to copying or Marshal. This is the default for Port.
  • :smart_move - Applies smart rules or a dedicated proxy, then falls back to moving.
  • :copy - The value will be copied between Ractors.
  • :marshal - The value will be serialized immediately and reconstructed once per Ractor with Ruby's Marshal protocol. Contents must support Marshal. Nested shareable values may be reconstructed too.
  • :make_shareable - The value will be made Ractor-shareable using Ractor.make_shareable.
  • :move - The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.
  • :mutable - A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.
  • :local - The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.
  • :proxy - The value will be wrapped in a Farce::Proxy that executes calls in the original Ractor.
  • :raise - An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.
  • :dedup - The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.
  • :shareable_copy - The value will be copied and the copy will be made Ractor-shareable.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

  • mode (Symbol, nil) (defaults to: nil) —

    The result's transfer mode, or nil to use the default.

Yields:

  • (value) —

    Compute the replacement while holding atomic-update access.

Yield Parameters:

  • value (BasicObject, nil) —

    The current value, or nil if absent.

Yield Returns:

  • (BasicObject) —

    The replacement value.

Returns:

  • (BasicObject, nil) —

    The replacement value, or nil on timeout.



137
138
139
140
141
142
143
144
145
# File 'lib/farce/vector.rb', line 137

def update(index, mode: nil, timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  check_frozen!
  @manager.unwrap(@vector.update(index, timeout:) do |current|
    value = yield(@manager.unwrap(current))
    check_frozen!
    @manager.wrap(value, mode:)
  end)
end

#upsert(index, initial, mode: nil, timeout: nil) {|value| ... } ⇒ BasicObject?

Store initial for an absent or nil index, otherwise replace it with the block result. Valid modes are:

  • :smart - Applies smart rules or a dedicated proxy. Raises for unsupported values. This is the default for most mode-backed classes.
  • :smart_raise - Applies smart rules or a dedicated proxy, with a raising fallback.
  • :smart_copy - Applies smart rules or a dedicated proxy, then falls back to copying or Marshal. This is the default for Port.
  • :smart_move - Applies smart rules or a dedicated proxy, then falls back to moving.
  • :copy - The value will be copied between Ractors.
  • :marshal - The value will be serialized immediately and reconstructed once per Ractor with Ruby's Marshal protocol. Contents must support Marshal. Nested shareable values may be reconstructed too.
  • :make_shareable - The value will be made Ractor-shareable using Ractor.make_shareable.
  • :move - The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.
  • :mutable - A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.
  • :local - The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.
  • :proxy - The value will be wrapped in a Farce::Proxy that executes calls in the original Ractor.
  • :raise - An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.
  • :dedup - The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.
  • :shareable_copy - The value will be copied and the copy will be made Ractor-shareable.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • initial (BasicObject) —

    The value to store when the slot is absent or nil.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

  • mode (Symbol, nil) (defaults to: nil) —

    The value transfer mode, or nil to use the default.

Yields:

  • (value) —

    Compute a replacement for an existing non-nil entry.

Yield Parameters:

  • value (BasicObject) —

    The current non-nil value.

Yield Returns:

  • (BasicObject) —

    The replacement value.

Returns:

  • (BasicObject, nil) —

    The stored value, or nil on timeout.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/farce/vector.rb', line 150

def upsert(index, initial, mode: nil, timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  check_frozen!
  @manager.unwrap(@vector.update(index, timeout:) do |current|
    value = if nil.equal?(current)
              initial
            else
              yielded = yield(@manager.unwrap(current))
              check_frozen!
              yielded
            end
    @manager.wrap(value, mode:)
  end)
end

#wait_until(index, timeout: nil) {|value| ... } ⇒ BasicObject?

Wait until a block condition matches the value at an index. Absent indexes are observed as nil. One timeout budget covers all checks and waits. The block is not interrupted.

Parameters:

  • index (Integer) —

    the index to observe. Negative indexes count from the end.

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available

Yield Parameters:

  • value (BasicObject, nil) —

    the current value

Yield Returns:

  • (Boolean) —

    whether the value matches

Returns:

  • (BasicObject, nil) —

    the matching value, or nil on timeout

Raises:

  • (LocalJumpError) —

    if no block is given



188
189
190
191
192
# File 'lib/farce/vector.rb', line 188

def wait_until(index, timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  result = Internal.wait_until(@vector, index, timeout:) { |current| yield(@manager.unwrap(current)) }
  @manager.unwrap(result)
end

#wait_until_changed(index, expected, timeout: nil) ⇒ BasicObject?

Wait until an index no longer matches expected. Absent indexes are observed as nil.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • expected (BasicObject) —

    The value to wait for the entry to stop matching.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

Returns:

  • (BasicObject, nil) —

    The changed value, or nil on timeout.



195
196
197
198
199
200
201
202
203
204
# File 'lib/farce/vector.rb', line 195

def wait_until_changed(index, expected, timeout: nil)
  deadline = timeout_deadline(timeout)
  expected = wrap_comparison(expected)
  loop do
    current = @vector[index]
    return @manager.unwrap(current) unless values_equal?(current, expected)
    return if deadline && Clock.now >= deadline
    @vector.wait_until_changed(index, current, timeout: Clock.remaining(deadline))
  end
end

#wait_until_non_nil(index, timeout: nil) ⇒ BasicObject?

Wait until an index contains a non-nil value.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • timeout (Numeric, nil) (defaults to: nil) —

    The maximum wait in seconds. Nil waits indefinitely.

Returns:

  • (BasicObject, nil) —

    The non-nil value, or nil on timeout.



207
# File 'lib/farce/vector.rb', line 207

def wait_until_non_nil(index, timeout: nil) = @manager.unwrap(@vector.wait_until_non_nil(index, timeout:))