farce 0.0.1.alpha2-x86-linux-musl

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/scopes.md ADDED
@@ -0,0 +1,649 @@
1
+ <!--
2
+ # @title Local scopes
3
+ -->
4
+
5
+ # Local scopes
6
+
7
+ Farce's local containers let you share one object while keeping separate contents for different Ractors, threads, or fibers. The `scope:` option selects which callers use the same contents.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Local scopes](#local-scopes)
12
+ - [Table of Contents](#table-of-contents)
13
+ - [Introduction: What Ruby gives you](#introduction-what-ruby-gives-you)
14
+ - [One handle, separate contents](#one-handle-separate-contents)
15
+ - [Choosing a scope](#choosing-a-scope)
16
+ - [`:ractor`: share within a Ractor](#ractor-share-within-a-ractor)
17
+ - [`:thread_group`: share within a group of threads](#thread_group-share-within-a-group-of-threads)
18
+ - [`:thread`: keep one set of contents per thread](#thread-keep-one-set-of-contents-per-thread)
19
+ - [`:fiber`: give every fiber its own contents](#fiber-give-every-fiber-its-own-contents)
20
+ - [`:fiber_storage`: let child fibers share a context](#fiber_storage-let-child-fibers-share-a-context)
21
+ - [Non-blocking fibers and schedulers](#non-blocking-fibers-and-schedulers)
22
+ - [Classes that support scopes](#classes-that-support-scopes)
23
+ - [Count within each scope](#count-within-each-scope)
24
+ - [Keep a cache in each Ractor](#keep-a-cache-in-each-ractor)
25
+ - [Create mutable values with a factory](#create-mutable-values-with-a-factory)
26
+ - [Delegate through a lazy reference](#delegate-through-a-lazy-reference)
27
+ - [Keep queues and their lifecycle local](#keep-queues-and-their-lifecycle-local)
28
+ - [Build ordered collections in each scope](#build-ordered-collections-in-each-scope)
29
+ - [Borrow resources from a local lease](#borrow-resources-from-a-local-lease)
30
+ - [Group resources or limit their number](#group-resources-or-limit-their-number)
31
+ - [Using scopes in an application](#using-scopes-in-an-application)
32
+ - [Restore context when reusing a fiber](#restore-context-when-reusing-a-fiber)
33
+ - [Pass local handles to scheduled tasks](#pass-local-handles-to-scheduled-tasks)
34
+ - [Coordinate changes within a shared scope](#coordinate-changes-within-a-shared-scope)
35
+ - [Under the hood](#under-the-hood)
36
+ - [Resolve the current backing object on every operation](#resolve-the-current-backing-object-on-every-operation)
37
+ - [Local handles can be garbage collected](#local-handles-can-be-garbage-collected)
38
+ - [Initial configuration is reused in new scopes](#initial-configuration-is-reused-in-new-scopes)
39
+ - [Separate containers can contain the same objects](#separate-containers-can-contain-the-same-objects)
40
+ - [Fiber storage inherits a reference to Farce storage](#fiber-storage-inherits-a-reference-to-farce-storage)
41
+ - [Scopes and transfer modes solve different problems](#scopes-and-transfer-modes-solve-different-problems)
42
+
43
+ ## Introduction: What Ruby gives you
44
+
45
+ Ruby has several places to store execution-local state. `Thread.current.thread_variable_set` stores a value for an entire thread. Despite its name, `Thread.current[:key]` stores a value for the current fiber.
46
+
47
+ ```ruby
48
+ Thread.current.thread_variable_set(:scopes_example, :thread_value)
49
+ Thread.current[:scopes_example] = :fiber_value
50
+
51
+ values = Fiber.new do
52
+ [Thread.current.thread_variable_get(:scopes_example),
53
+ Thread.current[:scopes_example]]
54
+ end.resume
55
+
56
+ values # => [:thread_value, nil]
57
+ Thread.current.thread_variable_set(:scopes_example, nil)
58
+ Thread.current[:scopes_example] = nil
59
+ ```
60
+
61
+ `Fiber[:key]` is another API. It supports storage inheritance when new fibers are created. Farce gives these choices a common interface through `scope:`, and adds Ractor and thread-group scopes. You can change the scope without rewriting each operation on your map, queue, or lazy value.
62
+
63
+ ## One handle, separate contents
64
+
65
+ A `Farce::Local` object is a shareable handle. Each operation finds the contents associated with the current scope. The handle is shareable while its contents remain mutable. Freezing a Local data container prevents explicit changes in every scope, including scopes first accessed later. Each scope keeps its own values. Local services such as queues and leases, and `Local::Lazy`, reject freezing with `TypeError`. Unlike a named slot in Ruby's built-in local storage, the local instance can also be garbage collected while the surrounding Ractor, thread, or fiber is still alive.
66
+
67
+ ```ruby
68
+ require "farce"
69
+
70
+ context = Farce::Local::Map.new(scope: :fiber)
71
+ context[:request_id] = :parent
72
+
73
+ child_value = Fiber.new do
74
+ context[:request_id] = :child
75
+ context[:request_id]
76
+ end.resume
77
+
78
+ child_value # => :child
79
+ context[:request_id] # => :parent
80
+ context.scope # => :fiber
81
+ Farce::Ractor.shareable?(context) # => true
82
+ context.frozen? # => false
83
+ ```
84
+
85
+ Call `freeze` only after coordinating with writers. It does not wait for operations already in progress. Freezing is shallow, so objects stored in a container remain independently mutable. Weak containers still allow garbage collection of their contents.
86
+
87
+ Load `farce` before running the remaining examples. Each code block is independent. Examples use `Farce::Ractor` so they can also use Farce's compatibility layer where native Ractors are unavailable.
88
+
89
+ ## Choosing a scope
90
+
91
+ Choose a scope based on who should see the same state. All these scopes are selected at construction. The default is `:ractor`. There is no `:global` scope for local containers (use the non-local variants of the storage classes instead).
92
+
93
+ | Scope | Who uses the same contents? | A typical use |
94
+ | --- | --- | --- |
95
+ | `:ractor` | Threads and fibers in the same Farce Ractor. | A cache for each worker Ractor. |
96
+ | `:thread_group` | Threads in the same `ThreadGroup` within a Ractor, including their fibers. | State for a group of related worker threads. |
97
+ | `:thread` | All fibers in one thread. | A reusable object for each worker thread. |
98
+ | `:fiber` | Only the current fiber. | Independent request state. |
99
+ | `:fiber_storage` | Execution contexts that inherit the same Farce storage entry through Ruby fiber storage. | Context shared with child fibers. |
100
+
101
+ ### `:ractor`: share within a Ractor
102
+
103
+ Use the default scope when each Ractor should have its own state. Threads inside one Ractor still use the same contents. Sending the handle to another Ractor gives that Ractor access to its own contents.
104
+
105
+ ```ruby
106
+ state = Farce::Local::Map.new
107
+ state[:worker] = :main
108
+ Thread.new { state[:completed] = 3 }.join
109
+ state[:completed] # => 3
110
+
111
+ results = Farce::Port.new
112
+ worker = Farce::Ractor.new(state, results) do |local, outbox|
113
+ before = local.empty?
114
+ local[:worker] = :background
115
+ outbox.send([before, local[:worker]])
116
+ end
117
+
118
+ results.receive # => [true, :background]
119
+ state[:worker] # => :main
120
+ worker.join
121
+ results.close
122
+ ```
123
+
124
+ The background Ractor starts with an empty map because this map had no initial entries. It does not inherit writes made through the main Ractor's handle. On platforms using Farce's thread-based Ractor shim, `:ractor` still selects storage by the current Farce Ractor.
125
+
126
+ ### `:thread_group`: share within a group of threads
127
+
128
+ Use `:thread_group` when a group of threads should share state separately from other groups. Ruby's [`ThreadGroup`](https://docs.ruby-lang.org/en/4.0/ThreadGroup.html) lets you assign threads to groups. New threads inherit their creator's group.
129
+
130
+ ```ruby
131
+ state = Farce::Local::Map.new(scope: :thread_group)
132
+ state[:service] = :web
133
+ background = ThreadGroup.new
134
+
135
+ result = Thread.new do
136
+ inherited = state[:service]
137
+ background.add(Thread.current)
138
+ starts_empty = state.empty?
139
+ state[:service] = :indexer
140
+
141
+ child_value = Thread.new { state[:service] }.value
142
+ [inherited, starts_empty, child_value]
143
+ end.value
144
+
145
+ result # => [:web, true, :indexer]
146
+ state[:service] # => :web
147
+ ```
148
+
149
+ Moving a thread to another group changes which contents subsequent operations select. Put workers into their intended group before they start using group-local resources. Membership in a group does not make independent edits to a returned mutable object atomic.
150
+
151
+ ### `:thread`: keep one set of contents per thread
152
+
153
+ Use `:thread` when fibers on the same thread should reuse state. A new thread starts with its own contents, even if it belongs to the same thread group.
154
+
155
+ ```ruby
156
+ state = Farce::Local::Map.new(scope: :thread)
157
+ state[:worker] = :main
158
+
159
+ Fiber.new { state[:worker] }.resume # => :main
160
+
161
+ other = Thread.new do
162
+ before = state.empty?
163
+ state[:worker] = :background
164
+ [before, Fiber.new { state[:worker] }.resume]
165
+ end.value
166
+
167
+ other # => [true, :background]
168
+ state[:worker] # => :main
169
+ ```
170
+
171
+ Thread-local state lasts across multiple jobs handled by the same thread. This suits caches and reusable helpers. For request-specific state, use a narrower scope or explicitly restore the previous value when a request finishes.
172
+
173
+ ### `:fiber`: give every fiber its own contents
174
+
175
+ Use `:fiber` when each fiber should start independently. Child fibers do not inherit the parent's local contents. Suspending and resuming a fiber preserves that fiber's contents.
176
+
177
+ ```ruby
178
+ request = Farce::Local::Atom.new(nil, scope: :fiber)
179
+ request.value = :outer
180
+
181
+ work = Fiber.new do
182
+ before = request.value
183
+ request.value = :upload
184
+ Fiber.yield(before)
185
+ request.value
186
+ end
187
+
188
+ work.resume # => nil
189
+ request.value # => :outer
190
+ work.resume # => :upload
191
+ request.value # => :outer
192
+ ```
193
+
194
+ This is useful when an application runs one request per fiber. It also means that a helper which starts a new fiber must receive any needed request data explicitly.
195
+
196
+ ### `:fiber_storage`: let child fibers share a context
197
+
198
+ Use `:fiber_storage` when related fibers should see the same local context. Ruby's [`Fiber.new`](https://docs.ruby-lang.org/en/4.0/Fiber.html#method-c-new) inherits a copy of the creator's fiber-storage hash by default. That copy can hold a reference to the same Farce storage object. A child can therefore update the same local map.
199
+
200
+ ```ruby
201
+ context = Farce::Local::Map.new(scope: :fiber_storage)
202
+ context[:request_id] = :upload
203
+
204
+ child_result = Fiber.new do
205
+ context[:stage] = :validated
206
+ context[:request_id]
207
+ end.resume
208
+
209
+ child_result # => :upload
210
+ context[:stage] # => :validated
211
+
212
+ # Start an independent context explicitly.
213
+ isolated = Fiber.new(storage: {}) do
214
+ before = context.empty?
215
+ context[:request_id] = :download
216
+ [before, context[:request_id]]
217
+ end.resume
218
+
219
+ isolated # => [true, :download]
220
+ context[:request_id] # => :upload
221
+ ```
222
+
223
+ Set up the context before creating children that should inherit it. Inheritance happens at fiber creation, not when the child is resumed. Use `storage: {}` at a new request boundary when it should start without inherited fiber storage.
224
+
225
+ #### Non-blocking fibers and schedulers
226
+
227
+ `blocking: false` alone does not turn off Ruby's default storage inheritance. A scheduler controls where and how task fibers are created. Do not assume a scheduled task inherits the submitting fiber's context, especially when it runs in another thread or Ractor.
228
+
229
+ ```ruby
230
+ context = Farce::Local::Map.new(scope: :fiber_storage)
231
+ context[:request_id] = :upload
232
+
233
+ Fiber.new(blocking: false) { context[:request_id] }.resume # => :upload
234
+ Fiber.new(blocking: false, storage: {}) { context.empty? }.resume # => true
235
+ ```
236
+
237
+ Pass the needed data as task arguments when crossing an execution boundary. Use `:fiber` when each task fiber should get its own state regardless of inherited storage.
238
+
239
+ ## Classes that support scopes
240
+
241
+ These classes accept the same five scopes. The scope determines which backing container or resource an operation uses. Each class retains its own behavior, such as queue ordering or weak references.
242
+
243
+ | Class | What each scope gets |
244
+ | --- | --- |
245
+ | `Farce::Local::Flag` | An atomic boolean with its own current value. |
246
+ | `Farce::Local::Counter` | An atomic integer counter with its own current value. |
247
+ | `Farce::Local::Atom` | An atomic reference with its own current value. |
248
+ | `Farce::Local::Molecule` | A record with independent atomic fields in each scope. |
249
+ | `Farce::Local::WeakAtom` | A reference that does not keep its value alive. |
250
+ | `Farce::Local::Map` | A mutable map. |
251
+ | `Farce::Local::TreeMap` | A map sorted by key. |
252
+ | `Farce::Local::LRUMap` | A bounded map with independent recency and capacity in each scope. |
253
+ | `Farce::Local::LFUMap` | A bounded map with independent frequency history and capacity in each scope. |
254
+ | `Farce::Local::WeakMap` | A map with weak keys and weak values. |
255
+ | `Farce::Local::WeakKeyMap` | A map with weak keys. |
256
+ | `Farce::Local::WeakValueMap` | A map with weak values. |
257
+ | `Farce::Local::Set` | A mutable set. |
258
+ | `Farce::Local::SortedSet` | A set maintained in ascending comparator order. |
259
+ | `Farce::Local::WeakSet` | A set that retains its elements weakly. |
260
+ | `Farce::Local::Vector` | An indexed collection. |
261
+ | `Farce::Local::Queue` | A FIFO queue with independent capacity and lifecycle. |
262
+ | `Farce::Local::PriorityQueue` | A queue ordered by priority. |
263
+ | `Farce::Local::TimerQueue` | A queue whose values become available at scheduled times. |
264
+ | `Farce::Local::Lazy` | A value computed on first access in that scope. |
265
+ | `Farce::Local::LazyRef` | A reference that delegates to a scoped lazy value. |
266
+ | `Farce::Local::Lease` | An independently initialized resource to borrow. |
267
+ | `Farce::Local::LeaseMap` | An independently initialized set of named resources. |
268
+ | `Farce::Local::LeasePool` | A pool with its own resources and capacity. |
269
+
270
+ ### Count within each scope
271
+
272
+ `Local::Counter` provides the numeric interface and atomic operations of `Farce::Counter`. Each scope starts at the configured initial integer. Reads and `reset` affect only the current scope. Values are not summed across scopes.
273
+
274
+ ```ruby
275
+ completed = Farce::Local::Counter.new(10, scope: :thread)
276
+ completed.increment(3)
277
+
278
+ child = Thread.new do
279
+ before = completed.value
280
+ completed.increment
281
+ completed.reset
282
+ [before, completed.value]
283
+ end.value
284
+
285
+ child # => [10, 10]
286
+ completed.value # => 13
287
+ completed + 2 # => 15
288
+ ```
289
+
290
+ Updates remain atomic when multiple threads share a Ractor or thread-group scope. Conditional operations such as `increment_if_below` apply their bounds to that scope's counter.
291
+
292
+ ### Keep a cache in each Ractor
293
+
294
+ A local map can hold mutable keys and values without preparing them for transfer. `store_if_absent` is useful for computing an entry once in the current map. Other Ractors maintain their own caches through the same handle.
295
+
296
+ ```ruby
297
+ cache = Farce::Local::Map.new
298
+ key = String.new("ruby concurrency")
299
+
300
+ tokens = cache.store_if_absent(key) { key.split }
301
+ again = cache.store_if_absent(key) { raise "already cached" }
302
+
303
+ again.equal?(tokens) # => true
304
+ Farce::Ractor.shareable?(cache) # => true
305
+ Farce::Ractor.shareable?(tokens) # => false
306
+ ```
307
+
308
+ Choose a weak map variant when entries should disappear as keys or values become unreachable. Scope selection does not change weak-reference behavior. Keep a strong reference elsewhere for as long as you need a weakly held object.
309
+
310
+ ### Create mutable values with a factory
311
+
312
+ Use `Local::Lazy` to build a fresh object for each scope that needs it. Its factory runs on the first `value` call in that scope. Later calls return the cached result, including `nil` or `false` results.
313
+
314
+ ```ruby
315
+ buffers = Farce::Local::Lazy.new(String, scope: :thread)
316
+ buffers.value << "main output"
317
+
318
+ child_output = Thread.new do
319
+ buffer = buffers.value
320
+ buffer << "worker output"
321
+ buffer.dup
322
+ end.value
323
+
324
+ child_output # => "worker output"
325
+ buffers.value # => "main output"
326
+ ```
327
+
328
+ You can supply a class, a shareable callable, or a block that can be made Ractor-shareable. The factory may create mutable objects, but it cannot capture arbitrary mutable state from another Ractor. A block can capture shareable configuration or use the explicit `self:` option.
329
+
330
+ ```ruby
331
+ settings = Farce::Ractor.make_shareable({ limit: 100 })
332
+ state = Farce::Local::Lazy.new(scope: :fiber, self: settings) do
333
+ { limit: self[:limit], pending: [] }
334
+ end
335
+
336
+ state.value[:pending] << :parent
337
+ child = Fiber.new { state.value }.resume
338
+
339
+ child # => { limit: 100, pending: [] }
340
+ state.value[:pending] # => [:parent]
341
+ ```
342
+
343
+ ### Delegate through a lazy reference
344
+
345
+ `Local::LazyRef` lets calling code use the result's interface directly. It selects the current scope's lazy value before forwarding the operation. This is handy for a library-level cache whose callers should not need to call `value` themselves.
346
+
347
+ ```ruby
348
+ cache = Farce::Local::LazyRef.new(Hash, scope: :fiber)
349
+ cache[:page] = :parent
350
+
351
+ child = Fiber.new { cache[:page] = :child }.resume
352
+ child # => :child
353
+ cache[:page] # => :parent
354
+ ```
355
+
356
+ ### Keep queues and their lifecycle local
357
+
358
+ Use `Local::Queue` when producers and consumers should communicate inside the selected scope. With the default `:ractor` scope, different threads in the same Ractor can exchange the original mutable object.
359
+
360
+ ```ruby
361
+ queue = Farce::Local::Queue.new
362
+ job = { ids: [1, 2] }
363
+ consumer = Thread.new { queue.pop }
364
+
365
+ queue.push(job)
366
+
367
+ consumer.value.equal?(job) # => true
368
+ queue.close
369
+ ```
370
+
371
+ Capacity, queued values, and closing belong to the backing queue in that scope. A different scope has a different queue. A thread-local queue therefore cannot deliver work from one thread to another.
372
+
373
+ ```ruby
374
+ queue = Farce::Local::Queue.new(scope: :thread, capacity: 1)
375
+ queue.push(:main_job)
376
+ queue.seal
377
+
378
+ other_state = Thread.new do
379
+ [queue.empty?, queue.closed?, queue.capacity]
380
+ end.value
381
+
382
+ other_state # => [true, false, 1]
383
+ queue.pop # => :main_job
384
+ queue.closed? # => true
385
+ ```
386
+
387
+ ### Build ordered collections in each scope
388
+
389
+ Use local vectors and tree maps for per-scope collections. Local priority and timer queues apply their ordering within each scope too. Here each fiber gets an independent list of processing stages.
390
+
391
+ ```ruby
392
+ stages = Farce::Local::Vector.new([:received], scope: :fiber)
393
+ stages.push(:validated)
394
+
395
+ child_stages = Fiber.new do
396
+ stages.push(:decoded)
397
+ [stages[0], stages[1]]
398
+ end.resume
399
+
400
+ child_stages # => [:received, :decoded]
401
+ [stages[0], stages[1]] # => [:received, :validated]
402
+ ```
403
+
404
+ ### Borrow resources from a local lease
405
+
406
+ Use `Local::Lease` when callers in the same scope must take turns using a resource. Its initializer creates a resource on first use in each scope. The block form of `checkout` returns the resource to the lease when the block finishes, including when it raises.
407
+
408
+ ```ruby
409
+ scratch = Farce::Local::Lease.new(scope: :thread) { String.new }
410
+
411
+ first = scratch.checkout do |buffer|
412
+ buffer.replace("report: ")
413
+ buffer << "ready"
414
+ buffer.dup
415
+ end
416
+
417
+ second = scratch.checkout { |buffer| buffer.dup }
418
+ other = Thread.new { scratch.checkout { |buffer| buffer.empty? } }.value
419
+
420
+ first # => "report: ready"
421
+ second # => "report: ready"
422
+ other # => true
423
+ ```
424
+
425
+ A lease reuses its resource within the scope. Clear or reset reusable buffers as part of your work when previous contents should not carry over. Keep checkout and use together in the block instead of letting borrowed resources escape into another scope.
426
+
427
+ ### Group resources or limit their number
428
+
429
+ `Local::LeaseMap` builds a mapping of named resources per scope. `Local::LeasePool` creates resources as needed up to its limit. That limit applies separately in each scope, so `max_size: 2` with thread scope permits two resources per thread.
430
+
431
+ ```ruby
432
+ resources = Farce::Local::LeaseMap.new(scope: :fiber) do
433
+ { input: String.new, output: String.new }
434
+ end
435
+ resources.checkout(:output) { |buffer| buffer << "parent" }
436
+
437
+ Fiber.new { resources.checkout(:output) { |buffer| buffer.empty? } }.resume # => true
438
+
439
+ pool = Farce::Local::LeasePool.new(scope: :thread, max_size: 2) { [] }
440
+ pool.checkout { |items| items << :used }
441
+ Thread.new { pool.checkout { |items| items.empty? } }.value # => true
442
+ ```
443
+
444
+ ## Using scopes in an application
445
+
446
+ ### Restore context when reusing a fiber
447
+
448
+ A scope is tied to an execution context, not to the lifetime of a Ruby method call. A thread or fiber that handles several requests keeps its local state between them. Use `ensure` to restore temporary context when nesting operations or reusing a worker.
449
+
450
+ ```ruby
451
+ class RequestContext
452
+ def initialize
453
+ @current = Farce::Local::Atom.new(nil, scope: :fiber)
454
+ end
455
+
456
+ def current = @current.value
457
+
458
+ def with(request_id)
459
+ previous = @current.swap(request_id)
460
+ begin
461
+ yield
462
+ ensure
463
+ @current.value = previous
464
+ end
465
+ end
466
+ end
467
+
468
+ context = RequestContext.new
469
+
470
+ context.with(:outer) do
471
+ context.with(:inner) { context.current } # => :inner
472
+ context.current # => :outer
473
+ end
474
+
475
+ context.current # => nil
476
+ ```
477
+
478
+ The wrapper above is an ordinary Ruby object used within one Ractor. Its local atom supplies fiber selection. A local field alone does not make an enclosing application object Ractor-shareable.
479
+
480
+ ### Pass local handles to scheduled tasks
481
+
482
+ A local handle is shareable, so it can be passed as a task argument. The task sees the contents selected by its own execution scope. Passing the handle does not send the caller's current local values along with it.
483
+
484
+ ```ruby
485
+ context = Farce::Local::Map.new(scope: :fiber)
486
+ context[:request_id] = :caller
487
+ results = Farce::Port.new
488
+ scheduler = Farce::Scheduler.new
489
+ worker = scheduler.launch_thread
490
+
491
+ [:upload, :download].each do |request_id|
492
+ scheduler.schedule(context, results, request_id) do |local, outbox, id|
493
+ before = local.empty?
494
+ local[:request_id] = id
495
+ outbox.send([id, before])
496
+ end
497
+ end
498
+
499
+ received = [results.receive, results.receive].sort
500
+ received # => [[:download, true], [:upload, true]]
501
+ context[:request_id] # => :caller
502
+ scheduler.close
503
+ worker.join
504
+ results.close
505
+ ```
506
+
507
+ Use task arguments for incoming request data, then set up local context inside the task. If child fibers should participate in that same context, consider `:fiber_storage` and establish an explicit inheritance boundary.
508
+
509
+ ### Coordinate changes within a shared scope
510
+
511
+ Ractor and thread-group scopes can be used by several threads at once. Use the container's coordinated operations for compound changes. Retrieving a mutable value does not make later edits to that object synchronized.
512
+
513
+ ```ruby
514
+ completed = Farce::Local::Atom.new(0, scope: :ractor)
515
+ threads = 4.times.map do
516
+ Thread.new do
517
+ 100.times { completed.update { |count| count + 1 } }
518
+ end
519
+ end
520
+ threads.each(&:join)
521
+
522
+ completed.value # => 400
523
+ ```
524
+
525
+ Likewise, use `Map#update` for a coordinated replacement or a lease for exclusive resource use. Choose the scope first, then the operation that provides the coordination callers in that scope need.
526
+
527
+ ## Under the hood
528
+
529
+ ### Resolve the current backing object on every operation
530
+
531
+ Most local classes share the `Farce::Local::Scoped` implementation. The shareable handle stores its scope and construction settings. A private storage table maps that handle to a backing object for the current scope. A map operation goes to a map, a queue operation to a queue, and a lazy read to a cached local value.
532
+
533
+ | Selected scope | How Farce finds the storage |
534
+ | --- | --- |
535
+ | `:ractor` | The current Farce Ractor. |
536
+ | `:thread_group` | The current thread's group within the current Ractor. |
537
+ | `:thread` | The current logical thread. |
538
+ | `:fiber` | The current fiber. |
539
+ | `:fiber_storage` | A Farce storage entry in `Fiber[...]`. |
540
+
541
+ There is one backing object per handle in each selected storage table. Two different local maps remain independent even when they have the same scope. These tables are internal details. Application code should use the local container's public methods.
542
+
543
+ ```ruby
544
+ left = Farce::Local::Map.new(scope: :thread)
545
+ right = Farce::Local::Map.new(scope: :thread)
546
+ left[:key] = :value
547
+
548
+ left[:key] # => :value
549
+ right[:key] # => nil
550
+ ```
551
+
552
+ ### Local handles can be garbage collected
553
+
554
+ Farce keeps local handles as weak keys in its scope storage. The storage does not keep a handle alive just because it has been used in that scope. Once nothing else references the local instance, it can be garbage collected. Its backing containers and their contents can then be released too, if nothing else retains them. This works even for scopes attached to long-lived workers.
555
+
556
+ ```ruby
557
+ cache = Farce::Local::Map.new(scope: :thread)
558
+ cache[:buffer] = String.new("temporary output")
559
+ cache = nil
560
+
561
+ # The local handle is now eligible for collection.
562
+ # No per-thread storage slot needs to be cleared by name.
563
+
564
+ # A built-in storage entry retains its value independently of this variable.
565
+ buffer = String.new("retained output")
566
+ Thread.current.thread_variable_set(:scopes_example_buffer, buffer)
567
+ buffer = nil
568
+ Thread.current.thread_variable_get(:scopes_example_buffer) # => "retained output"
569
+ Thread.current.thread_variable_set(:scopes_example_buffer, nil)
570
+ ```
571
+
572
+ Ruby's built-in Ractor, Thread, and Fiber storage holds named entries. Dropping an application reference does not remove those entries or release their values. Clear or replace the entry, or let its owning execution context become collectible. For example, `Ractor[:key] = nil`, `Thread.current[:key] = nil`, and `Fiber[:key] = nil` release those entries' references. See Ruby's [Ractor storage API](https://docs.ruby-lang.org/en/4.0/Ractor.html#method-c-5B-5D-3D) and the Thread and Fiber APIs linked above.
573
+
574
+ Keep a local handle in a constant or a live application object when you want its state to persist. The weak storage does not discard a handle that your application still retains. As with other Ruby objects, references from closures or stored values can also keep it alive.
575
+
576
+ ### Initial configuration is reused in new scopes
577
+
578
+ New scopes use the constructor's initial contents and options. They do not clone the latest state of another scope's backing container. Many local containers create their initial backing object during construction and create others on demand. Lazy values and lease resources still wait until needed to run their factories.
579
+
580
+ ```ruby
581
+ status = Farce::Local::Atom.new(:idle, scope: :fiber)
582
+ status.value = :busy
583
+
584
+ Fiber.new { status.value }.resume # => :idle
585
+ status.value # => :busy
586
+ ```
587
+
588
+ Farce uses a mode manager to retain shareable construction settings. Non-shareable settings are held in a copy envelope on native Ractors, giving each Ractor a copy when it needs them. This happens for construction data, not for every value later written to a local container. See [transfer modes](modes.md#under-the-hood) for how envelopes work.
589
+
590
+ ### Separate containers can contain the same objects
591
+
592
+ Within one Ractor, separate scopes may reuse objects from the same decoded construction settings. A fresh backing map therefore does not guarantee a deep copy of all initial values. The two child fibers below have different maps, but their initial array is the same object.
593
+
594
+ ```ruby
595
+ state = Farce::Local::Map.new({ items: [] }, scope: :fiber)
596
+
597
+ first = Fiber.new do
598
+ state[:only_first] = true
599
+ state[:items] << :first
600
+ state[:items]
601
+ end.resume
602
+
603
+ second = Fiber.new do
604
+ [state.key?(:only_first), state[:items]]
605
+ end.resume
606
+
607
+ second[0] # => false
608
+ second[1] # => [:first]
609
+ first.equal?(second[1]) # => true
610
+ ```
611
+
612
+ Use a factory when every scope needs fresh mutable values. `Local::Lazy.new(scope: :fiber) { { items: [] } }` builds a new nested array per fiber. Alternatively, start a local map empty and create values through `store_if_absent` in the scope that needs them. On platforms without native Ractors, construction data is not copied across Farce Ractors either, so factories are useful there too.
613
+
614
+ ### Fiber storage inherits a reference to Farce storage
615
+
616
+ Ruby copies the fiber-storage hash when creating a child with default storage inheritance. It does not deep-copy the objects stored in that hash. Farce keeps a storage object in one entry, so the inherited reference can lead parent and child to the same backing containers.
617
+
618
+ ```ruby
619
+ context = Farce::Local::Map.new(scope: :fiber_storage)
620
+ context[:phase] = :created
621
+
622
+ child = Fiber.new { context[:phase] }
623
+ context[:phase] = :ready
624
+ child.resume # => :ready
625
+ ```
626
+
627
+ This is shared context, not a snapshot of the map at fiber creation. A fresh fiber-storage hash selects fresh Farce storage. The choice affects all Farce objects using `:fiber_storage` in that context. Ruby can also inherit fiber storage into new threads, so this scope should not be treated as a strict thread boundary. Use `:thread` when thread identity is the boundary you need.
628
+
629
+ ### Scopes and transfer modes solve different problems
630
+
631
+ A scope selects which contents a caller accesses. A [transfer mode](modes.md) selects how a non-shareable value is carried or stored. `Farce::Queue.new(mode: :local)` has one queue whose local payloads belong to their originating Ractor. `Farce::Local::Queue.new` has a separate queue for each scope.
632
+
633
+ ```ruby
634
+ queue = Farce::Local::Queue.new
635
+ queue.push(:main_job)
636
+ results = Farce::Port.new
637
+
638
+ worker = Farce::Ractor.new(queue, results) do |local, outbox|
639
+ outbox.send(local.empty?)
640
+ end
641
+
642
+ results.receive # => true
643
+ queue.pop # => :main_job
644
+ worker.join
645
+ results.close
646
+ queue.close
647
+ ```
648
+
649
+ Use a local container for independent state behind a shared handle. Use a regular Farce container when callers in different Ractors need to communicate through the same contents. Choosing a scope does not move data from one scope to another.