farce 0.0.1.alpha2-x86-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +26 -0
  3. data/CONTRIBUTING.md +71 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +1524 -0
  6. data/SECURITY.md +10 -0
  7. data/docs/benchmarks.md +185 -0
  8. data/docs/gems/dry-types.md +290 -0
  9. data/docs/gems/msgpack.md +70 -0
  10. data/docs/gems/ractor-shim.md +63 -0
  11. data/docs/modes.md +628 -0
  12. data/docs/scopes.md +649 -0
  13. data/docs/variants.md +346 -0
  14. data/ext/ext_helper.rb +20 -0
  15. data/ext/farce/README.md +21 -0
  16. data/ext/farce/atom.c +1023 -0
  17. data/ext/farce/bounded_map.c +1683 -0
  18. data/ext/farce/containers.h +77 -0
  19. data/ext/farce/counter.c +399 -0
  20. data/ext/farce/darwin.c +100 -0
  21. data/ext/farce/depend +12 -0
  22. data/ext/farce/dict.c +1523 -0
  23. data/ext/farce/dict.h +152 -0
  24. data/ext/farce/drivers.c +237 -0
  25. data/ext/farce/exchanger.c +299 -0
  26. data/ext/farce/extconf.rb +99 -0
  27. data/ext/farce/farce.c +359 -0
  28. data/ext/farce/flag.c +288 -0
  29. data/ext/farce/io.c +338 -0
  30. data/ext/farce/lock.c +510 -0
  31. data/ext/farce/map.c +2240 -0
  32. data/ext/farce/priority_queue.c +2056 -0
  33. data/ext/farce/queue.c +1059 -0
  34. data/ext/farce/reactor.c +820 -0
  35. data/ext/farce/reactor.h +103 -0
  36. data/ext/farce/shareable.h +31 -0
  37. data/ext/farce/signal.c +350 -0
  38. data/ext/farce/transaction.c +354 -0
  39. data/ext/farce/transaction.h +40 -0
  40. data/ext/farce/tree_map.c +1953 -0
  41. data/ext/farce/trie.c +2020 -0
  42. data/ext/farce/unshareable.c +155 -0
  43. data/ext/farce/unshared_io_pool.h +234 -0
  44. data/ext/farce/unshared_signal.c +263 -0
  45. data/ext/farce/unshared_wait.h +193 -0
  46. data/ext/farce/unsupported.c +6 -0
  47. data/ext/farce/vector.c +1195 -0
  48. data/ext/farce/weak_map.c +1714 -0
  49. data/ext/java/org/farce/BoundedMap.java +394 -0
  50. data/ext/java/org/farce/FiberScheduler.java +141 -0
  51. data/ext/java/org/farce/PriorityKey.java +45 -0
  52. data/ext/java/org/farce/PriorityQueue.java +373 -0
  53. data/ext/java/org/farce/QueueSignal.java +28 -0
  54. data/ext/rebind/README.md +14 -0
  55. data/ext/rebind/extconf.rb +10 -0
  56. data/ext/rebind/rebind.c +186 -0
  57. data/lib/farce/_yard/internal.rb +13 -0
  58. data/lib/farce/_yard/macros.rb +61 -0
  59. data/lib/farce/_yard/ractor.rb +46 -0
  60. data/lib/farce/abstract/atom.rb +186 -0
  61. data/lib/farce/abstract/bounded_map.rb +232 -0
  62. data/lib/farce/abstract/collection.rb +151 -0
  63. data/lib/farce/abstract/concurrent_map.rb +381 -0
  64. data/lib/farce/abstract/counter.rb +193 -0
  65. data/lib/farce/abstract/duplicable_map.rb +229 -0
  66. data/lib/farce/abstract/exchanger.rb +26 -0
  67. data/lib/farce/abstract/flag.rb +71 -0
  68. data/lib/farce/abstract/lazy.rb +115 -0
  69. data/lib/farce/abstract/lease.rb +104 -0
  70. data/lib/farce/abstract/lease_map.rb +261 -0
  71. data/lib/farce/abstract/lease_pool.rb +91 -0
  72. data/lib/farce/abstract/lfu_map.rb +20 -0
  73. data/lib/farce/abstract/lru_map.rb +25 -0
  74. data/lib/farce/abstract/map.rb +345 -0
  75. data/lib/farce/abstract/molecule.rb +245 -0
  76. data/lib/farce/abstract/port.rb +74 -0
  77. data/lib/farce/abstract/priority_queue.rb +117 -0
  78. data/lib/farce/abstract/queue.rb +269 -0
  79. data/lib/farce/abstract/scheduler.rb +111 -0
  80. data/lib/farce/abstract/set.rb +910 -0
  81. data/lib/farce/abstract/sorted_set.rb +172 -0
  82. data/lib/farce/abstract/timer_queue.rb +136 -0
  83. data/lib/farce/abstract/tree_map.rb +269 -0
  84. data/lib/farce/abstract/value.rb +68 -0
  85. data/lib/farce/abstract/vector.rb +1126 -0
  86. data/lib/farce/abstract/weak_atom.rb +32 -0
  87. data/lib/farce/abstract/weak_key_map.rb +12 -0
  88. data/lib/farce/abstract/weak_map.rb +13 -0
  89. data/lib/farce/abstract/weak_set.rb +12 -0
  90. data/lib/farce/abstract/weak_value_map.rb +12 -0
  91. data/lib/farce/abstract.rb +17 -0
  92. data/lib/farce/atom.rb +293 -0
  93. data/lib/farce/class_mirror.rb +87 -0
  94. data/lib/farce/clock.rb +121 -0
  95. data/lib/farce/config.rb +229 -0
  96. data/lib/farce/counter.rb +71 -0
  97. data/lib/farce/deduper.rb +122 -0
  98. data/lib/farce/engine/jruby/bounded_map.rb +314 -0
  99. data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
  100. data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
  101. data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
  102. data/lib/farce/engine/jruby/map.rb +505 -0
  103. data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
  104. data/lib/farce/engine/jruby/signal.rb +147 -0
  105. data/lib/farce/engine/jruby.rb +63 -0
  106. data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
  107. data/lib/farce/engine/jvm/counter.rb +102 -0
  108. data/lib/farce/engine/jvm/extension.rb +32 -0
  109. data/lib/farce/engine/jvm/farce.jar +0 -0
  110. data/lib/farce/engine/jvm/flag.rb +79 -0
  111. data/lib/farce/engine/jvm/priority_queue.rb +217 -0
  112. data/lib/farce/engine/jvm/tree_map.rb +350 -0
  113. data/lib/farce/engine/jvm/types.rb +180 -0
  114. data/lib/farce/engine/jvm.rb +19 -0
  115. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  116. data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
  117. data/lib/farce/engine/ruby/3.4/port.rb +186 -0
  118. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
  119. data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
  120. data/lib/farce/engine/ruby/3.4/rebind.so +0 -0
  121. data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
  122. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  123. data/lib/farce/engine/ruby/4.0/port.rb +19 -0
  124. data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
  125. data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
  126. data/lib/farce/engine/ruby/4.0/rebind.so +0 -0
  127. data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
  128. data/lib/farce/engine/ruby/4.1/port.rb +21 -0
  129. data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
  130. data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
  131. data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
  132. data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
  133. data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
  134. data/lib/farce/engine/ruby/shared/lease.rb +26 -0
  135. data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
  136. data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
  137. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
  138. data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
  139. data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
  140. data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
  141. data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
  142. data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
  143. data/lib/farce/engine/ruby/shared/vault.rb +144 -0
  144. data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
  145. data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
  146. data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
  147. data/lib/farce/engine/ruby.rb +130 -0
  148. data/lib/farce/engine/shared/atom.rb +10 -0
  149. data/lib/farce/engine/shared/exchanger.rb +130 -0
  150. data/lib/farce/engine/shared/identity_key.rb +22 -0
  151. data/lib/farce/engine/shared/lease.rb +10 -0
  152. data/lib/farce/engine/shared/lease_pool.rb +10 -0
  153. data/lib/farce/engine/shared/main_scheduler.rb +20 -0
  154. data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
  155. data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
  156. data/lib/farce/engine/shared/port.rb +44 -0
  157. data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
  158. data/lib/farce/engine/shared/proxy_owner.rb +43 -0
  159. data/lib/farce/engine/shared/queue.rb +233 -0
  160. data/lib/farce/engine/shared/ractor_methods.rb +73 -0
  161. data/lib/farce/engine/shared/rebindable.rb +15 -0
  162. data/lib/farce/engine/shared/strict_atom.rb +73 -0
  163. data/lib/farce/engine/shared/strict_map.rb +117 -0
  164. data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
  165. data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
  166. data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
  167. data/lib/farce/engine/shared/trie.rb +317 -0
  168. data/lib/farce/engine/shared/trie_builder.rb +199 -0
  169. data/lib/farce/engine/shared/unshareable.rb +14 -0
  170. data/lib/farce/engine/shared/unshared_atom.rb +256 -0
  171. data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
  172. data/lib/farce/engine/shared/unshared_queue.rb +18 -0
  173. data/lib/farce/engine/shared/unshared_signal.rb +21 -0
  174. data/lib/farce/engine/shared/unshared_vector.rb +377 -0
  175. data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
  176. data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
  177. data/lib/farce/engine/shared/vault.rb +31 -0
  178. data/lib/farce/engine/shared/vector.rb +83 -0
  179. data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
  180. data/lib/farce/engine/shared/weak_atom.rb +20 -0
  181. data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
  182. data/lib/farce/engine/shared/weak_map/index.rb +249 -0
  183. data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
  184. data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
  185. data/lib/farce/engine/shared.rb +63 -0
  186. data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
  187. data/lib/farce/engine/truffleruby/lock.rb +121 -0
  188. data/lib/farce/engine/truffleruby/map.rb +656 -0
  189. data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
  190. data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
  191. data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
  192. data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
  193. data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
  194. data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
  195. data/lib/farce/engine/truffleruby/signal.rb +77 -0
  196. data/lib/farce/engine/truffleruby.rb +148 -0
  197. data/lib/farce/envelope.rb +335 -0
  198. data/lib/farce/error.rb +35 -0
  199. data/lib/farce/exchanger.rb +41 -0
  200. data/lib/farce/flag.rb +27 -0
  201. data/lib/farce/integrations/active_support/blank.rb +68 -0
  202. data/lib/farce/integrations/active_support/clock.rb +17 -0
  203. data/lib/farce/integrations/active_support/duplicable.rb +78 -0
  204. data/lib/farce/integrations/active_support/map.rb +175 -0
  205. data/lib/farce/integrations/active_support/set.rb +60 -0
  206. data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
  207. data/lib/farce/integrations/active_support/vector.rb +231 -0
  208. data/lib/farce/integrations/active_support.rb +9 -0
  209. data/lib/farce/integrations/activesupport.rb +5 -0
  210. data/lib/farce/integrations/bson.rb +108 -0
  211. data/lib/farce/integrations/cbor.rb +51 -0
  212. data/lib/farce/integrations/concurrent.rb +112 -0
  213. data/lib/farce/integrations/dry-types.rb +5 -0
  214. data/lib/farce/integrations/dry_types.rb +694 -0
  215. data/lib/farce/integrations/json.rb +7 -0
  216. data/lib/farce/integrations/msgpack.rb +112 -0
  217. data/lib/farce/integrations/oj.rb +69 -0
  218. data/lib/farce/integrations/psych.rb +191 -0
  219. data/lib/farce/integrations/ractor-sharing.rb +5 -0
  220. data/lib/farce/integrations/ractor-tmvar.rb +5 -0
  221. data/lib/farce/integrations/ractor_sharing.rb +172 -0
  222. data/lib/farce/integrations/ractor_tmvar.rb +64 -0
  223. data/lib/farce/integrations/shared/to_json.rb +42 -0
  224. data/lib/farce/integrations/sorted_set.rb +11 -0
  225. data/lib/farce/integrations/weakref.rb +38 -0
  226. data/lib/farce/integrations/yajl.rb +10 -0
  227. data/lib/farce/integrations.rb +156 -0
  228. data/lib/farce/internal/_frozen_config.rb +11 -0
  229. data/lib/farce/internal/autoloads.rb +47 -0
  230. data/lib/farce/internal/blocking_priority_queue.rb +122 -0
  231. data/lib/farce/internal/converter.rb +106 -0
  232. data/lib/farce/internal/copyable.rb +31 -0
  233. data/lib/farce/internal/delegation.rb +20 -0
  234. data/lib/farce/internal/external_transaction.rb +59 -0
  235. data/lib/farce/internal/fake_ractor.rb +179 -0
  236. data/lib/farce/internal/freeze.rb +118 -0
  237. data/lib/farce/internal/inspect.rb +157 -0
  238. data/lib/farce/internal/key_lock_map.rb +29 -0
  239. data/lib/farce/internal/key_normalizer.rb +289 -0
  240. data/lib/farce/internal/lease_initialization.rb +115 -0
  241. data/lib/farce/internal/lease_map.rb +441 -0
  242. data/lib/farce/internal/lease_pool_state.rb +228 -0
  243. data/lib/farce/internal/lease_state.rb +342 -0
  244. data/lib/farce/internal/lease_waiting.rb +13 -0
  245. data/lib/farce/internal/managed_queue.rb +26 -0
  246. data/lib/farce/internal/map_value_modes.rb +227 -0
  247. data/lib/farce/internal/marshal_support.rb +227 -0
  248. data/lib/farce/internal/mixin.rb +20 -0
  249. data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
  250. data/lib/farce/internal/noncopyable.rb +17 -0
  251. data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
  252. data/lib/farce/internal/pool_supervisor.rb +41 -0
  253. data/lib/farce/internal/pool_worker.rb +61 -0
  254. data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
  255. data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
  256. data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
  257. data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
  258. data/lib/farce/internal/portable_transaction.rb +164 -0
  259. data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
  260. data/lib/farce/internal/reservation_waiting.rb +24 -0
  261. data/lib/farce/internal/scheduled_task.rb +48 -0
  262. data/lib/farce/internal/scheduler_io.rb +171 -0
  263. data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
  264. data/lib/farce/internal/select_scheduler.rb +235 -0
  265. data/lib/farce/internal/storage.rb +162 -0
  266. data/lib/farce/internal/strict_lease.rb +30 -0
  267. data/lib/farce/internal/strict_lease_map.rb +20 -0
  268. data/lib/farce/internal/strict_lease_pool.rb +29 -0
  269. data/lib/farce/internal/thread_pool.rb +166 -0
  270. data/lib/farce/internal/transaction_conflict.rb +9 -0
  271. data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
  272. data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
  273. data/lib/farce/internal/undefined.rb +22 -0
  274. data/lib/farce/internal/unshared_lease.rb +29 -0
  275. data/lib/farce/internal/unshared_lease_pool.rb +34 -0
  276. data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
  277. data/lib/farce/internal/value_serialization.rb +13 -0
  278. data/lib/farce/internal/weak_map_value_modes.rb +58 -0
  279. data/lib/farce/internal/weak_mode_manager.rb +26 -0
  280. data/lib/farce/internal.rb +129 -0
  281. data/lib/farce/lazy.rb +100 -0
  282. data/lib/farce/lazy_ref.rb +40 -0
  283. data/lib/farce/lease.rb +28 -0
  284. data/lib/farce/lease_map.rb +34 -0
  285. data/lib/farce/lease_pool.rb +29 -0
  286. data/lib/farce/lfu_map.rb +53 -0
  287. data/lib/farce/local/atom.rb +23 -0
  288. data/lib/farce/local/counter.rb +88 -0
  289. data/lib/farce/local/flag.rb +65 -0
  290. data/lib/farce/local/lazy.rb +38 -0
  291. data/lib/farce/local/lazy_ref.rb +36 -0
  292. data/lib/farce/local/lease.rb +77 -0
  293. data/lib/farce/local/lease_map.rb +80 -0
  294. data/lib/farce/local/lease_pool.rb +38 -0
  295. data/lib/farce/local/lfu_map.rb +55 -0
  296. data/lib/farce/local/lru_map.rb +62 -0
  297. data/lib/farce/local/map.rb +47 -0
  298. data/lib/farce/local/molecule.rb +45 -0
  299. data/lib/farce/local/priority_queue.rb +53 -0
  300. data/lib/farce/local/queue.rb +32 -0
  301. data/lib/farce/local/scoped.rb +166 -0
  302. data/lib/farce/local/set.rb +18 -0
  303. data/lib/farce/local/sorted_set.rb +50 -0
  304. data/lib/farce/local/timer_queue.rb +41 -0
  305. data/lib/farce/local/tree_map.rb +47 -0
  306. data/lib/farce/local/vector.rb +30 -0
  307. data/lib/farce/local/weak_atom.rb +23 -0
  308. data/lib/farce/local/weak_key_map.rb +39 -0
  309. data/lib/farce/local/weak_map.rb +39 -0
  310. data/lib/farce/local/weak_set.rb +18 -0
  311. data/lib/farce/local/weak_value_map.rb +39 -0
  312. data/lib/farce/local.rb +35 -0
  313. data/lib/farce/lock.rb +54 -0
  314. data/lib/farce/lru_map.rb +63 -0
  315. data/lib/farce/map.rb +58 -0
  316. data/lib/farce/mode_manager.rb +142 -0
  317. data/lib/farce/molecule.rb +60 -0
  318. data/lib/farce/mutable.rb +171 -0
  319. data/lib/farce/pool.rb +332 -0
  320. data/lib/farce/port.rb +134 -0
  321. data/lib/farce/priority_queue.rb +70 -0
  322. data/lib/farce/proxy/register.rb +101 -0
  323. data/lib/farce/proxy/supervisor.rb +137 -0
  324. data/lib/farce/proxy/wrapper.rb +49 -0
  325. data/lib/farce/proxy.rb +291 -0
  326. data/lib/farce/queue.rb +60 -0
  327. data/lib/farce/ractor.rb +269 -0
  328. data/lib/farce/read_write_lock.rb +256 -0
  329. data/lib/farce/reference.rb +158 -0
  330. data/lib/farce/resolv/dns.rb +42 -0
  331. data/lib/farce/resolv.rb +85 -0
  332. data/lib/farce/scheduler.rb +536 -0
  333. data/lib/farce/set.rb +39 -0
  334. data/lib/farce/shareable.rb +150 -0
  335. data/lib/farce/signal.rb +97 -0
  336. data/lib/farce/sorted_set.rb +36 -0
  337. data/lib/farce/strict/atom.rb +34 -0
  338. data/lib/farce/strict/counter.rb +10 -0
  339. data/lib/farce/strict/exchanger.rb +25 -0
  340. data/lib/farce/strict/flag.rb +10 -0
  341. data/lib/farce/strict/lazy.rb +26 -0
  342. data/lib/farce/strict/lazy_ref.rb +26 -0
  343. data/lib/farce/strict/lease.rb +27 -0
  344. data/lib/farce/strict/lease_map.rb +33 -0
  345. data/lib/farce/strict/lease_pool.rb +28 -0
  346. data/lib/farce/strict/lfu_map.rb +27 -0
  347. data/lib/farce/strict/lru_map.rb +28 -0
  348. data/lib/farce/strict/map.rb +54 -0
  349. data/lib/farce/strict/molecule.rb +16 -0
  350. data/lib/farce/strict/port.rb +40 -0
  351. data/lib/farce/strict/priority_queue.rb +20 -0
  352. data/lib/farce/strict/queue.rb +17 -0
  353. data/lib/farce/strict/set.rb +14 -0
  354. data/lib/farce/strict/sorted_set.rb +19 -0
  355. data/lib/farce/strict/timer_queue.rb +13 -0
  356. data/lib/farce/strict/tree_map.rb +27 -0
  357. data/lib/farce/strict/vector.rb +24 -0
  358. data/lib/farce/strict/weak_atom.rb +31 -0
  359. data/lib/farce/strict/weak_key_map.rb +49 -0
  360. data/lib/farce/strict/weak_map.rb +49 -0
  361. data/lib/farce/strict/weak_set.rb +14 -0
  362. data/lib/farce/strict/weak_value_map.rb +48 -0
  363. data/lib/farce/strict.rb +28 -0
  364. data/lib/farce/system.rb +40 -0
  365. data/lib/farce/thread_scheduler.rb +70 -0
  366. data/lib/farce/timer_queue.rb +66 -0
  367. data/lib/farce/transaction/atom.rb +73 -0
  368. data/lib/farce/transaction/map.rb +25 -0
  369. data/lib/farce/transaction/map_operations.rb +143 -0
  370. data/lib/farce/transaction/molecule.rb +90 -0
  371. data/lib/farce/transaction/mutable.rb +61 -0
  372. data/lib/farce/transaction/set.rb +17 -0
  373. data/lib/farce/transaction/set_operations.rb +48 -0
  374. data/lib/farce/transaction/sorted_set.rb +18 -0
  375. data/lib/farce/transaction/tree_map.rb +78 -0
  376. data/lib/farce/transaction/vector.rb +139 -0
  377. data/lib/farce/transaction/wrapper.rb +202 -0
  378. data/lib/farce/transaction.rb +343 -0
  379. data/lib/farce/tree_map.rb +56 -0
  380. data/lib/farce/unsafe/lfu_map.rb +36 -0
  381. data/lib/farce/unsafe/lru_map.rb +36 -0
  382. data/lib/farce/unsafe/tree_map.rb +21 -0
  383. data/lib/farce/unsafe.rb +29 -0
  384. data/lib/farce/unshareable.rb +67 -0
  385. data/lib/farce/unshared/atom.rb +24 -0
  386. data/lib/farce/unshared/counter.rb +10 -0
  387. data/lib/farce/unshared/flag.rb +10 -0
  388. data/lib/farce/unshared/lazy.rb +38 -0
  389. data/lib/farce/unshared/lazy_ref.rb +26 -0
  390. data/lib/farce/unshared/lease.rb +27 -0
  391. data/lib/farce/unshared/lease_map.rb +26 -0
  392. data/lib/farce/unshared/lease_pool.rb +25 -0
  393. data/lib/farce/unshared/lfu_map.rb +20 -0
  394. data/lib/farce/unshared/lru_map.rb +20 -0
  395. data/lib/farce/unshared/map.rb +49 -0
  396. data/lib/farce/unshared/molecule.rb +16 -0
  397. data/lib/farce/unshared/priority_queue.rb +16 -0
  398. data/lib/farce/unshared/queue.rb +27 -0
  399. data/lib/farce/unshared/set.rb +14 -0
  400. data/lib/farce/unshared/sorted_set.rb +28 -0
  401. data/lib/farce/unshared/timer_queue.rb +16 -0
  402. data/lib/farce/unshared/tree_map.rb +21 -0
  403. data/lib/farce/unshared/vector.rb +19 -0
  404. data/lib/farce/unshared/weak_atom.rb +31 -0
  405. data/lib/farce/unshared/weak_key_map.rb +44 -0
  406. data/lib/farce/unshared/weak_map.rb +44 -0
  407. data/lib/farce/unshared/weak_set.rb +14 -0
  408. data/lib/farce/unshared/weak_value_map.rb +43 -0
  409. data/lib/farce/unshared.rb +28 -0
  410. data/lib/farce/vector.rb +228 -0
  411. data/lib/farce/version.rb +8 -0
  412. data/lib/farce/walker/definitions.rb +186 -0
  413. data/lib/farce/walker/modification.rb +355 -0
  414. data/lib/farce/walker.rb +319 -0
  415. data/lib/farce/weak_atom.rb +121 -0
  416. data/lib/farce/weak_key_map.rb +31 -0
  417. data/lib/farce/weak_map.rb +33 -0
  418. data/lib/farce/weak_ref.rb +67 -0
  419. data/lib/farce/weak_set.rb +82 -0
  420. data/lib/farce/weak_value.rb +195 -0
  421. data/lib/farce/weak_value_map.rb +32 -0
  422. data/lib/farce.rb +519 -0
  423. metadata +468 -0
data/ext/farce/flag.c ADDED
@@ -0,0 +1,288 @@
1
+ #include "containers.h"
2
+ #include "transaction.h"
3
+
4
+ #include <stdatomic.h>
5
+
6
+ #if ATOMIC_BOOL_LOCK_FREE == 2
7
+ #define CONTAINERS_FLAG_LOCK_FREE 1
8
+ #else
9
+ #define CONTAINERS_FLAG_LOCK_FREE 0
10
+ #endif
11
+
12
+ static VALUE cFlag;
13
+
14
+ typedef struct {
15
+ #if CONTAINERS_FLAG_LOCK_FREE
16
+ atomic_bool value;
17
+ #else
18
+ pthread_mutex_t lock;
19
+ bool value;
20
+ #endif
21
+ bool initialized;
22
+ } flag_t;
23
+
24
+ /* On platforms with always-lock-free boolean atomics, flag operations only
25
+ * linearize this boolean value. Relaxed ordering does not publish or
26
+ * synchronize accesses to any other shared state. Platforms without that
27
+ * guarantee use a mutex. */
28
+
29
+ static void
30
+ flag_free(void *pointer)
31
+ {
32
+ #if !CONTAINERS_FLAG_LOCK_FREE
33
+ flag_t *flag = pointer;
34
+ if (flag) pthread_mutex_destroy(&flag->lock);
35
+ #endif
36
+ ruby_xfree(pointer);
37
+ }
38
+
39
+ static size_t
40
+ flag_memsize(const void *pointer)
41
+ {
42
+ return pointer ? sizeof(flag_t) : 0;
43
+ }
44
+
45
+ static const rb_data_type_t flag_type = {
46
+ .wrap_struct_name = "Ractor::Containers::Flag",
47
+ .function = {
48
+ .dfree = flag_free,
49
+ .dsize = flag_memsize,
50
+ },
51
+ .flags = RUBY_TYPED_FROZEN_SHAREABLE,
52
+ };
53
+
54
+ static VALUE
55
+ flag_boolean(bool value)
56
+ {
57
+ return value ? Qtrue : Qfalse;
58
+ }
59
+
60
+ static flag_t *
61
+ get_flag(VALUE self)
62
+ {
63
+ flag_t *flag;
64
+ TypedData_Get_Struct(self, flag_t, &flag_type, flag);
65
+ if (!flag->initialized) rb_raise(rb_eRuntimeError, "uninitialized Flag");
66
+ return flag;
67
+ }
68
+
69
+ static VALUE
70
+ flag_allocate(VALUE klass)
71
+ {
72
+ flag_t *flag;
73
+ VALUE object = TypedData_Make_Struct(klass, flag_t, &flag_type, flag);
74
+ #if CONTAINERS_FLAG_LOCK_FREE
75
+ atomic_init(&flag->value, false);
76
+ #else
77
+ pthread_mutex_init(&flag->lock, NULL);
78
+ flag->value = false;
79
+ #endif
80
+ flag->initialized = false;
81
+ return object;
82
+ }
83
+
84
+ static VALUE
85
+ flag_initialize(int argc, VALUE *argv, VALUE self)
86
+ {
87
+ flag_t *flag;
88
+
89
+ rb_check_arity(argc, 0, 1);
90
+ VALUE initial = argc == 0 ? Qfalse : argv[0];
91
+ TypedData_Get_Struct(self, flag_t, &flag_type, flag);
92
+ if (flag->initialized) rb_raise(rb_eRuntimeError, "Flag is already initialized");
93
+ rb_check_frozen(self);
94
+
95
+ bool value = containers_strict_bool(initial, "initial value");
96
+ rb_check_frozen(self);
97
+ #if CONTAINERS_FLAG_LOCK_FREE
98
+ atomic_store_explicit(&flag->value, value, memory_order_relaxed);
99
+ #else
100
+ flag->value = value;
101
+ #endif
102
+ flag->initialized = true;
103
+ return containers_publish_native_reference_free(self);
104
+ }
105
+
106
+ static VALUE
107
+ flag_value(VALUE self)
108
+ {
109
+ flag_t *flag = get_flag(self);
110
+ #if CONTAINERS_FLAG_LOCK_FREE
111
+ bool value = atomic_load_explicit(&flag->value, memory_order_relaxed);
112
+ #else
113
+ pthread_mutex_lock(&flag->lock);
114
+ bool value = flag->value;
115
+ pthread_mutex_unlock(&flag->lock);
116
+ #endif
117
+ return flag_boolean(value);
118
+ }
119
+
120
+ static VALUE
121
+ flag_initialize_copy(VALUE self, VALUE other)
122
+ {
123
+ if (self == other) return self;
124
+ rb_check_frozen(self);
125
+ flag_t *copy;
126
+ TypedData_Get_Struct(self, flag_t, &flag_type, copy);
127
+ if (copy->initialized) rb_raise(rb_eRuntimeError, "copy is already initialized");
128
+ bool value = RTEST(flag_value(other));
129
+ #if CONTAINERS_FLAG_LOCK_FREE
130
+ atomic_store_explicit(&copy->value, value, memory_order_relaxed);
131
+ #else
132
+ copy->value = value;
133
+ #endif
134
+ copy->initialized = true;
135
+ return containers_publish_native_reference_free(self);
136
+ }
137
+
138
+ static VALUE
139
+ flag_set(VALUE self)
140
+ {
141
+ flag_t *flag = get_flag(self);
142
+ containers_check_typed_frozen(self);
143
+ #if CONTAINERS_FLAG_LOCK_FREE
144
+ atomic_store_explicit(&flag->value, true, memory_order_relaxed);
145
+ #else
146
+ pthread_mutex_lock(&flag->lock);
147
+ flag->value = true;
148
+ pthread_mutex_unlock(&flag->lock);
149
+ #endif
150
+ return Qtrue;
151
+ }
152
+
153
+ static VALUE
154
+ flag_unset(VALUE self)
155
+ {
156
+ flag_t *flag = get_flag(self);
157
+ containers_check_typed_frozen(self);
158
+ #if CONTAINERS_FLAG_LOCK_FREE
159
+ atomic_store_explicit(&flag->value, false, memory_order_relaxed);
160
+ #else
161
+ pthread_mutex_lock(&flag->lock);
162
+ flag->value = false;
163
+ pthread_mutex_unlock(&flag->lock);
164
+ #endif
165
+ return Qfalse;
166
+ }
167
+
168
+ static VALUE
169
+ flag_store(VALUE self, VALUE input)
170
+ {
171
+ flag_t *flag = get_flag(self);
172
+ containers_check_typed_frozen(self);
173
+ bool value = containers_strict_bool(input, "value");
174
+ #if CONTAINERS_FLAG_LOCK_FREE
175
+ atomic_store_explicit(&flag->value, value, memory_order_relaxed);
176
+ #else
177
+ pthread_mutex_lock(&flag->lock);
178
+ flag->value = value;
179
+ pthread_mutex_unlock(&flag->lock);
180
+ #endif
181
+ return input;
182
+ }
183
+
184
+ static VALUE
185
+ flag_swap(VALUE self, VALUE input)
186
+ {
187
+ flag_t *flag = get_flag(self);
188
+ containers_check_typed_frozen(self);
189
+ bool replacement = containers_strict_bool(input, "value");
190
+ #if CONTAINERS_FLAG_LOCK_FREE
191
+ bool previous = atomic_exchange_explicit(
192
+ &flag->value,
193
+ replacement,
194
+ memory_order_relaxed
195
+ );
196
+ #else
197
+ pthread_mutex_lock(&flag->lock);
198
+ bool previous = flag->value;
199
+ flag->value = replacement;
200
+ pthread_mutex_unlock(&flag->lock);
201
+ #endif
202
+ return flag_boolean(previous);
203
+ }
204
+
205
+ static VALUE
206
+ flag_compare_and_set(VALUE self, VALUE expected_input, VALUE replacement_input)
207
+ {
208
+ flag_t *flag = get_flag(self);
209
+ containers_check_typed_frozen(self);
210
+ bool expected = containers_strict_bool(expected_input, "expected value");
211
+ bool replacement = containers_strict_bool(replacement_input, "replacement value");
212
+ #if CONTAINERS_FLAG_LOCK_FREE
213
+ bool exchanged = atomic_compare_exchange_strong_explicit(
214
+ &flag->value,
215
+ &expected,
216
+ replacement,
217
+ memory_order_relaxed,
218
+ memory_order_relaxed
219
+ );
220
+ #else
221
+ pthread_mutex_lock(&flag->lock);
222
+ bool exchanged = flag->value == expected;
223
+ if (exchanged) flag->value = replacement;
224
+ pthread_mutex_unlock(&flag->lock);
225
+ #endif
226
+ return flag_boolean(exchanged);
227
+ }
228
+
229
+ static VALUE
230
+ flag_toggle(VALUE self)
231
+ {
232
+ flag_t *flag = get_flag(self);
233
+ containers_check_typed_frozen(self);
234
+ #if CONTAINERS_FLAG_LOCK_FREE
235
+ bool current = atomic_load_explicit(&flag->value, memory_order_relaxed);
236
+
237
+ for (;;) {
238
+ bool replacement = !current;
239
+ if (atomic_compare_exchange_weak_explicit(
240
+ &flag->value,
241
+ &current,
242
+ replacement,
243
+ memory_order_relaxed,
244
+ memory_order_relaxed
245
+ )) {
246
+ return flag_boolean(replacement);
247
+ }
248
+ }
249
+ #else
250
+ pthread_mutex_lock(&flag->lock);
251
+ bool value = flag->value = !flag->value;
252
+ pthread_mutex_unlock(&flag->lock);
253
+ return flag_boolean(value);
254
+ #endif
255
+ }
256
+
257
+ void
258
+ containers_init_flag(VALUE namespace)
259
+ {
260
+ cFlag = rb_define_class_under(namespace, "Flag", rb_cObject);
261
+ rb_define_alloc_func(cFlag, flag_allocate);
262
+ rb_define_method(cFlag, "initialize", flag_initialize, -1);
263
+ rb_define_private_method(cFlag, "initialize_copy", flag_initialize_copy, 1);
264
+ rb_define_method(cFlag, "value", flag_value, 0);
265
+ rb_define_alias(cFlag, "get", "value");
266
+ rb_define_method(cFlag, "set", flag_set, 0);
267
+ rb_define_method(cFlag, "unset", flag_unset, 0);
268
+ rb_define_method(cFlag, "store", flag_store, 1);
269
+ rb_define_alias(cFlag, "value=", "store");
270
+ rb_define_method(cFlag, "swap", flag_swap, 1);
271
+ rb_define_method(cFlag, "compare_and_set", flag_compare_and_set, 2);
272
+ rb_define_method(cFlag, "toggle", flag_toggle, 0);
273
+ }
274
+
275
+ /* Called only by transaction validation. Ordinary flag operations are unchanged. */
276
+ bool
277
+ farce_transaction_flag_set(VALUE self)
278
+ {
279
+ flag_t *flag = get_flag(self);
280
+ #if CONTAINERS_FLAG_LOCK_FREE
281
+ return atomic_load_explicit(&flag->value, memory_order_relaxed);
282
+ #else
283
+ pthread_mutex_lock(&flag->lock);
284
+ bool value = flag->value;
285
+ pthread_mutex_unlock(&flag->lock);
286
+ return value;
287
+ #endif
288
+ }
data/ext/farce/io.c ADDED
@@ -0,0 +1,338 @@
1
+ #include "reactor.h"
2
+ #include <ruby/version.h>
3
+ #include <ruby/fiber/scheduler.h>
4
+ #include <sys/stat.h>
5
+ #include <sys/socket.h>
6
+ #include <unistd.h>
7
+ #include <fcntl.h>
8
+ #include <poll.h>
9
+ #include <errno.h>
10
+
11
+ // BasicSocket is kept alive so reads can choose the socket path without loading it again.
12
+ static VALUE socket_class = Qnil;
13
+
14
+ // Remove the wait and its IO watches after a readiness call returns or raises.
15
+ static VALUE wait_cleanup(VALUE data) {
16
+ VALUE *args = (VALUE *)data; return fs_retire(args[0], args[1]);
17
+ }
18
+
19
+ // Watch the requested IO events and suspend the current fiber.
20
+ // A timeout resumes it with zero when no event arrives in time.
21
+ static VALUE wait_body(VALUE data) {
22
+ VALUE *args = (VALUE *)data;
23
+ fs_state *s = fs_get(args[0]); fs_wait *w = fs_lookup(s, args[1]);
24
+ w->ios = args[2]; // GC root for the watched IO
25
+ fs_watch_io(s, w, rb_io_descriptor(args[2]), NUM2INT(args[3]), -1, 0);
26
+ if (NIL_P(args[4])) return fs_suspend(args[0], args[1]);
27
+ return rb_funcall(args[0], rb_intern("wait_with_timeout"), 3, args[1], args[4], INT2NUM(0));
28
+ }
29
+
30
+ // Create a readiness wait and make sure it is cleaned up on every exit.
31
+ // The caller has already checked that this fiber belongs to the scheduler.
32
+ static VALUE wait_for(VALUE self, VALUE io, int events, VALUE timeout) {
33
+ VALUE args[] = {self, fs_arm_checked(self), io, INT2NUM(events), timeout};
34
+ return rb_ensure(wait_body, (VALUE)args, wait_cleanup, (VALUE)args);
35
+ }
36
+
37
+ // Implement Ruby's io_wait hook. Return events that are ready now, or wait
38
+ // for an event or timeout. Even an immediate result gives other fibers a turn periodically.
39
+ VALUE fs_io_wait(int argc, VALUE *argv, VALUE self) {
40
+ VALUE io, events, timeout;
41
+ rb_scan_args(argc, argv, "21", &io, &events, &timeout);
42
+ fs_check_fiber(self);
43
+ io = rb_io_get_io(io);
44
+ int mask = NUM2INT(events);
45
+ timeout = rb_funcall(self, rb_intern("duration_value"), 1, timeout);
46
+ fs_check_fiber(self);
47
+ if (mask <= 0 || (mask & ~7)) rb_raise(rb_eArgError, "invalid IO event mask");
48
+ struct pollfd fd = {.fd = rb_io_descriptor(io)};
49
+ if (mask & FS_READ) fd.events |= POLLIN;
50
+ if (mask & FS_WRITE) fd.events |= POLLOUT;
51
+ if (mask & FS_PRI) fd.events |= POLLPRI;
52
+ int result = poll(&fd, 1, 0);
53
+ if (result < 0 && errno != EINTR) rb_sys_fail("scheduler readiness snapshot");
54
+ if (fd.revents & POLLNVAL) rb_raise(rb_eIOError, "closed stream");
55
+ int found = 0;
56
+ if (fd.revents & (POLLIN | POLLHUP | POLLERR)) found |= FS_READ;
57
+ if (fd.revents & (POLLOUT | POLLHUP | POLLERR)) found |= FS_WRITE;
58
+ if (fd.revents & POLLPRI) found |= FS_PRI;
59
+ if ((found & mask) || (!NIL_P(timeout) && NUM2DBL(timeout) == 0))
60
+ return fs_checkpoint(self, INT2NUM(found & mask));
61
+ return wait_for(self, io, mask, timeout);
62
+ }
63
+
64
+ // Keep the state of one buffer read or write across waits and cleanup.
65
+ // The Ruby IO and buffer stay alive while native code uses their storage.
66
+ typedef struct {
67
+ // Ruby objects for the call and its current wait token.
68
+ VALUE self, io, buffer, token;
69
+ // Completion target, starting offset, buffer limit, and bytes already written.
70
+ size_t minimum, offset, size, initial;
71
+ // Start of the buffer storage used by native IO calls.
72
+ void *base;
73
+ // Write mode, lock ownership, original descriptor, and socket-call mode.
74
+ int writing, unlock, fd, socket;
75
+ // Suspension count before this call, used to detect an immediate result.
76
+ uint64_t serial;
77
+ } transfer;
78
+
79
+ // Pause a transfer until its descriptor is ready, then remove that wait.
80
+ // If the wait raises, transfer_cleanup removes it instead.
81
+ static void transfer_wait(transfer *t, int events) {
82
+ t->token = fs_arm_checked(t->self);
83
+ fs_state *s = fs_get(t->self);
84
+ fs_wait *w = fs_lookup(s, t->token);
85
+ w->ios = t->io;
86
+ fs_watch_io(s, w, rb_io_descriptor(t->io), events, -1, 0);
87
+ fs_suspend(t->self, t->token);
88
+ fs_retire(t->self, t->token);
89
+ t->token = Qnil;
90
+ }
91
+
92
+ // Submit an io_uring receive directly into the locked buffer and wait for it.
93
+ // Return the byte count or a negative error number from the completion.
94
+ static ssize_t transfer_receive(transfer *t, void *buffer, size_t capacity) {
95
+ t->token = fs_arm_checked(t->self);
96
+ fs_state *s = fs_get(t->self);
97
+ fs_wait *w = fs_lookup(s, t->token);
98
+ w->ios = t->io;
99
+ int error = fs_driver_receive(s, w, rb_io_descriptor(t->io), buffer, capacity);
100
+ if (error) rb_syserr_fail(error, "scheduler receive submission");
101
+ VALUE result = fs_suspend(t->self, t->token);
102
+ fs_retire(t->self, t->token);
103
+ t->token = Qnil;
104
+ return NUM2LONG(result);
105
+ }
106
+
107
+ // Pass a regular file transfer to the Ruby helper and its bounded worker pool.
108
+ // The byte limit keeps the worker inside the requested buffer range.
109
+ static VALUE transfer_file(transfer *t) {
110
+ return rb_funcall(t->self, rb_intern("file_transfer"), 6,
111
+ t->writing ? Qtrue : Qfalse, t->io, t->buffer, SIZET2NUM(t->minimum),
112
+ SIZET2NUM(t->offset), SIZET2NUM(t->size - t->offset));
113
+ }
114
+
115
+ // Read or write the requested bytes without blocking the scheduler thread.
116
+ // Wait when the IO is not ready, and use a worker for regular files.
117
+ // Return bytes transferred, or a negative error number if nothing was transferred.
118
+ static VALUE transfer_body(VALUE data) {
119
+ transfer *t = (transfer *)data;
120
+ // MSG_DONTWAIT is scoped to this syscall. Try sockets directly: this also
121
+ // supports IO.for_fd wrappers without caching descriptor type across reopen.
122
+ #ifdef MSG_DONTWAIT
123
+ t->socket = 1;
124
+ #else
125
+ t->socket = 0;
126
+ struct stat info;
127
+ if (fstat(t->fd, &info) < 0) rb_sys_fail("scheduler fstat");
128
+ if (S_ISREG(info.st_mode))
129
+ return transfer_file(t);
130
+ int flags = fcntl(t->fd, F_GETFL);
131
+ if (flags < 0 || (!(flags & O_NONBLOCK) && fcntl(t->fd, F_SETFL, flags | O_NONBLOCK) < 0))
132
+ rb_sys_fail("scheduler nonblocking IO");
133
+ #endif
134
+ int direct = 0;
135
+ #if defined(FARCE_URING) && defined(IORING_RECVSEND_POLL_FIRST)
136
+ fs_state *state = fs_get(t->self);
137
+ direct = !t->writing && t->minimum > 0 && state->driver == FS_URING_DRIVER &&
138
+ !state->receive_disabled && !NIL_P(socket_class) && rb_obj_is_kind_of(t->io, socket_class);
139
+ #endif
140
+ if (!direct && !t->writing && t->size > t->offset &&
141
+ !NIL_P(socket_class) && rb_obj_is_kind_of(t->io, socket_class)) {
142
+ transfer_wait(t, FS_READ);
143
+ }
144
+ size_t total = 0;
145
+ for (;;) {
146
+ size_t capacity = t->size - t->offset - total;
147
+ if (!capacity) return SIZET2NUM(total);
148
+ // Recheck IO identity after each suspension. A numeric fd may have been
149
+ // reused, while this call still retains the original Ruby IO object.
150
+ if (rb_io_descriptor(t->io) != t->fd) rb_raise(rb_eIOError, "IO descriptor changed while waiting");
151
+ ssize_t count;
152
+ int socket_flags = 0;
153
+ #ifdef MSG_DONTWAIT
154
+ socket_flags |= MSG_DONTWAIT;
155
+ #endif
156
+ #ifdef MSG_NOSIGNAL
157
+ if (t->writing) socket_flags |= MSG_NOSIGNAL;
158
+ #endif
159
+ if (direct) {
160
+ count = transfer_receive(t, (char *)t->base + t->offset + total, capacity);
161
+ if (count < 0) { errno = (int)-count; count = -1; }
162
+ } else if (t->socket) {
163
+ count = t->writing ? send(t->fd, (char *)t->base + t->offset + total, capacity, socket_flags) :
164
+ recv(t->fd, (char *)t->base + t->offset + total, capacity, socket_flags);
165
+ } else {
166
+ count = t->writing ? write(t->fd, (char *)t->base + t->offset + total, capacity) :
167
+ read(t->fd, (char *)t->base + t->offset + total, capacity);
168
+ }
169
+ if (count < 0) {
170
+ int error = errno;
171
+ if (direct && (error == EINVAL || error == EOPNOTSUPP || error == ENOSYS)) {
172
+ // Older kernels may support the ring but not poll-first receive.
173
+ // Fall back to readiness for this call and subsequent transfers.
174
+ fs_get(t->self)->receive_disabled = 1;
175
+ direct = 0;
176
+ continue;
177
+ }
178
+ if (t->socket && error == ENOTSOCK) {
179
+ struct stat info;
180
+ if (fstat(t->fd, &info) < 0) rb_sys_fail("scheduler fstat");
181
+ if (S_ISREG(info.st_mode))
182
+ return transfer_file(t);
183
+ t->socket = 0;
184
+ int flags = fcntl(t->fd, F_GETFL);
185
+ if (flags < 0 || (!(flags & O_NONBLOCK) && fcntl(t->fd, F_SETFL, flags | O_NONBLOCK) < 0))
186
+ rb_sys_fail("scheduler nonblocking IO");
187
+ continue;
188
+ }
189
+ if (error == EINTR) { rb_thread_check_ints(); continue; }
190
+ if (error == EAGAIN || error == EWOULDBLOCK) {
191
+ transfer_wait(t, t->writing ? FS_WRITE : FS_READ);
192
+ continue;
193
+ }
194
+ return total ? SIZET2NUM(total) : INT2NUM(-error);
195
+ }
196
+ if (!count) return SIZET2NUM(total);
197
+ total += (size_t)count;
198
+ if (total >= t->minimum) return SIZET2NUM(total);
199
+ }
200
+ }
201
+
202
+ // Adapt the transfer loop to the callback signature used by a Ruby block.
203
+ static VALUE timed_body(RB_BLOCK_CALL_FUNC_ARGLIST(ignored, data)) { return transfer_body(data); }
204
+
205
+ // Run the transfer while its buffer is protected.
206
+ // Apply the IO object's timeout when this Ruby version supports one.
207
+ static VALUE transfer_locked(VALUE data) {
208
+ transfer *t = (transfer *)data;
209
+ #ifdef HAVE_RB_IO_TIMEOUT
210
+ VALUE timeout = rb_io_timeout(t->io);
211
+ if (!NIL_P(timeout)) return rb_block_call(t->self, rb_intern("with_io_timeout"), 1, &timeout, timed_body, data);
212
+ #endif
213
+ return transfer_body(data);
214
+ }
215
+
216
+ // Cancel any remaining wait before releasing the buffer lock owned by this call.
217
+ // This also runs when a transfer raises or is interrupted.
218
+ static VALUE transfer_cleanup(VALUE data) {
219
+ transfer *t = (transfer *)data;
220
+ if (!NIL_P(t->token)) fs_retire(t->self, t->token);
221
+ if (t->unlock) rb_io_buffer_unlock(t->buffer);
222
+ return Qnil;
223
+ }
224
+
225
+ // Check Ruby's buffer arguments and prepare a read or write.
226
+ // Try an immediate socket write first, then protect the buffer for any waits.
227
+ // Ruby 4.1 requests a maximum length, while older versions request a minimum.
228
+ static VALUE transfer_io(int argc, VALUE *argv, VALUE self, int writing) {
229
+ VALUE io, buffer, minimum, offset;
230
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
231
+ rb_scan_args(argc, argv, "40", &io, &buffer, &offset, &minimum);
232
+ #else
233
+ rb_scan_args(argc, argv, "31", &io, &buffer, &minimum, &offset);
234
+ #endif
235
+ fs_check_fiber(self);
236
+ int coerces = !RB_TYPE_P(io, T_FILE) || !FIXNUM_P(minimum) || (!NIL_P(offset) && !FIXNUM_P(offset));
237
+ transfer t = {.self = self, .io = rb_io_get_io(io), .buffer = buffer, .writing = writing, .token = Qnil};
238
+ t.minimum = NUM2SIZET(minimum); t.offset = NIL_P(offset) ? 0 : NUM2SIZET(offset);
239
+ if (coerces) fs_check_fiber(self);
240
+ t.fd = rb_io_descriptor(t.io);
241
+ #if RUBY_IO_BUFFER_VERSION >= 3
242
+ rb_io_buffer_get_bytes(buffer, &t.base, &t.size);
243
+ #else
244
+ enum rb_io_buffer_flags flags = rb_io_buffer_get_bytes(buffer, &t.base, &t.size);
245
+ #endif
246
+ if (!writing) rb_io_buffer_get_bytes_for_writing(buffer, &t.base, &t.size);
247
+ if (t.offset > t.size || t.minimum > t.size - t.offset) rb_raise(rb_eArgError, "length and offset exceed buffer size");
248
+ #if RUBY_FIBER_SCHEDULER_VERSION >= 4
249
+ // Ruby 4.1 requests one transfer bounded by length, not a minimum byte count.
250
+ t.size = t.offset + t.minimum;
251
+ t.minimum = t.minimum ? 1 : 0;
252
+ #endif
253
+ #ifdef MSG_DONTWAIT
254
+ if (writing && t.size > t.offset) {
255
+ int flags = MSG_DONTWAIT;
256
+ #ifdef MSG_NOSIGNAL
257
+ flags |= MSG_NOSIGNAL;
258
+ #endif
259
+ ssize_t count = send(t.fd, (char *)t.base + t.offset, t.size - t.offset, flags);
260
+ if (count >= 0 && (size_t)count >= t.minimum) {
261
+ VALUE result = fs_checkpoint(self, SIZET2NUM((size_t)count));
262
+ RB_GC_GUARD(t.io); RB_GC_GUARD(buffer);
263
+ return result;
264
+ }
265
+ if (count > 0) {
266
+ // Continue a partial write without transmitting its prefix twice.
267
+ t.offset += (size_t)count; t.minimum -= (size_t)count;
268
+ t.initial = (size_t)count;
269
+ } else if (count < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR && errno != ENOTSOCK) {
270
+ return fs_checkpoint(self, INT2NUM(-errno));
271
+ }
272
+ }
273
+ #endif
274
+ t.serial = fs_get(self)->suspensions;
275
+ #if RUBY_IO_BUFFER_VERSION >= 3
276
+ // Locks now reference the backing allocation. Own one reference even when
277
+ // Ruby or another buffer view already holds a lock, and release only ours.
278
+ rb_io_buffer_lock(buffer); t.unlock = 1;
279
+ #else
280
+ if (!(flags & RB_IO_BUFFER_LOCKED)) { rb_io_buffer_lock(buffer); t.unlock = 1; }
281
+ #endif
282
+ VALUE result = rb_ensure(transfer_locked, (VALUE)&t, transfer_cleanup, (VALUE)&t);
283
+ if (t.initial) result = NUM2LONG(result) < 0 ? SIZET2NUM(t.initial) : SIZET2NUM(t.initial + NUM2SIZET(result));
284
+ if (t.serial == fs_get(self)->suspensions) fs_checkpoint(self, result);
285
+ RB_GC_GUARD(t.io); RB_GC_GUARD(buffer);
286
+ return result;
287
+ }
288
+
289
+ // Handle Ruby's io_read hook using the shared transfer code.
290
+ static VALUE read_io(int argc, VALUE *argv, VALUE self) { return transfer_io(argc, argv, self, 0); }
291
+
292
+ // Handle Ruby's io_write hook using the shared transfer code.
293
+ static VALUE write_io(int argc, VALUE *argv, VALUE self) { return transfer_io(argc, argv, self, 1); }
294
+ #if RUBY_API_VERSION_MAJOR >= 4
295
+
296
+ // Handle a descriptor Ruby has handed to the scheduler for closing.
297
+ // Wake its waiters with an error and retire its registration before closing it,
298
+ // so a new IO can safely reuse the descriptor number.
299
+ static VALUE close_io(VALUE self, VALUE descriptor) {
300
+ rb_funcall(self, rb_intern("check_owner"), 0);
301
+ int fd = NUM2INT(descriptor); fs_state *s = fs_get(self); st_data_t entry;
302
+ if (st_lookup(s->regs, fd, &entry)) {
303
+ fs_reg *reg = (fs_reg *)entry;
304
+ VALUE error = rb_exc_new_cstr(rb_eIOError, "stream closed while waiting");
305
+ for (fs_watch *w = reg->watches; w; w = w->next_reg)
306
+ rb_funcall(self, rb_intern("interrupt_wait"), 2, w->wait->token, error);
307
+ fs_driver_update(s, reg, 0);
308
+ st_data_t key = fd; st_delete(s->regs, &key, NULL);
309
+ if (reg->watches) {
310
+ st_insert(s->retired_regs, (st_data_t)reg, (st_data_t)reg);
311
+ reg->fd = -1;
312
+ } else {
313
+ st_data_t generation = reg->generation;
314
+ st_delete(s->generations, &generation, NULL);
315
+ if (reg->poll_events) s->polling_regs--;
316
+ xfree(reg);
317
+ }
318
+ // New fd ownership receives a new registration generation.
319
+ }
320
+ // CRuby 4.0 calls this hook after flushing and handing ownership of the fd
321
+ // to the scheduler. Do not retry close(EINTR): the number may already be reused.
322
+ if (close(fd) < 0 && errno != EINTR) rb_sys_fail("scheduler close");
323
+ return Qtrue;
324
+ }
325
+ #endif
326
+
327
+ // Keep the socket class alive and attach the supported IO hooks to the scheduler.
328
+ void fs_init_io(VALUE klass) {
329
+ rb_require("socket");
330
+ socket_class = rb_const_get(rb_cObject, rb_intern("BasicSocket"));
331
+ rb_global_variable(&socket_class);
332
+ rb_define_method(klass, "io_read", read_io, -1);
333
+ rb_define_method(klass, "io_write", write_io, -1);
334
+ rb_define_method(klass, "io_wait", fs_io_wait, -1);
335
+ #if RUBY_API_VERSION_MAJOR >= 4
336
+ rb_define_method(klass, "io_close", close_io, 1);
337
+ #endif
338
+ }