Module: Farce

Defined in:
lib/farce/map.rb,
lib/farce/_yard/ractor.rb,
lib/farce/_yard/internal.rb,
lib/farce/set.rb,
lib/farce/atom.rb,
lib/farce/flag.rb,
lib/farce/lazy.rb,
lib/farce/lock.rb,
lib/farce/pool.rb,
lib/farce/port.rb,
lib/farce/clock.rb,
lib/farce/error.rb,
lib/farce/lease.rb,
lib/farce/local.rb,
lib/farce/proxy.rb,
lib/farce/queue.rb,
lib/farce/config.rb,
lib/farce/ractor.rb,
lib/farce/resolv.rb,
lib/farce/signal.rb,
lib/farce/strict.rb,
lib/farce/system.rb,
lib/farce/unsafe.rb,
lib/farce/vector.rb,
lib/farce/walker.rb,
lib/farce/counter.rb,
lib/farce/deduper.rb,
lib/farce/lfu_map.rb,
lib/farce/lru_map.rb,
lib/farce/mutable.rb,
lib/farce/version.rb,
lib/farce/abstract.rb,
lib/farce/envelope.rb,
lib/farce/lazy_ref.rb,
lib/farce/molecule.rb,
lib/farce/tree_map.rb,
lib/farce/unshared.rb,
lib/farce/weak_map.rb,
lib/farce/weak_ref.rb,
lib/farce/weak_set.rb,
lib/farce/exchanger.rb,
lib/farce/lease_map.rb,
lib/farce/local/map.rb,
lib/farce/local/set.rb,
lib/farce/reference.rb,
lib/farce/scheduler.rb,
lib/farce/shareable.rb,
lib/farce/weak_atom.rb,
lib/farce/lease_pool.rb,
lib/farce/local/atom.rb,
lib/farce/local/flag.rb,
lib/farce/local/lazy.rb,
lib/farce/resolv/dns.rb,
lib/farce/sorted_set.rb,
lib/farce/strict/map.rb,
lib/farce/strict/set.rb,
lib/farce/weak_value.rb,
lib/farce/local/lease.rb,
lib/farce/local/queue.rb,
lib/farce/strict/atom.rb,
lib/farce/strict/flag.rb,
lib/farce/strict/lazy.rb,
lib/farce/strict/port.rb,
lib/farce/timer_queue.rb,
lib/farce/transaction.rb,
lib/farce/unshareable.rb,
lib/farce/abstract/map.rb,
lib/farce/abstract/set.rb,
lib/farce/class_mirror.rb,
lib/farce/integrations.rb,
lib/farce/local/scoped.rb,
lib/farce/local/vector.rb,
lib/farce/mode_manager.rb,
lib/farce/strict/lease.rb,
lib/farce/strict/queue.rb,
lib/farce/unshared/map.rb,
lib/farce/unshared/set.rb,
lib/farce/weak_key_map.rb,
lib/farce/abstract/atom.rb,
lib/farce/abstract/flag.rb,
lib/farce/abstract/lazy.rb,
lib/farce/abstract/port.rb,
lib/farce/local/counter.rb,
lib/farce/local/lfu_map.rb,
lib/farce/local/lru_map.rb,
lib/farce/proxy/wrapper.rb,
lib/farce/strict/vector.rb,
lib/farce/unshared/atom.rb,
lib/farce/unshared/flag.rb,
lib/farce/unshared/lazy.rb,
lib/farce/abstract/lease.rb,
lib/farce/abstract/queue.rb,
lib/farce/abstract/value.rb,
lib/farce/local/lazy_ref.rb,
lib/farce/local/molecule.rb,
lib/farce/local/tree_map.rb,
lib/farce/local/weak_map.rb,
lib/farce/local/weak_set.rb,
lib/farce/priority_queue.rb,
lib/farce/proxy/register.rb,
lib/farce/strict/counter.rb,
lib/farce/strict/lfu_map.rb,
lib/farce/strict/lru_map.rb,
lib/farce/unsafe/lfu_map.rb,
lib/farce/unsafe/lru_map.rb,
lib/farce/unshared/lease.rb,
lib/farce/unshared/queue.rb,
lib/farce/weak_value_map.rb,
lib/farce/abstract/vector.rb,
lib/farce/integrations/oj.rb,
lib/farce/local/lease_map.rb,
lib/farce/local/weak_atom.rb,
lib/farce/read_write_lock.rb,
lib/farce/strict/lazy_ref.rb,
lib/farce/strict/molecule.rb,
lib/farce/strict/tree_map.rb,
lib/farce/strict/weak_map.rb,
lib/farce/strict/weak_set.rb,
lib/farce/transaction/map.rb,
lib/farce/transaction/set.rb,
lib/farce/unsafe/tree_map.rb,
lib/farce/unshared/vector.rb,
lib/farce/abstract/counter.rb,
lib/farce/abstract/lfu_map.rb,
lib/farce/abstract/lru_map.rb,
lib/farce/local/lease_pool.rb,
lib/farce/local/sorted_set.rb,
lib/farce/proxy/supervisor.rb,
lib/farce/strict/exchanger.rb,
lib/farce/strict/lease_map.rb,
lib/farce/strict/weak_atom.rb,
lib/farce/thread_scheduler.rb,
lib/farce/transaction/atom.rb,
lib/farce/unshared/counter.rb,
lib/farce/unshared/lfu_map.rb,
lib/farce/unshared/lru_map.rb,
lib/farce/abstract/molecule.rb,
lib/farce/abstract/tree_map.rb,
lib/farce/abstract/weak_map.rb,
lib/farce/abstract/weak_set.rb,
lib/farce/integrations/bson.rb,
lib/farce/integrations/cbor.rb,
lib/farce/local/timer_queue.rb,
lib/farce/strict/lease_pool.rb,
lib/farce/strict/sorted_set.rb,
lib/farce/unshared/lazy_ref.rb,
lib/farce/unshared/molecule.rb,
lib/farce/unshared/tree_map.rb,
lib/farce/unshared/weak_map.rb,
lib/farce/unshared/weak_set.rb,
lib/farce/abstract/exchanger.rb,
lib/farce/abstract/lease_map.rb,
lib/farce/abstract/scheduler.rb,
lib/farce/abstract/weak_atom.rb,
lib/farce/integrations/psych.rb,
lib/farce/local/weak_key_map.rb,
lib/farce/strict/timer_queue.rb,
lib/farce/transaction/vector.rb,
lib/farce/unshared/lease_map.rb,
lib/farce/unshared/weak_atom.rb,
lib/farce/walker/definitions.rb,
lib/farce/abstract/collection.rb,
lib/farce/abstract/lease_pool.rb,
lib/farce/abstract/sorted_set.rb,
lib/farce/strict/weak_key_map.rb,
lib/farce/transaction/mutable.rb,
lib/farce/transaction/wrapper.rb,
lib/farce/unshared/lease_pool.rb,
lib/farce/unshared/sorted_set.rb,
lib/farce/walker/modification.rb,
lib/farce/abstract/bounded_map.rb,
lib/farce/abstract/timer_queue.rb,
lib/farce/integrations/msgpack.rb,
lib/farce/integrations/weakref.rb,
lib/farce/local/priority_queue.rb,
lib/farce/local/weak_value_map.rb,
lib/farce/transaction/molecule.rb,
lib/farce/transaction/tree_map.rb,
lib/farce/unshared/timer_queue.rb,
lib/farce/abstract/weak_key_map.rb,
lib/farce/strict/priority_queue.rb,
lib/farce/strict/weak_value_map.rb,
lib/farce/unshared/weak_key_map.rb,
lib/farce/integrations/dry_types.rb,
lib/farce/transaction/sorted_set.rb,
lib/farce/abstract/concurrent_map.rb,
lib/farce/abstract/duplicable_map.rb,
lib/farce/abstract/priority_queue.rb,
lib/farce/abstract/weak_value_map.rb,
lib/farce/integrations/concurrent.rb,
lib/farce/integrations/sorted_set.rb,
lib/farce/unshared/priority_queue.rb,
lib/farce/unshared/weak_value_map.rb,
lib/farce/integrations/ractor_tmvar.rb,
lib/farce/transaction/map_operations.rb,
lib/farce/transaction/set_operations.rb,
lib/farce/integrations/ractor_sharing.rb,
lib/farce/integrations/shared/to_json.rb,
lib/farce/integrations/active_support/map.rb,
lib/farce/integrations/active_support/set.rb,
lib/farce/integrations/active_support/blank.rb,
lib/farce/integrations/active_support/clock.rb,
lib/farce/integrations/active_support/vector.rb,
lib/farce/integrations/active_support/duplicable.rb,
lib/farce/integrations/active_support/value_serialization.rb,
lib/farce.rb

Overview

Namespace for everything provided by Farce.

Including Farce

Including Farce in a class or module will include all public camel-case constants defined under the Farce namespace.

require "farce"

# This could also be done under a class or module, to avoid polluting the global namespace.
include Farce

# Now Ractor is available, even on JRuby or TruffleRuby!
Ractor.new { puts "Hello from a Ractor!" }

# Other constants are also available.
Clock.parse(1.minute.from_now)

# VERSION is not exposed. This is to avoid polluting other libraries with it.
defined?(VERSION) # => false

Defined Under Namespace

Modules: Abstract, Clock, Local, MessagePack, Ractor, Resolv, Shareable, Strict, System, Unsafe, Unshareable, Unshared Classes: Atom, ClassMirror, Config, Counter, Deduper, Envelope, Exchanger, Flag, LFUMap, LRUMap, Lazy, LazyRef, Lease, LeaseMap, LeasePool, Lock, Map, ModeManager, Molecule, Mutable, Pool, Port, PriorityQueue, Proxy, Queue, ReadWriteLock, Reference, Scheduler, Set, Signal, SortedSet, ThreadScheduler, TimerQueue, Transaction, TreeMap, Vector, Walker, WeakAtom, WeakKeyMap, WeakMap, WeakRef, WeakSet, WeakValue, WeakValueMap

Constant Summary collapse

ClosedQueueError =

Raised when an operation cannot proceed because a queue is closed.

Class.new(::ClosedQueueError)
SealedQueueError =

Raised when a push cannot proceed because a queue is sealed.

Class.new(ClosedQueueError)
SchedulerClosedError =

Raised when attempting to schedule a task on a closed scheduler.

Class.new(StandardError)
PoolClosedError =

Raised when attempting to schedule a task on a closed pool.

Class.new(SchedulerClosedError)
TimeoutError =

Raised when a timed operation does not complete before its timeout.

Class.new(StandardError)
OwnershipError =

Raised when an operation requires ownership by the current Fiber.

Class.new(StandardError)
RetiredLeaseError =

Raised when attempting to use a permanently retired lease.

Class.new(StandardError)
WeakRefError =

Raised when a weak reference is no longer valid because the referenced object has been garbage collected.

Class.new(defined?(::WeakRef::RefError) ? ::WeakRef::RefError : StandardError)
VERSION =

Returns the current version of Farce.

Returns:

  • (String) —

    the current version of Farce

"0.1.0"
SCOPES =

The valid storage scopes for local values and collections.

Returns:

  • (::Set<Symbol>)
::Set[:ractor, :thread_group, :thread, :fiber_storage, :fiber].freeze
MODES =

The valid transfer modes for values that are not Ractor-shareable.

Returns:

  • (::Set<Symbol>)
::Set[:copy, :move, :local, :make_shareable, :mutable, :raise, :shareable_copy, :dedup, :proxy].freeze

Dry Types Integration collapse

Class Method Summary collapse

Class Method Details

.clock ⇒ Float .clock(value) ⇒ Float .clock(at:) ⇒ Float .clock(time:) ⇒ Float .clock(timeout_at:) ⇒ Float .clock(delay:) ⇒ Float .clock(offset:) ⇒ Float .clock(timeout:) ⇒ Float .clock(wait:) ⇒ Float

Returns monotonic clock time in seconds, from when clock was called the first time.

Overloads:

  • .clock ⇒ Float

    The current clock time

  • .clock(value) ⇒ Float

    Parses value into a monotonic clock time.

    Parameters:

    • value (nil, Numeric, Time, ActiveSupport::Duration, Hash) —

      the value to convert to clock time

    See Also:

  • .clock(at:) ⇒ Float

    Gives the clock time for a fixed point in time. Independent of the current time.

    Parameters:

    • at (Numeric, Time) —

      the value to convert to clock time

  • .clock(time:) ⇒ Float

    Gives the clock time for a fixed point in time. Independent of the current time.

    Parameters:

    • time (Numeric, Time) —

      the value to convert to clock time

  • .clock(timeout_at:) ⇒ Float

    Gives the clock time for a fixed point in time. Independent of the current time.

    Parameters:

    • timeout_at (Numeric, Time) —

      the value to convert to clock time

  • .clock(delay:) ⇒ Float

    Gives the clock time for a relative offset from the current time.

    Parameters:

    • delay (Numeric) —

      the value to convert to clock time

  • .clock(offset:) ⇒ Float

    Gives the clock time for a relative offset from the current time.

    Parameters:

    • offset (Numeric) —

      the value to convert to clock time

  • .clock(timeout:) ⇒ Float

    Gives the clock time for a relative offset from the current time.

    Parameters:

    • timeout (Numeric) —

      the value to convert to clock time

  • .clock(wait:) ⇒ Float

    Gives the clock time for a relative offset from the current time.

    Parameters:

    • wait (Numeric) —

      the value to convert to clock time

Returns:

  • (Float) —

    monotonic clock time in seconds, from when clock was called the first time



144
# File 'lib/farce.rb', line 144

def self.clock(...) = Clock.parse(...)

.config ⇒ Config

Returns the global Farce configuration.

Returns:

  • (Config) —

    the global Farce configuration



225
226
227
228
# File 'lib/farce/config.rb', line 225

def self.config(&)
  configure(&) if block_given?
  CONFIG
end

.configure {|config| ... } ⇒ BasicObject

Configures Farce.

Examples:

Farce.configure do |config|
  config.fiber_scheduler = :carbon_fiber
end

Yields:

  • (config) —

    the configuration object



221
# File 'lib/farce/config.rb', line 221

def self.configure = yield CONFIG

.dedup(object, copy: false, skip: nil) ⇒ Object .dedup ⇒ Deduper

Deduplicate values using the default Deduper. Cached values are held weakly, so retaining only an object_id does not keep its canonical object alive. Keep the returned object to preserve its identity.

Overloads:

  • .dedup(object, copy: false, skip: nil) ⇒ Object

    Reuse equal strings and frozen containers throughout an object graph.

    Examples:

    Preserve the input

    first = Farce.dedup(["foo"], copy: true)
    Farce.dedup(["foo"]).equal?(first) # => true

    Parameters:

    • object (Object) —

      the root object

    • copy (Boolean, Symbol) (defaults to: false) —

      false to update the input, true to copy it, or a copy method such as :clone

    • skip (Module, Array<Module>, nil) (defaults to: nil) —

      additional classes or modules to skip

    Returns:

    • (Object) —

      the deduplicated result

  • .dedup ⇒ Deduper

    Return the default deduper to configure subsequent calls.

    Examples:

    Exclude a class and its children

    Farce.dedup.skip(SomeClass)

    Cache another value class

    Farce.dedup.store(MyValue)

    Returns:

See Also:



169
170
171
172
# File 'lib/farce.rb', line 169

def self.dedup(object = UNDEFINED, **)
  return DEDUPER if UNDEFINED.equal?(object)
  DEDUPER.dedup(object, **)
end

.DryTypes(*namespaces, default: UNDEFINED, variant: :shared, mode: UNDEFINED, scope: UNDEFINED, **aliases) ⇒ Module

Note:

This methods is only available if dry-types has been loaded.

Build a dry-types import for Farce types.

With no dry namespace options, the import uses the nearest Dry.Types() import already included in the receiving module. Without one, it uses the same strict defaults as Dry.Types(). Dry namespace arguments, default:, and aliases select an independent source import using Dry.Types() rules.

Parameters:

  • namespaces (Array<Symbol>) —

    Dry type namespaces to import Farce counterparts for.

  • default (Symbol) (defaults to: UNDEFINED) —

    The Dry namespace used for root Farce type constants.

  • variant (Symbol) (defaults to: :shared) —

    The output variant: :shared, :strict, :unshared, or :local.

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

    The transfer mode for the shared variant.

  • scope (Symbol) (defaults to: UNDEFINED) —

    The storage scope for the local variant.

  • aliases (Hash{Symbol => Symbol}) —

    Dry namespace aliases.

Returns:

  • (Module) —

    A module for inclusion beside Dry.Types().



688
689
690
691
692
693
# File 'lib/farce/integrations/dry_types.rb', line 688

def self.DryTypes( # rubocop:disable Naming/MethodName
  *namespaces, default: UNDEFINED, variant: :shared, mode: UNDEFINED, scope: UNDEFINED, **aliases
)
  internal = const_get(:Internal, false).const_get(:DryTypes, false)
  internal.import(namespaces, default:, aliases:, variant:, mode:, scope:)
end

.enfarce(object, freeze: nil, mode: :copy) {|object| ... } ⇒ BasicObject

Converts vanilla Ruby objects into their Farce equivalents.

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

Examples:

Farce.enfarce({ foo: ["bar"] }) # =>  #<Farce::Map {foo: #<Farce::Vector ["bar"]>}>

Parameters:

  • object (BasicObject) —

    The root object to convert

  • freeze (Boolean, nil) (defaults to: nil) —

    Whether to freeze the converted objects. If set to nil (default), the freezing behavior will depend on the original object's frozen state.

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

    The conversion mode, e.g., :copy.

Yields:

  • (object) —

    Optional block, called with any object that doesn't have a specific conversion defined.

Yield Parameters:

  • object (BasicObject) —

    the object to convert

Yield Returns:

  • (BasicObject) —

    the converted object

Returns:

  • (BasicObject) —

    the converted object

See Also:



195
# File 'lib/farce.rb', line 195

def self.enfarce(object, **, &) = Internal::Converter.new(self, **, &).convert(object)

.freeze_graph(object, freeze_modules: false, traverse_modules: freeze_modules) ⇒ Object

Recursively freeze an object graph using Walker traversal.

Visits container elements, hash keys and values, and instance variables. Shared children and cycles are preserved.

Classes and modules are skipped by default. If it is enabled, then traversal visits instance variables, directly defined public constants, and directly defined class variables. Autoloads are skipped.

Freezes objects in place and returns the original root. Already frozen objects are still traversed so their mutable children are frozen too.

Examples:

Freeze nested values in place

values = { tags: [+"ruby"] }
Farce.freeze_graph(values).equal?(values)        # => true
values[:tags].frozen?                           # => true
values[:tags].first.frozen?                     # => true

Freeze children of an already frozen container

values = [+"ruby"].freeze
Farce.freeze_graph(values).equal?(values)        # => true
values.first.frozen?                            # => true

Freeze module state while allowing new methods

mod = Module.new
mod.instance_variable_set(:@tags, [+"ruby"])
Farce.freeze_graph(mod, traverse_modules: true)
mod.instance_variable_get(:@tags).first.frozen? # => true
mod.frozen?                                     # => false

Parameters:

  • object (Object) —

    the root object

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

    whether to freeze classes and modules

  • traverse_modules (Boolean) (defaults to: freeze_modules) —

    whether to visit class and module instance variables. Defaults to freeze_modules, but can be set independently.

Returns:

  • (Object) —

    the original root. Skipped modules retain their frozen state.

See Also:



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/farce.rb', line 233

def self.freeze_graph(object, freeze_modules: false, traverse_modules: freeze_modules)
  Walker.visit(object) do |node, walker|
    if Module === node
      traverse = traverse_modules
      freeze   = freeze_modules
    else
      traverse = true
      freeze   = true
    end

    walker.traverse if traverse
    node.freeze if freeze
    node
  end
end

.in_parallel(*args, mode: :copy) {|*args| ... } ⇒ nil .in_parallel ⇒ Abstract::Scheduler

Schedules work without waiting for the block to finish. On CRuby, uses a shared Ractor pool with at most Farce::System.cpu_count workers. On JRuby and TruffleRuby, starts a new thread for each task.

Without a block, returns the shared scheduler. The CRuby pool starts workers when tasks arrive. Pass mutable task data as arguments so it can be transferred based on the given 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 Farce::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.

Examples:

Farce.in_parallel("hello") { |message| puts message.upcase }
Farce.in_parallel.schedule { puts "another task" }

Overloads:

  • .in_parallel(*args, mode: :copy) {|*args| ... } ⇒ nil

    Parameters:

    • args (Array<Object>) —

      Arguments passed to the block.

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

      Argument transfer mode. Ignored on JRuby and TruffleRuby.

    Yields:

    • (*args) —

      The task to schedule.

    Returns:

    • (nil)
  • .in_parallel ⇒ Abstract::Scheduler

    Returns The shared scheduler.

    Returns:

Returns:

See Also:



452
453
454
455
456
457
458
459
460
461
# File 'lib/farce.rb', line 452

def self.in_parallel(*args, mode: UNDEFINED, &)
  unless block_given?
    raise LocalJumpError, "no block given" unless args.empty? && UNDEFINED.equal?(mode)
    return Internal::ParallelScheduler
  end

  mode = :copy if UNDEFINED.equal?(mode)
  Internal::ParallelScheduler.schedule(*args, mode:, &)
  nil
end

.on_main(*args, mode: :copy) {|*args| ... } ⇒ nil .on_main ⇒ Abstract::Scheduler

Allows executing code on the main ractor from other ractors. This allows modifying objects and calling methods only accessible from the main ractor.

If a block is given, executes it on the main ractor, passing any given arguments to it. Blocks the current thread until the block has finished executing.

Arguments are transferred based on the given 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 Farce::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. Calls from the main ractor execute directly, preserving the block and arguments.

If no block is given, it returns a scheduler to execute tasks on the main ractor. This allows scheduling without blocking the current thread.

On JRuby and TruffleRuby, blocks run inline because there is no native Ractor isolation. The returned ThreadScheduler starts a new thread for each scheduled task.

Examples:

$results = []

Farce::Ractor.new do
  # maybe computing this string on the main ractor is too expensive?
  my_string = "foo bar baz"

  # can't access $results on the current ractor directly, as it isn't shareable
  Farce.on_main(my_string) { $results << it }
end.join

$results # => ["foo bar baz"]

Blocking vs non-blocking

Farce::Ractor.new do
  # This blocks the current thread until the block has finished executing.
  Farce.on_main do
    sleep 1
    puts "Hi from the main ractor!"
  end

  # This does not block the current thread.
  Farce.on_main.schedule do
    sleep 1
    puts "Hi again from the main ractor!"
  end

  puts "Hi from the current ractor!"
end

sleep 3 # Wait for all scheduled tasks to complete.

# Expected output:
# Hi from the main ractor!
# Hi from the current ractor!
# Hi again from the main ractor!

Overloads:

  • .on_main(*args, mode: :copy) {|*args| ... } ⇒ nil

    Parameters:

    • args (Array) —

      The arguments to be passed to the block.

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

      The argument transfer mode when called from another ractor.

    Yields:

    • (*args) —

      The block to be executed on the main ractor.

    Yield Parameters:

    • The (*args) —

      arguments passed to the block.

    Returns:

    • (nil)
  • .on_main ⇒ Abstract::Scheduler

    Returns a scheduler that executes tasks on the main ractor.

    Returns:

Returns:

See Also:



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/farce.rb', line 412

def self.on_main(*args, mode: UNDEFINED, &)
  unless block_given?
    raise LocalJumpError, "no block given" unless args.empty? && UNDEFINED.equal?(mode)
    return Internal::MainScheduler
  end

  if Ractor.main?
    yield(*args)
  else
    mode = :copy if UNDEFINED.equal?(mode)
    Internal::MainScheduler.execute(*args, mode:, auto_local: false, &)
  end

  nil
end

.rebind(self: nil, lambda: nil) { ... } ⇒ Proc .rebind(bindable, self: nil, lambda: nil) ⇒ Proc, Method

Binds a proc, lambda, block, bound or unbound method to a new self.

This is similar to the following common approaches:

  1. Ractor: using Ractor.shareable_proc/Ractor.shareable_lambda
  2. BasicObject: using #instance_exec/#instance_eval (used by many DSLs, like Hanami or the dry-rb gems)
  3. Module: using #define_method and method binding (used by many DSLs, like Sinatra or the money gem)

Approach 2 and 3 are very popular for DSLs, but the resulting objects cannot be shared across ractors. The first approach fixes that, but only works on a limited number of procs and receivers, and is not available on all Ruby implementations.

Property Ractor BasicObject Module Farce
Works with every proc 🚫 no ✅ yes ✅ yes ✅ yes
Results can be made shareable ✅ yes 🚫 no 🚫 no ✅ yes
Preserves parameters ✅ yes 🚫 no ✅ yes ✅ yes
Procs can accept blocks ✅ yes 🚫 no ✅ yes ✅ yes
Preserves lambda-ness ⚠️ manually 🚫 no 🚫 no ✅ yes
Works on JRuby and TruffleRuby 🚫 no ✅ yes ✅ yes ✅ yes
Performance overhead ✅ none ⚠️ up to 200% ⚠️ up to 80% ✅ none

If you want the proc to also be shareable, use Farce::Ractor.shareable_proc instead.

The performance overhead of BasicObject#instance_exec/BasicObject#instance_eval is the most significant on the official Ruby implementation, but is still present on JRuby, which does not exhibit a performance penalty for Module#define_method. On TruffleRuby, all approaches have similar performance. The more work is done inside the proc, the less significant the overhead becomes.

Examples:

# Rebinding a block to a new self
callback = Farce.rebind(self: 42) { self * 10 }
callback.call # => 420

# Rebound procs preserve parameters and can accept blocks
block   = ->(key, &fallback) { fetch(key, &fallback) }
rebound = Farce.rebind(block, self: { a: 1, b: 2 })
rebound.call(:a) { 0 } # => 1
rebound.call(:c) { 0 } # => 0
rebound.parameters == block.parameters # => true

# Rebinding an unbound method to a new self returns a bound method
method = Object.instance_method(:inspect) # => #<UnboundMethod>
method = Farce.rebind(method, self: 42)   # => #<Method>
method.receiver # => 42
method.call     # => "42"

# lambda-ness is preserved
Farce.rebind(proc {}).lambda?   # => false
Farce.rebind(lambda {}).lambda? # => true

# If there is no binding change, identity is preserved
Farce.rebind(&:to_s) == :to_s.to_proc # => true

Overloads:

  • .rebind(self: nil, lambda: nil) { ... } ⇒ Proc

    Returns The bound proc.

    Parameters:

    • self (BasicObject) (defaults to: nil) —

      The new self to bind to.

    Yields:

    • The block to bind to the new self.

    Returns:

    • (Proc) —

      The bound proc.

    Yield Receiver:

    • (BasicObject) —

      The object passed as self (or nil)

  • .rebind(bindable, self: nil, lambda: nil) ⇒ Proc, Method

    Returns The bound proc or method. A method is returned if the argument was a Method or UnboundMethod, otherwise a Proc is returned.

    Parameters:

    • bindable (Proc, Method, UnboundMethod) —

      The proc or method to bind to the new self.

    • self (Object) (defaults to: nil) —

      The new self to bind to.

    Returns:

    • (Proc, Method) —

      The bound proc or method. A method is returned if the argument was a Method or UnboundMethod, otherwise a Proc is returned.

Returns:

  • (Proc, Method) —

    The bound proc or method.

Raises:

  • (ArgumentError)


323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/farce.rb', line 323

def self.rebind(bindable = nil, lambda: nil, **self_option, &block)
  raise ArgumentError, "more than one block given" if bindable && block
  raise ArgumentError, "tried to create Proc object without a block" unless bindable ||= block
  new_self = Internal.self_option(self_option)

  if bindable.is_a? Proc
    return bindable if !Internal.rebindable?(bindable) || bindable.binding.receiver.equal?(new_self)
    rebound = Internal.rebind(bindable, new_self, lambda)
    rebound.freeze if bindable.frozen?
    return rebound
  end

  if bindable.is_a? Method
    return bindable if bindable.receiver.equal?(new_self)
    bindable = bindable.unbind
  end

  return bindable.bind(new_self) if bindable.is_a? UnboundMethod
  raise ArgumentError, "invalid bindable: #{bindable.inspect}"
end

.schedule(*args, mode: :copy, auto_local: true, **kwargs) {|*args| ... } ⇒ nil

Schedules work to be executed out of band.

If the mode is set to :local, it will use the current thread's fiber scheduler to schedule the task. If no fiber scheduler is available, it will create or reuse a Ractor-local scheduler (on the main Ractor, this is the same scheduler as on_main uses).

If auto_local is set to true (but with a different mode), the same logic is used as in local mode, except it will not create a new ractor-local scheduler if one is not already available.

Examples:

Scheduling work

# just run this asynchronously, don't care how
Farce.schedule("hello") { |message| puts message.upcase }

Using a fiber scheduler

Async do
  # this is basically the same as calling Async { do_something }
  Farce.schedule { do_something }
end

Parameters:

  • args (Array<Object>) —

    Arguments passed to the block.

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

    Argument transfer mode. Ignored on JRuby and TruffleRuby.

  • auto_local (Boolean) (defaults to: true) —

    Whether to automatically use the local scheduler if available.

  • kwargs (Hash) —

    Additional keyword arguments passed to the scheduler.

Yields:

  • (*args) —

    The task to schedule.

Returns:

  • (nil)

Raises:

  • (LocalJumpError)


489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/farce.rb', line 489

def self.schedule(*, mode: :copy, auto_local: true, **, &)
  raise LocalJumpError, "no block given" unless block_given?

  if auto_local || mode == :local
    if Fiber.respond_to?(:scheduler) && fiber_scheduler = Fiber.scheduler
      fiber_scheduler.fiber(**) { yield(*) }
      return
    end
    scheduler = Ractor.main? ? Internal::MainScheduler : Internal::Storage[:local_scheduler]
  end

  scheduler ||=
    if mode == :local
      Internal::Storage.store_if_absent(:local_scheduler) do
        Internal.native_ractors? ? Scheduler.create(Thread) : ThreadScheduler.new
      end
    else
      Internal::ParallelScheduler
    end

  scheduler.schedule(*, mode:, auto_local:, **, &)
  nil
end

.transaction(*objects, retries: nil, backoff_after: 10, max_backoff: 1.0) {|transaction, *objects| ... } ⇒ Boolean

Creates and runs a new transaction attempt.

Automatically retries failed attempts indefinitely unless a retry limit is specified. Starts backing off after the specified number of attempts, up to the maximum delay.

Examples:

Modifying multiple entries in a map

accounts = Farce::Map.new({a: 100, b: 200})

# transfer 80 from :a to :b, but only if both succeed
success = Farce.transaction(accounts) do |tx, accounts|
  tx.abort! if accounts[:b] < 80
  accounts[:a] += 80
  accounts[:b] -= 80
end

if success
  puts "Transaction succeeded"
else
  puts "Transaction failed"
end

Programmatically registering objects for transactions

map     = Farce::Map.new({a: 1, b: 2})
summary = Farce::Atom.new("size not calculated")

# make sure map[:size], map.size, and the summary all match
Farce.transaction do |tx|
  tx_map            = tx[map]
  tx_map[:size]     = size = tx_map.size
  tx[summary].value = "size: #{size}"
end

Parameters:

  • objects (Array) —

    list of objects to enroll in the transaction

  • retries (Integer, nil) (defaults to: nil) —

    maximum additional attempts, or nil for unlimited retries

  • backoff_after (Integer) (defaults to: 10) —

    number of attempts before starting to back off

  • max_backoff (Numeric) (defaults to: 1.0) —

    maximum backoff delay in seconds

Yields:

  • (transaction, *objects) —

    the current transaction and the enrolled objects

Yield Parameters:

  • transaction (Farce::Transaction) —

    the current transaction

  • objects (Array) —

    the enrolled objects

Returns:

  • (Boolean) —

    whether the transaction committed successfully



105
# File 'lib/farce.rb', line 105

def self.transaction(...) = Transaction.run(...)