farce 0.0.1.alpha2-x86_64-linux-gnu
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/ext/ext_helper.rb +20 -0
- data/ext/farce/README.md +21 -0
- data/ext/farce/atom.c +1023 -0
- data/ext/farce/bounded_map.c +1683 -0
- data/ext/farce/containers.h +77 -0
- data/ext/farce/counter.c +399 -0
- data/ext/farce/darwin.c +100 -0
- data/ext/farce/depend +12 -0
- data/ext/farce/dict.c +1523 -0
- data/ext/farce/dict.h +152 -0
- data/ext/farce/drivers.c +237 -0
- data/ext/farce/exchanger.c +299 -0
- data/ext/farce/extconf.rb +99 -0
- data/ext/farce/farce.c +359 -0
- data/ext/farce/flag.c +288 -0
- data/ext/farce/io.c +338 -0
- data/ext/farce/lock.c +510 -0
- data/ext/farce/map.c +2240 -0
- data/ext/farce/priority_queue.c +2056 -0
- data/ext/farce/queue.c +1059 -0
- data/ext/farce/reactor.c +820 -0
- data/ext/farce/reactor.h +103 -0
- data/ext/farce/shareable.h +31 -0
- data/ext/farce/signal.c +350 -0
- data/ext/farce/transaction.c +354 -0
- data/ext/farce/transaction.h +40 -0
- data/ext/farce/tree_map.c +1953 -0
- data/ext/farce/trie.c +2020 -0
- data/ext/farce/unshareable.c +155 -0
- data/ext/farce/unshared_io_pool.h +234 -0
- data/ext/farce/unshared_signal.c +263 -0
- data/ext/farce/unshared_wait.h +193 -0
- data/ext/farce/unsupported.c +6 -0
- data/ext/farce/vector.c +1195 -0
- data/ext/farce/weak_map.c +1714 -0
- data/ext/java/org/farce/BoundedMap.java +394 -0
- data/ext/java/org/farce/FiberScheduler.java +141 -0
- data/ext/java/org/farce/PriorityKey.java +45 -0
- data/ext/java/org/farce/PriorityQueue.java +373 -0
- data/ext/java/org/farce/QueueSignal.java +28 -0
- data/ext/rebind/README.md +14 -0
- data/ext/rebind/extconf.rb +10 -0
- data/ext/rebind/rebind.c +186 -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/farce.so +0 -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/rebind.so +0 -0
- data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -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/rebind.so +0 -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 +468 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
class Transaction
|
|
7
|
+
# Binds container wrappers to the transaction attempt that created them.
|
|
8
|
+
#
|
|
9
|
+
# Wrappers call {#access} or {#write} around operations on staged data.
|
|
10
|
+
# These checks reject calls from another Fiber or after the attempt ends.
|
|
11
|
+
# They also invalidate the attempt when an operation raises, so rescuing
|
|
12
|
+
# the error cannot publish writes staged before that failed operation.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
module Wrapper
|
|
16
|
+
include Internal::MarshalSupport::Reject
|
|
17
|
+
|
|
18
|
+
# Wrappers inherit abstract container classes for their interfaces, but
|
|
19
|
+
# must not initialize another ordinary container. This constructor ends
|
|
20
|
+
# Unshareable's super chain after binding the existing transaction snapshot.
|
|
21
|
+
module Initialization
|
|
22
|
+
# Bind the wrapper to its participant and active attempt.
|
|
23
|
+
# Enroll backend when the wrapper owns storage. Composite wrappers use
|
|
24
|
+
# other wrappers for their snapshots and leave backend nil.
|
|
25
|
+
# @param transaction [Transaction] the current attempt
|
|
26
|
+
# @param object [Object] the original participant
|
|
27
|
+
# @param backend [Object, nil] the participant's storage to enroll, if any
|
|
28
|
+
# @param manager [ModeManager, nil] the participant's value transfer manager
|
|
29
|
+
# @param nil_value [BasicObject, nil] the backend's sentinel for stored nil
|
|
30
|
+
def initialize(transaction, object, backend = nil, manager: nil, nil_value: nil)
|
|
31
|
+
@transaction = transaction
|
|
32
|
+
@object = object
|
|
33
|
+
@backend = backend
|
|
34
|
+
@manager = manager
|
|
35
|
+
@nil_value = nil_value
|
|
36
|
+
@entry = enlist(backend) if backend
|
|
37
|
+
@working = @entry&.working
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
private_constant :Initialization
|
|
41
|
+
|
|
42
|
+
include Initialization
|
|
43
|
+
include Unshareable
|
|
44
|
+
|
|
45
|
+
# Allow transaction[wrapper] to reuse a wrapper from this same attempt.
|
|
46
|
+
# A different attempt cannot adopt its snapshot or staged writes.
|
|
47
|
+
# @param transaction [Transaction] the attempt requesting the wrapper
|
|
48
|
+
# @return [self]
|
|
49
|
+
# @raise [TypeError] if the wrapper belongs to a different attempt
|
|
50
|
+
def transaction_wrapper(transaction)
|
|
51
|
+
access do
|
|
52
|
+
raise TypeError, "wrapper belongs to another transaction" unless @transaction.equal?(transaction)
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Select the abstract container helpers that can operate on staged data.
|
|
58
|
+
# Each wrapper supplies its supported helpers. Other inherited operations
|
|
59
|
+
# may wait on live storage, copy the participant, or bypass its snapshot.
|
|
60
|
+
#
|
|
61
|
+
# @param klass [Class] the wrapper class to configure
|
|
62
|
+
# @param helpers [Array<Symbol>] inherited reads to retain and guard
|
|
63
|
+
# @param writes [Array<Symbol>] inherited writes that must also check the
|
|
64
|
+
# original participant's frozen state
|
|
65
|
+
# @return [Class] the configured wrapper class
|
|
66
|
+
def self.inherit(klass, *helpers, writes: [])
|
|
67
|
+
helpers += writes
|
|
68
|
+
helpers |= %i[inspect pretty_print inspect_with] if klass.method_defined?(:inspect_with)
|
|
69
|
+
owners = klass.ancestors.take_while { |ancestor| ancestor != klass.superclass }
|
|
70
|
+
own = klass.public_instance_methods.select { owners.include?(klass.instance_method(it).owner) }
|
|
71
|
+
enumerable = klass <= Enumerable ? Enumerable.public_instance_methods : []
|
|
72
|
+
|
|
73
|
+
# Set and Vector override map/select to construct self.class. Use
|
|
74
|
+
# Enumerable's Array results so these calls do not construct wrappers
|
|
75
|
+
# without a transaction and participant.
|
|
76
|
+
enumerable.each do |name|
|
|
77
|
+
next if own.include?(name) || helpers.include?(name)
|
|
78
|
+
klass.define_method(name, Enumerable.instance_method(name))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
guards = Module.new
|
|
82
|
+
rejected = klass.superclass.public_instance_methods - Object.public_instance_methods -
|
|
83
|
+
enumerable - helpers - own
|
|
84
|
+
rejected.each { |name| klass.class_eval { undef_method(name) } }
|
|
85
|
+
|
|
86
|
+
# Metadata reads and empty-input calls can return without touching
|
|
87
|
+
# storage. Guard the helper itself so these also reject closed attempts.
|
|
88
|
+
helpers.each do |name|
|
|
89
|
+
next if own.include?(name)
|
|
90
|
+
operation = writes.include?(name) ? :write : :access
|
|
91
|
+
guards.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
92
|
+
def #{name}(...)
|
|
93
|
+
#{operation} { super }
|
|
94
|
+
end
|
|
95
|
+
RUBY
|
|
96
|
+
end
|
|
97
|
+
klass.prepend(guards)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def enlist(backend) = @transaction.enlist(backend)
|
|
103
|
+
|
|
104
|
+
# Initialize a composite wrapper after binding it to the participant.
|
|
105
|
+
# Copy the named settings, then replace each storage field with its
|
|
106
|
+
# wrapper from this attempt. Set uses this to retain membership settings
|
|
107
|
+
# while its map handles snapshots and commit validation.
|
|
108
|
+
# @param settings [Array<Symbol>] participant instance variables, without @
|
|
109
|
+
# @param components [Hash{Symbol => Object}] storage fields, without @,
|
|
110
|
+
# and the original objects to wrap
|
|
111
|
+
def compose(*settings, **components)
|
|
112
|
+
settings.each do |name|
|
|
113
|
+
variable = :"@#{name}"
|
|
114
|
+
instance_variable_set(variable, @object.instance_variable_get(variable))
|
|
115
|
+
end
|
|
116
|
+
components.each { |name, object| instance_variable_set(:"@#{name}", @transaction[object]) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# A rescued NoMethodError must still discard earlier staged writes.
|
|
120
|
+
# Local wrappers retain the participant's scope getter, with the same
|
|
121
|
+
# lifetime check as reads of staged values.
|
|
122
|
+
def method_missing(name, *arguments, **options, &block)
|
|
123
|
+
if name == :scope && arguments.empty? && options.empty? && !block && @object.respond_to?(:scope)
|
|
124
|
+
return access { @object.scope }
|
|
125
|
+
end
|
|
126
|
+
@transaction.fail!(retryable: false)
|
|
127
|
+
super
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def respond_to_missing?(name, include_private = false)
|
|
131
|
+
(name == :scope && @object.respond_to?(:scope)) || super
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def require_block!(present)
|
|
135
|
+
return if present
|
|
136
|
+
@transaction.fail!(retryable: false)
|
|
137
|
+
raise LocalJumpError, "no block given"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def access
|
|
141
|
+
@transaction.check_open!
|
|
142
|
+
yield
|
|
143
|
+
rescue Internal::TransactionConflict
|
|
144
|
+
@transaction.fail! if @transaction.state == :active
|
|
145
|
+
raise
|
|
146
|
+
rescue Exception # rubocop:disable Lint/RescueException -- cancellation must poison the attempt too
|
|
147
|
+
@transaction.fail!(retryable: false) if @transaction.state == :active
|
|
148
|
+
raise
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def initialize_copy(*)
|
|
152
|
+
access do
|
|
153
|
+
@transaction.fail!(retryable: false)
|
|
154
|
+
raise TypeError, "transaction wrappers cannot be copied"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def check_frozen! = Internal::Freeze.check(@object)
|
|
159
|
+
|
|
160
|
+
def write
|
|
161
|
+
access do
|
|
162
|
+
raise FrozenError, "cannot modify frozen transaction participant" if @object.frozen?
|
|
163
|
+
@transaction.guard(@object)
|
|
164
|
+
@entry&.write!
|
|
165
|
+
yield
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def wrap(value, mode: nil)
|
|
170
|
+
return @nil_value if value.nil? && @nil_value
|
|
171
|
+
return value unless @manager
|
|
172
|
+
selected = mode || @manager.mode
|
|
173
|
+
|
|
174
|
+
# These modes can change ownership or freeze supplied objects before
|
|
175
|
+
# commit. Discarding staged storage cannot undo those effects.
|
|
176
|
+
if %i[move make_shareable dedup proxy].include?(selected)
|
|
177
|
+
raise TypeError, "#{selected} transfers do not support transactions"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
@manager.wrap(value, mode:)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def unwrap_stored(value)
|
|
184
|
+
return if @nil_value.equal?(value)
|
|
185
|
+
return value unless @manager
|
|
186
|
+
raise TypeError, "move envelopes do not support transactions" if Envelope::Move === value
|
|
187
|
+
@manager.unwrap(value)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def matches?(current, expected, identity:)
|
|
191
|
+
comparison = @manager.wrap(expected, mode: identity ? :local : :copy)
|
|
192
|
+
comparison = @nil_value if expected.nil? && @nil_value
|
|
193
|
+
@manager.same_value?(current, comparison, identity:)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def compared(result)
|
|
197
|
+
@transaction.fail! unless result
|
|
198
|
+
result
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# A transaction groups multiple changes to Farce objects into a single atomic attempt.
|
|
7
|
+
#
|
|
8
|
+
# These changes either all succeed together when the transaction is committed
|
|
9
|
+
# or are all discarded if the transaction fails.
|
|
10
|
+
#
|
|
11
|
+
# Transaction attempts are isolated and can be retried safely.
|
|
12
|
+
#
|
|
13
|
+
# You must therefore make sure that any side-effects not going through transaction wrappers
|
|
14
|
+
# are either completely avoided, or at least made idempotent.
|
|
15
|
+
#
|
|
16
|
+
# ```ruby
|
|
17
|
+
# account1 = Farce::Atom.new(10)
|
|
18
|
+
# account2 = Farce::Atom.new(20)
|
|
19
|
+
#
|
|
20
|
+
# # Wire 10 from account1 to account2
|
|
21
|
+
# Farce.transaction do |tx|
|
|
22
|
+
# raise "insufficient funds" unless tx[account1].value >= 10
|
|
23
|
+
# tx[account1].update { it - 10 }
|
|
24
|
+
# tx[account2].update { it + 10 }
|
|
25
|
+
# end
|
|
26
|
+
# ```
|
|
27
|
+
#
|
|
28
|
+
# Transactions can mix different types and variants, and are therefore ideal for coordinating complex, coordinated
|
|
29
|
+
# changes affecting data within multiple scopes.
|
|
30
|
+
#
|
|
31
|
+
# ## Supported objects
|
|
32
|
+
#
|
|
33
|
+
# Out of the box, transactions support instances of the following classes:
|
|
34
|
+
# * Atoms, except weak atoms
|
|
35
|
+
# * ConcurrentMap subclasses, except weak maps
|
|
36
|
+
# * Molecules
|
|
37
|
+
# * Mutable wrappers with frozen snapshots
|
|
38
|
+
# * Sets, including sorted sets
|
|
39
|
+
# * TreeMap subclasses
|
|
40
|
+
# * Vectors
|
|
41
|
+
# * Concurrent::TVar from the concurrent-ruby gem
|
|
42
|
+
# * Ractor::TVar from the ractor-sharing gem
|
|
43
|
+
# * Ractor::TMVar from the ractor-tmvar gem
|
|
44
|
+
class Transaction
|
|
45
|
+
include Internal::MarshalSupport::Reject
|
|
46
|
+
include Internal::Autoloads
|
|
47
|
+
include Unshareable
|
|
48
|
+
|
|
49
|
+
# Raised when a wrapper is used outside its transaction attempt.
|
|
50
|
+
ClosedError = Class.new(StandardError)
|
|
51
|
+
# Raised when another Fiber uses a transaction.
|
|
52
|
+
OwnershipError = Class.new(StandardError)
|
|
53
|
+
Aborted = Class.new(StandardError)
|
|
54
|
+
private_constant :Aborted, :MapOperations, :SetOperations
|
|
55
|
+
|
|
56
|
+
# @note This method must be called from the main Ractor, and the block must be convertible to a shareable proc.
|
|
57
|
+
#
|
|
58
|
+
# Registers a wrapper for a custom class, allowing them to be used within transactions.
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# Account = Struct.new(:balance) do
|
|
62
|
+
# def withdraw(amount) = balance.value -= amount
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# Farce::Transaction.define(Account) do |account, tx|
|
|
66
|
+
# account.class.new(tx[account.balance])
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# account = Account.new(Farce::Atom.new(10))
|
|
70
|
+
# Farce.transaction { |tx| tx[account].withdraw(3) }
|
|
71
|
+
# account.balance.value # => 7
|
|
72
|
+
#
|
|
73
|
+
# @param klass [Class] the class to wrap
|
|
74
|
+
# @yieldparam object [Object] the participant
|
|
75
|
+
# @yieldparam transaction [Transaction] the current attempt
|
|
76
|
+
# @yieldreturn [Object] the transaction wrapper
|
|
77
|
+
def self.define(klass, &)
|
|
78
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
79
|
+
definition = Internal.prepare_method_definition(&)
|
|
80
|
+
REGISTER.define(klass) { define_method(:call, definition) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Default factory for participants without a registered wrapper definition.
|
|
84
|
+
# @api private
|
|
85
|
+
class Factory
|
|
86
|
+
include Shareable::Immutable
|
|
87
|
+
|
|
88
|
+
# Ask a participant's hook to build a wrapper for the supplied attempt.
|
|
89
|
+
# @param object [#transaction_wrapper] the participant to wrap
|
|
90
|
+
# @param transaction [Transaction] the current attempt
|
|
91
|
+
# @return [Object] the wrapper returned by the participant's hook
|
|
92
|
+
def call(object, transaction)
|
|
93
|
+
raise TypeError, "#{object.class} does not support transactions" unless object.respond_to?(:transaction_wrapper)
|
|
94
|
+
object.transaction_wrapper(transaction)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
REGISTER = ClassMirror.new(Factory) { |mapped, _source| mapped.new }
|
|
99
|
+
private_constant :Factory, :REGISTER
|
|
100
|
+
|
|
101
|
+
# @overload run(*objects, retries: nil, backoff_after: 10, max_backoff: 1.0)
|
|
102
|
+
# Creates and runs a new transaction attempt.
|
|
103
|
+
#
|
|
104
|
+
# Automatically retries failed attempts indefinitely unless a retry limit is specified.
|
|
105
|
+
# Starts backing off after the specified number of attempts, up to the maximum delay.
|
|
106
|
+
#
|
|
107
|
+
# @param objects [Array] list of objects to enroll in the transaction
|
|
108
|
+
# @param retries [Integer, nil] maximum additional attempts, or nil for unlimited retries
|
|
109
|
+
# @param backoff_after [Integer] number of attempts before starting to back off
|
|
110
|
+
# @param max_backoff [Numeric] maximum backoff delay in seconds
|
|
111
|
+
# @return [Boolean] whether the transaction committed successfully
|
|
112
|
+
# @see Farce.transaction
|
|
113
|
+
def self.run(*, retries: nil, backoff_after: 10, max_backoff: 1.0, &) # rubocop:disable Naming/PredicateMethod
|
|
114
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
115
|
+
|
|
116
|
+
unless retries.nil? || (Integer === retries && retries >= 0)
|
|
117
|
+
raise ArgumentError, "retries must be nil or a non-negative Integer"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
unless Integer === backoff_after && backoff_after >= 0
|
|
121
|
+
raise ArgumentError, "backoff_after must be a non-negative Integer"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
attempt = 0
|
|
125
|
+
|
|
126
|
+
while retries.nil? || attempt <= retries
|
|
127
|
+
if attempt > backoff_after
|
|
128
|
+
delay = (attempt - backoff_after) * 0.01
|
|
129
|
+
delay = max_backoff if delay > max_backoff
|
|
130
|
+
scheduler = Fiber.scheduler if Fiber.respond_to?(:scheduler) && !Fiber.current.blocking?
|
|
131
|
+
scheduler ? scheduler.kernel_sleep(delay) : sleep(delay)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
transaction = new
|
|
135
|
+
return true if transaction.run(*, &)
|
|
136
|
+
return false unless transaction.retryable?
|
|
137
|
+
|
|
138
|
+
attempt += 1
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Create an unused attempt bound to the current Fiber.
|
|
145
|
+
# Call {#run} once to enroll participants and stage writes. Use {.run} when
|
|
146
|
+
# failed comparisons or conflicts should create and run fresh attempts.
|
|
147
|
+
def initialize
|
|
148
|
+
@owner = Fiber.current
|
|
149
|
+
@state = :new
|
|
150
|
+
@failed = @aborted = false
|
|
151
|
+
@retryable = true
|
|
152
|
+
@wrappers = {}.compare_by_identity
|
|
153
|
+
@entries = {}.compare_by_identity
|
|
154
|
+
@guards = {}.compare_by_identity
|
|
155
|
+
super
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Report the attempt's lifecycle state.
|
|
159
|
+
# @return [Symbol] :new before run, :active during the block, :committing
|
|
160
|
+
# during publication, then :committed or :failed
|
|
161
|
+
attr_reader :state
|
|
162
|
+
|
|
163
|
+
# Whether abort! was called during this attempt.
|
|
164
|
+
# @return [Boolean]
|
|
165
|
+
def aborted? = @aborted
|
|
166
|
+
|
|
167
|
+
# Whether .run may retry this attempt after a failed commit or comparison.
|
|
168
|
+
# Explicit aborts, wrapper exceptions, and fail!(retryable: false) disable retries.
|
|
169
|
+
# @return [Boolean]
|
|
170
|
+
def retryable? = @retryable && !@aborted
|
|
171
|
+
|
|
172
|
+
# Execute this attempt once. Use {.run} for automatic retries.
|
|
173
|
+
#
|
|
174
|
+
# The block stages work through {#[]}. After it returns, commit validates
|
|
175
|
+
# the attempt's reads and publishes its staged writes together.
|
|
176
|
+
# Cancellation during commit is deferred until publication finishes. Once
|
|
177
|
+
# committed, an interruption does not undo the changes.
|
|
178
|
+
#
|
|
179
|
+
# @param objects [Array] the objects to enroll in this transaction attempt
|
|
180
|
+
# @yield [transaction, *objects] the current transaction and the enrolled objects
|
|
181
|
+
# @yieldparam transaction [Transaction] this active attempt
|
|
182
|
+
# @yieldparam objects [Array] the enrolled objects
|
|
183
|
+
# @return [Boolean] whether the changes committed
|
|
184
|
+
# @raise [ClosedError] if this attempt has already run
|
|
185
|
+
# @raise [OwnershipError] if called from a different Fiber
|
|
186
|
+
def run(*objects)
|
|
187
|
+
started = false
|
|
188
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
189
|
+
check_owner!
|
|
190
|
+
raise ClosedError, "transaction has already run" unless @state == :new
|
|
191
|
+
|
|
192
|
+
@state = :active
|
|
193
|
+
started = true
|
|
194
|
+
|
|
195
|
+
objects.map! { self[it] }
|
|
196
|
+
yield self, *objects
|
|
197
|
+
|
|
198
|
+
check_open!
|
|
199
|
+
|
|
200
|
+
Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
|
|
201
|
+
@state = :committing
|
|
202
|
+
entries = @entries.values
|
|
203
|
+
unless @failed
|
|
204
|
+
entries = entries.filter_map { Internal::TransactionMapSnapshot === it ? it.prepare_commit : it }
|
|
205
|
+
end
|
|
206
|
+
committed = !@failed && Internal.commit_transaction(entries, @guards.values, self)
|
|
207
|
+
@state = committed ? :committed : :failed
|
|
208
|
+
Internal.notify_transaction(entries) if committed && entries.none? { it.respond_to?(:commit_group) } &&
|
|
209
|
+
Internal.respond_to?(:notify_transaction)
|
|
210
|
+
committed
|
|
211
|
+
end
|
|
212
|
+
rescue Aborted, Internal::TransactionConflict
|
|
213
|
+
false
|
|
214
|
+
ensure
|
|
215
|
+
if started
|
|
216
|
+
@state = :failed if %i[active committing].include?(@state)
|
|
217
|
+
Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
|
|
218
|
+
@entries.each_value { it.release if it.respond_to?(:release) }
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Includes an object in this transaction and returns a transaction-aware version of it.
|
|
224
|
+
# Read from and write to the returned object, so the transaction can keep track of inputs and changes.
|
|
225
|
+
#
|
|
226
|
+
# @param object [Object] the participant or a wrapper from this attempt
|
|
227
|
+
# @return [Object] a wrapper valid only during this attempt
|
|
228
|
+
# @raise [TypeError] if the participant is unsupported or belongs to another attempt
|
|
229
|
+
def [](object)
|
|
230
|
+
check_open!
|
|
231
|
+
@wrappers.fetch(object) do
|
|
232
|
+
klass = Kernel.instance_method(:class).bind_call(object)
|
|
233
|
+
@wrappers[object] = REGISTER[klass].call(object, self)
|
|
234
|
+
end
|
|
235
|
+
rescue Internal::TransactionConflict
|
|
236
|
+
fail! if @state == :active && @owner.equal?(Fiber.current)
|
|
237
|
+
raise
|
|
238
|
+
rescue Exception # rubocop:disable Lint/RescueException -- cancellation must poison the attempt too
|
|
239
|
+
if @state == :active && @owner.equal?(Fiber.current)
|
|
240
|
+
@failed = true
|
|
241
|
+
@retryable = false
|
|
242
|
+
end
|
|
243
|
+
raise
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Stop the transaction block and discard all staged writes without retrying.
|
|
247
|
+
# The surrounding run call returns false. No writes to participants are published.
|
|
248
|
+
#
|
|
249
|
+
# @example
|
|
250
|
+
# balance = Farce::Atom.new(100)
|
|
251
|
+
#
|
|
252
|
+
# Farce.transaction do |tx|
|
|
253
|
+
# tx.abort! if tx[balance] < 100
|
|
254
|
+
# tx[balance].value -= 100
|
|
255
|
+
# end
|
|
256
|
+
# @return [void]
|
|
257
|
+
def abort!
|
|
258
|
+
check_open!
|
|
259
|
+
@failed = @aborted = true
|
|
260
|
+
raise Aborted, "transaction aborted"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Mark a failed condition, even if the caller ignores its return value.
|
|
264
|
+
#
|
|
265
|
+
# The block may continue, but commit will discard its staged writes.
|
|
266
|
+
# Custom wrappers can use this for their own conditional operations.
|
|
267
|
+
#
|
|
268
|
+
# Wrapper operations call this automatically when they raise. If you rescue
|
|
269
|
+
# an error before a wrapper is called, such as an argument computation error,
|
|
270
|
+
# call `fail!(retryable: false)` to prevent earlier writes from committing.
|
|
271
|
+
#
|
|
272
|
+
# @param retryable [Boolean] whether .run may repeat the block for this failure
|
|
273
|
+
# @return [false]
|
|
274
|
+
def fail!(retryable: true) # rubocop:disable Naming/PredicateMethod
|
|
275
|
+
check_open!
|
|
276
|
+
@failed = true
|
|
277
|
+
@retryable &&= retryable
|
|
278
|
+
false
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Check lifetime and Fiber ownership before a wrapper accesses staged data.
|
|
282
|
+
# @raise [ClosedError] if the attempt is not active
|
|
283
|
+
# @raise [OwnershipError] if called from a different Fiber
|
|
284
|
+
# @api private
|
|
285
|
+
def check_open!
|
|
286
|
+
check_owner!
|
|
287
|
+
raise ClosedError, "transaction attempt is closed" unless @state == :active
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Enroll storage once and reuse its staged data across wrappers.
|
|
291
|
+
#
|
|
292
|
+
# Backends that expose transaction_source share enrollment with that source,
|
|
293
|
+
# so wrappers of the same storage cannot stage conflicting independent copies.
|
|
294
|
+
#
|
|
295
|
+
# An optional block builds an entry for integrations whose sources do not
|
|
296
|
+
# expose transaction_snapshot. It runs once per source in each attempt.
|
|
297
|
+
# Without a block, enrollment uses the source's snapshot hook.
|
|
298
|
+
#
|
|
299
|
+
# Entries may implement release to free resources held during the attempt.
|
|
300
|
+
# It is called after commit or on any exit that discards staged writes.
|
|
301
|
+
#
|
|
302
|
+
# @param backend [Object] storage to enroll, optionally exposing transaction_source
|
|
303
|
+
# @param per_key [Boolean] track map dependencies when using the source's snapshot hook
|
|
304
|
+
# @yieldparam source [Object] the canonical source whose entry is being created
|
|
305
|
+
# @yieldreturn [#working] the entry containing staged storage and commit hooks
|
|
306
|
+
# @return [#working] the enrolled entry containing this attempt's staged storage
|
|
307
|
+
# @api private
|
|
308
|
+
def enlist(backend, per_key: false)
|
|
309
|
+
check_open!
|
|
310
|
+
source = backend.respond_to?(:transaction_source) ? backend.transaction_source : backend
|
|
311
|
+
@entries.fetch(source) do
|
|
312
|
+
unless block_given? || source.respond_to?(:transaction_snapshot)
|
|
313
|
+
raise TypeError, "#{source.class} does not support transactions"
|
|
314
|
+
end
|
|
315
|
+
Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
|
|
316
|
+
@entries[source] = if block_given?
|
|
317
|
+
yield source
|
|
318
|
+
elsif per_key
|
|
319
|
+
Internal::TransactionMapSnapshot.new(source)
|
|
320
|
+
else
|
|
321
|
+
source.transaction_snapshot
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Include a Local participant's shared freeze flag in commit validation.
|
|
328
|
+
# Its scoped backend alone cannot reveal that another scope froze the handle.
|
|
329
|
+
#
|
|
330
|
+
# @param object [Object] the participant being written
|
|
331
|
+
# @api private
|
|
332
|
+
def guard(object)
|
|
333
|
+
return unless Local::Scoped === object
|
|
334
|
+
@guards[object] = object.instance_variable_get(:@farce_freeze_state)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
private
|
|
338
|
+
|
|
339
|
+
def check_owner!
|
|
340
|
+
raise OwnershipError, "transaction belongs to another Fiber" unless @owner.equal?(Fiber.current)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# A Ractor-shareable map that keeps entries sorted by key.
|
|
7
|
+
# Transfer modes apply only to values. Keys are stored directly, except that mutable strings become immutable.
|
|
8
|
+
# Values stored through this map's mode manager are automatically unwrapped.
|
|
9
|
+
#
|
|
10
|
+
# @example Keeping copied values in key order
|
|
11
|
+
# map = Farce::TreeMap.new(mode: :copy)
|
|
12
|
+
# map[2] = ["draft"]
|
|
13
|
+
# map[1] = ["published"]
|
|
14
|
+
#
|
|
15
|
+
# map.values # => [["published"], ["draft"]]
|
|
16
|
+
class TreeMap < Farce::Abstract::TreeMap
|
|
17
|
+
include Shareable::Delegated
|
|
18
|
+
|
|
19
|
+
# @!macro modes
|
|
20
|
+
# @param entries [Hash, Array<Array(BasicObject, BasicObject)>, Abstract::Map, #each, nil]
|
|
21
|
+
# Optional initial entries for the map.
|
|
22
|
+
# @param mode [Symbol] The default value transfer mode.
|
|
23
|
+
def initialize(entries = nil, mode: :copy, normalize_keys: nil, **keyword_entries)
|
|
24
|
+
unless keyword_entries.empty?
|
|
25
|
+
raise ArgumentError, "entries given as both positional and keyword arguments" unless entries.nil?
|
|
26
|
+
entries = keyword_entries
|
|
27
|
+
end
|
|
28
|
+
@manager = marshal_mode_manager(mode)
|
|
29
|
+
super(entries, normalize_keys:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The default transfer mode for values.
|
|
33
|
+
# @return [Symbol]
|
|
34
|
+
def mode = @manager.mode
|
|
35
|
+
|
|
36
|
+
# Values are stored in Ractor-shareable representations.
|
|
37
|
+
# @return [true]
|
|
38
|
+
def shareable_values? = true
|
|
39
|
+
|
|
40
|
+
protected
|
|
41
|
+
|
|
42
|
+
def unwrap_value(value) = @manager.unwrap(value)
|
|
43
|
+
|
|
44
|
+
def wrap_value(value)
|
|
45
|
+
check_frozen!
|
|
46
|
+
@manager.wrap(value)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def each_for_inspect(&) = internal_map.each(&)
|
|
52
|
+
def inspect_value(inspector, value) = super(inspector, value, @manager)
|
|
53
|
+
def new_tree_map(...) = Internal::StrictTreeMap.new(...)
|
|
54
|
+
def freeze_backend = internal_map
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Unsafe
|
|
7
|
+
# @!macro unsafe
|
|
8
|
+
# An LFU map for caches whose caller provides exclusive access to composed operations.
|
|
9
|
+
# Individual storage operations retain the engine backend's integrity guard. Cache loading
|
|
10
|
+
# does not coordinate competing callers, including callers loading the same key.
|
|
11
|
+
class LFUMap < Abstract::LFUMap
|
|
12
|
+
include Unshareable
|
|
13
|
+
|
|
14
|
+
# @!method store_if_absent(key)
|
|
15
|
+
# Read an existing value or construct and store a value for an absent key.
|
|
16
|
+
# Competing callers can run the loader concurrently, including for equal keys.
|
|
17
|
+
# The caller must provide exclusive access when a single initialization is required.
|
|
18
|
+
# @param key [BasicObject] The key to retrieve or initialize.
|
|
19
|
+
# @yieldreturn [BasicObject] The value to store.
|
|
20
|
+
# @return [BasicObject] The existing or newly stored value.
|
|
21
|
+
# @raise [LocalJumpError] If no block is given.
|
|
22
|
+
|
|
23
|
+
# (see Abstract::Map#[])
|
|
24
|
+
def []=(key, value)
|
|
25
|
+
internal_map[prepare_key(key)] = value
|
|
26
|
+
value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def new_bounded_map(...) = Internal::LFUMap.new(...)
|
|
32
|
+
def new_key_locks(**) = nil
|
|
33
|
+
def with_key_lock(_key) = yield
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Unsafe
|
|
7
|
+
# @!macro unsafe
|
|
8
|
+
# An LRU map for caches whose caller provides exclusive access to composed operations.
|
|
9
|
+
# Individual storage operations retain the engine backend's integrity guard. Cache loading
|
|
10
|
+
# does not coordinate competing callers, including callers loading the same key.
|
|
11
|
+
class LRUMap < Abstract::LRUMap
|
|
12
|
+
include Unshareable
|
|
13
|
+
|
|
14
|
+
# @!method store_if_absent(key)
|
|
15
|
+
# Read an existing value or construct and store a value for an absent key.
|
|
16
|
+
# Competing callers can run the loader concurrently, including for equal keys.
|
|
17
|
+
# The caller must provide exclusive access when a single initialization is required.
|
|
18
|
+
# @param key [BasicObject] The key to retrieve or initialize.
|
|
19
|
+
# @yieldreturn [BasicObject] The value to store.
|
|
20
|
+
# @return [BasicObject] The existing or newly stored value.
|
|
21
|
+
# @raise [LocalJumpError] If no block is given.
|
|
22
|
+
|
|
23
|
+
# (see Abstract::Map#[])
|
|
24
|
+
def []=(key, value)
|
|
25
|
+
internal_map[prepare_key(key)] = value
|
|
26
|
+
value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def new_bounded_map(...) = Internal::LRUMap.new(...)
|
|
32
|
+
def new_key_locks(**) = nil
|
|
33
|
+
def with_key_lock(_key) = yield
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Unsafe
|
|
7
|
+
# @!macro unsafe
|
|
8
|
+
# A {Abstract::TreeMap tree map} that is not thread-safe and cannot be shared between Ractors.
|
|
9
|
+
class TreeMap < Abstract::TreeMap
|
|
10
|
+
include Unshareable
|
|
11
|
+
|
|
12
|
+
undef_method :transaction_wrapper
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def new_tree_map(...) = Internal::UnsafeTreeMap.new(...)
|
|
17
|
+
def new_key_locks = nil
|
|
18
|
+
def with_key_lock(_key) = yield
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|