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
data/ext/farce/map.c
ADDED
|
@@ -0,0 +1,2240 @@
|
|
|
1
|
+
#include "containers.h"
|
|
2
|
+
#include "transaction.h"
|
|
3
|
+
#include "ruby/fiber/scheduler.h"
|
|
4
|
+
#include "ruby/io.h"
|
|
5
|
+
|
|
6
|
+
#include <errno.h>
|
|
7
|
+
#include <fcntl.h>
|
|
8
|
+
#include <math.h>
|
|
9
|
+
#include <stdint.h>
|
|
10
|
+
#include <stdatomic.h>
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <time.h>
|
|
13
|
+
#include <unistd.h>
|
|
14
|
+
|
|
15
|
+
#define MAP_INITIAL_CAPACITY 16
|
|
16
|
+
|
|
17
|
+
static VALUE cMap;
|
|
18
|
+
static VALUE cUnsharedMap;
|
|
19
|
+
static VALUE map_keep;
|
|
20
|
+
static VALUE map_delete_token;
|
|
21
|
+
static VALUE cUnsharedKeyLockMap;
|
|
22
|
+
static VALUE cSharedKeyLockMap;
|
|
23
|
+
static VALUE cStrictMapFacade;
|
|
24
|
+
static VALUE cUnsharedMapFacade;
|
|
25
|
+
static VALUE cModeMapFacade;
|
|
26
|
+
static VALUE cMapEnvelope;
|
|
27
|
+
static VALUE map_nil_value;
|
|
28
|
+
static VALUE eIsolationError;
|
|
29
|
+
static const rb_data_type_t map_type;
|
|
30
|
+
static ID id_map;
|
|
31
|
+
static ID id_unwrap_value;
|
|
32
|
+
static ID id_wrap_value;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
typedef enum { MAP_EMPTY = 0, MAP_OCCUPIED = 1, MAP_TOMBSTONE = 2 } map_slot_state_t;
|
|
36
|
+
|
|
37
|
+
typedef struct {
|
|
38
|
+
VALUE key;
|
|
39
|
+
VALUE value;
|
|
40
|
+
st_index_t hash;
|
|
41
|
+
map_slot_state_t state;
|
|
42
|
+
} map_slot_t;
|
|
43
|
+
|
|
44
|
+
typedef struct map_waiter {
|
|
45
|
+
int read_fd;
|
|
46
|
+
int write_fd;
|
|
47
|
+
bool notified;
|
|
48
|
+
struct map_waiter *next;
|
|
49
|
+
} map_waiter_t;
|
|
50
|
+
|
|
51
|
+
typedef struct map_reservation {
|
|
52
|
+
VALUE key;
|
|
53
|
+
st_index_t hash;
|
|
54
|
+
VALUE fiber;
|
|
55
|
+
VALUE thread;
|
|
56
|
+
bool invalidated;
|
|
57
|
+
struct map_reservation *next;
|
|
58
|
+
} map_reservation_t;
|
|
59
|
+
|
|
60
|
+
typedef struct {
|
|
61
|
+
pthread_mutex_t lock;
|
|
62
|
+
map_slot_t *slots;
|
|
63
|
+
size_t capacity;
|
|
64
|
+
size_t size;
|
|
65
|
+
size_t tombstones;
|
|
66
|
+
uint64_t generation;
|
|
67
|
+
uint64_t iteration_epoch;
|
|
68
|
+
map_waiter_t *waiters;
|
|
69
|
+
bool compare_keys_by_identity;
|
|
70
|
+
bool compare_values_by_identity;
|
|
71
|
+
bool shareable;
|
|
72
|
+
uint8_t comparing;
|
|
73
|
+
VALUE comparing_owner;
|
|
74
|
+
VALUE comparing_thread;
|
|
75
|
+
map_reservation_t *reservations;
|
|
76
|
+
atomic_size_t reservation_count;
|
|
77
|
+
bool initialized;
|
|
78
|
+
} map_t;
|
|
79
|
+
|
|
80
|
+
enum { KEY_LOCK_UNINITIALIZED, KEY_LOCK_INITIALIZING, KEY_LOCK_PUBLISHING, KEY_LOCK_INITIALIZED };
|
|
81
|
+
|
|
82
|
+
/* Keep the map core first so its GC, waiter and reservation helpers can be
|
|
83
|
+
* reused without changing the public Map layout or publication protocol. */
|
|
84
|
+
typedef struct {
|
|
85
|
+
map_t core;
|
|
86
|
+
atomic_int state;
|
|
87
|
+
} key_lock_map_t;
|
|
88
|
+
|
|
89
|
+
typedef struct {
|
|
90
|
+
bool finite;
|
|
91
|
+
double deadline;
|
|
92
|
+
} map_timeout_t;
|
|
93
|
+
|
|
94
|
+
typedef struct {
|
|
95
|
+
VALUE fiber;
|
|
96
|
+
VALUE thread;
|
|
97
|
+
VALUE scheduler;
|
|
98
|
+
} map_execution_context_t;
|
|
99
|
+
|
|
100
|
+
/* Ruby's current Fiber/Thread accessors may allocate. Capture their values
|
|
101
|
+
* before taking a native mutex so a Ractor GC barrier can never wait on a
|
|
102
|
+
* peer that is blocked on that mutex. */
|
|
103
|
+
static map_execution_context_t
|
|
104
|
+
map_current_execution_context(void)
|
|
105
|
+
{
|
|
106
|
+
map_execution_context_t context = {
|
|
107
|
+
.fiber = rb_fiber_current(),
|
|
108
|
+
.thread = rb_thread_current(),
|
|
109
|
+
.scheduler = rb_fiber_scheduler_current(),
|
|
110
|
+
};
|
|
111
|
+
return context;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static double
|
|
115
|
+
map_monotonic_now(void)
|
|
116
|
+
{
|
|
117
|
+
struct timespec now;
|
|
118
|
+
#ifdef CLOCK_MONOTONIC
|
|
119
|
+
if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
|
|
120
|
+
return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
|
|
121
|
+
}
|
|
122
|
+
#endif
|
|
123
|
+
struct timeval fallback;
|
|
124
|
+
gettimeofday(&fallback, NULL);
|
|
125
|
+
return (double)fallback.tv_sec + (double)fallback.tv_usec / 1000000.0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static map_timeout_t
|
|
129
|
+
map_parse_timeout(VALUE timeout)
|
|
130
|
+
{
|
|
131
|
+
map_timeout_t parsed = {.finite = false, .deadline = 0};
|
|
132
|
+
if (NIL_P(timeout)) return parsed;
|
|
133
|
+
double seconds = NUM2DBL(timeout);
|
|
134
|
+
if (!isfinite(seconds) || seconds < 0) {
|
|
135
|
+
rb_raise(rb_eArgError, "timeout must be a finite, non-negative number or nil");
|
|
136
|
+
}
|
|
137
|
+
parsed.finite = true;
|
|
138
|
+
parsed.deadline = map_monotonic_now() + seconds;
|
|
139
|
+
return parsed;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static void
|
|
143
|
+
map_set_fd_flags(int fd)
|
|
144
|
+
{
|
|
145
|
+
#ifdef _WIN32
|
|
146
|
+
(void)fd;
|
|
147
|
+
#else
|
|
148
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
|
149
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
150
|
+
flags = fcntl(fd, F_GETFD, 0);
|
|
151
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
152
|
+
#endif
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static void
|
|
156
|
+
map_waiter_initialize(map_waiter_t *waiter)
|
|
157
|
+
{
|
|
158
|
+
int descriptors[2];
|
|
159
|
+
if (pipe(descriptors) != 0) rb_sys_fail("pipe");
|
|
160
|
+
waiter->read_fd = descriptors[0];
|
|
161
|
+
waiter->write_fd = descriptors[1];
|
|
162
|
+
waiter->notified = false;
|
|
163
|
+
waiter->next = NULL;
|
|
164
|
+
map_set_fd_flags(waiter->read_fd);
|
|
165
|
+
map_set_fd_flags(waiter->write_fd);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static void
|
|
169
|
+
map_notify_waiters_locked(map_t *map)
|
|
170
|
+
{
|
|
171
|
+
unsigned char byte = 1;
|
|
172
|
+
map->generation++;
|
|
173
|
+
for (map_waiter_t *waiter = map->waiters; waiter; waiter = waiter->next) {
|
|
174
|
+
if (waiter->notified) continue;
|
|
175
|
+
ssize_t result;
|
|
176
|
+
do {
|
|
177
|
+
result = write(waiter->write_fd, &byte, 1);
|
|
178
|
+
} while (result < 0 && errno == EINTR);
|
|
179
|
+
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
180
|
+
waiter->notified = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static void
|
|
186
|
+
map_mark(void *pointer)
|
|
187
|
+
{
|
|
188
|
+
map_t *map = pointer;
|
|
189
|
+
rb_gc_mark_movable(map->comparing_owner);
|
|
190
|
+
rb_gc_mark_movable(map->comparing_thread);
|
|
191
|
+
for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
|
|
192
|
+
rb_gc_mark_movable(reservation->key);
|
|
193
|
+
rb_gc_mark_movable(reservation->fiber);
|
|
194
|
+
rb_gc_mark_movable(reservation->thread);
|
|
195
|
+
}
|
|
196
|
+
if (!map->slots) return;
|
|
197
|
+
for (size_t index = 0; index < map->capacity; index++) {
|
|
198
|
+
if (map->slots[index].state == MAP_OCCUPIED) {
|
|
199
|
+
rb_gc_mark_movable(map->slots[index].key);
|
|
200
|
+
rb_gc_mark_movable(map->slots[index].value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static void
|
|
206
|
+
map_compact(void *pointer)
|
|
207
|
+
{
|
|
208
|
+
map_t *map = pointer;
|
|
209
|
+
map->comparing_owner = rb_gc_location(map->comparing_owner);
|
|
210
|
+
map->comparing_thread = rb_gc_location(map->comparing_thread);
|
|
211
|
+
for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
|
|
212
|
+
reservation->key = rb_gc_location(reservation->key);
|
|
213
|
+
reservation->fiber = rb_gc_location(reservation->fiber);
|
|
214
|
+
reservation->thread = rb_gc_location(reservation->thread);
|
|
215
|
+
}
|
|
216
|
+
if (!map->slots) return;
|
|
217
|
+
for (size_t index = 0; index < map->capacity; index++) {
|
|
218
|
+
if (map->slots[index].state == MAP_OCCUPIED) {
|
|
219
|
+
map->slots[index].key = rb_gc_location(map->slots[index].key);
|
|
220
|
+
map->slots[index].value = rb_gc_location(map->slots[index].value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static void
|
|
226
|
+
map_validate_references(VALUE self)
|
|
227
|
+
{
|
|
228
|
+
map_t *map;
|
|
229
|
+
TypedData_Get_Struct(self, map_t, &map_type, map);
|
|
230
|
+
for (size_t index = 0; index < map->capacity; index++) {
|
|
231
|
+
if (map->slots[index].state != MAP_OCCUPIED) continue;
|
|
232
|
+
containers_check_shareable(map->slots[index].key);
|
|
233
|
+
containers_check_shareable(map->slots[index].value);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
static void
|
|
238
|
+
map_free(void *pointer)
|
|
239
|
+
{
|
|
240
|
+
map_t *map = pointer;
|
|
241
|
+
pthread_mutex_destroy(&map->lock);
|
|
242
|
+
free(map->slots);
|
|
243
|
+
while (map->reservations) {
|
|
244
|
+
map_reservation_t *reservation = map->reservations;
|
|
245
|
+
map->reservations = reservation->next;
|
|
246
|
+
free(reservation);
|
|
247
|
+
}
|
|
248
|
+
ruby_xfree(map);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static size_t
|
|
252
|
+
map_memsize(const void *pointer)
|
|
253
|
+
{
|
|
254
|
+
const map_t *map = pointer;
|
|
255
|
+
if (!map) return 0;
|
|
256
|
+
size_t reservations = atomic_load_explicit(&map->reservation_count, memory_order_relaxed);
|
|
257
|
+
return sizeof(map_t) + map->capacity * sizeof(map_slot_t) + reservations * sizeof(map_reservation_t);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static const rb_data_type_t map_type = {
|
|
261
|
+
.wrap_struct_name = "Ractor::Containers::Map",
|
|
262
|
+
.function = {
|
|
263
|
+
.dmark = map_mark,
|
|
264
|
+
.dfree = map_free,
|
|
265
|
+
.dsize = map_memsize,
|
|
266
|
+
.dcompact = map_compact,
|
|
267
|
+
},
|
|
268
|
+
.flags = RUBY_TYPED_FROZEN_SHAREABLE,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
static const rb_data_type_t unshared_map_type = {
|
|
272
|
+
.wrap_struct_name = "Farce::Internal::UnsharedMap",
|
|
273
|
+
.function = {
|
|
274
|
+
.dmark = map_mark,
|
|
275
|
+
.dfree = map_free,
|
|
276
|
+
.dsize = map_memsize,
|
|
277
|
+
.dcompact = map_compact,
|
|
278
|
+
},
|
|
279
|
+
.parent = &map_type,
|
|
280
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
static size_t
|
|
284
|
+
key_lock_memsize(const void *pointer)
|
|
285
|
+
{
|
|
286
|
+
if (!pointer) return 0;
|
|
287
|
+
return map_memsize(pointer) + sizeof(key_lock_map_t) - sizeof(map_t);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* Local reservations may retain arbitrary Ruby keys. They must never use the
|
|
291
|
+
* frozen-shareable Map type, including while a loader is running. */
|
|
292
|
+
static const rb_data_type_t unshared_key_lock_type = {
|
|
293
|
+
.wrap_struct_name = "Farce::Internal::UnsharedKeyLockMap",
|
|
294
|
+
.function = {
|
|
295
|
+
.dmark = map_mark,
|
|
296
|
+
.dfree = map_free,
|
|
297
|
+
.dsize = key_lock_memsize,
|
|
298
|
+
.dcompact = map_compact,
|
|
299
|
+
},
|
|
300
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
static void
|
|
304
|
+
shared_key_lock_mark(void *pointer)
|
|
305
|
+
{
|
|
306
|
+
/* Reservations are transient synchronization state, not part of the
|
|
307
|
+
* shareability graph. Their keys are validated before insertion, while
|
|
308
|
+
* their Fiber and Thread owners are intentionally unshareable. Excluding
|
|
309
|
+
* them from non-GC reachability also keeps concurrent Ractor shareability
|
|
310
|
+
* traversals from walking a reservation that another Ractor can remove. */
|
|
311
|
+
if (rb_during_gc()) map_mark(pointer);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
static const rb_data_type_t shared_key_lock_type = {
|
|
315
|
+
.wrap_struct_name = "Farce::Internal::SharedKeyLockMap",
|
|
316
|
+
.function = {
|
|
317
|
+
.dmark = shared_key_lock_mark,
|
|
318
|
+
.dfree = map_free,
|
|
319
|
+
.dsize = key_lock_memsize,
|
|
320
|
+
.dcompact = map_compact,
|
|
321
|
+
},
|
|
322
|
+
.flags = RUBY_TYPED_FROZEN_SHAREABLE,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
static map_slot_t *
|
|
326
|
+
allocate_slots(size_t capacity)
|
|
327
|
+
{
|
|
328
|
+
map_slot_t *slots = calloc(capacity, sizeof(map_slot_t));
|
|
329
|
+
if (!slots) rb_memerror();
|
|
330
|
+
return slots;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
static void
|
|
334
|
+
map_initialize_storage(map_t *map)
|
|
335
|
+
{
|
|
336
|
+
pthread_mutex_init(&map->lock, NULL);
|
|
337
|
+
map->slots = NULL;
|
|
338
|
+
map->capacity = 0;
|
|
339
|
+
map->size = 0;
|
|
340
|
+
map->tombstones = 0;
|
|
341
|
+
map->generation = 0;
|
|
342
|
+
map->iteration_epoch = 0;
|
|
343
|
+
map->waiters = NULL;
|
|
344
|
+
map->compare_keys_by_identity = false;
|
|
345
|
+
map->compare_values_by_identity = false;
|
|
346
|
+
map->shareable = false;
|
|
347
|
+
map->comparing = false;
|
|
348
|
+
map->comparing_owner = Qnil;
|
|
349
|
+
map->comparing_thread = Qnil;
|
|
350
|
+
map->reservations = NULL;
|
|
351
|
+
atomic_init(&map->reservation_count, 0);
|
|
352
|
+
map->initialized = false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
static VALUE
|
|
356
|
+
map_allocate(VALUE klass)
|
|
357
|
+
{
|
|
358
|
+
map_t *map;
|
|
359
|
+
VALUE object = TypedData_Make_Struct(klass, map_t, &map_type, map);
|
|
360
|
+
map_initialize_storage(map);
|
|
361
|
+
map->shareable = true;
|
|
362
|
+
return object;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
static VALUE
|
|
366
|
+
unshared_map_allocate(VALUE klass)
|
|
367
|
+
{
|
|
368
|
+
map_t *map;
|
|
369
|
+
VALUE object = TypedData_Make_Struct(klass, map_t, &unshared_map_type, map);
|
|
370
|
+
map_initialize_storage(map);
|
|
371
|
+
VALUE guard = rb_path2class("Farce::Internal::Unshareable");
|
|
372
|
+
rb_funcall(guard, rb_intern("pin_to_current_ractor"), 1, object);
|
|
373
|
+
return object;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
static VALUE
|
|
377
|
+
key_lock_allocate(VALUE klass, const rb_data_type_t *type)
|
|
378
|
+
{
|
|
379
|
+
key_lock_map_t *map;
|
|
380
|
+
VALUE object = TypedData_Make_Struct(klass, key_lock_map_t, type, map);
|
|
381
|
+
map_initialize_storage(&map->core);
|
|
382
|
+
atomic_init(&map->state, KEY_LOCK_UNINITIALIZED);
|
|
383
|
+
return object;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
static VALUE
|
|
387
|
+
shared_key_lock_allocate(VALUE klass)
|
|
388
|
+
{
|
|
389
|
+
return key_lock_allocate(klass, &shared_key_lock_type);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
static VALUE
|
|
393
|
+
unshared_key_lock_allocate(VALUE klass)
|
|
394
|
+
{
|
|
395
|
+
VALUE self = key_lock_allocate(klass, &unshared_key_lock_type);
|
|
396
|
+
VALUE guard = rb_path2class("Farce::Internal::Unshareable");
|
|
397
|
+
rb_funcall(guard, rb_intern("pin_to_current_ractor"), 1, self);
|
|
398
|
+
return self;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
static map_t *
|
|
402
|
+
get_map(VALUE self)
|
|
403
|
+
{
|
|
404
|
+
map_t *map;
|
|
405
|
+
TypedData_Get_Struct(self, map_t, &map_type, map);
|
|
406
|
+
if (!map->initialized) rb_raise(rb_eRuntimeError, "uninitialized Map");
|
|
407
|
+
return map;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
typedef struct {
|
|
411
|
+
map_t *map;
|
|
412
|
+
map_waiter_t *waiter;
|
|
413
|
+
map_timeout_t *timeout;
|
|
414
|
+
bool registered;
|
|
415
|
+
} map_wait_context_t;
|
|
416
|
+
|
|
417
|
+
static VALUE
|
|
418
|
+
map_wait_body(VALUE opaque)
|
|
419
|
+
{
|
|
420
|
+
map_wait_context_t *context = (map_wait_context_t *)opaque;
|
|
421
|
+
VALUE wait_timeout = Qnil;
|
|
422
|
+
if (context->timeout->finite) {
|
|
423
|
+
double remaining = context->timeout->deadline - map_monotonic_now();
|
|
424
|
+
if (remaining <= 0) return Qfalse;
|
|
425
|
+
wait_timeout = DBL2NUM(remaining);
|
|
426
|
+
}
|
|
427
|
+
return containers_wait_for_readable(context->waiter->read_fd, wait_timeout) ? Qtrue : Qfalse;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
static VALUE
|
|
431
|
+
map_wait_cleanup(VALUE opaque)
|
|
432
|
+
{
|
|
433
|
+
map_wait_context_t *context = (map_wait_context_t *)opaque;
|
|
434
|
+
if (context->registered) {
|
|
435
|
+
pthread_mutex_lock(&context->map->lock);
|
|
436
|
+
map_waiter_t **cursor = &context->map->waiters;
|
|
437
|
+
while (*cursor && *cursor != context->waiter) cursor = &(*cursor)->next;
|
|
438
|
+
if (*cursor) *cursor = context->waiter->next;
|
|
439
|
+
pthread_mutex_unlock(&context->map->lock);
|
|
440
|
+
context->registered = false;
|
|
441
|
+
}
|
|
442
|
+
close(context->waiter->read_fd);
|
|
443
|
+
close(context->waiter->write_fd);
|
|
444
|
+
return Qnil;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
static bool
|
|
448
|
+
map_wait_once(map_t *map, uint64_t generation, map_timeout_t *timeout)
|
|
449
|
+
{
|
|
450
|
+
map_waiter_t waiter;
|
|
451
|
+
map_waiter_initialize(&waiter);
|
|
452
|
+
|
|
453
|
+
pthread_mutex_lock(&map->lock);
|
|
454
|
+
if (map->generation != generation) {
|
|
455
|
+
pthread_mutex_unlock(&map->lock);
|
|
456
|
+
close(waiter.read_fd);
|
|
457
|
+
close(waiter.write_fd);
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
waiter.next = map->waiters;
|
|
461
|
+
map->waiters = &waiter;
|
|
462
|
+
pthread_mutex_unlock(&map->lock);
|
|
463
|
+
|
|
464
|
+
map_wait_context_t context = {
|
|
465
|
+
.map = map,
|
|
466
|
+
.waiter = &waiter,
|
|
467
|
+
.timeout = timeout,
|
|
468
|
+
.registered = true,
|
|
469
|
+
};
|
|
470
|
+
return RTEST(rb_ensure(map_wait_body, (VALUE)&context, map_wait_cleanup, (VALUE)&context));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* Acquire the short native state mutex, waiting outside the runtime lock if a
|
|
474
|
+
* key equality callback currently owns the map's logical critical section.
|
|
475
|
+
* The common path is one branch after pthread_mutex_lock. */
|
|
476
|
+
static void
|
|
477
|
+
map_lock_state(map_t *map, const map_execution_context_t *context)
|
|
478
|
+
{
|
|
479
|
+
map_timeout_t timeout = {.finite = false, .deadline = 0};
|
|
480
|
+
|
|
481
|
+
for (;;) {
|
|
482
|
+
pthread_mutex_lock(&map->lock);
|
|
483
|
+
if (!map->comparing) return;
|
|
484
|
+
|
|
485
|
+
if (map->comparing_owner == context->fiber) {
|
|
486
|
+
pthread_mutex_unlock(&map->lock);
|
|
487
|
+
rb_raise(rb_eThreadError, "recursive map access from key equality");
|
|
488
|
+
}
|
|
489
|
+
if (map->comparing_thread == context->thread && NIL_P(context->scheduler)) {
|
|
490
|
+
pthread_mutex_unlock(&map->lock);
|
|
491
|
+
rb_raise(
|
|
492
|
+
rb_eThreadError,
|
|
493
|
+
"deadlock; map key equality is owned by another unscheduled fiber"
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
uint64_t generation = map->generation;
|
|
498
|
+
pthread_mutex_unlock(&map->lock);
|
|
499
|
+
(void)map_wait_once(map, generation, &timeout);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
static VALUE
|
|
504
|
+
map_timeout_result(void)
|
|
505
|
+
{
|
|
506
|
+
return rb_block_given_p() ? rb_yield_values(0) : Qnil;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
static st_index_t
|
|
510
|
+
map_key_hash(map_t *map, VALUE key)
|
|
511
|
+
{
|
|
512
|
+
if (map->compare_keys_by_identity) {
|
|
513
|
+
return (st_index_t)NUM2ULL(rb_obj_id(key));
|
|
514
|
+
}
|
|
515
|
+
return (st_index_t)NUM2LONG(rb_hash(key));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
typedef struct {
|
|
519
|
+
VALUE left;
|
|
520
|
+
VALUE right;
|
|
521
|
+
} map_eql_arguments_t;
|
|
522
|
+
|
|
523
|
+
static VALUE
|
|
524
|
+
map_eql_protected(VALUE opaque)
|
|
525
|
+
{
|
|
526
|
+
map_eql_arguments_t *arguments = (map_eql_arguments_t *)opaque;
|
|
527
|
+
return rb_eql(arguments->left, arguments->right) ? Qtrue : Qfalse;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* Called with map->lock held and returns with it held unless equality raises.
|
|
531
|
+
* The logical comparison gate preserves the original operation-wide
|
|
532
|
+
* exclusion while rb_io_wait lets other Threads/Ractors/Fibers park without
|
|
533
|
+
* starving the Ruby callback. */
|
|
534
|
+
static bool
|
|
535
|
+
map_keys_equal(
|
|
536
|
+
map_t *map,
|
|
537
|
+
VALUE left,
|
|
538
|
+
VALUE right,
|
|
539
|
+
const map_execution_context_t *context
|
|
540
|
+
)
|
|
541
|
+
{
|
|
542
|
+
if (left == right) return true;
|
|
543
|
+
if (map->compare_keys_by_identity) return left == right;
|
|
544
|
+
|
|
545
|
+
map_eql_arguments_t arguments = {.left = left, .right = right};
|
|
546
|
+
int state = 0;
|
|
547
|
+
|
|
548
|
+
map->comparing = true;
|
|
549
|
+
map->comparing_owner = context->fiber;
|
|
550
|
+
map->comparing_thread = context->thread;
|
|
551
|
+
pthread_mutex_unlock(&map->lock);
|
|
552
|
+
|
|
553
|
+
VALUE result = rb_protect(map_eql_protected, (VALUE)&arguments, &state);
|
|
554
|
+
|
|
555
|
+
pthread_mutex_lock(&map->lock);
|
|
556
|
+
map->comparing = false;
|
|
557
|
+
map->comparing_owner = Qnil;
|
|
558
|
+
map->comparing_thread = Qnil;
|
|
559
|
+
map_notify_waiters_locked(map);
|
|
560
|
+
if (state) {
|
|
561
|
+
pthread_mutex_unlock(&map->lock);
|
|
562
|
+
rb_jump_tag(state);
|
|
563
|
+
}
|
|
564
|
+
return RTEST(result);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
static size_t
|
|
568
|
+
map_find_slot(
|
|
569
|
+
map_t *map,
|
|
570
|
+
VALUE key,
|
|
571
|
+
st_index_t hash,
|
|
572
|
+
bool *found,
|
|
573
|
+
const map_execution_context_t *context
|
|
574
|
+
)
|
|
575
|
+
{
|
|
576
|
+
size_t mask = map->capacity - 1;
|
|
577
|
+
size_t first_tombstone = SIZE_MAX;
|
|
578
|
+
|
|
579
|
+
for (size_t offset = 0; offset < map->capacity; offset++) {
|
|
580
|
+
size_t index = ((size_t)hash + offset) & mask;
|
|
581
|
+
map_slot_t *slot = &map->slots[index];
|
|
582
|
+
|
|
583
|
+
if (slot->state == MAP_EMPTY) {
|
|
584
|
+
*found = false;
|
|
585
|
+
return first_tombstone == SIZE_MAX ? index : first_tombstone;
|
|
586
|
+
}
|
|
587
|
+
if (slot->state == MAP_TOMBSTONE) {
|
|
588
|
+
if (first_tombstone == SIZE_MAX) first_tombstone = index;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (slot->hash == hash && map_keys_equal(map, slot->key, key, context)) {
|
|
592
|
+
*found = true;
|
|
593
|
+
return index;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
*found = false;
|
|
598
|
+
return first_tombstone;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* Resolve lookups that cannot call user equality code. This lets the common
|
|
602
|
+
* same-object hit avoid capturing Fiber and Thread state. Return false when
|
|
603
|
+
* an equal-hash key requires the full callback-safe comparison protocol. */
|
|
604
|
+
static bool
|
|
605
|
+
map_find_slot_without_equality(
|
|
606
|
+
map_t *map,
|
|
607
|
+
VALUE key,
|
|
608
|
+
st_index_t hash,
|
|
609
|
+
size_t *index,
|
|
610
|
+
bool *found
|
|
611
|
+
)
|
|
612
|
+
{
|
|
613
|
+
size_t mask = map->capacity - 1;
|
|
614
|
+
size_t first_tombstone = SIZE_MAX;
|
|
615
|
+
|
|
616
|
+
for (size_t offset = 0; offset < map->capacity; offset++) {
|
|
617
|
+
size_t candidate = ((size_t)hash + offset) & mask;
|
|
618
|
+
map_slot_t *slot = &map->slots[candidate];
|
|
619
|
+
|
|
620
|
+
if (slot->state == MAP_EMPTY) {
|
|
621
|
+
*index = first_tombstone == SIZE_MAX ? candidate : first_tombstone;
|
|
622
|
+
*found = false;
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
if (slot->state == MAP_TOMBSTONE) {
|
|
626
|
+
if (first_tombstone == SIZE_MAX) first_tombstone = candidate;
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (slot->hash != hash) continue;
|
|
630
|
+
if (slot->key == key) {
|
|
631
|
+
*index = candidate;
|
|
632
|
+
*found = true;
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
if (map->compare_keys_by_identity) continue;
|
|
636
|
+
/* CRuby's rb_eql uses built-in comparison for exact Strings. Keep
|
|
637
|
+
* subclasses and singleton classes on the callback-safe path. */
|
|
638
|
+
if (CLASS_OF(slot->key) == rb_cString && RB_TYPE_P(key, T_STRING)) {
|
|
639
|
+
if (RTEST(rb_str_equal(slot->key, key))) {
|
|
640
|
+
*index = candidate;
|
|
641
|
+
*found = true;
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
*index = first_tombstone;
|
|
650
|
+
*found = false;
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/* Reservations use the same hash and equality rules as stored entries. They
|
|
655
|
+
* are kept separately so a missing key remains missing while user code runs. */
|
|
656
|
+
static map_reservation_t *
|
|
657
|
+
map_find_reservation_locked(
|
|
658
|
+
map_t *map,
|
|
659
|
+
VALUE key,
|
|
660
|
+
st_index_t hash,
|
|
661
|
+
const map_execution_context_t *context
|
|
662
|
+
)
|
|
663
|
+
{
|
|
664
|
+
for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
|
|
665
|
+
if (!reservation->invalidated && reservation->hash == hash &&
|
|
666
|
+
map_keys_equal(map, reservation->key, key, context)) {
|
|
667
|
+
return reservation;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return NULL;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
static void
|
|
674
|
+
map_check_reservation_wait_locked(
|
|
675
|
+
map_t *map,
|
|
676
|
+
map_reservation_t *reservation,
|
|
677
|
+
const map_execution_context_t *context
|
|
678
|
+
)
|
|
679
|
+
{
|
|
680
|
+
if (reservation->fiber == context->fiber) {
|
|
681
|
+
pthread_mutex_unlock(&map->lock);
|
|
682
|
+
rb_raise(rb_eThreadError, "deadlock; recursive map access during an update");
|
|
683
|
+
}
|
|
684
|
+
if (reservation->thread == context->thread && NIL_P(context->scheduler)) {
|
|
685
|
+
pthread_mutex_unlock(&map->lock);
|
|
686
|
+
rb_raise(
|
|
687
|
+
rb_eThreadError,
|
|
688
|
+
"deadlock; map update is owned by another unscheduled fiber"
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
static bool
|
|
694
|
+
map_lock_for_key_with_timeout(
|
|
695
|
+
map_t *map,
|
|
696
|
+
VALUE key,
|
|
697
|
+
st_index_t hash,
|
|
698
|
+
map_timeout_t *timeout,
|
|
699
|
+
const map_execution_context_t *context
|
|
700
|
+
)
|
|
701
|
+
{
|
|
702
|
+
for (;;) {
|
|
703
|
+
map_lock_state(map, context);
|
|
704
|
+
map_reservation_t *reservation = map_find_reservation_locked(map, key, hash, context);
|
|
705
|
+
if (!reservation) return true;
|
|
706
|
+
map_check_reservation_wait_locked(map, reservation, context);
|
|
707
|
+
uint64_t generation = map->generation;
|
|
708
|
+
pthread_mutex_unlock(&map->lock);
|
|
709
|
+
if (!map_wait_once(map, generation, timeout)) return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
static void
|
|
714
|
+
map_lock_for_key(
|
|
715
|
+
map_t *map,
|
|
716
|
+
VALUE key,
|
|
717
|
+
st_index_t hash,
|
|
718
|
+
const map_execution_context_t *context
|
|
719
|
+
)
|
|
720
|
+
{
|
|
721
|
+
map_timeout_t timeout = {.finite = false, .deadline = 0};
|
|
722
|
+
(void)map_lock_for_key_with_timeout(map, key, hash, &timeout, context);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
static map_reservation_t *
|
|
726
|
+
map_begin_reservation_locked(
|
|
727
|
+
map_t *map,
|
|
728
|
+
VALUE key,
|
|
729
|
+
st_index_t hash,
|
|
730
|
+
const map_execution_context_t *context
|
|
731
|
+
)
|
|
732
|
+
{
|
|
733
|
+
map_reservation_t *reservation = malloc(sizeof(map_reservation_t));
|
|
734
|
+
if (!reservation) {
|
|
735
|
+
pthread_mutex_unlock(&map->lock);
|
|
736
|
+
rb_memerror();
|
|
737
|
+
}
|
|
738
|
+
reservation->key = key;
|
|
739
|
+
reservation->hash = hash;
|
|
740
|
+
reservation->fiber = context->fiber;
|
|
741
|
+
reservation->thread = context->thread;
|
|
742
|
+
reservation->invalidated = false;
|
|
743
|
+
reservation->next = map->reservations;
|
|
744
|
+
map->reservations = reservation;
|
|
745
|
+
atomic_fetch_add_explicit(&map->reservation_count, 1, memory_order_relaxed);
|
|
746
|
+
return reservation;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
static void
|
|
750
|
+
map_finish_reservation_locked(map_t *map, map_reservation_t *reservation)
|
|
751
|
+
{
|
|
752
|
+
map_reservation_t **cursor = &map->reservations;
|
|
753
|
+
while (*cursor && *cursor != reservation) cursor = &(*cursor)->next;
|
|
754
|
+
if (*cursor) {
|
|
755
|
+
*cursor = reservation->next;
|
|
756
|
+
atomic_fetch_sub_explicit(&map->reservation_count, 1, memory_order_relaxed);
|
|
757
|
+
}
|
|
758
|
+
map_notify_waiters_locked(map);
|
|
759
|
+
free(reservation);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
static void
|
|
763
|
+
map_invalidate_reservations_locked(map_t *map)
|
|
764
|
+
{
|
|
765
|
+
for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
|
|
766
|
+
reservation->invalidated = true;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
static void
|
|
771
|
+
map_resize(map_t *map, size_t new_capacity)
|
|
772
|
+
{
|
|
773
|
+
map->iteration_epoch++;
|
|
774
|
+
map_slot_t *old_slots = map->slots;
|
|
775
|
+
size_t old_capacity = map->capacity;
|
|
776
|
+
map_slot_t *new_slots = calloc(new_capacity, sizeof(map_slot_t));
|
|
777
|
+
if (!new_slots) {
|
|
778
|
+
pthread_mutex_unlock(&map->lock);
|
|
779
|
+
rb_memerror();
|
|
780
|
+
}
|
|
781
|
+
map->slots = new_slots;
|
|
782
|
+
map->capacity = new_capacity;
|
|
783
|
+
map->size = 0;
|
|
784
|
+
map->tombstones = 0;
|
|
785
|
+
|
|
786
|
+
for (size_t index = 0; index < old_capacity; index++) {
|
|
787
|
+
map_slot_t *old = &old_slots[index];
|
|
788
|
+
if (old->state == MAP_OCCUPIED) {
|
|
789
|
+
size_t mask = map->capacity - 1;
|
|
790
|
+
size_t target = (size_t)old->hash & mask;
|
|
791
|
+
while (map->slots[target].state == MAP_OCCUPIED) {
|
|
792
|
+
target = (target + 1) & mask;
|
|
793
|
+
}
|
|
794
|
+
map->slots[target] = *old;
|
|
795
|
+
map->size++;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
free(old_slots);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
static void
|
|
802
|
+
map_prepare_insert(map_t *map)
|
|
803
|
+
{
|
|
804
|
+
if ((map->size + map->tombstones + 1) * 10 >= map->capacity * 7) {
|
|
805
|
+
map_resize(map, map->capacity * 2);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
static VALUE
|
|
810
|
+
map_store_locked(
|
|
811
|
+
VALUE self,
|
|
812
|
+
map_t *map,
|
|
813
|
+
VALUE key,
|
|
814
|
+
VALUE value,
|
|
815
|
+
st_index_t hash,
|
|
816
|
+
const map_execution_context_t *context
|
|
817
|
+
)
|
|
818
|
+
{
|
|
819
|
+
bool found;
|
|
820
|
+
map_prepare_insert(map);
|
|
821
|
+
size_t index = map_find_slot(map, key, hash, &found, context);
|
|
822
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
823
|
+
pthread_mutex_unlock(&map->lock);
|
|
824
|
+
rb_check_frozen(self);
|
|
825
|
+
}
|
|
826
|
+
map_slot_t *slot = &map->slots[index];
|
|
827
|
+
if (!found) {
|
|
828
|
+
if (slot->state == MAP_TOMBSTONE) map->tombstones--;
|
|
829
|
+
slot->key = key;
|
|
830
|
+
slot->hash = hash;
|
|
831
|
+
slot->state = MAP_OCCUPIED;
|
|
832
|
+
map->size++;
|
|
833
|
+
map->iteration_epoch++;
|
|
834
|
+
}
|
|
835
|
+
slot->value = value;
|
|
836
|
+
map_notify_waiters_locked(map);
|
|
837
|
+
return value;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
static VALUE
|
|
841
|
+
map_normalize_key(map_t *map, VALUE key)
|
|
842
|
+
{
|
|
843
|
+
if (!map->compare_keys_by_identity && RB_TYPE_P(key, T_STRING) && !RB_OBJ_FROZEN(key)) {
|
|
844
|
+
key = containers_normalize_string_key(key);
|
|
845
|
+
}
|
|
846
|
+
return key;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
static VALUE
|
|
850
|
+
map_prepare_key(map_t *map, VALUE key)
|
|
851
|
+
{
|
|
852
|
+
key = map_normalize_key(map, key);
|
|
853
|
+
if (map->shareable) containers_check_shareable(key);
|
|
854
|
+
return key;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
static void
|
|
858
|
+
map_check_value(map_t *map, VALUE value)
|
|
859
|
+
{
|
|
860
|
+
if (map->shareable) containers_check_shareable(value);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
typedef struct {
|
|
864
|
+
VALUE self;
|
|
865
|
+
map_t *map;
|
|
866
|
+
map_execution_context_t execution;
|
|
867
|
+
} map_init_context_t;
|
|
868
|
+
|
|
869
|
+
static int
|
|
870
|
+
map_initialize_entry(VALUE key, VALUE value, VALUE opaque)
|
|
871
|
+
{
|
|
872
|
+
map_init_context_t *context = (map_init_context_t *)opaque;
|
|
873
|
+
key = map_prepare_key(context->map, key);
|
|
874
|
+
map_check_value(context->map, value);
|
|
875
|
+
st_index_t hash = map_key_hash(context->map, key);
|
|
876
|
+
map_lock_state(context->map, &context->execution);
|
|
877
|
+
map_store_locked(context->self, context->map, key, value, hash, &context->execution);
|
|
878
|
+
pthread_mutex_unlock(&context->map->lock);
|
|
879
|
+
return ST_CONTINUE;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
static VALUE
|
|
883
|
+
map_initialize(int argc, VALUE *argv, VALUE self)
|
|
884
|
+
{
|
|
885
|
+
VALUE mapping = Qnil;
|
|
886
|
+
VALUE keywords = Qnil;
|
|
887
|
+
ID keyword_ids[] = {
|
|
888
|
+
rb_intern("compare_by_identity"),
|
|
889
|
+
rb_intern("compare_keys_by_identity"),
|
|
890
|
+
rb_intern("compare_values_by_identity")
|
|
891
|
+
};
|
|
892
|
+
VALUE keyword_values[3] = {Qundef, Qundef, Qundef};
|
|
893
|
+
map_t *map;
|
|
894
|
+
|
|
895
|
+
rb_scan_args(argc, argv, "01:", &mapping, &keywords);
|
|
896
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 3, keyword_values);
|
|
897
|
+
TypedData_Get_Struct(self, map_t, &map_type, map);
|
|
898
|
+
if (map->initialized) rb_raise(rb_eRuntimeError, "Map is already initialized");
|
|
899
|
+
|
|
900
|
+
bool common = keyword_values[0] == Qundef
|
|
901
|
+
? false
|
|
902
|
+
: containers_strict_bool(keyword_values[0], "compare_by_identity");
|
|
903
|
+
map->compare_keys_by_identity = keyword_values[1] == Qundef
|
|
904
|
+
? common
|
|
905
|
+
: containers_strict_bool(keyword_values[1], "compare_keys_by_identity");
|
|
906
|
+
map->compare_values_by_identity = keyword_values[2] == Qundef
|
|
907
|
+
? common
|
|
908
|
+
: containers_strict_bool(keyword_values[2], "compare_values_by_identity");
|
|
909
|
+
map->capacity = MAP_INITIAL_CAPACITY;
|
|
910
|
+
map->slots = allocate_slots(map->capacity);
|
|
911
|
+
|
|
912
|
+
if (!NIL_P(mapping)) {
|
|
913
|
+
Check_Type(mapping, T_HASH);
|
|
914
|
+
map_init_context_t context = {
|
|
915
|
+
.self = self,
|
|
916
|
+
.map = map,
|
|
917
|
+
.execution = map_current_execution_context(),
|
|
918
|
+
};
|
|
919
|
+
rb_hash_foreach(mapping, map_initialize_entry, (VALUE)&context);
|
|
920
|
+
}
|
|
921
|
+
map->initialized = true;
|
|
922
|
+
if (map->shareable) containers_publish_native_with_references(self, map_validate_references);
|
|
923
|
+
return self;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
static VALUE
|
|
927
|
+
map_get(VALUE self, VALUE key)
|
|
928
|
+
{
|
|
929
|
+
map_t *map = get_map(self);
|
|
930
|
+
key = map_prepare_key(map, key);
|
|
931
|
+
st_index_t hash = map_key_hash(map, key);
|
|
932
|
+
bool found;
|
|
933
|
+
VALUE result = Qnil;
|
|
934
|
+
size_t index;
|
|
935
|
+
|
|
936
|
+
pthread_mutex_lock(&map->lock);
|
|
937
|
+
if (!map->comparing && map_find_slot_without_equality(map, key, hash, &index, &found)) {
|
|
938
|
+
if (found) result = map->slots[index].value;
|
|
939
|
+
pthread_mutex_unlock(&map->lock);
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
pthread_mutex_unlock(&map->lock);
|
|
943
|
+
|
|
944
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
945
|
+
map_lock_state(map, &execution);
|
|
946
|
+
index = map_find_slot(map, key, hash, &found, &execution);
|
|
947
|
+
if (found) result = map->slots[index].value;
|
|
948
|
+
pthread_mutex_unlock(&map->lock);
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
static VALUE
|
|
953
|
+
map_check_mutation(VALUE self)
|
|
954
|
+
{
|
|
955
|
+
rb_check_frozen(self);
|
|
956
|
+
return self;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
static VALUE
|
|
960
|
+
map_prepare_mutation_key(VALUE self, VALUE key)
|
|
961
|
+
{
|
|
962
|
+
rb_check_frozen(self);
|
|
963
|
+
map_t *map = get_map(self);
|
|
964
|
+
key = map_normalize_key(map, key);
|
|
965
|
+
if (map->shareable && !rb_ractor_shareable_p(key)) {
|
|
966
|
+
rb_raise(eIsolationError, "key must be Ractor-shareable");
|
|
967
|
+
}
|
|
968
|
+
return key;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
static VALUE
|
|
972
|
+
map_fetch(int argc, VALUE *argv, VALUE self)
|
|
973
|
+
{
|
|
974
|
+
VALUE key;
|
|
975
|
+
VALUE default_value;
|
|
976
|
+
rb_scan_args(argc, argv, "11", &key, &default_value);
|
|
977
|
+
|
|
978
|
+
bool default_given = argc == 2;
|
|
979
|
+
bool block_given = rb_block_given_p();
|
|
980
|
+
if (block_given && default_given) {
|
|
981
|
+
rb_warn("block supersedes default value argument");
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
VALUE original_key = key;
|
|
985
|
+
map_t *map = get_map(self);
|
|
986
|
+
key = map_prepare_key(map, key);
|
|
987
|
+
st_index_t hash = map_key_hash(map, key);
|
|
988
|
+
bool found;
|
|
989
|
+
VALUE result = Qnil;
|
|
990
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
991
|
+
map_lock_state(map, &execution);
|
|
992
|
+
size_t index = map_find_slot(map, key, hash, &found, &execution);
|
|
993
|
+
if (found) result = map->slots[index].value;
|
|
994
|
+
pthread_mutex_unlock(&map->lock);
|
|
995
|
+
|
|
996
|
+
if (found) return result;
|
|
997
|
+
if (block_given) return rb_yield(original_key);
|
|
998
|
+
if (default_given) return default_value;
|
|
999
|
+
containers_raise_key_error(self, original_key);
|
|
1000
|
+
return Qnil;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
static VALUE
|
|
1004
|
+
map_set(VALUE self, VALUE key, VALUE value)
|
|
1005
|
+
{
|
|
1006
|
+
rb_check_frozen(self);
|
|
1007
|
+
map_t *map = get_map(self);
|
|
1008
|
+
key = map_prepare_key(map, key);
|
|
1009
|
+
map_check_value(map, value);
|
|
1010
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1011
|
+
rb_check_frozen(self);
|
|
1012
|
+
|
|
1013
|
+
/* Replacing an existing entry needs neither allocation nor execution
|
|
1014
|
+
* context when no callback or atomic update owns the map. Keep the
|
|
1015
|
+
* callback/reservation path below for all other cases. */
|
|
1016
|
+
bool found;
|
|
1017
|
+
size_t index;
|
|
1018
|
+
pthread_mutex_lock(&map->lock);
|
|
1019
|
+
if (!map->comparing && !map->reservations &&
|
|
1020
|
+
map_find_slot_without_equality(map, key, hash, &index, &found) && found) {
|
|
1021
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1022
|
+
pthread_mutex_unlock(&map->lock);
|
|
1023
|
+
rb_check_frozen(self);
|
|
1024
|
+
}
|
|
1025
|
+
map->slots[index].value = value;
|
|
1026
|
+
map_notify_waiters_locked(map);
|
|
1027
|
+
pthread_mutex_unlock(&map->lock);
|
|
1028
|
+
return value;
|
|
1029
|
+
}
|
|
1030
|
+
pthread_mutex_unlock(&map->lock);
|
|
1031
|
+
|
|
1032
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1033
|
+
map_lock_for_key(map, key, hash, &execution);
|
|
1034
|
+
map_store_locked(self, map, key, value, hash, &execution);
|
|
1035
|
+
pthread_mutex_unlock(&map->lock);
|
|
1036
|
+
return value;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
static bool
|
|
1040
|
+
map_is_backend(VALUE backend, const rb_data_type_t *type)
|
|
1041
|
+
{
|
|
1042
|
+
return RB_TYPE_P(backend, T_DATA) && RTYPEDDATA_P(backend) &&
|
|
1043
|
+
RTYPEDDATA_TYPE(backend) == type;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/* The public direct maps retain Ruby fallbacks for subclasses and normalized
|
|
1047
|
+
* adapters. Ordinary access reaches the native backend without Ruby forwarding. */
|
|
1048
|
+
static const rb_data_type_t *
|
|
1049
|
+
map_facade_type(VALUE self)
|
|
1050
|
+
{
|
|
1051
|
+
VALUE klass = rb_obj_class(self);
|
|
1052
|
+
if (klass == cStrictMapFacade) return &map_type;
|
|
1053
|
+
if (klass == cUnsharedMapFacade) return &unshared_map_type;
|
|
1054
|
+
return NULL;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
static VALUE
|
|
1058
|
+
plain_map_get(VALUE self, VALUE key)
|
|
1059
|
+
{
|
|
1060
|
+
const rb_data_type_t *type = map_facade_type(self);
|
|
1061
|
+
if (!type) return rb_call_super(1, &key);
|
|
1062
|
+
VALUE backend = rb_ivar_get(self, id_map);
|
|
1063
|
+
if (map_is_backend(backend, type)) return map_get(backend, key);
|
|
1064
|
+
return rb_funcall(backend, rb_intern("[]"), 1, key);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
static VALUE
|
|
1068
|
+
plain_map_set(VALUE self, VALUE key, VALUE value)
|
|
1069
|
+
{
|
|
1070
|
+
const rb_data_type_t *type = map_facade_type(self);
|
|
1071
|
+
if (!type) {
|
|
1072
|
+
VALUE arguments[] = {key, value};
|
|
1073
|
+
return rb_call_super(2, arguments);
|
|
1074
|
+
}
|
|
1075
|
+
VALUE backend = rb_ivar_get(self, id_map);
|
|
1076
|
+
if (map_is_backend(backend, type)) return map_set(backend, key, value);
|
|
1077
|
+
return rb_funcall(backend, rb_intern("[]="), 2, key, value);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/* Values already shareable need no mode conversion. Envelopes, normalized
|
|
1081
|
+
* backends, and subclasses retain the Ruby hooks that implement their policy. */
|
|
1082
|
+
static VALUE
|
|
1083
|
+
mode_map_get(VALUE self, VALUE key)
|
|
1084
|
+
{
|
|
1085
|
+
if (rb_obj_class(self) != cModeMapFacade) return rb_call_super(1, &key);
|
|
1086
|
+
VALUE backend = rb_ivar_get(self, id_map);
|
|
1087
|
+
if (!map_is_backend(backend, &map_type)) return rb_call_super(1, &key);
|
|
1088
|
+
VALUE result = map_get(backend, key);
|
|
1089
|
+
if (result == map_nil_value) return Qnil;
|
|
1090
|
+
if (rb_obj_is_kind_of(result, cMapEnvelope)) {
|
|
1091
|
+
return rb_funcall(self, id_unwrap_value, 1, result);
|
|
1092
|
+
}
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
static VALUE
|
|
1097
|
+
mode_map_set(VALUE self, VALUE key, VALUE value)
|
|
1098
|
+
{
|
|
1099
|
+
VALUE backend = rb_ivar_get(self, id_map);
|
|
1100
|
+
if (rb_obj_class(self) != cModeMapFacade || !map_is_backend(backend, &map_type)) {
|
|
1101
|
+
VALUE arguments[] = {key, value};
|
|
1102
|
+
return rb_call_super(2, arguments);
|
|
1103
|
+
}
|
|
1104
|
+
/* Preserve the key-specific error and validate before any value transfer. */
|
|
1105
|
+
key = map_prepare_mutation_key(backend, key);
|
|
1106
|
+
/* These are the positive fast checks from rb_ractor_shareable_p. A missing
|
|
1107
|
+
* flag means unknown, so let wrap_value classify and transfer it once.
|
|
1108
|
+
* Preclassifying here would traverse partly frozen graphs twice. */
|
|
1109
|
+
VALUE stored = RB_SPECIAL_CONST_P(value) || RB_OBJ_SHAREABLE_P(value)
|
|
1110
|
+
? (NIL_P(value) ? map_nil_value : value)
|
|
1111
|
+
: rb_funcall(self, id_wrap_value, 1, value);
|
|
1112
|
+
map_set(backend, key, stored);
|
|
1113
|
+
return value;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
static VALUE
|
|
1117
|
+
map_prepare_access(VALUE namespace, VALUE klass, VALUE kind)
|
|
1118
|
+
{
|
|
1119
|
+
(void)namespace;
|
|
1120
|
+
Check_Type(klass, T_CLASS);
|
|
1121
|
+
Check_Type(kind, T_SYMBOL);
|
|
1122
|
+
ID policy_id = SYM2ID(kind);
|
|
1123
|
+
if (policy_id == rb_intern("modes")) {
|
|
1124
|
+
cModeMapFacade = klass;
|
|
1125
|
+
cMapEnvelope = rb_path2class("Farce::Envelope");
|
|
1126
|
+
VALUE policy = rb_path2class("Farce::Internal::MapValueModes");
|
|
1127
|
+
map_nil_value = rb_const_get(policy, rb_intern("NIL_VALUE"));
|
|
1128
|
+
rb_define_method(klass, "[]", mode_map_get, 1);
|
|
1129
|
+
rb_define_method(klass, "[]=", mode_map_set, 2);
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
if (policy_id == rb_intern("strict")) cStrictMapFacade = klass;
|
|
1133
|
+
else if (policy_id == rb_intern("unshared")) cUnsharedMapFacade = klass;
|
|
1134
|
+
else rb_raise(rb_eArgError, "unknown native map policy");
|
|
1135
|
+
rb_define_method(klass, "[]", plain_map_get, 1);
|
|
1136
|
+
rb_define_method(klass, "[]=", plain_map_set, 2);
|
|
1137
|
+
}
|
|
1138
|
+
return klass;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
static VALUE
|
|
1142
|
+
map_key_p(VALUE self, VALUE key)
|
|
1143
|
+
{
|
|
1144
|
+
map_t *map = get_map(self);
|
|
1145
|
+
key = map_prepare_key(map, key);
|
|
1146
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1147
|
+
bool found;
|
|
1148
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1149
|
+
map_lock_state(map, &execution);
|
|
1150
|
+
(void)map_find_slot(map, key, hash, &found, &execution);
|
|
1151
|
+
pthread_mutex_unlock(&map->lock);
|
|
1152
|
+
return found ? Qtrue : Qfalse;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
static VALUE
|
|
1156
|
+
map_getkey(VALUE self, VALUE key)
|
|
1157
|
+
{
|
|
1158
|
+
map_t *map = get_map(self);
|
|
1159
|
+
key = map_prepare_key(map, key);
|
|
1160
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1161
|
+
bool found;
|
|
1162
|
+
VALUE result = Qnil;
|
|
1163
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1164
|
+
map_lock_state(map, &execution);
|
|
1165
|
+
size_t index = map_find_slot(map, key, hash, &found, &execution);
|
|
1166
|
+
if (found) result = map->compare_keys_by_identity ? key : map->slots[index].key;
|
|
1167
|
+
pthread_mutex_unlock(&map->lock);
|
|
1168
|
+
return result;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
static VALUE
|
|
1172
|
+
map_delete(VALUE self, VALUE key)
|
|
1173
|
+
{
|
|
1174
|
+
rb_check_frozen(self);
|
|
1175
|
+
map_t *map = get_map(self);
|
|
1176
|
+
key = map_prepare_key(map, key);
|
|
1177
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1178
|
+
rb_check_frozen(self);
|
|
1179
|
+
bool found;
|
|
1180
|
+
VALUE result = Qnil;
|
|
1181
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1182
|
+
map_lock_for_key(map, key, hash, &execution);
|
|
1183
|
+
size_t index = map_find_slot(map, key, hash, &found, &execution);
|
|
1184
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1185
|
+
pthread_mutex_unlock(&map->lock);
|
|
1186
|
+
rb_check_frozen(self);
|
|
1187
|
+
}
|
|
1188
|
+
if (found) {
|
|
1189
|
+
map_slot_t *slot = &map->slots[index];
|
|
1190
|
+
result = slot->value;
|
|
1191
|
+
slot->key = Qnil;
|
|
1192
|
+
slot->value = Qnil;
|
|
1193
|
+
slot->state = MAP_TOMBSTONE;
|
|
1194
|
+
map->size--;
|
|
1195
|
+
map->tombstones++;
|
|
1196
|
+
map_notify_waiters_locked(map);
|
|
1197
|
+
}
|
|
1198
|
+
pthread_mutex_unlock(&map->lock);
|
|
1199
|
+
return result;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
static VALUE
|
|
1203
|
+
map_clear(VALUE self)
|
|
1204
|
+
{
|
|
1205
|
+
rb_check_frozen(self);
|
|
1206
|
+
map_t *map = get_map(self);
|
|
1207
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1208
|
+
map_lock_state(map, &execution);
|
|
1209
|
+
map_invalidate_reservations_locked(map);
|
|
1210
|
+
for (size_t index = 0; index < map->capacity; index++) {
|
|
1211
|
+
map_slot_t *slot = &map->slots[index];
|
|
1212
|
+
slot->key = Qnil;
|
|
1213
|
+
slot->value = Qnil;
|
|
1214
|
+
slot->hash = 0;
|
|
1215
|
+
slot->state = MAP_EMPTY;
|
|
1216
|
+
}
|
|
1217
|
+
map->iteration_epoch++;
|
|
1218
|
+
map->size = 0;
|
|
1219
|
+
map->tombstones = 0;
|
|
1220
|
+
map_notify_waiters_locked(map);
|
|
1221
|
+
pthread_mutex_unlock(&map->lock);
|
|
1222
|
+
return self;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
typedef struct {
|
|
1226
|
+
VALUE self;
|
|
1227
|
+
map_t *map;
|
|
1228
|
+
map_execution_context_t execution;
|
|
1229
|
+
VALUE key;
|
|
1230
|
+
VALUE current;
|
|
1231
|
+
VALUE argument;
|
|
1232
|
+
VALUE replacement;
|
|
1233
|
+
st_index_t hash;
|
|
1234
|
+
map_reservation_t *reservation;
|
|
1235
|
+
bool identity;
|
|
1236
|
+
bool complete;
|
|
1237
|
+
bool present;
|
|
1238
|
+
} map_operation_t;
|
|
1239
|
+
|
|
1240
|
+
static VALUE
|
|
1241
|
+
map_operation_cleanup(VALUE opaque)
|
|
1242
|
+
{
|
|
1243
|
+
map_operation_t *operation = (map_operation_t *)opaque;
|
|
1244
|
+
if (!operation->complete) {
|
|
1245
|
+
map_lock_state(operation->map, &operation->execution);
|
|
1246
|
+
map_finish_reservation_locked(operation->map, operation->reservation);
|
|
1247
|
+
pthread_mutex_unlock(&operation->map->lock);
|
|
1248
|
+
}
|
|
1249
|
+
return Qnil;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
static VALUE
|
|
1253
|
+
map_store_body(VALUE opaque)
|
|
1254
|
+
{
|
|
1255
|
+
map_operation_t *operation = (map_operation_t *)opaque;
|
|
1256
|
+
VALUE result = rb_yield_values(0);
|
|
1257
|
+
map_check_value(operation->map, result);
|
|
1258
|
+
rb_check_frozen(operation->self);
|
|
1259
|
+
map_lock_state(operation->map, &operation->execution);
|
|
1260
|
+
bool stored = !operation->reservation->invalidated;
|
|
1261
|
+
if (stored) {
|
|
1262
|
+
map_store_locked(
|
|
1263
|
+
operation->self,
|
|
1264
|
+
operation->map,
|
|
1265
|
+
operation->key,
|
|
1266
|
+
result,
|
|
1267
|
+
operation->hash,
|
|
1268
|
+
&operation->execution
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
operation->complete = true;
|
|
1272
|
+
map_finish_reservation_locked(operation->map, operation->reservation);
|
|
1273
|
+
pthread_mutex_unlock(&operation->map->lock);
|
|
1274
|
+
return stored ? result : Qnil;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
static VALUE
|
|
1278
|
+
map_store_if_absent(int argc, VALUE *argv, VALUE self)
|
|
1279
|
+
{
|
|
1280
|
+
rb_check_frozen(self);
|
|
1281
|
+
VALUE key;
|
|
1282
|
+
VALUE keywords = Qnil;
|
|
1283
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1284
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1285
|
+
rb_scan_args(argc, argv, "1:", &key, &keywords);
|
|
1286
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1287
|
+
|
|
1288
|
+
map_t *map = get_map(self);
|
|
1289
|
+
key = map_prepare_key(map, key);
|
|
1290
|
+
map_operation_t operation = {
|
|
1291
|
+
.self = self,
|
|
1292
|
+
.map = map,
|
|
1293
|
+
.execution = map_current_execution_context(),
|
|
1294
|
+
.key = key,
|
|
1295
|
+
.complete = false,
|
|
1296
|
+
};
|
|
1297
|
+
rb_need_block();
|
|
1298
|
+
operation.hash = map_key_hash(map, key);
|
|
1299
|
+
rb_check_frozen(self);
|
|
1300
|
+
map_timeout_t timeout = map_parse_timeout(
|
|
1301
|
+
keyword_values[0] == Qundef ? Qnil : keyword_values[0]
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
if (!map_lock_for_key_with_timeout(
|
|
1305
|
+
map, key, operation.hash, &timeout, &operation.execution
|
|
1306
|
+
)) return Qnil;
|
|
1307
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1308
|
+
pthread_mutex_unlock(&map->lock);
|
|
1309
|
+
rb_check_frozen(self);
|
|
1310
|
+
}
|
|
1311
|
+
bool found;
|
|
1312
|
+
size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
|
|
1313
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1314
|
+
pthread_mutex_unlock(&map->lock);
|
|
1315
|
+
rb_check_frozen(self);
|
|
1316
|
+
}
|
|
1317
|
+
if (found) {
|
|
1318
|
+
VALUE value = map->slots[index].value;
|
|
1319
|
+
pthread_mutex_unlock(&map->lock);
|
|
1320
|
+
return value;
|
|
1321
|
+
}
|
|
1322
|
+
operation.reservation = map_begin_reservation_locked(
|
|
1323
|
+
map, key, operation.hash, &operation.execution
|
|
1324
|
+
);
|
|
1325
|
+
pthread_mutex_unlock(&map->lock);
|
|
1326
|
+
return rb_ensure(map_store_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
static VALUE
|
|
1330
|
+
map_cas_body(VALUE opaque)
|
|
1331
|
+
{
|
|
1332
|
+
map_operation_t *operation = (map_operation_t *)opaque;
|
|
1333
|
+
bool matches = operation->identity
|
|
1334
|
+
? operation->current == operation->argument
|
|
1335
|
+
: RTEST(rb_equal(operation->current, operation->argument));
|
|
1336
|
+
rb_check_frozen(operation->self);
|
|
1337
|
+
|
|
1338
|
+
map_lock_state(operation->map, &operation->execution);
|
|
1339
|
+
bool replaced = matches && !operation->reservation->invalidated;
|
|
1340
|
+
if (replaced) {
|
|
1341
|
+
map_store_locked(
|
|
1342
|
+
operation->self,
|
|
1343
|
+
operation->map,
|
|
1344
|
+
operation->key,
|
|
1345
|
+
operation->replacement,
|
|
1346
|
+
operation->hash,
|
|
1347
|
+
&operation->execution
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
operation->complete = true;
|
|
1351
|
+
map_finish_reservation_locked(operation->map, operation->reservation);
|
|
1352
|
+
pthread_mutex_unlock(&operation->map->lock);
|
|
1353
|
+
return replaced ? Qtrue : Qfalse;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
static VALUE
|
|
1357
|
+
map_compare_and_set(int argc, VALUE *argv, VALUE self)
|
|
1358
|
+
{
|
|
1359
|
+
rb_check_frozen(self);
|
|
1360
|
+
VALUE key;
|
|
1361
|
+
VALUE expected;
|
|
1362
|
+
VALUE replacement;
|
|
1363
|
+
VALUE keywords = Qnil;
|
|
1364
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1365
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1366
|
+
rb_scan_args(argc, argv, "3:", &key, &expected, &replacement, &keywords);
|
|
1367
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1368
|
+
|
|
1369
|
+
map_t *map = get_map(self);
|
|
1370
|
+
key = map_prepare_key(map, key);
|
|
1371
|
+
map_operation_t operation = {
|
|
1372
|
+
.self = self,
|
|
1373
|
+
.map = map,
|
|
1374
|
+
.execution = map_current_execution_context(),
|
|
1375
|
+
.key = key,
|
|
1376
|
+
.argument = expected,
|
|
1377
|
+
.replacement = replacement,
|
|
1378
|
+
.identity = map->compare_values_by_identity,
|
|
1379
|
+
.complete = false,
|
|
1380
|
+
};
|
|
1381
|
+
map_check_value(map, expected);
|
|
1382
|
+
map_check_value(map, replacement);
|
|
1383
|
+
operation.hash = map_key_hash(map, key);
|
|
1384
|
+
rb_check_frozen(self);
|
|
1385
|
+
map_timeout_t timeout = map_parse_timeout(
|
|
1386
|
+
keyword_values[0] == Qundef ? Qnil : keyword_values[0]
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
if (!map_lock_for_key_with_timeout(
|
|
1390
|
+
map, key, operation.hash, &timeout, &operation.execution
|
|
1391
|
+
)) return Qfalse;
|
|
1392
|
+
bool found;
|
|
1393
|
+
size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
|
|
1394
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1395
|
+
pthread_mutex_unlock(&map->lock);
|
|
1396
|
+
rb_check_frozen(self);
|
|
1397
|
+
}
|
|
1398
|
+
if (!found) {
|
|
1399
|
+
pthread_mutex_unlock(&map->lock);
|
|
1400
|
+
return Qfalse;
|
|
1401
|
+
}
|
|
1402
|
+
operation.current = map->slots[index].value;
|
|
1403
|
+
operation.reservation = map_begin_reservation_locked(
|
|
1404
|
+
map, key, operation.hash, &operation.execution
|
|
1405
|
+
);
|
|
1406
|
+
pthread_mutex_unlock(&map->lock);
|
|
1407
|
+
return rb_ensure(map_cas_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/* Decisions are interpreted before value validation. KEEP never writes storage. */
|
|
1411
|
+
static VALUE
|
|
1412
|
+
map_modify_body(VALUE opaque)
|
|
1413
|
+
{
|
|
1414
|
+
map_operation_t *operation = (map_operation_t *)opaque;
|
|
1415
|
+
VALUE value = rb_yield_values(2, operation->present ? Qtrue : Qfalse, operation->current);
|
|
1416
|
+
bool remove = value == map_delete_token;
|
|
1417
|
+
bool store = !remove && value != map_keep;
|
|
1418
|
+
if (store) map_check_value(operation->map, value);
|
|
1419
|
+
rb_check_frozen(operation->self);
|
|
1420
|
+
map_lock_state(operation->map, &operation->execution);
|
|
1421
|
+
bool changed = false;
|
|
1422
|
+
if (store && !operation->reservation->invalidated) {
|
|
1423
|
+
map_store_locked(operation->self, operation->map, operation->key, value,
|
|
1424
|
+
operation->hash, &operation->execution);
|
|
1425
|
+
changed = true;
|
|
1426
|
+
} else if (remove && !operation->reservation->invalidated) {
|
|
1427
|
+
bool found;
|
|
1428
|
+
size_t index = map_find_slot(operation->map, operation->key,
|
|
1429
|
+
operation->hash, &found, &operation->execution);
|
|
1430
|
+
if (RB_OBJ_FROZEN(operation->self)) {
|
|
1431
|
+
pthread_mutex_unlock(&operation->map->lock);
|
|
1432
|
+
rb_check_frozen(operation->self);
|
|
1433
|
+
}
|
|
1434
|
+
if (found) {
|
|
1435
|
+
map_slot_t *slot = &operation->map->slots[index];
|
|
1436
|
+
slot->key = Qnil;
|
|
1437
|
+
slot->value = Qnil;
|
|
1438
|
+
slot->state = MAP_TOMBSTONE;
|
|
1439
|
+
operation->map->size--;
|
|
1440
|
+
operation->map->tombstones++;
|
|
1441
|
+
map_notify_waiters_locked(operation->map);
|
|
1442
|
+
changed = true;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
operation->complete = true;
|
|
1446
|
+
map_finish_reservation_locked(operation->map, operation->reservation);
|
|
1447
|
+
pthread_mutex_unlock(&operation->map->lock);
|
|
1448
|
+
return changed ? Qtrue : Qfalse;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
static VALUE
|
|
1452
|
+
map_modify(VALUE self, VALUE key)
|
|
1453
|
+
{
|
|
1454
|
+
rb_check_frozen(self);
|
|
1455
|
+
rb_need_block();
|
|
1456
|
+
map_t *map = get_map(self);
|
|
1457
|
+
key = map_prepare_key(map, key);
|
|
1458
|
+
map_operation_t operation = {
|
|
1459
|
+
.self = self,
|
|
1460
|
+
.execution = map_current_execution_context(),
|
|
1461
|
+
.map = map,
|
|
1462
|
+
.key = key,
|
|
1463
|
+
.complete = false,
|
|
1464
|
+
};
|
|
1465
|
+
operation.hash = map_key_hash(map, key);
|
|
1466
|
+
rb_check_frozen(self);
|
|
1467
|
+
map_lock_for_key(map, key, operation.hash, &operation.execution);
|
|
1468
|
+
bool found;
|
|
1469
|
+
size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
|
|
1470
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1471
|
+
pthread_mutex_unlock(&map->lock);
|
|
1472
|
+
rb_check_frozen(self);
|
|
1473
|
+
}
|
|
1474
|
+
operation.present = found;
|
|
1475
|
+
operation.current = found ? map->slots[index].value : Qnil;
|
|
1476
|
+
operation.reservation = map_begin_reservation_locked(map, key, operation.hash, &operation.execution);
|
|
1477
|
+
pthread_mutex_unlock(&map->lock);
|
|
1478
|
+
return rb_ensure(map_modify_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
static VALUE
|
|
1482
|
+
map_upsert_body(VALUE opaque)
|
|
1483
|
+
{
|
|
1484
|
+
map_operation_t *operation = (map_operation_t *)opaque;
|
|
1485
|
+
VALUE result = rb_yield(operation->current);
|
|
1486
|
+
map_check_value(operation->map, result);
|
|
1487
|
+
rb_check_frozen(operation->self);
|
|
1488
|
+
map_lock_state(operation->map, &operation->execution);
|
|
1489
|
+
bool stored = !operation->reservation->invalidated;
|
|
1490
|
+
if (stored) {
|
|
1491
|
+
map_store_locked(
|
|
1492
|
+
operation->self,
|
|
1493
|
+
operation->map,
|
|
1494
|
+
operation->key,
|
|
1495
|
+
result,
|
|
1496
|
+
operation->hash,
|
|
1497
|
+
&operation->execution
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
operation->complete = true;
|
|
1501
|
+
map_finish_reservation_locked(operation->map, operation->reservation);
|
|
1502
|
+
pthread_mutex_unlock(&operation->map->lock);
|
|
1503
|
+
return stored ? result : Qnil;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
static VALUE
|
|
1507
|
+
map_upsert(int argc, VALUE *argv, VALUE self)
|
|
1508
|
+
{
|
|
1509
|
+
rb_check_frozen(self);
|
|
1510
|
+
VALUE key;
|
|
1511
|
+
VALUE initial;
|
|
1512
|
+
VALUE keywords = Qnil;
|
|
1513
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1514
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1515
|
+
rb_scan_args(argc, argv, "2:", &key, &initial, &keywords);
|
|
1516
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1517
|
+
|
|
1518
|
+
map_t *map = get_map(self);
|
|
1519
|
+
key = map_prepare_key(map, key);
|
|
1520
|
+
map_operation_t operation = {
|
|
1521
|
+
.self = self,
|
|
1522
|
+
.map = map,
|
|
1523
|
+
.execution = map_current_execution_context(),
|
|
1524
|
+
.key = key,
|
|
1525
|
+
.complete = false,
|
|
1526
|
+
};
|
|
1527
|
+
map_check_value(map, initial);
|
|
1528
|
+
rb_need_block();
|
|
1529
|
+
operation.hash = map_key_hash(map, key);
|
|
1530
|
+
rb_check_frozen(self);
|
|
1531
|
+
map_timeout_t timeout = map_parse_timeout(
|
|
1532
|
+
keyword_values[0] == Qundef ? Qnil : keyword_values[0]
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
if (!map_lock_for_key_with_timeout(
|
|
1536
|
+
map, key, operation.hash, &timeout, &operation.execution
|
|
1537
|
+
)) return Qnil;
|
|
1538
|
+
bool found;
|
|
1539
|
+
size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
|
|
1540
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1541
|
+
pthread_mutex_unlock(&map->lock);
|
|
1542
|
+
rb_check_frozen(self);
|
|
1543
|
+
}
|
|
1544
|
+
if (!found) {
|
|
1545
|
+
map_store_locked(self, map, key, initial, operation.hash, &operation.execution);
|
|
1546
|
+
pthread_mutex_unlock(&map->lock);
|
|
1547
|
+
return initial;
|
|
1548
|
+
}
|
|
1549
|
+
operation.current = map->slots[index].value;
|
|
1550
|
+
operation.reservation = map_begin_reservation_locked(
|
|
1551
|
+
map, key, operation.hash, &operation.execution
|
|
1552
|
+
);
|
|
1553
|
+
pthread_mutex_unlock(&map->lock);
|
|
1554
|
+
return rb_ensure(map_upsert_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
static VALUE
|
|
1558
|
+
map_compare_keys_by_identity_p(VALUE self)
|
|
1559
|
+
{
|
|
1560
|
+
return get_map(self)->compare_keys_by_identity ? Qtrue : Qfalse;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
static VALUE
|
|
1564
|
+
map_compare_values_by_identity_p(VALUE self)
|
|
1565
|
+
{
|
|
1566
|
+
return get_map(self)->compare_values_by_identity ? Qtrue : Qfalse;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
static VALUE
|
|
1570
|
+
map_size(VALUE self)
|
|
1571
|
+
{
|
|
1572
|
+
map_t *map = get_map(self);
|
|
1573
|
+
size_t size;
|
|
1574
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1575
|
+
map_lock_state(map, &execution);
|
|
1576
|
+
size = map->size;
|
|
1577
|
+
pthread_mutex_unlock(&map->lock);
|
|
1578
|
+
return SIZET2NUM(size);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
static VALUE
|
|
1582
|
+
map_enumerator_size(VALUE self, VALUE arguments, VALUE enumerator)
|
|
1583
|
+
{
|
|
1584
|
+
(void)arguments;
|
|
1585
|
+
(void)enumerator;
|
|
1586
|
+
return map_size(self);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
static VALUE
|
|
1590
|
+
map_entries_snapshot(map_t *map)
|
|
1591
|
+
{
|
|
1592
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1593
|
+
for (;;) {
|
|
1594
|
+
size_t capacity;
|
|
1595
|
+
map_lock_state(map, &execution);
|
|
1596
|
+
capacity = map->size;
|
|
1597
|
+
pthread_mutex_unlock(&map->lock);
|
|
1598
|
+
|
|
1599
|
+
long reserved_entries = (long)(capacity * 2);
|
|
1600
|
+
VALUE entries = rb_ary_new_capa(reserved_entries);
|
|
1601
|
+
rb_ary_resize(entries, reserved_entries);
|
|
1602
|
+
map_lock_state(map, &execution);
|
|
1603
|
+
if (map->size > capacity) {
|
|
1604
|
+
pthread_mutex_unlock(&map->lock);
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
long entry_count = 0;
|
|
1608
|
+
VALUE *storage = RARRAY_PTR(entries);
|
|
1609
|
+
for (size_t index = 0; index < map->capacity; index++) {
|
|
1610
|
+
map_slot_t *slot = &map->slots[index];
|
|
1611
|
+
if (slot->state != MAP_OCCUPIED) continue;
|
|
1612
|
+
RB_OBJ_WRITE(entries, &storage[entry_count++], slot->key);
|
|
1613
|
+
RB_OBJ_WRITE(entries, &storage[entry_count++], slot->value);
|
|
1614
|
+
}
|
|
1615
|
+
pthread_mutex_unlock(&map->lock);
|
|
1616
|
+
if (entry_count < reserved_entries) rb_ary_resize(entries, entry_count);
|
|
1617
|
+
return entries;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
static VALUE
|
|
1622
|
+
map_keys(VALUE self)
|
|
1623
|
+
{
|
|
1624
|
+
VALUE entries = map_entries_snapshot(get_map(self));
|
|
1625
|
+
long entry_count = RARRAY_LEN(entries);
|
|
1626
|
+
VALUE keys = rb_ary_new_capa(entry_count / 2);
|
|
1627
|
+
for (long index = 0; index < entry_count; index += 2) {
|
|
1628
|
+
rb_ary_push(keys, RARRAY_AREF(entries, index));
|
|
1629
|
+
}
|
|
1630
|
+
RB_GC_GUARD(entries);
|
|
1631
|
+
return keys;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
static VALUE
|
|
1635
|
+
map_each(VALUE self)
|
|
1636
|
+
{
|
|
1637
|
+
RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
|
|
1638
|
+
VALUE entries = map_entries_snapshot(get_map(self));
|
|
1639
|
+
long entry_count = RARRAY_LEN(entries);
|
|
1640
|
+
for (long index = 0; index < entry_count; index += 2) {
|
|
1641
|
+
rb_yield(rb_assoc_new(RARRAY_AREF(entries, index), RARRAY_AREF(entries, index + 1)));
|
|
1642
|
+
}
|
|
1643
|
+
RB_GC_GUARD(entries);
|
|
1644
|
+
return self;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/* A bounded live traversal. No native lock is held across a Ruby yield.
|
|
1648
|
+
* Insertions, rehashing and clearing invalidate the cursor. Deletions and
|
|
1649
|
+
* value replacement are permitted while the table layout stays unchanged. */
|
|
1650
|
+
static VALUE
|
|
1651
|
+
map_each_live(VALUE self)
|
|
1652
|
+
{
|
|
1653
|
+
RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
|
|
1654
|
+
map_t *map = get_map(self);
|
|
1655
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1656
|
+
size_t cursor = 0;
|
|
1657
|
+
uint64_t epoch = 0;
|
|
1658
|
+
bool started = false;
|
|
1659
|
+
const long batch_slots = 64;
|
|
1660
|
+
VALUE batch = rb_ary_new_capa(batch_slots);
|
|
1661
|
+
rb_ary_resize(batch, batch_slots);
|
|
1662
|
+
|
|
1663
|
+
for (;;) {
|
|
1664
|
+
VALUE *storage = RARRAY_PTR(batch);
|
|
1665
|
+
for (long index = 0; index < batch_slots; index++) {
|
|
1666
|
+
RB_OBJ_WRITE(batch, &storage[index], Qnil);
|
|
1667
|
+
}
|
|
1668
|
+
map_lock_state(map, &execution);
|
|
1669
|
+
if (started && epoch != map->iteration_epoch) {
|
|
1670
|
+
pthread_mutex_unlock(&map->lock);
|
|
1671
|
+
rb_raise(rb_eRuntimeError, "map structurally changed during live iteration");
|
|
1672
|
+
}
|
|
1673
|
+
epoch = map->iteration_epoch;
|
|
1674
|
+
started = true;
|
|
1675
|
+
long length = 0;
|
|
1676
|
+
while (cursor < map->capacity && length < batch_slots) {
|
|
1677
|
+
map_slot_t *slot = &map->slots[cursor++];
|
|
1678
|
+
if (slot->state != MAP_OCCUPIED) continue;
|
|
1679
|
+
RB_OBJ_WRITE(batch, &storage[length++], slot->key);
|
|
1680
|
+
RB_OBJ_WRITE(batch, &storage[length++], slot->value);
|
|
1681
|
+
}
|
|
1682
|
+
pthread_mutex_unlock(&map->lock);
|
|
1683
|
+
if (length == 0) break;
|
|
1684
|
+
for (long index = 0; index < length; index += 2) {
|
|
1685
|
+
rb_yield(rb_assoc_new(RARRAY_AREF(batch, index), RARRAY_AREF(batch, index + 1)));
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
RB_GC_GUARD(batch);
|
|
1689
|
+
return self;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
static VALUE
|
|
1693
|
+
map_each_key(VALUE self)
|
|
1694
|
+
{
|
|
1695
|
+
RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
|
|
1696
|
+
VALUE entries = map_entries_snapshot(get_map(self));
|
|
1697
|
+
long entry_count = RARRAY_LEN(entries);
|
|
1698
|
+
for (long index = 0; index < entry_count; index += 2) {
|
|
1699
|
+
rb_yield(RARRAY_AREF(entries, index));
|
|
1700
|
+
}
|
|
1701
|
+
RB_GC_GUARD(entries);
|
|
1702
|
+
return self;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
static VALUE
|
|
1706
|
+
map_each_value(VALUE self)
|
|
1707
|
+
{
|
|
1708
|
+
RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
|
|
1709
|
+
VALUE entries = map_entries_snapshot(get_map(self));
|
|
1710
|
+
long entry_count = RARRAY_LEN(entries);
|
|
1711
|
+
for (long index = 1; index < entry_count; index += 2) {
|
|
1712
|
+
rb_yield(RARRAY_AREF(entries, index));
|
|
1713
|
+
}
|
|
1714
|
+
RB_GC_GUARD(entries);
|
|
1715
|
+
return self;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
static VALUE
|
|
1719
|
+
map_get_with_timeout(int argc, VALUE *argv, VALUE self)
|
|
1720
|
+
{
|
|
1721
|
+
VALUE key;
|
|
1722
|
+
VALUE keywords = Qnil;
|
|
1723
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1724
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1725
|
+
rb_scan_args(argc, argv, "1:", &key, &keywords);
|
|
1726
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1727
|
+
|
|
1728
|
+
map_t *map = get_map(self);
|
|
1729
|
+
key = map_prepare_key(map, key);
|
|
1730
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1731
|
+
map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
|
|
1732
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1733
|
+
if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
|
|
1734
|
+
return map_timeout_result();
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
bool found;
|
|
1738
|
+
size_t index = map_find_slot(map, key, hash, &found, &execution);
|
|
1739
|
+
VALUE result = found ? map->slots[index].value : Qnil;
|
|
1740
|
+
pthread_mutex_unlock(&map->lock);
|
|
1741
|
+
return result;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
static VALUE
|
|
1745
|
+
map_store_with_timeout(int argc, VALUE *argv, VALUE self)
|
|
1746
|
+
{
|
|
1747
|
+
rb_check_frozen(self);
|
|
1748
|
+
VALUE key;
|
|
1749
|
+
VALUE value;
|
|
1750
|
+
VALUE keywords = Qnil;
|
|
1751
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1752
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1753
|
+
rb_scan_args(argc, argv, "2:", &key, &value, &keywords);
|
|
1754
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1755
|
+
|
|
1756
|
+
map_t *map = get_map(self);
|
|
1757
|
+
key = map_prepare_key(map, key);
|
|
1758
|
+
map_check_value(map, value);
|
|
1759
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1760
|
+
rb_check_frozen(self);
|
|
1761
|
+
map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
|
|
1762
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1763
|
+
if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
|
|
1764
|
+
return map_timeout_result();
|
|
1765
|
+
}
|
|
1766
|
+
map_store_locked(self, map, key, value, hash, &execution);
|
|
1767
|
+
pthread_mutex_unlock(&map->lock);
|
|
1768
|
+
return value;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
static VALUE
|
|
1772
|
+
map_swap(int argc, VALUE *argv, VALUE self)
|
|
1773
|
+
{
|
|
1774
|
+
rb_check_frozen(self);
|
|
1775
|
+
VALUE key;
|
|
1776
|
+
VALUE replacement;
|
|
1777
|
+
VALUE keywords = Qnil;
|
|
1778
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1779
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1780
|
+
rb_scan_args(argc, argv, "2:", &key, &replacement, &keywords);
|
|
1781
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1782
|
+
|
|
1783
|
+
map_t *map = get_map(self);
|
|
1784
|
+
key = map_prepare_key(map, key);
|
|
1785
|
+
map_check_value(map, replacement);
|
|
1786
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1787
|
+
rb_check_frozen(self);
|
|
1788
|
+
map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
|
|
1789
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1790
|
+
if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
|
|
1791
|
+
return map_timeout_result();
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
bool found;
|
|
1795
|
+
size_t index = map_find_slot(map, key, hash, &found, &execution);
|
|
1796
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1797
|
+
pthread_mutex_unlock(&map->lock);
|
|
1798
|
+
rb_check_frozen(self);
|
|
1799
|
+
}
|
|
1800
|
+
VALUE previous = found ? map->slots[index].value : Qnil;
|
|
1801
|
+
map_store_locked(self, map, key, replacement, hash, &execution);
|
|
1802
|
+
pthread_mutex_unlock(&map->lock);
|
|
1803
|
+
return previous;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
static bool
|
|
1807
|
+
map_values_equal(map_t *map, VALUE left, VALUE right)
|
|
1808
|
+
{
|
|
1809
|
+
return map->compare_values_by_identity ? left == right : RTEST(rb_equal(left, right));
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
static VALUE
|
|
1813
|
+
map_wait_for_value(int argc, VALUE *argv, VALUE self, bool non_nil)
|
|
1814
|
+
{
|
|
1815
|
+
VALUE key;
|
|
1816
|
+
VALUE expected = Qnil;
|
|
1817
|
+
VALUE keywords = Qnil;
|
|
1818
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1819
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1820
|
+
if (non_nil) rb_scan_args(argc, argv, "1:", &key, &keywords);
|
|
1821
|
+
else rb_scan_args(argc, argv, "2:", &key, &expected, &keywords);
|
|
1822
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1823
|
+
|
|
1824
|
+
map_t *map = get_map(self);
|
|
1825
|
+
key = map_prepare_key(map, key);
|
|
1826
|
+
if (!non_nil) map_check_value(map, expected);
|
|
1827
|
+
st_index_t hash = map_key_hash(map, key);
|
|
1828
|
+
map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
|
|
1829
|
+
map_execution_context_t execution = map_current_execution_context();
|
|
1830
|
+
|
|
1831
|
+
for (;;) {
|
|
1832
|
+
bool found;
|
|
1833
|
+
VALUE current = Qnil;
|
|
1834
|
+
uint64_t generation;
|
|
1835
|
+
map_lock_state(map, &execution);
|
|
1836
|
+
size_t index = map_find_slot(map, key, hash, &found, &execution);
|
|
1837
|
+
if (found) current = map->slots[index].value;
|
|
1838
|
+
generation = map->generation;
|
|
1839
|
+
pthread_mutex_unlock(&map->lock);
|
|
1840
|
+
|
|
1841
|
+
bool ready = non_nil ? !NIL_P(current) : !map_values_equal(map, current, expected);
|
|
1842
|
+
if (ready) return current;
|
|
1843
|
+
|
|
1844
|
+
map_lock_state(map, &execution);
|
|
1845
|
+
if (map->generation != generation) {
|
|
1846
|
+
pthread_mutex_unlock(&map->lock);
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
map_reservation_t *reservation = map_find_reservation_locked(map, key, hash, &execution);
|
|
1850
|
+
if (reservation) map_check_reservation_wait_locked(map, reservation, &execution);
|
|
1851
|
+
pthread_mutex_unlock(&map->lock);
|
|
1852
|
+
if (!map_wait_once(map, generation, &timeout)) return map_timeout_result();
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
static VALUE
|
|
1857
|
+
map_wait_until_changed(int argc, VALUE *argv, VALUE self)
|
|
1858
|
+
{
|
|
1859
|
+
return map_wait_for_value(argc, argv, self, false);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
static VALUE
|
|
1863
|
+
map_wait_until_non_nil(int argc, VALUE *argv, VALUE self)
|
|
1864
|
+
{
|
|
1865
|
+
return map_wait_for_value(argc, argv, self, true);
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
static VALUE
|
|
1869
|
+
map_update(int argc, VALUE *argv, VALUE self)
|
|
1870
|
+
{
|
|
1871
|
+
rb_check_frozen(self);
|
|
1872
|
+
VALUE key;
|
|
1873
|
+
VALUE keywords = Qnil;
|
|
1874
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1875
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1876
|
+
rb_scan_args(argc, argv, "1:", &key, &keywords);
|
|
1877
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1878
|
+
|
|
1879
|
+
map_t *map = get_map(self);
|
|
1880
|
+
key = map_prepare_key(map, key);
|
|
1881
|
+
map_operation_t operation = {
|
|
1882
|
+
.self = self,
|
|
1883
|
+
.map = map,
|
|
1884
|
+
.execution = map_current_execution_context(),
|
|
1885
|
+
.key = key,
|
|
1886
|
+
.complete = false,
|
|
1887
|
+
};
|
|
1888
|
+
rb_need_block();
|
|
1889
|
+
operation.hash = map_key_hash(map, key);
|
|
1890
|
+
rb_check_frozen(self);
|
|
1891
|
+
map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
|
|
1892
|
+
|
|
1893
|
+
if (!map_lock_for_key_with_timeout(
|
|
1894
|
+
map, key, operation.hash, &timeout, &operation.execution
|
|
1895
|
+
)) return Qnil;
|
|
1896
|
+
bool found;
|
|
1897
|
+
size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
|
|
1898
|
+
if (RB_OBJ_FROZEN(self)) {
|
|
1899
|
+
pthread_mutex_unlock(&map->lock);
|
|
1900
|
+
rb_check_frozen(self);
|
|
1901
|
+
}
|
|
1902
|
+
operation.current = found ? map->slots[index].value : Qnil;
|
|
1903
|
+
operation.reservation = map_begin_reservation_locked(
|
|
1904
|
+
map, key, operation.hash, &operation.execution
|
|
1905
|
+
);
|
|
1906
|
+
pthread_mutex_unlock(&map->lock);
|
|
1907
|
+
return rb_ensure(map_upsert_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
static key_lock_map_t *
|
|
1911
|
+
key_lock_get_raw(VALUE self)
|
|
1912
|
+
{
|
|
1913
|
+
key_lock_map_t *map;
|
|
1914
|
+
if (rb_typeddata_is_kind_of(self, &unshared_key_lock_type)) {
|
|
1915
|
+
TypedData_Get_Struct(self, key_lock_map_t, &unshared_key_lock_type, map);
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
TypedData_Get_Struct(self, key_lock_map_t, &shared_key_lock_type, map);
|
|
1919
|
+
}
|
|
1920
|
+
return map;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
static void
|
|
1924
|
+
shared_key_lock_validate_references(VALUE self)
|
|
1925
|
+
{
|
|
1926
|
+
key_lock_map_t *storage;
|
|
1927
|
+
TypedData_Get_Struct(self, key_lock_map_t, &shared_key_lock_type, storage);
|
|
1928
|
+
map_t *map = &storage->core;
|
|
1929
|
+
for (size_t index = 0; index < map->capacity; index++) {
|
|
1930
|
+
if (map->slots[index].state != MAP_OCCUPIED) continue;
|
|
1931
|
+
containers_check_shareable(map->slots[index].key);
|
|
1932
|
+
containers_check_shareable(map->slots[index].value);
|
|
1933
|
+
}
|
|
1934
|
+
for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
|
|
1935
|
+
containers_check_shareable(reservation->key);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
static VALUE
|
|
1940
|
+
key_lock_publish(VALUE self)
|
|
1941
|
+
{
|
|
1942
|
+
return containers_publish_native_with_references(self, shared_key_lock_validate_references);
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
static VALUE
|
|
1946
|
+
key_lock_finish_publication(VALUE self)
|
|
1947
|
+
{
|
|
1948
|
+
key_lock_map_t *map = key_lock_get_raw(self);
|
|
1949
|
+
int state = rb_ractor_shareable_p(self) ? KEY_LOCK_INITIALIZED : KEY_LOCK_UNINITIALIZED;
|
|
1950
|
+
atomic_store_explicit(&map->state, state, memory_order_release);
|
|
1951
|
+
return Qnil;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
static VALUE
|
|
1955
|
+
key_lock_initialize(int argc, VALUE *argv, VALUE self)
|
|
1956
|
+
{
|
|
1957
|
+
VALUE keywords = Qnil;
|
|
1958
|
+
ID ids[] = {
|
|
1959
|
+
rb_intern("compare_by_identity"),
|
|
1960
|
+
rb_intern("compare_keys_by_identity"),
|
|
1961
|
+
rb_intern("compare_values_by_identity")
|
|
1962
|
+
};
|
|
1963
|
+
VALUE values[3] = {Qundef, Qundef, Qundef};
|
|
1964
|
+
rb_scan_args(argc, argv, "0:", &keywords);
|
|
1965
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, ids, 0, 3, values);
|
|
1966
|
+
key_lock_map_t *map = key_lock_get_raw(self);
|
|
1967
|
+
if (atomic_load_explicit(&map->state, memory_order_acquire) != KEY_LOCK_UNINITIALIZED) {
|
|
1968
|
+
rb_raise(rb_eRuntimeError, "key lock map is already initialized");
|
|
1969
|
+
}
|
|
1970
|
+
rb_check_frozen(self);
|
|
1971
|
+
bool common = values[0] == Qundef ? false : containers_strict_bool(values[0], "compare_by_identity");
|
|
1972
|
+
bool keys = values[1] == Qundef ? common : containers_strict_bool(values[1], "compare_keys_by_identity");
|
|
1973
|
+
if (values[2] != Qundef) containers_strict_bool(values[2], "compare_values_by_identity");
|
|
1974
|
+
int expected = KEY_LOCK_UNINITIALIZED;
|
|
1975
|
+
if (!atomic_compare_exchange_strong(&map->state, &expected, KEY_LOCK_INITIALIZING)) {
|
|
1976
|
+
rb_raise(rb_eRuntimeError, "key lock map is already initialized");
|
|
1977
|
+
}
|
|
1978
|
+
map->core.compare_keys_by_identity = keys;
|
|
1979
|
+
if (rb_typeddata_is_kind_of(self, &unshared_key_lock_type)) {
|
|
1980
|
+
atomic_store_explicit(&map->state, KEY_LOCK_INITIALIZED, memory_order_release);
|
|
1981
|
+
return self;
|
|
1982
|
+
}
|
|
1983
|
+
atomic_store_explicit(&map->state, KEY_LOCK_PUBLISHING, memory_order_release);
|
|
1984
|
+
return rb_ensure(key_lock_publish, self, key_lock_finish_publication, self);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
static VALUE
|
|
1988
|
+
key_lock_yield(VALUE opaque)
|
|
1989
|
+
{
|
|
1990
|
+
(void)opaque;
|
|
1991
|
+
return rb_yield_values(0);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
static VALUE
|
|
1995
|
+
key_lock_synchronize(VALUE self, VALUE key)
|
|
1996
|
+
{
|
|
1997
|
+
key_lock_map_t *storage = key_lock_get_raw(self);
|
|
1998
|
+
int state = atomic_load_explicit(&storage->state, memory_order_acquire);
|
|
1999
|
+
bool shared = rb_typeddata_is_kind_of(self, &shared_key_lock_type);
|
|
2000
|
+
if (state != KEY_LOCK_INITIALIZED &&
|
|
2001
|
+
!(shared && state == KEY_LOCK_PUBLISHING && rb_ractor_shareable_p(self))) {
|
|
2002
|
+
rb_raise(rb_eRuntimeError, "uninitialized key lock map");
|
|
2003
|
+
}
|
|
2004
|
+
rb_need_block();
|
|
2005
|
+
if (shared) containers_check_shareable(key);
|
|
2006
|
+
map_t *map = &storage->core;
|
|
2007
|
+
map_operation_t operation = {
|
|
2008
|
+
.map = map,
|
|
2009
|
+
.execution = map_current_execution_context(),
|
|
2010
|
+
.key = key,
|
|
2011
|
+
.complete = false,
|
|
2012
|
+
};
|
|
2013
|
+
operation.hash = map_key_hash(map, key);
|
|
2014
|
+
map_lock_for_key(map, key, operation.hash, &operation.execution);
|
|
2015
|
+
operation.reservation = map_begin_reservation_locked(
|
|
2016
|
+
map, key, operation.hash, &operation.execution
|
|
2017
|
+
);
|
|
2018
|
+
pthread_mutex_unlock(&map->lock);
|
|
2019
|
+
return rb_ensure(key_lock_yield, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
static VALUE
|
|
2023
|
+
key_lock_initialize_copy(VALUE self, VALUE other)
|
|
2024
|
+
{
|
|
2025
|
+
(void)self;
|
|
2026
|
+
(void)other;
|
|
2027
|
+
rb_raise(rb_eTypeError, "key lock maps cannot be copied");
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
static bool
|
|
2031
|
+
map_transaction_valid(farce_transaction_entry_t *entry)
|
|
2032
|
+
{
|
|
2033
|
+
map_t *map = entry->source_data;
|
|
2034
|
+
return map->generation == entry->version && !map->comparing && !map->reservations;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
static void
|
|
2038
|
+
map_transaction_apply(farce_transaction_entry_t *entry)
|
|
2039
|
+
{
|
|
2040
|
+
map_t *map = entry->source_data;
|
|
2041
|
+
map_t *copy = entry->working_data;
|
|
2042
|
+
map_slot_t *slots = map->slots;
|
|
2043
|
+
size_t capacity = map->capacity, size = map->size, tombstones = map->tombstones;
|
|
2044
|
+
map->slots = copy->slots;
|
|
2045
|
+
map->capacity = copy->capacity;
|
|
2046
|
+
map->size = copy->size;
|
|
2047
|
+
map->tombstones = copy->tombstones;
|
|
2048
|
+
map->iteration_epoch++;
|
|
2049
|
+
copy->slots = slots;
|
|
2050
|
+
copy->capacity = capacity;
|
|
2051
|
+
copy->size = size;
|
|
2052
|
+
copy->tombstones = tombstones;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
static void
|
|
2056
|
+
map_transaction_notify(farce_transaction_entry_t *entry)
|
|
2057
|
+
{
|
|
2058
|
+
map_notify_waiters_locked(entry->source_data);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
static void
|
|
2062
|
+
map_transaction_reserve(farce_transaction_entry_t *entry, VALUE fiber, VALUE thread)
|
|
2063
|
+
{
|
|
2064
|
+
map_t *map = entry->source_data;
|
|
2065
|
+
map->comparing = FARCE_TRANSACTION_RESERVED;
|
|
2066
|
+
map->comparing_owner = fiber;
|
|
2067
|
+
map->comparing_thread = thread;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
static void
|
|
2071
|
+
map_transaction_release(farce_transaction_entry_t *entry)
|
|
2072
|
+
{
|
|
2073
|
+
map_t *map = entry->source_data;
|
|
2074
|
+
map->comparing = false;
|
|
2075
|
+
map->comparing_owner = Qnil;
|
|
2076
|
+
map->comparing_thread = Qnil;
|
|
2077
|
+
map_notify_waiters_locked(map);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
static VALUE
|
|
2081
|
+
map_freeze(VALUE self)
|
|
2082
|
+
{
|
|
2083
|
+
map_t *map;
|
|
2084
|
+
TypedData_Get_Struct(self, map_t, &map_type, map);
|
|
2085
|
+
pthread_mutex_lock(&map->lock);
|
|
2086
|
+
if (map->comparing == FARCE_TRANSACTION_RESERVED) {
|
|
2087
|
+
pthread_mutex_unlock(&map->lock);
|
|
2088
|
+
map_execution_context_t context = map_current_execution_context();
|
|
2089
|
+
map_lock_state(map, &context);
|
|
2090
|
+
}
|
|
2091
|
+
rb_obj_freeze(self);
|
|
2092
|
+
pthread_mutex_unlock(&map->lock);
|
|
2093
|
+
return self;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
static const farce_transaction_ops_t map_transaction_ops = {
|
|
2097
|
+
map_transaction_valid, map_transaction_apply, map_transaction_notify,
|
|
2098
|
+
map_transaction_reserve, map_transaction_release,
|
|
2099
|
+
};
|
|
2100
|
+
|
|
2101
|
+
static bool
|
|
2102
|
+
map_transaction_size_valid(farce_transaction_entry_t *entry)
|
|
2103
|
+
{
|
|
2104
|
+
map_t *map = entry->source_data;
|
|
2105
|
+
return map->size == entry->version && !map->comparing && !map->reservations;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
static void
|
|
2109
|
+
map_transaction_size_noop(farce_transaction_entry_t *entry)
|
|
2110
|
+
{
|
|
2111
|
+
(void)entry;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
static const farce_transaction_ops_t map_transaction_size_ops = {
|
|
2115
|
+
map_transaction_size_valid, map_transaction_size_noop, map_transaction_size_noop,
|
|
2116
|
+
map_transaction_reserve, map_transaction_release,
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
/* Count-only participants use the same commit lock without copying slots or
|
|
2120
|
+
* rejecting completed value replacements that leave cardinality unchanged. */
|
|
2121
|
+
static VALUE
|
|
2122
|
+
map_transaction_size_snapshot(VALUE self, VALUE size)
|
|
2123
|
+
{
|
|
2124
|
+
uint64_t expected = NUM2ULL(size);
|
|
2125
|
+
map_t *source = get_map(self);
|
|
2126
|
+
farce_transaction_entry_t *entry;
|
|
2127
|
+
VALUE result = farce_transaction_entry_new(
|
|
2128
|
+
self, Qnil, source, NULL, &source->lock, &map_transaction_size_ops, &entry
|
|
2129
|
+
);
|
|
2130
|
+
entry->version = expected;
|
|
2131
|
+
return result;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
static VALUE
|
|
2135
|
+
map_transaction_snapshot(VALUE self)
|
|
2136
|
+
{
|
|
2137
|
+
map_t *source = get_map(self);
|
|
2138
|
+
VALUE working = source->shareable ? map_allocate(cMap) : unshared_map_allocate(cUnsharedMap);
|
|
2139
|
+
map_t *copy;
|
|
2140
|
+
TypedData_Get_Struct(working, map_t, &map_type, copy);
|
|
2141
|
+
farce_transaction_entry_t *entry;
|
|
2142
|
+
VALUE result = farce_transaction_entry_new(
|
|
2143
|
+
self, working, source, copy, &source->lock, &map_transaction_ops, &entry
|
|
2144
|
+
);
|
|
2145
|
+
pthread_mutex_lock(&source->lock);
|
|
2146
|
+
copy->slots = malloc(source->capacity * sizeof(map_slot_t));
|
|
2147
|
+
if (!copy->slots) {
|
|
2148
|
+
pthread_mutex_unlock(&source->lock);
|
|
2149
|
+
rb_memerror();
|
|
2150
|
+
}
|
|
2151
|
+
memcpy(copy->slots, source->slots, source->capacity * sizeof(map_slot_t));
|
|
2152
|
+
copy->capacity = source->capacity;
|
|
2153
|
+
copy->size = source->size;
|
|
2154
|
+
copy->tombstones = source->tombstones;
|
|
2155
|
+
copy->compare_keys_by_identity = source->compare_keys_by_identity;
|
|
2156
|
+
copy->compare_values_by_identity = source->compare_values_by_identity;
|
|
2157
|
+
copy->initialized = true;
|
|
2158
|
+
entry->version = source->generation;
|
|
2159
|
+
pthread_mutex_unlock(&source->lock);
|
|
2160
|
+
return result;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
static void
|
|
2164
|
+
map_define_methods(VALUE klass)
|
|
2165
|
+
{
|
|
2166
|
+
rb_define_method(klass, "initialize", map_initialize, -1);
|
|
2167
|
+
rb_define_method(klass, "[]", map_get, 1);
|
|
2168
|
+
rb_define_method(klass, "[]=", map_set, 2);
|
|
2169
|
+
rb_define_method(klass, "check_mutation", map_check_mutation, 0);
|
|
2170
|
+
rb_define_method(klass, "prepare_mutation_key", map_prepare_mutation_key, 1);
|
|
2171
|
+
rb_define_method(klass, "fetch", map_fetch, -1);
|
|
2172
|
+
rb_define_method(klass, "get", map_get_with_timeout, -1);
|
|
2173
|
+
rb_define_method(klass, "store", map_store_with_timeout, -1);
|
|
2174
|
+
rb_define_method(klass, "swap", map_swap, -1);
|
|
2175
|
+
rb_define_method(klass, "update", map_update, -1);
|
|
2176
|
+
rb_define_method(klass, "modify", map_modify, 1);
|
|
2177
|
+
rb_define_method(klass, "wait_until_changed", map_wait_until_changed, -1);
|
|
2178
|
+
rb_define_method(klass, "wait_until_non_nil", map_wait_until_non_nil, -1);
|
|
2179
|
+
rb_define_method(klass, "store_if_absent", map_store_if_absent, -1);
|
|
2180
|
+
rb_define_method(klass, "key?", map_key_p, 1);
|
|
2181
|
+
rb_define_method(klass, "delete", map_delete, 1);
|
|
2182
|
+
rb_define_method(klass, "clear", map_clear, 0);
|
|
2183
|
+
rb_define_method(klass, "freeze", map_freeze, 0);
|
|
2184
|
+
rb_define_method(klass, "transaction_snapshot", map_transaction_snapshot, 0);
|
|
2185
|
+
rb_define_method(klass, "transaction_size_snapshot", map_transaction_size_snapshot, 1);
|
|
2186
|
+
rb_define_method(klass, "compare_and_set", map_compare_and_set, -1);
|
|
2187
|
+
rb_define_method(klass, "upsert", map_upsert, -1);
|
|
2188
|
+
rb_define_method(klass, "compare_keys_by_identity?", map_compare_keys_by_identity_p, 0);
|
|
2189
|
+
rb_define_method(klass, "compare_values_by_identity?", map_compare_values_by_identity_p, 0);
|
|
2190
|
+
rb_define_method(klass, "getkey", map_getkey, 1);
|
|
2191
|
+
rb_define_method(klass, "size", map_size, 0);
|
|
2192
|
+
rb_define_method(klass, "keys", map_keys, 0);
|
|
2193
|
+
rb_define_method(klass, "each", map_each, 0);
|
|
2194
|
+
rb_define_method(klass, "each_live", map_each_live, 0);
|
|
2195
|
+
rb_define_method(klass, "each_pair", map_each, 0);
|
|
2196
|
+
rb_define_method(klass, "each_key", map_each_key, 0);
|
|
2197
|
+
rb_define_method(klass, "each_value", map_each_value, 0);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
void
|
|
2202
|
+
containers_init_map(VALUE namespace)
|
|
2203
|
+
{
|
|
2204
|
+
rb_global_variable(&cStrictMapFacade);
|
|
2205
|
+
rb_global_variable(&cUnsharedMapFacade);
|
|
2206
|
+
id_map = rb_intern("@map");
|
|
2207
|
+
id_unwrap_value = rb_intern("unwrap_value");
|
|
2208
|
+
id_wrap_value = rb_intern("wrap_value");
|
|
2209
|
+
rb_global_variable(&cModeMapFacade);
|
|
2210
|
+
rb_global_variable(&cMapEnvelope);
|
|
2211
|
+
rb_global_variable(&map_nil_value);
|
|
2212
|
+
eIsolationError = rb_const_get(rb_cRactor, rb_intern("IsolationError"));
|
|
2213
|
+
map_keep = rb_const_get(namespace, rb_intern("MAP_KEEP"));
|
|
2214
|
+
map_delete_token = rb_const_get(namespace, rb_intern("MAP_DELETE"));
|
|
2215
|
+
rb_global_variable(&map_keep);
|
|
2216
|
+
rb_global_variable(&map_delete_token);
|
|
2217
|
+
cUnsharedKeyLockMap = rb_define_class_under(namespace, "UnsharedKeyLockMap", rb_cObject);
|
|
2218
|
+
rb_define_alloc_func(cUnsharedKeyLockMap, unshared_key_lock_allocate);
|
|
2219
|
+
rb_define_method(cUnsharedKeyLockMap, "initialize", key_lock_initialize, -1);
|
|
2220
|
+
rb_define_method(cUnsharedKeyLockMap, "initialize_copy", key_lock_initialize_copy, 1);
|
|
2221
|
+
rb_define_method(cUnsharedKeyLockMap, "synchronize", key_lock_synchronize, 1);
|
|
2222
|
+
|
|
2223
|
+
cSharedKeyLockMap = rb_define_class_under(namespace, "SharedKeyLockMap", rb_cObject);
|
|
2224
|
+
rb_define_alloc_func(cSharedKeyLockMap, shared_key_lock_allocate);
|
|
2225
|
+
rb_define_method(cSharedKeyLockMap, "initialize", key_lock_initialize, -1);
|
|
2226
|
+
rb_define_method(cSharedKeyLockMap, "initialize_copy", key_lock_initialize_copy, 1);
|
|
2227
|
+
rb_define_method(cSharedKeyLockMap, "synchronize", key_lock_synchronize, 1);
|
|
2228
|
+
rb_define_method(cSharedKeyLockMap, "freeze", containers_raise_unfreezable, 0);
|
|
2229
|
+
|
|
2230
|
+
cMap = rb_define_class_under(namespace, "Map", rb_cObject);
|
|
2231
|
+
rb_define_alloc_func(cMap, map_allocate);
|
|
2232
|
+
map_define_methods(cMap);
|
|
2233
|
+
|
|
2234
|
+
cUnsharedMap = rb_define_class_under(namespace, "UnsharedMap", rb_cObject);
|
|
2235
|
+
rb_define_alloc_func(cUnsharedMap, unshared_map_allocate);
|
|
2236
|
+
map_define_methods(cUnsharedMap);
|
|
2237
|
+
rb_define_singleton_method(
|
|
2238
|
+
namespace, "prepare_map_access", map_prepare_access, 2
|
|
2239
|
+
);
|
|
2240
|
+
}
|