farce 0.0.1.alpha2-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +26 -0
- data/CONTRIBUTING.md +71 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1524 -0
- data/SECURITY.md +10 -0
- data/docs/benchmarks.md +185 -0
- data/docs/gems/dry-types.md +290 -0
- data/docs/gems/msgpack.md +70 -0
- data/docs/gems/ractor-shim.md +63 -0
- data/docs/modes.md +628 -0
- data/docs/scopes.md +649 -0
- data/docs/variants.md +346 -0
- data/lib/farce/_yard/internal.rb +13 -0
- data/lib/farce/_yard/macros.rb +61 -0
- data/lib/farce/_yard/ractor.rb +46 -0
- data/lib/farce/abstract/atom.rb +186 -0
- data/lib/farce/abstract/bounded_map.rb +232 -0
- data/lib/farce/abstract/collection.rb +151 -0
- data/lib/farce/abstract/concurrent_map.rb +381 -0
- data/lib/farce/abstract/counter.rb +193 -0
- data/lib/farce/abstract/duplicable_map.rb +229 -0
- data/lib/farce/abstract/exchanger.rb +26 -0
- data/lib/farce/abstract/flag.rb +71 -0
- data/lib/farce/abstract/lazy.rb +115 -0
- data/lib/farce/abstract/lease.rb +104 -0
- data/lib/farce/abstract/lease_map.rb +261 -0
- data/lib/farce/abstract/lease_pool.rb +91 -0
- data/lib/farce/abstract/lfu_map.rb +20 -0
- data/lib/farce/abstract/lru_map.rb +25 -0
- data/lib/farce/abstract/map.rb +345 -0
- data/lib/farce/abstract/molecule.rb +245 -0
- data/lib/farce/abstract/port.rb +74 -0
- data/lib/farce/abstract/priority_queue.rb +117 -0
- data/lib/farce/abstract/queue.rb +269 -0
- data/lib/farce/abstract/scheduler.rb +111 -0
- data/lib/farce/abstract/set.rb +910 -0
- data/lib/farce/abstract/sorted_set.rb +172 -0
- data/lib/farce/abstract/timer_queue.rb +136 -0
- data/lib/farce/abstract/tree_map.rb +269 -0
- data/lib/farce/abstract/value.rb +68 -0
- data/lib/farce/abstract/vector.rb +1126 -0
- data/lib/farce/abstract/weak_atom.rb +32 -0
- data/lib/farce/abstract/weak_key_map.rb +12 -0
- data/lib/farce/abstract/weak_map.rb +13 -0
- data/lib/farce/abstract/weak_set.rb +12 -0
- data/lib/farce/abstract/weak_value_map.rb +12 -0
- data/lib/farce/abstract.rb +17 -0
- data/lib/farce/atom.rb +293 -0
- data/lib/farce/class_mirror.rb +87 -0
- data/lib/farce/clock.rb +121 -0
- data/lib/farce/config.rb +229 -0
- data/lib/farce/counter.rb +71 -0
- data/lib/farce/deduper.rb +122 -0
- data/lib/farce/engine/jruby/bounded_map.rb +314 -0
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
- data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
- data/lib/farce/engine/jruby/map.rb +505 -0
- data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
- data/lib/farce/engine/jruby/signal.rb +147 -0
- data/lib/farce/engine/jruby.rb +63 -0
- data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
- data/lib/farce/engine/jvm/counter.rb +102 -0
- data/lib/farce/engine/jvm/extension.rb +32 -0
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/flag.rb +79 -0
- data/lib/farce/engine/jvm/priority_queue.rb +217 -0
- data/lib/farce/engine/jvm/tree_map.rb +350 -0
- data/lib/farce/engine/jvm/types.rb +180 -0
- data/lib/farce/engine/jvm.rb +19 -0
- data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
- data/lib/farce/engine/ruby/3.4/port.rb +186 -0
- data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
- data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
- data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
- data/lib/farce/engine/ruby/4.0/port.rb +19 -0
- data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
- data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
- data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
- data/lib/farce/engine/ruby/4.1/port.rb +21 -0
- data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
- data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
- data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
- data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
- data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
- data/lib/farce/engine/ruby/shared/lease.rb +26 -0
- data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
- data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
- data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
- data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
- data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
- data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
- data/lib/farce/engine/ruby/shared/vault.rb +144 -0
- data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
- data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
- data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
- data/lib/farce/engine/ruby.rb +130 -0
- data/lib/farce/engine/shared/atom.rb +10 -0
- data/lib/farce/engine/shared/exchanger.rb +130 -0
- data/lib/farce/engine/shared/identity_key.rb +22 -0
- data/lib/farce/engine/shared/lease.rb +10 -0
- data/lib/farce/engine/shared/lease_pool.rb +10 -0
- data/lib/farce/engine/shared/main_scheduler.rb +20 -0
- data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
- data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
- data/lib/farce/engine/shared/port.rb +44 -0
- data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
- data/lib/farce/engine/shared/proxy_owner.rb +43 -0
- data/lib/farce/engine/shared/queue.rb +233 -0
- data/lib/farce/engine/shared/ractor_methods.rb +73 -0
- data/lib/farce/engine/shared/rebindable.rb +15 -0
- data/lib/farce/engine/shared/strict_atom.rb +73 -0
- data/lib/farce/engine/shared/strict_map.rb +117 -0
- data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
- data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
- data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
- data/lib/farce/engine/shared/trie.rb +317 -0
- data/lib/farce/engine/shared/trie_builder.rb +199 -0
- data/lib/farce/engine/shared/unshareable.rb +14 -0
- data/lib/farce/engine/shared/unshared_atom.rb +256 -0
- data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
- data/lib/farce/engine/shared/unshared_queue.rb +18 -0
- data/lib/farce/engine/shared/unshared_signal.rb +21 -0
- data/lib/farce/engine/shared/unshared_vector.rb +377 -0
- data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
- data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
- data/lib/farce/engine/shared/vault.rb +31 -0
- data/lib/farce/engine/shared/vector.rb +83 -0
- data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
- data/lib/farce/engine/shared/weak_atom.rb +20 -0
- data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
- data/lib/farce/engine/shared/weak_map/index.rb +249 -0
- data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
- data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
- data/lib/farce/engine/shared.rb +63 -0
- data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
- data/lib/farce/engine/truffleruby/lock.rb +121 -0
- data/lib/farce/engine/truffleruby/map.rb +656 -0
- data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
- data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
- data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
- data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
- data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
- data/lib/farce/engine/truffleruby/signal.rb +77 -0
- data/lib/farce/engine/truffleruby.rb +148 -0
- data/lib/farce/envelope.rb +335 -0
- data/lib/farce/error.rb +35 -0
- data/lib/farce/exchanger.rb +41 -0
- data/lib/farce/flag.rb +27 -0
- data/lib/farce/integrations/active_support/blank.rb +68 -0
- data/lib/farce/integrations/active_support/clock.rb +17 -0
- data/lib/farce/integrations/active_support/duplicable.rb +78 -0
- data/lib/farce/integrations/active_support/map.rb +175 -0
- data/lib/farce/integrations/active_support/set.rb +60 -0
- data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
- data/lib/farce/integrations/active_support/vector.rb +231 -0
- data/lib/farce/integrations/active_support.rb +9 -0
- data/lib/farce/integrations/activesupport.rb +5 -0
- data/lib/farce/integrations/bson.rb +108 -0
- data/lib/farce/integrations/cbor.rb +51 -0
- data/lib/farce/integrations/concurrent.rb +112 -0
- data/lib/farce/integrations/dry-types.rb +5 -0
- data/lib/farce/integrations/dry_types.rb +694 -0
- data/lib/farce/integrations/json.rb +7 -0
- data/lib/farce/integrations/msgpack.rb +112 -0
- data/lib/farce/integrations/oj.rb +69 -0
- data/lib/farce/integrations/psych.rb +191 -0
- data/lib/farce/integrations/ractor-sharing.rb +5 -0
- data/lib/farce/integrations/ractor-tmvar.rb +5 -0
- data/lib/farce/integrations/ractor_sharing.rb +172 -0
- data/lib/farce/integrations/ractor_tmvar.rb +64 -0
- data/lib/farce/integrations/shared/to_json.rb +42 -0
- data/lib/farce/integrations/sorted_set.rb +11 -0
- data/lib/farce/integrations/weakref.rb +38 -0
- data/lib/farce/integrations/yajl.rb +10 -0
- data/lib/farce/integrations.rb +156 -0
- data/lib/farce/internal/_frozen_config.rb +11 -0
- data/lib/farce/internal/autoloads.rb +47 -0
- data/lib/farce/internal/blocking_priority_queue.rb +122 -0
- data/lib/farce/internal/converter.rb +106 -0
- data/lib/farce/internal/copyable.rb +31 -0
- data/lib/farce/internal/delegation.rb +20 -0
- data/lib/farce/internal/external_transaction.rb +59 -0
- data/lib/farce/internal/fake_ractor.rb +179 -0
- data/lib/farce/internal/freeze.rb +118 -0
- data/lib/farce/internal/inspect.rb +157 -0
- data/lib/farce/internal/key_lock_map.rb +29 -0
- data/lib/farce/internal/key_normalizer.rb +289 -0
- data/lib/farce/internal/lease_initialization.rb +115 -0
- data/lib/farce/internal/lease_map.rb +441 -0
- data/lib/farce/internal/lease_pool_state.rb +228 -0
- data/lib/farce/internal/lease_state.rb +342 -0
- data/lib/farce/internal/lease_waiting.rb +13 -0
- data/lib/farce/internal/managed_queue.rb +26 -0
- data/lib/farce/internal/map_value_modes.rb +227 -0
- data/lib/farce/internal/marshal_support.rb +227 -0
- data/lib/farce/internal/mixin.rb +20 -0
- data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
- data/lib/farce/internal/noncopyable.rb +17 -0
- data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
- data/lib/farce/internal/pool_supervisor.rb +41 -0
- data/lib/farce/internal/pool_worker.rb +61 -0
- data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
- data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
- data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
- data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
- data/lib/farce/internal/portable_transaction.rb +164 -0
- data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
- data/lib/farce/internal/reservation_waiting.rb +24 -0
- data/lib/farce/internal/scheduled_task.rb +48 -0
- data/lib/farce/internal/scheduler_io.rb +171 -0
- data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
- data/lib/farce/internal/select_scheduler.rb +235 -0
- data/lib/farce/internal/storage.rb +162 -0
- data/lib/farce/internal/strict_lease.rb +30 -0
- data/lib/farce/internal/strict_lease_map.rb +20 -0
- data/lib/farce/internal/strict_lease_pool.rb +29 -0
- data/lib/farce/internal/thread_pool.rb +166 -0
- data/lib/farce/internal/transaction_conflict.rb +9 -0
- data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
- data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
- data/lib/farce/internal/undefined.rb +22 -0
- data/lib/farce/internal/unshared_lease.rb +29 -0
- data/lib/farce/internal/unshared_lease_pool.rb +34 -0
- data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
- data/lib/farce/internal/value_serialization.rb +13 -0
- data/lib/farce/internal/weak_map_value_modes.rb +58 -0
- data/lib/farce/internal/weak_mode_manager.rb +26 -0
- data/lib/farce/internal.rb +129 -0
- data/lib/farce/lazy.rb +100 -0
- data/lib/farce/lazy_ref.rb +40 -0
- data/lib/farce/lease.rb +28 -0
- data/lib/farce/lease_map.rb +34 -0
- data/lib/farce/lease_pool.rb +29 -0
- data/lib/farce/lfu_map.rb +53 -0
- data/lib/farce/local/atom.rb +23 -0
- data/lib/farce/local/counter.rb +88 -0
- data/lib/farce/local/flag.rb +65 -0
- data/lib/farce/local/lazy.rb +38 -0
- data/lib/farce/local/lazy_ref.rb +36 -0
- data/lib/farce/local/lease.rb +77 -0
- data/lib/farce/local/lease_map.rb +80 -0
- data/lib/farce/local/lease_pool.rb +38 -0
- data/lib/farce/local/lfu_map.rb +55 -0
- data/lib/farce/local/lru_map.rb +62 -0
- data/lib/farce/local/map.rb +47 -0
- data/lib/farce/local/molecule.rb +45 -0
- data/lib/farce/local/priority_queue.rb +53 -0
- data/lib/farce/local/queue.rb +32 -0
- data/lib/farce/local/scoped.rb +166 -0
- data/lib/farce/local/set.rb +18 -0
- data/lib/farce/local/sorted_set.rb +50 -0
- data/lib/farce/local/timer_queue.rb +41 -0
- data/lib/farce/local/tree_map.rb +47 -0
- data/lib/farce/local/vector.rb +30 -0
- data/lib/farce/local/weak_atom.rb +23 -0
- data/lib/farce/local/weak_key_map.rb +39 -0
- data/lib/farce/local/weak_map.rb +39 -0
- data/lib/farce/local/weak_set.rb +18 -0
- data/lib/farce/local/weak_value_map.rb +39 -0
- data/lib/farce/local.rb +35 -0
- data/lib/farce/lock.rb +54 -0
- data/lib/farce/lru_map.rb +63 -0
- data/lib/farce/map.rb +58 -0
- data/lib/farce/mode_manager.rb +142 -0
- data/lib/farce/molecule.rb +60 -0
- data/lib/farce/mutable.rb +171 -0
- data/lib/farce/pool.rb +332 -0
- data/lib/farce/port.rb +134 -0
- data/lib/farce/priority_queue.rb +70 -0
- data/lib/farce/proxy/register.rb +101 -0
- data/lib/farce/proxy/supervisor.rb +137 -0
- data/lib/farce/proxy/wrapper.rb +49 -0
- data/lib/farce/proxy.rb +291 -0
- data/lib/farce/queue.rb +60 -0
- data/lib/farce/ractor.rb +269 -0
- data/lib/farce/read_write_lock.rb +256 -0
- data/lib/farce/reference.rb +158 -0
- data/lib/farce/resolv/dns.rb +42 -0
- data/lib/farce/resolv.rb +85 -0
- data/lib/farce/scheduler.rb +536 -0
- data/lib/farce/set.rb +39 -0
- data/lib/farce/shareable.rb +150 -0
- data/lib/farce/signal.rb +97 -0
- data/lib/farce/sorted_set.rb +36 -0
- data/lib/farce/strict/atom.rb +34 -0
- data/lib/farce/strict/counter.rb +10 -0
- data/lib/farce/strict/exchanger.rb +25 -0
- data/lib/farce/strict/flag.rb +10 -0
- data/lib/farce/strict/lazy.rb +26 -0
- data/lib/farce/strict/lazy_ref.rb +26 -0
- data/lib/farce/strict/lease.rb +27 -0
- data/lib/farce/strict/lease_map.rb +33 -0
- data/lib/farce/strict/lease_pool.rb +28 -0
- data/lib/farce/strict/lfu_map.rb +27 -0
- data/lib/farce/strict/lru_map.rb +28 -0
- data/lib/farce/strict/map.rb +54 -0
- data/lib/farce/strict/molecule.rb +16 -0
- data/lib/farce/strict/port.rb +40 -0
- data/lib/farce/strict/priority_queue.rb +20 -0
- data/lib/farce/strict/queue.rb +17 -0
- data/lib/farce/strict/set.rb +14 -0
- data/lib/farce/strict/sorted_set.rb +19 -0
- data/lib/farce/strict/timer_queue.rb +13 -0
- data/lib/farce/strict/tree_map.rb +27 -0
- data/lib/farce/strict/vector.rb +24 -0
- data/lib/farce/strict/weak_atom.rb +31 -0
- data/lib/farce/strict/weak_key_map.rb +49 -0
- data/lib/farce/strict/weak_map.rb +49 -0
- data/lib/farce/strict/weak_set.rb +14 -0
- data/lib/farce/strict/weak_value_map.rb +48 -0
- data/lib/farce/strict.rb +28 -0
- data/lib/farce/system.rb +40 -0
- data/lib/farce/thread_scheduler.rb +70 -0
- data/lib/farce/timer_queue.rb +66 -0
- data/lib/farce/transaction/atom.rb +73 -0
- data/lib/farce/transaction/map.rb +25 -0
- data/lib/farce/transaction/map_operations.rb +143 -0
- data/lib/farce/transaction/molecule.rb +90 -0
- data/lib/farce/transaction/mutable.rb +61 -0
- data/lib/farce/transaction/set.rb +17 -0
- data/lib/farce/transaction/set_operations.rb +48 -0
- data/lib/farce/transaction/sorted_set.rb +18 -0
- data/lib/farce/transaction/tree_map.rb +78 -0
- data/lib/farce/transaction/vector.rb +139 -0
- data/lib/farce/transaction/wrapper.rb +202 -0
- data/lib/farce/transaction.rb +343 -0
- data/lib/farce/tree_map.rb +56 -0
- data/lib/farce/unsafe/lfu_map.rb +36 -0
- data/lib/farce/unsafe/lru_map.rb +36 -0
- data/lib/farce/unsafe/tree_map.rb +21 -0
- data/lib/farce/unsafe.rb +29 -0
- data/lib/farce/unshareable.rb +67 -0
- data/lib/farce/unshared/atom.rb +24 -0
- data/lib/farce/unshared/counter.rb +10 -0
- data/lib/farce/unshared/flag.rb +10 -0
- data/lib/farce/unshared/lazy.rb +38 -0
- data/lib/farce/unshared/lazy_ref.rb +26 -0
- data/lib/farce/unshared/lease.rb +27 -0
- data/lib/farce/unshared/lease_map.rb +26 -0
- data/lib/farce/unshared/lease_pool.rb +25 -0
- data/lib/farce/unshared/lfu_map.rb +20 -0
- data/lib/farce/unshared/lru_map.rb +20 -0
- data/lib/farce/unshared/map.rb +49 -0
- data/lib/farce/unshared/molecule.rb +16 -0
- data/lib/farce/unshared/priority_queue.rb +16 -0
- data/lib/farce/unshared/queue.rb +27 -0
- data/lib/farce/unshared/set.rb +14 -0
- data/lib/farce/unshared/sorted_set.rb +28 -0
- data/lib/farce/unshared/timer_queue.rb +16 -0
- data/lib/farce/unshared/tree_map.rb +21 -0
- data/lib/farce/unshared/vector.rb +19 -0
- data/lib/farce/unshared/weak_atom.rb +31 -0
- data/lib/farce/unshared/weak_key_map.rb +44 -0
- data/lib/farce/unshared/weak_map.rb +44 -0
- data/lib/farce/unshared/weak_set.rb +14 -0
- data/lib/farce/unshared/weak_value_map.rb +43 -0
- data/lib/farce/unshared.rb +28 -0
- data/lib/farce/vector.rb +228 -0
- data/lib/farce/version.rb +8 -0
- data/lib/farce/walker/definitions.rb +186 -0
- data/lib/farce/walker/modification.rb +355 -0
- data/lib/farce/walker.rb +319 -0
- data/lib/farce/weak_atom.rb +121 -0
- data/lib/farce/weak_key_map.rb +31 -0
- data/lib/farce/weak_map.rb +33 -0
- data/lib/farce/weak_ref.rb +67 -0
- data/lib/farce/weak_set.rb +82 -0
- data/lib/farce/weak_value.rb +195 -0
- data/lib/farce/weak_value_map.rb +32 -0
- data/lib/farce.rb +519 -0
- metadata +420 -0
data/docs/scopes.md
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @title Local scopes
|
|
3
|
+
-->
|
|
4
|
+
|
|
5
|
+
# Local scopes
|
|
6
|
+
|
|
7
|
+
Farce's local containers let you share one object while keeping separate contents for different Ractors, threads, or fibers. The `scope:` option selects which callers use the same contents.
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Local scopes](#local-scopes)
|
|
12
|
+
- [Table of Contents](#table-of-contents)
|
|
13
|
+
- [Introduction: What Ruby gives you](#introduction-what-ruby-gives-you)
|
|
14
|
+
- [One handle, separate contents](#one-handle-separate-contents)
|
|
15
|
+
- [Choosing a scope](#choosing-a-scope)
|
|
16
|
+
- [`:ractor`: share within a Ractor](#ractor-share-within-a-ractor)
|
|
17
|
+
- [`:thread_group`: share within a group of threads](#thread_group-share-within-a-group-of-threads)
|
|
18
|
+
- [`:thread`: keep one set of contents per thread](#thread-keep-one-set-of-contents-per-thread)
|
|
19
|
+
- [`:fiber`: give every fiber its own contents](#fiber-give-every-fiber-its-own-contents)
|
|
20
|
+
- [`:fiber_storage`: let child fibers share a context](#fiber_storage-let-child-fibers-share-a-context)
|
|
21
|
+
- [Non-blocking fibers and schedulers](#non-blocking-fibers-and-schedulers)
|
|
22
|
+
- [Classes that support scopes](#classes-that-support-scopes)
|
|
23
|
+
- [Count within each scope](#count-within-each-scope)
|
|
24
|
+
- [Keep a cache in each Ractor](#keep-a-cache-in-each-ractor)
|
|
25
|
+
- [Create mutable values with a factory](#create-mutable-values-with-a-factory)
|
|
26
|
+
- [Delegate through a lazy reference](#delegate-through-a-lazy-reference)
|
|
27
|
+
- [Keep queues and their lifecycle local](#keep-queues-and-their-lifecycle-local)
|
|
28
|
+
- [Build ordered collections in each scope](#build-ordered-collections-in-each-scope)
|
|
29
|
+
- [Borrow resources from a local lease](#borrow-resources-from-a-local-lease)
|
|
30
|
+
- [Group resources or limit their number](#group-resources-or-limit-their-number)
|
|
31
|
+
- [Using scopes in an application](#using-scopes-in-an-application)
|
|
32
|
+
- [Restore context when reusing a fiber](#restore-context-when-reusing-a-fiber)
|
|
33
|
+
- [Pass local handles to scheduled tasks](#pass-local-handles-to-scheduled-tasks)
|
|
34
|
+
- [Coordinate changes within a shared scope](#coordinate-changes-within-a-shared-scope)
|
|
35
|
+
- [Under the hood](#under-the-hood)
|
|
36
|
+
- [Resolve the current backing object on every operation](#resolve-the-current-backing-object-on-every-operation)
|
|
37
|
+
- [Local handles can be garbage collected](#local-handles-can-be-garbage-collected)
|
|
38
|
+
- [Initial configuration is reused in new scopes](#initial-configuration-is-reused-in-new-scopes)
|
|
39
|
+
- [Separate containers can contain the same objects](#separate-containers-can-contain-the-same-objects)
|
|
40
|
+
- [Fiber storage inherits a reference to Farce storage](#fiber-storage-inherits-a-reference-to-farce-storage)
|
|
41
|
+
- [Scopes and transfer modes solve different problems](#scopes-and-transfer-modes-solve-different-problems)
|
|
42
|
+
|
|
43
|
+
## Introduction: What Ruby gives you
|
|
44
|
+
|
|
45
|
+
Ruby has several places to store execution-local state. `Thread.current.thread_variable_set` stores a value for an entire thread. Despite its name, `Thread.current[:key]` stores a value for the current fiber.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
Thread.current.thread_variable_set(:scopes_example, :thread_value)
|
|
49
|
+
Thread.current[:scopes_example] = :fiber_value
|
|
50
|
+
|
|
51
|
+
values = Fiber.new do
|
|
52
|
+
[Thread.current.thread_variable_get(:scopes_example),
|
|
53
|
+
Thread.current[:scopes_example]]
|
|
54
|
+
end.resume
|
|
55
|
+
|
|
56
|
+
values # => [:thread_value, nil]
|
|
57
|
+
Thread.current.thread_variable_set(:scopes_example, nil)
|
|
58
|
+
Thread.current[:scopes_example] = nil
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`Fiber[:key]` is another API. It supports storage inheritance when new fibers are created. Farce gives these choices a common interface through `scope:`, and adds Ractor and thread-group scopes. You can change the scope without rewriting each operation on your map, queue, or lazy value.
|
|
62
|
+
|
|
63
|
+
## One handle, separate contents
|
|
64
|
+
|
|
65
|
+
A `Farce::Local` object is a shareable handle. Each operation finds the contents associated with the current scope. The handle is shareable while its contents remain mutable. Freezing a Local data container prevents explicit changes in every scope, including scopes first accessed later. Each scope keeps its own values. Local services such as queues and leases, and `Local::Lazy`, reject freezing with `TypeError`. Unlike a named slot in Ruby's built-in local storage, the local instance can also be garbage collected while the surrounding Ractor, thread, or fiber is still alive.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
require "farce"
|
|
69
|
+
|
|
70
|
+
context = Farce::Local::Map.new(scope: :fiber)
|
|
71
|
+
context[:request_id] = :parent
|
|
72
|
+
|
|
73
|
+
child_value = Fiber.new do
|
|
74
|
+
context[:request_id] = :child
|
|
75
|
+
context[:request_id]
|
|
76
|
+
end.resume
|
|
77
|
+
|
|
78
|
+
child_value # => :child
|
|
79
|
+
context[:request_id] # => :parent
|
|
80
|
+
context.scope # => :fiber
|
|
81
|
+
Farce::Ractor.shareable?(context) # => true
|
|
82
|
+
context.frozen? # => false
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Call `freeze` only after coordinating with writers. It does not wait for operations already in progress. Freezing is shallow, so objects stored in a container remain independently mutable. Weak containers still allow garbage collection of their contents.
|
|
86
|
+
|
|
87
|
+
Load `farce` before running the remaining examples. Each code block is independent. Examples use `Farce::Ractor` so they can also use Farce's compatibility layer where native Ractors are unavailable.
|
|
88
|
+
|
|
89
|
+
## Choosing a scope
|
|
90
|
+
|
|
91
|
+
Choose a scope based on who should see the same state. All these scopes are selected at construction. The default is `:ractor`. There is no `:global` scope for local containers (use the non-local variants of the storage classes instead).
|
|
92
|
+
|
|
93
|
+
| Scope | Who uses the same contents? | A typical use |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| `:ractor` | Threads and fibers in the same Farce Ractor. | A cache for each worker Ractor. |
|
|
96
|
+
| `:thread_group` | Threads in the same `ThreadGroup` within a Ractor, including their fibers. | State for a group of related worker threads. |
|
|
97
|
+
| `:thread` | All fibers in one thread. | A reusable object for each worker thread. |
|
|
98
|
+
| `:fiber` | Only the current fiber. | Independent request state. |
|
|
99
|
+
| `:fiber_storage` | Execution contexts that inherit the same Farce storage entry through Ruby fiber storage. | Context shared with child fibers. |
|
|
100
|
+
|
|
101
|
+
### `:ractor`: share within a Ractor
|
|
102
|
+
|
|
103
|
+
Use the default scope when each Ractor should have its own state. Threads inside one Ractor still use the same contents. Sending the handle to another Ractor gives that Ractor access to its own contents.
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
state = Farce::Local::Map.new
|
|
107
|
+
state[:worker] = :main
|
|
108
|
+
Thread.new { state[:completed] = 3 }.join
|
|
109
|
+
state[:completed] # => 3
|
|
110
|
+
|
|
111
|
+
results = Farce::Port.new
|
|
112
|
+
worker = Farce::Ractor.new(state, results) do |local, outbox|
|
|
113
|
+
before = local.empty?
|
|
114
|
+
local[:worker] = :background
|
|
115
|
+
outbox.send([before, local[:worker]])
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
results.receive # => [true, :background]
|
|
119
|
+
state[:worker] # => :main
|
|
120
|
+
worker.join
|
|
121
|
+
results.close
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The background Ractor starts with an empty map because this map had no initial entries. It does not inherit writes made through the main Ractor's handle. On platforms using Farce's thread-based Ractor shim, `:ractor` still selects storage by the current Farce Ractor.
|
|
125
|
+
|
|
126
|
+
### `:thread_group`: share within a group of threads
|
|
127
|
+
|
|
128
|
+
Use `:thread_group` when a group of threads should share state separately from other groups. Ruby's [`ThreadGroup`](https://docs.ruby-lang.org/en/4.0/ThreadGroup.html) lets you assign threads to groups. New threads inherit their creator's group.
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
state = Farce::Local::Map.new(scope: :thread_group)
|
|
132
|
+
state[:service] = :web
|
|
133
|
+
background = ThreadGroup.new
|
|
134
|
+
|
|
135
|
+
result = Thread.new do
|
|
136
|
+
inherited = state[:service]
|
|
137
|
+
background.add(Thread.current)
|
|
138
|
+
starts_empty = state.empty?
|
|
139
|
+
state[:service] = :indexer
|
|
140
|
+
|
|
141
|
+
child_value = Thread.new { state[:service] }.value
|
|
142
|
+
[inherited, starts_empty, child_value]
|
|
143
|
+
end.value
|
|
144
|
+
|
|
145
|
+
result # => [:web, true, :indexer]
|
|
146
|
+
state[:service] # => :web
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Moving a thread to another group changes which contents subsequent operations select. Put workers into their intended group before they start using group-local resources. Membership in a group does not make independent edits to a returned mutable object atomic.
|
|
150
|
+
|
|
151
|
+
### `:thread`: keep one set of contents per thread
|
|
152
|
+
|
|
153
|
+
Use `:thread` when fibers on the same thread should reuse state. A new thread starts with its own contents, even if it belongs to the same thread group.
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
state = Farce::Local::Map.new(scope: :thread)
|
|
157
|
+
state[:worker] = :main
|
|
158
|
+
|
|
159
|
+
Fiber.new { state[:worker] }.resume # => :main
|
|
160
|
+
|
|
161
|
+
other = Thread.new do
|
|
162
|
+
before = state.empty?
|
|
163
|
+
state[:worker] = :background
|
|
164
|
+
[before, Fiber.new { state[:worker] }.resume]
|
|
165
|
+
end.value
|
|
166
|
+
|
|
167
|
+
other # => [true, :background]
|
|
168
|
+
state[:worker] # => :main
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Thread-local state lasts across multiple jobs handled by the same thread. This suits caches and reusable helpers. For request-specific state, use a narrower scope or explicitly restore the previous value when a request finishes.
|
|
172
|
+
|
|
173
|
+
### `:fiber`: give every fiber its own contents
|
|
174
|
+
|
|
175
|
+
Use `:fiber` when each fiber should start independently. Child fibers do not inherit the parent's local contents. Suspending and resuming a fiber preserves that fiber's contents.
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
request = Farce::Local::Atom.new(nil, scope: :fiber)
|
|
179
|
+
request.value = :outer
|
|
180
|
+
|
|
181
|
+
work = Fiber.new do
|
|
182
|
+
before = request.value
|
|
183
|
+
request.value = :upload
|
|
184
|
+
Fiber.yield(before)
|
|
185
|
+
request.value
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
work.resume # => nil
|
|
189
|
+
request.value # => :outer
|
|
190
|
+
work.resume # => :upload
|
|
191
|
+
request.value # => :outer
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
This is useful when an application runs one request per fiber. It also means that a helper which starts a new fiber must receive any needed request data explicitly.
|
|
195
|
+
|
|
196
|
+
### `:fiber_storage`: let child fibers share a context
|
|
197
|
+
|
|
198
|
+
Use `:fiber_storage` when related fibers should see the same local context. Ruby's [`Fiber.new`](https://docs.ruby-lang.org/en/4.0/Fiber.html#method-c-new) inherits a copy of the creator's fiber-storage hash by default. That copy can hold a reference to the same Farce storage object. A child can therefore update the same local map.
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
context = Farce::Local::Map.new(scope: :fiber_storage)
|
|
202
|
+
context[:request_id] = :upload
|
|
203
|
+
|
|
204
|
+
child_result = Fiber.new do
|
|
205
|
+
context[:stage] = :validated
|
|
206
|
+
context[:request_id]
|
|
207
|
+
end.resume
|
|
208
|
+
|
|
209
|
+
child_result # => :upload
|
|
210
|
+
context[:stage] # => :validated
|
|
211
|
+
|
|
212
|
+
# Start an independent context explicitly.
|
|
213
|
+
isolated = Fiber.new(storage: {}) do
|
|
214
|
+
before = context.empty?
|
|
215
|
+
context[:request_id] = :download
|
|
216
|
+
[before, context[:request_id]]
|
|
217
|
+
end.resume
|
|
218
|
+
|
|
219
|
+
isolated # => [true, :download]
|
|
220
|
+
context[:request_id] # => :upload
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Set up the context before creating children that should inherit it. Inheritance happens at fiber creation, not when the child is resumed. Use `storage: {}` at a new request boundary when it should start without inherited fiber storage.
|
|
224
|
+
|
|
225
|
+
#### Non-blocking fibers and schedulers
|
|
226
|
+
|
|
227
|
+
`blocking: false` alone does not turn off Ruby's default storage inheritance. A scheduler controls where and how task fibers are created. Do not assume a scheduled task inherits the submitting fiber's context, especially when it runs in another thread or Ractor.
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
context = Farce::Local::Map.new(scope: :fiber_storage)
|
|
231
|
+
context[:request_id] = :upload
|
|
232
|
+
|
|
233
|
+
Fiber.new(blocking: false) { context[:request_id] }.resume # => :upload
|
|
234
|
+
Fiber.new(blocking: false, storage: {}) { context.empty? }.resume # => true
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Pass the needed data as task arguments when crossing an execution boundary. Use `:fiber` when each task fiber should get its own state regardless of inherited storage.
|
|
238
|
+
|
|
239
|
+
## Classes that support scopes
|
|
240
|
+
|
|
241
|
+
These classes accept the same five scopes. The scope determines which backing container or resource an operation uses. Each class retains its own behavior, such as queue ordering or weak references.
|
|
242
|
+
|
|
243
|
+
| Class | What each scope gets |
|
|
244
|
+
| --- | --- |
|
|
245
|
+
| `Farce::Local::Flag` | An atomic boolean with its own current value. |
|
|
246
|
+
| `Farce::Local::Counter` | An atomic integer counter with its own current value. |
|
|
247
|
+
| `Farce::Local::Atom` | An atomic reference with its own current value. |
|
|
248
|
+
| `Farce::Local::Molecule` | A record with independent atomic fields in each scope. |
|
|
249
|
+
| `Farce::Local::WeakAtom` | A reference that does not keep its value alive. |
|
|
250
|
+
| `Farce::Local::Map` | A mutable map. |
|
|
251
|
+
| `Farce::Local::TreeMap` | A map sorted by key. |
|
|
252
|
+
| `Farce::Local::LRUMap` | A bounded map with independent recency and capacity in each scope. |
|
|
253
|
+
| `Farce::Local::LFUMap` | A bounded map with independent frequency history and capacity in each scope. |
|
|
254
|
+
| `Farce::Local::WeakMap` | A map with weak keys and weak values. |
|
|
255
|
+
| `Farce::Local::WeakKeyMap` | A map with weak keys. |
|
|
256
|
+
| `Farce::Local::WeakValueMap` | A map with weak values. |
|
|
257
|
+
| `Farce::Local::Set` | A mutable set. |
|
|
258
|
+
| `Farce::Local::SortedSet` | A set maintained in ascending comparator order. |
|
|
259
|
+
| `Farce::Local::WeakSet` | A set that retains its elements weakly. |
|
|
260
|
+
| `Farce::Local::Vector` | An indexed collection. |
|
|
261
|
+
| `Farce::Local::Queue` | A FIFO queue with independent capacity and lifecycle. |
|
|
262
|
+
| `Farce::Local::PriorityQueue` | A queue ordered by priority. |
|
|
263
|
+
| `Farce::Local::TimerQueue` | A queue whose values become available at scheduled times. |
|
|
264
|
+
| `Farce::Local::Lazy` | A value computed on first access in that scope. |
|
|
265
|
+
| `Farce::Local::LazyRef` | A reference that delegates to a scoped lazy value. |
|
|
266
|
+
| `Farce::Local::Lease` | An independently initialized resource to borrow. |
|
|
267
|
+
| `Farce::Local::LeaseMap` | An independently initialized set of named resources. |
|
|
268
|
+
| `Farce::Local::LeasePool` | A pool with its own resources and capacity. |
|
|
269
|
+
|
|
270
|
+
### Count within each scope
|
|
271
|
+
|
|
272
|
+
`Local::Counter` provides the numeric interface and atomic operations of `Farce::Counter`. Each scope starts at the configured initial integer. Reads and `reset` affect only the current scope. Values are not summed across scopes.
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
completed = Farce::Local::Counter.new(10, scope: :thread)
|
|
276
|
+
completed.increment(3)
|
|
277
|
+
|
|
278
|
+
child = Thread.new do
|
|
279
|
+
before = completed.value
|
|
280
|
+
completed.increment
|
|
281
|
+
completed.reset
|
|
282
|
+
[before, completed.value]
|
|
283
|
+
end.value
|
|
284
|
+
|
|
285
|
+
child # => [10, 10]
|
|
286
|
+
completed.value # => 13
|
|
287
|
+
completed + 2 # => 15
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Updates remain atomic when multiple threads share a Ractor or thread-group scope. Conditional operations such as `increment_if_below` apply their bounds to that scope's counter.
|
|
291
|
+
|
|
292
|
+
### Keep a cache in each Ractor
|
|
293
|
+
|
|
294
|
+
A local map can hold mutable keys and values without preparing them for transfer. `store_if_absent` is useful for computing an entry once in the current map. Other Ractors maintain their own caches through the same handle.
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
cache = Farce::Local::Map.new
|
|
298
|
+
key = String.new("ruby concurrency")
|
|
299
|
+
|
|
300
|
+
tokens = cache.store_if_absent(key) { key.split }
|
|
301
|
+
again = cache.store_if_absent(key) { raise "already cached" }
|
|
302
|
+
|
|
303
|
+
again.equal?(tokens) # => true
|
|
304
|
+
Farce::Ractor.shareable?(cache) # => true
|
|
305
|
+
Farce::Ractor.shareable?(tokens) # => false
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Choose a weak map variant when entries should disappear as keys or values become unreachable. Scope selection does not change weak-reference behavior. Keep a strong reference elsewhere for as long as you need a weakly held object.
|
|
309
|
+
|
|
310
|
+
### Create mutable values with a factory
|
|
311
|
+
|
|
312
|
+
Use `Local::Lazy` to build a fresh object for each scope that needs it. Its factory runs on the first `value` call in that scope. Later calls return the cached result, including `nil` or `false` results.
|
|
313
|
+
|
|
314
|
+
```ruby
|
|
315
|
+
buffers = Farce::Local::Lazy.new(String, scope: :thread)
|
|
316
|
+
buffers.value << "main output"
|
|
317
|
+
|
|
318
|
+
child_output = Thread.new do
|
|
319
|
+
buffer = buffers.value
|
|
320
|
+
buffer << "worker output"
|
|
321
|
+
buffer.dup
|
|
322
|
+
end.value
|
|
323
|
+
|
|
324
|
+
child_output # => "worker output"
|
|
325
|
+
buffers.value # => "main output"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
You can supply a class, a shareable callable, or a block that can be made Ractor-shareable. The factory may create mutable objects, but it cannot capture arbitrary mutable state from another Ractor. A block can capture shareable configuration or use the explicit `self:` option.
|
|
329
|
+
|
|
330
|
+
```ruby
|
|
331
|
+
settings = Farce::Ractor.make_shareable({ limit: 100 })
|
|
332
|
+
state = Farce::Local::Lazy.new(scope: :fiber, self: settings) do
|
|
333
|
+
{ limit: self[:limit], pending: [] }
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
state.value[:pending] << :parent
|
|
337
|
+
child = Fiber.new { state.value }.resume
|
|
338
|
+
|
|
339
|
+
child # => { limit: 100, pending: [] }
|
|
340
|
+
state.value[:pending] # => [:parent]
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Delegate through a lazy reference
|
|
344
|
+
|
|
345
|
+
`Local::LazyRef` lets calling code use the result's interface directly. It selects the current scope's lazy value before forwarding the operation. This is handy for a library-level cache whose callers should not need to call `value` themselves.
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
cache = Farce::Local::LazyRef.new(Hash, scope: :fiber)
|
|
349
|
+
cache[:page] = :parent
|
|
350
|
+
|
|
351
|
+
child = Fiber.new { cache[:page] = :child }.resume
|
|
352
|
+
child # => :child
|
|
353
|
+
cache[:page] # => :parent
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Keep queues and their lifecycle local
|
|
357
|
+
|
|
358
|
+
Use `Local::Queue` when producers and consumers should communicate inside the selected scope. With the default `:ractor` scope, different threads in the same Ractor can exchange the original mutable object.
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
queue = Farce::Local::Queue.new
|
|
362
|
+
job = { ids: [1, 2] }
|
|
363
|
+
consumer = Thread.new { queue.pop }
|
|
364
|
+
|
|
365
|
+
queue.push(job)
|
|
366
|
+
|
|
367
|
+
consumer.value.equal?(job) # => true
|
|
368
|
+
queue.close
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Capacity, queued values, and closing belong to the backing queue in that scope. A different scope has a different queue. A thread-local queue therefore cannot deliver work from one thread to another.
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
queue = Farce::Local::Queue.new(scope: :thread, capacity: 1)
|
|
375
|
+
queue.push(:main_job)
|
|
376
|
+
queue.seal
|
|
377
|
+
|
|
378
|
+
other_state = Thread.new do
|
|
379
|
+
[queue.empty?, queue.closed?, queue.capacity]
|
|
380
|
+
end.value
|
|
381
|
+
|
|
382
|
+
other_state # => [true, false, 1]
|
|
383
|
+
queue.pop # => :main_job
|
|
384
|
+
queue.closed? # => true
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Build ordered collections in each scope
|
|
388
|
+
|
|
389
|
+
Use local vectors and tree maps for per-scope collections. Local priority and timer queues apply their ordering within each scope too. Here each fiber gets an independent list of processing stages.
|
|
390
|
+
|
|
391
|
+
```ruby
|
|
392
|
+
stages = Farce::Local::Vector.new([:received], scope: :fiber)
|
|
393
|
+
stages.push(:validated)
|
|
394
|
+
|
|
395
|
+
child_stages = Fiber.new do
|
|
396
|
+
stages.push(:decoded)
|
|
397
|
+
[stages[0], stages[1]]
|
|
398
|
+
end.resume
|
|
399
|
+
|
|
400
|
+
child_stages # => [:received, :decoded]
|
|
401
|
+
[stages[0], stages[1]] # => [:received, :validated]
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Borrow resources from a local lease
|
|
405
|
+
|
|
406
|
+
Use `Local::Lease` when callers in the same scope must take turns using a resource. Its initializer creates a resource on first use in each scope. The block form of `checkout` returns the resource to the lease when the block finishes, including when it raises.
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
scratch = Farce::Local::Lease.new(scope: :thread) { String.new }
|
|
410
|
+
|
|
411
|
+
first = scratch.checkout do |buffer|
|
|
412
|
+
buffer.replace("report: ")
|
|
413
|
+
buffer << "ready"
|
|
414
|
+
buffer.dup
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
second = scratch.checkout { |buffer| buffer.dup }
|
|
418
|
+
other = Thread.new { scratch.checkout { |buffer| buffer.empty? } }.value
|
|
419
|
+
|
|
420
|
+
first # => "report: ready"
|
|
421
|
+
second # => "report: ready"
|
|
422
|
+
other # => true
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
A lease reuses its resource within the scope. Clear or reset reusable buffers as part of your work when previous contents should not carry over. Keep checkout and use together in the block instead of letting borrowed resources escape into another scope.
|
|
426
|
+
|
|
427
|
+
### Group resources or limit their number
|
|
428
|
+
|
|
429
|
+
`Local::LeaseMap` builds a mapping of named resources per scope. `Local::LeasePool` creates resources as needed up to its limit. That limit applies separately in each scope, so `max_size: 2` with thread scope permits two resources per thread.
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
resources = Farce::Local::LeaseMap.new(scope: :fiber) do
|
|
433
|
+
{ input: String.new, output: String.new }
|
|
434
|
+
end
|
|
435
|
+
resources.checkout(:output) { |buffer| buffer << "parent" }
|
|
436
|
+
|
|
437
|
+
Fiber.new { resources.checkout(:output) { |buffer| buffer.empty? } }.resume # => true
|
|
438
|
+
|
|
439
|
+
pool = Farce::Local::LeasePool.new(scope: :thread, max_size: 2) { [] }
|
|
440
|
+
pool.checkout { |items| items << :used }
|
|
441
|
+
Thread.new { pool.checkout { |items| items.empty? } }.value # => true
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Using scopes in an application
|
|
445
|
+
|
|
446
|
+
### Restore context when reusing a fiber
|
|
447
|
+
|
|
448
|
+
A scope is tied to an execution context, not to the lifetime of a Ruby method call. A thread or fiber that handles several requests keeps its local state between them. Use `ensure` to restore temporary context when nesting operations or reusing a worker.
|
|
449
|
+
|
|
450
|
+
```ruby
|
|
451
|
+
class RequestContext
|
|
452
|
+
def initialize
|
|
453
|
+
@current = Farce::Local::Atom.new(nil, scope: :fiber)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def current = @current.value
|
|
457
|
+
|
|
458
|
+
def with(request_id)
|
|
459
|
+
previous = @current.swap(request_id)
|
|
460
|
+
begin
|
|
461
|
+
yield
|
|
462
|
+
ensure
|
|
463
|
+
@current.value = previous
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
context = RequestContext.new
|
|
469
|
+
|
|
470
|
+
context.with(:outer) do
|
|
471
|
+
context.with(:inner) { context.current } # => :inner
|
|
472
|
+
context.current # => :outer
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
context.current # => nil
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
The wrapper above is an ordinary Ruby object used within one Ractor. Its local atom supplies fiber selection. A local field alone does not make an enclosing application object Ractor-shareable.
|
|
479
|
+
|
|
480
|
+
### Pass local handles to scheduled tasks
|
|
481
|
+
|
|
482
|
+
A local handle is shareable, so it can be passed as a task argument. The task sees the contents selected by its own execution scope. Passing the handle does not send the caller's current local values along with it.
|
|
483
|
+
|
|
484
|
+
```ruby
|
|
485
|
+
context = Farce::Local::Map.new(scope: :fiber)
|
|
486
|
+
context[:request_id] = :caller
|
|
487
|
+
results = Farce::Port.new
|
|
488
|
+
scheduler = Farce::Scheduler.new
|
|
489
|
+
worker = scheduler.launch_thread
|
|
490
|
+
|
|
491
|
+
[:upload, :download].each do |request_id|
|
|
492
|
+
scheduler.schedule(context, results, request_id) do |local, outbox, id|
|
|
493
|
+
before = local.empty?
|
|
494
|
+
local[:request_id] = id
|
|
495
|
+
outbox.send([id, before])
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
received = [results.receive, results.receive].sort
|
|
500
|
+
received # => [[:download, true], [:upload, true]]
|
|
501
|
+
context[:request_id] # => :caller
|
|
502
|
+
scheduler.close
|
|
503
|
+
worker.join
|
|
504
|
+
results.close
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Use task arguments for incoming request data, then set up local context inside the task. If child fibers should participate in that same context, consider `:fiber_storage` and establish an explicit inheritance boundary.
|
|
508
|
+
|
|
509
|
+
### Coordinate changes within a shared scope
|
|
510
|
+
|
|
511
|
+
Ractor and thread-group scopes can be used by several threads at once. Use the container's coordinated operations for compound changes. Retrieving a mutable value does not make later edits to that object synchronized.
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
completed = Farce::Local::Atom.new(0, scope: :ractor)
|
|
515
|
+
threads = 4.times.map do
|
|
516
|
+
Thread.new do
|
|
517
|
+
100.times { completed.update { |count| count + 1 } }
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
threads.each(&:join)
|
|
521
|
+
|
|
522
|
+
completed.value # => 400
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Likewise, use `Map#update` for a coordinated replacement or a lease for exclusive resource use. Choose the scope first, then the operation that provides the coordination callers in that scope need.
|
|
526
|
+
|
|
527
|
+
## Under the hood
|
|
528
|
+
|
|
529
|
+
### Resolve the current backing object on every operation
|
|
530
|
+
|
|
531
|
+
Most local classes share the `Farce::Local::Scoped` implementation. The shareable handle stores its scope and construction settings. A private storage table maps that handle to a backing object for the current scope. A map operation goes to a map, a queue operation to a queue, and a lazy read to a cached local value.
|
|
532
|
+
|
|
533
|
+
| Selected scope | How Farce finds the storage |
|
|
534
|
+
| --- | --- |
|
|
535
|
+
| `:ractor` | The current Farce Ractor. |
|
|
536
|
+
| `:thread_group` | The current thread's group within the current Ractor. |
|
|
537
|
+
| `:thread` | The current logical thread. |
|
|
538
|
+
| `:fiber` | The current fiber. |
|
|
539
|
+
| `:fiber_storage` | A Farce storage entry in `Fiber[...]`. |
|
|
540
|
+
|
|
541
|
+
There is one backing object per handle in each selected storage table. Two different local maps remain independent even when they have the same scope. These tables are internal details. Application code should use the local container's public methods.
|
|
542
|
+
|
|
543
|
+
```ruby
|
|
544
|
+
left = Farce::Local::Map.new(scope: :thread)
|
|
545
|
+
right = Farce::Local::Map.new(scope: :thread)
|
|
546
|
+
left[:key] = :value
|
|
547
|
+
|
|
548
|
+
left[:key] # => :value
|
|
549
|
+
right[:key] # => nil
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Local handles can be garbage collected
|
|
553
|
+
|
|
554
|
+
Farce keeps local handles as weak keys in its scope storage. The storage does not keep a handle alive just because it has been used in that scope. Once nothing else references the local instance, it can be garbage collected. Its backing containers and their contents can then be released too, if nothing else retains them. This works even for scopes attached to long-lived workers.
|
|
555
|
+
|
|
556
|
+
```ruby
|
|
557
|
+
cache = Farce::Local::Map.new(scope: :thread)
|
|
558
|
+
cache[:buffer] = String.new("temporary output")
|
|
559
|
+
cache = nil
|
|
560
|
+
|
|
561
|
+
# The local handle is now eligible for collection.
|
|
562
|
+
# No per-thread storage slot needs to be cleared by name.
|
|
563
|
+
|
|
564
|
+
# A built-in storage entry retains its value independently of this variable.
|
|
565
|
+
buffer = String.new("retained output")
|
|
566
|
+
Thread.current.thread_variable_set(:scopes_example_buffer, buffer)
|
|
567
|
+
buffer = nil
|
|
568
|
+
Thread.current.thread_variable_get(:scopes_example_buffer) # => "retained output"
|
|
569
|
+
Thread.current.thread_variable_set(:scopes_example_buffer, nil)
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Ruby's built-in Ractor, Thread, and Fiber storage holds named entries. Dropping an application reference does not remove those entries or release their values. Clear or replace the entry, or let its owning execution context become collectible. For example, `Ractor[:key] = nil`, `Thread.current[:key] = nil`, and `Fiber[:key] = nil` release those entries' references. See Ruby's [Ractor storage API](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-c-5B-5D-3D) and the Thread and Fiber APIs linked above.
|
|
573
|
+
|
|
574
|
+
Keep a local handle in a constant or a live application object when you want its state to persist. The weak storage does not discard a handle that your application still retains. As with other Ruby objects, references from closures or stored values can also keep it alive.
|
|
575
|
+
|
|
576
|
+
### Initial configuration is reused in new scopes
|
|
577
|
+
|
|
578
|
+
New scopes use the constructor's initial contents and options. They do not clone the latest state of another scope's backing container. Many local containers create their initial backing object during construction and create others on demand. Lazy values and lease resources still wait until needed to run their factories.
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
status = Farce::Local::Atom.new(:idle, scope: :fiber)
|
|
582
|
+
status.value = :busy
|
|
583
|
+
|
|
584
|
+
Fiber.new { status.value }.resume # => :idle
|
|
585
|
+
status.value # => :busy
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Farce uses a mode manager to retain shareable construction settings. Non-shareable settings are held in a copy envelope on native Ractors, giving each Ractor a copy when it needs them. This happens for construction data, not for every value later written to a local container. See [transfer modes](modes.md#under-the-hood) for how envelopes work.
|
|
589
|
+
|
|
590
|
+
### Separate containers can contain the same objects
|
|
591
|
+
|
|
592
|
+
Within one Ractor, separate scopes may reuse objects from the same decoded construction settings. A fresh backing map therefore does not guarantee a deep copy of all initial values. The two child fibers below have different maps, but their initial array is the same object.
|
|
593
|
+
|
|
594
|
+
```ruby
|
|
595
|
+
state = Farce::Local::Map.new({ items: [] }, scope: :fiber)
|
|
596
|
+
|
|
597
|
+
first = Fiber.new do
|
|
598
|
+
state[:only_first] = true
|
|
599
|
+
state[:items] << :first
|
|
600
|
+
state[:items]
|
|
601
|
+
end.resume
|
|
602
|
+
|
|
603
|
+
second = Fiber.new do
|
|
604
|
+
[state.key?(:only_first), state[:items]]
|
|
605
|
+
end.resume
|
|
606
|
+
|
|
607
|
+
second[0] # => false
|
|
608
|
+
second[1] # => [:first]
|
|
609
|
+
first.equal?(second[1]) # => true
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Use a factory when every scope needs fresh mutable values. `Local::Lazy.new(scope: :fiber) { { items: [] } }` builds a new nested array per fiber. Alternatively, start a local map empty and create values through `store_if_absent` in the scope that needs them. On platforms without native Ractors, construction data is not copied across Farce Ractors either, so factories are useful there too.
|
|
613
|
+
|
|
614
|
+
### Fiber storage inherits a reference to Farce storage
|
|
615
|
+
|
|
616
|
+
Ruby copies the fiber-storage hash when creating a child with default storage inheritance. It does not deep-copy the objects stored in that hash. Farce keeps a storage object in one entry, so the inherited reference can lead parent and child to the same backing containers.
|
|
617
|
+
|
|
618
|
+
```ruby
|
|
619
|
+
context = Farce::Local::Map.new(scope: :fiber_storage)
|
|
620
|
+
context[:phase] = :created
|
|
621
|
+
|
|
622
|
+
child = Fiber.new { context[:phase] }
|
|
623
|
+
context[:phase] = :ready
|
|
624
|
+
child.resume # => :ready
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
This is shared context, not a snapshot of the map at fiber creation. A fresh fiber-storage hash selects fresh Farce storage. The choice affects all Farce objects using `:fiber_storage` in that context. Ruby can also inherit fiber storage into new threads, so this scope should not be treated as a strict thread boundary. Use `:thread` when thread identity is the boundary you need.
|
|
628
|
+
|
|
629
|
+
### Scopes and transfer modes solve different problems
|
|
630
|
+
|
|
631
|
+
A scope selects which contents a caller accesses. A [transfer mode](modes.md) selects how a non-shareable value is carried or stored. `Farce::Queue.new(mode: :local)` has one queue whose local payloads belong to their originating Ractor. `Farce::Local::Queue.new` has a separate queue for each scope.
|
|
632
|
+
|
|
633
|
+
```ruby
|
|
634
|
+
queue = Farce::Local::Queue.new
|
|
635
|
+
queue.push(:main_job)
|
|
636
|
+
results = Farce::Port.new
|
|
637
|
+
|
|
638
|
+
worker = Farce::Ractor.new(queue, results) do |local, outbox|
|
|
639
|
+
outbox.send(local.empty?)
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
results.receive # => true
|
|
643
|
+
queue.pop # => :main_job
|
|
644
|
+
worker.join
|
|
645
|
+
results.close
|
|
646
|
+
queue.close
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Use a local container for independent state behind a shared handle. Use a regular Farce container when callers in different Ractors need to communicate through the same contents. Choosing a scope does not move data from one scope to another.
|