farce 0.0.1.alpha2-x86-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/flag.c
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#include "containers.h"
|
|
2
|
+
#include "transaction.h"
|
|
3
|
+
|
|
4
|
+
#include <stdatomic.h>
|
|
5
|
+
|
|
6
|
+
#if ATOMIC_BOOL_LOCK_FREE == 2
|
|
7
|
+
#define CONTAINERS_FLAG_LOCK_FREE 1
|
|
8
|
+
#else
|
|
9
|
+
#define CONTAINERS_FLAG_LOCK_FREE 0
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
static VALUE cFlag;
|
|
13
|
+
|
|
14
|
+
typedef struct {
|
|
15
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
16
|
+
atomic_bool value;
|
|
17
|
+
#else
|
|
18
|
+
pthread_mutex_t lock;
|
|
19
|
+
bool value;
|
|
20
|
+
#endif
|
|
21
|
+
bool initialized;
|
|
22
|
+
} flag_t;
|
|
23
|
+
|
|
24
|
+
/* On platforms with always-lock-free boolean atomics, flag operations only
|
|
25
|
+
* linearize this boolean value. Relaxed ordering does not publish or
|
|
26
|
+
* synchronize accesses to any other shared state. Platforms without that
|
|
27
|
+
* guarantee use a mutex. */
|
|
28
|
+
|
|
29
|
+
static void
|
|
30
|
+
flag_free(void *pointer)
|
|
31
|
+
{
|
|
32
|
+
#if !CONTAINERS_FLAG_LOCK_FREE
|
|
33
|
+
flag_t *flag = pointer;
|
|
34
|
+
if (flag) pthread_mutex_destroy(&flag->lock);
|
|
35
|
+
#endif
|
|
36
|
+
ruby_xfree(pointer);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static size_t
|
|
40
|
+
flag_memsize(const void *pointer)
|
|
41
|
+
{
|
|
42
|
+
return pointer ? sizeof(flag_t) : 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static const rb_data_type_t flag_type = {
|
|
46
|
+
.wrap_struct_name = "Ractor::Containers::Flag",
|
|
47
|
+
.function = {
|
|
48
|
+
.dfree = flag_free,
|
|
49
|
+
.dsize = flag_memsize,
|
|
50
|
+
},
|
|
51
|
+
.flags = RUBY_TYPED_FROZEN_SHAREABLE,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
static VALUE
|
|
55
|
+
flag_boolean(bool value)
|
|
56
|
+
{
|
|
57
|
+
return value ? Qtrue : Qfalse;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static flag_t *
|
|
61
|
+
get_flag(VALUE self)
|
|
62
|
+
{
|
|
63
|
+
flag_t *flag;
|
|
64
|
+
TypedData_Get_Struct(self, flag_t, &flag_type, flag);
|
|
65
|
+
if (!flag->initialized) rb_raise(rb_eRuntimeError, "uninitialized Flag");
|
|
66
|
+
return flag;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static VALUE
|
|
70
|
+
flag_allocate(VALUE klass)
|
|
71
|
+
{
|
|
72
|
+
flag_t *flag;
|
|
73
|
+
VALUE object = TypedData_Make_Struct(klass, flag_t, &flag_type, flag);
|
|
74
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
75
|
+
atomic_init(&flag->value, false);
|
|
76
|
+
#else
|
|
77
|
+
pthread_mutex_init(&flag->lock, NULL);
|
|
78
|
+
flag->value = false;
|
|
79
|
+
#endif
|
|
80
|
+
flag->initialized = false;
|
|
81
|
+
return object;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static VALUE
|
|
85
|
+
flag_initialize(int argc, VALUE *argv, VALUE self)
|
|
86
|
+
{
|
|
87
|
+
flag_t *flag;
|
|
88
|
+
|
|
89
|
+
rb_check_arity(argc, 0, 1);
|
|
90
|
+
VALUE initial = argc == 0 ? Qfalse : argv[0];
|
|
91
|
+
TypedData_Get_Struct(self, flag_t, &flag_type, flag);
|
|
92
|
+
if (flag->initialized) rb_raise(rb_eRuntimeError, "Flag is already initialized");
|
|
93
|
+
rb_check_frozen(self);
|
|
94
|
+
|
|
95
|
+
bool value = containers_strict_bool(initial, "initial value");
|
|
96
|
+
rb_check_frozen(self);
|
|
97
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
98
|
+
atomic_store_explicit(&flag->value, value, memory_order_relaxed);
|
|
99
|
+
#else
|
|
100
|
+
flag->value = value;
|
|
101
|
+
#endif
|
|
102
|
+
flag->initialized = true;
|
|
103
|
+
return containers_publish_native_reference_free(self);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static VALUE
|
|
107
|
+
flag_value(VALUE self)
|
|
108
|
+
{
|
|
109
|
+
flag_t *flag = get_flag(self);
|
|
110
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
111
|
+
bool value = atomic_load_explicit(&flag->value, memory_order_relaxed);
|
|
112
|
+
#else
|
|
113
|
+
pthread_mutex_lock(&flag->lock);
|
|
114
|
+
bool value = flag->value;
|
|
115
|
+
pthread_mutex_unlock(&flag->lock);
|
|
116
|
+
#endif
|
|
117
|
+
return flag_boolean(value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static VALUE
|
|
121
|
+
flag_initialize_copy(VALUE self, VALUE other)
|
|
122
|
+
{
|
|
123
|
+
if (self == other) return self;
|
|
124
|
+
rb_check_frozen(self);
|
|
125
|
+
flag_t *copy;
|
|
126
|
+
TypedData_Get_Struct(self, flag_t, &flag_type, copy);
|
|
127
|
+
if (copy->initialized) rb_raise(rb_eRuntimeError, "copy is already initialized");
|
|
128
|
+
bool value = RTEST(flag_value(other));
|
|
129
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
130
|
+
atomic_store_explicit(©->value, value, memory_order_relaxed);
|
|
131
|
+
#else
|
|
132
|
+
copy->value = value;
|
|
133
|
+
#endif
|
|
134
|
+
copy->initialized = true;
|
|
135
|
+
return containers_publish_native_reference_free(self);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static VALUE
|
|
139
|
+
flag_set(VALUE self)
|
|
140
|
+
{
|
|
141
|
+
flag_t *flag = get_flag(self);
|
|
142
|
+
containers_check_typed_frozen(self);
|
|
143
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
144
|
+
atomic_store_explicit(&flag->value, true, memory_order_relaxed);
|
|
145
|
+
#else
|
|
146
|
+
pthread_mutex_lock(&flag->lock);
|
|
147
|
+
flag->value = true;
|
|
148
|
+
pthread_mutex_unlock(&flag->lock);
|
|
149
|
+
#endif
|
|
150
|
+
return Qtrue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static VALUE
|
|
154
|
+
flag_unset(VALUE self)
|
|
155
|
+
{
|
|
156
|
+
flag_t *flag = get_flag(self);
|
|
157
|
+
containers_check_typed_frozen(self);
|
|
158
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
159
|
+
atomic_store_explicit(&flag->value, false, memory_order_relaxed);
|
|
160
|
+
#else
|
|
161
|
+
pthread_mutex_lock(&flag->lock);
|
|
162
|
+
flag->value = false;
|
|
163
|
+
pthread_mutex_unlock(&flag->lock);
|
|
164
|
+
#endif
|
|
165
|
+
return Qfalse;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static VALUE
|
|
169
|
+
flag_store(VALUE self, VALUE input)
|
|
170
|
+
{
|
|
171
|
+
flag_t *flag = get_flag(self);
|
|
172
|
+
containers_check_typed_frozen(self);
|
|
173
|
+
bool value = containers_strict_bool(input, "value");
|
|
174
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
175
|
+
atomic_store_explicit(&flag->value, value, memory_order_relaxed);
|
|
176
|
+
#else
|
|
177
|
+
pthread_mutex_lock(&flag->lock);
|
|
178
|
+
flag->value = value;
|
|
179
|
+
pthread_mutex_unlock(&flag->lock);
|
|
180
|
+
#endif
|
|
181
|
+
return input;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static VALUE
|
|
185
|
+
flag_swap(VALUE self, VALUE input)
|
|
186
|
+
{
|
|
187
|
+
flag_t *flag = get_flag(self);
|
|
188
|
+
containers_check_typed_frozen(self);
|
|
189
|
+
bool replacement = containers_strict_bool(input, "value");
|
|
190
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
191
|
+
bool previous = atomic_exchange_explicit(
|
|
192
|
+
&flag->value,
|
|
193
|
+
replacement,
|
|
194
|
+
memory_order_relaxed
|
|
195
|
+
);
|
|
196
|
+
#else
|
|
197
|
+
pthread_mutex_lock(&flag->lock);
|
|
198
|
+
bool previous = flag->value;
|
|
199
|
+
flag->value = replacement;
|
|
200
|
+
pthread_mutex_unlock(&flag->lock);
|
|
201
|
+
#endif
|
|
202
|
+
return flag_boolean(previous);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static VALUE
|
|
206
|
+
flag_compare_and_set(VALUE self, VALUE expected_input, VALUE replacement_input)
|
|
207
|
+
{
|
|
208
|
+
flag_t *flag = get_flag(self);
|
|
209
|
+
containers_check_typed_frozen(self);
|
|
210
|
+
bool expected = containers_strict_bool(expected_input, "expected value");
|
|
211
|
+
bool replacement = containers_strict_bool(replacement_input, "replacement value");
|
|
212
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
213
|
+
bool exchanged = atomic_compare_exchange_strong_explicit(
|
|
214
|
+
&flag->value,
|
|
215
|
+
&expected,
|
|
216
|
+
replacement,
|
|
217
|
+
memory_order_relaxed,
|
|
218
|
+
memory_order_relaxed
|
|
219
|
+
);
|
|
220
|
+
#else
|
|
221
|
+
pthread_mutex_lock(&flag->lock);
|
|
222
|
+
bool exchanged = flag->value == expected;
|
|
223
|
+
if (exchanged) flag->value = replacement;
|
|
224
|
+
pthread_mutex_unlock(&flag->lock);
|
|
225
|
+
#endif
|
|
226
|
+
return flag_boolean(exchanged);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static VALUE
|
|
230
|
+
flag_toggle(VALUE self)
|
|
231
|
+
{
|
|
232
|
+
flag_t *flag = get_flag(self);
|
|
233
|
+
containers_check_typed_frozen(self);
|
|
234
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
235
|
+
bool current = atomic_load_explicit(&flag->value, memory_order_relaxed);
|
|
236
|
+
|
|
237
|
+
for (;;) {
|
|
238
|
+
bool replacement = !current;
|
|
239
|
+
if (atomic_compare_exchange_weak_explicit(
|
|
240
|
+
&flag->value,
|
|
241
|
+
¤t,
|
|
242
|
+
replacement,
|
|
243
|
+
memory_order_relaxed,
|
|
244
|
+
memory_order_relaxed
|
|
245
|
+
)) {
|
|
246
|
+
return flag_boolean(replacement);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
#else
|
|
250
|
+
pthread_mutex_lock(&flag->lock);
|
|
251
|
+
bool value = flag->value = !flag->value;
|
|
252
|
+
pthread_mutex_unlock(&flag->lock);
|
|
253
|
+
return flag_boolean(value);
|
|
254
|
+
#endif
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
void
|
|
258
|
+
containers_init_flag(VALUE namespace)
|
|
259
|
+
{
|
|
260
|
+
cFlag = rb_define_class_under(namespace, "Flag", rb_cObject);
|
|
261
|
+
rb_define_alloc_func(cFlag, flag_allocate);
|
|
262
|
+
rb_define_method(cFlag, "initialize", flag_initialize, -1);
|
|
263
|
+
rb_define_private_method(cFlag, "initialize_copy", flag_initialize_copy, 1);
|
|
264
|
+
rb_define_method(cFlag, "value", flag_value, 0);
|
|
265
|
+
rb_define_alias(cFlag, "get", "value");
|
|
266
|
+
rb_define_method(cFlag, "set", flag_set, 0);
|
|
267
|
+
rb_define_method(cFlag, "unset", flag_unset, 0);
|
|
268
|
+
rb_define_method(cFlag, "store", flag_store, 1);
|
|
269
|
+
rb_define_alias(cFlag, "value=", "store");
|
|
270
|
+
rb_define_method(cFlag, "swap", flag_swap, 1);
|
|
271
|
+
rb_define_method(cFlag, "compare_and_set", flag_compare_and_set, 2);
|
|
272
|
+
rb_define_method(cFlag, "toggle", flag_toggle, 0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* Called only by transaction validation. Ordinary flag operations are unchanged. */
|
|
276
|
+
bool
|
|
277
|
+
farce_transaction_flag_set(VALUE self)
|
|
278
|
+
{
|
|
279
|
+
flag_t *flag = get_flag(self);
|
|
280
|
+
#if CONTAINERS_FLAG_LOCK_FREE
|
|
281
|
+
return atomic_load_explicit(&flag->value, memory_order_relaxed);
|
|
282
|
+
#else
|
|
283
|
+
pthread_mutex_lock(&flag->lock);
|
|
284
|
+
bool value = flag->value;
|
|
285
|
+
pthread_mutex_unlock(&flag->lock);
|
|
286
|
+
return value;
|
|
287
|
+
#endif
|
|
288
|
+
}
|
data/ext/farce/io.c
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#include "reactor.h"
|
|
2
|
+
#include <ruby/version.h>
|
|
3
|
+
#include <ruby/fiber/scheduler.h>
|
|
4
|
+
#include <sys/stat.h>
|
|
5
|
+
#include <sys/socket.h>
|
|
6
|
+
#include <unistd.h>
|
|
7
|
+
#include <fcntl.h>
|
|
8
|
+
#include <poll.h>
|
|
9
|
+
#include <errno.h>
|
|
10
|
+
|
|
11
|
+
// BasicSocket is kept alive so reads can choose the socket path without loading it again.
|
|
12
|
+
static VALUE socket_class = Qnil;
|
|
13
|
+
|
|
14
|
+
// Remove the wait and its IO watches after a readiness call returns or raises.
|
|
15
|
+
static VALUE wait_cleanup(VALUE data) {
|
|
16
|
+
VALUE *args = (VALUE *)data; return fs_retire(args[0], args[1]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Watch the requested IO events and suspend the current fiber.
|
|
20
|
+
// A timeout resumes it with zero when no event arrives in time.
|
|
21
|
+
static VALUE wait_body(VALUE data) {
|
|
22
|
+
VALUE *args = (VALUE *)data;
|
|
23
|
+
fs_state *s = fs_get(args[0]); fs_wait *w = fs_lookup(s, args[1]);
|
|
24
|
+
w->ios = args[2]; // GC root for the watched IO
|
|
25
|
+
fs_watch_io(s, w, rb_io_descriptor(args[2]), NUM2INT(args[3]), -1, 0);
|
|
26
|
+
if (NIL_P(args[4])) return fs_suspend(args[0], args[1]);
|
|
27
|
+
return rb_funcall(args[0], rb_intern("wait_with_timeout"), 3, args[1], args[4], INT2NUM(0));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Create a readiness wait and make sure it is cleaned up on every exit.
|
|
31
|
+
// The caller has already checked that this fiber belongs to the scheduler.
|
|
32
|
+
static VALUE wait_for(VALUE self, VALUE io, int events, VALUE timeout) {
|
|
33
|
+
VALUE args[] = {self, fs_arm_checked(self), io, INT2NUM(events), timeout};
|
|
34
|
+
return rb_ensure(wait_body, (VALUE)args, wait_cleanup, (VALUE)args);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Implement Ruby's io_wait hook. Return events that are ready now, or wait
|
|
38
|
+
// for an event or timeout. Even an immediate result gives other fibers a turn periodically.
|
|
39
|
+
VALUE fs_io_wait(int argc, VALUE *argv, VALUE self) {
|
|
40
|
+
VALUE io, events, timeout;
|
|
41
|
+
rb_scan_args(argc, argv, "21", &io, &events, &timeout);
|
|
42
|
+
fs_check_fiber(self);
|
|
43
|
+
io = rb_io_get_io(io);
|
|
44
|
+
int mask = NUM2INT(events);
|
|
45
|
+
timeout = rb_funcall(self, rb_intern("duration_value"), 1, timeout);
|
|
46
|
+
fs_check_fiber(self);
|
|
47
|
+
if (mask <= 0 || (mask & ~7)) rb_raise(rb_eArgError, "invalid IO event mask");
|
|
48
|
+
struct pollfd fd = {.fd = rb_io_descriptor(io)};
|
|
49
|
+
if (mask & FS_READ) fd.events |= POLLIN;
|
|
50
|
+
if (mask & FS_WRITE) fd.events |= POLLOUT;
|
|
51
|
+
if (mask & FS_PRI) fd.events |= POLLPRI;
|
|
52
|
+
int result = poll(&fd, 1, 0);
|
|
53
|
+
if (result < 0 && errno != EINTR) rb_sys_fail("scheduler readiness snapshot");
|
|
54
|
+
if (fd.revents & POLLNVAL) rb_raise(rb_eIOError, "closed stream");
|
|
55
|
+
int found = 0;
|
|
56
|
+
if (fd.revents & (POLLIN | POLLHUP | POLLERR)) found |= FS_READ;
|
|
57
|
+
if (fd.revents & (POLLOUT | POLLHUP | POLLERR)) found |= FS_WRITE;
|
|
58
|
+
if (fd.revents & POLLPRI) found |= FS_PRI;
|
|
59
|
+
if ((found & mask) || (!NIL_P(timeout) && NUM2DBL(timeout) == 0))
|
|
60
|
+
return fs_checkpoint(self, INT2NUM(found & mask));
|
|
61
|
+
return wait_for(self, io, mask, timeout);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Keep the state of one buffer read or write across waits and cleanup.
|
|
65
|
+
// The Ruby IO and buffer stay alive while native code uses their storage.
|
|
66
|
+
typedef struct {
|
|
67
|
+
// Ruby objects for the call and its current wait token.
|
|
68
|
+
VALUE self, io, buffer, token;
|
|
69
|
+
// Completion target, starting offset, buffer limit, and bytes already written.
|
|
70
|
+
size_t minimum, offset, size, initial;
|
|
71
|
+
// Start of the buffer storage used by native IO calls.
|
|
72
|
+
void *base;
|
|
73
|
+
// Write mode, lock ownership, original descriptor, and socket-call mode.
|
|
74
|
+
int writing, unlock, fd, socket;
|
|
75
|
+
// Suspension count before this call, used to detect an immediate result.
|
|
76
|
+
uint64_t serial;
|
|
77
|
+
} transfer;
|
|
78
|
+
|
|
79
|
+
// Pause a transfer until its descriptor is ready, then remove that wait.
|
|
80
|
+
// If the wait raises, transfer_cleanup removes it instead.
|
|
81
|
+
static void transfer_wait(transfer *t, int events) {
|
|
82
|
+
t->token = fs_arm_checked(t->self);
|
|
83
|
+
fs_state *s = fs_get(t->self);
|
|
84
|
+
fs_wait *w = fs_lookup(s, t->token);
|
|
85
|
+
w->ios = t->io;
|
|
86
|
+
fs_watch_io(s, w, rb_io_descriptor(t->io), events, -1, 0);
|
|
87
|
+
fs_suspend(t->self, t->token);
|
|
88
|
+
fs_retire(t->self, t->token);
|
|
89
|
+
t->token = Qnil;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Submit an io_uring receive directly into the locked buffer and wait for it.
|
|
93
|
+
// Return the byte count or a negative error number from the completion.
|
|
94
|
+
static ssize_t transfer_receive(transfer *t, void *buffer, size_t capacity) {
|
|
95
|
+
t->token = fs_arm_checked(t->self);
|
|
96
|
+
fs_state *s = fs_get(t->self);
|
|
97
|
+
fs_wait *w = fs_lookup(s, t->token);
|
|
98
|
+
w->ios = t->io;
|
|
99
|
+
int error = fs_driver_receive(s, w, rb_io_descriptor(t->io), buffer, capacity);
|
|
100
|
+
if (error) rb_syserr_fail(error, "scheduler receive submission");
|
|
101
|
+
VALUE result = fs_suspend(t->self, t->token);
|
|
102
|
+
fs_retire(t->self, t->token);
|
|
103
|
+
t->token = Qnil;
|
|
104
|
+
return NUM2LONG(result);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Pass a regular file transfer to the Ruby helper and its bounded worker pool.
|
|
108
|
+
// The byte limit keeps the worker inside the requested buffer range.
|
|
109
|
+
static VALUE transfer_file(transfer *t) {
|
|
110
|
+
return rb_funcall(t->self, rb_intern("file_transfer"), 6,
|
|
111
|
+
t->writing ? Qtrue : Qfalse, t->io, t->buffer, SIZET2NUM(t->minimum),
|
|
112
|
+
SIZET2NUM(t->offset), SIZET2NUM(t->size - t->offset));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Read or write the requested bytes without blocking the scheduler thread.
|
|
116
|
+
// Wait when the IO is not ready, and use a worker for regular files.
|
|
117
|
+
// Return bytes transferred, or a negative error number if nothing was transferred.
|
|
118
|
+
static VALUE transfer_body(VALUE data) {
|
|
119
|
+
transfer *t = (transfer *)data;
|
|
120
|
+
// MSG_DONTWAIT is scoped to this syscall. Try sockets directly: this also
|
|
121
|
+
// supports IO.for_fd wrappers without caching descriptor type across reopen.
|
|
122
|
+
#ifdef MSG_DONTWAIT
|
|
123
|
+
t->socket = 1;
|
|
124
|
+
#else
|
|
125
|
+
t->socket = 0;
|
|
126
|
+
struct stat info;
|
|
127
|
+
if (fstat(t->fd, &info) < 0) rb_sys_fail("scheduler fstat");
|
|
128
|
+
if (S_ISREG(info.st_mode))
|
|
129
|
+
return transfer_file(t);
|
|
130
|
+
int flags = fcntl(t->fd, F_GETFL);
|
|
131
|
+
if (flags < 0 || (!(flags & O_NONBLOCK) && fcntl(t->fd, F_SETFL, flags | O_NONBLOCK) < 0))
|
|
132
|
+
rb_sys_fail("scheduler nonblocking IO");
|
|
133
|
+
#endif
|
|
134
|
+
int direct = 0;
|
|
135
|
+
#if defined(FARCE_URING) && defined(IORING_RECVSEND_POLL_FIRST)
|
|
136
|
+
fs_state *state = fs_get(t->self);
|
|
137
|
+
direct = !t->writing && t->minimum > 0 && state->driver == FS_URING_DRIVER &&
|
|
138
|
+
!state->receive_disabled && !NIL_P(socket_class) && rb_obj_is_kind_of(t->io, socket_class);
|
|
139
|
+
#endif
|
|
140
|
+
if (!direct && !t->writing && t->size > t->offset &&
|
|
141
|
+
!NIL_P(socket_class) && rb_obj_is_kind_of(t->io, socket_class)) {
|
|
142
|
+
transfer_wait(t, FS_READ);
|
|
143
|
+
}
|
|
144
|
+
size_t total = 0;
|
|
145
|
+
for (;;) {
|
|
146
|
+
size_t capacity = t->size - t->offset - total;
|
|
147
|
+
if (!capacity) return SIZET2NUM(total);
|
|
148
|
+
// Recheck IO identity after each suspension. A numeric fd may have been
|
|
149
|
+
// reused, while this call still retains the original Ruby IO object.
|
|
150
|
+
if (rb_io_descriptor(t->io) != t->fd) rb_raise(rb_eIOError, "IO descriptor changed while waiting");
|
|
151
|
+
ssize_t count;
|
|
152
|
+
int socket_flags = 0;
|
|
153
|
+
#ifdef MSG_DONTWAIT
|
|
154
|
+
socket_flags |= MSG_DONTWAIT;
|
|
155
|
+
#endif
|
|
156
|
+
#ifdef MSG_NOSIGNAL
|
|
157
|
+
if (t->writing) socket_flags |= MSG_NOSIGNAL;
|
|
158
|
+
#endif
|
|
159
|
+
if (direct) {
|
|
160
|
+
count = transfer_receive(t, (char *)t->base + t->offset + total, capacity);
|
|
161
|
+
if (count < 0) { errno = (int)-count; count = -1; }
|
|
162
|
+
} else if (t->socket) {
|
|
163
|
+
count = t->writing ? send(t->fd, (char *)t->base + t->offset + total, capacity, socket_flags) :
|
|
164
|
+
recv(t->fd, (char *)t->base + t->offset + total, capacity, socket_flags);
|
|
165
|
+
} else {
|
|
166
|
+
count = t->writing ? write(t->fd, (char *)t->base + t->offset + total, capacity) :
|
|
167
|
+
read(t->fd, (char *)t->base + t->offset + total, capacity);
|
|
168
|
+
}
|
|
169
|
+
if (count < 0) {
|
|
170
|
+
int error = errno;
|
|
171
|
+
if (direct && (error == EINVAL || error == EOPNOTSUPP || error == ENOSYS)) {
|
|
172
|
+
// Older kernels may support the ring but not poll-first receive.
|
|
173
|
+
// Fall back to readiness for this call and subsequent transfers.
|
|
174
|
+
fs_get(t->self)->receive_disabled = 1;
|
|
175
|
+
direct = 0;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (t->socket && error == ENOTSOCK) {
|
|
179
|
+
struct stat info;
|
|
180
|
+
if (fstat(t->fd, &info) < 0) rb_sys_fail("scheduler fstat");
|
|
181
|
+
if (S_ISREG(info.st_mode))
|
|
182
|
+
return transfer_file(t);
|
|
183
|
+
t->socket = 0;
|
|
184
|
+
int flags = fcntl(t->fd, F_GETFL);
|
|
185
|
+
if (flags < 0 || (!(flags & O_NONBLOCK) && fcntl(t->fd, F_SETFL, flags | O_NONBLOCK) < 0))
|
|
186
|
+
rb_sys_fail("scheduler nonblocking IO");
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (error == EINTR) { rb_thread_check_ints(); continue; }
|
|
190
|
+
if (error == EAGAIN || error == EWOULDBLOCK) {
|
|
191
|
+
transfer_wait(t, t->writing ? FS_WRITE : FS_READ);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
return total ? SIZET2NUM(total) : INT2NUM(-error);
|
|
195
|
+
}
|
|
196
|
+
if (!count) return SIZET2NUM(total);
|
|
197
|
+
total += (size_t)count;
|
|
198
|
+
if (total >= t->minimum) return SIZET2NUM(total);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Adapt the transfer loop to the callback signature used by a Ruby block.
|
|
203
|
+
static VALUE timed_body(RB_BLOCK_CALL_FUNC_ARGLIST(ignored, data)) { return transfer_body(data); }
|
|
204
|
+
|
|
205
|
+
// Run the transfer while its buffer is protected.
|
|
206
|
+
// Apply the IO object's timeout when this Ruby version supports one.
|
|
207
|
+
static VALUE transfer_locked(VALUE data) {
|
|
208
|
+
transfer *t = (transfer *)data;
|
|
209
|
+
#ifdef HAVE_RB_IO_TIMEOUT
|
|
210
|
+
VALUE timeout = rb_io_timeout(t->io);
|
|
211
|
+
if (!NIL_P(timeout)) return rb_block_call(t->self, rb_intern("with_io_timeout"), 1, &timeout, timed_body, data);
|
|
212
|
+
#endif
|
|
213
|
+
return transfer_body(data);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Cancel any remaining wait before releasing the buffer lock owned by this call.
|
|
217
|
+
// This also runs when a transfer raises or is interrupted.
|
|
218
|
+
static VALUE transfer_cleanup(VALUE data) {
|
|
219
|
+
transfer *t = (transfer *)data;
|
|
220
|
+
if (!NIL_P(t->token)) fs_retire(t->self, t->token);
|
|
221
|
+
if (t->unlock) rb_io_buffer_unlock(t->buffer);
|
|
222
|
+
return Qnil;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check Ruby's buffer arguments and prepare a read or write.
|
|
226
|
+
// Try an immediate socket write first, then protect the buffer for any waits.
|
|
227
|
+
// Ruby 4.1 requests a maximum length, while older versions request a minimum.
|
|
228
|
+
static VALUE transfer_io(int argc, VALUE *argv, VALUE self, int writing) {
|
|
229
|
+
VALUE io, buffer, minimum, offset;
|
|
230
|
+
#if RUBY_FIBER_SCHEDULER_VERSION >= 4
|
|
231
|
+
rb_scan_args(argc, argv, "40", &io, &buffer, &offset, &minimum);
|
|
232
|
+
#else
|
|
233
|
+
rb_scan_args(argc, argv, "31", &io, &buffer, &minimum, &offset);
|
|
234
|
+
#endif
|
|
235
|
+
fs_check_fiber(self);
|
|
236
|
+
int coerces = !RB_TYPE_P(io, T_FILE) || !FIXNUM_P(minimum) || (!NIL_P(offset) && !FIXNUM_P(offset));
|
|
237
|
+
transfer t = {.self = self, .io = rb_io_get_io(io), .buffer = buffer, .writing = writing, .token = Qnil};
|
|
238
|
+
t.minimum = NUM2SIZET(minimum); t.offset = NIL_P(offset) ? 0 : NUM2SIZET(offset);
|
|
239
|
+
if (coerces) fs_check_fiber(self);
|
|
240
|
+
t.fd = rb_io_descriptor(t.io);
|
|
241
|
+
#if RUBY_IO_BUFFER_VERSION >= 3
|
|
242
|
+
rb_io_buffer_get_bytes(buffer, &t.base, &t.size);
|
|
243
|
+
#else
|
|
244
|
+
enum rb_io_buffer_flags flags = rb_io_buffer_get_bytes(buffer, &t.base, &t.size);
|
|
245
|
+
#endif
|
|
246
|
+
if (!writing) rb_io_buffer_get_bytes_for_writing(buffer, &t.base, &t.size);
|
|
247
|
+
if (t.offset > t.size || t.minimum > t.size - t.offset) rb_raise(rb_eArgError, "length and offset exceed buffer size");
|
|
248
|
+
#if RUBY_FIBER_SCHEDULER_VERSION >= 4
|
|
249
|
+
// Ruby 4.1 requests one transfer bounded by length, not a minimum byte count.
|
|
250
|
+
t.size = t.offset + t.minimum;
|
|
251
|
+
t.minimum = t.minimum ? 1 : 0;
|
|
252
|
+
#endif
|
|
253
|
+
#ifdef MSG_DONTWAIT
|
|
254
|
+
if (writing && t.size > t.offset) {
|
|
255
|
+
int flags = MSG_DONTWAIT;
|
|
256
|
+
#ifdef MSG_NOSIGNAL
|
|
257
|
+
flags |= MSG_NOSIGNAL;
|
|
258
|
+
#endif
|
|
259
|
+
ssize_t count = send(t.fd, (char *)t.base + t.offset, t.size - t.offset, flags);
|
|
260
|
+
if (count >= 0 && (size_t)count >= t.minimum) {
|
|
261
|
+
VALUE result = fs_checkpoint(self, SIZET2NUM((size_t)count));
|
|
262
|
+
RB_GC_GUARD(t.io); RB_GC_GUARD(buffer);
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
if (count > 0) {
|
|
266
|
+
// Continue a partial write without transmitting its prefix twice.
|
|
267
|
+
t.offset += (size_t)count; t.minimum -= (size_t)count;
|
|
268
|
+
t.initial = (size_t)count;
|
|
269
|
+
} else if (count < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR && errno != ENOTSOCK) {
|
|
270
|
+
return fs_checkpoint(self, INT2NUM(-errno));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
#endif
|
|
274
|
+
t.serial = fs_get(self)->suspensions;
|
|
275
|
+
#if RUBY_IO_BUFFER_VERSION >= 3
|
|
276
|
+
// Locks now reference the backing allocation. Own one reference even when
|
|
277
|
+
// Ruby or another buffer view already holds a lock, and release only ours.
|
|
278
|
+
rb_io_buffer_lock(buffer); t.unlock = 1;
|
|
279
|
+
#else
|
|
280
|
+
if (!(flags & RB_IO_BUFFER_LOCKED)) { rb_io_buffer_lock(buffer); t.unlock = 1; }
|
|
281
|
+
#endif
|
|
282
|
+
VALUE result = rb_ensure(transfer_locked, (VALUE)&t, transfer_cleanup, (VALUE)&t);
|
|
283
|
+
if (t.initial) result = NUM2LONG(result) < 0 ? SIZET2NUM(t.initial) : SIZET2NUM(t.initial + NUM2SIZET(result));
|
|
284
|
+
if (t.serial == fs_get(self)->suspensions) fs_checkpoint(self, result);
|
|
285
|
+
RB_GC_GUARD(t.io); RB_GC_GUARD(buffer);
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Handle Ruby's io_read hook using the shared transfer code.
|
|
290
|
+
static VALUE read_io(int argc, VALUE *argv, VALUE self) { return transfer_io(argc, argv, self, 0); }
|
|
291
|
+
|
|
292
|
+
// Handle Ruby's io_write hook using the shared transfer code.
|
|
293
|
+
static VALUE write_io(int argc, VALUE *argv, VALUE self) { return transfer_io(argc, argv, self, 1); }
|
|
294
|
+
#if RUBY_API_VERSION_MAJOR >= 4
|
|
295
|
+
|
|
296
|
+
// Handle a descriptor Ruby has handed to the scheduler for closing.
|
|
297
|
+
// Wake its waiters with an error and retire its registration before closing it,
|
|
298
|
+
// so a new IO can safely reuse the descriptor number.
|
|
299
|
+
static VALUE close_io(VALUE self, VALUE descriptor) {
|
|
300
|
+
rb_funcall(self, rb_intern("check_owner"), 0);
|
|
301
|
+
int fd = NUM2INT(descriptor); fs_state *s = fs_get(self); st_data_t entry;
|
|
302
|
+
if (st_lookup(s->regs, fd, &entry)) {
|
|
303
|
+
fs_reg *reg = (fs_reg *)entry;
|
|
304
|
+
VALUE error = rb_exc_new_cstr(rb_eIOError, "stream closed while waiting");
|
|
305
|
+
for (fs_watch *w = reg->watches; w; w = w->next_reg)
|
|
306
|
+
rb_funcall(self, rb_intern("interrupt_wait"), 2, w->wait->token, error);
|
|
307
|
+
fs_driver_update(s, reg, 0);
|
|
308
|
+
st_data_t key = fd; st_delete(s->regs, &key, NULL);
|
|
309
|
+
if (reg->watches) {
|
|
310
|
+
st_insert(s->retired_regs, (st_data_t)reg, (st_data_t)reg);
|
|
311
|
+
reg->fd = -1;
|
|
312
|
+
} else {
|
|
313
|
+
st_data_t generation = reg->generation;
|
|
314
|
+
st_delete(s->generations, &generation, NULL);
|
|
315
|
+
if (reg->poll_events) s->polling_regs--;
|
|
316
|
+
xfree(reg);
|
|
317
|
+
}
|
|
318
|
+
// New fd ownership receives a new registration generation.
|
|
319
|
+
}
|
|
320
|
+
// CRuby 4.0 calls this hook after flushing and handing ownership of the fd
|
|
321
|
+
// to the scheduler. Do not retry close(EINTR): the number may already be reused.
|
|
322
|
+
if (close(fd) < 0 && errno != EINTR) rb_sys_fail("scheduler close");
|
|
323
|
+
return Qtrue;
|
|
324
|
+
}
|
|
325
|
+
#endif
|
|
326
|
+
|
|
327
|
+
// Keep the socket class alive and attach the supported IO hooks to the scheduler.
|
|
328
|
+
void fs_init_io(VALUE klass) {
|
|
329
|
+
rb_require("socket");
|
|
330
|
+
socket_class = rb_const_get(rb_cObject, rb_intern("BasicSocket"));
|
|
331
|
+
rb_global_variable(&socket_class);
|
|
332
|
+
rb_define_method(klass, "io_read", read_io, -1);
|
|
333
|
+
rb_define_method(klass, "io_write", write_io, -1);
|
|
334
|
+
rb_define_method(klass, "io_wait", fs_io_wait, -1);
|
|
335
|
+
#if RUBY_API_VERSION_MAJOR >= 4
|
|
336
|
+
rb_define_method(klass, "io_close", close_io, 1);
|
|
337
|
+
#endif
|
|
338
|
+
}
|