Class: Farce::Envelope

Inherits:
Object
  • Object
show all
Includes:
Abstract::Value, Internal::Noncopyable, Shareable, Internal::Inspect
Defined in:
lib/farce/envelope.rb,
lib/farce/integrations/active_support/blank.rb

Overview

An envelope can be used to wrap an unshareable value in a shareable object.

Once created, the envelope can be shared between Ractors without any objects being moved or copied until the envelope is opened.

When a Ractor attempts to open an envelope, it attempts "claim" it. Some envelopes may only be claimed by a single Ractor, others can be claimed by any Ractor. Claims cannot be taken back.

Direct Known Subclasses

Copy, Local, Move, Share

Defined Under Namespace

Classes: Copy, Local, Move, Share

Constant Summary collapse

AlreadyClaimed =

Raised when trying to claim an envelope that has already been claimed by another Ractor.

Class.new(Ractor::IsolationError)

ActiveSupport Integration collapse

Methods included from Internal::Noncopyable

#duplicable?

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Abstract::Value

#unwrap

Methods included from Shareable

#ractor_shareable?

Constructor Details

#initialize(value) ⇒ Envelope

Returns a new instance of Envelope.

Parameters:

  • value (BasicObject) —

    The value to wrap in the envelope.

Raises:

  • (ArgumentError)


282
283
284
285
286
# File 'lib/farce/envelope.rb', line 282

def initialize(_, auto_unwrap = nil)
  raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap)
  @auto_unwrap = auto_unwrap
  super()
end

Class Method Details

.new(value, move: false) ⇒ Envelope .new(value, local: false) ⇒ Envelope .new(value, mode:) ⇒ Envelope

Note:

The keyword arguments are only accepted by Envelope itself, not by its subclasses. Also, if multiple keyword arguments are provided, mode takes precedence over local, which takes precedence over move.

Creates an envelope wrapping the given value. If the value is Ractor-shareable, a Share envelope will be created. Otherwise, the type of envelope will be determined by the keyword arguments.

Overloads:

  • .new(value, move: false) ⇒ Envelope

    Returns A new envelope wrapping the given value.

    Parameters:

    • value (BasicObject) —

      The value to wrap in the envelope.

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

      Whether to create a Move envelope. Ignored if local or mode is provided.

    Returns:

    • (Envelope) —

      A new envelope wrapping the given value.

  • .new(value, local: false) ⇒ Envelope

    Returns A new envelope wrapping the given value.

    Parameters:

    • value (BasicObject) —

      The value to wrap in the envelope.

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

      Whether to create a Local envelope. Ignored if mode is provided.

    Returns:

    • (Envelope) —

      A new envelope wrapping the given value.

  • .new(value, mode:) ⇒ Envelope

    Returns A new envelope wrapping the given value.

    Parameters:

    • value (BasicObject) —

      The value to wrap in the envelope.

    • mode (Symbol, nil) —

      The type of envelope to create. Can be :move, :copy, or :local.

    Returns:

    • (Envelope) —

      A new envelope wrapping the given value.

Returns:

  • (Envelope) —

    A new envelope wrapping the given value.



260
261
262
263
264
# File 'lib/farce/envelope.rb', line 260

def self.new(value, *, **)
  return super if self != Envelope
  return Share.new(value, *) if Ractor.shareable?(value)
  subclass_for(**).new(value, *)
end

Instance Method Details

#blank? ⇒ Boolean

Note:

This methods is only available if ActiveSupport has been loaded.

Checks if the envelope's value is blank without claiming it.

Returns:

  • (Boolean) —

    true if the envelope's value is blank or inaccessible, false otherwise.



57
58
59
60
61
62
63
64
65
66
# File 'lib/farce/integrations/active_support/blank.rb', line 57

def blank?
  if !claimed? && (vault = comparison_vault)
    result = vault.is_blank?(comparison_key)
    # A concurrent claim may have removed the value from the vault.
    return result unless claimed?
  end
  return true unless owned?

  value.blank?
end

#claim ⇒ Envelope?

Attempts to claim the envelope for the current Ractor.

Returns:

  • (Envelope, nil) —

    The envelope if the claim was successful, or nil if the envelope has already been claimed by another Ractor.



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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/farce/envelope.rb', line 24

class Envelope
  include Internal::Noncopyable
  include Internal::Inspect

  VAULT_ATOM = Internal::Atom.new
  private_constant :VAULT_ATOM

  include Shareable
  include Abstract::Value

  # Raised when trying to claim an envelope that has already been claimed by another Ractor.
  AlreadyClaimed = Class.new(Ractor::IsolationError)

  module Copyable
    include Internal::Copyable

    define_method(:dup, Object.instance_method(:dup))
    define_method(:clone, Object.instance_method(:clone))

    private

    def initialize_copy(other)
      Object.instance_method(:initialize_copy).bind_call(self, other)
    end
  end
  private_constant :Copyable

  # An envelope that copies its contents. Can be opened by multiple Ractors.
  # The value will be copied once when the envelope is created, and then once per Ractor that opens the envelope.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Copy, Share] A new envelope wrapping the given value.
  class Copy < Farce::Envelope
    include Copyable

    # @overload initialize(value)
    #   @param [Object] value The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new }
      super
      @vault.copy_in(self, value)
    end

    # @api private
    def marshal_dump
      storage = Internal::Storage.ractor
      [1, storage.key?(self) ? storage[self] : retrieve, auto_unwrap]
    end

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(value, manager)
      Internal::Storage.ractor[self] = value
    end

    # (see Envelope#claim)
    def claim = self

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = true

    protected

    def retrieve = @vault.copy_out(vault_key)

    private

    def initialize_dup(other)
      value      = other.retrieve
      @vault_key = Object.new.freeze
      super
      @vault.copy_in(@vault_key, value)
    end

    def initialize_clone(other, freeze: nil)
      value      = other.retrieve
      @vault_key = Object.new.freeze
      super
      @vault.copy_in(@vault_key, value)
    end

    def vault_key = defined?(@vault_key) ? @vault_key : self
  end

  # An envelope that moves its contents. Can only be opened by a single Ractor.
  # The value will be moved once when the envelope is created, and then once when the envelope is opened.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Move, Share] A new envelope wrapping the given value.
  class Move < Farce::Envelope
    include Internal::MarshalSupport::Reject
    include Shareable::Unfreezable

    # @overload initialize(value)
    #   @param [Object] value The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new }
      @owner = Internal::Atom.new
      super
      @vault.move_in(self, value)
    end

    # (see Envelope#claim)
    def claim
      owner = @owner.store_if_absent { Ractor.current }
      self if owner == Ractor.current
    end

    # (see Envelope#claimed?)
    def claimed? = !@owner.value.nil?

    # (see Envelope#owned?)
    def owned? = @owner.value == Ractor.current

    private def retrieve = @vault.move_out(self)
  end

  # An envelope that keeps its contents local to the Ractor that created it.
  # Can only be opened by the Ractor that created it.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Local, Share] A new envelope wrapping the given value.
  class Local < Farce::Envelope
    include Copyable

    # @overload initialize(value)
    #   @param value [Object] The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @owner = Ractor.current
      Internal::Storage.ractor[self] = value
      super
    end

    # @api private
    def marshal_dump
      raise TypeError, "local envelope belongs to another Ractor" unless owned?
      [1, Internal::Storage.ractor[self], auto_unwrap]
    end

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(value, manager)
    end

    # (see Envelope#claim)
    def claim = owned? ? self : nil

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = @owner == Ractor.current

    private

    def initialize_copy(other)
      super
      other.claim!
      Internal::Storage.ractor[self] = other.value.dup
    end
  end

  # An envelope that wraps a Ractor-shareable value. Can be opened by any Ractor.
  # Defeats the purpose of an envelope, but is provided for code that expects an envelope.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Share] A new envelope wrapping the given value.
  class Share < Farce::Envelope
    include Copyable

    attr_reader :value

    # @overload initialize(value)
    #   @param value [Object] The value to wrap in the envelope. Must be shareable.
    def initialize(value, auto_unwrap = nil)
      raise ArgumentError, "value must be shareable" unless Ractor.shareable?(value)
      @value = value
      super
    end

    # @api private
    def marshal_dump = [1, value, auto_unwrap]

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(Ractor.make_shareable(value), manager)
    end

    # (see Envelope#claim)
    def claim = self

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = true
  end

  # @note
  #   The keyword arguments are only accepted by Envelope itself, not by its subclasses.
  #   Also, if multiple keyword arguments are provided, `mode` takes precedence over `local`, which takes precedence
  #   over `move`.
  #
  # Creates an envelope wrapping the given value. If the value is Ractor-shareable, a {Share} envelope will be
  # created. Otherwise, the type of envelope will be determined by the keyword arguments.
  #
  # @overload new(value, move: false)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param move [Boolean] Whether to create a {Move} envelope. Ignored if `local` or `mode` is provided.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @overload new(value, local: false)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param local [Boolean] Whether to create a {Local} envelope. Ignored if `mode` is provided.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @overload new(value, mode:)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, or `:local`.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @return [Envelope] A new envelope wrapping the given value.
  def self.new(value, *, **)
    return super if self != Envelope
    return Share.new(value, *) if Ractor.shareable?(value)
    subclass_for(**).new(value, *)
  end

  def self.subclass_for(move: false, local: false, mode: nil)
    case mode
    when :move  then Move
    when :copy  then Copy
    when :local then Local
    when nil    then local ? Local : (move ? Move : Copy)
    else raise ArgumentError, "invalid mode: #{mode.inspect}"
    end
  end
  private_class_method :subclass_for

  # @api private
  attr_reader :auto_unwrap

  # @overload initialize(value)
  #   @param value [BasicObject] The value to wrap in the envelope.
  def initialize(_, auto_unwrap = nil)
    raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap)
    @auto_unwrap = auto_unwrap
    super()
  end

  # Attempts to claim the envelope for the current Ractor.
  # Raises an exception if it was unable to claim the envelope.
  # @return [Envelope] The envelope if the claim was successful.
  # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor.
  def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor")

  # Attempts to claim the envelope and retrieves the value if successful.
  # May be called multiple times.
  # @return [BasicObject] The value wrapped in the envelope.
  # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor.
  def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve }

  # Compares wrapped values. Compatible envelopes can be compared without
  # claiming or opening either envelope.
  # @param other [BasicObject, Envelope] The value or envelope to compare.
  # @param identity [Boolean] Whether to compare the values by identity instead of equality.
  # @return [Boolean] Whether the wrapped values match.
  def same_value?(other, identity: false)
    if comparison_vault
      other_vault = other.comparison_vault if Envelope === other
      if comparison_vault.equal?(other_vault)
        return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:)
      elsif !(Envelope === other) && Ractor.shareable?(other)
        return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false)
      end
    end

    left  = value
    right = Envelope === other ? other.value : other
    return BasicObject.instance_method(:equal?).bind_call(left, right) if identity

    left == right
  end

  # @api private
  def inspect_with(inspector)
    super do
      inspector.breakable
      inspector.from_envelope(self, "value=")
    end
  end

  protected

  def comparison_vault = defined?(@vault) && @vault
  def comparison_key   = defined?(@vault_key) ? @vault_key : self
end

#claim! ⇒ Envelope

Attempts to claim the envelope for the current Ractor. Raises an exception if it was unable to claim the envelope.

Returns:

  • (Envelope) —

    The envelope if the claim was successful.

Raises:

  • (AlreadyClaimed) —

    If the envelope has already been claimed by another Ractor.



292
# File 'lib/farce/envelope.rb', line 292

def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor")

#claimed? ⇒ Boolean

Returns true if the envelope has been claimed by any Ractor, false otherwise.

Returns:

  • (Boolean) —

    true if the envelope has been claimed by any Ractor, false otherwise.



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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/farce/envelope.rb', line 24

class Envelope
  include Internal::Noncopyable
  include Internal::Inspect

  VAULT_ATOM = Internal::Atom.new
  private_constant :VAULT_ATOM

  include Shareable
  include Abstract::Value

  # Raised when trying to claim an envelope that has already been claimed by another Ractor.
  AlreadyClaimed = Class.new(Ractor::IsolationError)

  module Copyable
    include Internal::Copyable

    define_method(:dup, Object.instance_method(:dup))
    define_method(:clone, Object.instance_method(:clone))

    private

    def initialize_copy(other)
      Object.instance_method(:initialize_copy).bind_call(self, other)
    end
  end
  private_constant :Copyable

  # An envelope that copies its contents. Can be opened by multiple Ractors.
  # The value will be copied once when the envelope is created, and then once per Ractor that opens the envelope.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Copy, Share] A new envelope wrapping the given value.
  class Copy < Farce::Envelope
    include Copyable

    # @overload initialize(value)
    #   @param [Object] value The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new }
      super
      @vault.copy_in(self, value)
    end

    # @api private
    def marshal_dump
      storage = Internal::Storage.ractor
      [1, storage.key?(self) ? storage[self] : retrieve, auto_unwrap]
    end

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(value, manager)
      Internal::Storage.ractor[self] = value
    end

    # (see Envelope#claim)
    def claim = self

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = true

    protected

    def retrieve = @vault.copy_out(vault_key)

    private

    def initialize_dup(other)
      value      = other.retrieve
      @vault_key = Object.new.freeze
      super
      @vault.copy_in(@vault_key, value)
    end

    def initialize_clone(other, freeze: nil)
      value      = other.retrieve
      @vault_key = Object.new.freeze
      super
      @vault.copy_in(@vault_key, value)
    end

    def vault_key = defined?(@vault_key) ? @vault_key : self
  end

  # An envelope that moves its contents. Can only be opened by a single Ractor.
  # The value will be moved once when the envelope is created, and then once when the envelope is opened.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Move, Share] A new envelope wrapping the given value.
  class Move < Farce::Envelope
    include Internal::MarshalSupport::Reject
    include Shareable::Unfreezable

    # @overload initialize(value)
    #   @param [Object] value The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new }
      @owner = Internal::Atom.new
      super
      @vault.move_in(self, value)
    end

    # (see Envelope#claim)
    def claim
      owner = @owner.store_if_absent { Ractor.current }
      self if owner == Ractor.current
    end

    # (see Envelope#claimed?)
    def claimed? = !@owner.value.nil?

    # (see Envelope#owned?)
    def owned? = @owner.value == Ractor.current

    private def retrieve = @vault.move_out(self)
  end

  # An envelope that keeps its contents local to the Ractor that created it.
  # Can only be opened by the Ractor that created it.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Local, Share] A new envelope wrapping the given value.
  class Local < Farce::Envelope
    include Copyable

    # @overload initialize(value)
    #   @param value [Object] The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @owner = Ractor.current
      Internal::Storage.ractor[self] = value
      super
    end

    # @api private
    def marshal_dump
      raise TypeError, "local envelope belongs to another Ractor" unless owned?
      [1, Internal::Storage.ractor[self], auto_unwrap]
    end

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(value, manager)
    end

    # (see Envelope#claim)
    def claim = owned? ? self : nil

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = @owner == Ractor.current

    private

    def initialize_copy(other)
      super
      other.claim!
      Internal::Storage.ractor[self] = other.value.dup
    end
  end

  # An envelope that wraps a Ractor-shareable value. Can be opened by any Ractor.
  # Defeats the purpose of an envelope, but is provided for code that expects an envelope.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Share] A new envelope wrapping the given value.
  class Share < Farce::Envelope
    include Copyable

    attr_reader :value

    # @overload initialize(value)
    #   @param value [Object] The value to wrap in the envelope. Must be shareable.
    def initialize(value, auto_unwrap = nil)
      raise ArgumentError, "value must be shareable" unless Ractor.shareable?(value)
      @value = value
      super
    end

    # @api private
    def marshal_dump = [1, value, auto_unwrap]

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(Ractor.make_shareable(value), manager)
    end

    # (see Envelope#claim)
    def claim = self

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = true
  end

  # @note
  #   The keyword arguments are only accepted by Envelope itself, not by its subclasses.
  #   Also, if multiple keyword arguments are provided, `mode` takes precedence over `local`, which takes precedence
  #   over `move`.
  #
  # Creates an envelope wrapping the given value. If the value is Ractor-shareable, a {Share} envelope will be
  # created. Otherwise, the type of envelope will be determined by the keyword arguments.
  #
  # @overload new(value, move: false)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param move [Boolean] Whether to create a {Move} envelope. Ignored if `local` or `mode` is provided.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @overload new(value, local: false)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param local [Boolean] Whether to create a {Local} envelope. Ignored if `mode` is provided.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @overload new(value, mode:)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, or `:local`.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @return [Envelope] A new envelope wrapping the given value.
  def self.new(value, *, **)
    return super if self != Envelope
    return Share.new(value, *) if Ractor.shareable?(value)
    subclass_for(**).new(value, *)
  end

  def self.subclass_for(move: false, local: false, mode: nil)
    case mode
    when :move  then Move
    when :copy  then Copy
    when :local then Local
    when nil    then local ? Local : (move ? Move : Copy)
    else raise ArgumentError, "invalid mode: #{mode.inspect}"
    end
  end
  private_class_method :subclass_for

  # @api private
  attr_reader :auto_unwrap

  # @overload initialize(value)
  #   @param value [BasicObject] The value to wrap in the envelope.
  def initialize(_, auto_unwrap = nil)
    raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap)
    @auto_unwrap = auto_unwrap
    super()
  end

  # Attempts to claim the envelope for the current Ractor.
  # Raises an exception if it was unable to claim the envelope.
  # @return [Envelope] The envelope if the claim was successful.
  # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor.
  def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor")

  # Attempts to claim the envelope and retrieves the value if successful.
  # May be called multiple times.
  # @return [BasicObject] The value wrapped in the envelope.
  # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor.
  def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve }

  # Compares wrapped values. Compatible envelopes can be compared without
  # claiming or opening either envelope.
  # @param other [BasicObject, Envelope] The value or envelope to compare.
  # @param identity [Boolean] Whether to compare the values by identity instead of equality.
  # @return [Boolean] Whether the wrapped values match.
  def same_value?(other, identity: false)
    if comparison_vault
      other_vault = other.comparison_vault if Envelope === other
      if comparison_vault.equal?(other_vault)
        return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:)
      elsif !(Envelope === other) && Ractor.shareable?(other)
        return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false)
      end
    end

    left  = value
    right = Envelope === other ? other.value : other
    return BasicObject.instance_method(:equal?).bind_call(left, right) if identity

    left == right
  end

  # @api private
  def inspect_with(inspector)
    super do
      inspector.breakable
      inspector.from_envelope(self, "value=")
    end
  end

  protected

  def comparison_vault = defined?(@vault) && @vault
  def comparison_key   = defined?(@vault_key) ? @vault_key : self
end

#owned? ⇒ Boolean

Returns true if the envelope has been claimed by the current Ractor, false otherwise.

Returns:

  • (Boolean) —

    true if the envelope has been claimed by the current Ractor, false otherwise.



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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/farce/envelope.rb', line 24

class Envelope
  include Internal::Noncopyable
  include Internal::Inspect

  VAULT_ATOM = Internal::Atom.new
  private_constant :VAULT_ATOM

  include Shareable
  include Abstract::Value

  # Raised when trying to claim an envelope that has already been claimed by another Ractor.
  AlreadyClaimed = Class.new(Ractor::IsolationError)

  module Copyable
    include Internal::Copyable

    define_method(:dup, Object.instance_method(:dup))
    define_method(:clone, Object.instance_method(:clone))

    private

    def initialize_copy(other)
      Object.instance_method(:initialize_copy).bind_call(self, other)
    end
  end
  private_constant :Copyable

  # An envelope that copies its contents. Can be opened by multiple Ractors.
  # The value will be copied once when the envelope is created, and then once per Ractor that opens the envelope.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Copy, Share] A new envelope wrapping the given value.
  class Copy < Farce::Envelope
    include Copyable

    # @overload initialize(value)
    #   @param [Object] value The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new }
      super
      @vault.copy_in(self, value)
    end

    # @api private
    def marshal_dump
      storage = Internal::Storage.ractor
      [1, storage.key?(self) ? storage[self] : retrieve, auto_unwrap]
    end

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(value, manager)
      Internal::Storage.ractor[self] = value
    end

    # (see Envelope#claim)
    def claim = self

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = true

    protected

    def retrieve = @vault.copy_out(vault_key)

    private

    def initialize_dup(other)
      value      = other.retrieve
      @vault_key = Object.new.freeze
      super
      @vault.copy_in(@vault_key, value)
    end

    def initialize_clone(other, freeze: nil)
      value      = other.retrieve
      @vault_key = Object.new.freeze
      super
      @vault.copy_in(@vault_key, value)
    end

    def vault_key = defined?(@vault_key) ? @vault_key : self
  end

  # An envelope that moves its contents. Can only be opened by a single Ractor.
  # The value will be moved once when the envelope is created, and then once when the envelope is opened.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Move, Share] A new envelope wrapping the given value.
  class Move < Farce::Envelope
    include Internal::MarshalSupport::Reject
    include Shareable::Unfreezable

    # @overload initialize(value)
    #   @param [Object] value The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new }
      @owner = Internal::Atom.new
      super
      @vault.move_in(self, value)
    end

    # (see Envelope#claim)
    def claim
      owner = @owner.store_if_absent { Ractor.current }
      self if owner == Ractor.current
    end

    # (see Envelope#claimed?)
    def claimed? = !@owner.value.nil?

    # (see Envelope#owned?)
    def owned? = @owner.value == Ractor.current

    private def retrieve = @vault.move_out(self)
  end

  # An envelope that keeps its contents local to the Ractor that created it.
  # Can only be opened by the Ractor that created it.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Local, Share] A new envelope wrapping the given value.
  class Local < Farce::Envelope
    include Copyable

    # @overload initialize(value)
    #   @param value [Object] The value to wrap in the envelope.
    def initialize(value, auto_unwrap = nil)
      @owner = Ractor.current
      Internal::Storage.ractor[self] = value
      super
    end

    # @api private
    def marshal_dump
      raise TypeError, "local envelope belongs to another Ractor" unless owned?
      [1, Internal::Storage.ractor[self], auto_unwrap]
    end

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(value, manager)
    end

    # (see Envelope#claim)
    def claim = owned? ? self : nil

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = @owner == Ractor.current

    private

    def initialize_copy(other)
      super
      other.claim!
      Internal::Storage.ractor[self] = other.value.dup
    end
  end

  # An envelope that wraps a Ractor-shareable value. Can be opened by any Ractor.
  # Defeats the purpose of an envelope, but is provided for code that expects an envelope.
  #
  # @!method new(value)
  #   @!scope class
  #   @param (see Farce::Envelope#initialize)
  #   @return [Share] A new envelope wrapping the given value.
  class Share < Farce::Envelope
    include Copyable

    attr_reader :value

    # @overload initialize(value)
    #   @param value [Object] The value to wrap in the envelope. Must be shareable.
    def initialize(value, auto_unwrap = nil)
      raise ArgumentError, "value must be shareable" unless Ractor.shareable?(value)
      @value = value
      super
    end

    # @api private
    def marshal_dump = [1, value, auto_unwrap]

    # @api private
    def marshal_load(data)
      value, manager = Internal::MarshalSupport.payload(data, 2)
      initialize(Ractor.make_shareable(value), manager)
    end

    # (see Envelope#claim)
    def claim = self

    # (see Envelope#claimed?)
    def claimed? = true

    # (see Envelope#owned?)
    def owned? = true
  end

  # @note
  #   The keyword arguments are only accepted by Envelope itself, not by its subclasses.
  #   Also, if multiple keyword arguments are provided, `mode` takes precedence over `local`, which takes precedence
  #   over `move`.
  #
  # Creates an envelope wrapping the given value. If the value is Ractor-shareable, a {Share} envelope will be
  # created. Otherwise, the type of envelope will be determined by the keyword arguments.
  #
  # @overload new(value, move: false)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param move [Boolean] Whether to create a {Move} envelope. Ignored if `local` or `mode` is provided.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @overload new(value, local: false)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param local [Boolean] Whether to create a {Local} envelope. Ignored if `mode` is provided.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @overload new(value, mode:)
  #   @param value [BasicObject] The value to wrap in the envelope.
  #   @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, or `:local`.
  #   @return [Envelope] A new envelope wrapping the given value.
  #
  # @return [Envelope] A new envelope wrapping the given value.
  def self.new(value, *, **)
    return super if self != Envelope
    return Share.new(value, *) if Ractor.shareable?(value)
    subclass_for(**).new(value, *)
  end

  def self.subclass_for(move: false, local: false, mode: nil)
    case mode
    when :move  then Move
    when :copy  then Copy
    when :local then Local
    when nil    then local ? Local : (move ? Move : Copy)
    else raise ArgumentError, "invalid mode: #{mode.inspect}"
    end
  end
  private_class_method :subclass_for

  # @api private
  attr_reader :auto_unwrap

  # @overload initialize(value)
  #   @param value [BasicObject] The value to wrap in the envelope.
  def initialize(_, auto_unwrap = nil)
    raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap)
    @auto_unwrap = auto_unwrap
    super()
  end

  # Attempts to claim the envelope for the current Ractor.
  # Raises an exception if it was unable to claim the envelope.
  # @return [Envelope] The envelope if the claim was successful.
  # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor.
  def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor")

  # Attempts to claim the envelope and retrieves the value if successful.
  # May be called multiple times.
  # @return [BasicObject] The value wrapped in the envelope.
  # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor.
  def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve }

  # Compares wrapped values. Compatible envelopes can be compared without
  # claiming or opening either envelope.
  # @param other [BasicObject, Envelope] The value or envelope to compare.
  # @param identity [Boolean] Whether to compare the values by identity instead of equality.
  # @return [Boolean] Whether the wrapped values match.
  def same_value?(other, identity: false)
    if comparison_vault
      other_vault = other.comparison_vault if Envelope === other
      if comparison_vault.equal?(other_vault)
        return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:)
      elsif !(Envelope === other) && Ractor.shareable?(other)
        return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false)
      end
    end

    left  = value
    right = Envelope === other ? other.value : other
    return BasicObject.instance_method(:equal?).bind_call(left, right) if identity

    left == right
  end

  # @api private
  def inspect_with(inspector)
    super do
      inspector.breakable
      inspector.from_envelope(self, "value=")
    end
  end

  protected

  def comparison_vault = defined?(@vault) && @vault
  def comparison_key   = defined?(@vault_key) ? @vault_key : self
end

#same_value?(other, identity: false) ⇒ Boolean

Compares wrapped values. Compatible envelopes can be compared without claiming or opening either envelope.

Parameters:

  • other (BasicObject, Envelope) —

    The value or envelope to compare.

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

    Whether to compare the values by identity instead of equality.

Returns:

  • (Boolean) —

    Whether the wrapped values match.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/farce/envelope.rb', line 305

def same_value?(other, identity: false)
  if comparison_vault
    other_vault = other.comparison_vault if Envelope === other
    if comparison_vault.equal?(other_vault)
      return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:)
    elsif !(Envelope === other) && Ractor.shareable?(other)
      return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false)
    end
  end

  left  = value
  right = Envelope === other ? other.value : other
  return BasicObject.instance_method(:equal?).bind_call(left, right) if identity

  left == right
end

#value ⇒ BasicObject

Attempts to claim the envelope and retrieves the value if successful. May be called multiple times.

Returns:

  • (BasicObject) —

    The value wrapped in the envelope.

Raises:

  • (AlreadyClaimed) —

    If the envelope has already been claimed by another Ractor.



298
# File 'lib/farce/envelope.rb', line 298

def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve }