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
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ # Load worker dependencies before a scheduler is used from another Ractor.
6
+ # Some concurrent autoload can lead to stalls on Windows with Ruby 3.4.
7
+ require "farce/clock"
8
+ require "farce/internal/thread_pool"
9
+ require "farce/resolv"
10
+ require "farce/resolv/dns"
11
+
12
+ module Farce
13
+ module Internal
14
+ # Task admission, timers and shutdown shared by the scheduler implementations.
15
+ module SchedulerLifecycle
16
+ include Farce::Unshareable
17
+
18
+ # Prepending bypasses Unshareable's include hook, so remove freeze here.
19
+ def self.prepended(scheduler)
20
+ scheduler.undef_method(:freeze)
21
+ end
22
+
23
+ class Cancelled < Exception # rubocop:disable Lint/InheritException
24
+ end
25
+
26
+ Timer = Struct.new(:deadline, :token, :fiber, :error, :active)
27
+ Completion = Struct.new(:token, :value, :error)
28
+ SLOT_COUNT = 64
29
+ private_constant :Timer, :Completion, :SLOT_COUNT
30
+
31
+ def initialize(backend: FROZEN_CONFIG.io_backend, thread_pool: ThreadPool.current)
32
+ super(backend: backend)
33
+ @owner = owner_thread
34
+ @root = Fiber.current
35
+ @fibers = {}.compare_by_identity
36
+ @timers = Internal::UnsharedPriorityQueue.new
37
+ @mailbox = Thread::Queue.new
38
+ @thread_pool = thread_pool
39
+ @closed = @closing = @running = @stopping = false
40
+ @suspensions = @immediate = 0
41
+ @dispatch_budget = dispatch_budget
42
+ rescue Exception # rubocop:disable Lint/RescueException
43
+ destroy
44
+ raise
45
+ end
46
+
47
+ def fiber(*, **, &work)
48
+ check_owner
49
+ raise IOError, "scheduler is closed" if @closed || @stopping
50
+ raise FiberError, "scheduler must be installed" unless Fiber.scheduler.equal?(self)
51
+ raise ArgumentError, "no block given" unless work
52
+ admitting = true
53
+ failure = nil
54
+ task = Fiber.new(blocking: false, storage: nil) do
55
+ work.call(*, **)
56
+ rescue Exception => e # rubocop:disable Lint/RescueException
57
+ raise unless admitting
58
+ failure = e
59
+ ensure
60
+ @fibers.delete(Fiber.current)
61
+ finish_fiber
62
+ end
63
+ @fibers[task] = true
64
+ start_fiber(task)
65
+ admitting = false
66
+ raise failure if failure
67
+ task
68
+ rescue Exception # rubocop:disable Lint/RescueException
69
+ shutdown if task && !@running && Fiber.current.equal?(@root)
70
+ raise
71
+ ensure
72
+ admitting = false
73
+ end
74
+
75
+ def block(_blocker, timeout = nil)
76
+ timeout = duration_value(timeout)
77
+ token = arm_wait
78
+ wait_with_timeout(token, timeout, false)
79
+ ensure
80
+ retire_wait(token) if token
81
+ end
82
+
83
+ def unblock(_blocker, fiber) # rubocop:disable Naming/PredicateMethod
84
+ return false if @closed || !(token = current_wait(fiber))
85
+ @mailbox << Completion.new(token, true, nil).freeze
86
+ wakeup
87
+ true
88
+ end
89
+
90
+ def fiber_interrupt(fiber, exception) # rubocop:disable Naming/PredicateMethod
91
+ return false if @closed || !(token = current_wait(fiber))
92
+ @mailbox << Completion.new(token, nil, exception).freeze
93
+ wakeup
94
+ true
95
+ end
96
+
97
+ def kernel_sleep(duration = nil)
98
+ started = Clock.now
99
+ block(nil, duration)
100
+ Clock.now - started
101
+ end
102
+
103
+ def timeout_after(duration, klass = Timeout::Error, *)
104
+ check_fiber
105
+ duration = duration_value(duration)
106
+ return yield(duration) unless duration
107
+ timer = Timer.new(Clock.now + duration, nil, Fiber.current, klass.exception(*), true)
108
+ @timers.push(timer.deadline, timer)
109
+ policy_changed
110
+ yield duration
111
+ ensure
112
+ remove_timer(timer) if timer
113
+ end
114
+
115
+ def yield
116
+ token = arm_wait
117
+ resume_wait(token, nil)
118
+ suspend(token)
119
+ end
120
+ alias cooperate yield
121
+
122
+ def address_resolve(hostname)
123
+ check_fiber
124
+ Resolv.getaddresses(hostname)
125
+ end
126
+
127
+ def process_wait(pid, flags)
128
+ background { Process::Status.wait(pid, flags) }
129
+ end
130
+
131
+ def blocking_operation_wait(operation)
132
+ # CRuby also invokes this hook from finalizers, where parking is illegal.
133
+ # Keep the opaque runtime operation on its original stack. Explicit file
134
+ # fallbacks use the thread pool. Process waits use separate threads.
135
+ Fiber.blocking { operation.call }
136
+ end
137
+
138
+ def run
139
+ check_root
140
+
141
+ raise IOError, "scheduler is closed" if @closed || @stopping
142
+ raise FiberError, "recursive scheduler run" if @running
143
+ entered = @running = true
144
+
145
+ Internal.prepare_thread if defined?(Internal.prepare_thread)
146
+
147
+ until idle?
148
+ service_policy
149
+ break if idle?
150
+ deadline = @timers.peek_priority
151
+ timeout = deadline ? deadline - Clock.now : nil
152
+ timeout = 0 if timeout&.negative? || ready? || !@mailbox.empty?
153
+ dispatch(timeout, @timers.empty? ? @dispatch_budget : 256)
154
+ end
155
+
156
+ true
157
+ rescue Exception # rubocop:disable Lint/RescueException
158
+ shutdown if entered
159
+ raise
160
+ ensure
161
+ if entered
162
+ @running = false
163
+ finish_close if @closing
164
+ end
165
+ end
166
+
167
+ def idle? = @fibers.empty? && !pending? && @timers.empty? && @mailbox.empty?
168
+ def closed? = @closed
169
+
170
+ # Return a lower bound for fibers that can resume immediately.
171
+ def farce_runnable_count = scheduler_ready_count + @mailbox.size
172
+
173
+ # Stop admission immediately. An active driver owns the drain and releases
174
+ # resources after returning from dispatch. Destroying them here would leave
175
+ # its native stack referring to freed state.
176
+ def close # rubocop:disable Naming/PredicateMethod
177
+ check_owner
178
+ return true if @closed
179
+ begin_close
180
+ @closing = true
181
+ return true if @running || !Fiber.current.equal?(@root)
182
+ begin
183
+ run unless @stopping
184
+ ensure
185
+ finish_close
186
+ end
187
+ true
188
+ end
189
+
190
+ # Ruby 3.4+ invokes this hook on scheduler replacement and thread exit.
191
+ def scheduler_close(error = $!) # rubocop:disable Style/SpecialGlobalVars
192
+ check_root
193
+ raise FiberError, "recursive scheduler close" if @running
194
+ return close unless error
195
+ # The root is already unwinding: run task ensures without waiting for
196
+ # ordinary completion, and preserve the exception that caused shutdown.
197
+ begin_close
198
+ @closing = true
199
+ finish_close
200
+ true
201
+ end
202
+
203
+ private
204
+
205
+ def finish_close
206
+ return if @closed
207
+ shutdown
208
+ destroy
209
+ @timers.clear
210
+ @mailbox.clear
211
+ @closed = true
212
+ @closing = false
213
+ end
214
+
215
+ def initialize_copy(*) = raise(TypeError, "fiber schedulers cannot be copied")
216
+ def marshal_dump = raise(TypeError, "fiber schedulers cannot be marshaled")
217
+
218
+ def check_owner
219
+ raise ThreadError, "scheduler belongs to another thread" unless owner_thread.equal?(@owner)
220
+ end
221
+
222
+ def check_root
223
+ check_owner
224
+ raise FiberError, "operation requires the root fiber" unless Fiber.current.equal?(@root)
225
+ end
226
+
227
+ def check_fiber
228
+ check_owner
229
+ raise IOError, "scheduler is closed" if @closed || @stopping
230
+ raise FiberError, "operation requires an admitted fiber" unless @fibers.key?(Fiber.current)
231
+ raise FiberError, "scheduler must be installed" unless @closing || Fiber.scheduler.equal?(self)
232
+ end
233
+
234
+ def duration_value(value)
235
+ return if value.nil?
236
+ seconds = Float(value)
237
+ raise ArgumentError, "invalid timeout" if seconds.nan? || seconds.negative?
238
+ seconds.finite? ? seconds : nil
239
+ end
240
+
241
+ def suspend(token)
242
+ @suspensions += 1
243
+ park_current(token)
244
+ end
245
+
246
+ def wait_with_timeout(token, timeout, value)
247
+ if timeout
248
+ timer = Timer.new(Clock.now + timeout, token, nil, nil, true)
249
+ @timers.push(timer.deadline, timer)
250
+ policy_changed
251
+ end
252
+ result = suspend(token)
253
+ result.nil? ? value : result
254
+ ensure
255
+ remove_timer(timer) if timer
256
+ end
257
+
258
+ def remove_timer(timer)
259
+ timer.active = false
260
+ @timers.delete_identity(timer.deadline, timer)
261
+ end
262
+
263
+ def with_io_timeout(duration, &)
264
+ timeout_after(duration, IO::TimeoutError, "IO timed out", &)
265
+ end
266
+
267
+ def checkpoint_result(value)
268
+ @immediate += 1
269
+ if @immediate >= SLOT_COUNT
270
+ @immediate = 0
271
+ self.yield
272
+ end
273
+ value
274
+ end
275
+
276
+ def service_policy
277
+ SLOT_COUNT.times do
278
+ break if @mailbox.empty?
279
+ item = @mailbox.pop(true)
280
+ break unless item
281
+ item.error ? interrupt_wait(item.token, item.error) : resume_wait(item.token, item.value)
282
+ end
283
+ return if @timers.empty?
284
+ SLOT_COUNT.times do
285
+ timer = @timers.pop_before(Clock.now)
286
+ break unless timer
287
+ next unless timer.active
288
+ timer.active = false
289
+ token = timer.token || current_wait(timer.fiber)
290
+ next unless token
291
+ timer.error ? interrupt_wait(token, timer.error) : resume_wait(token, nil)
292
+ end
293
+ end
294
+
295
+ def background(&)
296
+ check_fiber
297
+ @thread_pool.call(&)
298
+ end
299
+
300
+ def shutdown
301
+ begin_shutdown
302
+ @stopping = true
303
+ @fibers.keys.each do |task| # rubocop:disable Style/HashEachMethods
304
+ next unless task.alive?
305
+ begin
306
+ task.raise(Cancelled.new("scheduler aborted"))
307
+ rescue Exception # rubocop:disable Lint/RescueException
308
+ # Preserve the initiating failure, while running other tasks' ensures.
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal
7
+ # Ruby scheduler built around IO.select. Engine implementations can override its IO helpers.
8
+ module SelectScheduler
9
+ include SchedulerIO
10
+
11
+ Wait = Struct.new(:fiber, :status, :value, :error, :groups, :ios)
12
+ private_constant :Wait
13
+
14
+ attr_reader :backend, :fallback_reason
15
+
16
+ def initialize(backend: :auto)
17
+ raise ArgumentError, "unavailable select driver: #{backend}" unless %i[auto select].include?(backend)
18
+ @backend = :select
19
+ @wait_mutex = Thread::Mutex.new
20
+ @waits = {}
21
+ @waiting = {}.compare_by_identity
22
+ @ready = []
23
+ @serial = 0
24
+ @wake_reader, @wake_writer = IO.pipe
25
+ end
26
+
27
+ def io_wait(io, events, timeout = nil)
28
+ check_fiber
29
+ events = Integer(events)
30
+ raise ArgumentError, "invalid IO event mask" unless events.positive? && events.nobits?(~7)
31
+ readiness(io_select(events.nobits?(IO::READABLE) ? nil : [io],
32
+ events.nobits?(IO::WRITABLE) ? nil : [io], events.nobits?(IO::PRIORITY) ? nil : [io], timeout))
33
+ end
34
+
35
+ if SINGLE_TRANSFER
36
+ def io_read(io, buffer, offset, length)
37
+ transfer_io(false, io, buffer, length, offset, single_transfer: true)
38
+ end
39
+
40
+ def io_write(io, buffer, offset, length)
41
+ transfer_io(true, io, buffer, length, offset, single_transfer: true)
42
+ end
43
+ else
44
+ def io_read(io, buffer, minimum, offset = 0)
45
+ transfer_io(false, io, buffer, minimum, offset)
46
+ end
47
+
48
+ def io_write(io, buffer, minimum, offset = 0)
49
+ transfer_io(true, io, buffer, minimum, offset)
50
+ end
51
+ end
52
+
53
+ def io_close(descriptor) # rubocop:disable Naming/PredicateMethod
54
+ check_owner
55
+ cancel_descriptor(descriptor)
56
+ Fiber.blocking { IO.for_fd(descriptor).close }
57
+ true
58
+ end
59
+
60
+ private
61
+
62
+ def arm_wait
63
+ check_fiber
64
+ @wait_mutex.synchronize do
65
+ token = @serial += 1
66
+ @waits[token] = Wait.new(Fiber.current, :pending)
67
+ @waiting[Fiber.current] = token
68
+ token
69
+ end
70
+ end
71
+
72
+ def current_wait(fiber)
73
+ @wait_mutex.synchronize { @waiting[fiber] }
74
+ end
75
+
76
+ def register_select(token, groups, ios)
77
+ wait = @waits.fetch(token)
78
+ wait.groups, wait.ios = groups, ios
79
+ end
80
+
81
+ def park_current(token)
82
+ Fiber.yield
83
+ wait = @waits.fetch(token)
84
+ raise wait.error if wait.error
85
+ wait.value
86
+ ensure
87
+ retire_wait(token)
88
+ end
89
+
90
+ def resume_wait(token, value, error = nil)
91
+ @wait_mutex.synchronize do
92
+ wait = @waits[token]
93
+ return false unless wait && wait.status == :pending
94
+ wait.status, wait.value, wait.error = :ready, value, error
95
+ @ready << token
96
+ true
97
+ end
98
+ end
99
+
100
+ def interrupt_wait(token, error)
101
+ # An interrupt may replace an undispatched result, but never a later wait.
102
+ @wait_mutex.synchronize do
103
+ wait = @waits[token]
104
+ return false unless wait
105
+ @ready << token if wait.status == :pending
106
+ wait.status, wait.error = :ready, error
107
+ true
108
+ end
109
+ end
110
+
111
+ def retire_wait(token)
112
+ @wait_mutex.synchronize do
113
+ wait = @waits.delete(token)
114
+ @waiting.delete(wait.fiber) if wait && @waiting[wait.fiber] == token
115
+ end
116
+ end
117
+
118
+ def ready? = !@ready.empty?
119
+ def scheduler_ready_count = @ready.size
120
+ def pending? = !@waits.empty?
121
+ def pending_count = @wait_mutex.synchronize { @waits.size }
122
+
123
+ def write_wakeup = @wake_writer.write_nonblock(".", exception: false)
124
+ def read_wakeup = @wake_reader.read_nonblock(4096, exception: false)
125
+
126
+ def wakeup
127
+ write_wakeup
128
+ true
129
+ rescue IOError, SystemCallError
130
+ false
131
+ end
132
+
133
+ def dispatch(timeout, budget)
134
+ check_root
135
+ snapshot = @waits.to_a.select { |_, wait| wait.status == :pending && wait.groups }
136
+ groups = [[@wake_reader], [], []]
137
+ snapshot.each do |token, wait|
138
+ if wait.ios.flatten.any?(&:closed?)
139
+ interrupt_wait(token, IOError.new("stream closed while waiting"))
140
+ else
141
+ 3.times { |i| groups[i].concat(wait.ios[i]) }
142
+ end
143
+ end
144
+ # Linux select may keep sleeping after another thread closes an IO.
145
+ timeout = [timeout || 0.05, 0.05].min unless snapshot.empty?
146
+ begin
147
+ found = select_snapshot(groups.map(&:uniq), ready? ? 0 : timeout)
148
+ rescue IOError, Errno::EBADF, Errno::ENOTSOCK
149
+ closed = snapshot.select { |_, wait| wait.ios.flatten.any?(&:closed?) }
150
+ raise if closed.empty?
151
+ closed.each { |token, _| interrupt_wait(token, IOError.new("stream closed while waiting")) } # rubocop:disable Style/HashEachMethods -- snapshot is an Array
152
+ end
153
+ if found
154
+ if found[0].delete(@wake_reader)
155
+ loop { break unless read_wakeup.is_a?(String) }
156
+ end
157
+ sets = found.map { |list| list.each_with_object({}.compare_by_identity) { |io, set| set[io] = true } }
158
+ snapshot.each do |token, wait|
159
+ next unless @waits[token].equal?(wait) && wait.status == :pending
160
+ result = wait.groups.each_with_index.map do |list, i|
161
+ list.each_with_index.filter_map { |object, j| object if sets[i][wait.ios[i][j]] }
162
+ end
163
+ resume_wait(token, result) if result.any? { |list| !list.empty? }
164
+ end
165
+ end
166
+ count = 0
167
+ budget.times do
168
+ token = @ready.shift
169
+ break unless token
170
+ wait = @waits[token]
171
+ next unless wait && wait.status == :ready
172
+ wait.status = :delivered
173
+ wait.fiber.resume
174
+ count += 1
175
+ end
176
+ count
177
+ rescue Errno::EINTR
178
+ 0
179
+ end
180
+
181
+ def cancel_descriptor(descriptor)
182
+ @waits.each do |token, wait|
183
+ next unless wait.ios&.flatten&.any? { |io| !io.closed? && io.fileno == descriptor }
184
+ interrupt_wait(token, IOError.new("stream closed while waiting"))
185
+ end
186
+ end
187
+
188
+ def destroy
189
+ @wake_reader.close if @wake_reader && !@wake_reader.closed?
190
+ @wake_writer.close if @wake_writer && !@wake_writer.closed?
191
+ @waits&.clear
192
+ @waiting&.clear
193
+ @ready&.clear
194
+ end
195
+
196
+ def transfer_nonblock(writing, io, buffer, offset, capacity)
197
+ writing ? io.write_nonblock(buffer.get_string(offset, capacity), exception: false) :
198
+ io.read_nonblock(capacity, exception: false)
199
+ end
200
+
201
+ def transfer_io(writing, io, buffer, minimum, offset, single_transfer: false)
202
+ io, minimum, offset = transfer_arguments(io, buffer, minimum, offset, writing)
203
+ limit = single_transfer ? minimum : buffer.size - offset
204
+ minimum = limit.zero? ? 0 : 1 if single_transfer
205
+ serial = @suspensions
206
+ result = locked_buffer(buffer) do
207
+ operation = lambda do |*_ignored|
208
+ next file_transfer(writing, io, buffer, minimum, offset, limit) if io.stat.file?
209
+ total = 0
210
+ loop do
211
+ capacity = limit - total
212
+ break total if capacity.zero?
213
+ value = Fiber.blocking do
214
+ transfer_nonblock(writing, io, buffer, offset + total, capacity)
215
+ end
216
+ if value == :wait_readable || value == :wait_writable
217
+ io_wait(io, value == :wait_readable ? IO::READABLE : IO::WRITABLE)
218
+ next
219
+ end
220
+ break total if value.nil? || value == 0 # rubocop:disable Style/NumericPredicate -- reads return strings, writes integers
221
+ buffer.set_string(value, offset + total) unless writing
222
+ total += writing ? value : value.bytesize
223
+ break total if total >= minimum
224
+ end
225
+ rescue SystemCallError => e
226
+ total&.positive? ? total : -e.errno
227
+ end
228
+ io.respond_to?(:timeout) && io.timeout ? with_io_timeout(io.timeout, &operation) : operation.call
229
+ end
230
+ checkpoint_result(result) if serial == @suspensions
231
+ result
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # @!visibility private
7
+ module Internal # :nodoc: all
8
+ class Storage
9
+ # Protect shared map access, including reads on engines with unsafe weak maps.
10
+ # Per-key locks let initializers for unrelated keys run independently.
11
+ class ThreadSafe < Storage
12
+ def initialize
13
+ @strong_locks = {}
14
+ @weak_locks = ObjectSpace::WeakKeyMap.new
15
+ super
16
+ end
17
+
18
+ def [](key, positional_mode = nil, mode: :auto)
19
+ map = map_for(key, positional_mode || mode, create: false)
20
+ @mutex.synchronize { map[key] } if map
21
+ end
22
+
23
+ def []=(key, mode = :auto, value) # rubocop:disable Style/OptionalArguments
24
+ map = map_for(key, mode)
25
+ @mutex.synchronize { map[key] = value }
26
+ end
27
+
28
+ def key?(key, mode = :auto)
29
+ map = map_for(key, mode, create: false)
30
+ map ? @mutex.synchronize { map.key?(key) } : false
31
+ end
32
+
33
+ def store_if_absent(key, mode: :auto)
34
+ value = self[key, mode]
35
+ return value unless value.nil?
36
+
37
+ mutex_map = Internal.garbage_collectable?(key) ? @weak_locks : @strong_locks
38
+ mutex = @mutex.synchronize { mutex_map[key] ||= Mutex.new }
39
+
40
+ mutex.synchronize do
41
+ self[key, mode] = yield unless key?(key, mode)
42
+ self[key, mode]
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.[](key, scope: :ractor, **) = scope(scope)[key, **]
48
+
49
+ def self.[]=(key, scope = :ractor, mode = :auto, value) # rubocop:disable Style/OptionalArguments
50
+ scope(scope)[key, mode] = value
51
+ end
52
+
53
+ def self.dig(*, scope: :ractor, **) = scope(scope).dig(*, **)
54
+
55
+ def self.store_if_absent(key, scope: :ractor, **, &) = scope(scope).store_if_absent(key, **, &)
56
+
57
+ def self.scope(scope, raise_exception: true)
58
+ case scope
59
+ when :global
60
+ raise ArgumentError, "global scope is not supported" if raise_exception
61
+ when Symbol
62
+ return __send__(scope) if Farce::SCOPES.include?(scope)
63
+ return unless raise_exception
64
+ raise ArgumentError, "Invalid scope: #{scope.inspect}"
65
+ when Class
66
+ storage = scope(scope.name.downcase.to_sym, raise_exception: false)
67
+ return storage if storage
68
+ raise ArgumentError, "Invalid scope: #{scope.inspect}" if raise_exception
69
+ when Storage
70
+ scope
71
+ else
72
+ raise ArgumentError, "Invalid scope: #{scope.inspect}" if raise_exception
73
+ end
74
+ end
75
+
76
+ def self.ractor
77
+ return MAIN_STORAGE if Ractor.main?
78
+ Ractor.store_if_absent(name) { ThreadSafe.new }
79
+ end
80
+
81
+ def self.thread_group(thread = Thread.current)
82
+ group = thread.is_a?(ThreadGroup) ? thread : thread.group
83
+ by_group = ractor.store_if_absent(:thread_groups) { ThreadSafe.new }
84
+ by_group.store_if_absent(group) { ThreadSafe.new }
85
+ end
86
+
87
+ def self.thread(thread = Thread.current)
88
+ thread = Internal.storage_thread(thread)
89
+ root = Internal.native_ractors? ? ractor : MAIN_STORAGE
90
+ by_thread = root.store_if_absent(:threads) { ThreadSafe.new }
91
+ by_thread.store_if_absent(thread) { Storage.new }
92
+ end
93
+
94
+ def self.fiber_storage = Fiber[name] ||= Storage.new
95
+
96
+ def self.fiber(fiber = Fiber.current)
97
+ by_fiber = ractor.store_if_absent(:fibers) { ThreadSafe.new }
98
+ by_fiber.store_if_absent(fiber) { Storage.new }
99
+ end
100
+
101
+ def initialize
102
+ @mutex = Mutex.new
103
+ @strong = nil
104
+ @weak = nil
105
+ end
106
+
107
+ def [](key, positional_mode = nil, mode: :auto)
108
+ map = map_for(key, positional_mode || mode, create: false)
109
+ map[key] if map
110
+ end
111
+
112
+ def []=(key, mode = :auto, value) # rubocop:disable Style/OptionalArguments
113
+ map_for(key, mode)[key] = value
114
+ end
115
+
116
+ def key?(key, mode = :auto) = map_for(key, mode, create: false)&.key?(key)
117
+
118
+ def store_if_absent(key, mode: :auto)
119
+ map = map_for(key, mode)
120
+ map[key] = yield unless map.key?(key)
121
+ map[key]
122
+ end
123
+
124
+ def dig(key, *keys, mode: :auto)
125
+ value = self[key, mode]
126
+ keys.empty? ? value : value&.dig(*keys)
127
+ end
128
+
129
+ def clear
130
+ @mutex.synchronize do
131
+ @strong&.clear
132
+ @weak&.clear
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def strong(create: true)
139
+ return @strong unless create && !@strong
140
+ @mutex.synchronize { @strong ||= {} }
141
+ end
142
+
143
+ def weak(create: true)
144
+ return @weak unless create && !@weak
145
+ @mutex.synchronize { @weak ||= ObjectSpace::WeakKeyMap.new }
146
+ end
147
+
148
+ def map_for(key, mode, **)
149
+ case mode
150
+ when :auto then return strong(**) if key.instance_of?(String)
151
+ when :strong then return strong(**)
152
+ when :weak # no-op
153
+ else raise ArgumentError, "Invalid mode: #{mode.inspect}"
154
+ end
155
+ Internal.garbage_collectable?(key) ? weak(**) : strong(**)
156
+ end
157
+ end
158
+
159
+ # shareable_constant_value: none
160
+ MAIN_STORAGE = Storage::ThreadSafe.new
161
+ end
162
+ end