farce 0.0.1.alpha2-x64-mingw-ucrt

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,60 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # A Ractor-shareable record with independently atomic fields.
7
+ # Values use the selected transfer mode, just like {Farce::Atom}.
8
+ # An explicitly supplied atom keeps its own settings.
9
+ #
10
+ # This class is an alternative to Ruby's Struct or Data classes.
11
+ #
12
+ # @example Updating a named field atomically
13
+ # Job = Farce::Molecule.define(:status, :attempts)
14
+ # job = Job.new(status: :pending, attempts: 0)
15
+ # job.attempts_atom.update { |count| count + 1 }
16
+ # job.status = :running
17
+ # job.to_h # => { status: :running, attempts: 1 }
18
+ class Molecule < Farce::Abstract::Molecule
19
+ include Shareable
20
+
21
+ # Create a record class with defaults for newly created atoms.
22
+ # @!macro modes
23
+ # @param fields [Array<Symbol, String>] the field names
24
+ # @param mode [Symbol, nil] the default transfer mode, or nil for :copy
25
+ # @param compare_by_identity [Boolean, nil] the default comparison policy
26
+ # @return [Class<Molecule>]
27
+ def self.define(*fields, mode: nil, **, &)
28
+ subclass = super(*fields, **)
29
+ subclass.class_eval "def self.default_mode = #{mode.to_sym.inspect}", __FILE__, __LINE__ unless nil.equal?(mode)
30
+ subclass.class_eval(&) if block_given?
31
+ subclass
32
+ end
33
+
34
+ # @return [Symbol] the default transfer mode for new records
35
+ def self.default_mode = :copy
36
+
37
+ # @!visibility private
38
+ def self.define_member(field)
39
+ return super unless field == :mode
40
+ raise ArgumentError, "#{field.inspect} is not a valid field name"
41
+ end
42
+ private_class_method :define_member
43
+
44
+ # Initialize fields and optionally override the record class defaults.
45
+ # @!macro modes
46
+ # @param mode [Symbol, nil] the transfer mode, or nil for the class default
47
+ # @param compare_by_identity [Boolean, nil] the comparison policy, or nil for the class default
48
+ # @see Abstract::Molecule#initialize
49
+ def initialize(*, mode: nil, **)
50
+ @mode = mode&.to_sym || self.class.default_mode
51
+ super(*, **)
52
+ end
53
+
54
+ # The default mode used to transfer values between Ractors.
55
+ # @return [Symbol]
56
+ attr_reader :mode
57
+
58
+ private def create_atom(_, value) = Atom.new(value, mode:, compare_by_identity: compare_by_identity?)
59
+ end
60
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # A wrapper making generic Ruby objects shareable while preserving mutability.
7
+ #
8
+ # It achieves this by keeping an immutable copy of the object,
9
+ # and performing an atomic update of this reference for mutating method calls.
10
+ #
11
+ # This means the entire object is copied whenever it is being mutated.
12
+ #
13
+ # You should therefore favor dedicated data structures whenever possible,
14
+ # like using a {Map} instead of wrapping a Hash in a {Mutable}, or a {Vector}
15
+ # instead of doing the same with an Array.
16
+ #
17
+ # Non-mutating calls do not incur this cost.
18
+ #
19
+ # Use a transaction view to stage mutations alongside changes to other participants.
20
+ # The original value changes only when the transaction commits.
21
+ #
22
+ # @example Updating two strings together
23
+ # first = Farce::Mutable.new("queued")
24
+ # second = Farce::Mutable.new("waiting")
25
+ # Farce.transaction(first, second) do |_, first_view, second_view|
26
+ # first_view.replace("running")
27
+ # second_view << " for worker"
28
+ # end
29
+ #
30
+ # @example
31
+ # shareable_string = Farce::Mutable.new("foo")
32
+ # Ractor.new(shareable_string) { it << "bar" }.join
33
+ # shareable_string.to_s # => "foobar"
34
+ class Mutable < BasicObject
35
+ module Inherited
36
+ include ::Farce.const_get(:Internal)::Delegation
37
+ include ::Farce.const_get(:Internal)::Inspect
38
+
39
+ define_method(:dup, ::Kernel.instance_method(:dup))
40
+ define_method(:clone, ::Kernel.instance_method(:clone))
41
+ define_method(:is_a?, ::Kernel.instance_method(:is_a?))
42
+ define_method(:initialize_copy, ::Kernel.instance_method(:initialize_copy))
43
+ define_method(:initialize_dup, ::Kernel.instance_method(:initialize_dup))
44
+ define_method(:initialize_clone, ::Kernel.instance_method(:initialize_clone))
45
+
46
+ private :initialize_copy, :initialize_dup, :initialize_clone
47
+ end
48
+
49
+ module Mutator
50
+ extend self
51
+
52
+ def prepare(value, in_place: false)
53
+ return -value if value.is_a? String
54
+ return value if value.frozen? || (Internal.native_ractors? && Ractor.shareable?(value))
55
+ in_place ? value.freeze : value.dup.freeze
56
+ end
57
+
58
+ def deref(mutable) = ::Kernel.instance_method(:instance_variable_get).bind_call(mutable, :@atom).value
59
+ end
60
+
61
+ private_constant :Mutator, :Inherited
62
+ include ::Farce::Shareable::Delegated
63
+ include Inherited
64
+
65
+ # Turns the given {Mutable} into a frozen copy of the underlying object.
66
+ # @param mutable [Mutable] the mutable
67
+ # @return [BasicObject] current snapshot of the object it is wrapping
68
+ def self.deref(mutable) = Mutable === mutable ? Mutator.deref(mutable) : mutable
69
+
70
+ # Creates a subclass of {Mutable} that automatically generates values based on the given factory (usually a class),
71
+ # by invoking `new` on it with any arguments being passed on.
72
+ #
73
+ # @example
74
+ # # You should probably use Farce::Vector instead.
75
+ # MutableArray = Farce::Mutable[Array]
76
+ # MutableArray.new(2) # => #<MutableArray [nil, nil]>
77
+ #
78
+ # @param factory [#new] The factory to create a subclass for. Typically a class. Must be shareable.
79
+ # @return [Class] The subclass of {Mutable}.
80
+ def self.[](factory)
81
+ raise ::Farce::Ractor::IsolationError, "factory is not shareable" unless ::Farce::Ractor.shareable?(factory)
82
+
83
+ klass = ::Class.new(self)
84
+ klass.set_temporary_name("#{name}[#{factory.name}]")
85
+ klass.instance_variable_set(:@value_factory, factory)
86
+
87
+ klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
88
+ def self.new(...) = super(value_factory.new(...))
89
+ def self.value_factory
90
+ return @value_factory if defined?(@value_factory) && @value_factory
91
+ superclass.value_factory
92
+ end
93
+ RUBY
94
+
95
+ klass.singleton_class.class_eval "undef []", __FILE__, __LINE__
96
+ klass
97
+ end
98
+
99
+ # Creates a new mutable version of the given object.
100
+ # The object will be copied on initialization if it isn't frozen.
101
+ # A frozen version of the object must be Ractor-shareable.
102
+ # @param object [BasicObject] The object to wrap. Must implement `dup`, `freeze`, and `frozen?`.
103
+ def initialize(object)
104
+ @atom = Internal::Atom.new Mutator.prepare(object)
105
+ super()
106
+ end
107
+
108
+ # @api private
109
+ def transaction_wrapper(transaction) = Transaction::Mutable.new(transaction, self, @atom)
110
+
111
+ # @api private
112
+ def marshal_dump = ::Kernel.raise(::TypeError, "Farce::Mutable cannot be marshaled")
113
+
114
+ # Marshal hook discovery must inspect the wrapper without delegating to its snapshot.
115
+ def respond_to?(name, include_private = false) # rubocop:disable Style/OptionalBooleanParameter
116
+ return true if name == :marshal_dump || name == :transaction_wrapper
117
+ return false if Internal.marshal_protocol_method?(name)
118
+ method_missing(:respond_to?, name, include_private)
119
+ end
120
+
121
+ # @api private
122
+ def is_a?(...) = super || @atom.value.is_a?(...)
123
+
124
+ # @api private
125
+ def inspect_with(inspector)
126
+ value = @atom.value
127
+ klass = ::Kernel.instance_method(:class).bind_call(self)
128
+ name = klass.name
129
+ name += "[#{value.class.name}]" if klass == ::Farce::Mutable
130
+ inspector.group("#<#{name}", ">") do
131
+ inspector.breakable
132
+ inspector.object(value)
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ # Delegates all methods to the frozen copy of the current value.
139
+ # If it throws a `FrozenError` it reruns the method against an unfrozen duplicate of the value,
140
+ # freezes it, and uses that as internal value.
141
+ def method_missing(...) # rubocop:disable Style/MissingRespondToMissing
142
+ @atom.value.__send__(...)
143
+ rescue ::FrozenError
144
+ result = nil
145
+ @atom.update do |current|
146
+ copy = current.dup
147
+ result = copy.__send__(...)
148
+ result = self if result.equal?(copy)
149
+ Mutator.prepare(copy, in_place: true)
150
+ end
151
+ result
152
+ end
153
+
154
+ def freeze_backend = @atom
155
+
156
+ def initialize_copy(other)
157
+ super
158
+ @atom = Internal::Atom.new(Mutator.deref(other))
159
+ end
160
+
161
+ def initialize_dup(other)
162
+ super
163
+ publish_shareable_copy(other, operation: :dup)
164
+ end
165
+
166
+ def initialize_clone(other, freeze: nil)
167
+ super
168
+ publish_shareable_copy(other, operation: :clone, freeze:)
169
+ end
170
+ end
171
+ end
data/lib/farce/pool.rb ADDED
@@ -0,0 +1,332 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # A dynamically sized pool of Ractor schedulers.
7
+ #
8
+ # Tasks wait in one shared queue. An empty pool starts its first worker immediately
9
+ # when work arrives. A task that remains queued for `grow_after`
10
+ # causes the pool to add a worker until `max_size` is reached. Workers above
11
+ # `min_size` retire after `shrink_after` without queued or running work.
12
+ # By default, the pool starts with no workers and can shrink back to zero.
13
+ #
14
+ # Most importantly, it exposes a {#schedule} method compatible with {Scheduler#schedule}.
15
+ #
16
+ # @example
17
+ # # Create a new pool
18
+ # pool = Farce::Pool.new
19
+ #
20
+ # # Schedule some work on the pool.
21
+ # pool.schedule do
22
+ # # The work runs in a non-blocking fiber, multiple tasks can run concurrently even on the same worker.
23
+ # loop do
24
+ # sleep 5
25
+ # MyClass.recurring_work
26
+ # end
27
+ # end
28
+ #
29
+ # @example Custom Fiber scheduler
30
+ # require "farce"
31
+ # require "carbon_fiber"
32
+ #
33
+ # # At least two Ractors, up to four. Launch a new one if a task waits longer than 100 milliseconds.
34
+ # # Use the fiber scheduler from the carbon_fiber gem.
35
+ # pool = Farce::Pool.new(min_size: 2, max_size: 4, grow_after: 0.1) { CarbonFiber::Scheduler.new }
36
+ #
37
+ # # Schedule some work
38
+ # pool.schedule { MyClass.do_something }
39
+ class Pool < Farce::Abstract::Scheduler
40
+ include Internal::MarshalSupport::Reject
41
+ include Shareable::Unfreezable
42
+ include Internal::Inspect
43
+
44
+ # @return [Float] Maximum queue wait before adding a worker.
45
+ attr_reader :grow_after
46
+
47
+ # @return [Integer, nil] Soft task limit for each worker.
48
+ attr_reader :max_inflight
49
+
50
+ # @return [Integer] Maximum worker count.
51
+ attr_reader :max_size
52
+
53
+ # @return [Integer] Minimum worker count.
54
+ attr_reader :min_size
55
+
56
+ # @return [Float, nil] Idle time before an extra worker retires.
57
+ attr_reader :shrink_after
58
+
59
+ # Create and start an elastic pool using the configured fiber scheduler.
60
+ # An explicit constructor block overrides the configured default.
61
+ #
62
+ # `max_inflight` is a soft limit. A worker may exceed it when its scheduler
63
+ # has no runnable fibers or when the pool has reached `max_size`.
64
+ #
65
+ # @param min_size [Integer] Workers started immediately and retained while open.
66
+ # @param max_size [Integer] Maximum workers allowed.
67
+ # @param max_inflight [Integer, nil] Soft task limit for each worker.
68
+ # @param grow_after [Numeric] Queue wait before adding another worker.
69
+ # @param shrink_after [Numeric, nil] Idle time before an extra worker retires.
70
+ # @param capacity [Integer, nil] Pending-task capacity.
71
+ # @param backend [Symbol] IO backend for the built-in scheduler.
72
+ # @yieldreturn [Object] Fiber scheduler constructed in each worker.
73
+ def initialize(min_size: 0, max_size: 4, max_inflight: 64,
74
+ grow_after: 0.005, shrink_after: 30, capacity: 1024,
75
+ backend: Internal::FROZEN_CONFIG.io_backend, &constructor)
76
+ constructor ||= Internal::FROZEN_CONFIG.fiber_scheduler_constructor
77
+ @min_size = Integer(min_size)
78
+ @max_size = Integer(max_size)
79
+ @max_inflight = max_inflight && Integer(max_inflight)
80
+ @grow_after = duration(grow_after, :grow_after)
81
+ @shrink_after = duration(shrink_after, :shrink_after, nil_allowed: true)
82
+ validate_sizes
83
+
84
+ @backend = backend
85
+ @constructor = constructor
86
+ @constructor = Ractor.shareable_proc(&constructor) if constructor && !Ractor.shareable?(constructor)
87
+ @queue = Internal::Queue.new(capacity: capacity && Integer(capacity), track_age: true)
88
+ @state = Internal::Atom.new(:running)
89
+ @error = Internal::Atom.new
90
+ @worker_count = Internal::Atom.new(0)
91
+ @pressure = Internal::Atom.new
92
+ @admission = Internal::Signal.new
93
+ super()
94
+ begin
95
+ @min_size.times { start_worker }
96
+ rescue Exception # rubocop:disable Lint/RescueException
97
+ close
98
+ raise
99
+ end
100
+ end
101
+
102
+ # Enqueue a task for any worker in the pool.
103
+ #
104
+ # Pools have no single owner, so `auto_local` does not select local transfer.
105
+ # Explicit local transfer is rejected because another Ractor may run the task.
106
+ #
107
+ # @!macro modes
108
+ # @param args [Array<Object>] Positional arguments passed to the task block.
109
+ # @param mode [Symbol] Argument transfer mode.
110
+ # @param auto_local [Boolean] Accepted for compatibility with {Scheduler#schedule}.
111
+ # @yield [*args] Task to execute.
112
+ # @return [Pool] self.
113
+ # @raise [PoolClosedError] If the pool is closing or closed.
114
+ def schedule(*args, mode: :copy, auto_local: true, &block) # rubocop:disable Lint/UnusedMethodArgument
115
+ raise PoolClosedError, "cannot schedule task on a closed pool" if closed?
116
+ raise ArgumentError, "local transfer is not supported by a pool" if mode == :local
117
+
118
+ task = Internal::ScheduledTask.new(args, block, mode)
119
+ @queue.push(task)
120
+ arm_scaler
121
+ self
122
+ rescue ClosedQueueError
123
+ raise unless closed?
124
+ raise PoolClosedError, "cannot schedule task on a closed pool"
125
+ end
126
+
127
+ # Stop accepting tasks and drain queued work.
128
+ #
129
+ # @return [Pool] self.
130
+ def close
131
+ return self if closed?
132
+ @state.compare_and_set(:running, :closing)
133
+ @queue.seal
134
+ admission_changed
135
+ if size.zero?
136
+ @queue.closed? ? finish_close : start_worker(drain: true)
137
+ end
138
+ self
139
+ end
140
+
141
+ # Return the first worker error, if any.
142
+ def error
143
+ value = @error.value
144
+ value.is_a?(Envelope) ? value.value : value
145
+ end
146
+
147
+ # Return the current number of running and starting workers.
148
+ def size = @worker_count.value
149
+
150
+ # Return the current pool state.
151
+ def state = @state.value
152
+
153
+ # Return whether the pool rejects new tasks.
154
+ def closed? = state != :running
155
+
156
+ # Return whether the pool is draining queued work.
157
+ def closing? = state == :closing
158
+
159
+ # Return whether closing workers have drained all admitted submissions.
160
+ # @api private
161
+ def drained? = closing? && @queue.closed?
162
+
163
+ # Return whether all configured workers have been started.
164
+ def at_max_size? = size >= @max_size
165
+
166
+ # Return the generation used by workers waiting for admission.
167
+ # @api private
168
+ def admission_generation = @admission.generation
169
+
170
+ # Wait for worker capacity or a pool state change.
171
+ # @api private
172
+ def wait_for_admission(generation) = @admission.wait(generation)
173
+
174
+ # Wake workers waiting at their soft admission limit.
175
+ # @api private
176
+ def admission_changed = @admission.broadcast
177
+
178
+ # Record that a task left the shared queue.
179
+ # @api private
180
+ def task_taken
181
+ return if @pressure.value.nil? || at_max_size?
182
+ return unless queue_empty?
183
+ @pressure.value = nil
184
+ arm_scaler if @queue.size.positive?
185
+ end
186
+
187
+ # Reserve a worker retirement above the minimum size.
188
+ # @api private
189
+ def retire_worker
190
+ retired = false
191
+ @worker_count.update do |count|
192
+ if state == :running && count > @min_size && queue_empty?
193
+ retired = true
194
+ count - 1
195
+ else
196
+ count
197
+ end
198
+ end
199
+ retired
200
+ end
201
+
202
+ # Remove a stopped worker and preserve its failure.
203
+ # @api private
204
+ def worker_stopped(error, replace: true)
205
+ @worker_count.update { |count| count - 1 }
206
+ @error.compare_and_set(nil, Envelope.new(error)) if error
207
+ if closing?
208
+ if size.zero?
209
+ error && !drained? ? start_worker(drain: true) : finish_close
210
+ end
211
+ elsif state == :running && replace
212
+ begin
213
+ start_worker while size < @min_size
214
+ rescue Exception # rubocop:disable Lint/RescueException
215
+ nil
216
+ end
217
+ arm_scaler if @queue.size.positive?
218
+ end
219
+ admission_changed
220
+ end
221
+
222
+ # Recheck delayed queue pressure and grow by one worker.
223
+ # @api private
224
+ def scale_if_needed(started)
225
+ return unless state == :running && @pressure.value == started
226
+ age = @queue.oldest_age
227
+ return clear_pressure(started) unless age
228
+ return reschedule_scaler(started, @grow_after - age) if age < @grow_after
229
+
230
+ start_worker unless at_max_size?
231
+ return clear_pressure(started) if queue_empty? || at_max_size?
232
+
233
+ next_started = @queue.generation
234
+ schedule_scaler(next_started) if @pressure.compare_and_set(started, next_started)
235
+ rescue Exception => e # rubocop:disable Lint/RescueException
236
+ @error.compare_and_set(nil, Envelope.new(e))
237
+ end
238
+
239
+ # @api private
240
+ def inspect_with(inspector)
241
+ super do
242
+ inspector.attribute(:state, state)
243
+ inspector.attribute(:backend, @backend)
244
+ inspector.attribute(:size, size)
245
+ inspector.attribute(:min_size, @min_size)
246
+ inspector.attribute(:max_size, @max_size)
247
+ inspector.attribute(:max_inflight, @max_inflight) if @max_inflight
248
+ yield if block_given?
249
+ end
250
+ end
251
+
252
+ private
253
+
254
+ def validate_sizes
255
+ raise ArgumentError, "min_size must be non-negative" if @min_size.negative?
256
+ raise ArgumentError, "max_size must be positive" unless @max_size.positive?
257
+ raise ArgumentError, "min_size must not exceed max_size" if @min_size > @max_size
258
+ raise ArgumentError, "max_inflight must be positive" if @max_inflight && !@max_inflight.positive?
259
+ end
260
+
261
+ def duration(value, name, nil_allowed: false)
262
+ return if value.nil? && nil_allowed
263
+ value = Float(value)
264
+ raise ArgumentError, "#{name} must be finite and non-negative" if !value.finite? || value.negative?
265
+ value
266
+ end
267
+
268
+ def start_worker(drain: false, only_if_empty: false)
269
+ reserved = false
270
+ @worker_count.update do |count|
271
+ can_start = state == :running || (drain && closing? && !queue_empty?)
272
+ if can_start && count < @max_size && (!only_if_empty || count.zero?)
273
+ reserved = true
274
+ count + 1
275
+ else
276
+ count
277
+ end
278
+ end
279
+ return false unless reserved
280
+
281
+ worker = Internal::PoolWorker.new(self)
282
+ scheduler = Scheduler.new(
283
+ backend: @backend,
284
+ queue: @queue,
285
+ pool_worker: worker,
286
+ queue_owner: false,
287
+ &@constructor
288
+ )
289
+ scheduler.launch_ractor
290
+ admission_changed if at_max_size?
291
+ true
292
+ rescue Exception => e # rubocop:disable Lint/RescueException
293
+ worker ? worker.stopped(e, replace: false) : @worker_count.update { |count| count - 1 }
294
+ raise
295
+ end
296
+
297
+ def arm_scaler
298
+ return if at_max_size? || queue_empty? || state != :running
299
+ start_worker(only_if_empty: true) if size.zero?
300
+ return if at_max_size?
301
+
302
+ started = @queue.generation
303
+ age = @queue.oldest_age
304
+
305
+ return unless age
306
+ return unless @pressure.compare_and_set(nil, started)
307
+
308
+ remaining = @grow_after - age
309
+ remaining = 0 if remaining.negative?
310
+
311
+ schedule_scaler(started, remaining)
312
+ end
313
+
314
+ def schedule_scaler(started, delay = @grow_after)
315
+ Internal::PoolSupervisor.schedule(self, started, delay)
316
+ end
317
+
318
+ def reschedule_scaler(started, delay)
319
+ next_started = @queue.generation
320
+ schedule_scaler(next_started, delay) if @pressure.compare_and_set(started, next_started)
321
+ end
322
+
323
+ def clear_pressure(started)
324
+ return unless @pressure.compare_and_set(started, nil)
325
+ arm_scaler if @queue.size.positive?
326
+ end
327
+
328
+ def queue_empty? = @queue.size.zero? # rubocop:disable Style/ZeroLengthPredicate
329
+
330
+ def finish_close = @state.compare_and_set(:closing, :closed)
331
+ end
332
+ end
data/lib/farce/port.rb ADDED
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # @!parse
7
+ # class Port < Farce::Ractor::Port
8
+ # end
9
+ #
10
+ # `Ractor::Port` subclass with additional features, namely {#owned? ownership tracking} and
11
+ # {#send mode based sending}.
12
+ class Port < Internal::Port
13
+ include Abstract::Port
14
+
15
+ MANAGER = ModeManager.new
16
+ SUBCLASSES = Farce::MODES.to_h do |mode|
17
+ if mode == :copy
18
+ normal = self
19
+ else
20
+ normal = Class.new(self)
21
+ normal.set_temporary_name("#{name}[#{mode.inspect}]")
22
+ normal.class_eval "def self.mode = #{mode.inspect}", __FILE__, __LINE__
23
+ end
24
+
25
+ auto_local = Class.new(normal)
26
+ auto_local.set_temporary_name("#{name}[#{mode.inspect}, auto_local: true]")
27
+ auto_local.class_eval "def self.auto_local? = true", __FILE__, __LINE__
28
+ [mode, [normal, auto_local].freeze]
29
+ end.freeze
30
+
31
+ private_constant :MANAGER, :SUBCLASSES
32
+
33
+ # @api private
34
+ def self.[](mode, auto_local: false)
35
+ if self == Port
36
+ return SUBCLASSES.dig(mode, auto_local ? 1 : 0) || raise(ArgumentError, "invalid mode: #{mode.inspect}")
37
+ end
38
+ raise ArgumentError, "mode mismatch: #{mode.inspect} vs #{self.mode.inspect}" if mode != self.mode
39
+ return self if auto_local? == auto_local
40
+ raise ArgumentError, "auto_local mismatch: #{auto_local.inspect} vs #{auto_local?.inspect}"
41
+ end
42
+
43
+ # @api private
44
+ def self.mode = :copy
45
+
46
+ # @api private
47
+ def self.auto_local? = false
48
+
49
+ # @!visibility private
50
+ # (see #initialize)
51
+ def self.new(mode: nil, auto_local: nil)
52
+ return super() if mode.nil? && auto_local.nil?
53
+ auto_local = auto_local? if auto_local.nil?
54
+ self[mode || self.mode, auto_local: auto_local].new
55
+ end
56
+
57
+ # @!method initialize(mode: :copy, auto_local: false)
58
+ # Creates a new port with the given default mode.
59
+ #
60
+ # @!macro modes
61
+ #
62
+ # @param mode [Symbol]
63
+ # The default mode to use when sending values through this port.
64
+ #
65
+ # @param auto_local [Boolean]
66
+ # Whether to automatically use `:local` mode when sending values from the owning ractor.
67
+
68
+ # @attribute [r] mode
69
+ # The port's default mode. Set via {#initialize}.
70
+ # @return [Symbol] The default mode of this port.
71
+ # @see #send
72
+ def mode = self.class.mode
73
+
74
+ # Whether or not auto_local is enabled by default. Set via {#initialize}.
75
+ # @return [Boolean] `true` if auto_local is enabled, `false` otherwise.
76
+ # @see #send
77
+ def auto_local? = self.class.auto_local?
78
+
79
+ # Sends a message through the port.
80
+ #
81
+ # @param message [BasicObject] The message to send.
82
+ #
83
+ # @param move [Boolean, nil]
84
+ # Used to determine `mode` if it is not explicitly specified.
85
+ # If true, the message will be sent in `:move` mode.
86
+ # If false, the message will be sent in the port's default mode, unless that mode is `:move`, in which case it
87
+ # will be sent in `:copy` mode.
88
+ #
89
+ # @param mode [Symbol, nil]
90
+ # The mode to use when sending the message. If nil, the mode will be determined by the `move` parameter and the
91
+ # port's default mode.
92
+ #
93
+ # @param auto_local [Boolean, nil]
94
+ # If `true`, or `nil` and {#auto_local?} is `true`, and send is called from the owning ractor, then the message
95
+ # will be sent in `:local` mode regardless of the specified mode or move flag.
96
+ #
97
+ # @raise [Farce::Ractor::ClosedError] if the port is closed.
98
+ # @return [self]
99
+ def send(message, move: nil, mode: nil, auto_local: nil)
100
+ return super(message) if Ractor.shareable?(message)
101
+ raise Ractor::ClosedError, "port is closed" if closed?
102
+ auto_local = auto_local? if auto_local.nil?
103
+
104
+ if auto_local && owned?
105
+ mode = :local
106
+ elsif mode.nil?
107
+ mode = self.mode
108
+ case move
109
+ when true then mode = :move
110
+ when false then mode = :copy if mode == :move
111
+ when nil # no-op
112
+ else raise ArgumentError, "invalid move: #{move.inspect}"
113
+ end
114
+ end
115
+
116
+ case mode
117
+ when :copy then super(message)
118
+ when :move then super(message, move: true)
119
+ else super(MANAGER.wrap(message, mode:))
120
+ end
121
+ end
122
+
123
+ # (see Abstract::Port#receive)
124
+ # Automatically opens envelopes created by this port's mode manager.
125
+ def receive(timeout: nil)
126
+ # Omit the absent keyword instead of allocating arguments for super.
127
+ result = timeout.nil? ? super() : super
128
+ MANAGER.unwrap(result)
129
+ end
130
+
131
+ # @return [String] A string representation of the port, including its class name and mode.
132
+ def inspect = super.sub(/\A#<.+? (?=(?:to|id):#?\d+)/, "#<Farce::Port mode:#{mode} ")
133
+ end
134
+ end