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,202 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ class Transaction
7
+ # Binds container wrappers to the transaction attempt that created them.
8
+ #
9
+ # Wrappers call {#access} or {#write} around operations on staged data.
10
+ # These checks reject calls from another Fiber or after the attempt ends.
11
+ # They also invalidate the attempt when an operation raises, so rescuing
12
+ # the error cannot publish writes staged before that failed operation.
13
+ #
14
+ # @api private
15
+ module Wrapper
16
+ include Internal::MarshalSupport::Reject
17
+
18
+ # Wrappers inherit abstract container classes for their interfaces, but
19
+ # must not initialize another ordinary container. This constructor ends
20
+ # Unshareable's super chain after binding the existing transaction snapshot.
21
+ module Initialization
22
+ # Bind the wrapper to its participant and active attempt.
23
+ # Enroll backend when the wrapper owns storage. Composite wrappers use
24
+ # other wrappers for their snapshots and leave backend nil.
25
+ # @param transaction [Transaction] the current attempt
26
+ # @param object [Object] the original participant
27
+ # @param backend [Object, nil] the participant's storage to enroll, if any
28
+ # @param manager [ModeManager, nil] the participant's value transfer manager
29
+ # @param nil_value [BasicObject, nil] the backend's sentinel for stored nil
30
+ def initialize(transaction, object, backend = nil, manager: nil, nil_value: nil)
31
+ @transaction = transaction
32
+ @object = object
33
+ @backend = backend
34
+ @manager = manager
35
+ @nil_value = nil_value
36
+ @entry = enlist(backend) if backend
37
+ @working = @entry&.working
38
+ end
39
+ end
40
+ private_constant :Initialization
41
+
42
+ include Initialization
43
+ include Unshareable
44
+
45
+ # Allow transaction[wrapper] to reuse a wrapper from this same attempt.
46
+ # A different attempt cannot adopt its snapshot or staged writes.
47
+ # @param transaction [Transaction] the attempt requesting the wrapper
48
+ # @return [self]
49
+ # @raise [TypeError] if the wrapper belongs to a different attempt
50
+ def transaction_wrapper(transaction)
51
+ access do
52
+ raise TypeError, "wrapper belongs to another transaction" unless @transaction.equal?(transaction)
53
+ self
54
+ end
55
+ end
56
+
57
+ # Select the abstract container helpers that can operate on staged data.
58
+ # Each wrapper supplies its supported helpers. Other inherited operations
59
+ # may wait on live storage, copy the participant, or bypass its snapshot.
60
+ #
61
+ # @param klass [Class] the wrapper class to configure
62
+ # @param helpers [Array<Symbol>] inherited reads to retain and guard
63
+ # @param writes [Array<Symbol>] inherited writes that must also check the
64
+ # original participant's frozen state
65
+ # @return [Class] the configured wrapper class
66
+ def self.inherit(klass, *helpers, writes: [])
67
+ helpers += writes
68
+ helpers |= %i[inspect pretty_print inspect_with] if klass.method_defined?(:inspect_with)
69
+ owners = klass.ancestors.take_while { |ancestor| ancestor != klass.superclass }
70
+ own = klass.public_instance_methods.select { owners.include?(klass.instance_method(it).owner) }
71
+ enumerable = klass <= Enumerable ? Enumerable.public_instance_methods : []
72
+
73
+ # Set and Vector override map/select to construct self.class. Use
74
+ # Enumerable's Array results so these calls do not construct wrappers
75
+ # without a transaction and participant.
76
+ enumerable.each do |name|
77
+ next if own.include?(name) || helpers.include?(name)
78
+ klass.define_method(name, Enumerable.instance_method(name))
79
+ end
80
+
81
+ guards = Module.new
82
+ rejected = klass.superclass.public_instance_methods - Object.public_instance_methods -
83
+ enumerable - helpers - own
84
+ rejected.each { |name| klass.class_eval { undef_method(name) } }
85
+
86
+ # Metadata reads and empty-input calls can return without touching
87
+ # storage. Guard the helper itself so these also reject closed attempts.
88
+ helpers.each do |name|
89
+ next if own.include?(name)
90
+ operation = writes.include?(name) ? :write : :access
91
+ guards.module_eval <<~RUBY, __FILE__, __LINE__ + 1
92
+ def #{name}(...)
93
+ #{operation} { super }
94
+ end
95
+ RUBY
96
+ end
97
+ klass.prepend(guards)
98
+ end
99
+
100
+ private
101
+
102
+ def enlist(backend) = @transaction.enlist(backend)
103
+
104
+ # Initialize a composite wrapper after binding it to the participant.
105
+ # Copy the named settings, then replace each storage field with its
106
+ # wrapper from this attempt. Set uses this to retain membership settings
107
+ # while its map handles snapshots and commit validation.
108
+ # @param settings [Array<Symbol>] participant instance variables, without @
109
+ # @param components [Hash{Symbol => Object}] storage fields, without @,
110
+ # and the original objects to wrap
111
+ def compose(*settings, **components)
112
+ settings.each do |name|
113
+ variable = :"@#{name}"
114
+ instance_variable_set(variable, @object.instance_variable_get(variable))
115
+ end
116
+ components.each { |name, object| instance_variable_set(:"@#{name}", @transaction[object]) }
117
+ end
118
+
119
+ # A rescued NoMethodError must still discard earlier staged writes.
120
+ # Local wrappers retain the participant's scope getter, with the same
121
+ # lifetime check as reads of staged values.
122
+ def method_missing(name, *arguments, **options, &block)
123
+ if name == :scope && arguments.empty? && options.empty? && !block && @object.respond_to?(:scope)
124
+ return access { @object.scope }
125
+ end
126
+ @transaction.fail!(retryable: false)
127
+ super
128
+ end
129
+
130
+ def respond_to_missing?(name, include_private = false)
131
+ (name == :scope && @object.respond_to?(:scope)) || super
132
+ end
133
+
134
+ def require_block!(present)
135
+ return if present
136
+ @transaction.fail!(retryable: false)
137
+ raise LocalJumpError, "no block given"
138
+ end
139
+
140
+ def access
141
+ @transaction.check_open!
142
+ yield
143
+ rescue Internal::TransactionConflict
144
+ @transaction.fail! if @transaction.state == :active
145
+ raise
146
+ rescue Exception # rubocop:disable Lint/RescueException -- cancellation must poison the attempt too
147
+ @transaction.fail!(retryable: false) if @transaction.state == :active
148
+ raise
149
+ end
150
+
151
+ def initialize_copy(*)
152
+ access do
153
+ @transaction.fail!(retryable: false)
154
+ raise TypeError, "transaction wrappers cannot be copied"
155
+ end
156
+ end
157
+
158
+ def check_frozen! = Internal::Freeze.check(@object)
159
+
160
+ def write
161
+ access do
162
+ raise FrozenError, "cannot modify frozen transaction participant" if @object.frozen?
163
+ @transaction.guard(@object)
164
+ @entry&.write!
165
+ yield
166
+ end
167
+ end
168
+
169
+ def wrap(value, mode: nil)
170
+ return @nil_value if value.nil? && @nil_value
171
+ return value unless @manager
172
+ selected = mode || @manager.mode
173
+
174
+ # These modes can change ownership or freeze supplied objects before
175
+ # commit. Discarding staged storage cannot undo those effects.
176
+ if %i[move make_shareable dedup proxy].include?(selected)
177
+ raise TypeError, "#{selected} transfers do not support transactions"
178
+ end
179
+
180
+ @manager.wrap(value, mode:)
181
+ end
182
+
183
+ def unwrap_stored(value)
184
+ return if @nil_value.equal?(value)
185
+ return value unless @manager
186
+ raise TypeError, "move envelopes do not support transactions" if Envelope::Move === value
187
+ @manager.unwrap(value)
188
+ end
189
+
190
+ def matches?(current, expected, identity:)
191
+ comparison = @manager.wrap(expected, mode: identity ? :local : :copy)
192
+ comparison = @nil_value if expected.nil? && @nil_value
193
+ @manager.same_value?(current, comparison, identity:)
194
+ end
195
+
196
+ def compared(result)
197
+ @transaction.fail! unless result
198
+ result
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,343 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # A transaction groups multiple changes to Farce objects into a single atomic attempt.
7
+ #
8
+ # These changes either all succeed together when the transaction is committed
9
+ # or are all discarded if the transaction fails.
10
+ #
11
+ # Transaction attempts are isolated and can be retried safely.
12
+ #
13
+ # You must therefore make sure that any side-effects not going through transaction wrappers
14
+ # are either completely avoided, or at least made idempotent.
15
+ #
16
+ # ```ruby
17
+ # account1 = Farce::Atom.new(10)
18
+ # account2 = Farce::Atom.new(20)
19
+ #
20
+ # # Wire 10 from account1 to account2
21
+ # Farce.transaction do |tx|
22
+ # raise "insufficient funds" unless tx[account1].value >= 10
23
+ # tx[account1].update { it - 10 }
24
+ # tx[account2].update { it + 10 }
25
+ # end
26
+ # ```
27
+ #
28
+ # Transactions can mix different types and variants, and are therefore ideal for coordinating complex, coordinated
29
+ # changes affecting data within multiple scopes.
30
+ #
31
+ # ## Supported objects
32
+ #
33
+ # Out of the box, transactions support instances of the following classes:
34
+ # * Atoms, except weak atoms
35
+ # * ConcurrentMap subclasses, except weak maps
36
+ # * Molecules
37
+ # * Mutable wrappers with frozen snapshots
38
+ # * Sets, including sorted sets
39
+ # * TreeMap subclasses
40
+ # * Vectors
41
+ # * Concurrent::TVar from the concurrent-ruby gem
42
+ # * Ractor::TVar from the ractor-sharing gem
43
+ # * Ractor::TMVar from the ractor-tmvar gem
44
+ class Transaction
45
+ include Internal::MarshalSupport::Reject
46
+ include Internal::Autoloads
47
+ include Unshareable
48
+
49
+ # Raised when a wrapper is used outside its transaction attempt.
50
+ ClosedError = Class.new(StandardError)
51
+ # Raised when another Fiber uses a transaction.
52
+ OwnershipError = Class.new(StandardError)
53
+ Aborted = Class.new(StandardError)
54
+ private_constant :Aborted, :MapOperations, :SetOperations
55
+
56
+ # @note This method must be called from the main Ractor, and the block must be convertible to a shareable proc.
57
+ #
58
+ # Registers a wrapper for a custom class, allowing them to be used within transactions.
59
+ #
60
+ # @example
61
+ # Account = Struct.new(:balance) do
62
+ # def withdraw(amount) = balance.value -= amount
63
+ # end
64
+ #
65
+ # Farce::Transaction.define(Account) do |account, tx|
66
+ # account.class.new(tx[account.balance])
67
+ # end
68
+ #
69
+ # account = Account.new(Farce::Atom.new(10))
70
+ # Farce.transaction { |tx| tx[account].withdraw(3) }
71
+ # account.balance.value # => 7
72
+ #
73
+ # @param klass [Class] the class to wrap
74
+ # @yieldparam object [Object] the participant
75
+ # @yieldparam transaction [Transaction] the current attempt
76
+ # @yieldreturn [Object] the transaction wrapper
77
+ def self.define(klass, &)
78
+ raise LocalJumpError, "no block given" unless block_given?
79
+ definition = Internal.prepare_method_definition(&)
80
+ REGISTER.define(klass) { define_method(:call, definition) }
81
+ end
82
+
83
+ # Default factory for participants without a registered wrapper definition.
84
+ # @api private
85
+ class Factory
86
+ include Shareable::Immutable
87
+
88
+ # Ask a participant's hook to build a wrapper for the supplied attempt.
89
+ # @param object [#transaction_wrapper] the participant to wrap
90
+ # @param transaction [Transaction] the current attempt
91
+ # @return [Object] the wrapper returned by the participant's hook
92
+ def call(object, transaction)
93
+ raise TypeError, "#{object.class} does not support transactions" unless object.respond_to?(:transaction_wrapper)
94
+ object.transaction_wrapper(transaction)
95
+ end
96
+ end
97
+
98
+ REGISTER = ClassMirror.new(Factory) { |mapped, _source| mapped.new }
99
+ private_constant :Factory, :REGISTER
100
+
101
+ # @overload run(*objects, retries: nil, backoff_after: 10, max_backoff: 1.0)
102
+ # Creates and runs a new transaction attempt.
103
+ #
104
+ # Automatically retries failed attempts indefinitely unless a retry limit is specified.
105
+ # Starts backing off after the specified number of attempts, up to the maximum delay.
106
+ #
107
+ # @param objects [Array] list of objects to enroll in the transaction
108
+ # @param retries [Integer, nil] maximum additional attempts, or nil for unlimited retries
109
+ # @param backoff_after [Integer] number of attempts before starting to back off
110
+ # @param max_backoff [Numeric] maximum backoff delay in seconds
111
+ # @return [Boolean] whether the transaction committed successfully
112
+ # @see Farce.transaction
113
+ def self.run(*, retries: nil, backoff_after: 10, max_backoff: 1.0, &) # rubocop:disable Naming/PredicateMethod
114
+ raise LocalJumpError, "no block given" unless block_given?
115
+
116
+ unless retries.nil? || (Integer === retries && retries >= 0)
117
+ raise ArgumentError, "retries must be nil or a non-negative Integer"
118
+ end
119
+
120
+ unless Integer === backoff_after && backoff_after >= 0
121
+ raise ArgumentError, "backoff_after must be a non-negative Integer"
122
+ end
123
+
124
+ attempt = 0
125
+
126
+ while retries.nil? || attempt <= retries
127
+ if attempt > backoff_after
128
+ delay = (attempt - backoff_after) * 0.01
129
+ delay = max_backoff if delay > max_backoff
130
+ scheduler = Fiber.scheduler if Fiber.respond_to?(:scheduler) && !Fiber.current.blocking?
131
+ scheduler ? scheduler.kernel_sleep(delay) : sleep(delay)
132
+ end
133
+
134
+ transaction = new
135
+ return true if transaction.run(*, &)
136
+ return false unless transaction.retryable?
137
+
138
+ attempt += 1
139
+ end
140
+
141
+ false
142
+ end
143
+
144
+ # Create an unused attempt bound to the current Fiber.
145
+ # Call {#run} once to enroll participants and stage writes. Use {.run} when
146
+ # failed comparisons or conflicts should create and run fresh attempts.
147
+ def initialize
148
+ @owner = Fiber.current
149
+ @state = :new
150
+ @failed = @aborted = false
151
+ @retryable = true
152
+ @wrappers = {}.compare_by_identity
153
+ @entries = {}.compare_by_identity
154
+ @guards = {}.compare_by_identity
155
+ super
156
+ end
157
+
158
+ # Report the attempt's lifecycle state.
159
+ # @return [Symbol] :new before run, :active during the block, :committing
160
+ # during publication, then :committed or :failed
161
+ attr_reader :state
162
+
163
+ # Whether abort! was called during this attempt.
164
+ # @return [Boolean]
165
+ def aborted? = @aborted
166
+
167
+ # Whether .run may retry this attempt after a failed commit or comparison.
168
+ # Explicit aborts, wrapper exceptions, and fail!(retryable: false) disable retries.
169
+ # @return [Boolean]
170
+ def retryable? = @retryable && !@aborted
171
+
172
+ # Execute this attempt once. Use {.run} for automatic retries.
173
+ #
174
+ # The block stages work through {#[]}. After it returns, commit validates
175
+ # the attempt's reads and publishes its staged writes together.
176
+ # Cancellation during commit is deferred until publication finishes. Once
177
+ # committed, an interruption does not undo the changes.
178
+ #
179
+ # @param objects [Array] the objects to enroll in this transaction attempt
180
+ # @yield [transaction, *objects] the current transaction and the enrolled objects
181
+ # @yieldparam transaction [Transaction] this active attempt
182
+ # @yieldparam objects [Array] the enrolled objects
183
+ # @return [Boolean] whether the changes committed
184
+ # @raise [ClosedError] if this attempt has already run
185
+ # @raise [OwnershipError] if called from a different Fiber
186
+ def run(*objects)
187
+ started = false
188
+ raise LocalJumpError, "no block given" unless block_given?
189
+ check_owner!
190
+ raise ClosedError, "transaction has already run" unless @state == :new
191
+
192
+ @state = :active
193
+ started = true
194
+
195
+ objects.map! { self[it] }
196
+ yield self, *objects
197
+
198
+ check_open!
199
+
200
+ Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
201
+ @state = :committing
202
+ entries = @entries.values
203
+ unless @failed
204
+ entries = entries.filter_map { Internal::TransactionMapSnapshot === it ? it.prepare_commit : it }
205
+ end
206
+ committed = !@failed && Internal.commit_transaction(entries, @guards.values, self)
207
+ @state = committed ? :committed : :failed
208
+ Internal.notify_transaction(entries) if committed && entries.none? { it.respond_to?(:commit_group) } &&
209
+ Internal.respond_to?(:notify_transaction)
210
+ committed
211
+ end
212
+ rescue Aborted, Internal::TransactionConflict
213
+ false
214
+ ensure
215
+ if started
216
+ @state = :failed if %i[active committing].include?(@state)
217
+ Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
218
+ @entries.each_value { it.release if it.respond_to?(:release) }
219
+ end
220
+ end
221
+ end
222
+
223
+ # Includes an object in this transaction and returns a transaction-aware version of it.
224
+ # Read from and write to the returned object, so the transaction can keep track of inputs and changes.
225
+ #
226
+ # @param object [Object] the participant or a wrapper from this attempt
227
+ # @return [Object] a wrapper valid only during this attempt
228
+ # @raise [TypeError] if the participant is unsupported or belongs to another attempt
229
+ def [](object)
230
+ check_open!
231
+ @wrappers.fetch(object) do
232
+ klass = Kernel.instance_method(:class).bind_call(object)
233
+ @wrappers[object] = REGISTER[klass].call(object, self)
234
+ end
235
+ rescue Internal::TransactionConflict
236
+ fail! if @state == :active && @owner.equal?(Fiber.current)
237
+ raise
238
+ rescue Exception # rubocop:disable Lint/RescueException -- cancellation must poison the attempt too
239
+ if @state == :active && @owner.equal?(Fiber.current)
240
+ @failed = true
241
+ @retryable = false
242
+ end
243
+ raise
244
+ end
245
+
246
+ # Stop the transaction block and discard all staged writes without retrying.
247
+ # The surrounding run call returns false. No writes to participants are published.
248
+ #
249
+ # @example
250
+ # balance = Farce::Atom.new(100)
251
+ #
252
+ # Farce.transaction do |tx|
253
+ # tx.abort! if tx[balance] < 100
254
+ # tx[balance].value -= 100
255
+ # end
256
+ # @return [void]
257
+ def abort!
258
+ check_open!
259
+ @failed = @aborted = true
260
+ raise Aborted, "transaction aborted"
261
+ end
262
+
263
+ # Mark a failed condition, even if the caller ignores its return value.
264
+ #
265
+ # The block may continue, but commit will discard its staged writes.
266
+ # Custom wrappers can use this for their own conditional operations.
267
+ #
268
+ # Wrapper operations call this automatically when they raise. If you rescue
269
+ # an error before a wrapper is called, such as an argument computation error,
270
+ # call `fail!(retryable: false)` to prevent earlier writes from committing.
271
+ #
272
+ # @param retryable [Boolean] whether .run may repeat the block for this failure
273
+ # @return [false]
274
+ def fail!(retryable: true) # rubocop:disable Naming/PredicateMethod
275
+ check_open!
276
+ @failed = true
277
+ @retryable &&= retryable
278
+ false
279
+ end
280
+
281
+ # Check lifetime and Fiber ownership before a wrapper accesses staged data.
282
+ # @raise [ClosedError] if the attempt is not active
283
+ # @raise [OwnershipError] if called from a different Fiber
284
+ # @api private
285
+ def check_open!
286
+ check_owner!
287
+ raise ClosedError, "transaction attempt is closed" unless @state == :active
288
+ end
289
+
290
+ # Enroll storage once and reuse its staged data across wrappers.
291
+ #
292
+ # Backends that expose transaction_source share enrollment with that source,
293
+ # so wrappers of the same storage cannot stage conflicting independent copies.
294
+ #
295
+ # An optional block builds an entry for integrations whose sources do not
296
+ # expose transaction_snapshot. It runs once per source in each attempt.
297
+ # Without a block, enrollment uses the source's snapshot hook.
298
+ #
299
+ # Entries may implement release to free resources held during the attempt.
300
+ # It is called after commit or on any exit that discards staged writes.
301
+ #
302
+ # @param backend [Object] storage to enroll, optionally exposing transaction_source
303
+ # @param per_key [Boolean] track map dependencies when using the source's snapshot hook
304
+ # @yieldparam source [Object] the canonical source whose entry is being created
305
+ # @yieldreturn [#working] the entry containing staged storage and commit hooks
306
+ # @return [#working] the enrolled entry containing this attempt's staged storage
307
+ # @api private
308
+ def enlist(backend, per_key: false)
309
+ check_open!
310
+ source = backend.respond_to?(:transaction_source) ? backend.transaction_source : backend
311
+ @entries.fetch(source) do
312
+ unless block_given? || source.respond_to?(:transaction_snapshot)
313
+ raise TypeError, "#{source.class} does not support transactions"
314
+ end
315
+ Thread.handle_interrupt(Internal::INTERRUPT_MASK) do
316
+ @entries[source] = if block_given?
317
+ yield source
318
+ elsif per_key
319
+ Internal::TransactionMapSnapshot.new(source)
320
+ else
321
+ source.transaction_snapshot
322
+ end
323
+ end
324
+ end
325
+ end
326
+
327
+ # Include a Local participant's shared freeze flag in commit validation.
328
+ # Its scoped backend alone cannot reveal that another scope froze the handle.
329
+ #
330
+ # @param object [Object] the participant being written
331
+ # @api private
332
+ def guard(object)
333
+ return unless Local::Scoped === object
334
+ @guards[object] = object.instance_variable_get(:@farce_freeze_state)
335
+ end
336
+
337
+ private
338
+
339
+ def check_owner!
340
+ raise OwnershipError, "transaction belongs to another Fiber" unless @owner.equal?(Fiber.current)
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # A Ractor-shareable map that keeps entries sorted by key.
7
+ # Transfer modes apply only to values. Keys are stored directly, except that mutable strings become immutable.
8
+ # Values stored through this map's mode manager are automatically unwrapped.
9
+ #
10
+ # @example Keeping copied values in key order
11
+ # map = Farce::TreeMap.new(mode: :copy)
12
+ # map[2] = ["draft"]
13
+ # map[1] = ["published"]
14
+ #
15
+ # map.values # => [["published"], ["draft"]]
16
+ class TreeMap < Farce::Abstract::TreeMap
17
+ include Shareable::Delegated
18
+
19
+ # @!macro modes
20
+ # @param entries [Hash, Array<Array(BasicObject, BasicObject)>, Abstract::Map, #each, nil]
21
+ # Optional initial entries for the map.
22
+ # @param mode [Symbol] The default value transfer mode.
23
+ def initialize(entries = nil, mode: :copy, normalize_keys: nil, **keyword_entries)
24
+ unless keyword_entries.empty?
25
+ raise ArgumentError, "entries given as both positional and keyword arguments" unless entries.nil?
26
+ entries = keyword_entries
27
+ end
28
+ @manager = marshal_mode_manager(mode)
29
+ super(entries, normalize_keys:)
30
+ end
31
+
32
+ # The default transfer mode for values.
33
+ # @return [Symbol]
34
+ def mode = @manager.mode
35
+
36
+ # Values are stored in Ractor-shareable representations.
37
+ # @return [true]
38
+ def shareable_values? = true
39
+
40
+ protected
41
+
42
+ def unwrap_value(value) = @manager.unwrap(value)
43
+
44
+ def wrap_value(value)
45
+ check_frozen!
46
+ @manager.wrap(value)
47
+ end
48
+
49
+ private
50
+
51
+ def each_for_inspect(&) = internal_map.each(&)
52
+ def inspect_value(inspector, value) = super(inspector, value, @manager)
53
+ def new_tree_map(...) = Internal::StrictTreeMap.new(...)
54
+ def freeze_backend = internal_map
55
+ end
56
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Unsafe
7
+ # @!macro unsafe
8
+ # An LFU map for caches whose caller provides exclusive access to composed operations.
9
+ # Individual storage operations retain the engine backend's integrity guard. Cache loading
10
+ # does not coordinate competing callers, including callers loading the same key.
11
+ class LFUMap < Abstract::LFUMap
12
+ include Unshareable
13
+
14
+ # @!method store_if_absent(key)
15
+ # Read an existing value or construct and store a value for an absent key.
16
+ # Competing callers can run the loader concurrently, including for equal keys.
17
+ # The caller must provide exclusive access when a single initialization is required.
18
+ # @param key [BasicObject] The key to retrieve or initialize.
19
+ # @yieldreturn [BasicObject] The value to store.
20
+ # @return [BasicObject] The existing or newly stored value.
21
+ # @raise [LocalJumpError] If no block is given.
22
+
23
+ # (see Abstract::Map#[])
24
+ def []=(key, value)
25
+ internal_map[prepare_key(key)] = value
26
+ value
27
+ end
28
+
29
+ private
30
+
31
+ def new_bounded_map(...) = Internal::LFUMap.new(...)
32
+ def new_key_locks(**) = nil
33
+ def with_key_lock(_key) = yield
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Unsafe
7
+ # @!macro unsafe
8
+ # An LRU map for caches whose caller provides exclusive access to composed operations.
9
+ # Individual storage operations retain the engine backend's integrity guard. Cache loading
10
+ # does not coordinate competing callers, including callers loading the same key.
11
+ class LRUMap < Abstract::LRUMap
12
+ include Unshareable
13
+
14
+ # @!method store_if_absent(key)
15
+ # Read an existing value or construct and store a value for an absent key.
16
+ # Competing callers can run the loader concurrently, including for equal keys.
17
+ # The caller must provide exclusive access when a single initialization is required.
18
+ # @param key [BasicObject] The key to retrieve or initialize.
19
+ # @yieldreturn [BasicObject] The value to store.
20
+ # @return [BasicObject] The existing or newly stored value.
21
+ # @raise [LocalJumpError] If no block is given.
22
+
23
+ # (see Abstract::Map#[])
24
+ def []=(key, value)
25
+ internal_map[prepare_key(key)] = value
26
+ value
27
+ end
28
+
29
+ private
30
+
31
+ def new_bounded_map(...) = Internal::LRUMap.new(...)
32
+ def new_key_locks(**) = nil
33
+ def with_key_lock(_key) = yield
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Unsafe
7
+ # @!macro unsafe
8
+ # A {Abstract::TreeMap tree map} that is not thread-safe and cannot be shared between Ractors.
9
+ class TreeMap < Abstract::TreeMap
10
+ include Unshareable
11
+
12
+ undef_method :transaction_wrapper
13
+
14
+ private
15
+
16
+ def new_tree_map(...) = Internal::UnsafeTreeMap.new(...)
17
+ def new_key_locks = nil
18
+ def with_key_lock(_key) = yield
19
+ end
20
+ end
21
+ end