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,619 @@
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
+ # Portable correctness backend for bounded maps confined to one Ractor.
9
+ # Production engines may replace it with specialized storage.
10
+ class PortableBoundedMap
11
+ INITIALIZATION_LOCK = Lock.new
12
+ private_constant :INITIALIZATION_LOCK
13
+
14
+ class HashCode
15
+ def initialize(value) = @value = value
16
+ def hash = @value
17
+
18
+ def eql?(other)
19
+ HashCode === other && Integer.instance_method(:==).bind_call(@value, other.value)
20
+ end
21
+
22
+ protected attr_reader :value
23
+ end
24
+ private_constant :HashCode
25
+
26
+ class Node
27
+ attr_accessor :key, :hash_code, :collision_index, :value, :previous, :following, :bucket
28
+
29
+ def initialize(key, hash_code, value)
30
+ @key = key
31
+ @hash_code = hash_code
32
+ @collision_index = nil
33
+ @value = value
34
+ @previous = nil
35
+ @following = nil
36
+ @bucket = nil
37
+ end
38
+ end
39
+ private_constant :Node
40
+
41
+ def initialize(
42
+ max_size:,
43
+ compare_by_identity: false,
44
+ compare_keys_by_identity: compare_by_identity,
45
+ compare_values_by_identity: compare_by_identity
46
+ )
47
+ check_initializable!
48
+ validate_boolean(compare_by_identity, "compare_by_identity")
49
+ validate_boolean(compare_keys_by_identity, "compare_keys_by_identity")
50
+ validate_boolean(compare_values_by_identity, "compare_values_by_identity")
51
+ validate_limit(max_size, "max_size")
52
+
53
+ mutex = Mutex.new
54
+ INITIALIZATION_LOCK.synchronize do
55
+ commit do
56
+ check_initializable!
57
+ @initializing = true
58
+ begin
59
+ @max_size = max_size
60
+ @compare_keys_by_identity = compare_keys_by_identity
61
+ @compare_values_by_identity = compare_values_by_identity
62
+ @buckets = {}
63
+ @size = 0
64
+ @mutex = mutex
65
+ @freeze_state = Flag.new(false)
66
+ initialize_policy
67
+ @initialized = true
68
+ ensure
69
+ remove_instance_variable(:@initializing) if instance_variable_defined?(:@initializing)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def initialize_copy(other)
76
+ super
77
+ other.send(:synchronize) do
78
+ @max_size = other.instance_variable_get(:@max_size)
79
+ @compare_keys_by_identity = other.instance_variable_get(:@compare_keys_by_identity)
80
+ @compare_values_by_identity = other.instance_variable_get(:@compare_values_by_identity)
81
+ @mutex = Mutex.new
82
+ @freeze_state = Flag.new(false)
83
+ @buckets = {}
84
+ @size = 0
85
+ initialize_policy
86
+ copy_policy_nodes(other)
87
+ end
88
+ end
89
+
90
+ def [](key)
91
+ synchronize do
92
+ node = find_node(key)
93
+ return nil unless node
94
+ unless frozen?
95
+ prepared = policy_prepare_access(node)
96
+ commit { policy_commit_access(node, prepared) }
97
+ end
98
+ node.value
99
+ end
100
+ end
101
+
102
+ def freeze
103
+ state = @freeze_state
104
+ return super unless state
105
+
106
+ state.set
107
+ self
108
+ end
109
+
110
+ def frozen?
111
+ state = @freeze_state
112
+ state ? state.value : super
113
+ end
114
+
115
+ def []=(key, value)
116
+ synchronize do
117
+ check_mutable!
118
+ if @max_size.zero?
119
+ key_hash_code(canonical_key(key))
120
+ check_mutable!
121
+ next value
122
+ end
123
+ node = find_node(key)
124
+ if node
125
+ prepared = policy_prepare_access(node)
126
+ check_mutable!
127
+ commit do
128
+ node.value = value
129
+ policy_commit_access(node, prepared)
130
+ end
131
+ else
132
+ victim = policy_victim if @size >= @max_size
133
+ stored_key = canonical_key(key)
134
+ hash_code = key_hash_code(stored_key)
135
+ node = Node.new(stored_key, hash_code, value)
136
+ prepared = policy_prepare_insert(node)
137
+ bucket = @buckets[hash_code]
138
+ replacement_bucket = bucket ? bucket.dup : []
139
+ node.collision_index = replacement_bucket.length
140
+ replacement_bucket << node
141
+ check_mutable!
142
+
143
+ commit do
144
+ # The internal hash-code keys cannot invoke user callbacks. Replacing a
145
+ # prepared collision chain happens before callback-free eviction.
146
+ @buckets[hash_code] = replacement_bucket
147
+ @size += 1
148
+ policy_commit_insert(node, prepared)
149
+ remove_node(victim) if victim
150
+ end
151
+ end
152
+ value
153
+ end
154
+ end
155
+
156
+ def fetch(*arguments)
157
+ unless arguments.length.between?(1, 2)
158
+ raise ArgumentError, "wrong number of arguments (given #{arguments.length}, expected 1..2)"
159
+ end
160
+
161
+ key, default = arguments
162
+ default_given = arguments.length == 2
163
+
164
+ warn "block supersedes default value argument", uplevel: 1 if block_given? && default_given
165
+
166
+ found, value = synchronize do
167
+ node = find_node(key)
168
+ if node
169
+ unless frozen?
170
+ prepared = policy_prepare_access(node)
171
+ commit { policy_commit_access(node, prepared) }
172
+ end
173
+ [true, node.value]
174
+ else
175
+ [false, nil]
176
+ end
177
+ end
178
+
179
+ return value if found
180
+ return yield(key) if block_given?
181
+ return default if default_given
182
+
183
+ raise KeyError.new("key not found: #{key.inspect}", receiver: self, key: key)
184
+ end
185
+
186
+ def prepare_key(key)
187
+ synchronize do
188
+ stored_key = canonical_key(key)
189
+ find_node(stored_key)
190
+ stored_key
191
+ end
192
+ end
193
+
194
+ def key?(key) = synchronize { !find_node(key).nil? }
195
+
196
+ def getkey(key)
197
+ synchronize { find_node(key)&.key }
198
+ end
199
+
200
+ def delete(key)
201
+ synchronize do
202
+ check_mutable!
203
+ node = find_node(key)
204
+ if node
205
+ check_mutable!
206
+ commit { remove_node(node).value }
207
+ end
208
+ end
209
+ end
210
+
211
+ def clear
212
+ synchronize do
213
+ check_mutable!
214
+ commit do
215
+ @buckets.clear
216
+ @size = 0
217
+ policy_clear
218
+ end
219
+ end
220
+ self
221
+ end
222
+
223
+ def max_size = synchronize { @max_size }
224
+
225
+ def max_size=(limit)
226
+ validate_limit(limit, "max_size")
227
+ synchronize do
228
+ check_mutable!
229
+ commit do
230
+ remove_victims_until(limit)
231
+ @max_size = limit
232
+ end
233
+ end
234
+ limit
235
+ end
236
+
237
+ def prune(to:)
238
+ validate_limit(to, "to")
239
+ synchronize do
240
+ check_mutable!
241
+ original_size = @size
242
+ commit { remove_victims_until(to) }
243
+ original_size - @size
244
+ end
245
+ end
246
+
247
+ def shift
248
+ synchronize do
249
+ check_mutable!
250
+ node = policy_victim
251
+ node ? commit { remove_node(node).then { [it.key, it.value] } } : nil
252
+ end
253
+ end
254
+
255
+ def empty? = synchronize { @size.zero? }
256
+ def size = synchronize { @size }
257
+ alias length size
258
+
259
+ def compare_keys_by_identity? = synchronize { @compare_keys_by_identity }
260
+ def compare_values_by_identity? = synchronize { @compare_values_by_identity }
261
+
262
+ def each(&block)
263
+ return enum_for(__method__) unless block_given?
264
+ snapshot.each { block.call(*it) }
265
+ self
266
+ end
267
+ alias each_pair each
268
+
269
+ def each_key(&block)
270
+ return enum_for(__method__) unless block_given?
271
+ snapshot.each { block.call(it.first) }
272
+ self
273
+ end
274
+
275
+ def each_value(&block)
276
+ return enum_for(__method__) unless block_given?
277
+ snapshot.each { block.call(it.last) }
278
+ self
279
+ end
280
+
281
+ def keys = snapshot.map!(&:first).freeze
282
+
283
+ private
284
+
285
+ def check_initializable!
286
+ if instance_variable_defined?(:@initializing) || instance_variable_defined?(:@initialized)
287
+ raise "bounded map is already initialized"
288
+ end
289
+ raise FrozenError, "can't modify frozen #{self.class}" if frozen?
290
+ end
291
+
292
+ def synchronize(&)
293
+ raise "bounded map is not initialized" unless instance_variable_defined?(:@initialized)
294
+
295
+ @mutex.synchronize(&)
296
+ end
297
+
298
+ def check_mutable! = Internal::Freeze.check(self)
299
+
300
+ def find_node(key)
301
+ hash_code = key_hash_code(key)
302
+ @buckets[hash_code]&.find { keys_equal?(it.key, key) }
303
+ end
304
+
305
+ def primitive_id(value) = BasicObject.instance_method(:__id__).bind_call(value)
306
+
307
+ def key_hash_code(key)
308
+ hash = @compare_keys_by_identity ? primitive_id(key) : key.hash
309
+ hash = Integer.try_convert(hash) unless Integer === hash
310
+ raise TypeError, "hash value must be an Integer" unless hash
311
+ HashCode.new(hash)
312
+ end
313
+
314
+ def keys_equal?(stored, probe)
315
+ identical = BasicObject.instance_method(:equal?).bind_call(stored, probe)
316
+ return true if identical
317
+ !@compare_keys_by_identity && stored.eql?(probe)
318
+ end
319
+
320
+ def canonical_key(key) = !@compare_keys_by_identity && String === key ? canonical_string(key) : key
321
+
322
+ def canonical_string(string)
323
+ klass = Object.instance_method(:class).bind_call(string)
324
+ if BasicObject.instance_method(:equal?).bind_call(klass, String)
325
+ return String.instance_method(:-@).bind_call(string)
326
+ end
327
+
328
+ copy = Class.instance_method(:allocate).bind_call(klass)
329
+ String.instance_method(:replace).bind_call(copy, string)
330
+ Object.instance_method(:instance_variables).bind_call(string).each do |name|
331
+ value = Object.instance_method(:instance_variable_get).bind_call(string, name)
332
+ Object.instance_method(:instance_variable_set).bind_call(copy, name, value)
333
+ end
334
+ Object.instance_method(:freeze).bind_call(copy)
335
+ end
336
+
337
+ def remove_node(node)
338
+ policy_remove(node)
339
+ bucket = @buckets.fetch(node.hash_code)
340
+ index = node.collision_index
341
+ last = bucket.pop
342
+ unless BasicObject.instance_method(:equal?).bind_call(last, node)
343
+ bucket[index] = last
344
+ last.collision_index = index
345
+ end
346
+ node.collision_index = nil
347
+ @buckets.delete(node.hash_code) if bucket.empty?
348
+ @size -= 1
349
+ node
350
+ end
351
+
352
+ def copy_node(node)
353
+ copy = Node.new(node.key, node.hash_code, node.value)
354
+ bucket = (@buckets[node.hash_code] ||= [])
355
+ copy.collision_index = bucket.length
356
+ bucket << copy
357
+ @size += 1
358
+ copy
359
+ end
360
+
361
+ def remove_victims_until(target)
362
+ remove_node(policy_victim) while @size > target
363
+ end
364
+
365
+ def snapshot
366
+ synchronize do
367
+ @buckets.each_value.flat_map { |bucket| bucket.map { [it.key, it.value] } }
368
+ end
369
+ end
370
+
371
+ def validate_boolean(value, name)
372
+ equal = BasicObject.instance_method(:equal?)
373
+ return if equal.bind_call(value, true) || equal.bind_call(value, false)
374
+ raise ArgumentError, "#{name} must be true or false"
375
+ end
376
+
377
+ def validate_limit(value, name)
378
+ raise TypeError, "#{name} must be an Integer" unless Integer === value
379
+ raise ArgumentError, "#{name} must be non-negative" if value.negative?
380
+ end
381
+
382
+ def commit(&) = Thread.handle_interrupt(INTERRUPT_MASK, &)
383
+
384
+ # Policy hooks are deliberately separate from the public storage protocol.
385
+ def initialize_policy = raise NotImplementedError
386
+ def policy_clear = raise NotImplementedError
387
+ def policy_commit_access(node, prepared) = raise NotImplementedError
388
+ def policy_commit_insert(node, prepared) = raise NotImplementedError
389
+ def policy_prepare_access(node) = raise NotImplementedError
390
+ def policy_prepare_insert(node) = raise NotImplementedError
391
+ def policy_remove(node) = raise NotImplementedError
392
+ def policy_victim = raise NotImplementedError
393
+ def copy_policy_nodes(_other) = raise NotImplementedError
394
+ end
395
+
396
+ class PortableLRUMap < PortableBoundedMap
397
+ private
398
+
399
+ def initialize_policy
400
+ @least_recent = nil
401
+ @most_recent = nil
402
+ end
403
+
404
+ def copy_policy_nodes(other)
405
+ node = other.instance_variable_get(:@least_recent)
406
+ while node
407
+ append(copy_node(node))
408
+ node = node.following
409
+ end
410
+ end
411
+
412
+ def policy_prepare_access(_node) = nil
413
+ def policy_prepare_insert(_node) = nil
414
+
415
+ def policy_commit_access(node, _prepared)
416
+ return if node.equal?(@most_recent)
417
+ unlink(node)
418
+ append(node)
419
+ end
420
+
421
+ def policy_commit_insert(node, _prepared) = append(node)
422
+
423
+ def policy_remove(node) = unlink(node)
424
+ def policy_victim = @least_recent
425
+
426
+ def policy_clear
427
+ @least_recent = nil
428
+ @most_recent = nil
429
+ end
430
+
431
+ def append(node)
432
+ node.previous = @most_recent
433
+ node.following = nil
434
+ @most_recent.following = node if @most_recent
435
+ @least_recent ||= node
436
+ @most_recent = node
437
+ end
438
+
439
+ def unlink(node)
440
+ if node.previous
441
+ node.previous.following = node.following
442
+ else
443
+ @least_recent = node.following
444
+ end
445
+ if node.following
446
+ node.following.previous = node.previous
447
+ else
448
+ @most_recent = node.previous
449
+ end
450
+ node.previous = nil
451
+ node.following = nil
452
+ end
453
+ end
454
+
455
+ # Adds the public strict-map key and value contract to portable storage.
456
+ module PortableStrictBoundedMap
457
+ def [](key) = super(strict_key(key))
458
+
459
+ def []=(key, value)
460
+ super(strict_key(key), strict_value(value))
461
+ end
462
+
463
+ def fetch(*arguments, &)
464
+ strict_key(arguments.first) unless arguments.empty?
465
+ super
466
+ end
467
+
468
+ def prepare_key(key) = super(strict_key(key))
469
+ def key?(key) = super(strict_key(key))
470
+ def getkey(key) = super(strict_key(key))
471
+ def delete(key) = super(strict_key(key))
472
+
473
+ private
474
+
475
+ def strict_key(key)
476
+ key = canonical_key(key)
477
+ return key if Ractor.shareable?(key)
478
+ raise Ractor::IsolationError, "key must be Ractor-shareable"
479
+ end
480
+
481
+ def strict_value(value)
482
+ return value unless Internal.native_ractors?
483
+ return value if Ractor.shareable?(value)
484
+ raise Ractor::IsolationError, "value must be Ractor-shareable"
485
+ end
486
+ end
487
+ private_constant :PortableStrictBoundedMap
488
+
489
+ class PortableStrictLRUMap < PortableLRUMap
490
+ include PortableStrictBoundedMap
491
+ end
492
+
493
+ class PortableLFUMap < PortableBoundedMap
494
+ class Bucket
495
+ attr_accessor :frequency, :previous, :following, :least_recent, :most_recent
496
+
497
+ def initialize(frequency)
498
+ @frequency = frequency
499
+ @previous = nil
500
+ @following = nil
501
+ @least_recent = nil
502
+ @most_recent = nil
503
+ end
504
+
505
+ def empty? = least_recent.nil?
506
+ end
507
+ private_constant :Bucket
508
+
509
+ private
510
+
511
+ def initialize_policy
512
+ @least_frequent = nil
513
+ end
514
+
515
+ def copy_policy_nodes(other)
516
+ source_bucket = other.instance_variable_get(:@least_frequent)
517
+ previous = nil
518
+ while source_bucket
519
+ bucket = Bucket.new(source_bucket.frequency)
520
+ bucket.previous = previous
521
+ previous.following = bucket if previous
522
+ @least_frequent ||= bucket
523
+ node = source_bucket.least_recent
524
+ while node
525
+ append_to_bucket(copy_node(node), bucket)
526
+ node = node.following
527
+ end
528
+ previous = bucket
529
+ source_bucket = source_bucket.following
530
+ end
531
+ end
532
+
533
+ def policy_prepare_insert(_node)
534
+ return @least_frequent if @least_frequent&.frequency == 1
535
+ Bucket.new(1)
536
+ end
537
+
538
+ def policy_commit_insert(node, bucket)
539
+ unless bucket.equal?(@least_frequent)
540
+ bucket.following = @least_frequent
541
+ @least_frequent.previous = bucket if @least_frequent
542
+ @least_frequent = bucket
543
+ end
544
+ append_to_bucket(node, bucket)
545
+ end
546
+
547
+ def policy_prepare_access(node)
548
+ source = node.bucket
549
+ frequency = source.frequency + 1
550
+ following = source.following
551
+ return following if following&.frequency == frequency
552
+ Bucket.new(frequency)
553
+ end
554
+
555
+ def policy_commit_access(node, destination)
556
+ source = node.bucket
557
+ unless destination.equal?(source.following)
558
+ destination.previous = source
559
+ destination.following = source.following
560
+ source.following.previous = destination if source.following
561
+ source.following = destination
562
+ end
563
+ unlink_from_bucket(node)
564
+ remove_bucket(source) if source.empty?
565
+ append_to_bucket(node, destination)
566
+ end
567
+
568
+ def policy_remove(node)
569
+ bucket = node.bucket
570
+ unlink_from_bucket(node)
571
+ remove_bucket(bucket) if bucket.empty?
572
+ end
573
+
574
+ def policy_victim = @least_frequent&.least_recent
575
+ def policy_clear = @least_frequent = nil
576
+
577
+ def append_to_bucket(node, bucket)
578
+ node.bucket = bucket
579
+ node.previous = bucket.most_recent
580
+ node.following = nil
581
+ bucket.most_recent.following = node if bucket.most_recent
582
+ bucket.least_recent ||= node
583
+ bucket.most_recent = node
584
+ end
585
+
586
+ def unlink_from_bucket(node)
587
+ bucket = node.bucket
588
+ if node.previous
589
+ node.previous.following = node.following
590
+ else
591
+ bucket.least_recent = node.following
592
+ end
593
+ if node.following
594
+ node.following.previous = node.previous
595
+ else
596
+ bucket.most_recent = node.previous
597
+ end
598
+ node.previous = nil
599
+ node.following = nil
600
+ node.bucket = nil
601
+ end
602
+
603
+ def remove_bucket(bucket)
604
+ if bucket.previous
605
+ bucket.previous.following = bucket.following
606
+ else
607
+ @least_frequent = bucket.following
608
+ end
609
+ bucket.following.previous = bucket.previous if bucket.following
610
+ bucket.previous = nil
611
+ bucket.following = nil
612
+ end
613
+ end
614
+
615
+ class PortableStrictLFUMap < PortableLFUMap
616
+ include PortableStrictBoundedMap
617
+ end
618
+ end
619
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Internal
7
+ class ProxyOwner
8
+ include ProxyOwnerNotifications
9
+
10
+ class OwnerScheduler < Farce::ThreadScheduler
11
+ def initialize
12
+ @threads = Thread::Queue.new
13
+ super
14
+ end
15
+
16
+ def schedule(*, **, &)
17
+ @threads << Thread.new(*, &)
18
+ self
19
+ end
20
+
21
+ def join
22
+ @threads.pop.join until @threads.empty?
23
+ end
24
+ end
25
+ private_constant :OwnerScheduler
26
+
27
+ def self.create_scheduler
28
+ Storage[:local_scheduler] = OwnerScheduler.new
29
+ end
30
+
31
+ def self.drain_scheduler(scheduler) = scheduler.join
32
+
33
+ def self.current = Storage.store_if_absent(self) { new }
34
+
35
+ def initialize
36
+ super
37
+ Ractor.current.monitor(self)
38
+ end
39
+
40
+ def <<(_) = stop
41
+ end
42
+ end
43
+ end