farce 0.0.1.alpha2-x86-linux-musl
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,314 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
# Load worker dependencies before a scheduler is used from another Ractor.
|
|
6
|
+
# Some concurrent autoload can lead to stalls on Windows with Ruby 3.4.
|
|
7
|
+
require "farce/clock"
|
|
8
|
+
require "farce/internal/thread_pool"
|
|
9
|
+
require "farce/resolv"
|
|
10
|
+
require "farce/resolv/dns"
|
|
11
|
+
|
|
12
|
+
module Farce
|
|
13
|
+
module Internal
|
|
14
|
+
# Task admission, timers and shutdown shared by the scheduler implementations.
|
|
15
|
+
module SchedulerLifecycle
|
|
16
|
+
include Farce::Unshareable
|
|
17
|
+
|
|
18
|
+
# Prepending bypasses Unshareable's include hook, so remove freeze here.
|
|
19
|
+
def self.prepended(scheduler)
|
|
20
|
+
scheduler.undef_method(:freeze)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Cancelled < Exception # rubocop:disable Lint/InheritException
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Timer = Struct.new(:deadline, :token, :fiber, :error, :active)
|
|
27
|
+
Completion = Struct.new(:token, :value, :error)
|
|
28
|
+
SLOT_COUNT = 64
|
|
29
|
+
private_constant :Timer, :Completion, :SLOT_COUNT
|
|
30
|
+
|
|
31
|
+
def initialize(backend: FROZEN_CONFIG.io_backend, thread_pool: ThreadPool.current)
|
|
32
|
+
super(backend: backend)
|
|
33
|
+
@owner = owner_thread
|
|
34
|
+
@root = Fiber.current
|
|
35
|
+
@fibers = {}.compare_by_identity
|
|
36
|
+
@timers = Internal::UnsharedPriorityQueue.new
|
|
37
|
+
@mailbox = Thread::Queue.new
|
|
38
|
+
@thread_pool = thread_pool
|
|
39
|
+
@closed = @closing = @running = @stopping = false
|
|
40
|
+
@suspensions = @immediate = 0
|
|
41
|
+
@dispatch_budget = dispatch_budget
|
|
42
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
43
|
+
destroy
|
|
44
|
+
raise
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def fiber(*, **, &work)
|
|
48
|
+
check_owner
|
|
49
|
+
raise IOError, "scheduler is closed" if @closed || @stopping
|
|
50
|
+
raise FiberError, "scheduler must be installed" unless Fiber.scheduler.equal?(self)
|
|
51
|
+
raise ArgumentError, "no block given" unless work
|
|
52
|
+
admitting = true
|
|
53
|
+
failure = nil
|
|
54
|
+
task = Fiber.new(blocking: false, storage: nil) do
|
|
55
|
+
work.call(*, **)
|
|
56
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
57
|
+
raise unless admitting
|
|
58
|
+
failure = e
|
|
59
|
+
ensure
|
|
60
|
+
@fibers.delete(Fiber.current)
|
|
61
|
+
finish_fiber
|
|
62
|
+
end
|
|
63
|
+
@fibers[task] = true
|
|
64
|
+
start_fiber(task)
|
|
65
|
+
admitting = false
|
|
66
|
+
raise failure if failure
|
|
67
|
+
task
|
|
68
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
69
|
+
shutdown if task && !@running && Fiber.current.equal?(@root)
|
|
70
|
+
raise
|
|
71
|
+
ensure
|
|
72
|
+
admitting = false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def block(_blocker, timeout = nil)
|
|
76
|
+
timeout = duration_value(timeout)
|
|
77
|
+
token = arm_wait
|
|
78
|
+
wait_with_timeout(token, timeout, false)
|
|
79
|
+
ensure
|
|
80
|
+
retire_wait(token) if token
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def unblock(_blocker, fiber) # rubocop:disable Naming/PredicateMethod
|
|
84
|
+
return false if @closed || !(token = current_wait(fiber))
|
|
85
|
+
@mailbox << Completion.new(token, true, nil).freeze
|
|
86
|
+
wakeup
|
|
87
|
+
true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def fiber_interrupt(fiber, exception) # rubocop:disable Naming/PredicateMethod
|
|
91
|
+
return false if @closed || !(token = current_wait(fiber))
|
|
92
|
+
@mailbox << Completion.new(token, nil, exception).freeze
|
|
93
|
+
wakeup
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def kernel_sleep(duration = nil)
|
|
98
|
+
started = Clock.now
|
|
99
|
+
block(nil, duration)
|
|
100
|
+
Clock.now - started
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def timeout_after(duration, klass = Timeout::Error, *)
|
|
104
|
+
check_fiber
|
|
105
|
+
duration = duration_value(duration)
|
|
106
|
+
return yield(duration) unless duration
|
|
107
|
+
timer = Timer.new(Clock.now + duration, nil, Fiber.current, klass.exception(*), true)
|
|
108
|
+
@timers.push(timer.deadline, timer)
|
|
109
|
+
policy_changed
|
|
110
|
+
yield duration
|
|
111
|
+
ensure
|
|
112
|
+
remove_timer(timer) if timer
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def yield
|
|
116
|
+
token = arm_wait
|
|
117
|
+
resume_wait(token, nil)
|
|
118
|
+
suspend(token)
|
|
119
|
+
end
|
|
120
|
+
alias cooperate yield
|
|
121
|
+
|
|
122
|
+
def address_resolve(hostname)
|
|
123
|
+
check_fiber
|
|
124
|
+
Resolv.getaddresses(hostname)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def process_wait(pid, flags)
|
|
128
|
+
background { Process::Status.wait(pid, flags) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def blocking_operation_wait(operation)
|
|
132
|
+
# CRuby also invokes this hook from finalizers, where parking is illegal.
|
|
133
|
+
# Keep the opaque runtime operation on its original stack. Explicit file
|
|
134
|
+
# fallbacks use the thread pool. Process waits use separate threads.
|
|
135
|
+
Fiber.blocking { operation.call }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def run
|
|
139
|
+
check_root
|
|
140
|
+
|
|
141
|
+
raise IOError, "scheduler is closed" if @closed || @stopping
|
|
142
|
+
raise FiberError, "recursive scheduler run" if @running
|
|
143
|
+
entered = @running = true
|
|
144
|
+
|
|
145
|
+
Internal.prepare_thread if defined?(Internal.prepare_thread)
|
|
146
|
+
|
|
147
|
+
until idle?
|
|
148
|
+
service_policy
|
|
149
|
+
break if idle?
|
|
150
|
+
deadline = @timers.peek_priority
|
|
151
|
+
timeout = deadline ? deadline - Clock.now : nil
|
|
152
|
+
timeout = 0 if timeout&.negative? || ready? || !@mailbox.empty?
|
|
153
|
+
dispatch(timeout, @timers.empty? ? @dispatch_budget : 256)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
true
|
|
157
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
158
|
+
shutdown if entered
|
|
159
|
+
raise
|
|
160
|
+
ensure
|
|
161
|
+
if entered
|
|
162
|
+
@running = false
|
|
163
|
+
finish_close if @closing
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def idle? = @fibers.empty? && !pending? && @timers.empty? && @mailbox.empty?
|
|
168
|
+
def closed? = @closed
|
|
169
|
+
|
|
170
|
+
# Return a lower bound for fibers that can resume immediately.
|
|
171
|
+
def farce_runnable_count = scheduler_ready_count + @mailbox.size
|
|
172
|
+
|
|
173
|
+
# Stop admission immediately. An active driver owns the drain and releases
|
|
174
|
+
# resources after returning from dispatch. Destroying them here would leave
|
|
175
|
+
# its native stack referring to freed state.
|
|
176
|
+
def close # rubocop:disable Naming/PredicateMethod
|
|
177
|
+
check_owner
|
|
178
|
+
return true if @closed
|
|
179
|
+
begin_close
|
|
180
|
+
@closing = true
|
|
181
|
+
return true if @running || !Fiber.current.equal?(@root)
|
|
182
|
+
begin
|
|
183
|
+
run unless @stopping
|
|
184
|
+
ensure
|
|
185
|
+
finish_close
|
|
186
|
+
end
|
|
187
|
+
true
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Ruby 3.4+ invokes this hook on scheduler replacement and thread exit.
|
|
191
|
+
def scheduler_close(error = $!) # rubocop:disable Style/SpecialGlobalVars
|
|
192
|
+
check_root
|
|
193
|
+
raise FiberError, "recursive scheduler close" if @running
|
|
194
|
+
return close unless error
|
|
195
|
+
# The root is already unwinding: run task ensures without waiting for
|
|
196
|
+
# ordinary completion, and preserve the exception that caused shutdown.
|
|
197
|
+
begin_close
|
|
198
|
+
@closing = true
|
|
199
|
+
finish_close
|
|
200
|
+
true
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
def finish_close
|
|
206
|
+
return if @closed
|
|
207
|
+
shutdown
|
|
208
|
+
destroy
|
|
209
|
+
@timers.clear
|
|
210
|
+
@mailbox.clear
|
|
211
|
+
@closed = true
|
|
212
|
+
@closing = false
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def initialize_copy(*) = raise(TypeError, "fiber schedulers cannot be copied")
|
|
216
|
+
def marshal_dump = raise(TypeError, "fiber schedulers cannot be marshaled")
|
|
217
|
+
|
|
218
|
+
def check_owner
|
|
219
|
+
raise ThreadError, "scheduler belongs to another thread" unless owner_thread.equal?(@owner)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def check_root
|
|
223
|
+
check_owner
|
|
224
|
+
raise FiberError, "operation requires the root fiber" unless Fiber.current.equal?(@root)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def check_fiber
|
|
228
|
+
check_owner
|
|
229
|
+
raise IOError, "scheduler is closed" if @closed || @stopping
|
|
230
|
+
raise FiberError, "operation requires an admitted fiber" unless @fibers.key?(Fiber.current)
|
|
231
|
+
raise FiberError, "scheduler must be installed" unless @closing || Fiber.scheduler.equal?(self)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def duration_value(value)
|
|
235
|
+
return if value.nil?
|
|
236
|
+
seconds = Float(value)
|
|
237
|
+
raise ArgumentError, "invalid timeout" if seconds.nan? || seconds.negative?
|
|
238
|
+
seconds.finite? ? seconds : nil
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def suspend(token)
|
|
242
|
+
@suspensions += 1
|
|
243
|
+
park_current(token)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def wait_with_timeout(token, timeout, value)
|
|
247
|
+
if timeout
|
|
248
|
+
timer = Timer.new(Clock.now + timeout, token, nil, nil, true)
|
|
249
|
+
@timers.push(timer.deadline, timer)
|
|
250
|
+
policy_changed
|
|
251
|
+
end
|
|
252
|
+
result = suspend(token)
|
|
253
|
+
result.nil? ? value : result
|
|
254
|
+
ensure
|
|
255
|
+
remove_timer(timer) if timer
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def remove_timer(timer)
|
|
259
|
+
timer.active = false
|
|
260
|
+
@timers.delete_identity(timer.deadline, timer)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def with_io_timeout(duration, &)
|
|
264
|
+
timeout_after(duration, IO::TimeoutError, "IO timed out", &)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def checkpoint_result(value)
|
|
268
|
+
@immediate += 1
|
|
269
|
+
if @immediate >= SLOT_COUNT
|
|
270
|
+
@immediate = 0
|
|
271
|
+
self.yield
|
|
272
|
+
end
|
|
273
|
+
value
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def service_policy
|
|
277
|
+
SLOT_COUNT.times do
|
|
278
|
+
break if @mailbox.empty?
|
|
279
|
+
item = @mailbox.pop(true)
|
|
280
|
+
break unless item
|
|
281
|
+
item.error ? interrupt_wait(item.token, item.error) : resume_wait(item.token, item.value)
|
|
282
|
+
end
|
|
283
|
+
return if @timers.empty?
|
|
284
|
+
SLOT_COUNT.times do
|
|
285
|
+
timer = @timers.pop_before(Clock.now)
|
|
286
|
+
break unless timer
|
|
287
|
+
next unless timer.active
|
|
288
|
+
timer.active = false
|
|
289
|
+
token = timer.token || current_wait(timer.fiber)
|
|
290
|
+
next unless token
|
|
291
|
+
timer.error ? interrupt_wait(token, timer.error) : resume_wait(token, nil)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def background(&)
|
|
296
|
+
check_fiber
|
|
297
|
+
@thread_pool.call(&)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def shutdown
|
|
301
|
+
begin_shutdown
|
|
302
|
+
@stopping = true
|
|
303
|
+
@fibers.keys.each do |task| # rubocop:disable Style/HashEachMethods
|
|
304
|
+
next unless task.alive?
|
|
305
|
+
begin
|
|
306
|
+
task.raise(Cancelled.new("scheduler aborted"))
|
|
307
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
308
|
+
# Preserve the initiating failure, while running other tasks' ensures.
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Internal
|
|
7
|
+
# Ruby scheduler built around IO.select. Engine implementations can override its IO helpers.
|
|
8
|
+
module SelectScheduler
|
|
9
|
+
include SchedulerIO
|
|
10
|
+
|
|
11
|
+
Wait = Struct.new(:fiber, :status, :value, :error, :groups, :ios)
|
|
12
|
+
private_constant :Wait
|
|
13
|
+
|
|
14
|
+
attr_reader :backend, :fallback_reason
|
|
15
|
+
|
|
16
|
+
def initialize(backend: :auto)
|
|
17
|
+
raise ArgumentError, "unavailable select driver: #{backend}" unless %i[auto select].include?(backend)
|
|
18
|
+
@backend = :select
|
|
19
|
+
@wait_mutex = Thread::Mutex.new
|
|
20
|
+
@waits = {}
|
|
21
|
+
@waiting = {}.compare_by_identity
|
|
22
|
+
@ready = []
|
|
23
|
+
@serial = 0
|
|
24
|
+
@wake_reader, @wake_writer = IO.pipe
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def io_wait(io, events, timeout = nil)
|
|
28
|
+
check_fiber
|
|
29
|
+
events = Integer(events)
|
|
30
|
+
raise ArgumentError, "invalid IO event mask" unless events.positive? && events.nobits?(~7)
|
|
31
|
+
readiness(io_select(events.nobits?(IO::READABLE) ? nil : [io],
|
|
32
|
+
events.nobits?(IO::WRITABLE) ? nil : [io], events.nobits?(IO::PRIORITY) ? nil : [io], timeout))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if SINGLE_TRANSFER
|
|
36
|
+
def io_read(io, buffer, offset, length)
|
|
37
|
+
transfer_io(false, io, buffer, length, offset, single_transfer: true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def io_write(io, buffer, offset, length)
|
|
41
|
+
transfer_io(true, io, buffer, length, offset, single_transfer: true)
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
def io_read(io, buffer, minimum, offset = 0)
|
|
45
|
+
transfer_io(false, io, buffer, minimum, offset)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def io_write(io, buffer, minimum, offset = 0)
|
|
49
|
+
transfer_io(true, io, buffer, minimum, offset)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def io_close(descriptor) # rubocop:disable Naming/PredicateMethod
|
|
54
|
+
check_owner
|
|
55
|
+
cancel_descriptor(descriptor)
|
|
56
|
+
Fiber.blocking { IO.for_fd(descriptor).close }
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def arm_wait
|
|
63
|
+
check_fiber
|
|
64
|
+
@wait_mutex.synchronize do
|
|
65
|
+
token = @serial += 1
|
|
66
|
+
@waits[token] = Wait.new(Fiber.current, :pending)
|
|
67
|
+
@waiting[Fiber.current] = token
|
|
68
|
+
token
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def current_wait(fiber)
|
|
73
|
+
@wait_mutex.synchronize { @waiting[fiber] }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def register_select(token, groups, ios)
|
|
77
|
+
wait = @waits.fetch(token)
|
|
78
|
+
wait.groups, wait.ios = groups, ios
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def park_current(token)
|
|
82
|
+
Fiber.yield
|
|
83
|
+
wait = @waits.fetch(token)
|
|
84
|
+
raise wait.error if wait.error
|
|
85
|
+
wait.value
|
|
86
|
+
ensure
|
|
87
|
+
retire_wait(token)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def resume_wait(token, value, error = nil)
|
|
91
|
+
@wait_mutex.synchronize do
|
|
92
|
+
wait = @waits[token]
|
|
93
|
+
return false unless wait && wait.status == :pending
|
|
94
|
+
wait.status, wait.value, wait.error = :ready, value, error
|
|
95
|
+
@ready << token
|
|
96
|
+
true
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def interrupt_wait(token, error)
|
|
101
|
+
# An interrupt may replace an undispatched result, but never a later wait.
|
|
102
|
+
@wait_mutex.synchronize do
|
|
103
|
+
wait = @waits[token]
|
|
104
|
+
return false unless wait
|
|
105
|
+
@ready << token if wait.status == :pending
|
|
106
|
+
wait.status, wait.error = :ready, error
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def retire_wait(token)
|
|
112
|
+
@wait_mutex.synchronize do
|
|
113
|
+
wait = @waits.delete(token)
|
|
114
|
+
@waiting.delete(wait.fiber) if wait && @waiting[wait.fiber] == token
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def ready? = !@ready.empty?
|
|
119
|
+
def scheduler_ready_count = @ready.size
|
|
120
|
+
def pending? = !@waits.empty?
|
|
121
|
+
def pending_count = @wait_mutex.synchronize { @waits.size }
|
|
122
|
+
|
|
123
|
+
def write_wakeup = @wake_writer.write_nonblock(".", exception: false)
|
|
124
|
+
def read_wakeup = @wake_reader.read_nonblock(4096, exception: false)
|
|
125
|
+
|
|
126
|
+
def wakeup
|
|
127
|
+
write_wakeup
|
|
128
|
+
true
|
|
129
|
+
rescue IOError, SystemCallError
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def dispatch(timeout, budget)
|
|
134
|
+
check_root
|
|
135
|
+
snapshot = @waits.to_a.select { |_, wait| wait.status == :pending && wait.groups }
|
|
136
|
+
groups = [[@wake_reader], [], []]
|
|
137
|
+
snapshot.each do |token, wait|
|
|
138
|
+
if wait.ios.flatten.any?(&:closed?)
|
|
139
|
+
interrupt_wait(token, IOError.new("stream closed while waiting"))
|
|
140
|
+
else
|
|
141
|
+
3.times { |i| groups[i].concat(wait.ios[i]) }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
# Linux select may keep sleeping after another thread closes an IO.
|
|
145
|
+
timeout = [timeout || 0.05, 0.05].min unless snapshot.empty?
|
|
146
|
+
begin
|
|
147
|
+
found = select_snapshot(groups.map(&:uniq), ready? ? 0 : timeout)
|
|
148
|
+
rescue IOError, Errno::EBADF, Errno::ENOTSOCK
|
|
149
|
+
closed = snapshot.select { |_, wait| wait.ios.flatten.any?(&:closed?) }
|
|
150
|
+
raise if closed.empty?
|
|
151
|
+
closed.each { |token, _| interrupt_wait(token, IOError.new("stream closed while waiting")) } # rubocop:disable Style/HashEachMethods -- snapshot is an Array
|
|
152
|
+
end
|
|
153
|
+
if found
|
|
154
|
+
if found[0].delete(@wake_reader)
|
|
155
|
+
loop { break unless read_wakeup.is_a?(String) }
|
|
156
|
+
end
|
|
157
|
+
sets = found.map { |list| list.each_with_object({}.compare_by_identity) { |io, set| set[io] = true } }
|
|
158
|
+
snapshot.each do |token, wait|
|
|
159
|
+
next unless @waits[token].equal?(wait) && wait.status == :pending
|
|
160
|
+
result = wait.groups.each_with_index.map do |list, i|
|
|
161
|
+
list.each_with_index.filter_map { |object, j| object if sets[i][wait.ios[i][j]] }
|
|
162
|
+
end
|
|
163
|
+
resume_wait(token, result) if result.any? { |list| !list.empty? }
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
count = 0
|
|
167
|
+
budget.times do
|
|
168
|
+
token = @ready.shift
|
|
169
|
+
break unless token
|
|
170
|
+
wait = @waits[token]
|
|
171
|
+
next unless wait && wait.status == :ready
|
|
172
|
+
wait.status = :delivered
|
|
173
|
+
wait.fiber.resume
|
|
174
|
+
count += 1
|
|
175
|
+
end
|
|
176
|
+
count
|
|
177
|
+
rescue Errno::EINTR
|
|
178
|
+
0
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def cancel_descriptor(descriptor)
|
|
182
|
+
@waits.each do |token, wait|
|
|
183
|
+
next unless wait.ios&.flatten&.any? { |io| !io.closed? && io.fileno == descriptor }
|
|
184
|
+
interrupt_wait(token, IOError.new("stream closed while waiting"))
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def destroy
|
|
189
|
+
@wake_reader.close if @wake_reader && !@wake_reader.closed?
|
|
190
|
+
@wake_writer.close if @wake_writer && !@wake_writer.closed?
|
|
191
|
+
@waits&.clear
|
|
192
|
+
@waiting&.clear
|
|
193
|
+
@ready&.clear
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def transfer_nonblock(writing, io, buffer, offset, capacity)
|
|
197
|
+
writing ? io.write_nonblock(buffer.get_string(offset, capacity), exception: false) :
|
|
198
|
+
io.read_nonblock(capacity, exception: false)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def transfer_io(writing, io, buffer, minimum, offset, single_transfer: false)
|
|
202
|
+
io, minimum, offset = transfer_arguments(io, buffer, minimum, offset, writing)
|
|
203
|
+
limit = single_transfer ? minimum : buffer.size - offset
|
|
204
|
+
minimum = limit.zero? ? 0 : 1 if single_transfer
|
|
205
|
+
serial = @suspensions
|
|
206
|
+
result = locked_buffer(buffer) do
|
|
207
|
+
operation = lambda do |*_ignored|
|
|
208
|
+
next file_transfer(writing, io, buffer, minimum, offset, limit) if io.stat.file?
|
|
209
|
+
total = 0
|
|
210
|
+
loop do
|
|
211
|
+
capacity = limit - total
|
|
212
|
+
break total if capacity.zero?
|
|
213
|
+
value = Fiber.blocking do
|
|
214
|
+
transfer_nonblock(writing, io, buffer, offset + total, capacity)
|
|
215
|
+
end
|
|
216
|
+
if value == :wait_readable || value == :wait_writable
|
|
217
|
+
io_wait(io, value == :wait_readable ? IO::READABLE : IO::WRITABLE)
|
|
218
|
+
next
|
|
219
|
+
end
|
|
220
|
+
break total if value.nil? || value == 0 # rubocop:disable Style/NumericPredicate -- reads return strings, writes integers
|
|
221
|
+
buffer.set_string(value, offset + total) unless writing
|
|
222
|
+
total += writing ? value : value.bytesize
|
|
223
|
+
break total if total >= minimum
|
|
224
|
+
end
|
|
225
|
+
rescue SystemCallError => e
|
|
226
|
+
total&.positive? ? total : -e.errno
|
|
227
|
+
end
|
|
228
|
+
io.respond_to?(:timeout) && io.timeout ? with_io_timeout(io.timeout, &operation) : operation.call
|
|
229
|
+
end
|
|
230
|
+
checkpoint_result(result) if serial == @suspensions
|
|
231
|
+
result
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# @!visibility private
|
|
7
|
+
module Internal # :nodoc: all
|
|
8
|
+
class Storage
|
|
9
|
+
# Protect shared map access, including reads on engines with unsafe weak maps.
|
|
10
|
+
# Per-key locks let initializers for unrelated keys run independently.
|
|
11
|
+
class ThreadSafe < Storage
|
|
12
|
+
def initialize
|
|
13
|
+
@strong_locks = {}
|
|
14
|
+
@weak_locks = ObjectSpace::WeakKeyMap.new
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def [](key, positional_mode = nil, mode: :auto)
|
|
19
|
+
map = map_for(key, positional_mode || mode, create: false)
|
|
20
|
+
@mutex.synchronize { map[key] } if map
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def []=(key, mode = :auto, value) # rubocop:disable Style/OptionalArguments
|
|
24
|
+
map = map_for(key, mode)
|
|
25
|
+
@mutex.synchronize { map[key] = value }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def key?(key, mode = :auto)
|
|
29
|
+
map = map_for(key, mode, create: false)
|
|
30
|
+
map ? @mutex.synchronize { map.key?(key) } : false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def store_if_absent(key, mode: :auto)
|
|
34
|
+
value = self[key, mode]
|
|
35
|
+
return value unless value.nil?
|
|
36
|
+
|
|
37
|
+
mutex_map = Internal.garbage_collectable?(key) ? @weak_locks : @strong_locks
|
|
38
|
+
mutex = @mutex.synchronize { mutex_map[key] ||= Mutex.new }
|
|
39
|
+
|
|
40
|
+
mutex.synchronize do
|
|
41
|
+
self[key, mode] = yield unless key?(key, mode)
|
|
42
|
+
self[key, mode]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.[](key, scope: :ractor, **) = scope(scope)[key, **]
|
|
48
|
+
|
|
49
|
+
def self.[]=(key, scope = :ractor, mode = :auto, value) # rubocop:disable Style/OptionalArguments
|
|
50
|
+
scope(scope)[key, mode] = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.dig(*, scope: :ractor, **) = scope(scope).dig(*, **)
|
|
54
|
+
|
|
55
|
+
def self.store_if_absent(key, scope: :ractor, **, &) = scope(scope).store_if_absent(key, **, &)
|
|
56
|
+
|
|
57
|
+
def self.scope(scope, raise_exception: true)
|
|
58
|
+
case scope
|
|
59
|
+
when :global
|
|
60
|
+
raise ArgumentError, "global scope is not supported" if raise_exception
|
|
61
|
+
when Symbol
|
|
62
|
+
return __send__(scope) if Farce::SCOPES.include?(scope)
|
|
63
|
+
return unless raise_exception
|
|
64
|
+
raise ArgumentError, "Invalid scope: #{scope.inspect}"
|
|
65
|
+
when Class
|
|
66
|
+
storage = scope(scope.name.downcase.to_sym, raise_exception: false)
|
|
67
|
+
return storage if storage
|
|
68
|
+
raise ArgumentError, "Invalid scope: #{scope.inspect}" if raise_exception
|
|
69
|
+
when Storage
|
|
70
|
+
scope
|
|
71
|
+
else
|
|
72
|
+
raise ArgumentError, "Invalid scope: #{scope.inspect}" if raise_exception
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.ractor
|
|
77
|
+
return MAIN_STORAGE if Ractor.main?
|
|
78
|
+
Ractor.store_if_absent(name) { ThreadSafe.new }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.thread_group(thread = Thread.current)
|
|
82
|
+
group = thread.is_a?(ThreadGroup) ? thread : thread.group
|
|
83
|
+
by_group = ractor.store_if_absent(:thread_groups) { ThreadSafe.new }
|
|
84
|
+
by_group.store_if_absent(group) { ThreadSafe.new }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.thread(thread = Thread.current)
|
|
88
|
+
thread = Internal.storage_thread(thread)
|
|
89
|
+
root = Internal.native_ractors? ? ractor : MAIN_STORAGE
|
|
90
|
+
by_thread = root.store_if_absent(:threads) { ThreadSafe.new }
|
|
91
|
+
by_thread.store_if_absent(thread) { Storage.new }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.fiber_storage = Fiber[name] ||= Storage.new
|
|
95
|
+
|
|
96
|
+
def self.fiber(fiber = Fiber.current)
|
|
97
|
+
by_fiber = ractor.store_if_absent(:fibers) { ThreadSafe.new }
|
|
98
|
+
by_fiber.store_if_absent(fiber) { Storage.new }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def initialize
|
|
102
|
+
@mutex = Mutex.new
|
|
103
|
+
@strong = nil
|
|
104
|
+
@weak = nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def [](key, positional_mode = nil, mode: :auto)
|
|
108
|
+
map = map_for(key, positional_mode || mode, create: false)
|
|
109
|
+
map[key] if map
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def []=(key, mode = :auto, value) # rubocop:disable Style/OptionalArguments
|
|
113
|
+
map_for(key, mode)[key] = value
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def key?(key, mode = :auto) = map_for(key, mode, create: false)&.key?(key)
|
|
117
|
+
|
|
118
|
+
def store_if_absent(key, mode: :auto)
|
|
119
|
+
map = map_for(key, mode)
|
|
120
|
+
map[key] = yield unless map.key?(key)
|
|
121
|
+
map[key]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def dig(key, *keys, mode: :auto)
|
|
125
|
+
value = self[key, mode]
|
|
126
|
+
keys.empty? ? value : value&.dig(*keys)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def clear
|
|
130
|
+
@mutex.synchronize do
|
|
131
|
+
@strong&.clear
|
|
132
|
+
@weak&.clear
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def strong(create: true)
|
|
139
|
+
return @strong unless create && !@strong
|
|
140
|
+
@mutex.synchronize { @strong ||= {} }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def weak(create: true)
|
|
144
|
+
return @weak unless create && !@weak
|
|
145
|
+
@mutex.synchronize { @weak ||= ObjectSpace::WeakKeyMap.new }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def map_for(key, mode, **)
|
|
149
|
+
case mode
|
|
150
|
+
when :auto then return strong(**) if key.instance_of?(String)
|
|
151
|
+
when :strong then return strong(**)
|
|
152
|
+
when :weak # no-op
|
|
153
|
+
else raise ArgumentError, "Invalid mode: #{mode.inspect}"
|
|
154
|
+
end
|
|
155
|
+
Internal.garbage_collectable?(key) ? weak(**) : strong(**)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# shareable_constant_value: none
|
|
160
|
+
MAIN_STORAGE = Storage::ThreadSafe.new
|
|
161
|
+
end
|
|
162
|
+
end
|