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
data/ext/farce/queue.c
ADDED
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
#include "containers.h"
|
|
2
|
+
#include "ruby/io.h"
|
|
3
|
+
#include "unshared_wait.h"
|
|
4
|
+
|
|
5
|
+
#include <errno.h>
|
|
6
|
+
#include <fcntl.h>
|
|
7
|
+
#include <math.h>
|
|
8
|
+
#include <stdint.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <time.h>
|
|
11
|
+
#include <unistd.h>
|
|
12
|
+
#ifdef _WIN32
|
|
13
|
+
#include <windows.h>
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
static VALUE cQueue;
|
|
17
|
+
static ID id_farce;
|
|
18
|
+
static ID id_closed_error;
|
|
19
|
+
static ID id_sealed_error;
|
|
20
|
+
static ID id_timeout;
|
|
21
|
+
#ifdef _WIN32
|
|
22
|
+
static rb_ractor_local_key_t queue_main_ractor_key;
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
#define QUEUE_INITIAL_CAPACITY 16
|
|
26
|
+
|
|
27
|
+
typedef struct {
|
|
28
|
+
int read_fd;
|
|
29
|
+
int write_fd;
|
|
30
|
+
bool set;
|
|
31
|
+
#ifdef _WIN32
|
|
32
|
+
HANDLE event;
|
|
33
|
+
#endif
|
|
34
|
+
} readiness_signal_t;
|
|
35
|
+
|
|
36
|
+
typedef struct {
|
|
37
|
+
pthread_mutex_t lock;
|
|
38
|
+
VALUE *values;
|
|
39
|
+
double *enqueued_at;
|
|
40
|
+
size_t storage_capacity;
|
|
41
|
+
size_t limit;
|
|
42
|
+
size_t size;
|
|
43
|
+
size_t head;
|
|
44
|
+
size_t tail;
|
|
45
|
+
size_t pop_waiters;
|
|
46
|
+
size_t push_waiters;
|
|
47
|
+
readiness_signal_t can_pop;
|
|
48
|
+
readiness_signal_t can_push;
|
|
49
|
+
farce_unshared_wait_list_t unshared_pop_waiters;
|
|
50
|
+
farce_unshared_wait_list_t unshared_push_waiters;
|
|
51
|
+
bool bounded;
|
|
52
|
+
bool sealed;
|
|
53
|
+
bool closed;
|
|
54
|
+
bool track_age;
|
|
55
|
+
uint64_t generation;
|
|
56
|
+
bool initialized;
|
|
57
|
+
bool shared;
|
|
58
|
+
bool unshared_fiber_io;
|
|
59
|
+
bool unshared_notifications_needed;
|
|
60
|
+
} queue_t;
|
|
61
|
+
|
|
62
|
+
static void
|
|
63
|
+
set_fd_flags(int fd)
|
|
64
|
+
{
|
|
65
|
+
#ifdef _WIN32
|
|
66
|
+
(void)fd;
|
|
67
|
+
#else
|
|
68
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
|
69
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
70
|
+
flags = fcntl(fd, F_GETFD, 0);
|
|
71
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
72
|
+
#endif
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static bool
|
|
76
|
+
readiness_initialize(readiness_signal_t *signal)
|
|
77
|
+
{
|
|
78
|
+
int descriptors[2];
|
|
79
|
+
if (pipe(descriptors) != 0) return false;
|
|
80
|
+
#ifdef _WIN32
|
|
81
|
+
signal->event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
|
82
|
+
if (!signal->event) {
|
|
83
|
+
close(descriptors[0]);
|
|
84
|
+
close(descriptors[1]);
|
|
85
|
+
errno = EIO;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
#endif
|
|
89
|
+
signal->read_fd = descriptors[0];
|
|
90
|
+
signal->write_fd = descriptors[1];
|
|
91
|
+
signal->set = false;
|
|
92
|
+
set_fd_flags(signal->read_fd);
|
|
93
|
+
set_fd_flags(signal->write_fd);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static void
|
|
98
|
+
readiness_close(readiness_signal_t *signal)
|
|
99
|
+
{
|
|
100
|
+
if (signal->read_fd >= 0) close(signal->read_fd);
|
|
101
|
+
if (signal->write_fd >= 0) close(signal->write_fd);
|
|
102
|
+
signal->read_fd = -1;
|
|
103
|
+
signal->write_fd = -1;
|
|
104
|
+
signal->set = false;
|
|
105
|
+
#ifdef _WIN32
|
|
106
|
+
if (signal->event) CloseHandle(signal->event);
|
|
107
|
+
signal->event = NULL;
|
|
108
|
+
#endif
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static void
|
|
112
|
+
readiness_set(readiness_signal_t *signal, bool desired)
|
|
113
|
+
{
|
|
114
|
+
unsigned char byte = 1;
|
|
115
|
+
ssize_t result;
|
|
116
|
+
if (signal->read_fd < 0) return;
|
|
117
|
+
if (desired == signal->set) {
|
|
118
|
+
#ifdef _WIN32
|
|
119
|
+
/* The unblock callback can set the event without a readiness byte.
|
|
120
|
+
* Clear that interrupt wakeup when the queue is still not ready. */
|
|
121
|
+
if (!desired) ResetEvent(signal->event);
|
|
122
|
+
#endif
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (desired) {
|
|
126
|
+
do {
|
|
127
|
+
result = write(signal->write_fd, &byte, 1);
|
|
128
|
+
} while (result < 0 && errno == EINTR);
|
|
129
|
+
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
130
|
+
signal->set = true;
|
|
131
|
+
#ifdef _WIN32
|
|
132
|
+
SetEvent(signal->event);
|
|
133
|
+
#endif
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
do {
|
|
138
|
+
result = read(signal->read_fd, &byte, 1);
|
|
139
|
+
} while (result < 0 && errno == EINTR);
|
|
140
|
+
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
141
|
+
signal->set = false;
|
|
142
|
+
#ifdef _WIN32
|
|
143
|
+
ResetEvent(signal->event);
|
|
144
|
+
#endif
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static void
|
|
150
|
+
queue_update_readiness(queue_t *queue)
|
|
151
|
+
{
|
|
152
|
+
if (!queue->shared) {
|
|
153
|
+
if (!queue->unshared_notifications_needed) return;
|
|
154
|
+
if (queue->closed || queue->size > 0) {
|
|
155
|
+
farce_unshared_wait_notify_all(&queue->unshared_pop_waiters);
|
|
156
|
+
}
|
|
157
|
+
if (queue->sealed || !queue->bounded || queue->size < queue->limit) {
|
|
158
|
+
farce_unshared_wait_notify_all(&queue->unshared_push_waiters);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
readiness_set(
|
|
162
|
+
&queue->can_pop,
|
|
163
|
+
queue->pop_waiters > 0 && (queue->closed || queue->size > 0)
|
|
164
|
+
);
|
|
165
|
+
readiness_set(
|
|
166
|
+
&queue->can_push,
|
|
167
|
+
queue->push_waiters > 0 &&
|
|
168
|
+
(queue->sealed || !queue->bounded || queue->size < queue->limit)
|
|
169
|
+
);
|
|
170
|
+
if (!queue->shared) {
|
|
171
|
+
/* Keep the slow path until both waiter mechanisms and every pending
|
|
172
|
+
* readiness byte have drained. Interrupted direct waits may leave the
|
|
173
|
+
* flag set, which is harmless and corrected by the next mutation. */
|
|
174
|
+
queue->unshared_notifications_needed = queue->pop_waiters || queue->push_waiters ||
|
|
175
|
+
queue->unshared_pop_waiters.count || queue->unshared_push_waiters.count ||
|
|
176
|
+
queue->can_pop.set || queue->can_push.set;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static void
|
|
181
|
+
queue_mark(void *pointer)
|
|
182
|
+
{
|
|
183
|
+
queue_t *queue = pointer;
|
|
184
|
+
farce_unshared_wait_mark(&queue->unshared_pop_waiters);
|
|
185
|
+
farce_unshared_wait_mark(&queue->unshared_push_waiters);
|
|
186
|
+
if (!queue->values) return;
|
|
187
|
+
size_t index = queue->head;
|
|
188
|
+
for (size_t remaining = queue->size; remaining > 0; remaining--) {
|
|
189
|
+
rb_gc_mark_movable(queue->values[index]);
|
|
190
|
+
if (++index == queue->storage_capacity) index = 0;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static void
|
|
195
|
+
queue_compact(void *pointer)
|
|
196
|
+
{
|
|
197
|
+
queue_t *queue = pointer;
|
|
198
|
+
if (!queue->values) return;
|
|
199
|
+
size_t index = queue->head;
|
|
200
|
+
for (size_t remaining = queue->size; remaining > 0; remaining--) {
|
|
201
|
+
queue->values[index] = rb_gc_location(queue->values[index]);
|
|
202
|
+
if (++index == queue->storage_capacity) index = 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static void
|
|
207
|
+
queue_free(void *pointer)
|
|
208
|
+
{
|
|
209
|
+
queue_t *queue = pointer;
|
|
210
|
+
if (queue->shared) pthread_mutex_destroy(&queue->lock);
|
|
211
|
+
free(queue->values);
|
|
212
|
+
free(queue->enqueued_at);
|
|
213
|
+
readiness_close(&queue->can_pop);
|
|
214
|
+
readiness_close(&queue->can_push);
|
|
215
|
+
ruby_xfree(queue);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static size_t
|
|
219
|
+
queue_memsize(const void *pointer)
|
|
220
|
+
{
|
|
221
|
+
const queue_t *queue = pointer;
|
|
222
|
+
return queue ? sizeof(queue_t) + queue->storage_capacity *
|
|
223
|
+
(sizeof(VALUE) + (queue->track_age ? sizeof(double) : 0)) : 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static const rb_data_type_t queue_type = {
|
|
227
|
+
.wrap_struct_name = "Ractor::Containers::Queue",
|
|
228
|
+
.function = {
|
|
229
|
+
.dmark = queue_mark,
|
|
230
|
+
.dfree = queue_free,
|
|
231
|
+
.dsize = queue_memsize,
|
|
232
|
+
.dcompact = queue_compact,
|
|
233
|
+
},
|
|
234
|
+
.flags = RUBY_TYPED_FROZEN_SHAREABLE,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
static const rb_data_type_t unshared_queue_type = {
|
|
238
|
+
.wrap_struct_name = "Farce::Internal::UnsharedQueue",
|
|
239
|
+
.function = {
|
|
240
|
+
.dmark = queue_mark,
|
|
241
|
+
.dfree = queue_free,
|
|
242
|
+
.dsize = queue_memsize,
|
|
243
|
+
.dcompact = queue_compact,
|
|
244
|
+
},
|
|
245
|
+
.parent = &queue_type,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/* Unshared queues are confined to one Ractor. These critical sections call no
|
|
249
|
+
* Ruby code and retain the GVL. Waiting releases it only after leaving them. */
|
|
250
|
+
static inline void queue_lock(queue_t *queue)
|
|
251
|
+
{
|
|
252
|
+
if (queue->shared) pthread_mutex_lock(&queue->lock);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static inline void queue_unlock(queue_t *queue)
|
|
256
|
+
{
|
|
257
|
+
if (queue->shared) pthread_mutex_unlock(&queue->lock);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static VALUE
|
|
261
|
+
queue_allocate_type(VALUE klass, bool shared)
|
|
262
|
+
{
|
|
263
|
+
queue_t *queue;
|
|
264
|
+
VALUE object = TypedData_Make_Struct(klass, queue_t, shared ? &queue_type : &unshared_queue_type, queue);
|
|
265
|
+
if (shared) pthread_mutex_init(&queue->lock, NULL);
|
|
266
|
+
queue->values = NULL;
|
|
267
|
+
queue->enqueued_at = NULL;
|
|
268
|
+
queue->storage_capacity = 0;
|
|
269
|
+
queue->limit = 0;
|
|
270
|
+
queue->size = 0;
|
|
271
|
+
queue->head = 0;
|
|
272
|
+
queue->tail = 0;
|
|
273
|
+
queue->pop_waiters = 0;
|
|
274
|
+
queue->push_waiters = 0;
|
|
275
|
+
queue->can_pop = (readiness_signal_t){.read_fd = -1, .write_fd = -1, .set = false};
|
|
276
|
+
queue->can_push = (readiness_signal_t){.read_fd = -1, .write_fd = -1, .set = false};
|
|
277
|
+
queue->unshared_pop_waiters = (farce_unshared_wait_list_t){0};
|
|
278
|
+
queue->unshared_push_waiters = (farce_unshared_wait_list_t){0};
|
|
279
|
+
queue->bounded = true;
|
|
280
|
+
queue->sealed = false;
|
|
281
|
+
queue->closed = false;
|
|
282
|
+
queue->track_age = false;
|
|
283
|
+
queue->generation = 0;
|
|
284
|
+
queue->initialized = false;
|
|
285
|
+
queue->shared = shared;
|
|
286
|
+
queue->unshared_notifications_needed = false;
|
|
287
|
+
return object;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
static VALUE queue_allocate(VALUE klass) { return queue_allocate_type(klass, true); }
|
|
291
|
+
static VALUE unshared_queue_allocate(VALUE klass) { return queue_allocate_type(klass, false); }
|
|
292
|
+
|
|
293
|
+
static queue_t *
|
|
294
|
+
get_queue(VALUE self)
|
|
295
|
+
{
|
|
296
|
+
queue_t *queue;
|
|
297
|
+
TypedData_Get_Struct(self, queue_t, &queue_type, queue);
|
|
298
|
+
if (!queue->initialized) rb_raise(rb_eRuntimeError, "uninitialized Queue");
|
|
299
|
+
return queue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
static void
|
|
303
|
+
queue_validate_references(VALUE self)
|
|
304
|
+
{
|
|
305
|
+
queue_t *queue = get_queue(self);
|
|
306
|
+
|
|
307
|
+
if (queue->size != 0 || queue->unshared_pop_waiters.count != 0 ||
|
|
308
|
+
queue->unshared_push_waiters.count != 0) {
|
|
309
|
+
rb_raise(rb_eRuntimeError, "queue publication requires empty storage");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static VALUE
|
|
314
|
+
queue_freeze(VALUE self)
|
|
315
|
+
{
|
|
316
|
+
queue_t *queue;
|
|
317
|
+
TypedData_Get_Struct(self, queue_t, &queue_type, queue);
|
|
318
|
+
if (queue->shared) containers_raise_unfreezable(self);
|
|
319
|
+
return rb_obj_freeze(self);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static VALUE
|
|
323
|
+
queue_frozen_p(VALUE self)
|
|
324
|
+
{
|
|
325
|
+
queue_t *queue;
|
|
326
|
+
TypedData_Get_Struct(self, queue_t, &queue_type, queue);
|
|
327
|
+
return queue->shared ? Qfalse : rb_obj_frozen_p(self);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
static VALUE
|
|
331
|
+
queue_initialize(int argc, VALUE *argv, VALUE self)
|
|
332
|
+
{
|
|
333
|
+
VALUE keywords = Qnil;
|
|
334
|
+
VALUE capacity_value = Qundef;
|
|
335
|
+
ID keyword_ids[] = {rb_intern("capacity"), rb_intern("track_age"), rb_intern("fiber_wait")};
|
|
336
|
+
VALUE keyword_values[3] = {Qundef, Qundef, Qundef};
|
|
337
|
+
queue_t *queue;
|
|
338
|
+
TypedData_Get_Struct(self, queue_t, &queue_type, queue);
|
|
339
|
+
rb_scan_args(argc, argv, "0:", &keywords);
|
|
340
|
+
if (!NIL_P(keywords)) {
|
|
341
|
+
rb_get_kwargs(keywords, keyword_ids, 0, queue->shared ? 2 : 3, keyword_values);
|
|
342
|
+
capacity_value = keyword_values[0];
|
|
343
|
+
}
|
|
344
|
+
if (queue->initialized) rb_raise(rb_eRuntimeError, "Queue is already initialized");
|
|
345
|
+
bool fiber_io = containers_unshared_fiber_io(keyword_values[2]);
|
|
346
|
+
|
|
347
|
+
if (NIL_P(capacity_value)) {
|
|
348
|
+
queue->bounded = false;
|
|
349
|
+
queue->storage_capacity = QUEUE_INITIAL_CAPACITY;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
VALUE converted = capacity_value == Qundef ? INT2FIX(1024) : rb_to_int(capacity_value);
|
|
353
|
+
long long capacity = NUM2LL(converted);
|
|
354
|
+
if (capacity <= 0) rb_raise(rb_eArgError, "capacity must be positive or nil");
|
|
355
|
+
if ((unsigned long long)capacity > SIZE_MAX / sizeof(VALUE)) {
|
|
356
|
+
rb_raise(rb_eArgError, "capacity is too large");
|
|
357
|
+
}
|
|
358
|
+
queue->bounded = true;
|
|
359
|
+
queue->limit = (size_t)capacity;
|
|
360
|
+
queue->storage_capacity = queue->limit;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
queue->values = calloc(queue->storage_capacity, sizeof(VALUE));
|
|
364
|
+
if (!queue->values) rb_memerror();
|
|
365
|
+
VALUE track_age = keyword_values[1];
|
|
366
|
+
queue->track_age = track_age != Qundef && RTEST(track_age);
|
|
367
|
+
if (queue->track_age) {
|
|
368
|
+
queue->enqueued_at = calloc(queue->storage_capacity, sizeof(double));
|
|
369
|
+
if (!queue->enqueued_at) rb_memerror();
|
|
370
|
+
}
|
|
371
|
+
queue->unshared_fiber_io = fiber_io;
|
|
372
|
+
queue->initialized = true;
|
|
373
|
+
if (queue->shared) containers_publish_native_with_references(self, queue_validate_references);
|
|
374
|
+
else rb_obj_freeze(self);
|
|
375
|
+
return self;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
typedef struct {
|
|
379
|
+
bool finite;
|
|
380
|
+
bool collected;
|
|
381
|
+
double deadline;
|
|
382
|
+
} queue_timeout_t;
|
|
383
|
+
|
|
384
|
+
static double
|
|
385
|
+
monotonic_now(void)
|
|
386
|
+
{
|
|
387
|
+
struct timespec now;
|
|
388
|
+
#ifdef CLOCK_MONOTONIC
|
|
389
|
+
if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
|
|
390
|
+
return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
|
|
391
|
+
}
|
|
392
|
+
#endif
|
|
393
|
+
struct timeval fallback;
|
|
394
|
+
gettimeofday(&fallback, NULL);
|
|
395
|
+
return (double)fallback.tv_sec + (double)fallback.tv_usec / 1000000.0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
static queue_timeout_t
|
|
399
|
+
parse_timeout(VALUE timeout)
|
|
400
|
+
{
|
|
401
|
+
queue_timeout_t parsed = {.finite = false, .deadline = 0};
|
|
402
|
+
if (NIL_P(timeout)) return parsed;
|
|
403
|
+
double seconds = NUM2DBL(timeout);
|
|
404
|
+
if (!isfinite(seconds) || seconds < 0) {
|
|
405
|
+
rb_raise(rb_eArgError, "timeout must be a finite, non-negative number or nil");
|
|
406
|
+
}
|
|
407
|
+
parsed.finite = true;
|
|
408
|
+
/* A zero deadline denotes polling and never needs to read the clock. */
|
|
409
|
+
parsed.deadline = seconds == 0 ? 0 : monotonic_now() + seconds;
|
|
410
|
+
return parsed;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
static VALUE
|
|
414
|
+
extract_timeout(int argc, VALUE *argv)
|
|
415
|
+
{
|
|
416
|
+
if (argc == 0) return Qnil;
|
|
417
|
+
/* rb_scan_args copies keyword hashes for rb_get_kwargs to consume. The
|
|
418
|
+
* common single-keyword case can read the original hash without copying. */
|
|
419
|
+
if (argc == 1 && rb_keyword_given_p() && RHASH_SIZE(argv[0]) == 1) {
|
|
420
|
+
VALUE timeout = rb_hash_lookup2(argv[0], ID2SYM(id_timeout), Qundef);
|
|
421
|
+
if (timeout != Qundef) return timeout;
|
|
422
|
+
}
|
|
423
|
+
VALUE keywords = Qnil;
|
|
424
|
+
VALUE timeout = Qnil;
|
|
425
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
426
|
+
VALUE keyword_values[1];
|
|
427
|
+
rb_scan_args(argc, argv, "0:", &keywords);
|
|
428
|
+
if (!NIL_P(keywords)) {
|
|
429
|
+
rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
430
|
+
if (keyword_values[0] != Qundef) timeout = keyword_values[0];
|
|
431
|
+
}
|
|
432
|
+
return timeout;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
static bool
|
|
436
|
+
queue_wait_for_descriptor(int fd, queue_timeout_t *timeout)
|
|
437
|
+
{
|
|
438
|
+
VALUE wait_timeout = Qnil;
|
|
439
|
+
if (timeout->finite) {
|
|
440
|
+
double remaining = timeout->deadline - monotonic_now();
|
|
441
|
+
if (remaining <= 0) return false;
|
|
442
|
+
wait_timeout = DBL2NUM(remaining);
|
|
443
|
+
}
|
|
444
|
+
return containers_wait_for_readable(fd, wait_timeout);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
typedef struct {
|
|
448
|
+
queue_t *queue;
|
|
449
|
+
readiness_signal_t *signal;
|
|
450
|
+
size_t *waiter_count;
|
|
451
|
+
queue_timeout_t *timeout;
|
|
452
|
+
int wait_fd;
|
|
453
|
+
} queue_wait_context_t;
|
|
454
|
+
|
|
455
|
+
static VALUE
|
|
456
|
+
queue_wait_body(VALUE opaque)
|
|
457
|
+
{
|
|
458
|
+
queue_wait_context_t *context = (queue_wait_context_t *)opaque;
|
|
459
|
+
return queue_wait_for_descriptor(context->wait_fd, context->timeout) ? Qtrue : Qfalse;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
#ifdef _WIN32
|
|
463
|
+
typedef struct {
|
|
464
|
+
HANDLE event;
|
|
465
|
+
DWORD timeout;
|
|
466
|
+
DWORD result;
|
|
467
|
+
} queue_event_wait_t;
|
|
468
|
+
|
|
469
|
+
static void *
|
|
470
|
+
queue_event_wait_without_gvl(void *opaque)
|
|
471
|
+
{
|
|
472
|
+
queue_event_wait_t *wait = (queue_event_wait_t *)opaque;
|
|
473
|
+
wait->result = WaitForSingleObject(wait->event, wait->timeout);
|
|
474
|
+
return NULL;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
static void
|
|
478
|
+
queue_event_wait_interrupt(void *opaque)
|
|
479
|
+
{
|
|
480
|
+
queue_event_wait_t *wait = (queue_event_wait_t *)opaque;
|
|
481
|
+
SetEvent(wait->event);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
static VALUE
|
|
485
|
+
queue_event_wait_body(VALUE opaque)
|
|
486
|
+
{
|
|
487
|
+
queue_wait_context_t *context = (queue_wait_context_t *)opaque;
|
|
488
|
+
/* Bound event waits so we can join VM barriers even when Ruby's Windows
|
|
489
|
+
* interrupt does not invoke the unblock callback. */
|
|
490
|
+
for (;;) {
|
|
491
|
+
DWORD timeout = 10;
|
|
492
|
+
if (context->timeout->finite) {
|
|
493
|
+
double remaining = context->timeout->deadline - monotonic_now();
|
|
494
|
+
if (remaining <= 0) return Qfalse;
|
|
495
|
+
if (remaining < 0.01) timeout = (DWORD)ceil(remaining * 1000);
|
|
496
|
+
}
|
|
497
|
+
queue_event_wait_t wait = {
|
|
498
|
+
.event = context->signal->event,
|
|
499
|
+
.timeout = timeout,
|
|
500
|
+
.result = WAIT_FAILED,
|
|
501
|
+
};
|
|
502
|
+
rb_thread_call_without_gvl(
|
|
503
|
+
queue_event_wait_without_gvl,
|
|
504
|
+
&wait,
|
|
505
|
+
queue_event_wait_interrupt,
|
|
506
|
+
&wait
|
|
507
|
+
);
|
|
508
|
+
if (wait.result == WAIT_TIMEOUT) {
|
|
509
|
+
containers_wait_safepoint();
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (wait.result == WAIT_OBJECT_0) return Qtrue;
|
|
513
|
+
errno = EIO;
|
|
514
|
+
rb_sys_fail("WaitForSingleObject");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
#endif
|
|
518
|
+
|
|
519
|
+
static VALUE
|
|
520
|
+
queue_wait_cleanup(VALUE opaque)
|
|
521
|
+
{
|
|
522
|
+
queue_wait_context_t *context = (queue_wait_context_t *)opaque;
|
|
523
|
+
if (context->wait_fd >= 0) close(context->wait_fd);
|
|
524
|
+
queue_lock(context->queue);
|
|
525
|
+
(*context->waiter_count)--;
|
|
526
|
+
queue_update_readiness(context->queue);
|
|
527
|
+
if (context->queue->closed && *context->waiter_count == 0) {
|
|
528
|
+
readiness_close(context->signal);
|
|
529
|
+
}
|
|
530
|
+
queue_unlock(context->queue);
|
|
531
|
+
return Qnil;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
typedef struct {
|
|
535
|
+
queue_wait_context_t wait;
|
|
536
|
+
int error;
|
|
537
|
+
const char *operation;
|
|
538
|
+
} queue_descriptor_pressure_t;
|
|
539
|
+
|
|
540
|
+
static VALUE
|
|
541
|
+
queue_descriptor_pressure_body(VALUE opaque)
|
|
542
|
+
{
|
|
543
|
+
queue_descriptor_pressure_t *context = (queue_descriptor_pressure_t *)opaque;
|
|
544
|
+
queue_timeout_t *timeout = context->wait.timeout;
|
|
545
|
+
VALUE remaining = timeout->finite ? DBL2NUM(timeout->deadline - monotonic_now()) : Qnil;
|
|
546
|
+
containers_wait_without_descriptor(context->error, context->operation, &timeout->collected, remaining);
|
|
547
|
+
return Qtrue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* Count polling waiters too, and unwind the count on scheduler cancellation or
|
|
551
|
+
* a GC callback failure. No descriptor exists for this wait to close. */
|
|
552
|
+
static bool
|
|
553
|
+
queue_wait_without_descriptor(
|
|
554
|
+
queue_t *queue, readiness_signal_t *signal, size_t *waiter_count,
|
|
555
|
+
queue_timeout_t *timeout, int error, const char *operation
|
|
556
|
+
)
|
|
557
|
+
{
|
|
558
|
+
queue_descriptor_pressure_t context = {
|
|
559
|
+
.wait = {
|
|
560
|
+
.queue = queue,
|
|
561
|
+
.signal = signal,
|
|
562
|
+
.waiter_count = waiter_count,
|
|
563
|
+
.timeout = timeout,
|
|
564
|
+
.wait_fd = -1,
|
|
565
|
+
},
|
|
566
|
+
.error = error,
|
|
567
|
+
.operation = operation,
|
|
568
|
+
};
|
|
569
|
+
(*waiter_count)++;
|
|
570
|
+
queue_unlock(queue);
|
|
571
|
+
return RTEST(rb_ensure(
|
|
572
|
+
queue_descriptor_pressure_body, (VALUE)&context,
|
|
573
|
+
queue_wait_cleanup, (VALUE)&context.wait
|
|
574
|
+
));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/* Called with queue->lock held and always returns with it released. Registering
|
|
578
|
+
* before the unlock closes the check-to-wait race with a producer or consumer. */
|
|
579
|
+
static bool
|
|
580
|
+
queue_wait(VALUE self, queue_t *queue, readiness_signal_t *signal, size_t *waiter_count, queue_timeout_t *timeout)
|
|
581
|
+
{
|
|
582
|
+
if (timeout->finite && (timeout->deadline == 0 || timeout->deadline <= monotonic_now())) {
|
|
583
|
+
queue_unlock(queue);
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
if (!queue->shared) queue->unshared_notifications_needed = true;
|
|
587
|
+
if (!queue->shared && (!queue->unshared_fiber_io || NIL_P(rb_fiber_scheduler_current()))) {
|
|
588
|
+
farce_unshared_wait_list_t *list = signal == &queue->can_pop ?
|
|
589
|
+
&queue->unshared_pop_waiters : &queue->unshared_push_waiters;
|
|
590
|
+
return farce_unshared_wait(list, self, timeout->finite, timeout->deadline);
|
|
591
|
+
}
|
|
592
|
+
if (signal->read_fd < 0 && !readiness_initialize(signal)) {
|
|
593
|
+
return queue_wait_without_descriptor(queue, signal, waiter_count, timeout, errno, "pipe");
|
|
594
|
+
}
|
|
595
|
+
#ifdef _WIN32
|
|
596
|
+
VALUE main_ractor_marker;
|
|
597
|
+
bool in_main_ractor = rb_ractor_local_storage_value_lookup(
|
|
598
|
+
queue_main_ractor_key,
|
|
599
|
+
&main_ractor_marker
|
|
600
|
+
);
|
|
601
|
+
if (in_main_ractor && NIL_P(rb_fiber_scheduler_current())) {
|
|
602
|
+
queue_wait_context_t context = {
|
|
603
|
+
.queue = queue,
|
|
604
|
+
.signal = signal,
|
|
605
|
+
.waiter_count = waiter_count,
|
|
606
|
+
.timeout = timeout,
|
|
607
|
+
.wait_fd = -1,
|
|
608
|
+
};
|
|
609
|
+
(*waiter_count)++;
|
|
610
|
+
queue_update_readiness(queue);
|
|
611
|
+
queue_unlock(queue);
|
|
612
|
+
return RTEST(rb_ensure(queue_event_wait_body, (VALUE)&context, queue_wait_cleanup, (VALUE)&context));
|
|
613
|
+
}
|
|
614
|
+
#endif
|
|
615
|
+
/* Ruby 3.4 reports rb_io_close for an external wrapper to every waiter on
|
|
616
|
+
* the same descriptor number. A duplicate observes the same readiness pipe
|
|
617
|
+
* without letting one waiter's wrapper cleanup cancel its siblings. */
|
|
618
|
+
int wait_fd = dup(signal->read_fd);
|
|
619
|
+
if (wait_fd < 0) {
|
|
620
|
+
return queue_wait_without_descriptor(queue, signal, waiter_count, timeout, errno, "dup");
|
|
621
|
+
}
|
|
622
|
+
set_fd_flags(wait_fd);
|
|
623
|
+
queue_wait_context_t context = {
|
|
624
|
+
.queue = queue,
|
|
625
|
+
.signal = signal,
|
|
626
|
+
.waiter_count = waiter_count,
|
|
627
|
+
.timeout = timeout,
|
|
628
|
+
.wait_fd = wait_fd,
|
|
629
|
+
};
|
|
630
|
+
(*waiter_count)++;
|
|
631
|
+
queue_update_readiness(queue);
|
|
632
|
+
queue_unlock(queue);
|
|
633
|
+
return RTEST(rb_ensure(queue_wait_body, (VALUE)&context, queue_wait_cleanup, (VALUE)&context));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
RBIMPL_ATTR_NORETURN()
|
|
637
|
+
static void
|
|
638
|
+
raise_queue_closed(void)
|
|
639
|
+
{
|
|
640
|
+
VALUE farce = rb_const_get(rb_cObject, id_farce);
|
|
641
|
+
rb_raise(rb_const_get(farce, id_closed_error), "queue is closed");
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
RBIMPL_ATTR_NORETURN()
|
|
645
|
+
static void
|
|
646
|
+
raise_queue_sealed(void)
|
|
647
|
+
{
|
|
648
|
+
VALUE farce = rb_const_get(rb_cObject, id_farce);
|
|
649
|
+
rb_raise(rb_const_get(farce, id_sealed_error), "queue is sealed");
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
static VALUE
|
|
653
|
+
unshared_queue_fiber_wait(VALUE self)
|
|
654
|
+
{
|
|
655
|
+
return ID2SYM(rb_intern(get_queue(self)->unshared_fiber_io ? "io" : "block"));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
static VALUE
|
|
659
|
+
queue_capacity(VALUE self)
|
|
660
|
+
{
|
|
661
|
+
queue_t *queue = get_queue(self);
|
|
662
|
+
return queue->bounded ? SIZET2NUM(queue->limit) : Qnil;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* Called with queue->lock held. The queue is unchanged if allocation fails. */
|
|
666
|
+
static bool
|
|
667
|
+
queue_grow(queue_t *queue)
|
|
668
|
+
{
|
|
669
|
+
size_t current = queue->storage_capacity;
|
|
670
|
+
if (current > SIZE_MAX / 2 || current * 2 > SIZE_MAX / sizeof(VALUE)) return false;
|
|
671
|
+
|
|
672
|
+
size_t grown = current * 2;
|
|
673
|
+
VALUE *values = calloc(grown, sizeof(VALUE));
|
|
674
|
+
if (!values) return false;
|
|
675
|
+
double *enqueued_at = NULL;
|
|
676
|
+
if (queue->track_age) {
|
|
677
|
+
enqueued_at = calloc(grown, sizeof(double));
|
|
678
|
+
if (!enqueued_at) {
|
|
679
|
+
free(values);
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
for (size_t index = 0; index < queue->size; index++) {
|
|
685
|
+
size_t source = (queue->head + index) % current;
|
|
686
|
+
values[index] = queue->values[source];
|
|
687
|
+
if (queue->track_age) enqueued_at[index] = queue->enqueued_at[source];
|
|
688
|
+
}
|
|
689
|
+
free(queue->values);
|
|
690
|
+
free(queue->enqueued_at);
|
|
691
|
+
queue->values = values;
|
|
692
|
+
queue->enqueued_at = enqueued_at;
|
|
693
|
+
queue->storage_capacity = grown;
|
|
694
|
+
queue->head = 0;
|
|
695
|
+
queue->tail = queue->size;
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
static VALUE
|
|
700
|
+
queue_size(VALUE self)
|
|
701
|
+
{
|
|
702
|
+
queue_t *queue = get_queue(self);
|
|
703
|
+
size_t size;
|
|
704
|
+
queue_lock(queue);
|
|
705
|
+
size = queue->size;
|
|
706
|
+
queue_unlock(queue);
|
|
707
|
+
return SIZET2NUM(size);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
static VALUE
|
|
711
|
+
queue_num_waiting(VALUE self)
|
|
712
|
+
{
|
|
713
|
+
queue_t *queue = get_queue(self);
|
|
714
|
+
size_t num_waiting;
|
|
715
|
+
queue_lock(queue);
|
|
716
|
+
num_waiting = queue->pop_waiters + queue->push_waiters +
|
|
717
|
+
queue->unshared_pop_waiters.count + queue->unshared_push_waiters.count;
|
|
718
|
+
queue_unlock(queue);
|
|
719
|
+
return SIZET2NUM(num_waiting);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
static VALUE
|
|
723
|
+
queue_clear(VALUE self)
|
|
724
|
+
{
|
|
725
|
+
queue_t *queue = get_queue(self);
|
|
726
|
+
queue_lock(queue);
|
|
727
|
+
|
|
728
|
+
size_t index = queue->head;
|
|
729
|
+
for (size_t remaining = queue->size; remaining > 0; remaining--) {
|
|
730
|
+
queue->values[index] = Qnil;
|
|
731
|
+
if (queue->track_age) queue->enqueued_at[index] = 0;
|
|
732
|
+
index++;
|
|
733
|
+
if (index == queue->storage_capacity) index = 0;
|
|
734
|
+
}
|
|
735
|
+
if (queue->size > 0 && queue->track_age) queue->generation++;
|
|
736
|
+
queue->size = 0;
|
|
737
|
+
queue->head = 0;
|
|
738
|
+
queue->tail = 0;
|
|
739
|
+
if (queue->sealed) queue->closed = true;
|
|
740
|
+
queue_update_readiness(queue);
|
|
741
|
+
|
|
742
|
+
queue_unlock(queue);
|
|
743
|
+
return self;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
static VALUE
|
|
747
|
+
queue_closed_p(VALUE self)
|
|
748
|
+
{
|
|
749
|
+
queue_t *queue = get_queue(self);
|
|
750
|
+
bool closed;
|
|
751
|
+
queue_lock(queue);
|
|
752
|
+
closed = queue->closed;
|
|
753
|
+
queue_unlock(queue);
|
|
754
|
+
return closed ? Qtrue : Qfalse;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
static VALUE
|
|
758
|
+
queue_sealed_p(VALUE self)
|
|
759
|
+
{
|
|
760
|
+
queue_t *queue = get_queue(self);
|
|
761
|
+
bool sealed;
|
|
762
|
+
queue_lock(queue);
|
|
763
|
+
sealed = queue->sealed;
|
|
764
|
+
queue_unlock(queue);
|
|
765
|
+
return sealed ? Qtrue : Qfalse;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
static VALUE
|
|
769
|
+
queue_age_tracking_p(VALUE self)
|
|
770
|
+
{
|
|
771
|
+
return get_queue(self)->track_age ? Qtrue : Qfalse;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
static VALUE
|
|
775
|
+
queue_generation(VALUE self)
|
|
776
|
+
{
|
|
777
|
+
queue_t *queue = get_queue(self);
|
|
778
|
+
uint64_t generation;
|
|
779
|
+
if (!queue->track_age) return Qnil;
|
|
780
|
+
queue_lock(queue);
|
|
781
|
+
generation = queue->generation;
|
|
782
|
+
queue_unlock(queue);
|
|
783
|
+
return ULL2NUM(generation);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
static VALUE
|
|
787
|
+
queue_oldest_enqueued_at(VALUE self)
|
|
788
|
+
{
|
|
789
|
+
queue_t *queue = get_queue(self);
|
|
790
|
+
double timestamp;
|
|
791
|
+
if (!queue->track_age) return Qnil;
|
|
792
|
+
queue_lock(queue);
|
|
793
|
+
if (queue->size == 0) {
|
|
794
|
+
queue_unlock(queue);
|
|
795
|
+
return Qnil;
|
|
796
|
+
}
|
|
797
|
+
timestamp = queue->enqueued_at[queue->head];
|
|
798
|
+
queue_unlock(queue);
|
|
799
|
+
return DBL2NUM(timestamp);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
static VALUE
|
|
803
|
+
queue_oldest_age(VALUE self)
|
|
804
|
+
{
|
|
805
|
+
queue_t *queue = get_queue(self);
|
|
806
|
+
double age;
|
|
807
|
+
if (!queue->track_age) return Qnil;
|
|
808
|
+
queue_lock(queue);
|
|
809
|
+
if (queue->size == 0) {
|
|
810
|
+
queue_unlock(queue);
|
|
811
|
+
return Qnil;
|
|
812
|
+
}
|
|
813
|
+
age = monotonic_now() - queue->enqueued_at[queue->head];
|
|
814
|
+
queue_unlock(queue);
|
|
815
|
+
return DBL2NUM(age);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
static VALUE
|
|
819
|
+
queue_seal(VALUE self)
|
|
820
|
+
{
|
|
821
|
+
queue_t *queue = get_queue(self);
|
|
822
|
+
queue_lock(queue);
|
|
823
|
+
if (!queue->sealed) {
|
|
824
|
+
queue->sealed = true;
|
|
825
|
+
if (queue->size == 0) queue->closed = true;
|
|
826
|
+
if (queue->track_age) queue->generation++;
|
|
827
|
+
queue_update_readiness(queue);
|
|
828
|
+
}
|
|
829
|
+
queue_unlock(queue);
|
|
830
|
+
return self;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
static VALUE
|
|
834
|
+
queue_close(VALUE self)
|
|
835
|
+
{
|
|
836
|
+
queue_t *queue = get_queue(self);
|
|
837
|
+
queue_lock(queue);
|
|
838
|
+
if (!queue->closed) {
|
|
839
|
+
queue->sealed = true;
|
|
840
|
+
queue->closed = true;
|
|
841
|
+
if (queue->track_age) queue->generation++;
|
|
842
|
+
queue_update_readiness(queue);
|
|
843
|
+
if (queue->pop_waiters == 0) readiness_close(&queue->can_pop);
|
|
844
|
+
if (queue->push_waiters == 0) readiness_close(&queue->can_push);
|
|
845
|
+
}
|
|
846
|
+
queue_unlock(queue);
|
|
847
|
+
return self;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/* Internal hook for one-consumer RPC queues. Closing dormant readiness
|
|
851
|
+
* descriptors keeps the Queue reusable while avoiding descriptor retention. */
|
|
852
|
+
static VALUE
|
|
853
|
+
queue_release_wait_descriptors(VALUE self)
|
|
854
|
+
{
|
|
855
|
+
queue_t *queue = get_queue(self);
|
|
856
|
+
queue_lock(queue);
|
|
857
|
+
if (queue->pop_waiters == 0) readiness_close(&queue->can_pop);
|
|
858
|
+
if (queue->push_waiters == 0) readiness_close(&queue->can_push);
|
|
859
|
+
queue_unlock(queue);
|
|
860
|
+
return self;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
static VALUE
|
|
864
|
+
queue_pop_with_timeout(VALUE self, queue_t *queue, queue_timeout_t timeout)
|
|
865
|
+
{
|
|
866
|
+
for (;;) {
|
|
867
|
+
queue_lock(queue);
|
|
868
|
+
if (queue->closed) {
|
|
869
|
+
queue_unlock(queue);
|
|
870
|
+
raise_queue_closed();
|
|
871
|
+
}
|
|
872
|
+
if (queue->size > 0) {
|
|
873
|
+
VALUE value = queue->values[queue->head];
|
|
874
|
+
queue->values[queue->head] = Qnil;
|
|
875
|
+
if (queue->track_age) queue->enqueued_at[queue->head] = 0;
|
|
876
|
+
if (++queue->head == queue->storage_capacity) queue->head = 0;
|
|
877
|
+
queue->size--;
|
|
878
|
+
if (queue->track_age) queue->generation++;
|
|
879
|
+
if (queue->sealed && queue->size == 0) queue->closed = true;
|
|
880
|
+
queue_update_readiness(queue);
|
|
881
|
+
queue_unlock(queue);
|
|
882
|
+
return value;
|
|
883
|
+
}
|
|
884
|
+
if (!queue_wait(self, queue, &queue->can_pop, &queue->pop_waiters, &timeout)) {
|
|
885
|
+
return rb_block_given_p() ? rb_yield_values(0) : Qnil;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
static VALUE
|
|
891
|
+
queue_pop(int argc, VALUE *argv, VALUE self)
|
|
892
|
+
{
|
|
893
|
+
queue_t *queue = get_queue(self);
|
|
894
|
+
queue_timeout_t timeout = {.finite = false, .deadline = 0};
|
|
895
|
+
if (argc > 0) timeout = parse_timeout(extract_timeout(argc, argv));
|
|
896
|
+
return queue_pop_with_timeout(self, queue, timeout);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
static VALUE
|
|
900
|
+
queue_try_pop(VALUE self)
|
|
901
|
+
{
|
|
902
|
+
queue_timeout_t timeout = {.finite = true, .deadline = 0};
|
|
903
|
+
return queue_pop_with_timeout(self, get_queue(self), timeout);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
static VALUE
|
|
907
|
+
queue_push_with_timeout(VALUE self, queue_t *queue, VALUE value, queue_timeout_t timeout)
|
|
908
|
+
{
|
|
909
|
+
for (;;) {
|
|
910
|
+
queue_lock(queue);
|
|
911
|
+
if (queue->closed) {
|
|
912
|
+
queue_unlock(queue);
|
|
913
|
+
raise_queue_closed();
|
|
914
|
+
}
|
|
915
|
+
if (queue->sealed) {
|
|
916
|
+
queue_unlock(queue);
|
|
917
|
+
raise_queue_sealed();
|
|
918
|
+
}
|
|
919
|
+
if (!queue->bounded || queue->size < queue->limit) {
|
|
920
|
+
if (queue->size == queue->storage_capacity && !queue_grow(queue)) {
|
|
921
|
+
queue_unlock(queue);
|
|
922
|
+
rb_memerror();
|
|
923
|
+
}
|
|
924
|
+
queue->values[queue->tail] = value;
|
|
925
|
+
if (queue->track_age) queue->enqueued_at[queue->tail] = monotonic_now();
|
|
926
|
+
if (++queue->tail == queue->storage_capacity) queue->tail = 0;
|
|
927
|
+
queue->size++;
|
|
928
|
+
if (queue->track_age) queue->generation++;
|
|
929
|
+
queue_update_readiness(queue);
|
|
930
|
+
queue_unlock(queue);
|
|
931
|
+
return Qtrue;
|
|
932
|
+
}
|
|
933
|
+
if (!queue_wait(self, queue, &queue->can_push, &queue->push_waiters, &timeout)) return Qfalse;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
static VALUE
|
|
938
|
+
queue_push(int argc, VALUE *argv, VALUE self)
|
|
939
|
+
{
|
|
940
|
+
VALUE value;
|
|
941
|
+
VALUE keywords = Qnil;
|
|
942
|
+
VALUE timeout_value = Qnil;
|
|
943
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
944
|
+
VALUE keyword_values[1];
|
|
945
|
+
if (argc == 1 && !rb_keyword_given_p()) {
|
|
946
|
+
value = argv[0];
|
|
947
|
+
}
|
|
948
|
+
else if (argc == 2 && rb_keyword_given_p()) {
|
|
949
|
+
value = argv[0];
|
|
950
|
+
timeout_value = extract_timeout(1, argv + 1);
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
/* Preserve Ruby's positional-hash and keyword-only argument rules. */
|
|
954
|
+
rb_scan_args(argc, argv, "1:", &value, &keywords);
|
|
955
|
+
if (!NIL_P(keywords)) {
|
|
956
|
+
rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
957
|
+
if (keyword_values[0] != Qundef) timeout_value = keyword_values[0];
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
queue_t *queue = get_queue(self);
|
|
962
|
+
if (queue->shared) containers_check_shareable(value);
|
|
963
|
+
queue_timeout_t timeout = parse_timeout(timeout_value);
|
|
964
|
+
return queue_push_with_timeout(self, queue, value, timeout);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
static VALUE
|
|
968
|
+
queue_try_push(VALUE self, VALUE value)
|
|
969
|
+
{
|
|
970
|
+
queue_t *queue = get_queue(self);
|
|
971
|
+
if (queue->shared) containers_check_shareable(value);
|
|
972
|
+
queue_timeout_t timeout = {.finite = true, .deadline = 0};
|
|
973
|
+
return queue_push_with_timeout(self, queue, value, timeout);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
static VALUE
|
|
977
|
+
queue_wait_pop(int argc, VALUE *argv, VALUE self)
|
|
978
|
+
{
|
|
979
|
+
queue_t *queue = get_queue(self);
|
|
980
|
+
queue_timeout_t timeout = parse_timeout(extract_timeout(argc, argv));
|
|
981
|
+
for (;;) {
|
|
982
|
+
queue_lock(queue);
|
|
983
|
+
if (queue->closed) {
|
|
984
|
+
queue_unlock(queue);
|
|
985
|
+
raise_queue_closed();
|
|
986
|
+
}
|
|
987
|
+
bool ready = queue->size > 0;
|
|
988
|
+
if (ready) {
|
|
989
|
+
queue_unlock(queue);
|
|
990
|
+
return Qtrue;
|
|
991
|
+
}
|
|
992
|
+
if (!queue_wait(self, queue, &queue->can_pop, &queue->pop_waiters, &timeout)) return Qfalse;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
static VALUE
|
|
997
|
+
queue_wait_push(int argc, VALUE *argv, VALUE self)
|
|
998
|
+
{
|
|
999
|
+
queue_t *queue = get_queue(self);
|
|
1000
|
+
queue_timeout_t timeout = parse_timeout(extract_timeout(argc, argv));
|
|
1001
|
+
for (;;) {
|
|
1002
|
+
queue_lock(queue);
|
|
1003
|
+
if (queue->closed) {
|
|
1004
|
+
queue_unlock(queue);
|
|
1005
|
+
raise_queue_closed();
|
|
1006
|
+
}
|
|
1007
|
+
if (queue->sealed) {
|
|
1008
|
+
queue_unlock(queue);
|
|
1009
|
+
raise_queue_sealed();
|
|
1010
|
+
}
|
|
1011
|
+
bool ready = !queue->bounded || queue->size < queue->limit;
|
|
1012
|
+
if (ready) {
|
|
1013
|
+
queue_unlock(queue);
|
|
1014
|
+
return Qtrue;
|
|
1015
|
+
}
|
|
1016
|
+
if (!queue_wait(self, queue, &queue->can_push, &queue->push_waiters, &timeout)) return Qfalse;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
void
|
|
1022
|
+
containers_init_queue(VALUE namespace)
|
|
1023
|
+
{
|
|
1024
|
+
#ifdef _WIN32
|
|
1025
|
+
queue_main_ractor_key = rb_ractor_local_storage_value_newkey();
|
|
1026
|
+
rb_ractor_local_storage_value_set(queue_main_ractor_key, Qtrue);
|
|
1027
|
+
#endif
|
|
1028
|
+
id_farce = rb_intern("Farce");
|
|
1029
|
+
id_closed_error = rb_intern("ClosedQueueError");
|
|
1030
|
+
id_sealed_error = rb_intern("SealedQueueError");
|
|
1031
|
+
id_timeout = rb_intern("timeout");
|
|
1032
|
+
cQueue = rb_define_class_under(namespace, "Queue", rb_cObject);
|
|
1033
|
+
rb_define_alloc_func(cQueue, queue_allocate);
|
|
1034
|
+
VALUE unshared = rb_define_class_under(namespace, "UnsharedQueue", cQueue);
|
|
1035
|
+
rb_define_alloc_func(unshared, unshared_queue_allocate);
|
|
1036
|
+
rb_define_method(unshared, "fiber_wait", unshared_queue_fiber_wait, 0);
|
|
1037
|
+
rb_define_method(cQueue, "initialize", queue_initialize, -1);
|
|
1038
|
+
rb_define_method(cQueue, "capacity", queue_capacity, 0);
|
|
1039
|
+
rb_define_method(cQueue, "size", queue_size, 0);
|
|
1040
|
+
rb_define_method(cQueue, "num_waiting", queue_num_waiting, 0);
|
|
1041
|
+
rb_define_method(cQueue, "clear", queue_clear, 0);
|
|
1042
|
+
rb_define_method(cQueue, "pop", queue_pop, -1);
|
|
1043
|
+
rb_define_method(cQueue, "try_pop", queue_try_pop, 0);
|
|
1044
|
+
rb_define_method(cQueue, "push", queue_push, -1);
|
|
1045
|
+
rb_define_method(cQueue, "try_push", queue_try_push, 1);
|
|
1046
|
+
rb_define_method(cQueue, "wait_pop", queue_wait_pop, -1);
|
|
1047
|
+
rb_define_method(cQueue, "wait_push", queue_wait_push, -1);
|
|
1048
|
+
rb_define_method(cQueue, "close", queue_close, 0);
|
|
1049
|
+
rb_define_method(cQueue, "seal", queue_seal, 0);
|
|
1050
|
+
rb_define_method(cQueue, "closed?", queue_closed_p, 0);
|
|
1051
|
+
rb_define_method(cQueue, "sealed?", queue_sealed_p, 0);
|
|
1052
|
+
rb_define_method(cQueue, "age_tracking?", queue_age_tracking_p, 0);
|
|
1053
|
+
rb_define_method(cQueue, "generation", queue_generation, 0);
|
|
1054
|
+
rb_define_method(cQueue, "oldest_enqueued_at", queue_oldest_enqueued_at, 0);
|
|
1055
|
+
rb_define_method(cQueue, "oldest_age", queue_oldest_age, 0);
|
|
1056
|
+
rb_define_method(cQueue, "freeze", queue_freeze, 0);
|
|
1057
|
+
rb_define_method(cQueue, "frozen?", queue_frozen_p, 0);
|
|
1058
|
+
rb_define_private_method(cQueue, "__release_wait_descriptors__", queue_release_wait_descriptors, 0);
|
|
1059
|
+
}
|