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.
"0.1.0"- SCOPES =
The valid storage scopes for local values and collections.
::Set[:ractor, :thread_group, :thread, :fiber_storage, :fiber].freeze
- MODES =
The valid transfer modes for values that are not Ractor-shareable.
::Set[:copy, :move, :local, :make_shareable, :mutable, :raise, :shareable_copy, :dedup, :proxy].freeze
Dry Types Integration collapse
-
.DryTypes(*namespaces, default: UNDEFINED, variant: :shared, mode: UNDEFINED, scope: UNDEFINED, **aliases) ⇒ Module
Build a dry-types import for Farce types.
Class Method Summary collapse
-
.clock ⇒ Float
Monotonic clock time in seconds, from when clock was called the first time.
- .config ⇒ Config
-
.configure {|config| ... } ⇒ BasicObject
Configures Farce.
-
.dedup(object = UNDEFINED) ⇒ BasicObject
Deduplicate values using the default Deduper.
-
.enfarce(object, freeze: nil, mode: :copy) {|object| ... } ⇒ BasicObject
Converts vanilla Ruby objects into their Farce equivalents.
-
.freeze_graph(object, freeze_modules: false, traverse_modules: freeze_modules) ⇒ Object
Recursively freeze an object graph using Walker traversal.
-
.in_parallel(*args, mode: UNDEFINED) ⇒ Abstract::Scheduler?
Schedules work without waiting for the block to finish.
-
.on_main(*args, mode: UNDEFINED) ⇒ Abstract::Scheduler?
Allows executing code on the main ractor from other ractors.
-
.rebind(bindable = nil, lambda: nil, **self_option, &block) ⇒ Proc, Method
Binds a proc, lambda, block, bound or unbound method to a new self.
-
.schedule(*args, mode: :copy, auto_local: true, **kwargs) {|*args| ... } ⇒ nil
Schedules work to be executed out of band.
-
.transaction(*objects, retries: nil, backoff_after: 10, max_backoff: 1.0) {|transaction, *objects| ... } ⇒ Boolean
Creates and runs a new transaction attempt.
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.
144 |
# File 'lib/farce.rb', line 144 def self.clock(...) = Clock.parse(...) |
.config ⇒ Config
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.
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.
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
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.
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
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.
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 aFarce::Proxythat 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.
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 aFarce::Proxythat 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.
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:
Ractor: usingRactor.shareable_proc/Ractor.shareable_lambdaBasicObject: using#instance_exec/#instance_eval(used by many DSLs, like Hanami or the dry-rb gems)Module: using#define_methodand 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.
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
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
105 |
# File 'lib/farce.rb', line 105 def self.transaction(...) = Transaction.run(...) |