Class: Farce::ModeManager

Inherits:
Object
  • Object
show all
Includes:
Internal::Copyable, Shareable::Immutable
Defined in:
lib/farce/mode_manager.rb

Overview

A mode manager encapsulates logic for dealing with Ruby objects that aren't Ractor-shareable. It is a perfect companion piece for APIs that require Ractor-shareable objects.

For instance, Ratomic::Queue expects Ractor-shareable objects without checking for them. Creating a thin wrapper with a mode manager allows you to use it safely and with non-shareable objects.

require "ratomic"
require "farce"

class MyQueue
  include Farce::Shareable

  def initialize(capacity: 1024, mode: :copy)
    @queue   = Ratomic::Queue.new(capacity)
    @manager = Farce::ModeManager.new(mode:)
  end

  def pop = @manager.unwrap(@queue.pop)

  def push(value, mode: nil)
    @queue.push(@manager.wrap(value, mode:))
    self
  end
end

queue = MyQueue.new(capacity: 100, mode: :move)

# consume the queue on a different Ractor
Ractor.new(queue) { |q| p Ractor.shareable?(q.pop) }

object = Object.new
q.push(object)

# push moved the object into the queue, so it is no longer accessible on the current Ractor
Ractor::MovedObject === object # => true

It is recommended to use a different mode manager for each instance of a queue, port, or whatever else you're wrapping, as #wrap may create an Envelope and #unwrap will only unwrap envelopes created by the same mode manager. This way users can still safely send envelopes they created between Ractors without them getting unexpectedly claimed.

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Shareable

#ractor_shareable?

Methods included from Internal::Copyable

#duplicable?

Constructor Details

#initialize(mode: :copy, register: nil) ⇒ ModeManager

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 Farce::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:

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

    The default mode to use when wrapping values. Must be one of the valid modes.

  • register (Proxy::Register, nil) (defaults to: nil) —

    The register to use for generating Farce::Proxy instances.

Raises:

  • (ArgumentError)


61
62
63
64
65
66
# File 'lib/farce/mode_manager.rb', line 61

def initialize(mode: :copy, register: nil)
  raise ArgumentError, "invalid mode: #{mode.inspect}" unless Farce::MODES.include?(mode)
  @mode     = mode
  @register = register
  super()
end

Instance Attribute Details

#mode ⇒ Symbol (readonly)

Returns The default mode to use when wrapping values.

Returns:

  • (Symbol) —

    The default mode to use when wrapping values.



56
57
58
# File 'lib/farce/mode_manager.rb', line 56

def mode
  @mode
end

Instance Method Details

#managed_envelope?(value) ⇒ Boolean

Returns Whether the value is an envelope managed by this mode manager.

Returns:

  • (Boolean) —

    Whether the value is an envelope managed by this mode manager.



140
# File 'lib/farce/mode_manager.rb', line 140

def managed_envelope?(value) = Envelope === value && value.auto_unwrap.equal?(self)

#same_value?(left, right, identity: false) ⇒ Boolean

Compares two values managed by this instance. Compatible envelopes can be compared without claiming or opening them.

Parameters:

  • left (BasicObject, Envelope) —

    The stored value.

  • right (BasicObject, Envelope) —

    The comparison value.

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

    Whether to compare by identity instead of equality.

Returns:

  • (Boolean) —

    Whether the values match.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/farce/mode_manager.rb', line 120

def same_value?(left, right, identity: false)
  left_managed  = managed_envelope?(left)
  right_managed = managed_envelope?(right)

  if left_managed && right_managed
    return false if identity && Envelope::Move === left && Envelope::Local === right && !left.owned?
    return left.same_value?(right, identity:)
  end

  return false if identity && left_managed
  return left.same_value?(right) if left_managed

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

  left == right
end

#unwrap(value) ⇒ BasicObject

Unwraps any envelope created by this mode manager, returning the value inside. Other options, including other envelopes, will be returned as-is.

Parameters:

  • value (BasicObject, Envelope) —

    The value to unwrap.

Returns:

  • (BasicObject) —

    The value inside the envelope if it was created by this mode manager, or the value itself if it was not.



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

def unwrap(value)
  return value unless managed_envelope?(value)
  value.value
end

#wrap(value, mode: nil) ⇒ BasicObject, Envelope

Wraps a value in an envelope if it is not Ractor-shareable.

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 Farce::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:

  • value (BasicObject) —

    The value to wrap.

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

    The mode to use when wrapping the value. If nil, the default mode will be used.

Returns:

  • (BasicObject, Envelope) —

    The prepared value, or a managed envelope containing it.



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

def wrap(value, mode: nil)
  return value if Ractor.shareable?(value)

  case mode || self.mode
  when :copy           then Envelope::Copy.new(value, self)
  when :dedup          then Ractor.make_shareable(Farce.dedup(value))
  when :local          then Envelope::Local.new(value, self)
  when :make_shareable then Ractor.make_shareable(value)
  when :move           then Envelope::Move.new(value, self)
  when :mutable        then Mutable.new(value)
  when :raise          then raise Ractor::IsolationError, "value is not Ractor-shareable: #{value.inspect}"
  when :shareable_copy then Ractor.make_shareable(value, copy: true)
  when :proxy          then Proxy.new(value, register: @register)
  else raise ArgumentError, "invalid mode: #{mode.inspect}"
  end
end