farce 0 → 0.1.0

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 (420) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/CODE_OF_CONDUCT.md +26 -0
  4. data/CONTRIBUTING.md +71 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +1525 -0
  7. data/SECURITY.md +10 -0
  8. data/docs/benchmarks.md +185 -0
  9. data/docs/gems/dry-types.md +290 -0
  10. data/docs/gems/msgpack.md +70 -0
  11. data/docs/gems/ractor-shim.md +63 -0
  12. data/docs/modes.md +628 -0
  13. data/docs/scopes.md +649 -0
  14. data/docs/variants.md +346 -0
  15. data/ext/ext_helper.rb +20 -0
  16. data/ext/farce/README.md +21 -0
  17. data/ext/farce/atom.c +1023 -0
  18. data/ext/farce/bounded_map.c +1683 -0
  19. data/ext/farce/containers.h +77 -0
  20. data/ext/farce/counter.c +399 -0
  21. data/ext/farce/darwin.c +100 -0
  22. data/ext/farce/depend +12 -0
  23. data/ext/farce/dict.c +1523 -0
  24. data/ext/farce/dict.h +152 -0
  25. data/ext/farce/drivers.c +237 -0
  26. data/ext/farce/exchanger.c +299 -0
  27. data/ext/farce/extconf.rb +99 -0
  28. data/ext/farce/farce.c +359 -0
  29. data/ext/farce/flag.c +288 -0
  30. data/ext/farce/io.c +338 -0
  31. data/ext/farce/lock.c +510 -0
  32. data/ext/farce/map.c +2240 -0
  33. data/ext/farce/priority_queue.c +2056 -0
  34. data/ext/farce/queue.c +1059 -0
  35. data/ext/farce/reactor.c +820 -0
  36. data/ext/farce/reactor.h +103 -0
  37. data/ext/farce/shareable.h +31 -0
  38. data/ext/farce/signal.c +350 -0
  39. data/ext/farce/transaction.c +354 -0
  40. data/ext/farce/transaction.h +40 -0
  41. data/ext/farce/tree_map.c +1953 -0
  42. data/ext/farce/trie.c +2020 -0
  43. data/ext/farce/unshareable.c +155 -0
  44. data/ext/farce/unshared_io_pool.h +234 -0
  45. data/ext/farce/unshared_signal.c +263 -0
  46. data/ext/farce/unshared_wait.h +193 -0
  47. data/ext/farce/unsupported.c +6 -0
  48. data/ext/farce/vector.c +1195 -0
  49. data/ext/farce/weak_map.c +1714 -0
  50. data/ext/java/org/farce/BoundedMap.java +394 -0
  51. data/ext/java/org/farce/FiberScheduler.java +141 -0
  52. data/ext/java/org/farce/PriorityKey.java +45 -0
  53. data/ext/java/org/farce/PriorityQueue.java +373 -0
  54. data/ext/java/org/farce/QueueSignal.java +28 -0
  55. data/ext/rebind/README.md +14 -0
  56. data/ext/rebind/extconf.rb +10 -0
  57. data/ext/rebind/rebind.c +186 -0
  58. data/lib/farce/_yard/internal.rb +13 -0
  59. data/lib/farce/_yard/macros.rb +61 -0
  60. data/lib/farce/_yard/ractor.rb +46 -0
  61. data/lib/farce/abstract/atom.rb +186 -0
  62. data/lib/farce/abstract/bounded_map.rb +232 -0
  63. data/lib/farce/abstract/collection.rb +151 -0
  64. data/lib/farce/abstract/concurrent_map.rb +381 -0
  65. data/lib/farce/abstract/counter.rb +193 -0
  66. data/lib/farce/abstract/duplicable_map.rb +229 -0
  67. data/lib/farce/abstract/exchanger.rb +26 -0
  68. data/lib/farce/abstract/flag.rb +71 -0
  69. data/lib/farce/abstract/lazy.rb +115 -0
  70. data/lib/farce/abstract/lease.rb +104 -0
  71. data/lib/farce/abstract/lease_map.rb +261 -0
  72. data/lib/farce/abstract/lease_pool.rb +91 -0
  73. data/lib/farce/abstract/lfu_map.rb +20 -0
  74. data/lib/farce/abstract/lru_map.rb +25 -0
  75. data/lib/farce/abstract/map.rb +345 -0
  76. data/lib/farce/abstract/molecule.rb +245 -0
  77. data/lib/farce/abstract/port.rb +74 -0
  78. data/lib/farce/abstract/priority_queue.rb +117 -0
  79. data/lib/farce/abstract/queue.rb +269 -0
  80. data/lib/farce/abstract/scheduler.rb +111 -0
  81. data/lib/farce/abstract/set.rb +910 -0
  82. data/lib/farce/abstract/sorted_set.rb +172 -0
  83. data/lib/farce/abstract/timer_queue.rb +136 -0
  84. data/lib/farce/abstract/tree_map.rb +269 -0
  85. data/lib/farce/abstract/value.rb +68 -0
  86. data/lib/farce/abstract/vector.rb +1126 -0
  87. data/lib/farce/abstract/weak_atom.rb +32 -0
  88. data/lib/farce/abstract/weak_key_map.rb +12 -0
  89. data/lib/farce/abstract/weak_map.rb +13 -0
  90. data/lib/farce/abstract/weak_set.rb +12 -0
  91. data/lib/farce/abstract/weak_value_map.rb +12 -0
  92. data/lib/farce/abstract.rb +17 -0
  93. data/lib/farce/atom.rb +293 -0
  94. data/lib/farce/class_mirror.rb +87 -0
  95. data/lib/farce/clock.rb +121 -0
  96. data/lib/farce/config.rb +229 -0
  97. data/lib/farce/counter.rb +71 -0
  98. data/lib/farce/deduper.rb +122 -0
  99. data/lib/farce/engine/jruby/bounded_map.rb +314 -0
  100. data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
  101. data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
  102. data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
  103. data/lib/farce/engine/jruby/map.rb +505 -0
  104. data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
  105. data/lib/farce/engine/jruby/signal.rb +147 -0
  106. data/lib/farce/engine/jruby.rb +63 -0
  107. data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
  108. data/lib/farce/engine/jvm/counter.rb +102 -0
  109. data/lib/farce/engine/jvm/extension.rb +32 -0
  110. data/lib/farce/engine/jvm/farce.jar +0 -0
  111. data/lib/farce/engine/jvm/flag.rb +79 -0
  112. data/lib/farce/engine/jvm/priority_queue.rb +217 -0
  113. data/lib/farce/engine/jvm/tree_map.rb +350 -0
  114. data/lib/farce/engine/jvm/types.rb +180 -0
  115. data/lib/farce/engine/jvm.rb +19 -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/vault.rb +56 -0
  121. data/lib/farce/engine/ruby/4.0/port.rb +19 -0
  122. data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
  123. data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
  124. data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
  125. data/lib/farce/engine/ruby/4.1/port.rb +21 -0
  126. data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
  127. data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
  128. data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
  129. data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
  130. data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
  131. data/lib/farce/engine/ruby/shared/lease.rb +26 -0
  132. data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
  133. data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
  134. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
  135. data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
  136. data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
  137. data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
  138. data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
  139. data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
  140. data/lib/farce/engine/ruby/shared/vault.rb +144 -0
  141. data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
  142. data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
  143. data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
  144. data/lib/farce/engine/ruby.rb +130 -0
  145. data/lib/farce/engine/shared/atom.rb +10 -0
  146. data/lib/farce/engine/shared/exchanger.rb +130 -0
  147. data/lib/farce/engine/shared/identity_key.rb +22 -0
  148. data/lib/farce/engine/shared/lease.rb +10 -0
  149. data/lib/farce/engine/shared/lease_pool.rb +10 -0
  150. data/lib/farce/engine/shared/main_scheduler.rb +20 -0
  151. data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
  152. data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
  153. data/lib/farce/engine/shared/port.rb +44 -0
  154. data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
  155. data/lib/farce/engine/shared/proxy_owner.rb +43 -0
  156. data/lib/farce/engine/shared/queue.rb +233 -0
  157. data/lib/farce/engine/shared/ractor_methods.rb +73 -0
  158. data/lib/farce/engine/shared/rebindable.rb +15 -0
  159. data/lib/farce/engine/shared/strict_atom.rb +73 -0
  160. data/lib/farce/engine/shared/strict_map.rb +117 -0
  161. data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
  162. data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
  163. data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
  164. data/lib/farce/engine/shared/trie.rb +317 -0
  165. data/lib/farce/engine/shared/trie_builder.rb +199 -0
  166. data/lib/farce/engine/shared/unshareable.rb +14 -0
  167. data/lib/farce/engine/shared/unshared_atom.rb +256 -0
  168. data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
  169. data/lib/farce/engine/shared/unshared_queue.rb +18 -0
  170. data/lib/farce/engine/shared/unshared_signal.rb +21 -0
  171. data/lib/farce/engine/shared/unshared_vector.rb +377 -0
  172. data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
  173. data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
  174. data/lib/farce/engine/shared/vault.rb +31 -0
  175. data/lib/farce/engine/shared/vector.rb +83 -0
  176. data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
  177. data/lib/farce/engine/shared/weak_atom.rb +20 -0
  178. data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
  179. data/lib/farce/engine/shared/weak_map/index.rb +249 -0
  180. data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
  181. data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
  182. data/lib/farce/engine/shared.rb +63 -0
  183. data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
  184. data/lib/farce/engine/truffleruby/lock.rb +121 -0
  185. data/lib/farce/engine/truffleruby/map.rb +656 -0
  186. data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
  187. data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
  188. data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
  189. data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
  190. data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
  191. data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
  192. data/lib/farce/engine/truffleruby/signal.rb +77 -0
  193. data/lib/farce/engine/truffleruby.rb +148 -0
  194. data/lib/farce/envelope.rb +335 -0
  195. data/lib/farce/error.rb +35 -0
  196. data/lib/farce/exchanger.rb +41 -0
  197. data/lib/farce/flag.rb +27 -0
  198. data/lib/farce/integrations/active_support/blank.rb +68 -0
  199. data/lib/farce/integrations/active_support/clock.rb +17 -0
  200. data/lib/farce/integrations/active_support/duplicable.rb +78 -0
  201. data/lib/farce/integrations/active_support/map.rb +175 -0
  202. data/lib/farce/integrations/active_support/set.rb +60 -0
  203. data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
  204. data/lib/farce/integrations/active_support/vector.rb +231 -0
  205. data/lib/farce/integrations/active_support.rb +9 -0
  206. data/lib/farce/integrations/activesupport.rb +5 -0
  207. data/lib/farce/integrations/bson.rb +108 -0
  208. data/lib/farce/integrations/cbor.rb +51 -0
  209. data/lib/farce/integrations/concurrent.rb +112 -0
  210. data/lib/farce/integrations/dry-types.rb +5 -0
  211. data/lib/farce/integrations/dry_types.rb +694 -0
  212. data/lib/farce/integrations/json.rb +7 -0
  213. data/lib/farce/integrations/msgpack.rb +112 -0
  214. data/lib/farce/integrations/oj.rb +69 -0
  215. data/lib/farce/integrations/psych.rb +191 -0
  216. data/lib/farce/integrations/ractor-sharing.rb +5 -0
  217. data/lib/farce/integrations/ractor-tmvar.rb +5 -0
  218. data/lib/farce/integrations/ractor_sharing.rb +172 -0
  219. data/lib/farce/integrations/ractor_tmvar.rb +64 -0
  220. data/lib/farce/integrations/shared/to_json.rb +42 -0
  221. data/lib/farce/integrations/sorted_set.rb +11 -0
  222. data/lib/farce/integrations/weakref.rb +38 -0
  223. data/lib/farce/integrations/yajl.rb +10 -0
  224. data/lib/farce/integrations.rb +156 -0
  225. data/lib/farce/internal/_frozen_config.rb +11 -0
  226. data/lib/farce/internal/autoloads.rb +47 -0
  227. data/lib/farce/internal/blocking_priority_queue.rb +122 -0
  228. data/lib/farce/internal/converter.rb +106 -0
  229. data/lib/farce/internal/copyable.rb +31 -0
  230. data/lib/farce/internal/delegation.rb +20 -0
  231. data/lib/farce/internal/external_transaction.rb +59 -0
  232. data/lib/farce/internal/fake_ractor.rb +179 -0
  233. data/lib/farce/internal/freeze.rb +118 -0
  234. data/lib/farce/internal/inspect.rb +157 -0
  235. data/lib/farce/internal/key_lock_map.rb +29 -0
  236. data/lib/farce/internal/key_normalizer.rb +289 -0
  237. data/lib/farce/internal/lease_initialization.rb +115 -0
  238. data/lib/farce/internal/lease_map.rb +441 -0
  239. data/lib/farce/internal/lease_pool_state.rb +228 -0
  240. data/lib/farce/internal/lease_state.rb +342 -0
  241. data/lib/farce/internal/lease_waiting.rb +13 -0
  242. data/lib/farce/internal/managed_queue.rb +26 -0
  243. data/lib/farce/internal/map_value_modes.rb +227 -0
  244. data/lib/farce/internal/marshal_support.rb +227 -0
  245. data/lib/farce/internal/mixin.rb +20 -0
  246. data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
  247. data/lib/farce/internal/noncopyable.rb +17 -0
  248. data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
  249. data/lib/farce/internal/pool_supervisor.rb +41 -0
  250. data/lib/farce/internal/pool_worker.rb +61 -0
  251. data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
  252. data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
  253. data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
  254. data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
  255. data/lib/farce/internal/portable_transaction.rb +164 -0
  256. data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
  257. data/lib/farce/internal/reservation_waiting.rb +24 -0
  258. data/lib/farce/internal/scheduled_task.rb +48 -0
  259. data/lib/farce/internal/scheduler_io.rb +171 -0
  260. data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
  261. data/lib/farce/internal/select_scheduler.rb +235 -0
  262. data/lib/farce/internal/storage.rb +162 -0
  263. data/lib/farce/internal/strict_lease.rb +30 -0
  264. data/lib/farce/internal/strict_lease_map.rb +20 -0
  265. data/lib/farce/internal/strict_lease_pool.rb +29 -0
  266. data/lib/farce/internal/thread_pool.rb +166 -0
  267. data/lib/farce/internal/transaction_conflict.rb +9 -0
  268. data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
  269. data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
  270. data/lib/farce/internal/undefined.rb +22 -0
  271. data/lib/farce/internal/unshared_lease.rb +29 -0
  272. data/lib/farce/internal/unshared_lease_pool.rb +34 -0
  273. data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
  274. data/lib/farce/internal/value_serialization.rb +13 -0
  275. data/lib/farce/internal/weak_map_value_modes.rb +58 -0
  276. data/lib/farce/internal/weak_mode_manager.rb +26 -0
  277. data/lib/farce/internal.rb +129 -0
  278. data/lib/farce/lazy.rb +100 -0
  279. data/lib/farce/lazy_ref.rb +40 -0
  280. data/lib/farce/lease.rb +28 -0
  281. data/lib/farce/lease_map.rb +34 -0
  282. data/lib/farce/lease_pool.rb +29 -0
  283. data/lib/farce/lfu_map.rb +53 -0
  284. data/lib/farce/local/atom.rb +23 -0
  285. data/lib/farce/local/counter.rb +88 -0
  286. data/lib/farce/local/flag.rb +65 -0
  287. data/lib/farce/local/lazy.rb +38 -0
  288. data/lib/farce/local/lazy_ref.rb +36 -0
  289. data/lib/farce/local/lease.rb +77 -0
  290. data/lib/farce/local/lease_map.rb +80 -0
  291. data/lib/farce/local/lease_pool.rb +38 -0
  292. data/lib/farce/local/lfu_map.rb +55 -0
  293. data/lib/farce/local/lru_map.rb +62 -0
  294. data/lib/farce/local/map.rb +47 -0
  295. data/lib/farce/local/molecule.rb +45 -0
  296. data/lib/farce/local/priority_queue.rb +53 -0
  297. data/lib/farce/local/queue.rb +32 -0
  298. data/lib/farce/local/scoped.rb +166 -0
  299. data/lib/farce/local/set.rb +18 -0
  300. data/lib/farce/local/sorted_set.rb +50 -0
  301. data/lib/farce/local/timer_queue.rb +41 -0
  302. data/lib/farce/local/tree_map.rb +47 -0
  303. data/lib/farce/local/vector.rb +30 -0
  304. data/lib/farce/local/weak_atom.rb +23 -0
  305. data/lib/farce/local/weak_key_map.rb +39 -0
  306. data/lib/farce/local/weak_map.rb +39 -0
  307. data/lib/farce/local/weak_set.rb +18 -0
  308. data/lib/farce/local/weak_value_map.rb +39 -0
  309. data/lib/farce/local.rb +35 -0
  310. data/lib/farce/lock.rb +54 -0
  311. data/lib/farce/lru_map.rb +63 -0
  312. data/lib/farce/map.rb +58 -0
  313. data/lib/farce/mode_manager.rb +142 -0
  314. data/lib/farce/molecule.rb +60 -0
  315. data/lib/farce/mutable.rb +171 -0
  316. data/lib/farce/pool.rb +332 -0
  317. data/lib/farce/port.rb +134 -0
  318. data/lib/farce/priority_queue.rb +70 -0
  319. data/lib/farce/proxy/register.rb +101 -0
  320. data/lib/farce/proxy/supervisor.rb +137 -0
  321. data/lib/farce/proxy/wrapper.rb +49 -0
  322. data/lib/farce/proxy.rb +291 -0
  323. data/lib/farce/queue.rb +60 -0
  324. data/lib/farce/ractor.rb +269 -0
  325. data/lib/farce/read_write_lock.rb +256 -0
  326. data/lib/farce/reference.rb +158 -0
  327. data/lib/farce/resolv/dns.rb +42 -0
  328. data/lib/farce/resolv.rb +85 -0
  329. data/lib/farce/scheduler.rb +536 -0
  330. data/lib/farce/set.rb +39 -0
  331. data/lib/farce/shareable.rb +150 -0
  332. data/lib/farce/signal.rb +97 -0
  333. data/lib/farce/sorted_set.rb +36 -0
  334. data/lib/farce/strict/atom.rb +34 -0
  335. data/lib/farce/strict/counter.rb +10 -0
  336. data/lib/farce/strict/exchanger.rb +25 -0
  337. data/lib/farce/strict/flag.rb +10 -0
  338. data/lib/farce/strict/lazy.rb +26 -0
  339. data/lib/farce/strict/lazy_ref.rb +26 -0
  340. data/lib/farce/strict/lease.rb +27 -0
  341. data/lib/farce/strict/lease_map.rb +33 -0
  342. data/lib/farce/strict/lease_pool.rb +28 -0
  343. data/lib/farce/strict/lfu_map.rb +27 -0
  344. data/lib/farce/strict/lru_map.rb +28 -0
  345. data/lib/farce/strict/map.rb +54 -0
  346. data/lib/farce/strict/molecule.rb +16 -0
  347. data/lib/farce/strict/port.rb +40 -0
  348. data/lib/farce/strict/priority_queue.rb +20 -0
  349. data/lib/farce/strict/queue.rb +17 -0
  350. data/lib/farce/strict/set.rb +14 -0
  351. data/lib/farce/strict/sorted_set.rb +19 -0
  352. data/lib/farce/strict/timer_queue.rb +13 -0
  353. data/lib/farce/strict/tree_map.rb +27 -0
  354. data/lib/farce/strict/vector.rb +24 -0
  355. data/lib/farce/strict/weak_atom.rb +31 -0
  356. data/lib/farce/strict/weak_key_map.rb +49 -0
  357. data/lib/farce/strict/weak_map.rb +49 -0
  358. data/lib/farce/strict/weak_set.rb +14 -0
  359. data/lib/farce/strict/weak_value_map.rb +48 -0
  360. data/lib/farce/strict.rb +28 -0
  361. data/lib/farce/system.rb +40 -0
  362. data/lib/farce/thread_scheduler.rb +70 -0
  363. data/lib/farce/timer_queue.rb +66 -0
  364. data/lib/farce/transaction/atom.rb +73 -0
  365. data/lib/farce/transaction/map.rb +25 -0
  366. data/lib/farce/transaction/map_operations.rb +143 -0
  367. data/lib/farce/transaction/molecule.rb +90 -0
  368. data/lib/farce/transaction/mutable.rb +61 -0
  369. data/lib/farce/transaction/set.rb +17 -0
  370. data/lib/farce/transaction/set_operations.rb +48 -0
  371. data/lib/farce/transaction/sorted_set.rb +18 -0
  372. data/lib/farce/transaction/tree_map.rb +78 -0
  373. data/lib/farce/transaction/vector.rb +139 -0
  374. data/lib/farce/transaction/wrapper.rb +202 -0
  375. data/lib/farce/transaction.rb +343 -0
  376. data/lib/farce/tree_map.rb +56 -0
  377. data/lib/farce/unsafe/lfu_map.rb +36 -0
  378. data/lib/farce/unsafe/lru_map.rb +36 -0
  379. data/lib/farce/unsafe/tree_map.rb +21 -0
  380. data/lib/farce/unsafe.rb +29 -0
  381. data/lib/farce/unshareable.rb +67 -0
  382. data/lib/farce/unshared/atom.rb +24 -0
  383. data/lib/farce/unshared/counter.rb +10 -0
  384. data/lib/farce/unshared/flag.rb +10 -0
  385. data/lib/farce/unshared/lazy.rb +38 -0
  386. data/lib/farce/unshared/lazy_ref.rb +26 -0
  387. data/lib/farce/unshared/lease.rb +27 -0
  388. data/lib/farce/unshared/lease_map.rb +26 -0
  389. data/lib/farce/unshared/lease_pool.rb +25 -0
  390. data/lib/farce/unshared/lfu_map.rb +20 -0
  391. data/lib/farce/unshared/lru_map.rb +20 -0
  392. data/lib/farce/unshared/map.rb +49 -0
  393. data/lib/farce/unshared/molecule.rb +16 -0
  394. data/lib/farce/unshared/priority_queue.rb +16 -0
  395. data/lib/farce/unshared/queue.rb +27 -0
  396. data/lib/farce/unshared/set.rb +14 -0
  397. data/lib/farce/unshared/sorted_set.rb +28 -0
  398. data/lib/farce/unshared/timer_queue.rb +16 -0
  399. data/lib/farce/unshared/tree_map.rb +21 -0
  400. data/lib/farce/unshared/vector.rb +19 -0
  401. data/lib/farce/unshared/weak_atom.rb +31 -0
  402. data/lib/farce/unshared/weak_key_map.rb +44 -0
  403. data/lib/farce/unshared/weak_map.rb +44 -0
  404. data/lib/farce/unshared/weak_set.rb +14 -0
  405. data/lib/farce/unshared/weak_value_map.rb +43 -0
  406. data/lib/farce/unshared.rb +28 -0
  407. data/lib/farce/vector.rb +228 -0
  408. data/lib/farce/version.rb +8 -0
  409. data/lib/farce/walker/definitions.rb +186 -0
  410. data/lib/farce/walker/modification.rb +355 -0
  411. data/lib/farce/walker.rb +319 -0
  412. data/lib/farce/weak_atom.rb +121 -0
  413. data/lib/farce/weak_key_map.rb +31 -0
  414. data/lib/farce/weak_map.rb +33 -0
  415. data/lib/farce/weak_ref.rb +67 -0
  416. data/lib/farce/weak_set.rb +82 -0
  417. data/lib/farce/weak_value.rb +195 -0
  418. data/lib/farce/weak_value_map.rb +32 -0
  419. data/lib/farce.rb +519 -0
  420. metadata +435 -8
@@ -0,0 +1,820 @@
1
+ #include "reactor.h"
2
+ #include <unistd.h>
3
+ #include <fcntl.h>
4
+ #include <errno.h>
5
+ #include <math.h>
6
+ #include <poll.h>
7
+ #include <ruby/fiber/scheduler.h>
8
+
9
+ // Tell Ruby's garbage collector which Ruby objects one fiber's wait still needs.
10
+ static int mark_wait(st_data_t key, st_data_t value, st_data_t arg) {
11
+ fs_wait *w = (fs_wait *)value;
12
+ rb_gc_mark(w->fiber); rb_gc_mark(w->token); rb_gc_mark(w->value);
13
+ rb_gc_mark(w->error); rb_gc_mark(w->groups); rb_gc_mark(w->ios);
14
+ return ST_CONTINUE;
15
+ }
16
+
17
+ // Keep the scheduler's Ruby objects alive, including fibers that are starting or waiting.
18
+ static void mark(void *data) {
19
+ fs_state *s = data;
20
+ rb_gc_mark(s->root); rb_gc_mark(s->active);
21
+ for (fs_admission *a = s->admission; a; a = a->previous) { rb_gc_mark(a->parent); rb_gc_mark(a->child); }
22
+ rb_gc_mark(s->owner);
23
+ rb_gc_mark(s->backend); rb_gc_mark(s->fallback);
24
+ if (s->fibers) st_foreach(s->fibers, mark_wait, 0);
25
+ }
26
+
27
+ // Free a fiber's wait record and any extra IO watches it owns.
28
+ // The first watch is stored inside the record and needs no separate free.
29
+ static int free_wait(st_data_t key, st_data_t value, st_data_t arg) {
30
+ fs_wait *w = (fs_wait *)value;
31
+ fs_watch *watch = w->watches;
32
+ while (watch) { fs_watch *next = watch->next_wait; if (watch != &w->first_watch) xfree(watch); watch = next; }
33
+ xfree(w); return ST_CONTINUE;
34
+ }
35
+
36
+ // Free one descriptor registration while walking a registration table.
37
+ static int free_reg(st_data_t key, st_data_t value, st_data_t arg) {
38
+ xfree((fs_reg *)value); return ST_CONTINUE;
39
+ }
40
+
41
+ // Close the driver and wake pipe, then discard all waits and registrations.
42
+ // The mutex prevents another thread from writing to the pipe while it closes.
43
+ // Keep the empty tables and slot storage for the final object cleanup.
44
+ static void release(fs_state *s) {
45
+ pthread_mutex_lock(&s->wake_mutex);
46
+ s->closed = 1;
47
+ fs_driver_close(s);
48
+ for (int i = 0; i < 2; i++) if (s->wake[i] >= 0) { close(s->wake[i]); s->wake[i] = -1; }
49
+ pthread_mutex_unlock(&s->wake_mutex);
50
+ s->pending = 0;
51
+ s->slot_count = 0; s->first_free = UINT32_MAX;
52
+ if (s->fibers) { st_foreach(s->fibers, free_wait, 0); st_clear(s->fibers); }
53
+ if (s->regs) { st_foreach(s->regs, free_reg, 0); st_clear(s->regs); }
54
+ if (s->generations) st_clear(s->generations);
55
+ if (s->retired_regs) { st_foreach(s->retired_regs, free_reg, 0); st_clear(s->retired_regs); }
56
+ s->polling_regs = 0;
57
+ s->ready_head = s->ready_tail = NULL;
58
+ }
59
+
60
+ // Release native resources and free the scheduler's remaining storage
61
+ // when Ruby's garbage collector destroys the object.
62
+ static void free_state(void *data) {
63
+ fs_state *s = data;
64
+ release(s);
65
+ xfree(s->slots);
66
+ if (s->fibers) st_free_table(s->fibers);
67
+ if (s->regs) st_free_table(s->regs);
68
+ if (s->generations) st_free_table(s->generations);
69
+ if (s->retired_regs) st_free_table(s->retired_regs);
70
+ pthread_mutex_destroy(&s->wake_mutex);
71
+ xfree(s);
72
+ }
73
+
74
+ // Add the space used by watches allocated outside their wait record.
75
+ static int count_watch_memory(st_data_t key, st_data_t value, st_data_t arg) {
76
+ fs_wait *wait = (fs_wait *)value;
77
+ size_t *size = (size_t *)arg;
78
+ for (fs_watch *watch = wait->watches; watch; watch = watch->next_wait) if (watch != &wait->first_watch) *size += sizeof(*watch);
79
+ return ST_CONTINUE;
80
+ }
81
+
82
+ // Report the scheduler's native memory use to Ruby's garbage collector.
83
+ static size_t memsize(const void *data) {
84
+ const fs_state *s = data;
85
+ size_t size = sizeof(*s) + (s->fibers ? st_memsize(s->fibers) + s->fibers->num_entries * sizeof(fs_wait) : 0) +
86
+ s->slot_capacity * sizeof(fs_slot) +
87
+ (s->regs ? st_memsize(s->regs) + s->regs->num_entries * sizeof(fs_reg) : 0) +
88
+ (s->generations ? st_memsize(s->generations) : 0) +
89
+ (s->retired_regs ? st_memsize(s->retired_regs) + s->retired_regs->num_entries * sizeof(fs_reg) : 0);
90
+ #ifdef HAVE_SYS_EVENT_H
91
+ size += s->change_capacity * sizeof(struct kevent);
92
+ #endif
93
+ if (s->fibers) st_foreach(s->fibers, count_watch_memory, (st_data_t)&size);
94
+ return size;
95
+ }
96
+
97
+ // Describe how Ruby should mark, free, and measure the native scheduler object.
98
+ const rb_data_type_t fs_type = {
99
+ .wrap_struct_name = "Farce::Internal::FiberScheduler",
100
+ .function = {.dmark = mark, .dfree = free_state, .dsize = memsize},
101
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
102
+ };
103
+
104
+ // Create an empty scheduler in a closed state.
105
+ // Set safe defaults so cleanup also works if initialization fails.
106
+ static VALUE allocate(VALUE klass) {
107
+ fs_state *s;
108
+ VALUE self = TypedData_Make_Struct(klass, fs_state, &fs_type, s);
109
+ s->active = s->root = s->owner = s->backend = s->fallback = Qnil;
110
+ s->descriptor = s->wake[0] = s->wake[1] = -1;
111
+ s->closed = 1;
112
+ pthread_mutex_init(&s->wake_mutex, NULL);
113
+ s->retired_regs = st_init_numtable();
114
+ s->first_free = UINT32_MAX;
115
+ s->regs = st_init_numtable(); s->generations = st_init_numtable();
116
+ return self;
117
+ }
118
+
119
+ // Bind the scheduler to its thread and root fiber, then open the chosen driver.
120
+ // Register a nonblocking wake pipe so another thread can interrupt a driver wait.
121
+ static VALUE initialize(int argc, VALUE *argv, VALUE self) {
122
+ VALUE options, backend = ID2SYM(rb_intern("auto"));
123
+ rb_scan_args(argc, argv, "0:", &options);
124
+ if (!NIL_P(options)) {
125
+ ID keys[] = {rb_intern("backend")}; VALUE values[1];
126
+ rb_get_kwargs(options, keys, 0, 1, values);
127
+ if (values[0] != Qundef) backend = values[0];
128
+ }
129
+ fs_state *s = fs_get(self);
130
+ if (!NIL_P(s->owner)) rb_raise(rb_eRuntimeError, "scheduler already initialized");
131
+ s->owner = rb_thread_current();
132
+ s->root = rb_fiber_current();
133
+ s->fibers = st_init_numtable();
134
+ int driver = 0;
135
+ if (backend == ID2SYM(rb_intern("auto"))) {
136
+ #ifdef HAVE_SYS_EPOLL_H
137
+ driver = FS_EPOLL; backend = ID2SYM(rb_intern("epoll"));
138
+ #elif defined(HAVE_SYS_EVENT_H)
139
+ driver = FS_KQUEUE; backend = ID2SYM(rb_intern("kqueue"));
140
+ #endif
141
+ } else if (backend == ID2SYM(rb_intern("epoll"))) driver = FS_EPOLL;
142
+ else if (backend == ID2SYM(rb_intern("kqueue"))) driver = FS_KQUEUE;
143
+ else if (backend == ID2SYM(rb_intern("io_uring"))) driver = FS_URING_DRIVER;
144
+ else rb_raise(rb_eArgError, "unknown native scheduler backend");
145
+ int error = fs_driver_init(s, driver);
146
+ if (error) rb_syserr_fail(error, "requested fiber scheduler backend is unavailable");
147
+ s->backend = backend;
148
+ if (s->descriptor >= 0) fcntl(s->descriptor, F_SETFD, FD_CLOEXEC);
149
+ if (pipe(s->wake) < 0) rb_sys_fail("scheduler wake pipe");
150
+ for (int i = 0; i < 2; i++) {
151
+ if (fcntl(s->wake[i], F_SETFD, FD_CLOEXEC) < 0 || fcntl(s->wake[i], F_SETFL, O_NONBLOCK) < 0)
152
+ rb_sys_fail("scheduler wake flags");
153
+ }
154
+ fs_reg *reg = ZALLOC(fs_reg); reg->fd = s->wake[0];
155
+ st_insert(s->regs, reg->fd, (st_data_t)reg);
156
+ error = fs_driver_update(s, reg, FS_READ);
157
+ if (error) rb_syserr_fail(error, "scheduler wake registration");
158
+ s->closed = 0;
159
+ return self;
160
+ }
161
+
162
+ // Find the live wait named by a token.
163
+ // Check both its slot and generation so an old token cannot name a reused wait.
164
+ fs_wait *fs_lookup(fs_state *s, VALUE token) {
165
+ uint64_t identity;
166
+ #if SIZEOF_VALUE >= 8
167
+ if (!FIXNUM_P(token) || FIX2LONG(token) < 0) return NULL;
168
+ identity = FIX2ULONG(token);
169
+ #else
170
+ // Keep the same generation lifetime on 32-bit Ruby. Tokens may be Bignums.
171
+ if (!RB_INTEGER_TYPE_P(token)) return NULL;
172
+ int sign = rb_integer_pack(token, &identity, 1, sizeof(identity), 0,
173
+ INTEGER_PACK_NATIVE_BYTE_ORDER | INTEGER_PACK_LSWORD_FIRST);
174
+ if (sign < 0 || sign > 1) return NULL;
175
+ #endif
176
+ uint32_t index = (uint32_t)identity & FS_SLOT_MASK;
177
+ if (index >= s->slot_count) return NULL;
178
+ fs_wait *w = s->slots[index].wait;
179
+ #if SIZEOF_VALUE >= 8
180
+ return w && w->token == token ? w : NULL;
181
+ #else
182
+ return w && !NIL_P(w->token) && NUM2ULL(w->token) == identity ? w : NULL;
183
+ #endif
184
+ }
185
+
186
+ // Reject calls from the wrong thread or fiber, or after shutdown has begun.
187
+ // Normally this scheduler must also be installed on the thread.
188
+ void fs_check_fiber(VALUE self) {
189
+ fs_state *s = fs_get(self);
190
+ if (s->owner != rb_thread_current()) rb_raise(rb_eThreadError, "scheduler belongs to another thread");
191
+ if (s->closed || s->stopping) rb_raise(rb_eIOError, "scheduler is closed");
192
+ if (rb_fiber_current() != s->active)
193
+ rb_raise(rb_const_get(rb_cObject, rb_intern("FiberError")), "operation requires an admitted fiber");
194
+ if (!s->closing && rb_fiber_scheduler_get() != self)
195
+ rb_raise(rb_const_get(rb_cObject, rb_intern("FiberError")), "scheduler must be installed");
196
+ }
197
+
198
+ // Check the current fiber, then create a token for its next wait.
199
+ VALUE fs_arm(VALUE self) {
200
+ fs_check_fiber(self);
201
+ return fs_arm_checked(self);
202
+ }
203
+
204
+ // Start a wait for a fiber the caller has already checked.
205
+ // Reuse its slot with a new generation and clear the previous result.
206
+ VALUE fs_arm_checked(VALUE self) {
207
+ fs_state *s = fs_get(self);
208
+ st_data_t entry;
209
+ if (!st_lookup(s->fibers, (st_data_t)s->active, &entry)) rb_raise(rb_eRuntimeError, "missing native fiber state");
210
+ fs_wait *w = (fs_wait *)entry;
211
+ if (!NIL_P(w->token)) rb_raise(rb_eRuntimeError, "fiber already has a suspension");
212
+ fs_slot *slot = &s->slots[w->slot];
213
+ if (slot->generation == (UINT64_MAX >> (FS_SLOT_BITS + 2))) rb_raise(rb_eRangeError, "scheduler generation exhausted");
214
+ VALUE token = ULL2NUM((++slot->generation << FS_SLOT_BITS) | w->slot);
215
+ w->token = token; w->status = w->events = w->selected = w->native_error = 0;
216
+ w->value = w->error = w->groups = w->ios = Qnil;
217
+ s->pending++;
218
+ return token;
219
+ }
220
+
221
+ // Return a fiber's active wait token, or nil if it has no wait.
222
+ static VALUE current_wait(VALUE self, VALUE fiber) {
223
+ fs_state *s = fs_get(self);
224
+ st_data_t entry;
225
+ return s->fibers && st_lookup(s->fibers, (st_data_t)fiber, &entry) ? ((fs_wait *)entry)->token : Qnil;
226
+ }
227
+
228
+ // Add a wait to the end of the ready queue unless it is already queued.
229
+ // This preserves the order in which waits become ready.
230
+ static void ready_push(fs_state *s, fs_wait *w) {
231
+ if (w->queued) return;
232
+ w->queued = 1; w->ready_prev = s->ready_tail; w->ready_next = NULL;
233
+ if (s->ready_tail) s->ready_tail->ready_next = w;
234
+ else s->ready_head = w;
235
+ s->ready_tail = w;
236
+ }
237
+
238
+ // Unlink a wait from the ready queue. Do nothing if it is not queued.
239
+ static void ready_remove(fs_state *s, fs_wait *w) {
240
+ if (!w->queued) return;
241
+ if (w->ready_prev) w->ready_prev->ready_next = w->ready_next;
242
+ else s->ready_head = w->ready_next;
243
+ if (w->ready_next) w->ready_next->ready_prev = w->ready_prev;
244
+ else s->ready_tail = w->ready_prev;
245
+ w->queued = 0; w->ready_prev = w->ready_next = NULL;
246
+ }
247
+
248
+ // Take the oldest ready wait from the queue, or return NULL if it is empty.
249
+ static fs_wait *ready_pop(fs_state *s) {
250
+ fs_wait *w = s->ready_head;
251
+ if (w) ready_remove(s, w);
252
+ return w;
253
+ }
254
+
255
+ // Store a pending wait's result and queue its fiber to run.
256
+ // Ignore old tokens and waits that already have a result.
257
+ VALUE fs_resume(VALUE self, VALUE token, VALUE value, VALUE error) {
258
+ fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
259
+ if (!w || w->status != 0) return Qfalse;
260
+ w->status = 1; w->value = value; w->error = error;
261
+ ready_push(s, w); return Qtrue;
262
+ }
263
+
264
+ // Accept Ruby arguments for resuming a wait, including an optional exception.
265
+ static VALUE resume_method(int argc, VALUE *argv, VALUE self) {
266
+ VALUE token, value, error; rb_scan_args(argc, argv, "21", &token, &value, &error);
267
+ return fs_resume(self, token, value, error);
268
+ }
269
+
270
+ // Give a wait an exception and make its fiber ready to run.
271
+ // An exception can replace a queued result until that result has been dispatched.
272
+ static VALUE interrupt(VALUE self, VALUE token, VALUE error) {
273
+ fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
274
+ if (!w || w->status == 2) return Qfalse;
275
+ if (!w->status) ready_push(s, w);
276
+ w->status = 1; w->error = error; return Qtrue;
277
+ }
278
+
279
+ // Declare the closed-IO check used below to detect descriptor reuse.
280
+ static int closed_io(VALUE io);
281
+
282
+ // Attach an IO watch to both its wait and its descriptor registration.
283
+ // If the descriptor number was reused after a close, fail the old waits
284
+ // and create a separate registration for the new IO.
285
+ void fs_watch_io(fs_state *s, fs_wait *w, int fd, int events, int group, long index) {
286
+ st_data_t entry;
287
+ fs_reg *reg;
288
+ reg = st_lookup(s->regs, fd, &entry) ? (fs_reg *)entry : NULL;
289
+ if (reg) {
290
+ for (fs_watch *old = reg->watches; old; old = old->next_reg) {
291
+ if (!closed_io(old->wait->ios)) continue;
292
+ // Ruby 3.4 has no io_close hook. A closed IO still identifies the
293
+ // previous fd generation even if a newly opened IO reused its number.
294
+ fs_driver_update(s, reg, 0);
295
+ st_data_t key = fd; st_delete(s->regs, &key, NULL);
296
+ st_insert(s->retired_regs, (st_data_t)reg, (st_data_t)reg);
297
+ reg->fd = -1;
298
+ for (fs_watch *item = reg->watches; item; item = item->next_reg) {
299
+ fs_wait *wait = item->wait;
300
+ if (!wait->status) {
301
+ wait->error = rb_exc_new_cstr(rb_eIOError, "IO descriptor was closed and reused");
302
+ wait->status = 1; ready_push(s, wait);
303
+ }
304
+ }
305
+ reg = NULL;
306
+ break;
307
+ }
308
+ }
309
+ if (!reg) {
310
+ reg = ZALLOC(fs_reg); reg->fd = fd;
311
+ st_insert(s->regs, fd, (st_data_t)reg);
312
+ }
313
+ fs_watch *watch = w->watches ? ZALLOC(fs_watch) : &w->first_watch;
314
+ watch->wait = w; watch->reg = reg; watch->events = events; watch->group = group; watch->index = index;
315
+ watch->next_wait = w->watches; w->watches = watch;
316
+ watch->next_reg = reg->watches; reg->watches = watch;
317
+ if ((reg->events | events) != reg->events) {
318
+ int error = fs_driver_update(s, reg, reg->events | events);
319
+ if (error) rb_syserr_fail(error, "scheduler registration");
320
+ }
321
+ }
322
+
323
+ // Finish a wait and remove its watches and ready-queue entry.
324
+ // A pending receive must stop using the buffer before cleanup can continue.
325
+ // Clear Ruby references and keep the fiber's slot available for its next wait.
326
+ VALUE fs_retire(VALUE self, VALUE token) {
327
+ fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
328
+ if (!w) return Qnil;
329
+ if (w->receive_pending) fs_cancel_receive(self, w);
330
+ ready_remove(s, w);
331
+ fs_watch *watch = w->watches;
332
+ while (watch) {
333
+ fs_watch *next = watch->next_wait;
334
+ fs_reg *reg = watch->reg;
335
+ fs_watch **cursor = &reg->watches;
336
+ while (*cursor && *cursor != watch) cursor = &(*cursor)->next_reg;
337
+ if (*cursor) *cursor = watch->next_reg;
338
+ int events = 0;
339
+ for (fs_watch *item = reg->watches; item; item = item->next_reg) events |= item->events;
340
+ if (events != reg->events && !s->closed) fs_driver_update(s, reg, events);
341
+ if (!events && reg->poll_events) { s->polling_regs--; reg->poll_events = 0; }
342
+ if (!events && (reg->fd < 0 || s->regs->num_entries > 4096)) {
343
+ st_data_t key = reg->fd; st_delete(s->regs, &key, NULL);
344
+ key = reg->generation; st_delete(s->generations, &key, NULL);
345
+ key = (st_data_t)reg; st_delete(s->retired_regs, &key, NULL);
346
+ if (reg->poll_events) s->polling_regs--;
347
+ xfree(reg);
348
+ }
349
+ if (watch != &w->first_watch) xfree(watch);
350
+ watch = next;
351
+ }
352
+ s->pending--;
353
+ w->token = w->value = w->error = w->groups = w->ios = Qnil;
354
+ w->watches = NULL;
355
+ memset(&w->first_watch, 0, sizeof(w->first_watch));
356
+ return Qnil;
357
+ }
358
+
359
+ // Switch from the root to a ready fiber.
360
+ // If a newly started child finishes, return control to its waiting parent
361
+ // before returning to the root dispatcher.
362
+ static VALUE transfer_to(fs_state *s, VALUE fiber) {
363
+ s->active = fiber;
364
+ VALUE result = rb_fiber_transfer(fiber, 0, NULL);
365
+ s->active = Qnil;
366
+ // A transferred fiber returns to the thread root when its block finishes.
367
+ // Complete nested immediate admissions before returning to the driver.
368
+ while (s->admission && s->admission->parent != s->root) {
369
+ s->active = s->admission->parent;
370
+ result = rb_fiber_transfer(s->admission->parent, 0, NULL);
371
+ s->active = Qnil;
372
+ }
373
+ return result;
374
+ }
375
+
376
+ // Switch into a newly admitted fiber from the root or from its parent fiber.
377
+ static VALUE start_body(VALUE data) {
378
+ VALUE *args = (VALUE *)data;
379
+ fs_state *s = fs_get(args[0]);
380
+ if (rb_fiber_current() == s->root) return transfer_to(s, args[1]);
381
+ s->active = args[1];
382
+ return rb_fiber_transfer(args[1], 0, NULL);
383
+ }
384
+
385
+ // Restore the parent fiber and remove the admission record after starting a child,
386
+ // including when the child raises.
387
+ static VALUE start_cleanup(VALUE data) {
388
+ VALUE *args = (VALUE *)data;
389
+ fs_state *s = fs_get(args[0]);
390
+ s->active = s->admission->parent == s->root ? Qnil : s->admission->parent;
391
+ s->admission = s->admission->previous;
392
+ return Qnil;
393
+ }
394
+
395
+ // Give a new fiber a reusable wait slot and run it immediately.
396
+ // Track its parent so a nested start returns to the right fiber.
397
+ static VALUE start_fiber(VALUE self, VALUE fiber) {
398
+ fs_state *s = fs_get(self);
399
+ uint32_t index;
400
+ if (s->first_free != UINT32_MAX) {
401
+ index = s->first_free; s->first_free = s->slots[index].next_free;
402
+ } else {
403
+ if (s->slot_count > FS_SLOT_MASK) rb_raise(rb_eRangeError, "too many admitted scheduler fibers");
404
+ if (s->slot_count == s->slot_capacity) {
405
+ uint32_t capacity = s->slot_capacity ? s->slot_capacity * 2 : 64;
406
+ REALLOC_N(s->slots, fs_slot, capacity);
407
+ memset(s->slots + s->slot_capacity, 0, (capacity - s->slot_capacity) * sizeof(fs_slot));
408
+ s->slot_capacity = capacity;
409
+ }
410
+ index = s->slot_count++;
411
+ }
412
+ fs_wait *w = ZALLOC(fs_wait); w->slot = index; s->slots[index].wait = w;
413
+ w->fiber = fiber; w->token = w->value = w->error = w->groups = w->ios = Qnil;
414
+ st_insert(s->fibers, (st_data_t)fiber, (st_data_t)w);
415
+ fs_admission admission = {rb_fiber_current(), fiber, s->admission};
416
+ s->admission = &admission;
417
+ VALUE args[] = {self, fiber};
418
+ return rb_ensure(start_body, (VALUE)args, start_cleanup, (VALUE)args);
419
+ }
420
+
421
+ // Declare the nonblocking poll used when switching between ready fibers.
422
+ static void poll_ready(VALUE self);
423
+
424
+ // Suspend the current fiber and choose where control goes next.
425
+ // Return to its parent during startup, or run another ready fiber within the budget.
426
+ // When this fiber resumes, return its wait result or raise its recorded error.
427
+ static VALUE park_body(VALUE data) {
428
+ VALUE *args = (VALUE *)data;
429
+ fs_state *s = fs_get(args[0]);
430
+ VALUE current = rb_fiber_current(), next = s->root;
431
+ if (s->admission && s->admission->child == current) next = s->admission->parent;
432
+ else if (s->dispatch_left > 0 && !s->policy_pending) {
433
+ if (!s->ready_head && s->allow_poll) poll_ready(args[0]);
434
+ while (s->ready_head && !s->policy_pending) {
435
+ fs_wait *ready = s->ready_head;
436
+ if (ready->fiber == current) break;
437
+ ready_pop(s);
438
+ if (ready->status != 1) continue;
439
+ ready->status = 2; s->dispatch_left--;
440
+ next = ready->fiber;
441
+ break;
442
+ }
443
+ }
444
+ s->active = next == s->root ? Qnil : next;
445
+ rb_fiber_transfer(next, 0, NULL);
446
+ s->active = current;
447
+ fs_wait *w = fs_lookup(fs_get(args[0]), args[1]);
448
+ if (!w) rb_raise(rb_eRuntimeError, "retired scheduler suspension");
449
+ if (!NIL_P(w->error)) rb_exc_raise(w->error);
450
+ if (w->native_error) rb_syserr_fail(w->native_error, "scheduler completion");
451
+ return w->value;
452
+ }
453
+
454
+ // Retire the wait after a parked fiber resumes or raises.
455
+ static VALUE park_ensure(VALUE data) {
456
+ VALUE *args = (VALUE *)data; return fs_retire(args[0], args[1]);
457
+ }
458
+
459
+ // Suspend a fiber while leaving wait cleanup to the caller.
460
+ // Buffer transfers use this to keep cleanup in their outer protected scope.
461
+ VALUE fs_suspend(VALUE self, VALUE token) {
462
+ fs_get(self)->suspensions++;
463
+ VALUE args[] = {self, token}; return park_body((VALUE)args);
464
+ }
465
+
466
+ // Suspend a fiber and always retire its wait when control returns or raises.
467
+ VALUE fs_park(VALUE self, VALUE token) {
468
+ fs_get(self)->suspensions++;
469
+ VALUE args[] = {self, token}; return rb_ensure(park_body, (VALUE)args, park_ensure, (VALUE)args);
470
+ }
471
+
472
+ // Give other fibers a turn after 64 operations complete without waiting.
473
+ // Return the original value once this fiber runs again.
474
+ VALUE fs_checkpoint(VALUE self, VALUE value) {
475
+ fs_state *s = fs_get(self);
476
+ if (++s->immediate >= 64) {
477
+ s->immediate = 0;
478
+ VALUE token = fs_arm(self);
479
+ fs_resume(self, token, Qnil, Qnil);
480
+ fs_park(self, token);
481
+ }
482
+ return value;
483
+ }
484
+
485
+ // Watch each IO in the read, write, and priority groups for an IO.select call.
486
+ // Keep the original groups so the result can return the caller's objects.
487
+ static VALUE register_select(VALUE self, VALUE token, VALUE groups, VALUE ios) {
488
+ fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
489
+ w->groups = groups; w->ios = ios;
490
+ for (int i = 0; i < 3; i++) {
491
+ VALUE list = rb_ary_entry(ios, i);
492
+ for (long j = 0; j < RARRAY_LEN(list); j++)
493
+ fs_watch_io(s, w, rb_io_descriptor(rb_ary_entry(list, j)), i == 0 ? FS_READ : i == 1 ? FS_WRITE : FS_PRI, i, j);
494
+ }
495
+ return Qnil;
496
+ }
497
+
498
+ // Report whether any wait is queued to run.
499
+ static VALUE ready(VALUE self) { return fs_get(self)->ready_head ? Qtrue : Qfalse; }
500
+
501
+ // Report whether any wait is still active, including waits already queued to run.
502
+ static VALUE pending(VALUE self) { return fs_get(self)->pending ? Qtrue : Qfalse; }
503
+
504
+ // Return the number of waits that have not yet been retired.
505
+ static VALUE pending_count(VALUE self) { return SIZET2NUM(fs_get(self)->pending); }
506
+
507
+ // Ask the dispatcher to return to Ruby policy code and wake a blocked driver.
508
+ // Guard the pipe write so another thread can safely call this during shutdown.
509
+ static VALUE wakeup(VALUE self) {
510
+ fs_state *s = fs_get(self); char byte = 0;
511
+ s->policy_pending = 1;
512
+ pthread_mutex_lock(&s->wake_mutex);
513
+ if (!s->closed) { ssize_t result = write(s->wake[1], &byte, 1); (void)result; }
514
+ pthread_mutex_unlock(&s->wake_mutex);
515
+ return Qtrue;
516
+ }
517
+
518
+ // Turn recorded readiness into a wait result.
519
+ // IO.select needs a fresh grouped snapshot to preserve Ruby's result format.
520
+ static int settle(st_data_t key, st_data_t value, st_data_t arg) {
521
+ fs_wait *w = (fs_wait *)value;
522
+ if (!w->selected || w->status) return ST_CONTINUE;
523
+ VALUE self = (VALUE)arg;
524
+ if (NIL_P(w->groups)) fs_resume(self, w->token, INT2NUM(w->events), Qnil);
525
+ else {
526
+ VALUE found = rb_funcall(self, rb_intern("select_result"), 3, w->groups, w->ios, INT2NUM(0));
527
+ if (!NIL_P(found)) fs_resume(self, w->token, found, Qnil);
528
+ }
529
+ w->events = w->selected = 0;
530
+ return ST_CONTINUE;
531
+ }
532
+
533
+ // Check whether an IO is closed.
534
+ // Also accept nested arrays so the same check works for IO.select groups.
535
+ static int closed_io(VALUE io) {
536
+ if (RB_TYPE_P(io, T_ARRAY)) {
537
+ for (long i = 0; i < RARRAY_LEN(io); i++) if (closed_io(rb_ary_entry(io, i))) return 1;
538
+ return 0;
539
+ }
540
+ return RB_TYPE_P(io, T_FILE) && (!RFILE(io)->fptr || RTEST(rb_io_closed_p(io)));
541
+ }
542
+
543
+ // Give a pending wait an IOError if any of its IO objects has closed.
544
+ static int cancel_closed(st_data_t key, st_data_t value, st_data_t arg) {
545
+ fs_wait *w = (fs_wait *)value;
546
+ if (!w->status && !NIL_P(w->ios) && closed_io(w->ios))
547
+ interrupt((VALUE)arg, w->token, rb_exc_new_cstr(rb_eIOError, "stream closed while waiting"));
548
+ return ST_CONTINUE;
549
+ }
550
+
551
+ // Check priority events with poll when the driver cannot report them directly.
552
+ // Record readiness or interrupt waits whose descriptor has become invalid.
553
+ static int snapshot_fallback(st_data_t key, st_data_t value, st_data_t arg) {
554
+ fs_reg *reg = (fs_reg *)value;
555
+ if (!(reg->poll_events & reg->events)) return ST_CONTINUE;
556
+ struct pollfd fd = {.fd = reg->fd, .events = POLLPRI};
557
+ int result = poll(&fd, 1, 0);
558
+ if (result < 0 && errno != EINTR) rb_sys_fail("scheduler compatibility snapshot");
559
+ for (fs_watch *watch = reg->watches; watch; watch = watch->next_reg) {
560
+ fs_wait *w = watch->wait;
561
+ if (w->status) continue;
562
+ if (fd.revents & POLLNVAL)
563
+ interrupt((VALUE)arg, w->token, rb_exc_new_cstr(rb_eIOError, "stream closed while waiting"));
564
+ else if ((fd.revents & POLLPRI) && (watch->events & FS_PRI)) {
565
+ w->selected = 1; w->events |= FS_PRI;
566
+ }
567
+ }
568
+ return ST_CONTINUE;
569
+ }
570
+
571
+ // Match a driver event to its live wait or descriptor registration.
572
+ // Ignore old generations, queue ready fibers, and record grouped IO.select events.
573
+ // Wake-pipe events ask the dispatcher to return to Ruby policy code.
574
+ static void accept_event(VALUE self, fs_event *e) {
575
+ fs_state *s = fs_get(self);
576
+ if (e->generation & FS_RECEIVE) {
577
+ fs_wait *w = fs_lookup(s, ULL2NUM(e->generation & ~FS_RECEIVE));
578
+ if (!w) return;
579
+ w->receive_pending = 0;
580
+ if (!w->status) fs_resume(self, w->token, INT2NUM(e->error), Qnil);
581
+ return;
582
+ }
583
+ st_data_t entry;
584
+ if (!st_lookup(s->generations, e->generation, &entry)) return;
585
+ fs_reg *reg = (fs_reg *)entry;
586
+ if (reg->fd == s->wake[0]) {
587
+ char buffer[4096]; while (read(s->wake[0], buffer, sizeof(buffer)) > 0) {}
588
+ s->policy_pending = 1;
589
+ } else {
590
+ for (fs_watch *watch = reg->watches; watch; watch = watch->next_reg) {
591
+ fs_wait *w = watch->wait;
592
+ if (w->status) continue;
593
+ if (e->error) {
594
+ w->native_error = e->error;
595
+ fs_resume(self, w->token, Qnil, Qnil);
596
+ } else if (watch->events & e->events) {
597
+ if (NIL_P(w->groups)) fs_resume(self, w->token, INT2FIX(watch->events & e->events), Qnil);
598
+ else { w->selected = 1; w->events |= watch->events & e->events; s->selected_pending = 1; }
599
+ }
600
+ }
601
+ }
602
+ if (s->driver == FS_URING_DRIVER) reg->events = 0;
603
+ }
604
+
605
+ // Restore io_uring readiness watches after their one-time notifications fire.
606
+ // Only keep watching events that still have pending waiters.
607
+ static void rearm_events(fs_state *s) {
608
+ #ifdef FARCE_URING
609
+ if (s->driver != FS_URING_DRIVER) return;
610
+ for (int i = 0; i < s->event_count; i++) {
611
+ st_data_t entry;
612
+ if (!st_lookup(s->generations, s->events[i].generation, &entry)) continue;
613
+ fs_reg *reg = (fs_reg *)entry;
614
+ if (reg->events) continue;
615
+ int events = reg->fd == s->wake[0] ? FS_READ : 0;
616
+ for (fs_watch *watch = reg->watches; watch; watch = watch->next_reg)
617
+ if (!watch->wait->status) events |= watch->events;
618
+ if (!events) continue;
619
+ int error = fs_driver_update(s, reg, events);
620
+ if (error) { s->poll_error = error; s->policy_pending = 1; return; }
621
+ }
622
+ #endif
623
+ }
624
+
625
+ // Collect driver events without waiting and restore any io_uring watches.
626
+ // Ask Ruby policy code to run if there is an error or an IO.select result to build.
627
+ static void poll_ready(VALUE self) {
628
+ fs_state *s = fs_get(self);
629
+ s->timeout = 0;
630
+ fs_driver_poll(s);
631
+ for (int i = 0; i < s->event_count; i++) accept_event(self, &s->events[i]);
632
+ rearm_events(s);
633
+ if (s->poll_error || s->selected_pending) s->policy_pending = 1;
634
+ }
635
+
636
+ // Cancel an io_uring receive and wait until the original operation has finished.
637
+ // Keep the caller's buffer locked throughout, because a cancellation reply alone
638
+ // does not prove the kernel has stopped writing to it.
639
+ void fs_cancel_receive(VALUE self, fs_wait *w) {
640
+ #ifdef FARCE_URING
641
+ fs_state *s = fs_get(self);
642
+ struct io_uring_sqe *entry = io_uring_get_sqe(&s->ring);
643
+ if (!entry) {
644
+ int result;
645
+ do { result = io_uring_submit(&s->ring); } while (result == -EINTR);
646
+ if (result < 0) rb_bug("cannot submit pending scheduler receive");
647
+ entry = io_uring_get_sqe(&s->ring);
648
+ if (!entry) rb_bug("cannot reserve scheduler cancellation");
649
+ }
650
+ io_uring_prep_cancel64(entry, FS_RECEIVE | NUM2ULL(w->token), 0);
651
+ entry->user_data = 0;
652
+ // Wait for the original receive CQE, not just the cancellation CQE. No Ruby
653
+ // fiber runs here, and the caller's buffer remains locked throughout.
654
+ while (w->receive_pending) {
655
+ int result = io_uring_submit_and_wait(&s->ring, 1);
656
+ if (result < 0 && result != -EINTR) rb_bug("cannot acknowledge scheduler receive cancellation");
657
+ s->timeout = 0;
658
+ fs_driver_poll(s);
659
+ if (s->poll_error && s->poll_error != EINTR) rb_bug("cannot drain scheduler receive cancellation");
660
+ for (int i = 0; i < s->event_count; i++) accept_event(self, &s->events[i]);
661
+ rearm_events(s);
662
+ }
663
+ #endif
664
+ }
665
+
666
+ // Call a helper for every active wait, skipping empty slots and idle fibers.
667
+ static void each_wait(fs_state *s, int (*callback)(st_data_t, st_data_t, st_data_t), st_data_t arg) {
668
+ for (uint32_t i = 0; i < s->slot_count; i++) {
669
+ fs_wait *w = s->slots[i].wait;
670
+ if (w && !NIL_P(w->token)) callback((st_data_t)w->token, (st_data_t)w, arg);
671
+ }
672
+ }
673
+
674
+ // Collect IO events and run ready fibers up to the requested budget.
675
+ // Release Ruby's VM lock if the driver needs to wait.
676
+ // Return to Ruby policy code for timers, notifications, or shutdown work.
677
+ static VALUE dispatch(VALUE self, VALUE timeout, VALUE budget) {
678
+ rb_funcall(self, rb_intern("check_root"), 0);
679
+ fs_state *s = fs_get(self);
680
+ if (s->closed) rb_raise(rb_eIOError, "scheduler is closed");
681
+ int limit = NUM2INT(budget), polled = 0;
682
+ s->policy_pending = 0;
683
+ s->allow_poll = NIL_P(timeout);
684
+ if (s->selected_pending) {
685
+ s->selected_pending = 0;
686
+ each_wait(s, settle, (st_data_t)self);
687
+ }
688
+ struct timespec now;
689
+ clock_gettime(CLOCK_MONOTONIC, &now);
690
+ double seconds = now.tv_sec + now.tv_nsec * 1e-9;
691
+ if (seconds >= s->close_check_at) {
692
+ each_wait(s, cancel_closed, (st_data_t)self);
693
+ s->close_check_at = seconds + 0.05;
694
+ }
695
+ if (s->ready_head && ++s->ready_batches < 4) goto drain_ready;
696
+ s->ready_batches = 0;
697
+ s->timeout = s->ready_head ? 0 : NIL_P(timeout) ? -1 : NUM2DBL(timeout);
698
+ // Closing an IO in another thread can remove its OS registration without
699
+ // delivering an event. Bound the interval before checking Ruby IO ownership.
700
+ if (s->pending && (s->timeout < 0 || s->timeout > 0.05)) s->timeout = 0.05;
701
+ polled = 1;
702
+ double blocking_timeout = s->timeout;
703
+ s->timeout = 0;
704
+ fs_driver_poll(s);
705
+ if (!s->event_count && !s->poll_error && blocking_timeout != 0) {
706
+ s->timeout = blocking_timeout;
707
+ rb_thread_call_without_gvl(fs_driver_poll, s, RUBY_UBF_IO, NULL);
708
+ }
709
+ if (s->poll_error && s->poll_error != EINTR) rb_syserr_fail(s->poll_error, "scheduler poll");
710
+ for (int i = 0; i < s->event_count; i++) accept_event(self, &s->events[i]);
711
+ if (s->polling_regs) st_foreach(s->regs, snapshot_fallback, (st_data_t)self);
712
+ // Only IO.select needs a grouped snapshot. Ordinary readiness is settled
713
+ // directly from the event above instead of scanning all pending waits.
714
+ if (s->polling_regs || s->selected_pending) {
715
+ s->selected_pending = 0;
716
+ each_wait(s, settle, (st_data_t)self);
717
+ }
718
+ drain_ready:;
719
+ if (polled) rearm_events(s);
720
+ s->dispatch_left = limit;
721
+ while (s->dispatch_left > 0 && !s->policy_pending) {
722
+ fs_wait *w = ready_pop(s);
723
+ if (!w && s->allow_poll && s->pending) {
724
+ poll_ready(self);
725
+ if (!s->policy_pending) w = ready_pop(s);
726
+ }
727
+ if (!w) break;
728
+ if (w->status != 1) continue;
729
+ w->status = 2;
730
+ s->dispatch_left--;
731
+ transfer_to(s, w->fiber);
732
+ }
733
+ int count = limit - s->dispatch_left;
734
+ s->dispatch_left = 0;
735
+ if (s->poll_error && s->poll_error != EINTR) rb_syserr_fail(s->poll_error, "scheduler poll");
736
+ return INT2NUM(count);
737
+ }
738
+
739
+ // Release the scheduler's native resources and clear its active fiber.
740
+ // The Ruby object remains available for the rest of shutdown.
741
+ static VALUE destroy(VALUE self) {
742
+ fs_state *s = fs_get(self); release(s); s->active = Qnil;
743
+ if (s->fibers) st_clear(s->fibers);
744
+ s->ready_head = s->ready_tail = NULL;
745
+ return Qnil;
746
+ }
747
+
748
+ // Remove a finished fiber's wait record and return its slot to the free list.
749
+ // Retire any remaining wait before freeing the record.
750
+ static VALUE finish_fiber(VALUE self) {
751
+ fs_state *s = fs_get(self);
752
+ st_data_t key = rb_fiber_current(), entry;
753
+ if (st_lookup(s->fibers, key, &entry)) {
754
+ fs_wait *w = (fs_wait *)entry;
755
+ if (!NIL_P(w->token)) fs_retire(self, w->token);
756
+ st_delete(s->fibers, &key, NULL);
757
+ s->slots[w->slot].wait = NULL;
758
+ s->slots[w->slot].next_free = s->first_free;
759
+ s->first_free = w->slot;
760
+ xfree(w);
761
+ }
762
+ s->active = Qnil;
763
+ return Qnil;
764
+ }
765
+
766
+ // Allow up to 4096 fiber dispatches before returning to Ruby policy code.
767
+ static VALUE dispatch_budget(VALUE self) { return INT2FIX(4096); }
768
+
769
+ // End the current dispatch batch so Ruby can process a policy change.
770
+ static VALUE policy_changed(VALUE self) { fs_get(self)->policy_pending = 1; return Qnil; }
771
+
772
+ // Allow admitted fibers to finish while Ruby removes this scheduler from the thread.
773
+ static VALUE begin_close(VALUE self) { fs_get(self)->closing = 1; return Qnil; }
774
+
775
+ // Reject new waits and stop the current dispatch batch for shutdown.
776
+ static VALUE begin_shutdown(VALUE self) {
777
+ fs_state *s = fs_get(self); s->stopping = 1; s->dispatch_left = 0; return Qnil;
778
+ }
779
+
780
+ // Return the name of the driver selected during initialization.
781
+ static VALUE backend(VALUE self) { return fs_get(self)->backend; }
782
+
783
+ // Return the recorded fallback reason, or nil when there is none.
784
+ static VALUE fallback(VALUE self) { return fs_get(self)->fallback; }
785
+
786
+ // Reject dup and clone because native scheduler resources have a single owner.
787
+ static VALUE no_copy(int argc, VALUE *argv, VALUE self) { rb_raise(rb_eTypeError, "scheduler ownership cannot be copied"); }
788
+
789
+ // Define the native scheduler class and connect its Ruby methods to these C functions.
790
+ // Include the shared Ruby IO helpers and install the native IO hooks.
791
+ void farce_init_fiber_scheduler(VALUE internal) {
792
+ // Each scheduler owns its mutable driver and wait state.
793
+ VALUE klass = rb_define_class_under(internal, "FiberScheduler", rb_cObject);
794
+ rb_include_module(klass, rb_const_get(internal, rb_intern("SchedulerIO")));
795
+ rb_define_alloc_func(klass, allocate);
796
+ rb_define_private_method(klass, "initialize", initialize, -1);
797
+ rb_define_private_method(klass, "initialize_copy", no_copy, -1);
798
+ rb_define_private_method(klass, "policy_changed", policy_changed, 0);
799
+ rb_define_private_method(klass, "dispatch_budget", dispatch_budget, 0);
800
+ rb_define_private_method(klass, "finish_fiber", finish_fiber, 0);
801
+ rb_define_private_method(klass, "begin_close", begin_close, 0);
802
+ rb_define_private_method(klass, "begin_shutdown", begin_shutdown, 0);
803
+ rb_define_private_method(klass, "start_fiber", start_fiber, 1);
804
+ rb_define_private_method(klass, "arm_wait", fs_arm, 0);
805
+ rb_define_private_method(klass, "current_wait", current_wait, 1);
806
+ rb_define_private_method(klass, "park_current", fs_park, 1);
807
+ rb_define_private_method(klass, "resume_wait", resume_method, -1);
808
+ rb_define_private_method(klass, "interrupt_wait", interrupt, 2);
809
+ rb_define_private_method(klass, "retire_wait", fs_retire, 1);
810
+ rb_define_private_method(klass, "register_select", register_select, 3);
811
+ rb_define_private_method(klass, "ready?", ready, 0);
812
+ rb_define_private_method(klass, "pending?", pending, 0);
813
+ rb_define_private_method(klass, "pending_count", pending_count, 0);
814
+ rb_define_private_method(klass, "wakeup", wakeup, 0);
815
+ rb_define_private_method(klass, "dispatch", dispatch, 2);
816
+ rb_define_private_method(klass, "destroy", destroy, 0);
817
+ rb_define_method(klass, "backend", backend, 0);
818
+ rb_define_method(klass, "fallback_reason", fallback, 0);
819
+ fs_init_io(klass);
820
+ }