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.
Files changed (423) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +26 -0
  3. data/CONTRIBUTING.md +71 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +1524 -0
  6. data/SECURITY.md +10 -0
  7. data/docs/benchmarks.md +185 -0
  8. data/docs/gems/dry-types.md +290 -0
  9. data/docs/gems/msgpack.md +70 -0
  10. data/docs/gems/ractor-shim.md +63 -0
  11. data/docs/modes.md +628 -0
  12. data/docs/scopes.md +649 -0
  13. data/docs/variants.md +346 -0
  14. data/ext/ext_helper.rb +20 -0
  15. data/ext/farce/README.md +21 -0
  16. data/ext/farce/atom.c +1023 -0
  17. data/ext/farce/bounded_map.c +1683 -0
  18. data/ext/farce/containers.h +77 -0
  19. data/ext/farce/counter.c +399 -0
  20. data/ext/farce/darwin.c +100 -0
  21. data/ext/farce/depend +12 -0
  22. data/ext/farce/dict.c +1523 -0
  23. data/ext/farce/dict.h +152 -0
  24. data/ext/farce/drivers.c +237 -0
  25. data/ext/farce/exchanger.c +299 -0
  26. data/ext/farce/extconf.rb +99 -0
  27. data/ext/farce/farce.c +359 -0
  28. data/ext/farce/flag.c +288 -0
  29. data/ext/farce/io.c +338 -0
  30. data/ext/farce/lock.c +510 -0
  31. data/ext/farce/map.c +2240 -0
  32. data/ext/farce/priority_queue.c +2056 -0
  33. data/ext/farce/queue.c +1059 -0
  34. data/ext/farce/reactor.c +820 -0
  35. data/ext/farce/reactor.h +103 -0
  36. data/ext/farce/shareable.h +31 -0
  37. data/ext/farce/signal.c +350 -0
  38. data/ext/farce/transaction.c +354 -0
  39. data/ext/farce/transaction.h +40 -0
  40. data/ext/farce/tree_map.c +1953 -0
  41. data/ext/farce/trie.c +2020 -0
  42. data/ext/farce/unshareable.c +155 -0
  43. data/ext/farce/unshared_io_pool.h +234 -0
  44. data/ext/farce/unshared_signal.c +263 -0
  45. data/ext/farce/unshared_wait.h +193 -0
  46. data/ext/farce/unsupported.c +6 -0
  47. data/ext/farce/vector.c +1195 -0
  48. data/ext/farce/weak_map.c +1714 -0
  49. data/ext/java/org/farce/BoundedMap.java +394 -0
  50. data/ext/java/org/farce/FiberScheduler.java +141 -0
  51. data/ext/java/org/farce/PriorityKey.java +45 -0
  52. data/ext/java/org/farce/PriorityQueue.java +373 -0
  53. data/ext/java/org/farce/QueueSignal.java +28 -0
  54. data/ext/rebind/README.md +14 -0
  55. data/ext/rebind/extconf.rb +10 -0
  56. data/ext/rebind/rebind.c +186 -0
  57. data/lib/farce/_yard/internal.rb +13 -0
  58. data/lib/farce/_yard/macros.rb +61 -0
  59. data/lib/farce/_yard/ractor.rb +46 -0
  60. data/lib/farce/abstract/atom.rb +186 -0
  61. data/lib/farce/abstract/bounded_map.rb +232 -0
  62. data/lib/farce/abstract/collection.rb +151 -0
  63. data/lib/farce/abstract/concurrent_map.rb +381 -0
  64. data/lib/farce/abstract/counter.rb +193 -0
  65. data/lib/farce/abstract/duplicable_map.rb +229 -0
  66. data/lib/farce/abstract/exchanger.rb +26 -0
  67. data/lib/farce/abstract/flag.rb +71 -0
  68. data/lib/farce/abstract/lazy.rb +115 -0
  69. data/lib/farce/abstract/lease.rb +104 -0
  70. data/lib/farce/abstract/lease_map.rb +261 -0
  71. data/lib/farce/abstract/lease_pool.rb +91 -0
  72. data/lib/farce/abstract/lfu_map.rb +20 -0
  73. data/lib/farce/abstract/lru_map.rb +25 -0
  74. data/lib/farce/abstract/map.rb +345 -0
  75. data/lib/farce/abstract/molecule.rb +245 -0
  76. data/lib/farce/abstract/port.rb +74 -0
  77. data/lib/farce/abstract/priority_queue.rb +117 -0
  78. data/lib/farce/abstract/queue.rb +269 -0
  79. data/lib/farce/abstract/scheduler.rb +111 -0
  80. data/lib/farce/abstract/set.rb +910 -0
  81. data/lib/farce/abstract/sorted_set.rb +172 -0
  82. data/lib/farce/abstract/timer_queue.rb +136 -0
  83. data/lib/farce/abstract/tree_map.rb +269 -0
  84. data/lib/farce/abstract/value.rb +68 -0
  85. data/lib/farce/abstract/vector.rb +1126 -0
  86. data/lib/farce/abstract/weak_atom.rb +32 -0
  87. data/lib/farce/abstract/weak_key_map.rb +12 -0
  88. data/lib/farce/abstract/weak_map.rb +13 -0
  89. data/lib/farce/abstract/weak_set.rb +12 -0
  90. data/lib/farce/abstract/weak_value_map.rb +12 -0
  91. data/lib/farce/abstract.rb +17 -0
  92. data/lib/farce/atom.rb +293 -0
  93. data/lib/farce/class_mirror.rb +87 -0
  94. data/lib/farce/clock.rb +121 -0
  95. data/lib/farce/config.rb +229 -0
  96. data/lib/farce/counter.rb +71 -0
  97. data/lib/farce/deduper.rb +122 -0
  98. data/lib/farce/engine/jruby/bounded_map.rb +314 -0
  99. data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
  100. data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
  101. data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
  102. data/lib/farce/engine/jruby/map.rb +505 -0
  103. data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
  104. data/lib/farce/engine/jruby/signal.rb +147 -0
  105. data/lib/farce/engine/jruby.rb +63 -0
  106. data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
  107. data/lib/farce/engine/jvm/counter.rb +102 -0
  108. data/lib/farce/engine/jvm/extension.rb +32 -0
  109. data/lib/farce/engine/jvm/farce.jar +0 -0
  110. data/lib/farce/engine/jvm/flag.rb +79 -0
  111. data/lib/farce/engine/jvm/priority_queue.rb +217 -0
  112. data/lib/farce/engine/jvm/tree_map.rb +350 -0
  113. data/lib/farce/engine/jvm/types.rb +180 -0
  114. data/lib/farce/engine/jvm.rb +19 -0
  115. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  116. data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
  117. data/lib/farce/engine/ruby/3.4/port.rb +186 -0
  118. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
  119. data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
  120. data/lib/farce/engine/ruby/3.4/rebind.so +0 -0
  121. data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
  122. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  123. data/lib/farce/engine/ruby/4.0/port.rb +19 -0
  124. data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
  125. data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
  126. data/lib/farce/engine/ruby/4.0/rebind.so +0 -0
  127. data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
  128. data/lib/farce/engine/ruby/4.1/port.rb +21 -0
  129. data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
  130. data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
  131. data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
  132. data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
  133. data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
  134. data/lib/farce/engine/ruby/shared/lease.rb +26 -0
  135. data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
  136. data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
  137. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
  138. data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
  139. data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
  140. data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
  141. data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
  142. data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
  143. data/lib/farce/engine/ruby/shared/vault.rb +144 -0
  144. data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
  145. data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
  146. data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
  147. data/lib/farce/engine/ruby.rb +130 -0
  148. data/lib/farce/engine/shared/atom.rb +10 -0
  149. data/lib/farce/engine/shared/exchanger.rb +130 -0
  150. data/lib/farce/engine/shared/identity_key.rb +22 -0
  151. data/lib/farce/engine/shared/lease.rb +10 -0
  152. data/lib/farce/engine/shared/lease_pool.rb +10 -0
  153. data/lib/farce/engine/shared/main_scheduler.rb +20 -0
  154. data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
  155. data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
  156. data/lib/farce/engine/shared/port.rb +44 -0
  157. data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
  158. data/lib/farce/engine/shared/proxy_owner.rb +43 -0
  159. data/lib/farce/engine/shared/queue.rb +233 -0
  160. data/lib/farce/engine/shared/ractor_methods.rb +73 -0
  161. data/lib/farce/engine/shared/rebindable.rb +15 -0
  162. data/lib/farce/engine/shared/strict_atom.rb +73 -0
  163. data/lib/farce/engine/shared/strict_map.rb +117 -0
  164. data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
  165. data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
  166. data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
  167. data/lib/farce/engine/shared/trie.rb +317 -0
  168. data/lib/farce/engine/shared/trie_builder.rb +199 -0
  169. data/lib/farce/engine/shared/unshareable.rb +14 -0
  170. data/lib/farce/engine/shared/unshared_atom.rb +256 -0
  171. data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
  172. data/lib/farce/engine/shared/unshared_queue.rb +18 -0
  173. data/lib/farce/engine/shared/unshared_signal.rb +21 -0
  174. data/lib/farce/engine/shared/unshared_vector.rb +377 -0
  175. data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
  176. data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
  177. data/lib/farce/engine/shared/vault.rb +31 -0
  178. data/lib/farce/engine/shared/vector.rb +83 -0
  179. data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
  180. data/lib/farce/engine/shared/weak_atom.rb +20 -0
  181. data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
  182. data/lib/farce/engine/shared/weak_map/index.rb +249 -0
  183. data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
  184. data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
  185. data/lib/farce/engine/shared.rb +63 -0
  186. data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
  187. data/lib/farce/engine/truffleruby/lock.rb +121 -0
  188. data/lib/farce/engine/truffleruby/map.rb +656 -0
  189. data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
  190. data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
  191. data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
  192. data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
  193. data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
  194. data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
  195. data/lib/farce/engine/truffleruby/signal.rb +77 -0
  196. data/lib/farce/engine/truffleruby.rb +148 -0
  197. data/lib/farce/envelope.rb +335 -0
  198. data/lib/farce/error.rb +35 -0
  199. data/lib/farce/exchanger.rb +41 -0
  200. data/lib/farce/flag.rb +27 -0
  201. data/lib/farce/integrations/active_support/blank.rb +68 -0
  202. data/lib/farce/integrations/active_support/clock.rb +17 -0
  203. data/lib/farce/integrations/active_support/duplicable.rb +78 -0
  204. data/lib/farce/integrations/active_support/map.rb +175 -0
  205. data/lib/farce/integrations/active_support/set.rb +60 -0
  206. data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
  207. data/lib/farce/integrations/active_support/vector.rb +231 -0
  208. data/lib/farce/integrations/active_support.rb +9 -0
  209. data/lib/farce/integrations/activesupport.rb +5 -0
  210. data/lib/farce/integrations/bson.rb +108 -0
  211. data/lib/farce/integrations/cbor.rb +51 -0
  212. data/lib/farce/integrations/concurrent.rb +112 -0
  213. data/lib/farce/integrations/dry-types.rb +5 -0
  214. data/lib/farce/integrations/dry_types.rb +694 -0
  215. data/lib/farce/integrations/json.rb +7 -0
  216. data/lib/farce/integrations/msgpack.rb +112 -0
  217. data/lib/farce/integrations/oj.rb +69 -0
  218. data/lib/farce/integrations/psych.rb +191 -0
  219. data/lib/farce/integrations/ractor-sharing.rb +5 -0
  220. data/lib/farce/integrations/ractor-tmvar.rb +5 -0
  221. data/lib/farce/integrations/ractor_sharing.rb +172 -0
  222. data/lib/farce/integrations/ractor_tmvar.rb +64 -0
  223. data/lib/farce/integrations/shared/to_json.rb +42 -0
  224. data/lib/farce/integrations/sorted_set.rb +11 -0
  225. data/lib/farce/integrations/weakref.rb +38 -0
  226. data/lib/farce/integrations/yajl.rb +10 -0
  227. data/lib/farce/integrations.rb +156 -0
  228. data/lib/farce/internal/_frozen_config.rb +11 -0
  229. data/lib/farce/internal/autoloads.rb +47 -0
  230. data/lib/farce/internal/blocking_priority_queue.rb +122 -0
  231. data/lib/farce/internal/converter.rb +106 -0
  232. data/lib/farce/internal/copyable.rb +31 -0
  233. data/lib/farce/internal/delegation.rb +20 -0
  234. data/lib/farce/internal/external_transaction.rb +59 -0
  235. data/lib/farce/internal/fake_ractor.rb +179 -0
  236. data/lib/farce/internal/freeze.rb +118 -0
  237. data/lib/farce/internal/inspect.rb +157 -0
  238. data/lib/farce/internal/key_lock_map.rb +29 -0
  239. data/lib/farce/internal/key_normalizer.rb +289 -0
  240. data/lib/farce/internal/lease_initialization.rb +115 -0
  241. data/lib/farce/internal/lease_map.rb +441 -0
  242. data/lib/farce/internal/lease_pool_state.rb +228 -0
  243. data/lib/farce/internal/lease_state.rb +342 -0
  244. data/lib/farce/internal/lease_waiting.rb +13 -0
  245. data/lib/farce/internal/managed_queue.rb +26 -0
  246. data/lib/farce/internal/map_value_modes.rb +227 -0
  247. data/lib/farce/internal/marshal_support.rb +227 -0
  248. data/lib/farce/internal/mixin.rb +20 -0
  249. data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
  250. data/lib/farce/internal/noncopyable.rb +17 -0
  251. data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
  252. data/lib/farce/internal/pool_supervisor.rb +41 -0
  253. data/lib/farce/internal/pool_worker.rb +61 -0
  254. data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
  255. data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
  256. data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
  257. data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
  258. data/lib/farce/internal/portable_transaction.rb +164 -0
  259. data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
  260. data/lib/farce/internal/reservation_waiting.rb +24 -0
  261. data/lib/farce/internal/scheduled_task.rb +48 -0
  262. data/lib/farce/internal/scheduler_io.rb +171 -0
  263. data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
  264. data/lib/farce/internal/select_scheduler.rb +235 -0
  265. data/lib/farce/internal/storage.rb +162 -0
  266. data/lib/farce/internal/strict_lease.rb +30 -0
  267. data/lib/farce/internal/strict_lease_map.rb +20 -0
  268. data/lib/farce/internal/strict_lease_pool.rb +29 -0
  269. data/lib/farce/internal/thread_pool.rb +166 -0
  270. data/lib/farce/internal/transaction_conflict.rb +9 -0
  271. data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
  272. data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
  273. data/lib/farce/internal/undefined.rb +22 -0
  274. data/lib/farce/internal/unshared_lease.rb +29 -0
  275. data/lib/farce/internal/unshared_lease_pool.rb +34 -0
  276. data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
  277. data/lib/farce/internal/value_serialization.rb +13 -0
  278. data/lib/farce/internal/weak_map_value_modes.rb +58 -0
  279. data/lib/farce/internal/weak_mode_manager.rb +26 -0
  280. data/lib/farce/internal.rb +129 -0
  281. data/lib/farce/lazy.rb +100 -0
  282. data/lib/farce/lazy_ref.rb +40 -0
  283. data/lib/farce/lease.rb +28 -0
  284. data/lib/farce/lease_map.rb +34 -0
  285. data/lib/farce/lease_pool.rb +29 -0
  286. data/lib/farce/lfu_map.rb +53 -0
  287. data/lib/farce/local/atom.rb +23 -0
  288. data/lib/farce/local/counter.rb +88 -0
  289. data/lib/farce/local/flag.rb +65 -0
  290. data/lib/farce/local/lazy.rb +38 -0
  291. data/lib/farce/local/lazy_ref.rb +36 -0
  292. data/lib/farce/local/lease.rb +77 -0
  293. data/lib/farce/local/lease_map.rb +80 -0
  294. data/lib/farce/local/lease_pool.rb +38 -0
  295. data/lib/farce/local/lfu_map.rb +55 -0
  296. data/lib/farce/local/lru_map.rb +62 -0
  297. data/lib/farce/local/map.rb +47 -0
  298. data/lib/farce/local/molecule.rb +45 -0
  299. data/lib/farce/local/priority_queue.rb +53 -0
  300. data/lib/farce/local/queue.rb +32 -0
  301. data/lib/farce/local/scoped.rb +166 -0
  302. data/lib/farce/local/set.rb +18 -0
  303. data/lib/farce/local/sorted_set.rb +50 -0
  304. data/lib/farce/local/timer_queue.rb +41 -0
  305. data/lib/farce/local/tree_map.rb +47 -0
  306. data/lib/farce/local/vector.rb +30 -0
  307. data/lib/farce/local/weak_atom.rb +23 -0
  308. data/lib/farce/local/weak_key_map.rb +39 -0
  309. data/lib/farce/local/weak_map.rb +39 -0
  310. data/lib/farce/local/weak_set.rb +18 -0
  311. data/lib/farce/local/weak_value_map.rb +39 -0
  312. data/lib/farce/local.rb +35 -0
  313. data/lib/farce/lock.rb +54 -0
  314. data/lib/farce/lru_map.rb +63 -0
  315. data/lib/farce/map.rb +58 -0
  316. data/lib/farce/mode_manager.rb +142 -0
  317. data/lib/farce/molecule.rb +60 -0
  318. data/lib/farce/mutable.rb +171 -0
  319. data/lib/farce/pool.rb +332 -0
  320. data/lib/farce/port.rb +134 -0
  321. data/lib/farce/priority_queue.rb +70 -0
  322. data/lib/farce/proxy/register.rb +101 -0
  323. data/lib/farce/proxy/supervisor.rb +137 -0
  324. data/lib/farce/proxy/wrapper.rb +49 -0
  325. data/lib/farce/proxy.rb +291 -0
  326. data/lib/farce/queue.rb +60 -0
  327. data/lib/farce/ractor.rb +269 -0
  328. data/lib/farce/read_write_lock.rb +256 -0
  329. data/lib/farce/reference.rb +158 -0
  330. data/lib/farce/resolv/dns.rb +42 -0
  331. data/lib/farce/resolv.rb +85 -0
  332. data/lib/farce/scheduler.rb +536 -0
  333. data/lib/farce/set.rb +39 -0
  334. data/lib/farce/shareable.rb +150 -0
  335. data/lib/farce/signal.rb +97 -0
  336. data/lib/farce/sorted_set.rb +36 -0
  337. data/lib/farce/strict/atom.rb +34 -0
  338. data/lib/farce/strict/counter.rb +10 -0
  339. data/lib/farce/strict/exchanger.rb +25 -0
  340. data/lib/farce/strict/flag.rb +10 -0
  341. data/lib/farce/strict/lazy.rb +26 -0
  342. data/lib/farce/strict/lazy_ref.rb +26 -0
  343. data/lib/farce/strict/lease.rb +27 -0
  344. data/lib/farce/strict/lease_map.rb +33 -0
  345. data/lib/farce/strict/lease_pool.rb +28 -0
  346. data/lib/farce/strict/lfu_map.rb +27 -0
  347. data/lib/farce/strict/lru_map.rb +28 -0
  348. data/lib/farce/strict/map.rb +54 -0
  349. data/lib/farce/strict/molecule.rb +16 -0
  350. data/lib/farce/strict/port.rb +40 -0
  351. data/lib/farce/strict/priority_queue.rb +20 -0
  352. data/lib/farce/strict/queue.rb +17 -0
  353. data/lib/farce/strict/set.rb +14 -0
  354. data/lib/farce/strict/sorted_set.rb +19 -0
  355. data/lib/farce/strict/timer_queue.rb +13 -0
  356. data/lib/farce/strict/tree_map.rb +27 -0
  357. data/lib/farce/strict/vector.rb +24 -0
  358. data/lib/farce/strict/weak_atom.rb +31 -0
  359. data/lib/farce/strict/weak_key_map.rb +49 -0
  360. data/lib/farce/strict/weak_map.rb +49 -0
  361. data/lib/farce/strict/weak_set.rb +14 -0
  362. data/lib/farce/strict/weak_value_map.rb +48 -0
  363. data/lib/farce/strict.rb +28 -0
  364. data/lib/farce/system.rb +40 -0
  365. data/lib/farce/thread_scheduler.rb +70 -0
  366. data/lib/farce/timer_queue.rb +66 -0
  367. data/lib/farce/transaction/atom.rb +73 -0
  368. data/lib/farce/transaction/map.rb +25 -0
  369. data/lib/farce/transaction/map_operations.rb +143 -0
  370. data/lib/farce/transaction/molecule.rb +90 -0
  371. data/lib/farce/transaction/mutable.rb +61 -0
  372. data/lib/farce/transaction/set.rb +17 -0
  373. data/lib/farce/transaction/set_operations.rb +48 -0
  374. data/lib/farce/transaction/sorted_set.rb +18 -0
  375. data/lib/farce/transaction/tree_map.rb +78 -0
  376. data/lib/farce/transaction/vector.rb +139 -0
  377. data/lib/farce/transaction/wrapper.rb +202 -0
  378. data/lib/farce/transaction.rb +343 -0
  379. data/lib/farce/tree_map.rb +56 -0
  380. data/lib/farce/unsafe/lfu_map.rb +36 -0
  381. data/lib/farce/unsafe/lru_map.rb +36 -0
  382. data/lib/farce/unsafe/tree_map.rb +21 -0
  383. data/lib/farce/unsafe.rb +29 -0
  384. data/lib/farce/unshareable.rb +67 -0
  385. data/lib/farce/unshared/atom.rb +24 -0
  386. data/lib/farce/unshared/counter.rb +10 -0
  387. data/lib/farce/unshared/flag.rb +10 -0
  388. data/lib/farce/unshared/lazy.rb +38 -0
  389. data/lib/farce/unshared/lazy_ref.rb +26 -0
  390. data/lib/farce/unshared/lease.rb +27 -0
  391. data/lib/farce/unshared/lease_map.rb +26 -0
  392. data/lib/farce/unshared/lease_pool.rb +25 -0
  393. data/lib/farce/unshared/lfu_map.rb +20 -0
  394. data/lib/farce/unshared/lru_map.rb +20 -0
  395. data/lib/farce/unshared/map.rb +49 -0
  396. data/lib/farce/unshared/molecule.rb +16 -0
  397. data/lib/farce/unshared/priority_queue.rb +16 -0
  398. data/lib/farce/unshared/queue.rb +27 -0
  399. data/lib/farce/unshared/set.rb +14 -0
  400. data/lib/farce/unshared/sorted_set.rb +28 -0
  401. data/lib/farce/unshared/timer_queue.rb +16 -0
  402. data/lib/farce/unshared/tree_map.rb +21 -0
  403. data/lib/farce/unshared/vector.rb +19 -0
  404. data/lib/farce/unshared/weak_atom.rb +31 -0
  405. data/lib/farce/unshared/weak_key_map.rb +44 -0
  406. data/lib/farce/unshared/weak_map.rb +44 -0
  407. data/lib/farce/unshared/weak_set.rb +14 -0
  408. data/lib/farce/unshared/weak_value_map.rb +43 -0
  409. data/lib/farce/unshared.rb +28 -0
  410. data/lib/farce/vector.rb +228 -0
  411. data/lib/farce/version.rb +8 -0
  412. data/lib/farce/walker/definitions.rb +186 -0
  413. data/lib/farce/walker/modification.rb +355 -0
  414. data/lib/farce/walker.rb +319 -0
  415. data/lib/farce/weak_atom.rb +121 -0
  416. data/lib/farce/weak_key_map.rb +31 -0
  417. data/lib/farce/weak_map.rb +33 -0
  418. data/lib/farce/weak_ref.rb +67 -0
  419. data/lib/farce/weak_set.rb +82 -0
  420. data/lib/farce/weak_value.rb +195 -0
  421. data/lib/farce/weak_value_map.rb +32 -0
  422. data/lib/farce.rb +519 -0
  423. metadata +468 -0
data/docs/modes.md ADDED
@@ -0,0 +1,628 @@
1
+ <!--
2
+ # @title Transfer modes
3
+ -->
4
+
5
+ # Transfer modes
6
+
7
+ Farce adds transfer modes for handling non-shareable data between Ractors.
8
+ This improves over the default control via `Ractor::Port`'s `move:` option.
9
+
10
+ ## Table of Contents
11
+
12
+ - [Transfer modes](#transfer-modes)
13
+ - [Table of Contents](#table-of-contents)
14
+ - [Introduction: What Ruby gives you](#introduction-what-ruby-gives-you)
15
+ - [What `move: true` does on `Ractor::Port`](#what-move-true-does-on-ractorport)
16
+ - [Farce's Modes](#farces-modes)
17
+ - [`:copy`: keep working with the original](#copy-keep-working-with-the-original)
18
+ - [`:move`: hand off a finished batch](#move-hand-off-a-finished-batch)
19
+ - [`:local`: preserve object identity](#local-preserve-object-identity)
20
+ - [`:make_shareable`: publish the original](#make_shareable-publish-the-original)
21
+ - [`:mutable`: share editable values](#mutable-share-editable-values)
22
+ - [`:shareable_copy`: publish without freezing your draft](#shareable_copy-publish-without-freezing-your-draft)
23
+ - [`:dedup`: reuse equal values](#dedup-reuse-equal-values)
24
+ - [`:proxy`: access an object in its original Ractor](#proxy-access-an-object-in-its-original-ractor)
25
+ - [`:raise`: require prepared data](#raise-require-prepared-data)
26
+ - [Set a default, then override individual sends](#set-a-default-then-override-individual-sends)
27
+ - [Let local sends keep their identity](#let-local-sends-keep-their-identity)
28
+ - [Other classes that support modes](#other-classes-that-support-modes)
29
+ - [Queue work for another Ractor](#queue-work-for-another-ractor)
30
+ - [Add priorities or delayed delivery](#add-priorities-or-delayed-delivery)
31
+ - [Exchange mutable messages with a partner](#exchange-mutable-messages-with-a-partner)
32
+ - [Publish state through an atom or map](#publish-state-through-an-atom-or-map)
33
+ - [Store a series of snapshots in a vector](#store-a-series-of-snapshots-in-a-vector)
34
+ - [Pass task data as arguments](#pass-task-data-as-arguments)
35
+ - [Under the hood](#under-the-hood)
36
+ - [Envelopes separate transport from access](#envelopes-separate-transport-from-access)
37
+ - [When copying and moving happen](#when-copying-and-moving-happen)
38
+ - [Forward envelopes without opening them](#forward-envelopes-without-opening-them)
39
+ - [Mode managers prepare values and open their own envelopes](#mode-managers-prepare-values-and-open-their-own-envelopes)
40
+ - [Add modes to your own abstraction](#add-modes-to-your-own-abstraction)
41
+
42
+
43
+ ## Introduction: What Ruby gives you
44
+
45
+ A shareable object can be used by several Ractors at once. Symbols, integers, and deeply frozen data are common examples. A mutable array or hash is normally non-shareable. Freezing just the outer container is not enough if it still contains mutable objects. Ruby provides [`Ractor.shareable?` and `Ractor.make_shareable`](https://docs.ruby-lang.org/en/4.0/Ractor.html#class-Ractor-label-Shareable+and+unshareable+objects) to check and prepare data.
46
+
47
+ ```ruby
48
+ Ractor.shareable?(:ready) # => true
49
+ Ractor.shareable?([1, 2]) # => false
50
+ Ractor.shareable?([String.new("draft")].freeze) # => false
51
+
52
+ settings = { formats: [String.new("json")] }
53
+ Ractor.make_shareable(settings)
54
+ Ractor.shareable?(settings) # => true
55
+ settings[:formats].frozen? # => true
56
+ ```
57
+
58
+ Shareability does not always mean immutability. Farce provides shareable objects such as queues and atoms with coordinated operations. A shareable queue can still carry mutable application data. Its transfer mode determines how that data becomes available to the reader.
59
+
60
+ #### What `move: true` does on `Ractor::Port`
61
+
62
+ Ruby's [`Ractor::Port`](https://docs.ruby-lang.org/en/4.0/Ractor/Port.html#method-i-send) sends shareable objects by reference. It copies non-shareable objects by default. With `move: true`, the sender gives up access to the non-shareable parts of the message. Only the Ractor that created a port can receive from it.
63
+
64
+ ```ruby
65
+ # Native Ruby 4.0 or later.
66
+ port = Ractor::Port.new
67
+ message = [1, 2]
68
+ port.send(message)
69
+ message << 3
70
+ port.receive # => [1, 2]
71
+
72
+ batch = [4, 5]
73
+ port.send(batch, move: true)
74
+ # Do not use batch after sending it. Its contents have been moved.
75
+ port.receive # => [4, 5]
76
+ port.close
77
+ ```
78
+
79
+ Farce extends this choice with nine named modes.
80
+
81
+ ## Farce's Modes
82
+
83
+ The following modes are accepted by `Farce::Port`. They apply to non-shareable values. Already-shareable values pass through unchanged, including when you select `:move` or `:raise`.
84
+
85
+ | Mode | What happens to non-shareable data | A typical use |
86
+ | --- | --- | --- |
87
+ | `:copy` (default) | Transfers a copy and leaves the original usable. This is the default. | Send a snapshot of a request. |
88
+ | `:move` | Transfers ownership and makes the original inaccessible. | Hand a completed batch to a consumer. |
89
+ | `:local` | Keeps the same object in its originating Ractor. | Pass work between local threads or fibers. |
90
+ | `:make_shareable` | Calls `Ractor.make_shareable` on the original. | Publish finished configuration. |
91
+ | `:mutable` | Copies non-shareable objects into a `Farce::Mutable`. | Synchronize mutations across Ractors. |
92
+ | `:shareable_copy` | Makes a shareable copy and leaves the original alone. | Publish a snapshot of an editable document. |
93
+ | `:dedup` | Deduplicates the value, then makes it shareable. May update and freeze the original. | Reuse repeated message contents. |
94
+ | `:proxy` | Creates a `Farce::Proxy` that executes calls in the original Ractor. | Share access to a mutable object. |
95
+ | `:raise` | Raises `Ractor::IsolationError`. | Enforce a shareable-data boundary. |
96
+
97
+ ### `:copy`: keep working with the original
98
+
99
+ Copying is a good starting point for ordinary hashes, arrays, and strings. The receiver can change its copy without changing the sender's data. Shareable parts of the message can still be reused.
100
+
101
+ ```ruby
102
+ port = Farce::Port.new
103
+ request = { ids: [10, 20] }
104
+ port.send(request)
105
+ request[:ids] << 30
106
+
107
+ received = port.receive
108
+ received[:ids] # => [10, 20]
109
+ received[:ids] << 40
110
+ request[:ids] # => [10, 20, 30]
111
+ port.close
112
+ ```
113
+
114
+ Copying is not a way to serialize every Ruby object. Some objects cannot be copied across Ractors. Choose a mode that fits the value, or send ordinary data from which the receiver can build what it needs.
115
+
116
+ ### `:move`: hand off a finished batch
117
+
118
+ Moving is useful when a producer has finished building a message and will never use it again. It avoids copying the payload. Do not retain a plan to reuse the original or its moved nested objects after sending.
119
+
120
+ ```ruby
121
+ results = Farce::Port.new(mode: :move)
122
+ producer = Farce::Ractor.new(results) do |outbox|
123
+ rows = [[1, 100], [2, 250]]
124
+ outbox.send(rows)
125
+ # rows now belongs to the receiving side.
126
+ nil
127
+ end
128
+
129
+ received = results.receive
130
+ received << [3, 75]
131
+ received.length # => 3
132
+ producer.join
133
+ results.close
134
+ ```
135
+
136
+ Moving does not freeze the received data. The receiver can continue editing it. Attempting to use a moved source object raises a moved-object error on native Ractors.
137
+
138
+ ### `:local`: preserve object identity
139
+
140
+ Use `:local` when the producer and consumer run in the same Ractor. This includes different threads or fibers in that Ractor. The receiver gets the original object, so changes made before receiving are visible.
141
+
142
+ ```ruby
143
+ port = Farce::Port.new(mode: :local)
144
+ job = { steps: [] }
145
+
146
+ Thread.new { port.send(job) }.join
147
+ job[:steps] << :prepared
148
+
149
+ received = port.receive
150
+ received.equal?(job) # => true
151
+ received[:steps] # => [:prepared]
152
+ port.close
153
+ ```
154
+
155
+ A different Ractor cannot open a local payload. For example, sending non-shareable data with `:local` from a worker to a port owned by the main Ractor makes receiving fail with `Farce::Envelope::AlreadyClaimed`. Local mode also does not synchronize later edits to the payload. Coordinate concurrent mutation yourself.
156
+
157
+ ### `:make_shareable`: publish the original
158
+
159
+ Use `:make_shareable` when a value is finished and every reader should see the same shareable object. For ordinary arrays and hashes, this recursively freezes their contents. All existing references to that data see the freezing too.
160
+
161
+ ```ruby
162
+ port = Farce::Port.new(mode: :make_shareable)
163
+ settings = { retries: [1, 2, 5] }
164
+ port.send(settings)
165
+
166
+ published = port.receive
167
+ published.equal?(settings) # => true
168
+ Farce::Ractor.shareable?(published) # => true
169
+ settings[:retries].frozen? # => true
170
+ port.close
171
+ ```
172
+
173
+ Ruby raises an error if the value cannot be made shareable. This mode works well for configuration and completed lookup tables, but it cannot turn arbitrary resources into shared objects.
174
+
175
+ ### `:mutable`: share editable values
176
+
177
+ Use `:mutable` when several Ractors need to edit the same stored value. Farce wraps each non-shareable value in a `Farce::Mutable`. The wrapper keeps a frozen snapshot and atomically replaces it when a method mutates the value. Changes are visible through the same wrapper in every Ractor. The original object stays separate and usable.
178
+
179
+ ```ruby
180
+ # Native Ruby 4.0 or later.
181
+ draft = String.new("queued")
182
+ statuses = Farce::Map.new(
183
+ { import: draft, export: String.new("queued") },
184
+ mode: :mutable,
185
+ )
186
+
187
+ worker = Farce::Ractor.new(statuses) do |shared|
188
+ shared[:import].replace("running")
189
+ shared[:export] << " for retry"
190
+ nil
191
+ end
192
+
193
+ worker.join
194
+
195
+ statuses[:import].to_s # => "running"
196
+ statuses[:export].to_s # => "queued for retry"
197
+ draft # => "queued"
198
+
199
+ Farce::Mutable.deref(statuses[:import]).frozen? # => true
200
+ ```
201
+
202
+ Each mutation copies the entire wrapped value. Prefer a dedicated Farce collection for large arrays or hashes. Wrapping is shallow, so nested values must already be shareable. A map applies the mode to its individual values, which makes mutable strings a useful fit. `Farce::Mutable.deref` returns the current snapshot without changing it.
203
+
204
+ On JRuby and TruffleRuby, ordinary values are already shareable and pass through unchanged, so this mode does not add wrappers or synchronize their mutations.
205
+
206
+ ### `:shareable_copy`: publish without freezing your draft
207
+
208
+ Use `:shareable_copy` when readers need a stable snapshot but the sender will keep editing. Farce calls `Ractor.make_shareable(value, copy: true)`.
209
+
210
+ ```ruby
211
+ port = Farce::Port.new(mode: :shareable_copy)
212
+ draft = { tags: [:ruby] }
213
+ port.send(draft)
214
+ draft[:tags] << :concurrency
215
+
216
+ published = port.receive
217
+ published[:tags] # => [:ruby]
218
+ Farce::Ractor.shareable?(published) # => true
219
+ draft[:tags] # => [:ruby, :concurrency]
220
+ draft.frozen? # => false
221
+ port.close
222
+ ```
223
+
224
+ ### `:dedup`: reuse equal values
225
+
226
+ Use `:dedup` when messages contain repeated values. Farce calls `Farce.dedup(value)`, then makes the result Ractor-shareable. Equal strings, arrays, hashes, and Ruby Sets can reuse cached instances, including nested values.
227
+
228
+ ```ruby
229
+ port = Farce::Port.new(mode: :dedup)
230
+ port.send([String.new("ready")])
231
+ first = port.receive
232
+ port.send([String.new("ready")])
233
+ second = port.receive
234
+
235
+ second.equal?(first) # => true
236
+ Farce::Ractor.shareable?(second) # => true
237
+ port.close
238
+ ```
239
+
240
+ Deduplication may update and freeze the original. The cache holds values weakly, so keep a reference to a result when its identity matters. Already-shareable inputs pass through unchanged. On JRuby and TruffleRuby, ordinary values are already shareable and skip deduplication. Values that cannot be made shareable raise an error.
241
+
242
+ ### `:proxy`: access an object in its original Ractor
243
+
244
+ Use `:proxy` when another Ractor needs to call methods on an object that should stay in its originating Ractor. Farce creates a shareable `Farce::Proxy`. Calls through the proxy execute in the original Ractor and can mutate the original object.
245
+
246
+ ```ruby
247
+ # Native Ruby 4.0 or later.
248
+ port = Farce::Port.new(mode: :proxy)
249
+ events = []
250
+
251
+ port.send(events)
252
+ proxy = port.receive
253
+
254
+ worker = Farce::Ractor.new(proxy) do |shared|
255
+ shared << :processed
256
+ nil
257
+ end
258
+ worker.join
259
+
260
+ events # => [:processed]
261
+ proxy.length # => 1
262
+ port.close
263
+ ```
264
+
265
+ Each delegated call involves a request and response. Non-shareable arguments and return values are copied by default, while methods that return the original object return its proxy. Keep the owning Ractor alive while callers use the proxy. Calls raise `Farce::Ractor::RemoteError` after the owner exits. Access through other references to the original object still needs its own coordination.
266
+
267
+ On JRuby and TruffleRuby, ordinary values are already shareable, so this mode passes them through without creating a proxy.
268
+
269
+ ### `:raise`: require prepared data
270
+
271
+ Use `:raise` when callers should prepare shareable messages explicitly. A bad value fails at the send operation, where the caller can fix it.
272
+
273
+ ```ruby
274
+ port = Farce::Port.new(mode: :raise)
275
+ port.send(:ready)
276
+ port.receive # => :ready
277
+
278
+ begin
279
+ port.send({ ids: [1, 2] })
280
+ rescue Farce::Ractor::IsolationError
281
+ # Prepare the message before retrying.
282
+ end
283
+
284
+ message = Farce::Ractor.make_shareable({ ids: [1, 2] })
285
+ port.send(message)
286
+ port.receive.equal?(message) # => true
287
+ port.close
288
+ ```
289
+
290
+ ### Set a default, then override individual sends
291
+
292
+ A port's `mode:` sets its default. A `mode:` on `send` changes that message alone. `send` also accepts Ruby's `move:` option. An explicit mode takes precedence over `move:`. On a port whose default is `:move`, `move: false` selects `:copy`. On other ports, `move: false` keeps the default.
293
+
294
+ ```ruby
295
+ port = Farce::Port.new(mode: :raise)
296
+ port.send([1, 2], mode: :copy)
297
+ port.receive # => [1, 2]
298
+ port.mode # => :raise
299
+ port.close
300
+
301
+ handoff = Farce::Port.new(mode: :move)
302
+ batch = [3, 4]
303
+ handoff.send(batch, move: false)
304
+ handoff.receive # => [3, 4]
305
+ batch << 5 # The original is still usable.
306
+ handoff.close
307
+ ```
308
+
309
+ ### Let local sends keep their identity
310
+
311
+ `auto_local: true` makes a port use `:local` for non-shareable messages sent from its owning Ractor. Sends from other Ractors still use the selected mode. This is useful for an inbox receiving both local work and remote results.
312
+
313
+ ```ruby
314
+ port = Farce::Port.new(mode: :copy, auto_local: true)
315
+ local_job = { ids: [1] }
316
+ port.send(local_job)
317
+ port.receive.equal?(local_job) # => true
318
+
319
+ # Force a snapshot even though the sender owns the port.
320
+ port.send(local_job, mode: :copy, auto_local: false)
321
+ port.receive.equal?(local_job) # => false
322
+ port.close
323
+ ```
324
+
325
+ Automatic local transfer takes precedence even over an explicit `mode:` or `move:`. Disable it for a particular send when the selected transfer behavior matters. Its default is `false` on ports.
326
+
327
+ ## Other classes that support modes
328
+
329
+ The same choices appear in several Farce APIs. Containers normally default to `:copy` and unwrap their managed values when you read them. Setting a container's mode does not make the values it returns shareable.
330
+
331
+ | Class | Where to select a mode | What it controls |
332
+ | --- | --- | --- |
333
+ | `Farce::Queue` | `new`, `push`, `try_push` | Queued values. |
334
+ | `Farce::PriorityQueue` | `new`, `push`, `try_push` | Values, independently of priority. |
335
+ | `Farce::TimerQueue` | `new`, `push`, `try_push` | Values, independently of their scheduled time. |
336
+ | `Farce::Exchanger` | `new`, `exchange` | The value offered to a partner. |
337
+ | `Farce::WeakAtom` | `new`, `store`, `update`, and other replacement operations | Weakly held values. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
338
+ | `Farce::Atom` | `new`, `store`, `swap`, `update`, and other replacement operations | The stored value. |
339
+ | `Farce::Lazy`, `Farce::LazyRef` | `new` | The result computed once by the factory. |
340
+ | `Farce::Molecule` | `define`, `new` | Newly created field atoms. |
341
+ | `Farce::Vector` | `new`, `push`, `store`, `update`, and other replacement operations | Element values. |
342
+ | `Farce::WeakSet` | `new`, `add`, `add?` | Weakly held elements. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
343
+ | `Farce::Set`, `Farce::SortedSet` | `new`, `add`, `add?` | Set elements. Membership uses an insertion-time snapshot. |
344
+ | `Farce::WeakMap`, `Farce::WeakValueMap` | `new`, `store`, `update`, and other replacement operations | Weakly held values only. Supports only `:raise`, `:make_shareable`, and `:dedup`. Defaults to `:raise`. |
345
+ | `Farce::Map`, `Farce::WeakKeyMap` | `new`, `store`, `update`, and other replacement operations | Values only. Keys must already be shareable. |
346
+ | `Farce::TreeMap` | `new` | Values only. Keys follow the tree map's own rules. |
347
+ | `Farce::LRUMap` | `new` | Values only. Individual value hits and writes update eviction order. |
348
+ | `Farce::LFUMap` | `new` | Values only. Individual value hits and writes update eviction frequency. |
349
+ | `Farce::Scheduler` | `schedule` | Task arguments, with automatic local transfer enabled by default. |
350
+ | `Farce::ThreadScheduler` | `schedule`, `execute` | Accepts scheduler options but always keeps arguments local. |
351
+ | `Farce::Pool` | `schedule` | Task arguments. `:local` is rejected. |
352
+
353
+ `Farce::Lazy` runs its shareable factory once and applies the mode to the result. With
354
+ `:copy`, each Ractor receives its own cached copy. Use `Farce::Strict::Lazy` when the
355
+ result must already be shareable, or `Farce::Unshared::Lazy` for a factory that captures
356
+ mutable state within one Ractor. The corresponding `LazyRef` classes delegate to those
357
+ results directly.
358
+
359
+ ```ruby
360
+ snapshot = Farce::Lazy.new(mode: :make_shareable) { { jobs: [] } }
361
+ snapshot.value # => { jobs: [] }
362
+ Farce::Ractor.shareable?(snapshot.value) # => true
363
+ ```
364
+
365
+ ### Queue work for another Ractor
366
+
367
+ Unlike a port, a queue does not restrict consumption to its creator. This makes a queue a natural place to hand work to a consumer. Here the worker gets ownership of a batch and sends back a shareable integer.
368
+
369
+ ```ruby
370
+ jobs = Farce::Queue.new(mode: :move)
371
+ results = Farce::Port.new(mode: :raise)
372
+ worker = Farce::Ractor.new(jobs, results) do |inbox, outbox|
373
+ numbers = inbox.pop
374
+ outbox.send(numbers.sum)
375
+ end
376
+
377
+ jobs.push([10, 20, 30])
378
+ results.receive # => 60
379
+ worker.join
380
+ results.close
381
+ ```
382
+
383
+ ### Add priorities or delayed delivery
384
+
385
+ Priority and timing do not change the meaning of a mode. You can select a default for the queue and override it for an individual item.
386
+
387
+ ```ruby
388
+ urgent = Farce::PriorityQueue.new(mode: :shareable_copy)
389
+ urgent.push({ action: :refresh }, priority: 0)
390
+ urgent.pop # => { action: :refresh }
391
+
392
+ retries = Farce::TimerQueue.new(mode: :copy)
393
+ retries.push({ attempt: 2 }, at: Farce::Clock.now, mode: :make_shareable)
394
+ Farce::Ractor.shareable?(retries.pop) # => true
395
+ ```
396
+
397
+ Wrapping happens before waiting for queue space or an exchange partner. A failed `try_push` or a timeout can therefore leave a value moved or frozen. Use copying when you need to retain the original for a retry. Also, reading a moved payload can claim it: `TimerQueue#peek` opens the payload even though it leaves the item queued.
398
+
399
+ ### Exchange mutable messages with a partner
400
+
401
+ An exchanger pairs two callers. Each caller receives the other's offered value. Copy mode lets both callers retain their originals.
402
+
403
+ ```ruby
404
+ exchange = Farce::Exchanger.new(mode: :copy)
405
+ worker = Farce::Ractor.new(exchange) do |meeting|
406
+ received = meeting.exchange({ status: :ready })
407
+ received[:command] # => :start
408
+ end
409
+
410
+ reply = exchange.exchange({ command: :start })
411
+ reply # => { status: :ready }
412
+ worker.join
413
+ ```
414
+
415
+ ### Publish state through an atom or map
416
+
417
+ Shareable snapshots work well when multiple Ractors read the same state. Replace a snapshot through the container's update operation instead of mutating a returned hash or array. The mode also applies to the replacement returned by the block.
418
+
419
+ ```ruby
420
+ state = Farce::Atom.new({ completed: 0 }, mode: :make_shareable)
421
+ state.update { |current| { completed: current[:completed] + 1 } }
422
+ state.value # => { completed: 1 }
423
+
424
+ cache = Farce::Map.new(mode: :shareable_copy)
425
+ draft = { roles: [:reader] }
426
+ cache[:account] = draft
427
+ draft[:roles] << :editor
428
+ cache[:account] # => { roles: [:reader] }
429
+
430
+ cache.update(:account) { |current| { roles: current[:roles] + [:admin] } }
431
+ cache[:account] # => { roles: [:reader, :admin] }
432
+ ```
433
+
434
+ `Farce::WeakAtom`, `Farce::WeakMap`, and `Farce::WeakValueMap` default to
435
+ `:raise` and accept only `:raise`, `:make_shareable`, and `:dedup`. They store
436
+ prepared values directly and retain them weakly. Unsupported modes raise
437
+ `ArgumentError`, including when the supplied value is already shareable.
438
+ Modes never prepare map keys or expected values used by comparisons and waits.
439
+
440
+ With `:make_shareable`, keeping the original referenced keeps the stored value
441
+ alive. With `:dedup`, the stored canonical value can differ from the input.
442
+ Keep the result returned by `store` or `update` when it must remain alive.
443
+ Assignment evaluates to the input, so it does not reliably retain the canonical
444
+ result. A constructor also retains no strong reference to its prepared value.
445
+
446
+ ```ruby
447
+ cache = Farce::WeakValueMap.new(mode: :dedup)
448
+ retained = cache.store(:roles, [String.new("reader")])
449
+ cache[:roles].equal?(retained) # => true
450
+ # The entry can disappear after retained is no longer referenced.
451
+ ```
452
+
453
+ `Farce::WeakSet` uses the same restricted modes for its elements. It stores
454
+ prepared elements directly and retains them weakly. Membership checks and
455
+ deletion do not freeze or deduplicate their arguments. `add` returns the set,
456
+ and `add?` returns the set or nil. Neither returns the prepared element.
457
+ With `:dedup`, retain the canonical element elsewhere if it must stay alive.
458
+ Already-shareable elements pass through unchanged, as in the other containers.
459
+
460
+ ```ruby
461
+ retained = Farce::Ractor.make_shareable(Farce.dedup([String.new("reader")]))
462
+ set = Farce::WeakSet.new(mode: :dedup)
463
+ set.add(retained)
464
+ set.include?(retained) # => true
465
+ # The element can disappear after retained is no longer referenced.
466
+ ```
467
+
468
+ `Farce::WeakKeyMap` uses the same value modes, but keeps its keys weakly. `Farce::TreeMap` keeps entries sorted by key and selects the value mode at construction. Modes do not copy or wrap map keys. Tree maps also make mutable string keys immutable.
469
+
470
+ ### Store a series of snapshots in a vector
471
+
472
+ A vector can apply a mode to its initial elements and to later writes. Use `push` or `store` when an individual write needs a different mode.
473
+
474
+ ```ruby
475
+ history = Farce::Vector.new([], mode: :shareable_copy)
476
+ draft = { version: 1 }
477
+ history.push(draft)
478
+ draft[:version] = 2
479
+ history.push(draft)
480
+
481
+ history[0] # => { version: 1 }
482
+ history[1] # => { version: 2 }
483
+ Farce::Ractor.shareable?(history[0]) # => true
484
+ ```
485
+
486
+ For containers that retain values, `:copy` gives each Ractor its own copy of a stored envelope's contents. Repeated reads in the same Ractor reuse that copy. Mutating it does not publish an update to other Ractors. Prefer explicit replacement operations for shared state. Similarly, `:move` is usually better suited to a handoff than to a value many Ractors need to read.
487
+
488
+ ### Pass task data as arguments
489
+
490
+ `Scheduler#schedule` and `Pool#schedule` accept `mode:` for task arguments. Pass mutable data as arguments instead of capturing it from the surrounding scope. A non-local task's block must be convertible to a shareable proc.
491
+
492
+ ```ruby
493
+ pool = Farce::Pool.new(max_size: 2)
494
+ results = Farce::Port.new(mode: :raise)
495
+ batch = [2, 4, 6]
496
+
497
+ pool.schedule(batch, results, mode: :copy) do |numbers, outbox|
498
+ outbox.send(numbers.sum)
499
+ end
500
+
501
+ results.receive # => 12
502
+ pool.close
503
+ results.close
504
+ ```
505
+
506
+ A scheduler defaults to `auto_local: true`, preserving the block and its arguments when scheduling from its owning Ractor. Set `auto_local: false` to enforce the requested mode there. A pool may choose another Ractor for any task, so it rejects `:local` and does not apply automatic local transfer.
507
+
508
+ ## Under the hood
509
+
510
+ ### Envelopes separate transport from access
511
+
512
+ A `Farce::Envelope` is a shareable wrapper around a value. Passing the envelope around does not require opening it. Calling `value` opens it and retrieves the payload according to the envelope's ownership rules.
513
+
514
+ ```ruby
515
+ source = { ids: [1, 2] }
516
+ envelope = Farce::Envelope.new(source, mode: :copy)
517
+ Farce::Ractor.shareable?(envelope) # => true
518
+ source[:ids] << 3
519
+
520
+ envelope.value[:ids] # => [1, 2]
521
+ envelope.value.equal?(envelope.value) # => true
522
+ ```
523
+
524
+ `Envelope.new` defaults to `:copy` and accepts `:copy`, `:move`, or `:local`. These are envelope types, not the full set of manager modes. A shareable payload produces an `Envelope::Share`. The other manager modes either prepare shareable data directly or raise an error.
525
+
526
+ ### When copying and moving happen
527
+
528
+ A copy envelope copies its non-shareable payload into internal storage when created. Each Ractor that opens it gets another copy, cached for that Ractor. A move envelope moves the payload into storage immediately, then moves it out when the winning Ractor first opens it. Forwarding either envelope does not add a payload copy or move at every hop.
529
+
530
+ | Envelope | Who can open it? | What repeated reads return |
531
+ | --- | --- | --- |
532
+ | `Envelope::Copy` | Any Ractor. | That Ractor's cached copy. |
533
+ | `Envelope::Move` | The first Ractor to claim it. | The owning Ractor's received object. |
534
+ | `Envelope::Local` | Its creating Ractor. | The original object. |
535
+ | `Envelope::Share` | Any Ractor. | The shared payload. |
536
+
537
+ A claim belongs to a Ractor, not a thread or fiber, and cannot be revoked. `claim` returns the envelope on success or `nil` if another Ractor owns it. `claim!` and `value` raise `Farce::Envelope::AlreadyClaimed` on failure. Use `claimed?` to ask whether it has an owner and `owned?` to ask whether the current Ractor can open it. Copy and share envelopes report both as true because every Ractor can open them.
538
+
539
+ ### Forward envelopes without opening them
540
+
541
+ An explicitly created envelope stays an envelope when passed through a Farce port or queue. This lets a dispatcher route a job without taking ownership of the job's mutable contents. In this example, only the consumer opens the move envelope.
542
+
543
+ ```ruby
544
+ incoming = Farce::Queue.new(mode: :raise)
545
+ ready = Farce::Queue.new(mode: :raise)
546
+ results = Farce::Port.new(mode: :raise)
547
+
548
+ router = Farce::Ractor.new(incoming, ready) do |inbox, outbox|
549
+ package = inbox.pop
550
+ # Route the shareable wrapper. Do not call package.value here.
551
+ outbox.push(package)
552
+ end
553
+
554
+ consumer = Farce::Ractor.new(ready, results) do |inbox, outbox|
555
+ package = inbox.pop
556
+ numbers = package.value
557
+ outbox.send(numbers.sum)
558
+ end
559
+
560
+ payload = [10, 20, 30]
561
+ package = Farce::Envelope.new(payload, mode: :move)
562
+
563
+ # payload is already moved, before the first queue operation.
564
+ incoming.push(package)
565
+
566
+ results.receive # => 60
567
+ router.join
568
+ consumer.join
569
+ results.close
570
+ ```
571
+
572
+ Both queues accept the envelope in `:raise` mode because the wrapper is shareable. Neither queue opens it automatically. For routing metadata, put the envelope in a shareable message such as `[:billing, package].freeze`. The router can read the destination without accessing the payload.
573
+
574
+ ### Mode managers prepare values and open their own envelopes
575
+
576
+ `Farce::ModeManager` provides two core operations: `wrap` prepares a value for shared storage, and `unwrap` retrieves values from envelopes that this manager created. It passes already-shareable values through unchanged. For non-shareable values, `:copy`, `:move`, and `:local` create managed envelopes. The remaining modes prepare shareable data directly, create a `Farce::Mutable` or `Farce::Proxy`, or raise. Mutable and proxy wrappers remain wrapped when read.
577
+
578
+ ```ruby
579
+ manager = Farce::ModeManager.new(mode: :copy)
580
+ source = { ids: [1] }
581
+ stored = manager.wrap(source)
582
+ source[:ids] << 2
583
+ manager.unwrap(stored) # => { ids: [1] }
584
+
585
+ # A different manager leaves this wrapper intact.
586
+ other = Farce::ModeManager.new
587
+ other.unwrap(stored).equal?(stored) # => true
588
+
589
+ # So does the original manager for a user-created envelope.
590
+ explicit = Farce::Envelope.new([3, 4], mode: :move)
591
+ manager.unwrap(manager.wrap(explicit)).equal?(explicit) # => true
592
+ explicit.claimed? # => false
593
+ ```
594
+
595
+ Containers automatically open the envelopes they created to implement a mode. They preserve envelopes supplied as application data. Ports use the underlying port's native copy and move paths for those two modes, and a mode manager for the additional behaviors.
596
+
597
+ ### Add modes to your own abstraction
598
+
599
+ Keep one manager per instance when building an abstraction around shareable storage. Use the same manager on both the write and read paths. Here a strict Farce queue stands in for storage that accepts only shareable objects.
600
+
601
+ ```ruby
602
+ class WorkInbox
603
+ include Farce::Shareable
604
+
605
+ def initialize(mode: :copy)
606
+ @manager = Farce::ModeManager.new(mode: mode)
607
+ @storage = Farce::Queue.new(mode: :raise)
608
+ super()
609
+ end
610
+
611
+ def push(value, mode: nil)
612
+ @storage.push(@manager.wrap(value, mode: mode))
613
+ self
614
+ end
615
+
616
+ def pop
617
+ @manager.unwrap(@storage.pop)
618
+ end
619
+ end
620
+
621
+ inbox = WorkInbox.new(mode: :shareable_copy)
622
+ draft = { ids: [1, 2] }
623
+ inbox.push(draft)
624
+ draft[:ids] << 3
625
+ inbox.pop # => { ids: [1, 2] }
626
+ ```
627
+
628
+ In application code, `Farce::Queue` already does this work. A separate manager is useful when adapting another storage primitive or building a larger abstraction. It provides the same transfer choices while preserving user-created envelopes for later processing.