farce 0.0.1.alpha2-x86-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,227 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal # :nodoc: all
7
+ # Portable snapshots of public values. Backing stores never enter the stream.
8
+ module MarshalSupport
9
+ extend self
10
+
11
+ def protocol_method?(name) = %i[marshal_dump marshal_load _dump _load _dump_data _load_data].include?(name)
12
+
13
+ # A version belongs to the public snapshot, not to a backend layout.
14
+ def payload(data, length)
15
+ unless data.is_a?(Array) && data.length == length + 1 && data.first == 1
16
+ raise TypeError, "invalid Farce Marshal snapshot"
17
+ end
18
+ data.drop(1)
19
+ end
20
+
21
+ def value(value) = [Internal.marshal_shareable?(value), Kernel.instance_method(:frozen?).bind_call(value), value]
22
+
23
+ def restore_value(data)
24
+ shareable, frozen, value = data
25
+ if shareable && Shareable === value && !(Shareable::Native === value) &&
26
+ !Kernel.instance_method(:frozen?).bind_call(value) &&
27
+ value.respond_to?(:marshal_load, true) && value.method(:marshal_load).owner.name&.start_with?("Farce::")
28
+ raise TypeError, "recursive shareable snapshots require an initialized value"
29
+ end
30
+ value = Ractor.make_shareable(value) if shareable
31
+ Kernel.instance_method(:freeze).bind_call(value) if frozen
32
+ value
33
+ end
34
+
35
+ def freeze(object, frozen)
36
+ return object unless frozen
37
+ object.freeze
38
+ end
39
+
40
+ module Initialize
41
+ private
42
+
43
+ # @api private
44
+ def marshal_initialize(arguments, options, _configuration = nil)
45
+ initialize(*arguments, **options)
46
+ end
47
+
48
+ # @api private
49
+ def marshal_configuration = nil
50
+
51
+ # A restored manager must retain its links to already-loaded envelopes.
52
+ # @api private
53
+ def marshal_mode_manager(mode) = @manager || ModeManager.new(mode:)
54
+
55
+ def publish_marshaled
56
+ if is_a?(Shareable)
57
+ publish_shareable
58
+ elsif is_a?(Unshareable)
59
+ pin_unshareable
60
+ end
61
+ end
62
+ end
63
+
64
+ module Counter
65
+ include Initialize
66
+
67
+ # @api private
68
+ def marshal_dump
69
+ options = respond_to?(:scope) ? { scope: } : {}
70
+ [1, initial, value, options, marshal_configuration, frozen?]
71
+ end
72
+
73
+ # @api private
74
+ def marshal_load(data)
75
+ initial, current, options, configuration, frozen = MarshalSupport.payload(data, 5)
76
+ marshal_initialize([initial], options, configuration)
77
+ store(current)
78
+ MarshalSupport.freeze(self, frozen)
79
+ end
80
+ end
81
+
82
+ module Flag
83
+ include Initialize
84
+
85
+ # @api private
86
+ def marshal_dump
87
+ options = respond_to?(:scope) ? { scope: } : {}
88
+ [1, value, options, marshal_configuration, frozen?]
89
+ end
90
+
91
+ # @api private
92
+ def marshal_load(data)
93
+ current, options, configuration, frozen = MarshalSupport.payload(data, 4)
94
+ marshal_initialize([current], options, configuration)
95
+ MarshalSupport.freeze(self, frozen)
96
+ end
97
+ end
98
+
99
+ module Atom
100
+ include Initialize
101
+
102
+ # @api private
103
+ def marshal_dump
104
+ options = { compare_by_identity: compare_by_identity? }
105
+ options[:mode] = mode if respond_to?(:mode)
106
+ options[:scope] = scope if respond_to?(:scope)
107
+ [1, MarshalSupport.value(value), options, marshal_configuration, frozen?]
108
+ end
109
+
110
+ # @api private
111
+ def marshal_load(data)
112
+ current, options, configuration, frozen = MarshalSupport.payload(data, 4)
113
+ marshal_initialize([nil], options, configuration)
114
+ store(MarshalSupport.restore_value(current))
115
+ MarshalSupport.freeze(self, frozen)
116
+ end
117
+ end
118
+
119
+ module Vector
120
+ include Initialize
121
+
122
+ # @api private
123
+ def marshal_dump
124
+ options = { compare_by_identity: compare_by_identity? }
125
+ options[:scope] = scope if respond_to?(:scope)
126
+ [1, internal_vector.snapshot.map { MarshalSupport.value(it) }, options, marshal_configuration, frozen?]
127
+ end
128
+
129
+ # @api private
130
+ def marshal_load(data)
131
+ entries, options, configuration, frozen = MarshalSupport.payload(data, 4)
132
+ marshal_initialize([[]], options, configuration)
133
+ entries.each { internal_vector.push(MarshalSupport.restore_value(it)) }
134
+ MarshalSupport.freeze(self, frozen)
135
+ end
136
+ end
137
+
138
+ module Map
139
+ include Initialize
140
+
141
+ # @api private
142
+ def marshal_dump
143
+ options = {}
144
+ unless is_a?(Abstract::TreeMap)
145
+ options[:compare_keys_by_identity] = compare_keys_by_identity?
146
+ options[:compare_values_by_identity] = compare_values_by_identity?
147
+ end
148
+ options[:mode] = mode if respond_to?(:mode)
149
+ options[:manager] = @manager if defined?(@manager) && !@manager.is_a?(WeakModeManager)
150
+ options[:scope] = scope if respond_to?(:scope)
151
+ options[:max_size] = max_size if respond_to?(:max_size)
152
+ options[:normalize_keys] = KeyNormalizer.dump(@key_normalizer) if @key_normalizer
153
+ entries = marshal_entries.map do |key, stored|
154
+ [MarshalSupport.value(key), marshal_map_value(stored)]
155
+ end
156
+ [1, entries, options, marshal_configuration, frozen?]
157
+ end
158
+
159
+ # @api private
160
+ def marshal_load(data)
161
+ entries, options, configuration, frozen = MarshalSupport.payload(data, 4)
162
+ @manager = options.delete(:manager) if options.key?(:manager)
163
+ options[:normalize_keys] = KeyNormalizer.restore(options[:normalize_keys]) if options.key?(:normalize_keys)
164
+ marshal_initialize([nil], options, configuration)
165
+ map = marshal_map
166
+ entries.each do |key, stored|
167
+ map[MarshalSupport.restore_value(key)] = marshal_restore_map_value(stored)
168
+ end
169
+ MarshalSupport.freeze(self, frozen)
170
+ end
171
+
172
+ private
173
+
174
+ # @api private
175
+ def marshal_entries = marshal_map.each
176
+
177
+ # @api private
178
+ def marshal_map
179
+ map = internal_map
180
+ KeyNormalizer::ConcurrentMap === map ? map.transaction_source : map
181
+ end
182
+
183
+ # @api private
184
+ def marshal_map_value(value)
185
+ MarshalSupport.value(value)
186
+ end
187
+
188
+ # @api private
189
+ def marshal_restore_map_value(data)
190
+ MarshalSupport.restore_value(data)
191
+ end
192
+ end
193
+
194
+ module Reject
195
+ # @api private
196
+ def marshal_dump = raise(TypeError, "#{self.class} cannot be marshaled")
197
+ end
198
+
199
+ module WeakValue
200
+ # @api private
201
+ def marshal_dump
202
+ state, current = state_and_value
203
+ raise TypeError, "moved weak values cannot be marshaled" if state == :moved
204
+ [1, MarshalSupport.value(current), frozen?]
205
+ end
206
+
207
+ # @api private
208
+ def marshal_load(data)
209
+ current, frozen = MarshalSupport.payload(data, 2)
210
+ initialize(MarshalSupport.restore_value(current))
211
+ freeze if frozen
212
+ end
213
+ end
214
+
215
+ module WeakRef
216
+ # @api private
217
+ def marshal_dump = [1, @value]
218
+
219
+ # @api private
220
+ def marshal_load(data)
221
+ @value, = MarshalSupport.payload(data, 1)
222
+ Kernel.instance_method(:freeze).bind_call(self)
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,20 @@
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
+ # When including Farce in a class or module, this module is included instead
9
+ # This avoids pulling in private constants, like {Farce::Internal} or `UNDEFINED`.
10
+ module Mixin
11
+ # This will trigger all autoloads, but any other approach would be complex and fragile.
12
+ Farce.constants(false).each { const_set(it, Farce.const_get(it)) unless it =~ /\A[A-Z_]+\Z|\ATest/ }
13
+
14
+ def self.included(base)
15
+ return unless base.const_defined?(:Ractor, false) && ractor = base.const_get(:Ractor, false)
16
+ ractor.const_set(:Port, Farce::Port) unless ractor.const_defined?(:Port, false)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,63 @@
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
+ # Coordinate mutable keys by ordering within one Ractor.
9
+ # SortedSet uses this for backing trees whose keys cannot be published.
10
+ class MutableOrderedKeyLockMap
11
+ Gate = Struct.new(:lock, :participants)
12
+ private_constant :Gate
13
+
14
+ def initialize
15
+ @guard = Farce::Lock.new
16
+ @entries = MutableTreeMap.new
17
+ @participants = 0
18
+ end
19
+
20
+ def synchronize(key, &)
21
+ raise LocalJumpError, "no block given" unless block_given?
22
+
23
+ gate = nil
24
+ registered = false
25
+ begin
26
+ Thread.handle_interrupt(INTERRUPT_MASK) do
27
+ @guard.synchronize do
28
+ gate = @entries[key]
29
+ unless gate
30
+ gate = Gate.new(KeyLockMap.new(registry_class: Farce::Strict::Map), 0)
31
+ @entries[key] = gate
32
+ end
33
+ gate.participants += 1
34
+ @participants += 1
35
+ registered = true
36
+ end
37
+ end
38
+ gate.lock.synchronize(:operation, &)
39
+ ensure
40
+ release(key, gate) if registered
41
+ end
42
+ end
43
+
44
+ def transaction_snapshot = PortableTransaction::ReservationEntry.new(self)
45
+
46
+ private
47
+
48
+ def release(key, gate)
49
+ Thread.handle_interrupt(INTERRUPT_MASK) do
50
+ @guard.synchronize do
51
+ gate.participants -= 1
52
+ @participants -= 1
53
+ if @participants.zero?
54
+ @entries.clear
55
+ elsif gate.participants.zero?
56
+ @entries.delete(key)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal
7
+ # Coordination objects own live state that cannot be duplicated.
8
+ module Noncopyable
9
+ include MarshalSupport::Reject
10
+
11
+ def dup = initialize_copy(self)
12
+ def clone(...) = initialize_copy(self)
13
+
14
+ private def initialize_copy(_other) = raise(TypeError, "#{self.class} cannot be copied")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,69 @@
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
+ # Coordinate keys using their ordering, independently of their hash values.
9
+ class OrderedKeyLockMap
10
+ include Shareable::Unfreezable
11
+
12
+ Gate = Data.define(:lock, :participants)
13
+ private_constant :Gate
14
+
15
+ def initialize
16
+ @guard = Farce::Lock.new
17
+ @entries = ShareableTreeMap.new
18
+ @participants = Counter.new(0)
19
+ super
20
+ end
21
+
22
+ def synchronize(key, &)
23
+ raise LocalJumpError, "no block given" unless block_given?
24
+
25
+ gate = nil
26
+ registered = false
27
+
28
+ begin
29
+ Thread.handle_interrupt(INTERRUPT_MASK) do
30
+ @guard.synchronize do
31
+ gate = @entries[key]
32
+ unless gate
33
+ gate = Gate.new(KeyLockMap.new(registry_class: Farce::Strict::Map), Counter.new(0))
34
+ Ractor.make_shareable(gate) if Internal.native_ractors?
35
+ @entries[key] = gate
36
+ end
37
+ gate.participants.add(1)
38
+ @participants.add(1)
39
+ registered = true
40
+ end
41
+ end
42
+ gate.lock.synchronize(:operation, &)
43
+ ensure
44
+ release(key, gate) if registered
45
+ end
46
+ end
47
+
48
+ def transaction_snapshot = PortableTransaction::ReservationEntry.new(self)
49
+
50
+ private
51
+
52
+ def release(key, gate)
53
+ Thread.handle_interrupt(INTERRUPT_MASK) do
54
+ @guard.synchronize do
55
+ remaining = gate.participants.add(-1)
56
+ total = @participants.add(-1)
57
+ # Clearing needs no comparisons. It also reclaims idle records left
58
+ # behind when a comparator raised during an earlier cleanup.
59
+ if total.zero?
60
+ @entries.clear
61
+ elsif remaining.zero?
62
+ @entries.delete(key)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal # :nodoc: all
7
+ # Runs delayed pool control work outside application Ractors.
8
+ class PoolSupervisorService
9
+ include Shareable::Unfreezable
10
+
11
+ STOP = :stop
12
+ private_constant :STOP
13
+
14
+ def initialize
15
+ @queue = TimerQueue.new(mode: :raise)
16
+ @closed = Atom.new(false)
17
+ @worker = Ractor.new(@queue, name: "farce-pool-supervisor") do |queue|
18
+ while (entry = queue.pop) != STOP
19
+ pool, token = entry
20
+ pool.scale_if_needed(token)
21
+ end
22
+ end
23
+ super
24
+ end
25
+
26
+ def schedule(pool, token, delay)
27
+ raise ::Farce::ClosedQueueError, "pool supervisor is closed" if @closed.value
28
+ @queue.push([pool, token].freeze, delay:)
29
+ end
30
+
31
+ def close
32
+ return unless @closed.compare_and_set(false, true)
33
+ @queue.push(STOP, clock: 0)
34
+ @worker.respond_to?(:join) ? @worker.join : @worker.take
35
+ end
36
+ end
37
+
38
+ PoolSupervisor = PoolSupervisorService.new
39
+ at_exit { PoolSupervisor.close }
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal # :nodoc: all
7
+ # Coordinates one scheduler with its pool.
8
+ class PoolWorker
9
+ include Shareable::Unfreezable
10
+
11
+ def initialize(pool)
12
+ @pool = pool
13
+ @inflight = Counter.new if pool.max_inflight || pool.shrink_after
14
+ @pressure = pool.min_size < pool.max_size
15
+ @registered = Atom.new(true)
16
+ super()
17
+ end
18
+
19
+ def idle_timeout = @pool.shrink_after
20
+
21
+ def wait_for_admission(scheduler, runnable_count)
22
+ return false if @pool.drained?
23
+ limit = @pool.max_inflight
24
+ return true unless limit
25
+
26
+ loop do
27
+ return false if @pool.drained?
28
+ return true if @pool.closing? || @pool.at_max_size? || @inflight.value < limit
29
+ return true if runnable_count && scheduler.public_send(runnable_count).zero?
30
+
31
+ generation = @pool.admission_generation
32
+ next if @pool.closing? || @pool.at_max_size? || @inflight.value < limit
33
+ @pool.wait_for_admission(generation)
34
+ end
35
+ end
36
+
37
+ def task_started
38
+ @inflight&.increment
39
+ @pool.task_taken if @pressure
40
+ end
41
+
42
+ def task_finished
43
+ return unless @inflight
44
+ @inflight.decrement
45
+ @pool.admission_changed if @pool.max_inflight && !@pool.at_max_size?
46
+ end
47
+
48
+ def retire?
49
+ return false unless @inflight&.value&.zero?
50
+ return false unless @pool.retire_worker
51
+ @registered.compare_and_set(true, false)
52
+ end
53
+
54
+ def stopped(error, replace: true) # rubocop:disable Naming/PredicateMethod
55
+ return false unless @registered.compare_and_set(true, false)
56
+ @pool.worker_stopped(error, replace:)
57
+ true
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal # :nodoc: all
7
+ module PortableTransaction
8
+ # Stop new ordered-key reservations and reject initializers already active.
9
+ # Ordinary TreeMap operations already use this guard and participant count.
10
+ class ReservationEntry < Entry
11
+ def initialize(source) # rubocop:disable Lint/MissingSuper
12
+ @source = source
13
+ @locks = [source.instance_variable_get(:@guard)]
14
+ end
15
+
16
+ def prepare = nil
17
+ def apply = nil
18
+ def restore = nil
19
+ def notify = nil
20
+
21
+ def valid?
22
+ participants = @source.instance_variable_get(:@participants)
23
+ participants = participants.value unless Integer === participants
24
+ participants.zero?
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal # :nodoc: all
7
+ module PortableTransaction
8
+ # Keep the index identity so operations already resolving a key retry
9
+ # against the installed entries. Retiring old cells invalidates queued
10
+ # operations without adding checks to ordinary map methods.
11
+ class StrongMapEntry < Entry
12
+ FIELDS = %i[@data @entries @registry_holes @sweep_cursor @iteration_epoch].freeze
13
+ CELL_FIELDS = %i[@retired @present @key @value].freeze
14
+
15
+ def self.pairs(index)
16
+ with_cells(index) do |cells|
17
+ cells.filter_map do |cell|
18
+ next if cell.instance_variable_get(:@retired) || !cell.instance_variable_get(:@present)
19
+ alive, key = cell.instance_variable_get(:@key).read
20
+ [key, cell.instance_variable_get(:@value)] if alive
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.size(index)
26
+ with_cells(index) do |cells|
27
+ cells.count { !it.instance_variable_get(:@retired) && it.instance_variable_get(:@present) }
28
+ end
29
+ end
30
+
31
+ # Read the current registry and cells together, so a concurrent commit
32
+ # cannot retire cells between enumeration and reading their contents.
33
+ def self.with_cells(index)
34
+ loop do
35
+ cells = index.snapshot
36
+ registry = index.instance_variable_get(:@registry_mutex)
37
+ locks = [registry, *cells.map { it.instance_variable_get(:@mutex) }].sort_by(&:object_id)
38
+ acquired = []
39
+ Thread.handle_interrupt(INTERRUPT_MASK) do
40
+ locked = locks.all? do |lock|
41
+ lock.try_lock && acquired.push(lock)
42
+ end
43
+ if locked
44
+ current = index.instance_variable_get(:@entries).compact
45
+ if current.size == cells.size &&
46
+ current.each_index.all? { PortableTransaction.same?(current[it], cells[it]) }
47
+ return yield(cells)
48
+ end
49
+ end
50
+ ensure
51
+ acquired.reverse_each(&:unlock)
52
+ end
53
+ if Fiber.respond_to?(:scheduler) && Fiber.scheduler
54
+ Fiber.scheduler.kernel_sleep(0)
55
+ else
56
+ Thread.pass
57
+ end
58
+ end
59
+ end
60
+ private_class_method :with_cells
61
+
62
+ # This entry coordinates index and cell locks rather than a single storage field.
63
+ def initialize(source, working, _kind, baseline) # rubocop:disable Lint/MissingSuper
64
+ @source, @working, @baseline = source, working, baseline
65
+ @dirty = @applied = false
66
+ @index = source.instance_variable_get(:@index)
67
+ @index_lock = @index.instance_variable_get(:@lock)
68
+ end
69
+
70
+ def prepare
71
+ capture_cells
72
+ replacement = @working.instance_variable_get(:@index)
73
+ @replacement = FIELDS.map { replacement.instance_variable_get(it) }
74
+ end
75
+
76
+ def valid?
77
+ return false if @dirty && @source.frozen?
78
+ return false if @index_lock.instance_variable_get(:@reserved)
79
+ current = @index.instance_variable_get(:@entries).compact
80
+ return false unless current.size == @cells.size &&
81
+ current.each_index.all? { PortableTransaction.same?(current[it], @cells[it]) }
82
+ return false unless @cells.size == @baseline.size
83
+ return false unless @cells.all? do |cell|
84
+ next false if cell.instance_variable_get(:@reserved) || cell.instance_variable_get(:@retired)
85
+ next false unless cell.instance_variable_get(:@present)
86
+ alive, key = cell.instance_variable_get(:@key).read
87
+ alive && @baseline.key?(key) &&
88
+ PortableTransaction.same?(@baseline[key], cell.instance_variable_get(:@value))
89
+ end
90
+ @original = FIELDS.map { @index.instance_variable_get(it) }
91
+ @original_cells = @cells.map { |cell| CELL_FIELDS.map { cell.instance_variable_get(it) } }
92
+ @replacement[-1] = @original[-1] + 1
93
+ true
94
+ end
95
+
96
+ def apply
97
+ return unless @dirty
98
+ @applied = true
99
+ FIELDS.each_with_index { |field, index| @index.instance_variable_set(field, @replacement[index]) }
100
+ @cells.each do |cell|
101
+ cell.instance_variable_set(:@retired, true)
102
+ cell.instance_variable_set(:@present, false)
103
+ cell.instance_variable_set(:@key, nil)
104
+ cell.instance_variable_set(:@value, nil)
105
+ end
106
+ end
107
+
108
+ def restore
109
+ return unless @applied
110
+ FIELDS.each_with_index { |field, index| @index.instance_variable_set(field, @original[index]) }
111
+ @cells.each_with_index do |cell, index|
112
+ CELL_FIELDS.each_with_index do |field, offset|
113
+ cell.instance_variable_set(field, @original_cells[index][offset])
114
+ end
115
+ end
116
+ end
117
+
118
+ def notify
119
+ return unless @dirty
120
+ @index.change_signal.broadcast
121
+ @cells.each do |cell|
122
+ cell.instance_variable_get(:@changes)&.broadcast
123
+ cell.instance_variable_get(:@signal)&.broadcast
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def capture_cells
130
+ @cells = @index.snapshot
131
+ @locks = [@index_lock.instance_variable_get(:@mutex), @index.instance_variable_get(:@registry_mutex)]
132
+ @locks.concat(@cells.map { it.instance_variable_get(:@mutex) })
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end