farce 0 → 0.1.0
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +26 -0
- data/CONTRIBUTING.md +71 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1525 -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/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 +435 -8
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# A Ractor-shareable record with independently atomic fields.
|
|
7
|
+
# Values use the selected transfer mode, just like {Farce::Atom}.
|
|
8
|
+
# An explicitly supplied atom keeps its own settings.
|
|
9
|
+
#
|
|
10
|
+
# This class is an alternative to Ruby's Struct or Data classes.
|
|
11
|
+
#
|
|
12
|
+
# @example Updating a named field atomically
|
|
13
|
+
# Job = Farce::Molecule.define(:status, :attempts)
|
|
14
|
+
# job = Job.new(status: :pending, attempts: 0)
|
|
15
|
+
# job.attempts_atom.update { |count| count + 1 }
|
|
16
|
+
# job.status = :running
|
|
17
|
+
# job.to_h # => { status: :running, attempts: 1 }
|
|
18
|
+
class Molecule < Farce::Abstract::Molecule
|
|
19
|
+
include Shareable
|
|
20
|
+
|
|
21
|
+
# Create a record class with defaults for newly created atoms.
|
|
22
|
+
# @!macro modes
|
|
23
|
+
# @param fields [Array<Symbol, String>] the field names
|
|
24
|
+
# @param mode [Symbol, nil] the default transfer mode, or nil for :copy
|
|
25
|
+
# @param compare_by_identity [Boolean, nil] the default comparison policy
|
|
26
|
+
# @return [Class<Molecule>]
|
|
27
|
+
def self.define(*fields, mode: nil, **, &)
|
|
28
|
+
subclass = super(*fields, **)
|
|
29
|
+
subclass.class_eval "def self.default_mode = #{mode.to_sym.inspect}", __FILE__, __LINE__ unless nil.equal?(mode)
|
|
30
|
+
subclass.class_eval(&) if block_given?
|
|
31
|
+
subclass
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Symbol] the default transfer mode for new records
|
|
35
|
+
def self.default_mode = :copy
|
|
36
|
+
|
|
37
|
+
# @!visibility private
|
|
38
|
+
def self.define_member(field)
|
|
39
|
+
return super unless field == :mode
|
|
40
|
+
raise ArgumentError, "#{field.inspect} is not a valid field name"
|
|
41
|
+
end
|
|
42
|
+
private_class_method :define_member
|
|
43
|
+
|
|
44
|
+
# Initialize fields and optionally override the record class defaults.
|
|
45
|
+
# @!macro modes
|
|
46
|
+
# @param mode [Symbol, nil] the transfer mode, or nil for the class default
|
|
47
|
+
# @param compare_by_identity [Boolean, nil] the comparison policy, or nil for the class default
|
|
48
|
+
# @see Abstract::Molecule#initialize
|
|
49
|
+
def initialize(*, mode: nil, **)
|
|
50
|
+
@mode = mode&.to_sym || self.class.default_mode
|
|
51
|
+
super(*, **)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The default mode used to transfer values between Ractors.
|
|
55
|
+
# @return [Symbol]
|
|
56
|
+
attr_reader :mode
|
|
57
|
+
|
|
58
|
+
private def create_atom(_, value) = Atom.new(value, mode:, compare_by_identity: compare_by_identity?)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# A wrapper making generic Ruby objects shareable while preserving mutability.
|
|
7
|
+
#
|
|
8
|
+
# It achieves this by keeping an immutable copy of the object,
|
|
9
|
+
# and performing an atomic update of this reference for mutating method calls.
|
|
10
|
+
#
|
|
11
|
+
# This means the entire object is copied whenever it is being mutated.
|
|
12
|
+
#
|
|
13
|
+
# You should therefore favor dedicated data structures whenever possible,
|
|
14
|
+
# like using a {Map} instead of wrapping a Hash in a {Mutable}, or a {Vector}
|
|
15
|
+
# instead of doing the same with an Array.
|
|
16
|
+
#
|
|
17
|
+
# Non-mutating calls do not incur this cost.
|
|
18
|
+
#
|
|
19
|
+
# Use a transaction view to stage mutations alongside changes to other participants.
|
|
20
|
+
# The original value changes only when the transaction commits.
|
|
21
|
+
#
|
|
22
|
+
# @example Updating two strings together
|
|
23
|
+
# first = Farce::Mutable.new("queued")
|
|
24
|
+
# second = Farce::Mutable.new("waiting")
|
|
25
|
+
# Farce.transaction(first, second) do |_, first_view, second_view|
|
|
26
|
+
# first_view.replace("running")
|
|
27
|
+
# second_view << " for worker"
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# shareable_string = Farce::Mutable.new("foo")
|
|
32
|
+
# Ractor.new(shareable_string) { it << "bar" }.join
|
|
33
|
+
# shareable_string.to_s # => "foobar"
|
|
34
|
+
class Mutable < BasicObject
|
|
35
|
+
module Inherited
|
|
36
|
+
include ::Farce.const_get(:Internal)::Delegation
|
|
37
|
+
include ::Farce.const_get(:Internal)::Inspect
|
|
38
|
+
|
|
39
|
+
define_method(:dup, ::Kernel.instance_method(:dup))
|
|
40
|
+
define_method(:clone, ::Kernel.instance_method(:clone))
|
|
41
|
+
define_method(:is_a?, ::Kernel.instance_method(:is_a?))
|
|
42
|
+
define_method(:initialize_copy, ::Kernel.instance_method(:initialize_copy))
|
|
43
|
+
define_method(:initialize_dup, ::Kernel.instance_method(:initialize_dup))
|
|
44
|
+
define_method(:initialize_clone, ::Kernel.instance_method(:initialize_clone))
|
|
45
|
+
|
|
46
|
+
private :initialize_copy, :initialize_dup, :initialize_clone
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module Mutator
|
|
50
|
+
extend self
|
|
51
|
+
|
|
52
|
+
def prepare(value, in_place: false)
|
|
53
|
+
return -value if value.is_a? String
|
|
54
|
+
return value if value.frozen? || (Internal.native_ractors? && Ractor.shareable?(value))
|
|
55
|
+
in_place ? value.freeze : value.dup.freeze
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def deref(mutable) = ::Kernel.instance_method(:instance_variable_get).bind_call(mutable, :@atom).value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private_constant :Mutator, :Inherited
|
|
62
|
+
include ::Farce::Shareable::Delegated
|
|
63
|
+
include Inherited
|
|
64
|
+
|
|
65
|
+
# Turns the given {Mutable} into a frozen copy of the underlying object.
|
|
66
|
+
# @param mutable [Mutable] the mutable
|
|
67
|
+
# @return [BasicObject] current snapshot of the object it is wrapping
|
|
68
|
+
def self.deref(mutable) = Mutable === mutable ? Mutator.deref(mutable) : mutable
|
|
69
|
+
|
|
70
|
+
# Creates a subclass of {Mutable} that automatically generates values based on the given factory (usually a class),
|
|
71
|
+
# by invoking `new` on it with any arguments being passed on.
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# # You should probably use Farce::Vector instead.
|
|
75
|
+
# MutableArray = Farce::Mutable[Array]
|
|
76
|
+
# MutableArray.new(2) # => #<MutableArray [nil, nil]>
|
|
77
|
+
#
|
|
78
|
+
# @param factory [#new] The factory to create a subclass for. Typically a class. Must be shareable.
|
|
79
|
+
# @return [Class] The subclass of {Mutable}.
|
|
80
|
+
def self.[](factory)
|
|
81
|
+
raise ::Farce::Ractor::IsolationError, "factory is not shareable" unless ::Farce::Ractor.shareable?(factory)
|
|
82
|
+
|
|
83
|
+
klass = ::Class.new(self)
|
|
84
|
+
klass.set_temporary_name("#{name}[#{factory.name}]")
|
|
85
|
+
klass.instance_variable_set(:@value_factory, factory)
|
|
86
|
+
|
|
87
|
+
klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
88
|
+
def self.new(...) = super(value_factory.new(...))
|
|
89
|
+
def self.value_factory
|
|
90
|
+
return @value_factory if defined?(@value_factory) && @value_factory
|
|
91
|
+
superclass.value_factory
|
|
92
|
+
end
|
|
93
|
+
RUBY
|
|
94
|
+
|
|
95
|
+
klass.singleton_class.class_eval "undef []", __FILE__, __LINE__
|
|
96
|
+
klass
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Creates a new mutable version of the given object.
|
|
100
|
+
# The object will be copied on initialization if it isn't frozen.
|
|
101
|
+
# A frozen version of the object must be Ractor-shareable.
|
|
102
|
+
# @param object [BasicObject] The object to wrap. Must implement `dup`, `freeze`, and `frozen?`.
|
|
103
|
+
def initialize(object)
|
|
104
|
+
@atom = Internal::Atom.new Mutator.prepare(object)
|
|
105
|
+
super()
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @api private
|
|
109
|
+
def transaction_wrapper(transaction) = Transaction::Mutable.new(transaction, self, @atom)
|
|
110
|
+
|
|
111
|
+
# @api private
|
|
112
|
+
def marshal_dump = ::Kernel.raise(::TypeError, "Farce::Mutable cannot be marshaled")
|
|
113
|
+
|
|
114
|
+
# Marshal hook discovery must inspect the wrapper without delegating to its snapshot.
|
|
115
|
+
def respond_to?(name, include_private = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
116
|
+
return true if name == :marshal_dump || name == :transaction_wrapper
|
|
117
|
+
return false if Internal.marshal_protocol_method?(name)
|
|
118
|
+
method_missing(:respond_to?, name, include_private)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @api private
|
|
122
|
+
def is_a?(...) = super || @atom.value.is_a?(...)
|
|
123
|
+
|
|
124
|
+
# @api private
|
|
125
|
+
def inspect_with(inspector)
|
|
126
|
+
value = @atom.value
|
|
127
|
+
klass = ::Kernel.instance_method(:class).bind_call(self)
|
|
128
|
+
name = klass.name
|
|
129
|
+
name += "[#{value.class.name}]" if klass == ::Farce::Mutable
|
|
130
|
+
inspector.group("#<#{name}", ">") do
|
|
131
|
+
inspector.breakable
|
|
132
|
+
inspector.object(value)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
# Delegates all methods to the frozen copy of the current value.
|
|
139
|
+
# If it throws a `FrozenError` it reruns the method against an unfrozen duplicate of the value,
|
|
140
|
+
# freezes it, and uses that as internal value.
|
|
141
|
+
def method_missing(...) # rubocop:disable Style/MissingRespondToMissing
|
|
142
|
+
@atom.value.__send__(...)
|
|
143
|
+
rescue ::FrozenError
|
|
144
|
+
result = nil
|
|
145
|
+
@atom.update do |current|
|
|
146
|
+
copy = current.dup
|
|
147
|
+
result = copy.__send__(...)
|
|
148
|
+
result = self if result.equal?(copy)
|
|
149
|
+
Mutator.prepare(copy, in_place: true)
|
|
150
|
+
end
|
|
151
|
+
result
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def freeze_backend = @atom
|
|
155
|
+
|
|
156
|
+
def initialize_copy(other)
|
|
157
|
+
super
|
|
158
|
+
@atom = Internal::Atom.new(Mutator.deref(other))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def initialize_dup(other)
|
|
162
|
+
super
|
|
163
|
+
publish_shareable_copy(other, operation: :dup)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def initialize_clone(other, freeze: nil)
|
|
167
|
+
super
|
|
168
|
+
publish_shareable_copy(other, operation: :clone, freeze:)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
data/lib/farce/pool.rb
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# A dynamically sized pool of Ractor schedulers.
|
|
7
|
+
#
|
|
8
|
+
# Tasks wait in one shared queue. An empty pool starts its first worker immediately
|
|
9
|
+
# when work arrives. A task that remains queued for `grow_after`
|
|
10
|
+
# causes the pool to add a worker until `max_size` is reached. Workers above
|
|
11
|
+
# `min_size` retire after `shrink_after` without queued or running work.
|
|
12
|
+
# By default, the pool starts with no workers and can shrink back to zero.
|
|
13
|
+
#
|
|
14
|
+
# Most importantly, it exposes a {#schedule} method compatible with {Scheduler#schedule}.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# # Create a new pool
|
|
18
|
+
# pool = Farce::Pool.new
|
|
19
|
+
#
|
|
20
|
+
# # Schedule some work on the pool.
|
|
21
|
+
# pool.schedule do
|
|
22
|
+
# # The work runs in a non-blocking fiber, multiple tasks can run concurrently even on the same worker.
|
|
23
|
+
# loop do
|
|
24
|
+
# sleep 5
|
|
25
|
+
# MyClass.recurring_work
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example Custom Fiber scheduler
|
|
30
|
+
# require "farce"
|
|
31
|
+
# require "carbon_fiber"
|
|
32
|
+
#
|
|
33
|
+
# # At least two Ractors, up to four. Launch a new one if a task waits longer than 100 milliseconds.
|
|
34
|
+
# # Use the fiber scheduler from the carbon_fiber gem.
|
|
35
|
+
# pool = Farce::Pool.new(min_size: 2, max_size: 4, grow_after: 0.1) { CarbonFiber::Scheduler.new }
|
|
36
|
+
#
|
|
37
|
+
# # Schedule some work
|
|
38
|
+
# pool.schedule { MyClass.do_something }
|
|
39
|
+
class Pool < Farce::Abstract::Scheduler
|
|
40
|
+
include Internal::MarshalSupport::Reject
|
|
41
|
+
include Shareable::Unfreezable
|
|
42
|
+
include Internal::Inspect
|
|
43
|
+
|
|
44
|
+
# @return [Float] Maximum queue wait before adding a worker.
|
|
45
|
+
attr_reader :grow_after
|
|
46
|
+
|
|
47
|
+
# @return [Integer, nil] Soft task limit for each worker.
|
|
48
|
+
attr_reader :max_inflight
|
|
49
|
+
|
|
50
|
+
# @return [Integer] Maximum worker count.
|
|
51
|
+
attr_reader :max_size
|
|
52
|
+
|
|
53
|
+
# @return [Integer] Minimum worker count.
|
|
54
|
+
attr_reader :min_size
|
|
55
|
+
|
|
56
|
+
# @return [Float, nil] Idle time before an extra worker retires.
|
|
57
|
+
attr_reader :shrink_after
|
|
58
|
+
|
|
59
|
+
# Create and start an elastic pool using the configured fiber scheduler.
|
|
60
|
+
# An explicit constructor block overrides the configured default.
|
|
61
|
+
#
|
|
62
|
+
# `max_inflight` is a soft limit. A worker may exceed it when its scheduler
|
|
63
|
+
# has no runnable fibers or when the pool has reached `max_size`.
|
|
64
|
+
#
|
|
65
|
+
# @param min_size [Integer] Workers started immediately and retained while open.
|
|
66
|
+
# @param max_size [Integer] Maximum workers allowed.
|
|
67
|
+
# @param max_inflight [Integer, nil] Soft task limit for each worker.
|
|
68
|
+
# @param grow_after [Numeric] Queue wait before adding another worker.
|
|
69
|
+
# @param shrink_after [Numeric, nil] Idle time before an extra worker retires.
|
|
70
|
+
# @param capacity [Integer, nil] Pending-task capacity.
|
|
71
|
+
# @param backend [Symbol] IO backend for the built-in scheduler.
|
|
72
|
+
# @yieldreturn [Object] Fiber scheduler constructed in each worker.
|
|
73
|
+
def initialize(min_size: 0, max_size: 4, max_inflight: 64,
|
|
74
|
+
grow_after: 0.005, shrink_after: 30, capacity: 1024,
|
|
75
|
+
backend: Internal::FROZEN_CONFIG.io_backend, &constructor)
|
|
76
|
+
constructor ||= Internal::FROZEN_CONFIG.fiber_scheduler_constructor
|
|
77
|
+
@min_size = Integer(min_size)
|
|
78
|
+
@max_size = Integer(max_size)
|
|
79
|
+
@max_inflight = max_inflight && Integer(max_inflight)
|
|
80
|
+
@grow_after = duration(grow_after, :grow_after)
|
|
81
|
+
@shrink_after = duration(shrink_after, :shrink_after, nil_allowed: true)
|
|
82
|
+
validate_sizes
|
|
83
|
+
|
|
84
|
+
@backend = backend
|
|
85
|
+
@constructor = constructor
|
|
86
|
+
@constructor = Ractor.shareable_proc(&constructor) if constructor && !Ractor.shareable?(constructor)
|
|
87
|
+
@queue = Internal::Queue.new(capacity: capacity && Integer(capacity), track_age: true)
|
|
88
|
+
@state = Internal::Atom.new(:running)
|
|
89
|
+
@error = Internal::Atom.new
|
|
90
|
+
@worker_count = Internal::Atom.new(0)
|
|
91
|
+
@pressure = Internal::Atom.new
|
|
92
|
+
@admission = Internal::Signal.new
|
|
93
|
+
super()
|
|
94
|
+
begin
|
|
95
|
+
@min_size.times { start_worker }
|
|
96
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
97
|
+
close
|
|
98
|
+
raise
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Enqueue a task for any worker in the pool.
|
|
103
|
+
#
|
|
104
|
+
# Pools have no single owner, so `auto_local` does not select local transfer.
|
|
105
|
+
# Explicit local transfer is rejected because another Ractor may run the task.
|
|
106
|
+
#
|
|
107
|
+
# @!macro modes
|
|
108
|
+
# @param args [Array<Object>] Positional arguments passed to the task block.
|
|
109
|
+
# @param mode [Symbol] Argument transfer mode.
|
|
110
|
+
# @param auto_local [Boolean] Accepted for compatibility with {Scheduler#schedule}.
|
|
111
|
+
# @yield [*args] Task to execute.
|
|
112
|
+
# @return [Pool] self.
|
|
113
|
+
# @raise [PoolClosedError] If the pool is closing or closed.
|
|
114
|
+
def schedule(*args, mode: :copy, auto_local: true, &block) # rubocop:disable Lint/UnusedMethodArgument
|
|
115
|
+
raise PoolClosedError, "cannot schedule task on a closed pool" if closed?
|
|
116
|
+
raise ArgumentError, "local transfer is not supported by a pool" if mode == :local
|
|
117
|
+
|
|
118
|
+
task = Internal::ScheduledTask.new(args, block, mode)
|
|
119
|
+
@queue.push(task)
|
|
120
|
+
arm_scaler
|
|
121
|
+
self
|
|
122
|
+
rescue ClosedQueueError
|
|
123
|
+
raise unless closed?
|
|
124
|
+
raise PoolClosedError, "cannot schedule task on a closed pool"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Stop accepting tasks and drain queued work.
|
|
128
|
+
#
|
|
129
|
+
# @return [Pool] self.
|
|
130
|
+
def close
|
|
131
|
+
return self if closed?
|
|
132
|
+
@state.compare_and_set(:running, :closing)
|
|
133
|
+
@queue.seal
|
|
134
|
+
admission_changed
|
|
135
|
+
if size.zero?
|
|
136
|
+
@queue.closed? ? finish_close : start_worker(drain: true)
|
|
137
|
+
end
|
|
138
|
+
self
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Return the first worker error, if any.
|
|
142
|
+
def error
|
|
143
|
+
value = @error.value
|
|
144
|
+
value.is_a?(Envelope) ? value.value : value
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Return the current number of running and starting workers.
|
|
148
|
+
def size = @worker_count.value
|
|
149
|
+
|
|
150
|
+
# Return the current pool state.
|
|
151
|
+
def state = @state.value
|
|
152
|
+
|
|
153
|
+
# Return whether the pool rejects new tasks.
|
|
154
|
+
def closed? = state != :running
|
|
155
|
+
|
|
156
|
+
# Return whether the pool is draining queued work.
|
|
157
|
+
def closing? = state == :closing
|
|
158
|
+
|
|
159
|
+
# Return whether closing workers have drained all admitted submissions.
|
|
160
|
+
# @api private
|
|
161
|
+
def drained? = closing? && @queue.closed?
|
|
162
|
+
|
|
163
|
+
# Return whether all configured workers have been started.
|
|
164
|
+
def at_max_size? = size >= @max_size
|
|
165
|
+
|
|
166
|
+
# Return the generation used by workers waiting for admission.
|
|
167
|
+
# @api private
|
|
168
|
+
def admission_generation = @admission.generation
|
|
169
|
+
|
|
170
|
+
# Wait for worker capacity or a pool state change.
|
|
171
|
+
# @api private
|
|
172
|
+
def wait_for_admission(generation) = @admission.wait(generation)
|
|
173
|
+
|
|
174
|
+
# Wake workers waiting at their soft admission limit.
|
|
175
|
+
# @api private
|
|
176
|
+
def admission_changed = @admission.broadcast
|
|
177
|
+
|
|
178
|
+
# Record that a task left the shared queue.
|
|
179
|
+
# @api private
|
|
180
|
+
def task_taken
|
|
181
|
+
return if @pressure.value.nil? || at_max_size?
|
|
182
|
+
return unless queue_empty?
|
|
183
|
+
@pressure.value = nil
|
|
184
|
+
arm_scaler if @queue.size.positive?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Reserve a worker retirement above the minimum size.
|
|
188
|
+
# @api private
|
|
189
|
+
def retire_worker
|
|
190
|
+
retired = false
|
|
191
|
+
@worker_count.update do |count|
|
|
192
|
+
if state == :running && count > @min_size && queue_empty?
|
|
193
|
+
retired = true
|
|
194
|
+
count - 1
|
|
195
|
+
else
|
|
196
|
+
count
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
retired
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Remove a stopped worker and preserve its failure.
|
|
203
|
+
# @api private
|
|
204
|
+
def worker_stopped(error, replace: true)
|
|
205
|
+
@worker_count.update { |count| count - 1 }
|
|
206
|
+
@error.compare_and_set(nil, Envelope.new(error)) if error
|
|
207
|
+
if closing?
|
|
208
|
+
if size.zero?
|
|
209
|
+
error && !drained? ? start_worker(drain: true) : finish_close
|
|
210
|
+
end
|
|
211
|
+
elsif state == :running && replace
|
|
212
|
+
begin
|
|
213
|
+
start_worker while size < @min_size
|
|
214
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
215
|
+
nil
|
|
216
|
+
end
|
|
217
|
+
arm_scaler if @queue.size.positive?
|
|
218
|
+
end
|
|
219
|
+
admission_changed
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Recheck delayed queue pressure and grow by one worker.
|
|
223
|
+
# @api private
|
|
224
|
+
def scale_if_needed(started)
|
|
225
|
+
return unless state == :running && @pressure.value == started
|
|
226
|
+
age = @queue.oldest_age
|
|
227
|
+
return clear_pressure(started) unless age
|
|
228
|
+
return reschedule_scaler(started, @grow_after - age) if age < @grow_after
|
|
229
|
+
|
|
230
|
+
start_worker unless at_max_size?
|
|
231
|
+
return clear_pressure(started) if queue_empty? || at_max_size?
|
|
232
|
+
|
|
233
|
+
next_started = @queue.generation
|
|
234
|
+
schedule_scaler(next_started) if @pressure.compare_and_set(started, next_started)
|
|
235
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
236
|
+
@error.compare_and_set(nil, Envelope.new(e))
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# @api private
|
|
240
|
+
def inspect_with(inspector)
|
|
241
|
+
super do
|
|
242
|
+
inspector.attribute(:state, state)
|
|
243
|
+
inspector.attribute(:backend, @backend)
|
|
244
|
+
inspector.attribute(:size, size)
|
|
245
|
+
inspector.attribute(:min_size, @min_size)
|
|
246
|
+
inspector.attribute(:max_size, @max_size)
|
|
247
|
+
inspector.attribute(:max_inflight, @max_inflight) if @max_inflight
|
|
248
|
+
yield if block_given?
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
def validate_sizes
|
|
255
|
+
raise ArgumentError, "min_size must be non-negative" if @min_size.negative?
|
|
256
|
+
raise ArgumentError, "max_size must be positive" unless @max_size.positive?
|
|
257
|
+
raise ArgumentError, "min_size must not exceed max_size" if @min_size > @max_size
|
|
258
|
+
raise ArgumentError, "max_inflight must be positive" if @max_inflight && !@max_inflight.positive?
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def duration(value, name, nil_allowed: false)
|
|
262
|
+
return if value.nil? && nil_allowed
|
|
263
|
+
value = Float(value)
|
|
264
|
+
raise ArgumentError, "#{name} must be finite and non-negative" if !value.finite? || value.negative?
|
|
265
|
+
value
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def start_worker(drain: false, only_if_empty: false)
|
|
269
|
+
reserved = false
|
|
270
|
+
@worker_count.update do |count|
|
|
271
|
+
can_start = state == :running || (drain && closing? && !queue_empty?)
|
|
272
|
+
if can_start && count < @max_size && (!only_if_empty || count.zero?)
|
|
273
|
+
reserved = true
|
|
274
|
+
count + 1
|
|
275
|
+
else
|
|
276
|
+
count
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
return false unless reserved
|
|
280
|
+
|
|
281
|
+
worker = Internal::PoolWorker.new(self)
|
|
282
|
+
scheduler = Scheduler.new(
|
|
283
|
+
backend: @backend,
|
|
284
|
+
queue: @queue,
|
|
285
|
+
pool_worker: worker,
|
|
286
|
+
queue_owner: false,
|
|
287
|
+
&@constructor
|
|
288
|
+
)
|
|
289
|
+
scheduler.launch_ractor
|
|
290
|
+
admission_changed if at_max_size?
|
|
291
|
+
true
|
|
292
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
293
|
+
worker ? worker.stopped(e, replace: false) : @worker_count.update { |count| count - 1 }
|
|
294
|
+
raise
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def arm_scaler
|
|
298
|
+
return if at_max_size? || queue_empty? || state != :running
|
|
299
|
+
start_worker(only_if_empty: true) if size.zero?
|
|
300
|
+
return if at_max_size?
|
|
301
|
+
|
|
302
|
+
started = @queue.generation
|
|
303
|
+
age = @queue.oldest_age
|
|
304
|
+
|
|
305
|
+
return unless age
|
|
306
|
+
return unless @pressure.compare_and_set(nil, started)
|
|
307
|
+
|
|
308
|
+
remaining = @grow_after - age
|
|
309
|
+
remaining = 0 if remaining.negative?
|
|
310
|
+
|
|
311
|
+
schedule_scaler(started, remaining)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def schedule_scaler(started, delay = @grow_after)
|
|
315
|
+
Internal::PoolSupervisor.schedule(self, started, delay)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def reschedule_scaler(started, delay)
|
|
319
|
+
next_started = @queue.generation
|
|
320
|
+
schedule_scaler(next_started, delay) if @pressure.compare_and_set(started, next_started)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def clear_pressure(started)
|
|
324
|
+
return unless @pressure.compare_and_set(started, nil)
|
|
325
|
+
arm_scaler if @queue.size.positive?
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def queue_empty? = @queue.size.zero? # rubocop:disable Style/ZeroLengthPredicate
|
|
329
|
+
|
|
330
|
+
def finish_close = @state.compare_and_set(:closing, :closed)
|
|
331
|
+
end
|
|
332
|
+
end
|
data/lib/farce/port.rb
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# @!parse
|
|
7
|
+
# class Port < Farce::Ractor::Port
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# `Ractor::Port` subclass with additional features, namely {#owned? ownership tracking} and
|
|
11
|
+
# {#send mode based sending}.
|
|
12
|
+
class Port < Internal::Port
|
|
13
|
+
include Abstract::Port
|
|
14
|
+
|
|
15
|
+
MANAGER = ModeManager.new
|
|
16
|
+
SUBCLASSES = Farce::MODES.to_h do |mode|
|
|
17
|
+
if mode == :copy
|
|
18
|
+
normal = self
|
|
19
|
+
else
|
|
20
|
+
normal = Class.new(self)
|
|
21
|
+
normal.set_temporary_name("#{name}[#{mode.inspect}]")
|
|
22
|
+
normal.class_eval "def self.mode = #{mode.inspect}", __FILE__, __LINE__
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
auto_local = Class.new(normal)
|
|
26
|
+
auto_local.set_temporary_name("#{name}[#{mode.inspect}, auto_local: true]")
|
|
27
|
+
auto_local.class_eval "def self.auto_local? = true", __FILE__, __LINE__
|
|
28
|
+
[mode, [normal, auto_local].freeze]
|
|
29
|
+
end.freeze
|
|
30
|
+
|
|
31
|
+
private_constant :MANAGER, :SUBCLASSES
|
|
32
|
+
|
|
33
|
+
# @api private
|
|
34
|
+
def self.[](mode, auto_local: false)
|
|
35
|
+
if self == Port
|
|
36
|
+
return SUBCLASSES.dig(mode, auto_local ? 1 : 0) || raise(ArgumentError, "invalid mode: #{mode.inspect}")
|
|
37
|
+
end
|
|
38
|
+
raise ArgumentError, "mode mismatch: #{mode.inspect} vs #{self.mode.inspect}" if mode != self.mode
|
|
39
|
+
return self if auto_local? == auto_local
|
|
40
|
+
raise ArgumentError, "auto_local mismatch: #{auto_local.inspect} vs #{auto_local?.inspect}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @api private
|
|
44
|
+
def self.mode = :copy
|
|
45
|
+
|
|
46
|
+
# @api private
|
|
47
|
+
def self.auto_local? = false
|
|
48
|
+
|
|
49
|
+
# @!visibility private
|
|
50
|
+
# (see #initialize)
|
|
51
|
+
def self.new(mode: nil, auto_local: nil)
|
|
52
|
+
return super() if mode.nil? && auto_local.nil?
|
|
53
|
+
auto_local = auto_local? if auto_local.nil?
|
|
54
|
+
self[mode || self.mode, auto_local: auto_local].new
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @!method initialize(mode: :copy, auto_local: false)
|
|
58
|
+
# Creates a new port with the given default mode.
|
|
59
|
+
#
|
|
60
|
+
# @!macro modes
|
|
61
|
+
#
|
|
62
|
+
# @param mode [Symbol]
|
|
63
|
+
# The default mode to use when sending values through this port.
|
|
64
|
+
#
|
|
65
|
+
# @param auto_local [Boolean]
|
|
66
|
+
# Whether to automatically use `:local` mode when sending values from the owning ractor.
|
|
67
|
+
|
|
68
|
+
# @attribute [r] mode
|
|
69
|
+
# The port's default mode. Set via {#initialize}.
|
|
70
|
+
# @return [Symbol] The default mode of this port.
|
|
71
|
+
# @see #send
|
|
72
|
+
def mode = self.class.mode
|
|
73
|
+
|
|
74
|
+
# Whether or not auto_local is enabled by default. Set via {#initialize}.
|
|
75
|
+
# @return [Boolean] `true` if auto_local is enabled, `false` otherwise.
|
|
76
|
+
# @see #send
|
|
77
|
+
def auto_local? = self.class.auto_local?
|
|
78
|
+
|
|
79
|
+
# Sends a message through the port.
|
|
80
|
+
#
|
|
81
|
+
# @param message [BasicObject] The message to send.
|
|
82
|
+
#
|
|
83
|
+
# @param move [Boolean, nil]
|
|
84
|
+
# Used to determine `mode` if it is not explicitly specified.
|
|
85
|
+
# If true, the message will be sent in `:move` mode.
|
|
86
|
+
# If false, the message will be sent in the port's default mode, unless that mode is `:move`, in which case it
|
|
87
|
+
# will be sent in `:copy` mode.
|
|
88
|
+
#
|
|
89
|
+
# @param mode [Symbol, nil]
|
|
90
|
+
# The mode to use when sending the message. If nil, the mode will be determined by the `move` parameter and the
|
|
91
|
+
# port's default mode.
|
|
92
|
+
#
|
|
93
|
+
# @param auto_local [Boolean, nil]
|
|
94
|
+
# If `true`, or `nil` and {#auto_local?} is `true`, and send is called from the owning ractor, then the message
|
|
95
|
+
# will be sent in `:local` mode regardless of the specified mode or move flag.
|
|
96
|
+
#
|
|
97
|
+
# @raise [Farce::Ractor::ClosedError] if the port is closed.
|
|
98
|
+
# @return [self]
|
|
99
|
+
def send(message, move: nil, mode: nil, auto_local: nil)
|
|
100
|
+
return super(message) if Ractor.shareable?(message)
|
|
101
|
+
raise Ractor::ClosedError, "port is closed" if closed?
|
|
102
|
+
auto_local = auto_local? if auto_local.nil?
|
|
103
|
+
|
|
104
|
+
if auto_local && owned?
|
|
105
|
+
mode = :local
|
|
106
|
+
elsif mode.nil?
|
|
107
|
+
mode = self.mode
|
|
108
|
+
case move
|
|
109
|
+
when true then mode = :move
|
|
110
|
+
when false then mode = :copy if mode == :move
|
|
111
|
+
when nil # no-op
|
|
112
|
+
else raise ArgumentError, "invalid move: #{move.inspect}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
case mode
|
|
117
|
+
when :copy then super(message)
|
|
118
|
+
when :move then super(message, move: true)
|
|
119
|
+
else super(MANAGER.wrap(message, mode:))
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# (see Abstract::Port#receive)
|
|
124
|
+
# Automatically opens envelopes created by this port's mode manager.
|
|
125
|
+
def receive(timeout: nil)
|
|
126
|
+
# Omit the absent keyword instead of allocating arguments for super.
|
|
127
|
+
result = timeout.nil? ? super() : super
|
|
128
|
+
MANAGER.unwrap(result)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @return [String] A string representation of the port, including its class name and mode.
|
|
132
|
+
def inspect = super.sub(/\A#<.+? (?=(?:to|id):#?\d+)/, "#<Farce::Port mode:#{mode} ")
|
|
133
|
+
end
|
|
134
|
+
end
|