farce 0.0.1.alpha2-x86_64-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +26 -0
- data/CONTRIBUTING.md +71 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1524 -0
- data/SECURITY.md +10 -0
- data/docs/benchmarks.md +185 -0
- data/docs/gems/dry-types.md +290 -0
- data/docs/gems/msgpack.md +70 -0
- data/docs/gems/ractor-shim.md +63 -0
- data/docs/modes.md +628 -0
- data/docs/scopes.md +649 -0
- data/docs/variants.md +346 -0
- data/ext/ext_helper.rb +20 -0
- data/ext/farce/README.md +21 -0
- data/ext/farce/atom.c +1023 -0
- data/ext/farce/bounded_map.c +1683 -0
- data/ext/farce/containers.h +77 -0
- data/ext/farce/counter.c +399 -0
- data/ext/farce/darwin.c +100 -0
- data/ext/farce/depend +12 -0
- data/ext/farce/dict.c +1523 -0
- data/ext/farce/dict.h +152 -0
- data/ext/farce/drivers.c +237 -0
- data/ext/farce/exchanger.c +299 -0
- data/ext/farce/extconf.rb +99 -0
- data/ext/farce/farce.c +359 -0
- data/ext/farce/flag.c +288 -0
- data/ext/farce/io.c +338 -0
- data/ext/farce/lock.c +510 -0
- data/ext/farce/map.c +2240 -0
- data/ext/farce/priority_queue.c +2056 -0
- data/ext/farce/queue.c +1059 -0
- data/ext/farce/reactor.c +820 -0
- data/ext/farce/reactor.h +103 -0
- data/ext/farce/shareable.h +31 -0
- data/ext/farce/signal.c +350 -0
- data/ext/farce/transaction.c +354 -0
- data/ext/farce/transaction.h +40 -0
- data/ext/farce/tree_map.c +1953 -0
- data/ext/farce/trie.c +2020 -0
- data/ext/farce/unshareable.c +155 -0
- data/ext/farce/unshared_io_pool.h +234 -0
- data/ext/farce/unshared_signal.c +263 -0
- data/ext/farce/unshared_wait.h +193 -0
- data/ext/farce/unsupported.c +6 -0
- data/ext/farce/vector.c +1195 -0
- data/ext/farce/weak_map.c +1714 -0
- data/ext/java/org/farce/BoundedMap.java +394 -0
- data/ext/java/org/farce/FiberScheduler.java +141 -0
- data/ext/java/org/farce/PriorityKey.java +45 -0
- data/ext/java/org/farce/PriorityQueue.java +373 -0
- data/ext/java/org/farce/QueueSignal.java +28 -0
- data/ext/rebind/README.md +14 -0
- data/ext/rebind/extconf.rb +10 -0
- data/ext/rebind/rebind.c +186 -0
- data/lib/farce/_yard/internal.rb +13 -0
- data/lib/farce/_yard/macros.rb +61 -0
- data/lib/farce/_yard/ractor.rb +46 -0
- data/lib/farce/abstract/atom.rb +186 -0
- data/lib/farce/abstract/bounded_map.rb +232 -0
- data/lib/farce/abstract/collection.rb +151 -0
- data/lib/farce/abstract/concurrent_map.rb +381 -0
- data/lib/farce/abstract/counter.rb +193 -0
- data/lib/farce/abstract/duplicable_map.rb +229 -0
- data/lib/farce/abstract/exchanger.rb +26 -0
- data/lib/farce/abstract/flag.rb +71 -0
- data/lib/farce/abstract/lazy.rb +115 -0
- data/lib/farce/abstract/lease.rb +104 -0
- data/lib/farce/abstract/lease_map.rb +261 -0
- data/lib/farce/abstract/lease_pool.rb +91 -0
- data/lib/farce/abstract/lfu_map.rb +20 -0
- data/lib/farce/abstract/lru_map.rb +25 -0
- data/lib/farce/abstract/map.rb +345 -0
- data/lib/farce/abstract/molecule.rb +245 -0
- data/lib/farce/abstract/port.rb +74 -0
- data/lib/farce/abstract/priority_queue.rb +117 -0
- data/lib/farce/abstract/queue.rb +269 -0
- data/lib/farce/abstract/scheduler.rb +111 -0
- data/lib/farce/abstract/set.rb +910 -0
- data/lib/farce/abstract/sorted_set.rb +172 -0
- data/lib/farce/abstract/timer_queue.rb +136 -0
- data/lib/farce/abstract/tree_map.rb +269 -0
- data/lib/farce/abstract/value.rb +68 -0
- data/lib/farce/abstract/vector.rb +1126 -0
- data/lib/farce/abstract/weak_atom.rb +32 -0
- data/lib/farce/abstract/weak_key_map.rb +12 -0
- data/lib/farce/abstract/weak_map.rb +13 -0
- data/lib/farce/abstract/weak_set.rb +12 -0
- data/lib/farce/abstract/weak_value_map.rb +12 -0
- data/lib/farce/abstract.rb +17 -0
- data/lib/farce/atom.rb +293 -0
- data/lib/farce/class_mirror.rb +87 -0
- data/lib/farce/clock.rb +121 -0
- data/lib/farce/config.rb +229 -0
- data/lib/farce/counter.rb +71 -0
- data/lib/farce/deduper.rb +122 -0
- data/lib/farce/engine/jruby/bounded_map.rb +314 -0
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
- data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
- data/lib/farce/engine/jruby/map.rb +505 -0
- data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
- data/lib/farce/engine/jruby/signal.rb +147 -0
- data/lib/farce/engine/jruby.rb +63 -0
- data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
- data/lib/farce/engine/jvm/counter.rb +102 -0
- data/lib/farce/engine/jvm/extension.rb +32 -0
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/flag.rb +79 -0
- data/lib/farce/engine/jvm/priority_queue.rb +217 -0
- data/lib/farce/engine/jvm/tree_map.rb +350 -0
- data/lib/farce/engine/jvm/types.rb +180 -0
- data/lib/farce/engine/jvm.rb +19 -0
- data/lib/farce/engine/ruby/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
- data/lib/farce/engine/ruby/3.4/port.rb +186 -0
- data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
- data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
- data/lib/farce/engine/ruby/3.4/rebind.so +0 -0
- data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/port.rb +19 -0
- data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
- data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
- data/lib/farce/engine/ruby/4.0/rebind.so +0 -0
- data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
- data/lib/farce/engine/ruby/4.1/port.rb +21 -0
- data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
- data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
- data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
- data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
- data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
- data/lib/farce/engine/ruby/shared/lease.rb +26 -0
- data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
- data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
- data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
- data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
- data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
- data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
- data/lib/farce/engine/ruby/shared/vault.rb +144 -0
- data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
- data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
- data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
- data/lib/farce/engine/ruby.rb +130 -0
- data/lib/farce/engine/shared/atom.rb +10 -0
- data/lib/farce/engine/shared/exchanger.rb +130 -0
- data/lib/farce/engine/shared/identity_key.rb +22 -0
- data/lib/farce/engine/shared/lease.rb +10 -0
- data/lib/farce/engine/shared/lease_pool.rb +10 -0
- data/lib/farce/engine/shared/main_scheduler.rb +20 -0
- data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
- data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
- data/lib/farce/engine/shared/port.rb +44 -0
- data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
- data/lib/farce/engine/shared/proxy_owner.rb +43 -0
- data/lib/farce/engine/shared/queue.rb +233 -0
- data/lib/farce/engine/shared/ractor_methods.rb +73 -0
- data/lib/farce/engine/shared/rebindable.rb +15 -0
- data/lib/farce/engine/shared/strict_atom.rb +73 -0
- data/lib/farce/engine/shared/strict_map.rb +117 -0
- data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
- data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
- data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
- data/lib/farce/engine/shared/trie.rb +317 -0
- data/lib/farce/engine/shared/trie_builder.rb +199 -0
- data/lib/farce/engine/shared/unshareable.rb +14 -0
- data/lib/farce/engine/shared/unshared_atom.rb +256 -0
- data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
- data/lib/farce/engine/shared/unshared_queue.rb +18 -0
- data/lib/farce/engine/shared/unshared_signal.rb +21 -0
- data/lib/farce/engine/shared/unshared_vector.rb +377 -0
- data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
- data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
- data/lib/farce/engine/shared/vault.rb +31 -0
- data/lib/farce/engine/shared/vector.rb +83 -0
- data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
- data/lib/farce/engine/shared/weak_atom.rb +20 -0
- data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
- data/lib/farce/engine/shared/weak_map/index.rb +249 -0
- data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
- data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
- data/lib/farce/engine/shared.rb +63 -0
- data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
- data/lib/farce/engine/truffleruby/lock.rb +121 -0
- data/lib/farce/engine/truffleruby/map.rb +656 -0
- data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
- data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
- data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
- data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
- data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
- data/lib/farce/engine/truffleruby/signal.rb +77 -0
- data/lib/farce/engine/truffleruby.rb +148 -0
- data/lib/farce/envelope.rb +335 -0
- data/lib/farce/error.rb +35 -0
- data/lib/farce/exchanger.rb +41 -0
- data/lib/farce/flag.rb +27 -0
- data/lib/farce/integrations/active_support/blank.rb +68 -0
- data/lib/farce/integrations/active_support/clock.rb +17 -0
- data/lib/farce/integrations/active_support/duplicable.rb +78 -0
- data/lib/farce/integrations/active_support/map.rb +175 -0
- data/lib/farce/integrations/active_support/set.rb +60 -0
- data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
- data/lib/farce/integrations/active_support/vector.rb +231 -0
- data/lib/farce/integrations/active_support.rb +9 -0
- data/lib/farce/integrations/activesupport.rb +5 -0
- data/lib/farce/integrations/bson.rb +108 -0
- data/lib/farce/integrations/cbor.rb +51 -0
- data/lib/farce/integrations/concurrent.rb +112 -0
- data/lib/farce/integrations/dry-types.rb +5 -0
- data/lib/farce/integrations/dry_types.rb +694 -0
- data/lib/farce/integrations/json.rb +7 -0
- data/lib/farce/integrations/msgpack.rb +112 -0
- data/lib/farce/integrations/oj.rb +69 -0
- data/lib/farce/integrations/psych.rb +191 -0
- data/lib/farce/integrations/ractor-sharing.rb +5 -0
- data/lib/farce/integrations/ractor-tmvar.rb +5 -0
- data/lib/farce/integrations/ractor_sharing.rb +172 -0
- data/lib/farce/integrations/ractor_tmvar.rb +64 -0
- data/lib/farce/integrations/shared/to_json.rb +42 -0
- data/lib/farce/integrations/sorted_set.rb +11 -0
- data/lib/farce/integrations/weakref.rb +38 -0
- data/lib/farce/integrations/yajl.rb +10 -0
- data/lib/farce/integrations.rb +156 -0
- data/lib/farce/internal/_frozen_config.rb +11 -0
- data/lib/farce/internal/autoloads.rb +47 -0
- data/lib/farce/internal/blocking_priority_queue.rb +122 -0
- data/lib/farce/internal/converter.rb +106 -0
- data/lib/farce/internal/copyable.rb +31 -0
- data/lib/farce/internal/delegation.rb +20 -0
- data/lib/farce/internal/external_transaction.rb +59 -0
- data/lib/farce/internal/fake_ractor.rb +179 -0
- data/lib/farce/internal/freeze.rb +118 -0
- data/lib/farce/internal/inspect.rb +157 -0
- data/lib/farce/internal/key_lock_map.rb +29 -0
- data/lib/farce/internal/key_normalizer.rb +289 -0
- data/lib/farce/internal/lease_initialization.rb +115 -0
- data/lib/farce/internal/lease_map.rb +441 -0
- data/lib/farce/internal/lease_pool_state.rb +228 -0
- data/lib/farce/internal/lease_state.rb +342 -0
- data/lib/farce/internal/lease_waiting.rb +13 -0
- data/lib/farce/internal/managed_queue.rb +26 -0
- data/lib/farce/internal/map_value_modes.rb +227 -0
- data/lib/farce/internal/marshal_support.rb +227 -0
- data/lib/farce/internal/mixin.rb +20 -0
- data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
- data/lib/farce/internal/noncopyable.rb +17 -0
- data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
- data/lib/farce/internal/pool_supervisor.rb +41 -0
- data/lib/farce/internal/pool_worker.rb +61 -0
- data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
- data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
- data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
- data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
- data/lib/farce/internal/portable_transaction.rb +164 -0
- data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
- data/lib/farce/internal/reservation_waiting.rb +24 -0
- data/lib/farce/internal/scheduled_task.rb +48 -0
- data/lib/farce/internal/scheduler_io.rb +171 -0
- data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
- data/lib/farce/internal/select_scheduler.rb +235 -0
- data/lib/farce/internal/storage.rb +162 -0
- data/lib/farce/internal/strict_lease.rb +30 -0
- data/lib/farce/internal/strict_lease_map.rb +20 -0
- data/lib/farce/internal/strict_lease_pool.rb +29 -0
- data/lib/farce/internal/thread_pool.rb +166 -0
- data/lib/farce/internal/transaction_conflict.rb +9 -0
- data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
- data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
- data/lib/farce/internal/undefined.rb +22 -0
- data/lib/farce/internal/unshared_lease.rb +29 -0
- data/lib/farce/internal/unshared_lease_pool.rb +34 -0
- data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
- data/lib/farce/internal/value_serialization.rb +13 -0
- data/lib/farce/internal/weak_map_value_modes.rb +58 -0
- data/lib/farce/internal/weak_mode_manager.rb +26 -0
- data/lib/farce/internal.rb +129 -0
- data/lib/farce/lazy.rb +100 -0
- data/lib/farce/lazy_ref.rb +40 -0
- data/lib/farce/lease.rb +28 -0
- data/lib/farce/lease_map.rb +34 -0
- data/lib/farce/lease_pool.rb +29 -0
- data/lib/farce/lfu_map.rb +53 -0
- data/lib/farce/local/atom.rb +23 -0
- data/lib/farce/local/counter.rb +88 -0
- data/lib/farce/local/flag.rb +65 -0
- data/lib/farce/local/lazy.rb +38 -0
- data/lib/farce/local/lazy_ref.rb +36 -0
- data/lib/farce/local/lease.rb +77 -0
- data/lib/farce/local/lease_map.rb +80 -0
- data/lib/farce/local/lease_pool.rb +38 -0
- data/lib/farce/local/lfu_map.rb +55 -0
- data/lib/farce/local/lru_map.rb +62 -0
- data/lib/farce/local/map.rb +47 -0
- data/lib/farce/local/molecule.rb +45 -0
- data/lib/farce/local/priority_queue.rb +53 -0
- data/lib/farce/local/queue.rb +32 -0
- data/lib/farce/local/scoped.rb +166 -0
- data/lib/farce/local/set.rb +18 -0
- data/lib/farce/local/sorted_set.rb +50 -0
- data/lib/farce/local/timer_queue.rb +41 -0
- data/lib/farce/local/tree_map.rb +47 -0
- data/lib/farce/local/vector.rb +30 -0
- data/lib/farce/local/weak_atom.rb +23 -0
- data/lib/farce/local/weak_key_map.rb +39 -0
- data/lib/farce/local/weak_map.rb +39 -0
- data/lib/farce/local/weak_set.rb +18 -0
- data/lib/farce/local/weak_value_map.rb +39 -0
- data/lib/farce/local.rb +35 -0
- data/lib/farce/lock.rb +54 -0
- data/lib/farce/lru_map.rb +63 -0
- data/lib/farce/map.rb +58 -0
- data/lib/farce/mode_manager.rb +142 -0
- data/lib/farce/molecule.rb +60 -0
- data/lib/farce/mutable.rb +171 -0
- data/lib/farce/pool.rb +332 -0
- data/lib/farce/port.rb +134 -0
- data/lib/farce/priority_queue.rb +70 -0
- data/lib/farce/proxy/register.rb +101 -0
- data/lib/farce/proxy/supervisor.rb +137 -0
- data/lib/farce/proxy/wrapper.rb +49 -0
- data/lib/farce/proxy.rb +291 -0
- data/lib/farce/queue.rb +60 -0
- data/lib/farce/ractor.rb +269 -0
- data/lib/farce/read_write_lock.rb +256 -0
- data/lib/farce/reference.rb +158 -0
- data/lib/farce/resolv/dns.rb +42 -0
- data/lib/farce/resolv.rb +85 -0
- data/lib/farce/scheduler.rb +536 -0
- data/lib/farce/set.rb +39 -0
- data/lib/farce/shareable.rb +150 -0
- data/lib/farce/signal.rb +97 -0
- data/lib/farce/sorted_set.rb +36 -0
- data/lib/farce/strict/atom.rb +34 -0
- data/lib/farce/strict/counter.rb +10 -0
- data/lib/farce/strict/exchanger.rb +25 -0
- data/lib/farce/strict/flag.rb +10 -0
- data/lib/farce/strict/lazy.rb +26 -0
- data/lib/farce/strict/lazy_ref.rb +26 -0
- data/lib/farce/strict/lease.rb +27 -0
- data/lib/farce/strict/lease_map.rb +33 -0
- data/lib/farce/strict/lease_pool.rb +28 -0
- data/lib/farce/strict/lfu_map.rb +27 -0
- data/lib/farce/strict/lru_map.rb +28 -0
- data/lib/farce/strict/map.rb +54 -0
- data/lib/farce/strict/molecule.rb +16 -0
- data/lib/farce/strict/port.rb +40 -0
- data/lib/farce/strict/priority_queue.rb +20 -0
- data/lib/farce/strict/queue.rb +17 -0
- data/lib/farce/strict/set.rb +14 -0
- data/lib/farce/strict/sorted_set.rb +19 -0
- data/lib/farce/strict/timer_queue.rb +13 -0
- data/lib/farce/strict/tree_map.rb +27 -0
- data/lib/farce/strict/vector.rb +24 -0
- data/lib/farce/strict/weak_atom.rb +31 -0
- data/lib/farce/strict/weak_key_map.rb +49 -0
- data/lib/farce/strict/weak_map.rb +49 -0
- data/lib/farce/strict/weak_set.rb +14 -0
- data/lib/farce/strict/weak_value_map.rb +48 -0
- data/lib/farce/strict.rb +28 -0
- data/lib/farce/system.rb +40 -0
- data/lib/farce/thread_scheduler.rb +70 -0
- data/lib/farce/timer_queue.rb +66 -0
- data/lib/farce/transaction/atom.rb +73 -0
- data/lib/farce/transaction/map.rb +25 -0
- data/lib/farce/transaction/map_operations.rb +143 -0
- data/lib/farce/transaction/molecule.rb +90 -0
- data/lib/farce/transaction/mutable.rb +61 -0
- data/lib/farce/transaction/set.rb +17 -0
- data/lib/farce/transaction/set_operations.rb +48 -0
- data/lib/farce/transaction/sorted_set.rb +18 -0
- data/lib/farce/transaction/tree_map.rb +78 -0
- data/lib/farce/transaction/vector.rb +139 -0
- data/lib/farce/transaction/wrapper.rb +202 -0
- data/lib/farce/transaction.rb +343 -0
- data/lib/farce/tree_map.rb +56 -0
- data/lib/farce/unsafe/lfu_map.rb +36 -0
- data/lib/farce/unsafe/lru_map.rb +36 -0
- data/lib/farce/unsafe/tree_map.rb +21 -0
- data/lib/farce/unsafe.rb +29 -0
- data/lib/farce/unshareable.rb +67 -0
- data/lib/farce/unshared/atom.rb +24 -0
- data/lib/farce/unshared/counter.rb +10 -0
- data/lib/farce/unshared/flag.rb +10 -0
- data/lib/farce/unshared/lazy.rb +38 -0
- data/lib/farce/unshared/lazy_ref.rb +26 -0
- data/lib/farce/unshared/lease.rb +27 -0
- data/lib/farce/unshared/lease_map.rb +26 -0
- data/lib/farce/unshared/lease_pool.rb +25 -0
- data/lib/farce/unshared/lfu_map.rb +20 -0
- data/lib/farce/unshared/lru_map.rb +20 -0
- data/lib/farce/unshared/map.rb +49 -0
- data/lib/farce/unshared/molecule.rb +16 -0
- data/lib/farce/unshared/priority_queue.rb +16 -0
- data/lib/farce/unshared/queue.rb +27 -0
- data/lib/farce/unshared/set.rb +14 -0
- data/lib/farce/unshared/sorted_set.rb +28 -0
- data/lib/farce/unshared/timer_queue.rb +16 -0
- data/lib/farce/unshared/tree_map.rb +21 -0
- data/lib/farce/unshared/vector.rb +19 -0
- data/lib/farce/unshared/weak_atom.rb +31 -0
- data/lib/farce/unshared/weak_key_map.rb +44 -0
- data/lib/farce/unshared/weak_map.rb +44 -0
- data/lib/farce/unshared/weak_set.rb +14 -0
- data/lib/farce/unshared/weak_value_map.rb +43 -0
- data/lib/farce/unshared.rb +28 -0
- data/lib/farce/vector.rb +228 -0
- data/lib/farce/version.rb +8 -0
- data/lib/farce/walker/definitions.rb +186 -0
- data/lib/farce/walker/modification.rb +355 -0
- data/lib/farce/walker.rb +319 -0
- data/lib/farce/weak_atom.rb +121 -0
- data/lib/farce/weak_key_map.rb +31 -0
- data/lib/farce/weak_map.rb +33 -0
- data/lib/farce/weak_ref.rb +67 -0
- data/lib/farce/weak_set.rb +82 -0
- data/lib/farce/weak_value.rb +195 -0
- data/lib/farce/weak_value_map.rb +32 -0
- data/lib/farce.rb +519 -0
- metadata +468 -0
|
@@ -0,0 +1,2056 @@
|
|
|
1
|
+
#include "containers.h"
|
|
2
|
+
#include "unshared_wait.h"
|
|
3
|
+
#include <ruby/atomic.h>
|
|
4
|
+
#include <ruby/fiber/scheduler.h>
|
|
5
|
+
#include <ruby/io.h>
|
|
6
|
+
#include <ruby/ractor.h>
|
|
7
|
+
#include <ruby/thread.h>
|
|
8
|
+
|
|
9
|
+
#include <errno.h>
|
|
10
|
+
#include <fcntl.h>
|
|
11
|
+
#include <math.h>
|
|
12
|
+
#include <pthread.h>
|
|
13
|
+
#include <stdbool.h>
|
|
14
|
+
#include <stdint.h>
|
|
15
|
+
#include <stdlib.h>
|
|
16
|
+
#include <string.h>
|
|
17
|
+
#include <time.h>
|
|
18
|
+
#include <unistd.h>
|
|
19
|
+
|
|
20
|
+
#include "dict.h"
|
|
21
|
+
|
|
22
|
+
#define PRIORITY_QUEUE_IDENTITY_INDEX_THRESHOLD 32
|
|
23
|
+
|
|
24
|
+
enum {
|
|
25
|
+
PRIORITY_QUEUE_UNINITIALIZED = 0,
|
|
26
|
+
PRIORITY_QUEUE_PUBLISHING = 1,
|
|
27
|
+
PRIORITY_QUEUE_PUBLISHED = 2,
|
|
28
|
+
PRIORITY_QUEUE_INITIALIZED = 3,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
static inline bool
|
|
32
|
+
priority_queue_publication_busy(rb_atomic_t state)
|
|
33
|
+
{
|
|
34
|
+
return state == PRIORITY_QUEUE_PUBLISHING ||
|
|
35
|
+
state == PRIORITY_QUEUE_PUBLISHED;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
typedef struct priority_queue_value priority_queue_value_t;
|
|
39
|
+
typedef struct priority_queue_bucket priority_queue_bucket_t;
|
|
40
|
+
typedef struct priority_queue_identity_index priority_queue_identity_index_t;
|
|
41
|
+
|
|
42
|
+
/* Build the snapshot through primitive String C APIs: retain the nominal
|
|
43
|
+
* subclass, encoding, and instance variables, but neither copy singleton
|
|
44
|
+
* methods nor invoke Ruby callbacks. */
|
|
45
|
+
static VALUE
|
|
46
|
+
priority_queue_snapshot_string(VALUE string)
|
|
47
|
+
{
|
|
48
|
+
VALUE copy = rb_obj_alloc(rb_obj_class(string));
|
|
49
|
+
VALUE instance_variables;
|
|
50
|
+
long index;
|
|
51
|
+
|
|
52
|
+
rb_str_replace(copy, string);
|
|
53
|
+
instance_variables = rb_obj_instance_variables(string);
|
|
54
|
+
for (index = 0; index < RARRAY_LEN(instance_variables); index++) {
|
|
55
|
+
ID id = SYM2ID(RARRAY_AREF(instance_variables, index));
|
|
56
|
+
rb_ivar_set(copy, id, rb_ivar_get(string, id));
|
|
57
|
+
}
|
|
58
|
+
rb_obj_freeze(copy);
|
|
59
|
+
RB_GC_GUARD(instance_variables);
|
|
60
|
+
return copy;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
struct priority_queue_value {
|
|
64
|
+
VALUE value;
|
|
65
|
+
priority_queue_value_t *previous;
|
|
66
|
+
priority_queue_value_t *next;
|
|
67
|
+
priority_queue_value_t *identity_next;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
typedef struct priority_queue_tracked_value {
|
|
71
|
+
priority_queue_value_t value;
|
|
72
|
+
double enqueued_at;
|
|
73
|
+
struct priority_queue_tracked_value *age_previous;
|
|
74
|
+
struct priority_queue_tracked_value *age_next;
|
|
75
|
+
} priority_queue_tracked_value_t;
|
|
76
|
+
|
|
77
|
+
typedef struct {
|
|
78
|
+
VALUE identity;
|
|
79
|
+
priority_queue_value_t *head;
|
|
80
|
+
priority_queue_value_t *tail;
|
|
81
|
+
bool occupied;
|
|
82
|
+
} priority_queue_identity_slot_t;
|
|
83
|
+
|
|
84
|
+
struct priority_queue_identity_index {
|
|
85
|
+
priority_queue_identity_slot_t *slots;
|
|
86
|
+
size_t capacity;
|
|
87
|
+
size_t count;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/* dnode_t must remain the first member: Kazlib passes its address to the
|
|
91
|
+
* configured node-free callback, where it is cast back to this structure. */
|
|
92
|
+
struct priority_queue_bucket {
|
|
93
|
+
dnode_t node;
|
|
94
|
+
VALUE priority;
|
|
95
|
+
double float_priority;
|
|
96
|
+
priority_queue_value_t *head;
|
|
97
|
+
priority_queue_value_t *tail;
|
|
98
|
+
priority_queue_identity_index_t *identity_index;
|
|
99
|
+
size_t size;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
static size_t
|
|
103
|
+
priority_queue_identity_hash(VALUE identity)
|
|
104
|
+
{
|
|
105
|
+
uintptr_t bits = (uintptr_t)identity;
|
|
106
|
+
#if UINTPTR_MAX > UINT32_MAX
|
|
107
|
+
bits ^= bits >> 33;
|
|
108
|
+
bits *= UINT64_C(0xff51afd7ed558ccd);
|
|
109
|
+
bits ^= bits >> 33;
|
|
110
|
+
bits *= UINT64_C(0xc4ceb9fe1a85ec53);
|
|
111
|
+
bits ^= bits >> 33;
|
|
112
|
+
#else
|
|
113
|
+
bits ^= bits >> 16;
|
|
114
|
+
bits *= UINT32_C(0x7feb352d);
|
|
115
|
+
bits ^= bits >> 15;
|
|
116
|
+
bits *= UINT32_C(0x846ca68b);
|
|
117
|
+
bits ^= bits >> 16;
|
|
118
|
+
#endif
|
|
119
|
+
return (size_t)bits;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static priority_queue_identity_index_t *
|
|
123
|
+
priority_queue_identity_index_allocate(size_t minimum_capacity)
|
|
124
|
+
{
|
|
125
|
+
priority_queue_identity_index_t *index;
|
|
126
|
+
size_t capacity = 64;
|
|
127
|
+
|
|
128
|
+
while (capacity < minimum_capacity) {
|
|
129
|
+
if (capacity > SIZE_MAX / 2) rb_memerror();
|
|
130
|
+
capacity *= 2;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
index = malloc(sizeof(priority_queue_identity_index_t));
|
|
134
|
+
if (!index) rb_memerror();
|
|
135
|
+
index->slots = calloc(capacity, sizeof(priority_queue_identity_slot_t));
|
|
136
|
+
if (!index->slots) {
|
|
137
|
+
free(index);
|
|
138
|
+
rb_memerror();
|
|
139
|
+
}
|
|
140
|
+
index->capacity = capacity;
|
|
141
|
+
index->count = 0;
|
|
142
|
+
return index;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static void
|
|
146
|
+
priority_queue_identity_index_free(priority_queue_identity_index_t *index)
|
|
147
|
+
{
|
|
148
|
+
if (!index) return;
|
|
149
|
+
free(index->slots);
|
|
150
|
+
free(index);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static priority_queue_identity_slot_t *
|
|
154
|
+
priority_queue_identity_index_lookup(priority_queue_identity_index_t *index, VALUE identity)
|
|
155
|
+
{
|
|
156
|
+
size_t position;
|
|
157
|
+
size_t mask;
|
|
158
|
+
|
|
159
|
+
if (!index) return NULL;
|
|
160
|
+
mask = index->capacity - 1;
|
|
161
|
+
position = priority_queue_identity_hash(identity) & mask;
|
|
162
|
+
while (index->slots[position].occupied) {
|
|
163
|
+
if (index->slots[position].identity == identity) return &index->slots[position];
|
|
164
|
+
position = (position + 1) & mask;
|
|
165
|
+
}
|
|
166
|
+
return NULL;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static priority_queue_identity_slot_t *
|
|
170
|
+
priority_queue_identity_index_insert_slot(
|
|
171
|
+
priority_queue_identity_index_t *index,
|
|
172
|
+
VALUE identity,
|
|
173
|
+
priority_queue_value_t *head,
|
|
174
|
+
priority_queue_value_t *tail
|
|
175
|
+
)
|
|
176
|
+
{
|
|
177
|
+
size_t mask = index->capacity - 1;
|
|
178
|
+
size_t position = priority_queue_identity_hash(identity) & mask;
|
|
179
|
+
priority_queue_identity_slot_t *slot;
|
|
180
|
+
|
|
181
|
+
while (index->slots[position].occupied) position = (position + 1) & mask;
|
|
182
|
+
slot = &index->slots[position];
|
|
183
|
+
slot->identity = identity;
|
|
184
|
+
slot->head = head;
|
|
185
|
+
slot->tail = tail;
|
|
186
|
+
slot->occupied = true;
|
|
187
|
+
index->count++;
|
|
188
|
+
return slot;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static void
|
|
192
|
+
priority_queue_identity_index_resize(priority_queue_identity_index_t *index)
|
|
193
|
+
{
|
|
194
|
+
priority_queue_identity_slot_t *old_slots = index->slots;
|
|
195
|
+
size_t old_capacity = index->capacity;
|
|
196
|
+
size_t new_capacity;
|
|
197
|
+
size_t position;
|
|
198
|
+
|
|
199
|
+
if (old_capacity > SIZE_MAX / 2) rb_memerror();
|
|
200
|
+
new_capacity = old_capacity * 2;
|
|
201
|
+
index->slots = calloc(new_capacity, sizeof(priority_queue_identity_slot_t));
|
|
202
|
+
if (!index->slots) {
|
|
203
|
+
index->slots = old_slots;
|
|
204
|
+
rb_memerror();
|
|
205
|
+
}
|
|
206
|
+
index->capacity = new_capacity;
|
|
207
|
+
index->count = 0;
|
|
208
|
+
for (position = 0; position < old_capacity; position++) {
|
|
209
|
+
priority_queue_identity_slot_t *slot = &old_slots[position];
|
|
210
|
+
if (!slot->occupied) continue;
|
|
211
|
+
priority_queue_identity_index_insert_slot(
|
|
212
|
+
index,
|
|
213
|
+
slot->identity,
|
|
214
|
+
slot->head,
|
|
215
|
+
slot->tail
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
free(old_slots);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static void
|
|
222
|
+
priority_queue_identity_index_prepare_insert(
|
|
223
|
+
priority_queue_identity_index_t *index,
|
|
224
|
+
VALUE identity
|
|
225
|
+
)
|
|
226
|
+
{
|
|
227
|
+
if (!index || priority_queue_identity_index_lookup(index, identity)) return;
|
|
228
|
+
if (index->count + 1 > index->capacity / 2) {
|
|
229
|
+
priority_queue_identity_index_resize(index);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static void
|
|
234
|
+
priority_queue_identity_index_add(
|
|
235
|
+
priority_queue_identity_index_t *index,
|
|
236
|
+
priority_queue_value_t *entry
|
|
237
|
+
)
|
|
238
|
+
{
|
|
239
|
+
priority_queue_identity_slot_t *slot;
|
|
240
|
+
|
|
241
|
+
if (!index) return;
|
|
242
|
+
entry->identity_next = NULL;
|
|
243
|
+
slot = priority_queue_identity_index_lookup(index, entry->value);
|
|
244
|
+
if (!slot) {
|
|
245
|
+
priority_queue_identity_index_insert_slot(index, entry->value, entry, entry);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
slot->tail->identity_next = entry;
|
|
249
|
+
slot->tail = entry;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static void
|
|
253
|
+
priority_queue_identity_index_delete_slot(
|
|
254
|
+
priority_queue_identity_index_t *index,
|
|
255
|
+
priority_queue_identity_slot_t *deleted
|
|
256
|
+
)
|
|
257
|
+
{
|
|
258
|
+
size_t mask = index->capacity - 1;
|
|
259
|
+
size_t position = (size_t)(deleted - index->slots);
|
|
260
|
+
|
|
261
|
+
memset(deleted, 0, sizeof(*deleted));
|
|
262
|
+
index->count--;
|
|
263
|
+
position = (position + 1) & mask;
|
|
264
|
+
while (index->slots[position].occupied) {
|
|
265
|
+
priority_queue_identity_slot_t moved = index->slots[position];
|
|
266
|
+
memset(&index->slots[position], 0, sizeof(index->slots[position]));
|
|
267
|
+
index->count--;
|
|
268
|
+
priority_queue_identity_index_insert_slot(
|
|
269
|
+
index,
|
|
270
|
+
moved.identity,
|
|
271
|
+
moved.head,
|
|
272
|
+
moved.tail
|
|
273
|
+
);
|
|
274
|
+
position = (position + 1) & mask;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static void
|
|
279
|
+
priority_queue_identity_index_remove(
|
|
280
|
+
priority_queue_identity_index_t *index,
|
|
281
|
+
priority_queue_value_t *entry
|
|
282
|
+
)
|
|
283
|
+
{
|
|
284
|
+
priority_queue_identity_slot_t *slot;
|
|
285
|
+
priority_queue_value_t *previous = NULL;
|
|
286
|
+
|
|
287
|
+
if (!index) return;
|
|
288
|
+
slot = priority_queue_identity_index_lookup(index, entry->value);
|
|
289
|
+
if (!slot) rb_bug("priority queue identity index lost an entry");
|
|
290
|
+
if (slot->head != entry) {
|
|
291
|
+
for (previous = slot->head;
|
|
292
|
+
previous && previous->identity_next != entry;
|
|
293
|
+
previous = previous->identity_next) {
|
|
294
|
+
}
|
|
295
|
+
if (!previous) rb_bug("priority queue identity chain lost an entry");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (previous) previous->identity_next = entry->identity_next;
|
|
299
|
+
else slot->head = entry->identity_next;
|
|
300
|
+
if (slot->tail == entry) slot->tail = previous;
|
|
301
|
+
entry->identity_next = NULL;
|
|
302
|
+
if (!slot->head) priority_queue_identity_index_delete_slot(index, slot);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
static void
|
|
306
|
+
priority_queue_identity_index_build(priority_queue_bucket_t *bucket)
|
|
307
|
+
{
|
|
308
|
+
priority_queue_identity_index_t *index;
|
|
309
|
+
priority_queue_value_t *entry;
|
|
310
|
+
size_t minimum_capacity;
|
|
311
|
+
|
|
312
|
+
if (bucket->identity_index) return;
|
|
313
|
+
if (bucket->size > SIZE_MAX / 2) rb_memerror();
|
|
314
|
+
minimum_capacity = bucket->size * 2;
|
|
315
|
+
index = priority_queue_identity_index_allocate(minimum_capacity);
|
|
316
|
+
for (entry = bucket->head; entry; entry = entry->next) {
|
|
317
|
+
priority_queue_identity_index_add(index, entry);
|
|
318
|
+
}
|
|
319
|
+
bucket->identity_index = index;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static void
|
|
323
|
+
priority_queue_identity_index_rebuild(priority_queue_bucket_t *bucket)
|
|
324
|
+
{
|
|
325
|
+
priority_queue_identity_index_t *index = bucket->identity_index;
|
|
326
|
+
priority_queue_value_t *entry;
|
|
327
|
+
|
|
328
|
+
if (!index) return;
|
|
329
|
+
memset(index->slots, 0, index->capacity * sizeof(priority_queue_identity_slot_t));
|
|
330
|
+
index->count = 0;
|
|
331
|
+
for (entry = bucket->head; entry; entry = entry->next) {
|
|
332
|
+
entry->identity_next = NULL;
|
|
333
|
+
priority_queue_identity_index_add(index, entry);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
static void
|
|
338
|
+
priority_queue_bucket_unlink(
|
|
339
|
+
priority_queue_bucket_t *bucket,
|
|
340
|
+
priority_queue_value_t *entry
|
|
341
|
+
)
|
|
342
|
+
{
|
|
343
|
+
if (entry->previous) entry->previous->next = entry->next;
|
|
344
|
+
else bucket->head = entry->next;
|
|
345
|
+
if (entry->next) entry->next->previous = entry->previous;
|
|
346
|
+
else bucket->tail = entry->previous;
|
|
347
|
+
bucket->size--;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
typedef struct priority_queue_lock_waiter {
|
|
351
|
+
int read_fd;
|
|
352
|
+
int write_fd;
|
|
353
|
+
bool notified;
|
|
354
|
+
struct priority_queue_lock_waiter *next;
|
|
355
|
+
} priority_queue_lock_waiter_t;
|
|
356
|
+
|
|
357
|
+
typedef struct {
|
|
358
|
+
dict_t tree;
|
|
359
|
+
pthread_mutex_t lock;
|
|
360
|
+
VALUE owner_fiber;
|
|
361
|
+
VALUE owner_ruby_thread;
|
|
362
|
+
priority_queue_lock_waiter_t *waiters;
|
|
363
|
+
priority_queue_lock_waiter_t *last_waiter;
|
|
364
|
+
farce_unshared_wait_list_t unshared_lock_waiters;
|
|
365
|
+
VALUE unshared_self;
|
|
366
|
+
/* Deliberately omitted from dmark: publication must remember its owner
|
|
367
|
+
* without making that Fiber/Thread part of the shareable object graph. */
|
|
368
|
+
VALUE publication_owner_fiber;
|
|
369
|
+
VALUE publication_owner_ruby_thread;
|
|
370
|
+
VALUE signal;
|
|
371
|
+
containers_unshared_signal_t *unshared_signal;
|
|
372
|
+
size_t size;
|
|
373
|
+
size_t capacity;
|
|
374
|
+
priority_queue_tracked_value_t *age_head;
|
|
375
|
+
priority_queue_tracked_value_t *age_tail;
|
|
376
|
+
uint64_t generation;
|
|
377
|
+
bool bounded;
|
|
378
|
+
bool sealed;
|
|
379
|
+
bool closed;
|
|
380
|
+
bool track_age;
|
|
381
|
+
/* Only homogeneous built-in numeric keys can reuse an insertion search
|
|
382
|
+
* across notification. Mixed/custom comparisons retain the two-pass path. */
|
|
383
|
+
bool numeric_only;
|
|
384
|
+
rb_atomic_t publication_state;
|
|
385
|
+
bool mutex_initialized;
|
|
386
|
+
bool shared;
|
|
387
|
+
bool unshared_fiber_io;
|
|
388
|
+
} priority_queue_t;
|
|
389
|
+
|
|
390
|
+
static VALUE cPriorityQueue;
|
|
391
|
+
static VALUE eIsolationError;
|
|
392
|
+
static ID id_farce;
|
|
393
|
+
static ID id_closed_error;
|
|
394
|
+
static ID id_sealed_error;
|
|
395
|
+
static ID id_compare;
|
|
396
|
+
static ID id_broadcast;
|
|
397
|
+
static ID id_case_equal;
|
|
398
|
+
|
|
399
|
+
static double
|
|
400
|
+
priority_queue_monotonic_now(void)
|
|
401
|
+
{
|
|
402
|
+
struct timespec now;
|
|
403
|
+
#ifdef CLOCK_MONOTONIC
|
|
404
|
+
if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
|
|
405
|
+
return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
|
|
406
|
+
}
|
|
407
|
+
#endif
|
|
408
|
+
struct timeval fallback;
|
|
409
|
+
gettimeofday(&fallback, NULL);
|
|
410
|
+
return (double)fallback.tv_sec + (double)fallback.tv_usec / 1000000.0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
static void
|
|
414
|
+
priority_queue_track_push(priority_queue_t *queue, priority_queue_value_t *value)
|
|
415
|
+
{
|
|
416
|
+
if (!queue->track_age) return;
|
|
417
|
+
priority_queue_tracked_value_t *tracked = (priority_queue_tracked_value_t *)value;
|
|
418
|
+
tracked->enqueued_at = priority_queue_monotonic_now();
|
|
419
|
+
tracked->age_previous = queue->age_tail;
|
|
420
|
+
tracked->age_next = NULL;
|
|
421
|
+
if (queue->age_tail) queue->age_tail->age_next = tracked;
|
|
422
|
+
else queue->age_head = tracked;
|
|
423
|
+
queue->age_tail = tracked;
|
|
424
|
+
queue->generation++;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
static void
|
|
428
|
+
priority_queue_track_remove(priority_queue_t *queue, priority_queue_value_t *value)
|
|
429
|
+
{
|
|
430
|
+
if (!queue->track_age) return;
|
|
431
|
+
priority_queue_tracked_value_t *tracked = (priority_queue_tracked_value_t *)value;
|
|
432
|
+
if (tracked->age_previous) tracked->age_previous->age_next = tracked->age_next;
|
|
433
|
+
else queue->age_head = tracked->age_next;
|
|
434
|
+
if (tracked->age_next) tracked->age_next->age_previous = tracked->age_previous;
|
|
435
|
+
else queue->age_tail = tracked->age_previous;
|
|
436
|
+
queue->generation++;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
static void priority_queue_wait_for_publication(priority_queue_t *queue);
|
|
440
|
+
|
|
441
|
+
typedef struct {
|
|
442
|
+
VALUE receiver;
|
|
443
|
+
VALUE argument;
|
|
444
|
+
bool equality;
|
|
445
|
+
int comparison;
|
|
446
|
+
} priority_queue_callback_t;
|
|
447
|
+
|
|
448
|
+
static VALUE
|
|
449
|
+
priority_queue_callback_body(VALUE opaque)
|
|
450
|
+
{
|
|
451
|
+
priority_queue_callback_t *callback = (priority_queue_callback_t *)opaque;
|
|
452
|
+
if (callback->equality) return rb_equal(callback->receiver, callback->argument);
|
|
453
|
+
|
|
454
|
+
VALUE result = rb_funcall(
|
|
455
|
+
callback->receiver,
|
|
456
|
+
id_compare,
|
|
457
|
+
1,
|
|
458
|
+
callback->argument
|
|
459
|
+
);
|
|
460
|
+
/* rb_cmpint can itself call Ruby `>`/`<` methods on a non-Integer result.
|
|
461
|
+
* Keep the callback guard active through that conversion as well. */
|
|
462
|
+
callback->comparison = rb_cmpint(
|
|
463
|
+
result,
|
|
464
|
+
callback->receiver,
|
|
465
|
+
callback->argument
|
|
466
|
+
);
|
|
467
|
+
return Qnil;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
static VALUE
|
|
471
|
+
priority_queue_call_ruby_callback(
|
|
472
|
+
VALUE receiver,
|
|
473
|
+
VALUE argument,
|
|
474
|
+
bool equality,
|
|
475
|
+
int *comparison
|
|
476
|
+
)
|
|
477
|
+
{
|
|
478
|
+
priority_queue_callback_t callback = {
|
|
479
|
+
.receiver = receiver,
|
|
480
|
+
.argument = argument,
|
|
481
|
+
.equality = equality,
|
|
482
|
+
.comparison = 0,
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
VALUE result = priority_queue_callback_body((VALUE)&callback);
|
|
486
|
+
if (comparison) *comparison = callback.comparison;
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
static int
|
|
491
|
+
priority_queue_compare(const void *left_pointer, const void *right_pointer, void *context)
|
|
492
|
+
{
|
|
493
|
+
VALUE left = (VALUE)left_pointer;
|
|
494
|
+
VALUE right = (VALUE)right_pointer;
|
|
495
|
+
int comparison;
|
|
496
|
+
(void)context;
|
|
497
|
+
|
|
498
|
+
if (FIXNUM_P(left) && FIXNUM_P(right)) {
|
|
499
|
+
intptr_t left_value = (intptr_t)left;
|
|
500
|
+
intptr_t right_value = (intptr_t)right;
|
|
501
|
+
if (left_value < right_value) return -1;
|
|
502
|
+
if (left_value > right_value) return 1;
|
|
503
|
+
return 0;
|
|
504
|
+
}
|
|
505
|
+
if (CLASS_OF(left) == rb_cString && CLASS_OF(right) == rb_cString) {
|
|
506
|
+
return rb_str_cmp(left, right);
|
|
507
|
+
}
|
|
508
|
+
if (CLASS_OF(left) == rb_cFloat && CLASS_OF(right) == rb_cFloat) {
|
|
509
|
+
double left_value = RFLOAT_VALUE(left);
|
|
510
|
+
double right_value = RFLOAT_VALUE(right);
|
|
511
|
+
|
|
512
|
+
/* Float#<=> returns nil for NaN, which rb_cmpint turns into the
|
|
513
|
+
* normal comparison ArgumentError. Keep that uncommon case on the
|
|
514
|
+
* Ruby callback path rather than treating NaN as an equal priority. */
|
|
515
|
+
if (!isnan(left_value) && !isnan(right_value)) {
|
|
516
|
+
if (left_value < right_value) return -1;
|
|
517
|
+
if (left_value > right_value) return 1;
|
|
518
|
+
return 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
(void)priority_queue_call_ruby_callback(
|
|
523
|
+
left,
|
|
524
|
+
right,
|
|
525
|
+
false,
|
|
526
|
+
&comparison
|
|
527
|
+
);
|
|
528
|
+
return comparison;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
static dnode_t *
|
|
532
|
+
priority_queue_allocate_tree_node(void *context)
|
|
533
|
+
{
|
|
534
|
+
priority_queue_bucket_t *bucket = calloc(1, sizeof(priority_queue_bucket_t));
|
|
535
|
+
(void)context;
|
|
536
|
+
return bucket ? &bucket->node : NULL;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
static void
|
|
540
|
+
priority_queue_free_tree_node(dnode_t *node, void *context)
|
|
541
|
+
{
|
|
542
|
+
priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
|
|
543
|
+
priority_queue_value_t *entry = bucket->head;
|
|
544
|
+
(void)context;
|
|
545
|
+
|
|
546
|
+
priority_queue_identity_index_free(bucket->identity_index);
|
|
547
|
+
while (entry) {
|
|
548
|
+
priority_queue_value_t *next = entry->next;
|
|
549
|
+
free(entry);
|
|
550
|
+
entry = next;
|
|
551
|
+
}
|
|
552
|
+
free(bucket);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
static void
|
|
556
|
+
priority_queue_mark(void *pointer)
|
|
557
|
+
{
|
|
558
|
+
priority_queue_t *queue = pointer;
|
|
559
|
+
dnode_t *node;
|
|
560
|
+
|
|
561
|
+
if (!queue) return;
|
|
562
|
+
rb_gc_mark_movable(queue->owner_fiber);
|
|
563
|
+
rb_gc_mark_movable(queue->owner_ruby_thread);
|
|
564
|
+
rb_gc_mark_movable(queue->signal);
|
|
565
|
+
rb_gc_mark_movable(queue->unshared_self);
|
|
566
|
+
farce_unshared_wait_mark(&queue->unshared_lock_waiters);
|
|
567
|
+
for (node = dict_first(&queue->tree); node; node = dict_next(&queue->tree, node)) {
|
|
568
|
+
priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
|
|
569
|
+
priority_queue_value_t *entry;
|
|
570
|
+
rb_gc_mark_movable(bucket->priority);
|
|
571
|
+
for (entry = bucket->head; entry; entry = entry->next) {
|
|
572
|
+
rb_gc_mark_movable(entry->value);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
static void
|
|
578
|
+
priority_queue_compact(void *pointer)
|
|
579
|
+
{
|
|
580
|
+
priority_queue_t *queue = pointer;
|
|
581
|
+
dnode_t *node;
|
|
582
|
+
|
|
583
|
+
if (!queue) return;
|
|
584
|
+
queue->owner_fiber = rb_gc_location(queue->owner_fiber);
|
|
585
|
+
queue->owner_ruby_thread = rb_gc_location(queue->owner_ruby_thread);
|
|
586
|
+
/* The VM already roots the running publisher. dcompact only refreshes the
|
|
587
|
+
* unmarked identity handles if that Fiber or Thread moved. */
|
|
588
|
+
queue->publication_owner_fiber =
|
|
589
|
+
rb_gc_location(queue->publication_owner_fiber);
|
|
590
|
+
queue->publication_owner_ruby_thread =
|
|
591
|
+
rb_gc_location(queue->publication_owner_ruby_thread);
|
|
592
|
+
queue->signal = rb_gc_location(queue->signal);
|
|
593
|
+
queue->unshared_self = rb_gc_location(queue->unshared_self);
|
|
594
|
+
for (node = dict_first(&queue->tree); node; node = dict_next(&queue->tree, node)) {
|
|
595
|
+
priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
|
|
596
|
+
priority_queue_value_t *entry;
|
|
597
|
+
bucket->priority = rb_gc_location(bucket->priority);
|
|
598
|
+
node->dict_key = (const void *)bucket->priority;
|
|
599
|
+
for (entry = bucket->head; entry; entry = entry->next) {
|
|
600
|
+
entry->value = rb_gc_location(entry->value);
|
|
601
|
+
}
|
|
602
|
+
priority_queue_identity_index_rebuild(bucket);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
static void
|
|
607
|
+
priority_queue_free(void *pointer)
|
|
608
|
+
{
|
|
609
|
+
priority_queue_t *queue = pointer;
|
|
610
|
+
if (!queue) return;
|
|
611
|
+
dict_free_nodes(&queue->tree);
|
|
612
|
+
if (queue->mutex_initialized) pthread_mutex_destroy(&queue->lock);
|
|
613
|
+
ruby_xfree(queue);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
static size_t
|
|
617
|
+
priority_queue_memsize(const void *pointer)
|
|
618
|
+
{
|
|
619
|
+
const priority_queue_t *queue = pointer;
|
|
620
|
+
dnode_t *node;
|
|
621
|
+
size_t size;
|
|
622
|
+
|
|
623
|
+
if (!queue) return 0;
|
|
624
|
+
size = sizeof(priority_queue_t)
|
|
625
|
+
+ (size_t)dict_count((dict_t *)&queue->tree) * sizeof(priority_queue_bucket_t)
|
|
626
|
+
+ queue->size * (queue->track_age ?
|
|
627
|
+
sizeof(priority_queue_tracked_value_t) : sizeof(priority_queue_value_t));
|
|
628
|
+
for (node = dict_first((dict_t *)&queue->tree);
|
|
629
|
+
node;
|
|
630
|
+
node = dict_next((dict_t *)&queue->tree, node)) {
|
|
631
|
+
priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
|
|
632
|
+
priority_queue_identity_index_t *index = bucket->identity_index;
|
|
633
|
+
if (!index) continue;
|
|
634
|
+
size += sizeof(priority_queue_identity_index_t)
|
|
635
|
+
+ index->capacity * sizeof(priority_queue_identity_slot_t);
|
|
636
|
+
}
|
|
637
|
+
return size;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
static const rb_data_type_t priority_queue_type = {
|
|
641
|
+
.wrap_struct_name = "Farce::Internal::PriorityQueue",
|
|
642
|
+
.function = {
|
|
643
|
+
.dmark = priority_queue_mark,
|
|
644
|
+
.dfree = priority_queue_free,
|
|
645
|
+
.dsize = priority_queue_memsize,
|
|
646
|
+
.dcompact = priority_queue_compact,
|
|
647
|
+
},
|
|
648
|
+
.flags = RUBY_TYPED_FROZEN_SHAREABLE,
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
static const rb_data_type_t unshared_priority_queue_type = {
|
|
652
|
+
.wrap_struct_name = "Farce::Internal::UnsharedPriorityQueue",
|
|
653
|
+
.function = {
|
|
654
|
+
.dmark = priority_queue_mark,
|
|
655
|
+
.dfree = priority_queue_free,
|
|
656
|
+
.dsize = priority_queue_memsize,
|
|
657
|
+
.dcompact = priority_queue_compact,
|
|
658
|
+
},
|
|
659
|
+
.parent = &priority_queue_type,
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
/* The GVL protects the native fields within one Ractor. Keep the logical
|
|
663
|
+
* owner and waiter protocol because comparisons and notifications can yield. */
|
|
664
|
+
static inline void priority_queue_mutex_lock(priority_queue_t *queue)
|
|
665
|
+
{
|
|
666
|
+
if (queue->shared) pthread_mutex_lock(&queue->lock);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
static inline void priority_queue_mutex_unlock(priority_queue_t *queue)
|
|
670
|
+
{
|
|
671
|
+
if (queue->shared) pthread_mutex_unlock(&queue->lock);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
static VALUE
|
|
675
|
+
priority_queue_allocate_type(VALUE klass, bool shared)
|
|
676
|
+
{
|
|
677
|
+
priority_queue_t *queue;
|
|
678
|
+
VALUE object = TypedData_Make_Struct(
|
|
679
|
+
klass,
|
|
680
|
+
priority_queue_t,
|
|
681
|
+
shared ? &priority_queue_type : &unshared_priority_queue_type,
|
|
682
|
+
queue
|
|
683
|
+
);
|
|
684
|
+
int error;
|
|
685
|
+
|
|
686
|
+
memset(queue, 0, sizeof(*queue));
|
|
687
|
+
queue->shared = shared;
|
|
688
|
+
queue->unshared_fiber_io = FARCE_UNSHARED_FIBER_IO;
|
|
689
|
+
queue->owner_fiber = Qnil;
|
|
690
|
+
queue->owner_ruby_thread = Qnil;
|
|
691
|
+
queue->publication_owner_fiber = Qnil;
|
|
692
|
+
queue->publication_owner_ruby_thread = Qnil;
|
|
693
|
+
queue->signal = Qnil;
|
|
694
|
+
queue->unshared_self = shared ? Qnil : object;
|
|
695
|
+
queue->numeric_only = true;
|
|
696
|
+
RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_UNINITIALIZED);
|
|
697
|
+
dict_init(&queue->tree, priority_queue_compare);
|
|
698
|
+
dict_set_allocator(
|
|
699
|
+
&queue->tree,
|
|
700
|
+
priority_queue_allocate_tree_node,
|
|
701
|
+
priority_queue_free_tree_node,
|
|
702
|
+
queue
|
|
703
|
+
);
|
|
704
|
+
if (shared) {
|
|
705
|
+
error = pthread_mutex_init(&queue->lock, NULL);
|
|
706
|
+
if (error) rb_syserr_fail(error, "pthread_mutex_init");
|
|
707
|
+
queue->mutex_initialized = true;
|
|
708
|
+
}
|
|
709
|
+
return object;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
static VALUE priority_queue_allocate(VALUE klass) { return priority_queue_allocate_type(klass, true); }
|
|
713
|
+
static VALUE unshared_priority_queue_allocate(VALUE klass) { return priority_queue_allocate_type(klass, false); }
|
|
714
|
+
|
|
715
|
+
static priority_queue_t *
|
|
716
|
+
priority_queue_get_raw(VALUE self)
|
|
717
|
+
{
|
|
718
|
+
priority_queue_t *queue;
|
|
719
|
+
|
|
720
|
+
if (rb_typeddata_is_kind_of(self, &priority_queue_type)) {
|
|
721
|
+
TypedData_Get_Struct(self, priority_queue_t, &priority_queue_type, queue);
|
|
722
|
+
return queue;
|
|
723
|
+
}
|
|
724
|
+
rb_raise(rb_eTypeError, "wrong priority queue type");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
static priority_queue_t *
|
|
728
|
+
priority_queue_get(VALUE self)
|
|
729
|
+
{
|
|
730
|
+
priority_queue_t *queue = priority_queue_get_raw(self);
|
|
731
|
+
|
|
732
|
+
rb_atomic_t state = RUBY_ATOMIC_LOAD(queue->publication_state);
|
|
733
|
+
|
|
734
|
+
/* Primitive freezing can make a clean typed object Ractor-visible before
|
|
735
|
+
* recursive publication returns. Always join an active publication, even
|
|
736
|
+
* if an async exception is committing it, then recheck so a failed
|
|
737
|
+
* traversal cannot leak a usable half-published queue. The steady-state
|
|
738
|
+
* path is one atomic load and does not touch the physical mutex. */
|
|
739
|
+
if (priority_queue_publication_busy(state)) {
|
|
740
|
+
priority_queue_wait_for_publication(queue);
|
|
741
|
+
state = RUBY_ATOMIC_LOAD(queue->publication_state);
|
|
742
|
+
}
|
|
743
|
+
if (state != PRIORITY_QUEUE_INITIALIZED) {
|
|
744
|
+
rb_raise(rb_eRuntimeError, "uninitialized priority queue");
|
|
745
|
+
}
|
|
746
|
+
return queue;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
static void
|
|
750
|
+
priority_queue_lock_set_fd_flags(int fd)
|
|
751
|
+
{
|
|
752
|
+
#ifdef _WIN32
|
|
753
|
+
(void)fd;
|
|
754
|
+
#else
|
|
755
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
|
756
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
757
|
+
flags = fcntl(fd, F_GETFD, 0);
|
|
758
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
759
|
+
#endif
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/* Called with queue->lock held. A logical unlock only needs to make one
|
|
763
|
+
* waiter runnable. The selected waiter remains linked until ensure cleanup. */
|
|
764
|
+
static void
|
|
765
|
+
priority_queue_lock_notify_one_locked(priority_queue_t *queue)
|
|
766
|
+
{
|
|
767
|
+
if (!queue->shared) {
|
|
768
|
+
/* Ownership has already been released. Notify the original cohort
|
|
769
|
+
* together so cancellation or synchronous unblock cannot strand the
|
|
770
|
+
* next waiter. Reentrant operations can register for a later unlock. */
|
|
771
|
+
farce_unshared_wait_notify_all(&queue->unshared_lock_waiters);
|
|
772
|
+
}
|
|
773
|
+
unsigned char byte = 1;
|
|
774
|
+
priority_queue_lock_waiter_t *waiter;
|
|
775
|
+
|
|
776
|
+
for (waiter = queue->waiters; waiter; waiter = waiter->next) {
|
|
777
|
+
ssize_t result;
|
|
778
|
+
if (waiter->notified) continue;
|
|
779
|
+
do {
|
|
780
|
+
result = write(waiter->write_fd, &byte, 1);
|
|
781
|
+
} while (result < 0 && errno == EINTR);
|
|
782
|
+
if (result == 1 ||
|
|
783
|
+
(result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
784
|
+
waiter->notified = true;
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
typedef struct {
|
|
791
|
+
priority_queue_t *queue;
|
|
792
|
+
priority_queue_lock_waiter_t waiter;
|
|
793
|
+
} priority_queue_lock_wait_context_t;
|
|
794
|
+
|
|
795
|
+
static bool
|
|
796
|
+
priority_queue_lock_wait_for_descriptor(int fd)
|
|
797
|
+
{
|
|
798
|
+
return containers_wait_for_readable(fd, Qnil);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
static VALUE
|
|
802
|
+
priority_queue_lock_wait_body(VALUE opaque)
|
|
803
|
+
{
|
|
804
|
+
priority_queue_lock_wait_context_t *context =
|
|
805
|
+
(priority_queue_lock_wait_context_t *)opaque;
|
|
806
|
+
bool awakened = priority_queue_lock_wait_for_descriptor(context->waiter.read_fd);
|
|
807
|
+
return awakened ? Qtrue : Qfalse;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
static VALUE
|
|
811
|
+
priority_queue_lock_wait_cleanup(VALUE opaque)
|
|
812
|
+
{
|
|
813
|
+
priority_queue_lock_wait_context_t *context =
|
|
814
|
+
(priority_queue_lock_wait_context_t *)opaque;
|
|
815
|
+
priority_queue_lock_waiter_t *previous = NULL;
|
|
816
|
+
priority_queue_lock_waiter_t **link;
|
|
817
|
+
|
|
818
|
+
priority_queue_mutex_lock(context->queue);
|
|
819
|
+
for (link = &context->queue->waiters; *link; link = &(*link)->next) {
|
|
820
|
+
if (*link == &context->waiter) {
|
|
821
|
+
*link = context->waiter.next;
|
|
822
|
+
if (context->queue->last_waiter == &context->waiter) {
|
|
823
|
+
context->queue->last_waiter = previous;
|
|
824
|
+
}
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
previous = *link;
|
|
828
|
+
}
|
|
829
|
+
/* A notification only makes this waiter runnable; it does not reserve
|
|
830
|
+
* ownership. Pass the baton whenever a notified waiter leaves while the
|
|
831
|
+
* queue is still ownerless. This covers both interruption inside rb_io_wait
|
|
832
|
+
* and cancellation after a normal wake but before the acquisition loop can
|
|
833
|
+
* claim ownership, so later waiters cannot be stranded. */
|
|
834
|
+
if (context->waiter.notified &&
|
|
835
|
+
NIL_P(context->queue->owner_fiber) &&
|
|
836
|
+
!priority_queue_publication_busy(
|
|
837
|
+
RUBY_ATOMIC_LOAD(context->queue->publication_state)
|
|
838
|
+
)) {
|
|
839
|
+
priority_queue_lock_notify_one_locked(context->queue);
|
|
840
|
+
}
|
|
841
|
+
priority_queue_mutex_unlock(context->queue);
|
|
842
|
+
close(context->waiter.read_fd);
|
|
843
|
+
close(context->waiter.write_fd);
|
|
844
|
+
return Qnil;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/* Called with queue->lock held and always returns with it released. */
|
|
848
|
+
static void
|
|
849
|
+
priority_queue_lock_wait_once(priority_queue_t *queue)
|
|
850
|
+
{
|
|
851
|
+
if (!queue->shared && (!queue->unshared_fiber_io || NIL_P(rb_fiber_scheduler_current()))) {
|
|
852
|
+
(void)farce_unshared_wait(&queue->unshared_lock_waiters, queue->unshared_self, false, 0);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
int descriptors[2];
|
|
856
|
+
priority_queue_lock_wait_context_t context;
|
|
857
|
+
|
|
858
|
+
if (pipe(descriptors) != 0) {
|
|
859
|
+
priority_queue_mutex_unlock(queue);
|
|
860
|
+
rb_sys_fail("pipe");
|
|
861
|
+
}
|
|
862
|
+
priority_queue_lock_set_fd_flags(descriptors[0]);
|
|
863
|
+
priority_queue_lock_set_fd_flags(descriptors[1]);
|
|
864
|
+
|
|
865
|
+
context = (priority_queue_lock_wait_context_t){
|
|
866
|
+
.queue = queue,
|
|
867
|
+
.waiter = {
|
|
868
|
+
.read_fd = descriptors[0],
|
|
869
|
+
.write_fd = descriptors[1],
|
|
870
|
+
.notified = false,
|
|
871
|
+
.next = NULL,
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
if (queue->last_waiter) queue->last_waiter->next = &context.waiter;
|
|
875
|
+
else queue->waiters = &context.waiter;
|
|
876
|
+
queue->last_waiter = &context.waiter;
|
|
877
|
+
priority_queue_mutex_unlock(queue);
|
|
878
|
+
(void)rb_ensure(
|
|
879
|
+
priority_queue_lock_wait_body,
|
|
880
|
+
(VALUE)&context,
|
|
881
|
+
priority_queue_lock_wait_cleanup,
|
|
882
|
+
(VALUE)&context
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
static void
|
|
887
|
+
priority_queue_lock(priority_queue_t *queue, bool *acquired)
|
|
888
|
+
{
|
|
889
|
+
VALUE current = rb_fiber_current();
|
|
890
|
+
VALUE current_thread = rb_thread_current();
|
|
891
|
+
VALUE scheduler = rb_fiber_scheduler_current();
|
|
892
|
+
|
|
893
|
+
for (;;) {
|
|
894
|
+
bool publication_busy;
|
|
895
|
+
|
|
896
|
+
priority_queue_mutex_lock(queue);
|
|
897
|
+
publication_busy = priority_queue_publication_busy(
|
|
898
|
+
RUBY_ATOMIC_LOAD(queue->publication_state)
|
|
899
|
+
);
|
|
900
|
+
if (queue->owner_fiber == current ||
|
|
901
|
+
(publication_busy && queue->publication_owner_fiber == current)) {
|
|
902
|
+
priority_queue_mutex_unlock(queue);
|
|
903
|
+
rb_raise(rb_eThreadError, "deadlock; recursive priority queue access");
|
|
904
|
+
}
|
|
905
|
+
if (NIL_P(queue->owner_fiber) && !publication_busy) {
|
|
906
|
+
queue->owner_fiber = current;
|
|
907
|
+
queue->owner_ruby_thread = current_thread;
|
|
908
|
+
*acquired = true;
|
|
909
|
+
priority_queue_mutex_unlock(queue);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if ((queue->owner_ruby_thread == current_thread ||
|
|
913
|
+
(publication_busy &&
|
|
914
|
+
queue->publication_owner_ruby_thread == current_thread)) &&
|
|
915
|
+
NIL_P(scheduler)) {
|
|
916
|
+
priority_queue_mutex_unlock(queue);
|
|
917
|
+
rb_raise(
|
|
918
|
+
rb_eThreadError,
|
|
919
|
+
"deadlock; priority queue contention between unscheduled fibers"
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
priority_queue_lock_wait_once(queue);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
static void
|
|
927
|
+
priority_queue_unlock(priority_queue_t *queue)
|
|
928
|
+
{
|
|
929
|
+
rb_atomic_t state;
|
|
930
|
+
|
|
931
|
+
priority_queue_mutex_lock(queue);
|
|
932
|
+
state = RUBY_ATOMIC_LOAD(queue->publication_state);
|
|
933
|
+
if (state == PRIORITY_QUEUE_PUBLISHED) {
|
|
934
|
+
RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_INITIALIZED);
|
|
935
|
+
}
|
|
936
|
+
else if (state == PRIORITY_QUEUE_PUBLISHING) {
|
|
937
|
+
RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_UNINITIALIZED);
|
|
938
|
+
}
|
|
939
|
+
queue->owner_fiber = Qnil;
|
|
940
|
+
queue->owner_ruby_thread = Qnil;
|
|
941
|
+
queue->publication_owner_fiber = Qnil;
|
|
942
|
+
queue->publication_owner_ruby_thread = Qnil;
|
|
943
|
+
priority_queue_lock_notify_one_locked(queue);
|
|
944
|
+
priority_queue_mutex_unlock(queue);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
typedef struct {
|
|
948
|
+
priority_queue_t *queue;
|
|
949
|
+
bool acquired;
|
|
950
|
+
} priority_queue_publication_wait_t;
|
|
951
|
+
|
|
952
|
+
static VALUE
|
|
953
|
+
priority_queue_publication_wait_body(VALUE opaque)
|
|
954
|
+
{
|
|
955
|
+
priority_queue_publication_wait_t *wait =
|
|
956
|
+
(priority_queue_publication_wait_t *)opaque;
|
|
957
|
+
|
|
958
|
+
priority_queue_lock(wait->queue, &wait->acquired);
|
|
959
|
+
return Qnil;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
static VALUE
|
|
963
|
+
priority_queue_publication_wait_ensure(VALUE opaque)
|
|
964
|
+
{
|
|
965
|
+
priority_queue_publication_wait_t *wait =
|
|
966
|
+
(priority_queue_publication_wait_t *)opaque;
|
|
967
|
+
|
|
968
|
+
if (wait->acquired) priority_queue_unlock(wait->queue);
|
|
969
|
+
return Qnil;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
static void
|
|
973
|
+
priority_queue_wait_for_publication(priority_queue_t *queue)
|
|
974
|
+
{
|
|
975
|
+
priority_queue_publication_wait_t wait = {
|
|
976
|
+
.queue = queue,
|
|
977
|
+
.acquired = false,
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
/* The unmarked publication owner makes recursive callbacks fail with the
|
|
981
|
+
* ordinary ThreadError, while other Ractors and scheduler-managed Fibers
|
|
982
|
+
* park until the outer operation unlocks. */
|
|
983
|
+
(void)rb_ensure(
|
|
984
|
+
priority_queue_publication_wait_body,
|
|
985
|
+
(VALUE)&wait,
|
|
986
|
+
priority_queue_publication_wait_ensure,
|
|
987
|
+
(VALUE)&wait
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
typedef VALUE (*priority_queue_operation_t)(VALUE opaque);
|
|
992
|
+
|
|
993
|
+
typedef struct {
|
|
994
|
+
priority_queue_t *queue;
|
|
995
|
+
priority_queue_operation_t operation;
|
|
996
|
+
VALUE argument;
|
|
997
|
+
bool acquired;
|
|
998
|
+
} priority_queue_call_t;
|
|
999
|
+
|
|
1000
|
+
static VALUE
|
|
1001
|
+
priority_queue_call_body(VALUE opaque)
|
|
1002
|
+
{
|
|
1003
|
+
priority_queue_call_t *call = (priority_queue_call_t *)opaque;
|
|
1004
|
+
priority_queue_lock(call->queue, &call->acquired);
|
|
1005
|
+
return call->operation(call->argument);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
static VALUE
|
|
1009
|
+
priority_queue_unlock_ensure(VALUE opaque)
|
|
1010
|
+
{
|
|
1011
|
+
priority_queue_call_t *call = (priority_queue_call_t *)opaque;
|
|
1012
|
+
|
|
1013
|
+
if (call->acquired) priority_queue_unlock(call->queue);
|
|
1014
|
+
return Qnil;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
static VALUE
|
|
1018
|
+
priority_queue_call(
|
|
1019
|
+
priority_queue_t *queue,
|
|
1020
|
+
priority_queue_operation_t operation,
|
|
1021
|
+
VALUE argument
|
|
1022
|
+
)
|
|
1023
|
+
{
|
|
1024
|
+
priority_queue_call_t call = {
|
|
1025
|
+
.queue = queue,
|
|
1026
|
+
.operation = operation,
|
|
1027
|
+
.argument = argument,
|
|
1028
|
+
.acquired = false,
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
return rb_ensure(
|
|
1032
|
+
priority_queue_call_body,
|
|
1033
|
+
(VALUE)&call,
|
|
1034
|
+
priority_queue_unlock_ensure,
|
|
1035
|
+
(VALUE)&call
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
static void
|
|
1040
|
+
priority_queue_check_shareable(VALUE value)
|
|
1041
|
+
{
|
|
1042
|
+
if (!rb_ractor_shareable_p(value)) {
|
|
1043
|
+
rb_raise(eIsolationError, "value is not shareable: %" PRIsVALUE, rb_inspect(value));
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/* Notification is deliberately issued while the queue operation still owns
|
|
1048
|
+
* its lock and before the state commit. A woken waiter cannot inspect the queue
|
|
1049
|
+
* until the commit and unlock complete. Callers must finish every allocation
|
|
1050
|
+
* and user callback before this point, then use only no-fail C mutations.
|
|
1051
|
+
*
|
|
1052
|
+
* The generic new-bucket insertion is the one conservative exception: Kazlib
|
|
1053
|
+
* repeats comparisons before linking the prepared node. If one raises, the
|
|
1054
|
+
* pending bucket ensure cleanup leaves the queue unchanged and the notification
|
|
1055
|
+
* is merely spurious; Kazlib never links a node before those comparisons end.
|
|
1056
|
+
* Homogeneous numeric keys reuse their search and commit without comparisons. */
|
|
1057
|
+
static void
|
|
1058
|
+
priority_queue_notify_before_commit(priority_queue_t *queue)
|
|
1059
|
+
{
|
|
1060
|
+
if (queue->unshared_signal) containers_unshared_signal_notify(queue->unshared_signal);
|
|
1061
|
+
else if (!NIL_P(queue->signal)) rb_funcall(queue->signal, id_broadcast, 0);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
RBIMPL_ATTR_NORETURN()
|
|
1065
|
+
static void
|
|
1066
|
+
priority_queue_raise_closed(void)
|
|
1067
|
+
{
|
|
1068
|
+
VALUE farce = rb_const_get(rb_cObject, id_farce);
|
|
1069
|
+
rb_raise(rb_const_get(farce, id_closed_error), "queue is closed");
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
RBIMPL_ATTR_NORETURN()
|
|
1073
|
+
static void
|
|
1074
|
+
priority_queue_raise_sealed(void)
|
|
1075
|
+
{
|
|
1076
|
+
VALUE farce = rb_const_get(rb_cObject, id_farce);
|
|
1077
|
+
rb_raise(rb_const_get(farce, id_sealed_error), "queue is sealed");
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
typedef struct {
|
|
1081
|
+
priority_queue_t *queue;
|
|
1082
|
+
VALUE self;
|
|
1083
|
+
VALUE signal;
|
|
1084
|
+
size_t capacity;
|
|
1085
|
+
bool bounded;
|
|
1086
|
+
bool track_age;
|
|
1087
|
+
} priority_queue_initialize_t;
|
|
1088
|
+
|
|
1089
|
+
static void
|
|
1090
|
+
priority_queue_check_initializable(VALUE self, priority_queue_t *queue)
|
|
1091
|
+
{
|
|
1092
|
+
rb_atomic_t state = RUBY_ATOMIC_LOAD(queue->publication_state);
|
|
1093
|
+
|
|
1094
|
+
if (state == PRIORITY_QUEUE_INITIALIZED) {
|
|
1095
|
+
rb_raise(rb_eRuntimeError, "priority queue is already initialized");
|
|
1096
|
+
}
|
|
1097
|
+
rb_check_frozen(self);
|
|
1098
|
+
|
|
1099
|
+
/* Publication marks the object busy before native shareability is set. Reject the
|
|
1100
|
+
* narrow pre-publication window here so a competing initializer cannot enter
|
|
1101
|
+
* user coercion callbacks while publication is already in progress. */
|
|
1102
|
+
if (RUBY_ATOMIC_LOAD(queue->publication_state) !=
|
|
1103
|
+
PRIORITY_QUEUE_UNINITIALIZED) {
|
|
1104
|
+
rb_raise(rb_eRuntimeError, "priority queue is already initialized");
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
static void
|
|
1109
|
+
priority_queue_validate_references(VALUE self)
|
|
1110
|
+
{
|
|
1111
|
+
priority_queue_t *queue = priority_queue_get_raw(self);
|
|
1112
|
+
|
|
1113
|
+
containers_check_shareable(queue->owner_fiber);
|
|
1114
|
+
containers_check_shareable(queue->owner_ruby_thread);
|
|
1115
|
+
containers_check_shareable(queue->signal);
|
|
1116
|
+
containers_check_shareable(queue->unshared_self);
|
|
1117
|
+
if (queue->size != 0 || dict_count(&queue->tree) != 0 ||
|
|
1118
|
+
queue->unshared_lock_waiters.count != 0) {
|
|
1119
|
+
rb_raise(rb_eRuntimeError, "priority queue publication requires empty storage");
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
static VALUE
|
|
1124
|
+
priority_queue_freeze(VALUE self)
|
|
1125
|
+
{
|
|
1126
|
+
priority_queue_t *queue = priority_queue_get_raw(self);
|
|
1127
|
+
if (queue->shared) containers_raise_unfreezable(self);
|
|
1128
|
+
return rb_obj_freeze(self);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
static VALUE
|
|
1132
|
+
priority_queue_frozen_p(VALUE self)
|
|
1133
|
+
{
|
|
1134
|
+
priority_queue_t *queue = priority_queue_get_raw(self);
|
|
1135
|
+
return queue->shared ? Qfalse : rb_obj_frozen_p(self);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
static VALUE
|
|
1139
|
+
priority_queue_make_shareable(VALUE opaque)
|
|
1140
|
+
{
|
|
1141
|
+
priority_queue_initialize_t *initialization =
|
|
1142
|
+
(priority_queue_initialize_t *)opaque;
|
|
1143
|
+
|
|
1144
|
+
return containers_publish_native_with_references(
|
|
1145
|
+
initialization->self,
|
|
1146
|
+
priority_queue_validate_references
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
static VALUE
|
|
1151
|
+
priority_queue_finish_publication(VALUE opaque)
|
|
1152
|
+
{
|
|
1153
|
+
priority_queue_initialize_t *initialization =
|
|
1154
|
+
(priority_queue_initialize_t *)opaque;
|
|
1155
|
+
bool shareable = rb_ractor_shareable_p(initialization->self);
|
|
1156
|
+
|
|
1157
|
+
/* An async exception can arrive after rb_ractor_make_shareable has set the
|
|
1158
|
+
* shareable flag. In that case the native fields already form a coherent
|
|
1159
|
+
* queue, so publish initialized before propagating it. */
|
|
1160
|
+
priority_queue_mutex_lock(initialization->queue);
|
|
1161
|
+
if (shareable) {
|
|
1162
|
+
RUBY_ATOMIC_SET(
|
|
1163
|
+
initialization->queue->publication_state,
|
|
1164
|
+
PRIORITY_QUEUE_PUBLISHED
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
priority_queue_mutex_unlock(initialization->queue);
|
|
1168
|
+
return Qnil;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
static VALUE
|
|
1172
|
+
priority_queue_publish_shareable(priority_queue_initialize_t *initialization)
|
|
1173
|
+
{
|
|
1174
|
+
priority_queue_t *queue = initialization->queue;
|
|
1175
|
+
|
|
1176
|
+
/* Install the gate before native publication can make this typed object
|
|
1177
|
+
* Ractor-visible. Move the logical owner out of dmark, while retaining it
|
|
1178
|
+
* for callback reentry and deadlock detection. */
|
|
1179
|
+
priority_queue_mutex_lock(queue);
|
|
1180
|
+
RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_PUBLISHING);
|
|
1181
|
+
queue->publication_owner_fiber = queue->owner_fiber;
|
|
1182
|
+
queue->publication_owner_ruby_thread = queue->owner_ruby_thread;
|
|
1183
|
+
queue->owner_fiber = Qnil;
|
|
1184
|
+
queue->owner_ruby_thread = Qnil;
|
|
1185
|
+
priority_queue_mutex_unlock(queue);
|
|
1186
|
+
return rb_ensure(
|
|
1187
|
+
priority_queue_make_shareable,
|
|
1188
|
+
(VALUE)initialization,
|
|
1189
|
+
priority_queue_finish_publication,
|
|
1190
|
+
(VALUE)initialization
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
static VALUE
|
|
1195
|
+
priority_queue_initialize_commit(VALUE opaque)
|
|
1196
|
+
{
|
|
1197
|
+
priority_queue_initialize_t *initialization =
|
|
1198
|
+
(priority_queue_initialize_t *)opaque;
|
|
1199
|
+
priority_queue_t *queue = initialization->queue;
|
|
1200
|
+
|
|
1201
|
+
/* Capacity coercion and signal protocol checks above this commit can run
|
|
1202
|
+
* arbitrary Ruby. They may recursively initialize or freeze this object,
|
|
1203
|
+
* and two threads may finish parsing concurrently. The synchronized queue
|
|
1204
|
+
* serializes this authoritative recheck and the following no-fail field
|
|
1205
|
+
* assignments so a losing initializer cannot overwrite the winner. */
|
|
1206
|
+
if (RUBY_ATOMIC_LOAD(queue->publication_state) !=
|
|
1207
|
+
PRIORITY_QUEUE_UNINITIALIZED) {
|
|
1208
|
+
rb_raise(rb_eRuntimeError, "priority queue is already initialized");
|
|
1209
|
+
}
|
|
1210
|
+
rb_check_frozen(initialization->self);
|
|
1211
|
+
|
|
1212
|
+
containers_unshared_signal_t *unshared_signal =
|
|
1213
|
+
containers_unshared_signal_get_if_exact(initialization->signal);
|
|
1214
|
+
bool fiber_io = containers_unshared_signal_uses_fiber_io(initialization->signal);
|
|
1215
|
+
queue->unshared_signal = unshared_signal;
|
|
1216
|
+
queue->unshared_fiber_io = fiber_io;
|
|
1217
|
+
queue->bounded = initialization->bounded;
|
|
1218
|
+
queue->capacity = initialization->capacity;
|
|
1219
|
+
queue->track_age = initialization->track_age;
|
|
1220
|
+
queue->signal = initialization->signal;
|
|
1221
|
+
/* Publication installs its logical gate before marking the object shareable
|
|
1222
|
+
* and keeps that whole transition under nested ensure cleanup. */
|
|
1223
|
+
if (queue->shared) return priority_queue_publish_shareable(initialization);
|
|
1224
|
+
rb_obj_freeze(initialization->self);
|
|
1225
|
+
RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_INITIALIZED);
|
|
1226
|
+
return initialization->self;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
static VALUE
|
|
1230
|
+
priority_queue_initialize(int argc, VALUE *argv, VALUE self)
|
|
1231
|
+
{
|
|
1232
|
+
VALUE keywords = Qnil;
|
|
1233
|
+
VALUE keyword_values[3] = {Qundef, Qundef, Qundef};
|
|
1234
|
+
ID keyword_ids[] = {rb_intern("capacity"), rb_intern("signal"), rb_intern("track_age")};
|
|
1235
|
+
priority_queue_t *queue = priority_queue_get_raw(self);
|
|
1236
|
+
priority_queue_initialize_t initialization = {
|
|
1237
|
+
.queue = queue,
|
|
1238
|
+
.self = self,
|
|
1239
|
+
.signal = Qnil,
|
|
1240
|
+
.capacity = 0,
|
|
1241
|
+
.bounded = false,
|
|
1242
|
+
};
|
|
1243
|
+
rb_scan_args(argc, argv, "0:", &keywords);
|
|
1244
|
+
if (!NIL_P(keywords)) {
|
|
1245
|
+
rb_get_kwargs(keywords, keyword_ids, 0, 3, keyword_values);
|
|
1246
|
+
}
|
|
1247
|
+
priority_queue_check_initializable(self, queue);
|
|
1248
|
+
|
|
1249
|
+
if (keyword_values[0] == Qundef || keyword_values[0] == Qnil) {
|
|
1250
|
+
initialization.bounded = false;
|
|
1251
|
+
initialization.capacity = 0;
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
VALUE capacity_value = rb_to_int(keyword_values[0]);
|
|
1255
|
+
long long capacity = NUM2LL(capacity_value);
|
|
1256
|
+
if (capacity <= 0) rb_raise(rb_eArgError, "capacity must be positive or nil");
|
|
1257
|
+
if ((unsigned long long)capacity > (unsigned long long)SIZE_MAX) {
|
|
1258
|
+
rb_raise(rb_eArgError, "capacity is too large");
|
|
1259
|
+
}
|
|
1260
|
+
initialization.bounded = true;
|
|
1261
|
+
initialization.capacity = (size_t)capacity;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if (keyword_values[1] != Qundef && !NIL_P(keyword_values[1])) {
|
|
1265
|
+
if (queue->shared) priority_queue_check_shareable(keyword_values[1]);
|
|
1266
|
+
if (!rb_respond_to(keyword_values[1], id_broadcast)) {
|
|
1267
|
+
rb_raise(rb_eTypeError, "signal must respond to #broadcast");
|
|
1268
|
+
}
|
|
1269
|
+
initialization.signal = keyword_values[1];
|
|
1270
|
+
}
|
|
1271
|
+
initialization.track_age = keyword_values[2] != Qundef && RTEST(keyword_values[2]);
|
|
1272
|
+
|
|
1273
|
+
/* Capacity coercion and signal inspection can invoke arbitrary Ruby. A
|
|
1274
|
+
* sibling Fiber may pause there, then be resumed while another initializer
|
|
1275
|
+
* recursively freezes this receiver for Ractor publication. Reject that
|
|
1276
|
+
* resumed initializer before it can wait on the publisher's logical lock.
|
|
1277
|
+
* The commit retains its synchronized recheck for ordinary thread races. */
|
|
1278
|
+
priority_queue_check_initializable(self, queue);
|
|
1279
|
+
|
|
1280
|
+
return priority_queue_call(
|
|
1281
|
+
queue,
|
|
1282
|
+
priority_queue_initialize_commit,
|
|
1283
|
+
(VALUE)&initialization
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
static VALUE
|
|
1288
|
+
priority_queue_initialize_copy(VALUE self, VALUE other)
|
|
1289
|
+
{
|
|
1290
|
+
(void)self;
|
|
1291
|
+
(void)other;
|
|
1292
|
+
rb_raise(rb_eTypeError, "priority queues cannot be copied");
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
typedef struct {
|
|
1296
|
+
priority_queue_t *queue;
|
|
1297
|
+
VALUE self;
|
|
1298
|
+
VALUE priority;
|
|
1299
|
+
VALUE stored_priority;
|
|
1300
|
+
VALUE value;
|
|
1301
|
+
priority_queue_bucket_t *pending_bucket;
|
|
1302
|
+
priority_queue_value_t *pending_entry;
|
|
1303
|
+
} priority_queue_push_t;
|
|
1304
|
+
|
|
1305
|
+
static VALUE
|
|
1306
|
+
priority_queue_push_cleanup(VALUE opaque)
|
|
1307
|
+
{
|
|
1308
|
+
priority_queue_push_t *push = (priority_queue_push_t *)opaque;
|
|
1309
|
+
if (push->pending_bucket) {
|
|
1310
|
+
priority_queue_free_tree_node(&push->pending_bucket->node, push->queue);
|
|
1311
|
+
push->pending_bucket = NULL;
|
|
1312
|
+
}
|
|
1313
|
+
if (push->pending_entry) {
|
|
1314
|
+
free(push->pending_entry);
|
|
1315
|
+
push->pending_entry = NULL;
|
|
1316
|
+
}
|
|
1317
|
+
return Qnil;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
static VALUE
|
|
1321
|
+
priority_queue_push_body(VALUE opaque)
|
|
1322
|
+
{
|
|
1323
|
+
priority_queue_push_t *push = (priority_queue_push_t *)opaque;
|
|
1324
|
+
priority_queue_t *queue = push->queue;
|
|
1325
|
+
dnode_t *node;
|
|
1326
|
+
priority_queue_bucket_t *bucket;
|
|
1327
|
+
priority_queue_value_t *entry;
|
|
1328
|
+
dnode_t *parent = NULL;
|
|
1329
|
+
int position = -1;
|
|
1330
|
+
bool numeric = FIXNUM_P(push->priority) ||
|
|
1331
|
+
(CLASS_OF(push->priority) == rb_cFloat && !isnan(RFLOAT_VALUE(push->priority)));
|
|
1332
|
+
bool reuse_position = queue->numeric_only && numeric &&
|
|
1333
|
+
(dict_isempty(&queue->tree) ||
|
|
1334
|
+
FIXNUM_P(push->priority) == FIXNUM_P((VALUE)dnode_getkey(queue->tree.dict_nilnode.dict_left)));
|
|
1335
|
+
|
|
1336
|
+
if (queue->closed) priority_queue_raise_closed();
|
|
1337
|
+
if (queue->sealed) priority_queue_raise_sealed();
|
|
1338
|
+
if (queue->bounded && queue->size >= queue->capacity) return Qfalse;
|
|
1339
|
+
|
|
1340
|
+
if (reuse_position && !FIXNUM_P(push->priority)) {
|
|
1341
|
+
/* A homogeneous Float tree needs no Ruby dispatch, type tests, or
|
|
1342
|
+
* unboxing of stored keys in this search. All NaNs use the slow path. */
|
|
1343
|
+
double priority = RFLOAT_VALUE(push->priority);
|
|
1344
|
+
dnode_t *nil = &queue->tree.dict_nilnode;
|
|
1345
|
+
dnode_t *where = nil->dict_left;
|
|
1346
|
+
parent = nil;
|
|
1347
|
+
while (where != nil) {
|
|
1348
|
+
double stored = ((priority_queue_bucket_t *)where)->float_priority;
|
|
1349
|
+
parent = where;
|
|
1350
|
+
position = priority < stored ? -1 : priority > stored ? 1 : 0;
|
|
1351
|
+
if (!position) break;
|
|
1352
|
+
where = position < 0 ? where->dict_left : where->dict_right;
|
|
1353
|
+
}
|
|
1354
|
+
node = where == nil ? NULL : where;
|
|
1355
|
+
}
|
|
1356
|
+
else {
|
|
1357
|
+
node = reuse_position
|
|
1358
|
+
? dict_lookup_position(&queue->tree, (const void *)push->priority, &parent, &position)
|
|
1359
|
+
: dict_lookup(&queue->tree, (const void *)push->priority);
|
|
1360
|
+
}
|
|
1361
|
+
if (node) {
|
|
1362
|
+
bucket = (priority_queue_bucket_t *)node;
|
|
1363
|
+
priority_queue_identity_index_prepare_insert(bucket->identity_index, push->value);
|
|
1364
|
+
entry = calloc(1, queue->track_age ? sizeof(priority_queue_tracked_value_t) : sizeof(priority_queue_value_t));
|
|
1365
|
+
if (!entry) rb_memerror();
|
|
1366
|
+
entry->value = push->value;
|
|
1367
|
+
entry->previous = bucket->tail;
|
|
1368
|
+
entry->next = NULL;
|
|
1369
|
+
entry->identity_next = NULL;
|
|
1370
|
+
push->pending_entry = entry;
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
bucket = calloc(1, sizeof(priority_queue_bucket_t));
|
|
1374
|
+
entry = calloc(1, queue->track_age ? sizeof(priority_queue_tracked_value_t) : sizeof(priority_queue_value_t));
|
|
1375
|
+
if (!bucket || !entry) {
|
|
1376
|
+
free(bucket);
|
|
1377
|
+
free(entry);
|
|
1378
|
+
rb_memerror();
|
|
1379
|
+
}
|
|
1380
|
+
entry->value = push->value;
|
|
1381
|
+
entry->previous = NULL;
|
|
1382
|
+
entry->next = NULL;
|
|
1383
|
+
entry->identity_next = NULL;
|
|
1384
|
+
bucket->head = entry;
|
|
1385
|
+
bucket->tail = entry;
|
|
1386
|
+
bucket->size = 1;
|
|
1387
|
+
/* Own both allocations before copying a String priority: that copy can
|
|
1388
|
+
* raise, and the ensure handler must then release the partial bucket. */
|
|
1389
|
+
push->pending_bucket = bucket;
|
|
1390
|
+
if (RB_TYPE_P(push->priority, T_STRING)) {
|
|
1391
|
+
push->stored_priority = priority_queue_snapshot_string(push->priority);
|
|
1392
|
+
}
|
|
1393
|
+
else {
|
|
1394
|
+
push->stored_priority = push->priority;
|
|
1395
|
+
}
|
|
1396
|
+
bucket->priority = push->stored_priority;
|
|
1397
|
+
if (CLASS_OF(push->priority) == rb_cFloat) {
|
|
1398
|
+
bucket->float_priority = RFLOAT_VALUE(push->priority);
|
|
1399
|
+
}
|
|
1400
|
+
/* The bucket embeds its dnode and is recovered by address, so Kazlib's
|
|
1401
|
+
* auxiliary data slot is intentionally unused. Keeping it NULL also
|
|
1402
|
+
* means an unstable comparator's duplicate-insert fallback cannot
|
|
1403
|
+
* leave a pointer to the pending bucket after ensure frees it. */
|
|
1404
|
+
dnode_init(&bucket->node, NULL);
|
|
1405
|
+
bucket->node.dict_key = (const void *)bucket->priority;
|
|
1406
|
+
priority_queue_notify_before_commit(queue);
|
|
1407
|
+
bool inserted = reuse_position
|
|
1408
|
+
? dict_insert_at(&queue->tree, &bucket->node, bucket->node.dict_key, parent, position)
|
|
1409
|
+
: dict_insert(&queue->tree, &bucket->node, bucket->node.dict_key);
|
|
1410
|
+
if (!inserted) {
|
|
1411
|
+
rb_raise(rb_eRuntimeError, "priority comparator changed during insertion");
|
|
1412
|
+
}
|
|
1413
|
+
push->pending_bucket = NULL;
|
|
1414
|
+
if (!reuse_position) queue->numeric_only = false;
|
|
1415
|
+
queue->size++;
|
|
1416
|
+
priority_queue_track_push(queue, entry);
|
|
1417
|
+
return Qtrue;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
priority_queue_notify_before_commit(queue);
|
|
1421
|
+
if (bucket->tail) bucket->tail->next = entry;
|
|
1422
|
+
else bucket->head = entry;
|
|
1423
|
+
bucket->tail = entry;
|
|
1424
|
+
bucket->size++;
|
|
1425
|
+
priority_queue_identity_index_add(bucket->identity_index, entry);
|
|
1426
|
+
queue->size++;
|
|
1427
|
+
priority_queue_track_push(queue, entry);
|
|
1428
|
+
push->pending_entry = NULL;
|
|
1429
|
+
return Qtrue;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
static VALUE
|
|
1433
|
+
priority_queue_push_locked(VALUE opaque)
|
|
1434
|
+
{
|
|
1435
|
+
return rb_ensure(
|
|
1436
|
+
priority_queue_push_body,
|
|
1437
|
+
opaque,
|
|
1438
|
+
priority_queue_push_cleanup,
|
|
1439
|
+
opaque
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/* A Ractor-confined operation may run under the GVL alone when it cannot call
|
|
1444
|
+
* Ruby. Never bypass a suspended callback's logical owner. The caller must
|
|
1445
|
+
* prove comparison/notification callbacks are impossible on this path. */
|
|
1446
|
+
static inline bool
|
|
1447
|
+
priority_queue_can_run_direct(priority_queue_t *queue)
|
|
1448
|
+
{
|
|
1449
|
+
return !queue->shared && NIL_P(queue->owner_fiber) &&
|
|
1450
|
+
(NIL_P(queue->signal) ||
|
|
1451
|
+
(queue->unshared_signal && !containers_unshared_signal_has_waiters(queue->unshared_signal)));
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
static VALUE
|
|
1455
|
+
priority_queue_push(VALUE self, VALUE priority, VALUE value)
|
|
1456
|
+
{
|
|
1457
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1458
|
+
priority_queue_push_t push = {
|
|
1459
|
+
.queue = queue,
|
|
1460
|
+
.self = self,
|
|
1461
|
+
.priority = priority,
|
|
1462
|
+
.stored_priority = priority,
|
|
1463
|
+
.value = value,
|
|
1464
|
+
.pending_bucket = NULL,
|
|
1465
|
+
.pending_entry = NULL,
|
|
1466
|
+
};
|
|
1467
|
+
if (queue->shared) priority_queue_check_shareable(priority);
|
|
1468
|
+
if (queue->shared) priority_queue_check_shareable(value);
|
|
1469
|
+
if (priority_queue_can_run_direct(queue) && queue->numeric_only &&
|
|
1470
|
+
(FIXNUM_P(priority) || (CLASS_OF(priority) == rb_cFloat && !isnan(RFLOAT_VALUE(priority)))) &&
|
|
1471
|
+
(dict_isempty(&queue->tree) ||
|
|
1472
|
+
FIXNUM_P(priority) == FIXNUM_P((VALUE)dnode_getkey(queue->tree.dict_nilnode.dict_left)))) {
|
|
1473
|
+
/* Retain allocation cleanup, but there is no callback lock to release.
|
|
1474
|
+
* The homogeneous numeric path uses dict_insert_at without dispatch. */
|
|
1475
|
+
return priority_queue_push_locked((VALUE)&push);
|
|
1476
|
+
}
|
|
1477
|
+
return priority_queue_call(
|
|
1478
|
+
queue,
|
|
1479
|
+
priority_queue_push_locked,
|
|
1480
|
+
(VALUE)&push
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
typedef enum {
|
|
1485
|
+
PRIORITY_QUEUE_POP,
|
|
1486
|
+
PRIORITY_QUEUE_PEEK,
|
|
1487
|
+
PRIORITY_QUEUE_PEEK_PRIORITY,
|
|
1488
|
+
} priority_queue_read_kind_t;
|
|
1489
|
+
|
|
1490
|
+
typedef struct {
|
|
1491
|
+
priority_queue_t *queue;
|
|
1492
|
+
priority_queue_read_kind_t kind;
|
|
1493
|
+
VALUE result;
|
|
1494
|
+
VALUE latest_priority;
|
|
1495
|
+
bool empty;
|
|
1496
|
+
bool last;
|
|
1497
|
+
bool limited;
|
|
1498
|
+
} priority_queue_read_t;
|
|
1499
|
+
|
|
1500
|
+
static VALUE
|
|
1501
|
+
priority_queue_read_body(VALUE opaque)
|
|
1502
|
+
{
|
|
1503
|
+
priority_queue_read_t *read = (priority_queue_read_t *)opaque;
|
|
1504
|
+
priority_queue_t *queue = read->queue;
|
|
1505
|
+
dnode_t *node;
|
|
1506
|
+
priority_queue_bucket_t *bucket;
|
|
1507
|
+
priority_queue_value_t *entry;
|
|
1508
|
+
|
|
1509
|
+
if (queue->closed) priority_queue_raise_closed();
|
|
1510
|
+
node = read->last ? dict_last(&queue->tree) : dict_first(&queue->tree);
|
|
1511
|
+
if (!node) {
|
|
1512
|
+
read->empty = true;
|
|
1513
|
+
return Qnil;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/* Test and remove under the same logical lock: a timer consumer must not
|
|
1517
|
+
* pop a future entry if another consumer took the due one after a peek. */
|
|
1518
|
+
if (read->limited && priority_queue_compare(
|
|
1519
|
+
dnode_getkey(node), (const void *)read->latest_priority, queue) > 0) {
|
|
1520
|
+
read->empty = true;
|
|
1521
|
+
return Qnil;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
bucket = (priority_queue_bucket_t *)node;
|
|
1525
|
+
if (read->kind == PRIORITY_QUEUE_PEEK_PRIORITY) {
|
|
1526
|
+
read->result = (VALUE)dnode_getkey(node);
|
|
1527
|
+
return read->result;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
read->result = bucket->head->value;
|
|
1531
|
+
if (read->kind == PRIORITY_QUEUE_PEEK) return read->result;
|
|
1532
|
+
|
|
1533
|
+
entry = bucket->head;
|
|
1534
|
+
priority_queue_notify_before_commit(queue);
|
|
1535
|
+
priority_queue_identity_index_remove(bucket->identity_index, entry);
|
|
1536
|
+
priority_queue_bucket_unlink(bucket, entry);
|
|
1537
|
+
priority_queue_track_remove(queue, entry);
|
|
1538
|
+
free(entry);
|
|
1539
|
+
if (--queue->size == 0) {
|
|
1540
|
+
queue->numeric_only = true;
|
|
1541
|
+
if (queue->sealed) queue->closed = true;
|
|
1542
|
+
}
|
|
1543
|
+
if (bucket->size == 0) dict_delete_free(&queue->tree, node);
|
|
1544
|
+
return read->result;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
static VALUE
|
|
1548
|
+
priority_queue_read(VALUE self, priority_queue_read_kind_t kind, bool last)
|
|
1549
|
+
{
|
|
1550
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1551
|
+
priority_queue_read_t read = {
|
|
1552
|
+
.queue = queue,
|
|
1553
|
+
.kind = kind,
|
|
1554
|
+
.result = Qnil,
|
|
1555
|
+
.empty = false,
|
|
1556
|
+
.last = last,
|
|
1557
|
+
};
|
|
1558
|
+
if (priority_queue_can_run_direct(queue)) {
|
|
1559
|
+
priority_queue_read_body((VALUE)&read);
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
priority_queue_call(queue, priority_queue_read_body, (VALUE)&read);
|
|
1563
|
+
}
|
|
1564
|
+
if (read.empty) return rb_block_given_p() ? rb_yield_values(0) : Qnil;
|
|
1565
|
+
return read.result;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
static VALUE
|
|
1569
|
+
priority_queue_pop(VALUE self)
|
|
1570
|
+
{
|
|
1571
|
+
return priority_queue_read(self, PRIORITY_QUEUE_POP, false);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
static VALUE
|
|
1575
|
+
priority_queue_pop_before(VALUE self, VALUE latest_priority)
|
|
1576
|
+
{
|
|
1577
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1578
|
+
if (queue->shared) priority_queue_check_shareable(latest_priority);
|
|
1579
|
+
priority_queue_read_t read = {
|
|
1580
|
+
.queue = queue,
|
|
1581
|
+
.kind = PRIORITY_QUEUE_POP,
|
|
1582
|
+
.result = Qnil,
|
|
1583
|
+
.latest_priority = latest_priority,
|
|
1584
|
+
.limited = true,
|
|
1585
|
+
};
|
|
1586
|
+
bool direct = priority_queue_can_run_direct(queue);
|
|
1587
|
+
if (direct) {
|
|
1588
|
+
dnode_t *first = dict_first(&queue->tree);
|
|
1589
|
+
VALUE first_priority = first ? (VALUE)dnode_getkey(first) : Qnil;
|
|
1590
|
+
direct = !first ||
|
|
1591
|
+
(FIXNUM_P(latest_priority) && FIXNUM_P(first_priority)) ||
|
|
1592
|
+
(CLASS_OF(latest_priority) == rb_cFloat && !isnan(RFLOAT_VALUE(latest_priority)) &&
|
|
1593
|
+
CLASS_OF(first_priority) == rb_cFloat && !isnan(RFLOAT_VALUE(first_priority)));
|
|
1594
|
+
}
|
|
1595
|
+
if (direct) {
|
|
1596
|
+
priority_queue_read_body((VALUE)&read);
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
priority_queue_call(queue, priority_queue_read_body, (VALUE)&read);
|
|
1600
|
+
}
|
|
1601
|
+
if (read.empty) return rb_block_given_p() ? rb_yield_values(0) : Qnil;
|
|
1602
|
+
return read.result;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
static VALUE
|
|
1606
|
+
priority_queue_pop_last(VALUE self)
|
|
1607
|
+
{
|
|
1608
|
+
return priority_queue_read(self, PRIORITY_QUEUE_POP, true);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
static VALUE
|
|
1612
|
+
priority_queue_peek(VALUE self)
|
|
1613
|
+
{
|
|
1614
|
+
return priority_queue_read(self, PRIORITY_QUEUE_PEEK, false);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
static VALUE
|
|
1618
|
+
priority_queue_peek_last(VALUE self)
|
|
1619
|
+
{
|
|
1620
|
+
return priority_queue_read(self, PRIORITY_QUEUE_PEEK, true);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
static VALUE
|
|
1624
|
+
priority_queue_peek_priority(VALUE self)
|
|
1625
|
+
{
|
|
1626
|
+
return priority_queue_read(self, PRIORITY_QUEUE_PEEK_PRIORITY, false);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
static VALUE
|
|
1630
|
+
priority_queue_peek_last_priority(VALUE self)
|
|
1631
|
+
{
|
|
1632
|
+
return priority_queue_read(self, PRIORITY_QUEUE_PEEK_PRIORITY, true);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
typedef struct {
|
|
1636
|
+
priority_queue_t *queue;
|
|
1637
|
+
VALUE self;
|
|
1638
|
+
VALUE priority;
|
|
1639
|
+
VALUE value;
|
|
1640
|
+
bool match;
|
|
1641
|
+
} priority_queue_delete_t;
|
|
1642
|
+
|
|
1643
|
+
static VALUE
|
|
1644
|
+
priority_queue_delete_body(VALUE opaque)
|
|
1645
|
+
{
|
|
1646
|
+
priority_queue_delete_t *deletion = (priority_queue_delete_t *)opaque;
|
|
1647
|
+
priority_queue_t *queue = deletion->queue;
|
|
1648
|
+
dnode_t *node;
|
|
1649
|
+
priority_queue_bucket_t *bucket;
|
|
1650
|
+
priority_queue_value_t *entry;
|
|
1651
|
+
|
|
1652
|
+
if (queue->closed) priority_queue_raise_closed();
|
|
1653
|
+
node = dict_lookup(&queue->tree, (const void *)deletion->priority);
|
|
1654
|
+
if (!node) return Qfalse;
|
|
1655
|
+
bucket = (priority_queue_bucket_t *)node;
|
|
1656
|
+
|
|
1657
|
+
for (entry = bucket->head; entry; entry = entry->next) {
|
|
1658
|
+
VALUE matched = deletion->match
|
|
1659
|
+
? rb_funcall(deletion->value, id_case_equal, 1, entry->value)
|
|
1660
|
+
: priority_queue_call_ruby_callback(
|
|
1661
|
+
entry->value,
|
|
1662
|
+
deletion->value,
|
|
1663
|
+
true,
|
|
1664
|
+
NULL
|
|
1665
|
+
);
|
|
1666
|
+
if (!RTEST(matched)) continue;
|
|
1667
|
+
priority_queue_notify_before_commit(queue);
|
|
1668
|
+
priority_queue_identity_index_remove(bucket->identity_index, entry);
|
|
1669
|
+
priority_queue_bucket_unlink(bucket, entry);
|
|
1670
|
+
priority_queue_track_remove(queue, entry);
|
|
1671
|
+
free(entry);
|
|
1672
|
+
if (--queue->size == 0) {
|
|
1673
|
+
queue->numeric_only = true;
|
|
1674
|
+
if (queue->sealed) queue->closed = true;
|
|
1675
|
+
}
|
|
1676
|
+
if (bucket->size == 0) dict_delete_free(&queue->tree, node);
|
|
1677
|
+
return Qtrue;
|
|
1678
|
+
}
|
|
1679
|
+
return Qfalse;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
static VALUE
|
|
1683
|
+
priority_queue_delete(VALUE self, VALUE priority, VALUE value)
|
|
1684
|
+
{
|
|
1685
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1686
|
+
priority_queue_delete_t deletion = {
|
|
1687
|
+
.queue = queue,
|
|
1688
|
+
.self = self,
|
|
1689
|
+
.priority = priority,
|
|
1690
|
+
.value = value,
|
|
1691
|
+
};
|
|
1692
|
+
if (queue->shared) priority_queue_check_shareable(priority);
|
|
1693
|
+
if (queue->shared) priority_queue_check_shareable(value);
|
|
1694
|
+
return priority_queue_call(
|
|
1695
|
+
queue,
|
|
1696
|
+
priority_queue_delete_body,
|
|
1697
|
+
(VALUE)&deletion
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/* Match from the supplied pattern's side. Public queues can now store their
|
|
1702
|
+
* managed values directly and allocate a comparison probe only on deletion. */
|
|
1703
|
+
static VALUE
|
|
1704
|
+
priority_queue_delete_match(VALUE self, VALUE priority, VALUE pattern)
|
|
1705
|
+
{
|
|
1706
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1707
|
+
priority_queue_delete_t deletion = {
|
|
1708
|
+
.queue = queue,
|
|
1709
|
+
.self = self,
|
|
1710
|
+
.priority = priority,
|
|
1711
|
+
.value = pattern,
|
|
1712
|
+
.match = true,
|
|
1713
|
+
};
|
|
1714
|
+
if (queue->shared) priority_queue_check_shareable(priority);
|
|
1715
|
+
if (queue->shared) priority_queue_check_shareable(pattern);
|
|
1716
|
+
return priority_queue_call(queue, priority_queue_delete_body, (VALUE)&deletion);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
static VALUE
|
|
1720
|
+
priority_queue_delete_identity_body(VALUE opaque)
|
|
1721
|
+
{
|
|
1722
|
+
priority_queue_delete_t *deletion = (priority_queue_delete_t *)opaque;
|
|
1723
|
+
priority_queue_t *queue = deletion->queue;
|
|
1724
|
+
dnode_t *node;
|
|
1725
|
+
priority_queue_bucket_t *bucket;
|
|
1726
|
+
priority_queue_value_t *entry = NULL;
|
|
1727
|
+
|
|
1728
|
+
if (queue->closed) priority_queue_raise_closed();
|
|
1729
|
+
node = dict_lookup(&queue->tree, (const void *)deletion->priority);
|
|
1730
|
+
if (!node) return Qfalse;
|
|
1731
|
+
bucket = (priority_queue_bucket_t *)node;
|
|
1732
|
+
|
|
1733
|
+
if (!bucket->identity_index &&
|
|
1734
|
+
bucket->size >= PRIORITY_QUEUE_IDENTITY_INDEX_THRESHOLD) {
|
|
1735
|
+
priority_queue_identity_index_build(bucket);
|
|
1736
|
+
}
|
|
1737
|
+
if (bucket->identity_index) {
|
|
1738
|
+
priority_queue_identity_slot_t *slot = priority_queue_identity_index_lookup(
|
|
1739
|
+
bucket->identity_index,
|
|
1740
|
+
deletion->value
|
|
1741
|
+
);
|
|
1742
|
+
if (slot) entry = slot->head;
|
|
1743
|
+
}
|
|
1744
|
+
else {
|
|
1745
|
+
for (entry = bucket->head; entry; entry = entry->next) {
|
|
1746
|
+
if (entry->value == deletion->value) break;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
if (!entry) return Qfalse;
|
|
1751
|
+
priority_queue_notify_before_commit(queue);
|
|
1752
|
+
priority_queue_identity_index_remove(bucket->identity_index, entry);
|
|
1753
|
+
priority_queue_bucket_unlink(bucket, entry);
|
|
1754
|
+
priority_queue_track_remove(queue, entry);
|
|
1755
|
+
free(entry);
|
|
1756
|
+
if (--queue->size == 0) {
|
|
1757
|
+
queue->numeric_only = true;
|
|
1758
|
+
if (queue->sealed) queue->closed = true;
|
|
1759
|
+
}
|
|
1760
|
+
if (bucket->size == 0) dict_delete_free(&queue->tree, node);
|
|
1761
|
+
return Qtrue;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
static VALUE
|
|
1765
|
+
priority_queue_delete_identity(VALUE self, VALUE priority, VALUE value)
|
|
1766
|
+
{
|
|
1767
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1768
|
+
priority_queue_delete_t deletion = {
|
|
1769
|
+
.queue = queue,
|
|
1770
|
+
.self = self,
|
|
1771
|
+
.priority = priority,
|
|
1772
|
+
.value = value,
|
|
1773
|
+
};
|
|
1774
|
+
if (queue->shared) priority_queue_check_shareable(priority);
|
|
1775
|
+
if (queue->shared) priority_queue_check_shareable(value);
|
|
1776
|
+
return priority_queue_call(
|
|
1777
|
+
queue,
|
|
1778
|
+
priority_queue_delete_identity_body,
|
|
1779
|
+
(VALUE)&deletion
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
typedef struct {
|
|
1784
|
+
priority_queue_t *queue;
|
|
1785
|
+
VALUE self;
|
|
1786
|
+
} priority_queue_self_operation_t;
|
|
1787
|
+
|
|
1788
|
+
static VALUE
|
|
1789
|
+
priority_queue_size_body(VALUE opaque)
|
|
1790
|
+
{
|
|
1791
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1792
|
+
return SIZET2NUM(operation->queue->size);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
static VALUE
|
|
1796
|
+
priority_queue_size(VALUE self)
|
|
1797
|
+
{
|
|
1798
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1799
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1800
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_size_body((VALUE)&operation);
|
|
1801
|
+
return priority_queue_call(
|
|
1802
|
+
queue,
|
|
1803
|
+
priority_queue_size_body,
|
|
1804
|
+
(VALUE)&operation
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
static VALUE
|
|
1809
|
+
priority_queue_empty_body(VALUE opaque)
|
|
1810
|
+
{
|
|
1811
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1812
|
+
return operation->queue->size == 0 ? Qtrue : Qfalse;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
static VALUE
|
|
1816
|
+
priority_queue_empty(VALUE self)
|
|
1817
|
+
{
|
|
1818
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1819
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1820
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_empty_body((VALUE)&operation);
|
|
1821
|
+
return priority_queue_call(
|
|
1822
|
+
queue,
|
|
1823
|
+
priority_queue_empty_body,
|
|
1824
|
+
(VALUE)&operation
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
static VALUE
|
|
1829
|
+
priority_queue_clear_body(VALUE opaque)
|
|
1830
|
+
{
|
|
1831
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1832
|
+
priority_queue_notify_before_commit(operation->queue);
|
|
1833
|
+
dict_free_nodes(&operation->queue->tree);
|
|
1834
|
+
if (operation->queue->size > 0 && operation->queue->track_age) operation->queue->generation++;
|
|
1835
|
+
operation->queue->size = 0;
|
|
1836
|
+
operation->queue->age_head = NULL;
|
|
1837
|
+
operation->queue->age_tail = NULL;
|
|
1838
|
+
operation->queue->numeric_only = true;
|
|
1839
|
+
if (operation->queue->sealed) operation->queue->closed = true;
|
|
1840
|
+
return operation->self;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
static VALUE
|
|
1844
|
+
priority_queue_seal_body(VALUE opaque)
|
|
1845
|
+
{
|
|
1846
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1847
|
+
if (!operation->queue->sealed) {
|
|
1848
|
+
priority_queue_notify_before_commit(operation->queue);
|
|
1849
|
+
operation->queue->sealed = true;
|
|
1850
|
+
if (operation->queue->track_age) operation->queue->generation++;
|
|
1851
|
+
if (operation->queue->size == 0) operation->queue->closed = true;
|
|
1852
|
+
}
|
|
1853
|
+
return operation->self;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
static VALUE
|
|
1857
|
+
priority_queue_seal(VALUE self)
|
|
1858
|
+
{
|
|
1859
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1860
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1861
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_seal_body((VALUE)&operation);
|
|
1862
|
+
return priority_queue_call(queue, priority_queue_seal_body, (VALUE)&operation);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
static VALUE
|
|
1866
|
+
priority_queue_clear(VALUE self)
|
|
1867
|
+
{
|
|
1868
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1869
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1870
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_clear_body((VALUE)&operation);
|
|
1871
|
+
return priority_queue_call(
|
|
1872
|
+
queue,
|
|
1873
|
+
priority_queue_clear_body,
|
|
1874
|
+
(VALUE)&operation
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
static VALUE
|
|
1879
|
+
priority_queue_close_body(VALUE opaque)
|
|
1880
|
+
{
|
|
1881
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1882
|
+
priority_queue_notify_before_commit(operation->queue);
|
|
1883
|
+
if (!operation->queue->closed && operation->queue->track_age) operation->queue->generation++;
|
|
1884
|
+
operation->queue->sealed = true;
|
|
1885
|
+
operation->queue->closed = true;
|
|
1886
|
+
return operation->self;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
static VALUE
|
|
1890
|
+
priority_queue_close(VALUE self)
|
|
1891
|
+
{
|
|
1892
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1893
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1894
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_close_body((VALUE)&operation);
|
|
1895
|
+
return priority_queue_call(
|
|
1896
|
+
queue,
|
|
1897
|
+
priority_queue_close_body,
|
|
1898
|
+
(VALUE)&operation
|
|
1899
|
+
);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
static VALUE
|
|
1903
|
+
priority_queue_closed_body(VALUE opaque)
|
|
1904
|
+
{
|
|
1905
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1906
|
+
return operation->queue->closed ? Qtrue : Qfalse;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
static VALUE
|
|
1910
|
+
priority_queue_closed(VALUE self)
|
|
1911
|
+
{
|
|
1912
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1913
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1914
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_closed_body((VALUE)&operation);
|
|
1915
|
+
return priority_queue_call(
|
|
1916
|
+
queue,
|
|
1917
|
+
priority_queue_closed_body,
|
|
1918
|
+
(VALUE)&operation
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
static VALUE
|
|
1923
|
+
priority_queue_sealed_body(VALUE opaque)
|
|
1924
|
+
{
|
|
1925
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1926
|
+
return operation->queue->sealed ? Qtrue : Qfalse;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
static VALUE
|
|
1930
|
+
priority_queue_sealed(VALUE self)
|
|
1931
|
+
{
|
|
1932
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1933
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1934
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_sealed_body((VALUE)&operation);
|
|
1935
|
+
return priority_queue_call(queue, priority_queue_sealed_body, (VALUE)&operation);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
static VALUE
|
|
1939
|
+
priority_queue_age_tracking(VALUE self)
|
|
1940
|
+
{
|
|
1941
|
+
return priority_queue_get(self)->track_age ? Qtrue : Qfalse;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
static VALUE
|
|
1945
|
+
priority_queue_generation_body(VALUE opaque)
|
|
1946
|
+
{
|
|
1947
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1948
|
+
return ULL2NUM(operation->queue->generation);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
static VALUE
|
|
1952
|
+
priority_queue_generation(VALUE self)
|
|
1953
|
+
{
|
|
1954
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1955
|
+
if (!queue->track_age) return Qnil;
|
|
1956
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1957
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_generation_body((VALUE)&operation);
|
|
1958
|
+
return priority_queue_call(queue, priority_queue_generation_body, (VALUE)&operation);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
static VALUE
|
|
1962
|
+
priority_queue_oldest_body(VALUE opaque)
|
|
1963
|
+
{
|
|
1964
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1965
|
+
priority_queue_tracked_value_t *head = operation->queue->age_head;
|
|
1966
|
+
return head ? DBL2NUM(head->enqueued_at) : Qnil;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
static VALUE
|
|
1970
|
+
priority_queue_oldest_enqueued_at(VALUE self)
|
|
1971
|
+
{
|
|
1972
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1973
|
+
if (!queue->track_age) return Qnil;
|
|
1974
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1975
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_oldest_body((VALUE)&operation);
|
|
1976
|
+
return priority_queue_call(queue, priority_queue_oldest_body, (VALUE)&operation);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
static VALUE
|
|
1980
|
+
priority_queue_oldest_age_body(VALUE opaque)
|
|
1981
|
+
{
|
|
1982
|
+
priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
|
|
1983
|
+
priority_queue_tracked_value_t *head = operation->queue->age_head;
|
|
1984
|
+
return head ? DBL2NUM(priority_queue_monotonic_now() - head->enqueued_at) : Qnil;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
static VALUE
|
|
1988
|
+
priority_queue_oldest_age(VALUE self)
|
|
1989
|
+
{
|
|
1990
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
1991
|
+
if (!queue->track_age) return Qnil;
|
|
1992
|
+
priority_queue_self_operation_t operation = {.queue = queue, .self = self};
|
|
1993
|
+
if (priority_queue_can_run_direct(queue)) return priority_queue_oldest_age_body((VALUE)&operation);
|
|
1994
|
+
return priority_queue_call(queue, priority_queue_oldest_age_body, (VALUE)&operation);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
static VALUE
|
|
1998
|
+
priority_queue_capacity(VALUE self)
|
|
1999
|
+
{
|
|
2000
|
+
priority_queue_t *queue = priority_queue_get(self);
|
|
2001
|
+
return queue->bounded ? SIZET2NUM(queue->capacity) : Qnil;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
static void
|
|
2005
|
+
priority_queue_define_methods(VALUE klass)
|
|
2006
|
+
{
|
|
2007
|
+
rb_define_private_method(klass, "initialize", priority_queue_initialize, -1);
|
|
2008
|
+
rb_define_private_method(klass, "initialize_copy", priority_queue_initialize_copy, 1);
|
|
2009
|
+
rb_define_method(klass, "push", priority_queue_push, 2);
|
|
2010
|
+
rb_define_method(klass, "pop", priority_queue_pop, 0);
|
|
2011
|
+
rb_define_method(klass, "pop_before", priority_queue_pop_before, 1);
|
|
2012
|
+
rb_define_method(klass, "pop_last", priority_queue_pop_last, 0);
|
|
2013
|
+
rb_define_method(klass, "peek", priority_queue_peek, 0);
|
|
2014
|
+
rb_define_method(klass, "peek_last", priority_queue_peek_last, 0);
|
|
2015
|
+
rb_define_method(klass, "peek_priority", priority_queue_peek_priority, 0);
|
|
2016
|
+
rb_define_method(klass, "peek_last_priority", priority_queue_peek_last_priority, 0);
|
|
2017
|
+
rb_define_method(klass, "delete", priority_queue_delete, 2);
|
|
2018
|
+
rb_define_method(klass, "delete_match", priority_queue_delete_match, 2);
|
|
2019
|
+
rb_define_method(klass, "delete_identity", priority_queue_delete_identity, 2);
|
|
2020
|
+
rb_define_method(klass, "size", priority_queue_size, 0);
|
|
2021
|
+
rb_define_method(klass, "empty?", priority_queue_empty, 0);
|
|
2022
|
+
rb_define_method(klass, "clear", priority_queue_clear, 0);
|
|
2023
|
+
rb_define_method(klass, "close", priority_queue_close, 0);
|
|
2024
|
+
rb_define_method(klass, "seal", priority_queue_seal, 0);
|
|
2025
|
+
rb_define_method(klass, "closed?", priority_queue_closed, 0);
|
|
2026
|
+
rb_define_method(klass, "sealed?", priority_queue_sealed, 0);
|
|
2027
|
+
rb_define_method(klass, "age_tracking?", priority_queue_age_tracking, 0);
|
|
2028
|
+
rb_define_method(klass, "generation", priority_queue_generation, 0);
|
|
2029
|
+
rb_define_method(klass, "oldest_enqueued_at", priority_queue_oldest_enqueued_at, 0);
|
|
2030
|
+
rb_define_method(klass, "oldest_age", priority_queue_oldest_age, 0);
|
|
2031
|
+
rb_define_method(klass, "capacity", priority_queue_capacity, 0);
|
|
2032
|
+
rb_define_method(klass, "freeze", priority_queue_freeze, 0);
|
|
2033
|
+
rb_define_method(klass, "frozen?", priority_queue_frozen_p, 0);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
void
|
|
2037
|
+
containers_init_priority_queue(VALUE namespace)
|
|
2038
|
+
{
|
|
2039
|
+
ID id_ractor = rb_intern("Ractor");
|
|
2040
|
+
VALUE ractor;
|
|
2041
|
+
|
|
2042
|
+
id_compare = rb_intern("<=>");
|
|
2043
|
+
id_broadcast = rb_intern("broadcast");
|
|
2044
|
+
id_case_equal = rb_intern("===");
|
|
2045
|
+
id_farce = rb_intern("Farce");
|
|
2046
|
+
id_closed_error = rb_intern("ClosedQueueError");
|
|
2047
|
+
id_sealed_error = rb_intern("SealedQueueError");
|
|
2048
|
+
ractor = rb_const_get(rb_cObject, id_ractor);
|
|
2049
|
+
eIsolationError = rb_const_get(ractor, rb_intern("IsolationError"));
|
|
2050
|
+
|
|
2051
|
+
cPriorityQueue = rb_define_class_under(namespace, "PriorityQueue", rb_cObject);
|
|
2052
|
+
rb_define_alloc_func(cPriorityQueue, priority_queue_allocate);
|
|
2053
|
+
priority_queue_define_methods(cPriorityQueue);
|
|
2054
|
+
VALUE unshared = rb_define_class_under(namespace, "UnsharedPriorityQueue", cPriorityQueue);
|
|
2055
|
+
rb_define_alloc_func(unshared, unshared_priority_queue_allocate);
|
|
2056
|
+
}
|