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, #compact, #compact_blank, #concat, #deconstruct, #deep_dup, #difference, #dig, #drop, #drop_while, #each, #each_cons, #each_index, #each_slice, #entries, #excluding, #fetch, #fetch_values, #fifth, #filter_map, #first, #flat_map, #forty_two, #fourth, #from, #grep, #grep_v, #group_by, #in_groups, #in_groups_of, #in_order_of, #include?, #including, #index, #inquiry, #intersect?, #intersection, #last, #map, #max, #max_by, #maximum, #min, #min_by, #minimum, #minmax, #minmax_by, #pack, #partition, #pick, #pluck, #rassoc, #reject, #reverse, #reverse_each, #rfind, #rindex, #rotate, #sample, #second, #second_to_last, #select, #shuffle, #size, #slice, #slice_after, #slice_before, #slice_when, #sort, #sort_by, #split, #take, #take_while, #third, #third_to_last, #to, #to_a, #to_bson, #to_bson_normalized_value, #to_cbor, #to_fs, #to_h, #to_json, #to_msgpack, #to_param, #to_query, #to_sentence, #to_xml, #union, #uniq, #values_at, #wait_until, #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: :copy, compare_by_identity: false) ⇒ Vector

Valid modes are:

  • :copy - The value will be copied between Ractors. This is the default mode.
  • :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, nil) (defaults to: nil) —

    Initial values. The source array is not retained.

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

    The default value transfer mode.

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

    Whether values are compared by identity.

Raises:

  • (TypeError)


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

def initialize(source = nil, mode: :copy, compare_by_identity: false)
  raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
  unless true.equal?(compare_by_identity) || false.equal?(compare_by_identity)
    raise ArgumentError, "compare_by_identity must be true or false"
  end

  @compare_by_identity = compare_by_identity
  @manager = ModeManager.new(mode:)
  @vector = Internal::Vector.new(source&.map { @manager.wrap(it) }, compare_by_identity: true)
  super()
end

Instance Method Details

#[](index) ⇒ BasicObject?

Read an index without waiting for atomic-update access.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

Returns:

  • (BasicObject, nil) —

    The value, or nil for an index outside the vector.



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

def [](index) = @manager.unwrap(@vector[index])

#[]=(index, value) ⇒ BasicObject

Store a value, growing the vector if necessary.

Parameters:

  • index (Integer) —

    The index. Negative indexes count from the end.

  • value (BasicObject) —

    The value to store.

Returns:

  • (BasicObject) —

    The assigned value.



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

def []=(index, value)
  check_frozen!
  @vector[index] = @manager.wrap(value)
  value
end

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

Append one value using the same options as #push.

Parameters:

  • value (BasicObject) —

    The value 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
# File 'lib/farce/vector.rb', line 19

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

  # @!macro modes
  # @param source [Array, nil] Initial values. 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.
  def initialize(source = nil, mode: :copy, compare_by_identity: false)
    raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
    unless true.equal?(compare_by_identity) || false.equal?(compare_by_identity)
      raise ArgumentError, "compare_by_identity must be true or false"
    end

    @compare_by_identity = compare_by_identity
    @manager = ModeManager.new(mode:)
    @vector = Internal::Vector.new(source&.map { @manager.wrap(it) }, compare_by_identity: true)
    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(compare_by_identity: true)
    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) = @manager.unwrap(@vector[index])

  # (see Farce::Abstract::Vector#[]=)
  def []=(index, value)
    check_frozen!
    @vector[index] = @manager.wrap(value)
    value
  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, mode: nil, timeout: nil)
    check_frozen!
    @vector.push(@manager.wrap(value, mode:), timeout:) ? self : false
  end

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

  # (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: remaining_timeout(deadline))
      return false if nil.equal?(current) || (deadline && Clock.now >= deadline)
    end
  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: remaining_timeout(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 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 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

  def remaining_timeout(deadline)
    return unless deadline
    remaining = deadline - Clock.now
    remaining.negative? ? 0 : remaining
  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:

  • :copy - The value will be copied between Ractors. This is the default mode.
  • :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.



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

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: remaining_timeout(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.



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

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.



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

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

#mode ⇒ Symbol

Returns The default transfer mode.

Returns:

  • (Symbol) —

    The default transfer mode.



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

def mode = @manager.mode

#pop(timeout: nil) ⇒ BasicObject?

Remove and return the last value. This does not wait for an empty vector to become nonempty.

Parameters:

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

    The maximum wait in seconds. Nil waits indefinitely.

Returns:

  • (BasicObject, nil) —

    The last value, or nil if empty or timed out.



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

def pop(timeout: nil) = @manager.unwrap(@vector.pop(timeout:))

#push(value, mode: nil, timeout: nil) ⇒ self, false

Append a single value.

Parameters:

  • value (BasicObject) —

    The value 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.



88
89
90
91
# File 'lib/farce/vector.rb', line 88

def push(value, mode: nil, timeout: nil)
  check_frozen!
  @vector.push(@manager.wrap(value, mode:), timeout:) ? self : false
end

#shareable_values? ⇒ Boolean

Returns Whether stored values must be Ractor-shareable.

Returns:

  • (Boolean) —

    Whether stored values must be Ractor-shareable.



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

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.



81
82
83
84
# File 'lib/farce/vector.rb', line 81

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.



106
107
108
109
110
111
112
113
114
# File 'lib/farce/vector.rb', line 106

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:

  • :copy - The value will be copied between Ractors. This is the default mode.
  • :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.



99
100
101
102
# File 'lib/farce/vector.rb', line 99

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:

  • :copy - The value will be copied between Ractors. This is the default mode.
  • :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.



119
120
121
122
123
124
125
126
127
# File 'lib/farce/vector.rb', line 119

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:

  • :copy - The value will be copied between Ractors. This is the default mode.
  • :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.



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

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



170
171
172
173
174
175
176
177
178
179
# File 'lib/farce/vector.rb', line 170

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: remaining_timeout(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.



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

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