farce 0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +26 -0
- data/CONTRIBUTING.md +71 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1525 -0
- data/SECURITY.md +10 -0
- data/docs/benchmarks.md +185 -0
- data/docs/gems/dry-types.md +290 -0
- data/docs/gems/msgpack.md +70 -0
- data/docs/gems/ractor-shim.md +63 -0
- data/docs/modes.md +628 -0
- data/docs/scopes.md +649 -0
- data/docs/variants.md +346 -0
- data/ext/ext_helper.rb +20 -0
- data/ext/farce/README.md +21 -0
- data/ext/farce/atom.c +1023 -0
- data/ext/farce/bounded_map.c +1683 -0
- data/ext/farce/containers.h +77 -0
- data/ext/farce/counter.c +399 -0
- data/ext/farce/darwin.c +100 -0
- data/ext/farce/depend +12 -0
- data/ext/farce/dict.c +1523 -0
- data/ext/farce/dict.h +152 -0
- data/ext/farce/drivers.c +237 -0
- data/ext/farce/exchanger.c +299 -0
- data/ext/farce/extconf.rb +99 -0
- data/ext/farce/farce.c +359 -0
- data/ext/farce/flag.c +288 -0
- data/ext/farce/io.c +338 -0
- data/ext/farce/lock.c +510 -0
- data/ext/farce/map.c +2240 -0
- data/ext/farce/priority_queue.c +2056 -0
- data/ext/farce/queue.c +1059 -0
- data/ext/farce/reactor.c +820 -0
- data/ext/farce/reactor.h +103 -0
- data/ext/farce/shareable.h +31 -0
- data/ext/farce/signal.c +350 -0
- data/ext/farce/transaction.c +354 -0
- data/ext/farce/transaction.h +40 -0
- data/ext/farce/tree_map.c +1953 -0
- data/ext/farce/trie.c +2020 -0
- data/ext/farce/unshareable.c +155 -0
- data/ext/farce/unshared_io_pool.h +234 -0
- data/ext/farce/unshared_signal.c +263 -0
- data/ext/farce/unshared_wait.h +193 -0
- data/ext/farce/unsupported.c +6 -0
- data/ext/farce/vector.c +1195 -0
- data/ext/farce/weak_map.c +1714 -0
- data/ext/java/org/farce/BoundedMap.java +394 -0
- data/ext/java/org/farce/FiberScheduler.java +141 -0
- data/ext/java/org/farce/PriorityKey.java +45 -0
- data/ext/java/org/farce/PriorityQueue.java +373 -0
- data/ext/java/org/farce/QueueSignal.java +28 -0
- data/ext/rebind/README.md +14 -0
- data/ext/rebind/extconf.rb +10 -0
- data/ext/rebind/rebind.c +186 -0
- data/lib/farce/_yard/internal.rb +13 -0
- data/lib/farce/_yard/macros.rb +61 -0
- data/lib/farce/_yard/ractor.rb +46 -0
- data/lib/farce/abstract/atom.rb +186 -0
- data/lib/farce/abstract/bounded_map.rb +232 -0
- data/lib/farce/abstract/collection.rb +151 -0
- data/lib/farce/abstract/concurrent_map.rb +381 -0
- data/lib/farce/abstract/counter.rb +193 -0
- data/lib/farce/abstract/duplicable_map.rb +229 -0
- data/lib/farce/abstract/exchanger.rb +26 -0
- data/lib/farce/abstract/flag.rb +71 -0
- data/lib/farce/abstract/lazy.rb +115 -0
- data/lib/farce/abstract/lease.rb +104 -0
- data/lib/farce/abstract/lease_map.rb +261 -0
- data/lib/farce/abstract/lease_pool.rb +91 -0
- data/lib/farce/abstract/lfu_map.rb +20 -0
- data/lib/farce/abstract/lru_map.rb +25 -0
- data/lib/farce/abstract/map.rb +345 -0
- data/lib/farce/abstract/molecule.rb +245 -0
- data/lib/farce/abstract/port.rb +74 -0
- data/lib/farce/abstract/priority_queue.rb +117 -0
- data/lib/farce/abstract/queue.rb +269 -0
- data/lib/farce/abstract/scheduler.rb +111 -0
- data/lib/farce/abstract/set.rb +910 -0
- data/lib/farce/abstract/sorted_set.rb +172 -0
- data/lib/farce/abstract/timer_queue.rb +136 -0
- data/lib/farce/abstract/tree_map.rb +269 -0
- data/lib/farce/abstract/value.rb +68 -0
- data/lib/farce/abstract/vector.rb +1126 -0
- data/lib/farce/abstract/weak_atom.rb +32 -0
- data/lib/farce/abstract/weak_key_map.rb +12 -0
- data/lib/farce/abstract/weak_map.rb +13 -0
- data/lib/farce/abstract/weak_set.rb +12 -0
- data/lib/farce/abstract/weak_value_map.rb +12 -0
- data/lib/farce/abstract.rb +17 -0
- data/lib/farce/atom.rb +293 -0
- data/lib/farce/class_mirror.rb +87 -0
- data/lib/farce/clock.rb +121 -0
- data/lib/farce/config.rb +229 -0
- data/lib/farce/counter.rb +71 -0
- data/lib/farce/deduper.rb +122 -0
- data/lib/farce/engine/jruby/bounded_map.rb +314 -0
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
- data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
- data/lib/farce/engine/jruby/map.rb +505 -0
- data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
- data/lib/farce/engine/jruby/signal.rb +147 -0
- data/lib/farce/engine/jruby.rb +63 -0
- data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
- data/lib/farce/engine/jvm/counter.rb +102 -0
- data/lib/farce/engine/jvm/extension.rb +32 -0
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/flag.rb +79 -0
- data/lib/farce/engine/jvm/priority_queue.rb +217 -0
- data/lib/farce/engine/jvm/tree_map.rb +350 -0
- data/lib/farce/engine/jvm/types.rb +180 -0
- data/lib/farce/engine/jvm.rb +19 -0
- data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
- data/lib/farce/engine/ruby/3.4/port.rb +186 -0
- data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
- data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
- data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
- data/lib/farce/engine/ruby/4.0/port.rb +19 -0
- data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
- data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
- data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
- data/lib/farce/engine/ruby/4.1/port.rb +21 -0
- data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
- data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
- data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
- data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
- data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
- data/lib/farce/engine/ruby/shared/lease.rb +26 -0
- data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
- data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
- data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
- data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
- data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
- data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
- data/lib/farce/engine/ruby/shared/vault.rb +144 -0
- data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
- data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
- data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
- data/lib/farce/engine/ruby.rb +130 -0
- data/lib/farce/engine/shared/atom.rb +10 -0
- data/lib/farce/engine/shared/exchanger.rb +130 -0
- data/lib/farce/engine/shared/identity_key.rb +22 -0
- data/lib/farce/engine/shared/lease.rb +10 -0
- data/lib/farce/engine/shared/lease_pool.rb +10 -0
- data/lib/farce/engine/shared/main_scheduler.rb +20 -0
- data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
- data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
- data/lib/farce/engine/shared/port.rb +44 -0
- data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
- data/lib/farce/engine/shared/proxy_owner.rb +43 -0
- data/lib/farce/engine/shared/queue.rb +233 -0
- data/lib/farce/engine/shared/ractor_methods.rb +73 -0
- data/lib/farce/engine/shared/rebindable.rb +15 -0
- data/lib/farce/engine/shared/strict_atom.rb +73 -0
- data/lib/farce/engine/shared/strict_map.rb +117 -0
- data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
- data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
- data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
- data/lib/farce/engine/shared/trie.rb +317 -0
- data/lib/farce/engine/shared/trie_builder.rb +199 -0
- data/lib/farce/engine/shared/unshareable.rb +14 -0
- data/lib/farce/engine/shared/unshared_atom.rb +256 -0
- data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
- data/lib/farce/engine/shared/unshared_queue.rb +18 -0
- data/lib/farce/engine/shared/unshared_signal.rb +21 -0
- data/lib/farce/engine/shared/unshared_vector.rb +377 -0
- data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
- data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
- data/lib/farce/engine/shared/vault.rb +31 -0
- data/lib/farce/engine/shared/vector.rb +83 -0
- data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
- data/lib/farce/engine/shared/weak_atom.rb +20 -0
- data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
- data/lib/farce/engine/shared/weak_map/index.rb +249 -0
- data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
- data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
- data/lib/farce/engine/shared.rb +63 -0
- data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
- data/lib/farce/engine/truffleruby/lock.rb +121 -0
- data/lib/farce/engine/truffleruby/map.rb +656 -0
- data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
- data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
- data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
- data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
- data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
- data/lib/farce/engine/truffleruby/signal.rb +77 -0
- data/lib/farce/engine/truffleruby.rb +148 -0
- data/lib/farce/envelope.rb +335 -0
- data/lib/farce/error.rb +35 -0
- data/lib/farce/exchanger.rb +41 -0
- data/lib/farce/flag.rb +27 -0
- data/lib/farce/integrations/active_support/blank.rb +68 -0
- data/lib/farce/integrations/active_support/clock.rb +17 -0
- data/lib/farce/integrations/active_support/duplicable.rb +78 -0
- data/lib/farce/integrations/active_support/map.rb +175 -0
- data/lib/farce/integrations/active_support/set.rb +60 -0
- data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
- data/lib/farce/integrations/active_support/vector.rb +231 -0
- data/lib/farce/integrations/active_support.rb +9 -0
- data/lib/farce/integrations/activesupport.rb +5 -0
- data/lib/farce/integrations/bson.rb +108 -0
- data/lib/farce/integrations/cbor.rb +51 -0
- data/lib/farce/integrations/concurrent.rb +112 -0
- data/lib/farce/integrations/dry-types.rb +5 -0
- data/lib/farce/integrations/dry_types.rb +694 -0
- data/lib/farce/integrations/json.rb +7 -0
- data/lib/farce/integrations/msgpack.rb +112 -0
- data/lib/farce/integrations/oj.rb +69 -0
- data/lib/farce/integrations/psych.rb +191 -0
- data/lib/farce/integrations/ractor-sharing.rb +5 -0
- data/lib/farce/integrations/ractor-tmvar.rb +5 -0
- data/lib/farce/integrations/ractor_sharing.rb +172 -0
- data/lib/farce/integrations/ractor_tmvar.rb +64 -0
- data/lib/farce/integrations/shared/to_json.rb +42 -0
- data/lib/farce/integrations/sorted_set.rb +11 -0
- data/lib/farce/integrations/weakref.rb +38 -0
- data/lib/farce/integrations/yajl.rb +10 -0
- data/lib/farce/integrations.rb +156 -0
- data/lib/farce/internal/_frozen_config.rb +11 -0
- data/lib/farce/internal/autoloads.rb +47 -0
- data/lib/farce/internal/blocking_priority_queue.rb +122 -0
- data/lib/farce/internal/converter.rb +106 -0
- data/lib/farce/internal/copyable.rb +31 -0
- data/lib/farce/internal/delegation.rb +20 -0
- data/lib/farce/internal/external_transaction.rb +59 -0
- data/lib/farce/internal/fake_ractor.rb +179 -0
- data/lib/farce/internal/freeze.rb +118 -0
- data/lib/farce/internal/inspect.rb +157 -0
- data/lib/farce/internal/key_lock_map.rb +29 -0
- data/lib/farce/internal/key_normalizer.rb +289 -0
- data/lib/farce/internal/lease_initialization.rb +115 -0
- data/lib/farce/internal/lease_map.rb +441 -0
- data/lib/farce/internal/lease_pool_state.rb +228 -0
- data/lib/farce/internal/lease_state.rb +342 -0
- data/lib/farce/internal/lease_waiting.rb +13 -0
- data/lib/farce/internal/managed_queue.rb +26 -0
- data/lib/farce/internal/map_value_modes.rb +227 -0
- data/lib/farce/internal/marshal_support.rb +227 -0
- data/lib/farce/internal/mixin.rb +20 -0
- data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
- data/lib/farce/internal/noncopyable.rb +17 -0
- data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
- data/lib/farce/internal/pool_supervisor.rb +41 -0
- data/lib/farce/internal/pool_worker.rb +61 -0
- data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
- data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
- data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
- data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
- data/lib/farce/internal/portable_transaction.rb +164 -0
- data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
- data/lib/farce/internal/reservation_waiting.rb +24 -0
- data/lib/farce/internal/scheduled_task.rb +48 -0
- data/lib/farce/internal/scheduler_io.rb +171 -0
- data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
- data/lib/farce/internal/select_scheduler.rb +235 -0
- data/lib/farce/internal/storage.rb +162 -0
- data/lib/farce/internal/strict_lease.rb +30 -0
- data/lib/farce/internal/strict_lease_map.rb +20 -0
- data/lib/farce/internal/strict_lease_pool.rb +29 -0
- data/lib/farce/internal/thread_pool.rb +166 -0
- data/lib/farce/internal/transaction_conflict.rb +9 -0
- data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
- data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
- data/lib/farce/internal/undefined.rb +22 -0
- data/lib/farce/internal/unshared_lease.rb +29 -0
- data/lib/farce/internal/unshared_lease_pool.rb +34 -0
- data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
- data/lib/farce/internal/value_serialization.rb +13 -0
- data/lib/farce/internal/weak_map_value_modes.rb +58 -0
- data/lib/farce/internal/weak_mode_manager.rb +26 -0
- data/lib/farce/internal.rb +129 -0
- data/lib/farce/lazy.rb +100 -0
- data/lib/farce/lazy_ref.rb +40 -0
- data/lib/farce/lease.rb +28 -0
- data/lib/farce/lease_map.rb +34 -0
- data/lib/farce/lease_pool.rb +29 -0
- data/lib/farce/lfu_map.rb +53 -0
- data/lib/farce/local/atom.rb +23 -0
- data/lib/farce/local/counter.rb +88 -0
- data/lib/farce/local/flag.rb +65 -0
- data/lib/farce/local/lazy.rb +38 -0
- data/lib/farce/local/lazy_ref.rb +36 -0
- data/lib/farce/local/lease.rb +77 -0
- data/lib/farce/local/lease_map.rb +80 -0
- data/lib/farce/local/lease_pool.rb +38 -0
- data/lib/farce/local/lfu_map.rb +55 -0
- data/lib/farce/local/lru_map.rb +62 -0
- data/lib/farce/local/map.rb +47 -0
- data/lib/farce/local/molecule.rb +45 -0
- data/lib/farce/local/priority_queue.rb +53 -0
- data/lib/farce/local/queue.rb +32 -0
- data/lib/farce/local/scoped.rb +166 -0
- data/lib/farce/local/set.rb +18 -0
- data/lib/farce/local/sorted_set.rb +50 -0
- data/lib/farce/local/timer_queue.rb +41 -0
- data/lib/farce/local/tree_map.rb +47 -0
- data/lib/farce/local/vector.rb +30 -0
- data/lib/farce/local/weak_atom.rb +23 -0
- data/lib/farce/local/weak_key_map.rb +39 -0
- data/lib/farce/local/weak_map.rb +39 -0
- data/lib/farce/local/weak_set.rb +18 -0
- data/lib/farce/local/weak_value_map.rb +39 -0
- data/lib/farce/local.rb +35 -0
- data/lib/farce/lock.rb +54 -0
- data/lib/farce/lru_map.rb +63 -0
- data/lib/farce/map.rb +58 -0
- data/lib/farce/mode_manager.rb +142 -0
- data/lib/farce/molecule.rb +60 -0
- data/lib/farce/mutable.rb +171 -0
- data/lib/farce/pool.rb +332 -0
- data/lib/farce/port.rb +134 -0
- data/lib/farce/priority_queue.rb +70 -0
- data/lib/farce/proxy/register.rb +101 -0
- data/lib/farce/proxy/supervisor.rb +137 -0
- data/lib/farce/proxy/wrapper.rb +49 -0
- data/lib/farce/proxy.rb +291 -0
- data/lib/farce/queue.rb +60 -0
- data/lib/farce/ractor.rb +269 -0
- data/lib/farce/read_write_lock.rb +256 -0
- data/lib/farce/reference.rb +158 -0
- data/lib/farce/resolv/dns.rb +42 -0
- data/lib/farce/resolv.rb +85 -0
- data/lib/farce/scheduler.rb +536 -0
- data/lib/farce/set.rb +39 -0
- data/lib/farce/shareable.rb +150 -0
- data/lib/farce/signal.rb +97 -0
- data/lib/farce/sorted_set.rb +36 -0
- data/lib/farce/strict/atom.rb +34 -0
- data/lib/farce/strict/counter.rb +10 -0
- data/lib/farce/strict/exchanger.rb +25 -0
- data/lib/farce/strict/flag.rb +10 -0
- data/lib/farce/strict/lazy.rb +26 -0
- data/lib/farce/strict/lazy_ref.rb +26 -0
- data/lib/farce/strict/lease.rb +27 -0
- data/lib/farce/strict/lease_map.rb +33 -0
- data/lib/farce/strict/lease_pool.rb +28 -0
- data/lib/farce/strict/lfu_map.rb +27 -0
- data/lib/farce/strict/lru_map.rb +28 -0
- data/lib/farce/strict/map.rb +54 -0
- data/lib/farce/strict/molecule.rb +16 -0
- data/lib/farce/strict/port.rb +40 -0
- data/lib/farce/strict/priority_queue.rb +20 -0
- data/lib/farce/strict/queue.rb +17 -0
- data/lib/farce/strict/set.rb +14 -0
- data/lib/farce/strict/sorted_set.rb +19 -0
- data/lib/farce/strict/timer_queue.rb +13 -0
- data/lib/farce/strict/tree_map.rb +27 -0
- data/lib/farce/strict/vector.rb +24 -0
- data/lib/farce/strict/weak_atom.rb +31 -0
- data/lib/farce/strict/weak_key_map.rb +49 -0
- data/lib/farce/strict/weak_map.rb +49 -0
- data/lib/farce/strict/weak_set.rb +14 -0
- data/lib/farce/strict/weak_value_map.rb +48 -0
- data/lib/farce/strict.rb +28 -0
- data/lib/farce/system.rb +40 -0
- data/lib/farce/thread_scheduler.rb +70 -0
- data/lib/farce/timer_queue.rb +66 -0
- data/lib/farce/transaction/atom.rb +73 -0
- data/lib/farce/transaction/map.rb +25 -0
- data/lib/farce/transaction/map_operations.rb +143 -0
- data/lib/farce/transaction/molecule.rb +90 -0
- data/lib/farce/transaction/mutable.rb +61 -0
- data/lib/farce/transaction/set.rb +17 -0
- data/lib/farce/transaction/set_operations.rb +48 -0
- data/lib/farce/transaction/sorted_set.rb +18 -0
- data/lib/farce/transaction/tree_map.rb +78 -0
- data/lib/farce/transaction/vector.rb +139 -0
- data/lib/farce/transaction/wrapper.rb +202 -0
- data/lib/farce/transaction.rb +343 -0
- data/lib/farce/tree_map.rb +56 -0
- data/lib/farce/unsafe/lfu_map.rb +36 -0
- data/lib/farce/unsafe/lru_map.rb +36 -0
- data/lib/farce/unsafe/tree_map.rb +21 -0
- data/lib/farce/unsafe.rb +29 -0
- data/lib/farce/unshareable.rb +67 -0
- data/lib/farce/unshared/atom.rb +24 -0
- data/lib/farce/unshared/counter.rb +10 -0
- data/lib/farce/unshared/flag.rb +10 -0
- data/lib/farce/unshared/lazy.rb +38 -0
- data/lib/farce/unshared/lazy_ref.rb +26 -0
- data/lib/farce/unshared/lease.rb +27 -0
- data/lib/farce/unshared/lease_map.rb +26 -0
- data/lib/farce/unshared/lease_pool.rb +25 -0
- data/lib/farce/unshared/lfu_map.rb +20 -0
- data/lib/farce/unshared/lru_map.rb +20 -0
- data/lib/farce/unshared/map.rb +49 -0
- data/lib/farce/unshared/molecule.rb +16 -0
- data/lib/farce/unshared/priority_queue.rb +16 -0
- data/lib/farce/unshared/queue.rb +27 -0
- data/lib/farce/unshared/set.rb +14 -0
- data/lib/farce/unshared/sorted_set.rb +28 -0
- data/lib/farce/unshared/timer_queue.rb +16 -0
- data/lib/farce/unshared/tree_map.rb +21 -0
- data/lib/farce/unshared/vector.rb +19 -0
- data/lib/farce/unshared/weak_atom.rb +31 -0
- data/lib/farce/unshared/weak_key_map.rb +44 -0
- data/lib/farce/unshared/weak_map.rb +44 -0
- data/lib/farce/unshared/weak_set.rb +14 -0
- data/lib/farce/unshared/weak_value_map.rb +43 -0
- data/lib/farce/unshared.rb +28 -0
- data/lib/farce/vector.rb +228 -0
- data/lib/farce/version.rb +8 -0
- data/lib/farce/walker/definitions.rb +186 -0
- data/lib/farce/walker/modification.rb +355 -0
- data/lib/farce/walker.rb +319 -0
- data/lib/farce/weak_atom.rb +121 -0
- data/lib/farce/weak_key_map.rb +31 -0
- data/lib/farce/weak_map.rb +33 -0
- data/lib/farce/weak_ref.rb +67 -0
- data/lib/farce/weak_set.rb +82 -0
- data/lib/farce/weak_value.rb +195 -0
- data/lib/farce/weak_value_map.rb +32 -0
- data/lib/farce.rb +519 -0
- metadata +435 -8
data/lib/farce/config.rb
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
require "farce/internal/autoloads"
|
|
6
|
+
|
|
7
|
+
# Checks against RUBY_ENGINE, RUBY_VERSION, RUBY_PLATFORM, and ENV are explicitly allowed in this file.
|
|
8
|
+
# Checks against RUBY_ENGINE and similar should be avoided within lib outside this file, lib/farce/engine,
|
|
9
|
+
# and the one require in lib/farce.rb
|
|
10
|
+
# Checks against ENV should be avoided entirely within lib outside of this file
|
|
11
|
+
#-
|
|
12
|
+
|
|
13
|
+
module Farce
|
|
14
|
+
# Configuration for Farce. Will be frozen by Farce once it is accessed, to prevent accidental changes and make it
|
|
15
|
+
# shareable across Ractors.
|
|
16
|
+
#
|
|
17
|
+
# ```ruby
|
|
18
|
+
# require "farce"
|
|
19
|
+
#
|
|
20
|
+
# Farce.config do |c|
|
|
21
|
+
# c.fiber_scheduler = :select
|
|
22
|
+
# end
|
|
23
|
+
# ```
|
|
24
|
+
class Config
|
|
25
|
+
# Automatically load integrations when their dependencies are loaded. Defaults to true.
|
|
26
|
+
# Can also be set via `FARCE_AUTOLOAD_INTEGRATIONS` (`true`/`false` or `1`/`0`).
|
|
27
|
+
# Set before requiring Farce to also disable startup integration loading.
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
attr_reader :autoload_integrations
|
|
30
|
+
|
|
31
|
+
# Maximum workers in a pool created on the main Ractor. Workers start lazily.
|
|
32
|
+
# Can also be set via `FARCE_MAIN_THREAD_POOL_SIZE`. Defaults to 4.
|
|
33
|
+
attr_reader :main_thread_pool_size
|
|
34
|
+
|
|
35
|
+
# Maximum workers in a pool created on another Ractor. Workers start lazily.
|
|
36
|
+
# Can also be set via `FARCE_ADDITIONAL_THREAD_POOL_SIZE`. Defaults to 2.
|
|
37
|
+
attr_reader :additional_thread_pool_size
|
|
38
|
+
|
|
39
|
+
# @yield [config] block to modify the configuration
|
|
40
|
+
# @yieldparam config [Config] the configuration object to configure
|
|
41
|
+
def initialize
|
|
42
|
+
yield self if block_given?
|
|
43
|
+
self.autoload_integrations = ENV.fetch("FARCE_AUTOLOAD_INTEGRATIONS", true) if autoload_integrations.nil?
|
|
44
|
+
self.fiber_scheduler = ENV["FARCE_FIBER_SCHEDULER"] unless instance_variable_defined?(:@fiber_scheduler)
|
|
45
|
+
self.main_thread_pool_size ||= ENV.fetch("FARCE_MAIN_THREAD_POOL_SIZE", 4)
|
|
46
|
+
self.additional_thread_pool_size ||= ENV.fetch("FARCE_ADDITIONAL_THREAD_POOL_SIZE", 2)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
def marshal_dump
|
|
51
|
+
[1, autoload_integrations, fiber_scheduler, main_thread_pool_size, additional_thread_pool_size, frozen?]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @api private
|
|
55
|
+
def marshal_load(data)
|
|
56
|
+
autoload, scheduler, main_size, additional_size, frozen = Internal::MarshalSupport.payload(data, 5)
|
|
57
|
+
|
|
58
|
+
self.autoload_integrations = autoload
|
|
59
|
+
self.fiber_scheduler = scheduler
|
|
60
|
+
self.main_thread_pool_size = main_size
|
|
61
|
+
self.additional_thread_pool_size = additional_size
|
|
62
|
+
freeze if frozen
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Enable or disable automatic integration loading. Already loaded integrations remain active.
|
|
66
|
+
# @param value [Boolean, String] true, false, or their environment variable representations.
|
|
67
|
+
# @raise [ArgumentError] If the value is not a supported boolean.
|
|
68
|
+
def autoload_integrations=(value)
|
|
69
|
+
@autoload_integrations = case value
|
|
70
|
+
when true, "true", "1" then true
|
|
71
|
+
when false, "false", "0" then false
|
|
72
|
+
else raise ArgumentError, "autoload_integrations must be a boolean"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Default fiber scheduler for Pool and Scheduler.
|
|
77
|
+
# Explicit constructor blocks override it.
|
|
78
|
+
#
|
|
79
|
+
# Uses `FARCE_FIBER_SCHEDULER` unless assigned explicitly, including an explicit nil.
|
|
80
|
+
#
|
|
81
|
+
# Built-in choices are `:auto`, `:native`, `:jvm`, `:select`, `:kqueue`, `:epoll`,
|
|
82
|
+
# `:io_uring`, and `:nio`. Strings are also accepted. `nil` or an empty string are treated like `:auto`.
|
|
83
|
+
#
|
|
84
|
+
# Other names resolve constants, such as `carbon_fiber` to `CarbonFiber`.
|
|
85
|
+
# If the constant is not defined, it attempts to require the corresponding file.
|
|
86
|
+
# Modules must define their own `Scheduler` class.
|
|
87
|
+
#
|
|
88
|
+
# Classes are constructed without arguments in each worker. Procs must be shareable.
|
|
89
|
+
# Custom schedulers must support the executor used by the Pool or Scheduler.
|
|
90
|
+
#
|
|
91
|
+
# @return [Symbol, Class, Proc] normalized choice
|
|
92
|
+
def fiber_scheduler(&constructor)
|
|
93
|
+
self.fiber_scheduler = constructor if constructor
|
|
94
|
+
@fiber_scheduler
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Sets the default scheduler. Use `config.fiber_scheduler { MyScheduler.new }`
|
|
98
|
+
# or assign a proc to configure construction. Invalid assignments preserve the old value.
|
|
99
|
+
def fiber_scheduler=(value)
|
|
100
|
+
raise FrozenError, "can't modify frozen Config" if frozen?
|
|
101
|
+
@fiber_scheduler = normalize_fiber_scheduler(value)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Implementation used by Farce's built-in scheduler.
|
|
105
|
+
# @return [Symbol] one of `:native`, `:select`, or `:jvm`
|
|
106
|
+
def fiber_scheduler_implementation
|
|
107
|
+
case @fiber_scheduler
|
|
108
|
+
when :native, :jvm, :select then @fiber_scheduler
|
|
109
|
+
when :kqueue, :epoll, :io_uring then :native
|
|
110
|
+
when :nio then :jvm
|
|
111
|
+
else detected_fiber_scheduler_implementation
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# IO backend used by the built-in scheduler. Explicit `backend:` options override it.
|
|
116
|
+
# Availability is checked when a scheduler is created.
|
|
117
|
+
# @return [Symbol] the configured backend or `:auto`
|
|
118
|
+
def io_backend
|
|
119
|
+
case @fiber_scheduler
|
|
120
|
+
when :kqueue, :epoll, :io_uring, :nio, :select then @fiber_scheduler
|
|
121
|
+
else :auto
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Constructor for a custom scheduler, or nil for the built-in scheduler.
|
|
126
|
+
# @api private
|
|
127
|
+
def fiber_scheduler_constructor
|
|
128
|
+
case @fiber_scheduler
|
|
129
|
+
when Class
|
|
130
|
+
require "farce" unless Farce.const_defined?(:Ractor, false)
|
|
131
|
+
Ractor.shareable_proc(self: @fiber_scheduler) { new }
|
|
132
|
+
when Proc then @fiber_scheduler
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Sets a positive worker limit for pools created on the main Ractor.
|
|
137
|
+
def main_thread_pool_size=(value)
|
|
138
|
+
@main_thread_pool_size = normalize_thread_pool_size(value)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Sets a positive worker limit for pools created on other Ractors.
|
|
142
|
+
def additional_thread_pool_size=(value)
|
|
143
|
+
@additional_thread_pool_size = normalize_thread_pool_size(value)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Called internally once the configuration is applied.
|
|
147
|
+
def freeze
|
|
148
|
+
return self if frozen?
|
|
149
|
+
super # need to call this first to avoid infinite recursion in the next line
|
|
150
|
+
::Ractor.make_shareable(self) if defined?(::Ractor.make_shareable)
|
|
151
|
+
self
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def normalize_thread_pool_size(value)
|
|
157
|
+
size = value.is_a?(String) ? Integer(value, 10) : value
|
|
158
|
+
raise ArgumentError, "thread pool size must be a positive integer" unless size.is_a?(Integer) && size.positive?
|
|
159
|
+
size
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def normalize_fiber_scheduler(value, original = value)
|
|
163
|
+
case value
|
|
164
|
+
when nil, "", "auto", :auto
|
|
165
|
+
return :auto
|
|
166
|
+
|
|
167
|
+
when :native, :jvm, :select, :kqueue, :epoll, :io_uring, :nio, Class
|
|
168
|
+
return value
|
|
169
|
+
|
|
170
|
+
when "native", "jvm", "select", "kqueue", "epoll", "io_uring", "nio"
|
|
171
|
+
return value.to_sym
|
|
172
|
+
|
|
173
|
+
when Symbol, String
|
|
174
|
+
constant = Internal::Autoloads.inflect(value.to_s)
|
|
175
|
+
path = original.to_s if original.is_a?(String) || original.is_a?(Symbol)
|
|
176
|
+
|
|
177
|
+
begin
|
|
178
|
+
require path if path && !Object.const_defined?(constant)
|
|
179
|
+
rescue LoadError => e
|
|
180
|
+
raise e unless e.path == path
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
value = Object.const_get(constant)
|
|
184
|
+
return normalize_fiber_scheduler(value, original) if value.is_a?(Module) || value.is_a?(Proc)
|
|
185
|
+
|
|
186
|
+
when Module
|
|
187
|
+
if value.const_defined?(:Scheduler, false)
|
|
188
|
+
scheduler = value.const_get(:Scheduler, false)
|
|
189
|
+
return scheduler if scheduler.is_a?(Class)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
when Proc
|
|
193
|
+
require "farce" unless Farce.const_defined?(:Ractor, false)
|
|
194
|
+
return Ractor.shareable?(value) ? value : Ractor.shareable_proc(&value)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
raise ArgumentError, "invalid fiber scheduler: #{value.inspect}"
|
|
198
|
+
rescue NameError => e
|
|
199
|
+
raise ArgumentError, "unknown fiber scheduler: #{original.inspect} (#{e.message})"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def detected_fiber_scheduler_implementation
|
|
203
|
+
case RUBY_ENGINE
|
|
204
|
+
when "ruby" then RUBY_PLATFORM.match?(/linux|darwin|bsd/) ? :native : :select
|
|
205
|
+
when "jruby" then :jvm
|
|
206
|
+
else :select
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# shareable_constant_value: none
|
|
212
|
+
CONFIG = Config.new
|
|
213
|
+
private_constant :CONFIG
|
|
214
|
+
|
|
215
|
+
# Configures Farce.
|
|
216
|
+
# @example
|
|
217
|
+
# Farce.configure do |config|
|
|
218
|
+
# config.fiber_scheduler = :carbon_fiber
|
|
219
|
+
# end
|
|
220
|
+
# @yield [config] the configuration object
|
|
221
|
+
def self.configure = yield CONFIG
|
|
222
|
+
|
|
223
|
+
# @overload config
|
|
224
|
+
# @return [Config] the global Farce configuration
|
|
225
|
+
def self.config(&)
|
|
226
|
+
configure(&) if block_given?
|
|
227
|
+
CONFIG
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# Ractor-shareable, atomic counter.
|
|
7
|
+
# This is a stateful, numeric object.
|
|
8
|
+
#
|
|
9
|
+
# Values are converted to Integers. On CRuby, the stored value and arithmetic
|
|
10
|
+
# deltas must fit in a signed 64-bit integer. An overflow raises RangeError without
|
|
11
|
+
# changing the stored value. Conditional-operation bounds may be wider, but a
|
|
12
|
+
# resulting stored value must still fit.
|
|
13
|
+
#
|
|
14
|
+
# On CRuby, subclasses must not add Ruby instance variables.
|
|
15
|
+
#
|
|
16
|
+
# @!method initial
|
|
17
|
+
# @return [Integer] The initial value retained by reset and copies.
|
|
18
|
+
#
|
|
19
|
+
# @!method value
|
|
20
|
+
# @return [Integer] The current value of the counter.
|
|
21
|
+
#
|
|
22
|
+
# @!method increment(by = 1)
|
|
23
|
+
# Increment the counter by the given amount (default is 1).
|
|
24
|
+
# @param [Numeric, String, #to_int] by The amount, converted to an Integer.
|
|
25
|
+
# @return [self] Returns self for chaining.
|
|
26
|
+
#
|
|
27
|
+
# @!method decrement(by = 1)
|
|
28
|
+
# Decrement the counter by the given amount (default is 1).
|
|
29
|
+
# @param [Numeric, String, #to_int] by The amount, converted to an Integer.
|
|
30
|
+
# @return [self] Returns self for chaining.
|
|
31
|
+
#
|
|
32
|
+
# @example Cross-Ractor counting
|
|
33
|
+
# counter = Farce::Counter.new
|
|
34
|
+
# counter.value # => 0
|
|
35
|
+
#
|
|
36
|
+
# # Increase the counter by 1
|
|
37
|
+
# counter.increment
|
|
38
|
+
# counter.value # => 1
|
|
39
|
+
#
|
|
40
|
+
# # Increase the counter by 5 on another Ractor
|
|
41
|
+
# Ractor.new(counter) { it.add(5) }
|
|
42
|
+
#
|
|
43
|
+
# # Give the other ractor time to run
|
|
44
|
+
# sleep 0.1
|
|
45
|
+
#
|
|
46
|
+
# counter.value # => 6
|
|
47
|
+
#
|
|
48
|
+
# @example Counter as a numeric value
|
|
49
|
+
# counter = Farce::Counter.new(10.0)
|
|
50
|
+
# counter.to_i # => 10
|
|
51
|
+
# counter + 5 # => 15
|
|
52
|
+
# 2.5 * counter # => 25.0
|
|
53
|
+
#
|
|
54
|
+
# require "active_support/all"
|
|
55
|
+
# counter.minutes.to_i # => 600
|
|
56
|
+
class Counter < Internal::Counter
|
|
57
|
+
include Abstract::Counter
|
|
58
|
+
include Shareable::Native
|
|
59
|
+
|
|
60
|
+
# @param [Numeric, String, #to_int] value The initial value, converted to an Integer.
|
|
61
|
+
def initialize(value = 0)
|
|
62
|
+
raise FrozenError.new("can't initialize a frozen counter", receiver: self) if frozen?
|
|
63
|
+
super(Integer(value))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
alias subtract decrement
|
|
67
|
+
alias remove decrement
|
|
68
|
+
|
|
69
|
+
private :change_signal
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# Reuse equal frozen values throughout object graphs.
|
|
7
|
+
# Plain strings use Ruby's string deduplication. Arrays, hashes, and Ruby Sets are frozen
|
|
8
|
+
# and cached by default. Other objects are traversed but are not cached unless
|
|
9
|
+
# their class matches {#store}. Modules and classes are skipped by default.
|
|
10
|
+
#
|
|
11
|
+
# Cached objects are held weakly. Keep a reference to a returned object if its
|
|
12
|
+
# identity matters. Shareable results can be reused across Ractors. Other
|
|
13
|
+
# results are cached within the current Ractor.
|
|
14
|
+
#
|
|
15
|
+
# Cyclic containers are frozen after their references are connected and are
|
|
16
|
+
# not cached. This keeps canonical replacements from breaking back references.
|
|
17
|
+
#
|
|
18
|
+
# @example Use an independent cache
|
|
19
|
+
# deduper = Farce::Deduper.new
|
|
20
|
+
# first = deduper.dedup(["name"], copy: true)
|
|
21
|
+
# deduper.dedup(["name"]).equal?(first) # => true
|
|
22
|
+
class Deduper
|
|
23
|
+
include Shareable::Immutable
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
# Create an independent cache with the default skip and store classes.
|
|
27
|
+
def initialize
|
|
28
|
+
@shared = Strict::WeakKeyMap.new
|
|
29
|
+
@local = Local::WeakKeyMap.new
|
|
30
|
+
@skip = Strict::Vector[Module]
|
|
31
|
+
@store = Strict::Vector[Hash, Array, ::Set]
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
def marshal_dump = [1, @skip.to_a, @store.to_a]
|
|
37
|
+
|
|
38
|
+
# @api private
|
|
39
|
+
def marshal_load(data)
|
|
40
|
+
skip, store = Internal::MarshalSupport.payload(data, 2)
|
|
41
|
+
@shared = Strict::WeakKeyMap.new
|
|
42
|
+
@local = Local::WeakKeyMap.new
|
|
43
|
+
@skip = Strict::Vector.new(skip)
|
|
44
|
+
@store = Strict::Vector.new(store)
|
|
45
|
+
publish_shareable
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Exclude matching objects and their children from subsequent calls.
|
|
49
|
+
# @param modules [Array<Module>] classes or modules matched with is_a?
|
|
50
|
+
# @return [self]
|
|
51
|
+
def skip(*modules)
|
|
52
|
+
@skip.concat(modules)
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Also freeze and cache objects matching these classes or modules.
|
|
57
|
+
# Define consistent hash, eql?, and == methods for custom value classes.
|
|
58
|
+
# These methods must remain stable after deduplication.
|
|
59
|
+
# @param modules [Array<Module>] classes or modules matched with is_a?
|
|
60
|
+
# @return [self]
|
|
61
|
+
def store(*modules)
|
|
62
|
+
@store.concat(modules)
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Replace equal values with cached instances, including nested keys and values.
|
|
67
|
+
# By default, mutable containers are updated and cached containers are frozen.
|
|
68
|
+
# Use copy: true to preserve the input. Skipped objects are always returned
|
|
69
|
+
# unchanged. Strings are deduplicated before skip rules are checked.
|
|
70
|
+
# Noncopyable coordination objects follow {Walker.modify}'s in-place behavior.
|
|
71
|
+
# @param object [Object] the root object
|
|
72
|
+
# @param copy [Boolean, Symbol] copying policy passed to {Walker.modify}
|
|
73
|
+
# @param skip [Module, Array<Module>, nil] additional exclusions for this call
|
|
74
|
+
# @return [Object] the deduplicated result
|
|
75
|
+
# @see Walker.modify
|
|
76
|
+
def dedup(object, copy: false, skip: nil)
|
|
77
|
+
skip = skip ? [*@skip, *skip] : @skip.to_a
|
|
78
|
+
dedup!(object, skip, @store.to_a, copy)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def dedup!(object, skip, store, copy, walker = nil)
|
|
84
|
+
return -object if object.instance_of?(String)
|
|
85
|
+
return object unless Internal.garbage_collectable?(object)
|
|
86
|
+
|
|
87
|
+
if skip.any? { object.is_a?(it) }
|
|
88
|
+
walker&.skip(object)
|
|
89
|
+
return object
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if !walker || walker.hashable?(object)
|
|
93
|
+
shareable = Ractor.shareable?(object)
|
|
94
|
+
canonical = (shareable ? @shared : @local).getkey(object)
|
|
95
|
+
return canonical if canonical
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
return Walker.modify(object, copy:, freeze: false) { dedup!(_1, skip, store, copy, _2) } unless walker
|
|
99
|
+
|
|
100
|
+
canonical = walker.traverse
|
|
101
|
+
return canonical unless store.any? { canonical.is_a?(it) }
|
|
102
|
+
|
|
103
|
+
walker.freeze_result
|
|
104
|
+
walker.finalize do |result, cyclic|
|
|
105
|
+
cyclic ? result : intern(result)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def intern(canonical)
|
|
110
|
+
if Ractor.shareable?(canonical)
|
|
111
|
+
@shared.store_if_absent(canonical) { true }
|
|
112
|
+
canonical = @shared.getkey(canonical) || canonical
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
@local[canonical] = true
|
|
116
|
+
canonical
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
DEDUPER = Deduper.new
|
|
121
|
+
private_constant :DEDUPER
|
|
122
|
+
end
|