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,2056 @@
1
+ #include "containers.h"
2
+ #include "unshared_wait.h"
3
+ #include <ruby/atomic.h>
4
+ #include <ruby/fiber/scheduler.h>
5
+ #include <ruby/io.h>
6
+ #include <ruby/ractor.h>
7
+ #include <ruby/thread.h>
8
+
9
+ #include <errno.h>
10
+ #include <fcntl.h>
11
+ #include <math.h>
12
+ #include <pthread.h>
13
+ #include <stdbool.h>
14
+ #include <stdint.h>
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+ #include <time.h>
18
+ #include <unistd.h>
19
+
20
+ #include "dict.h"
21
+
22
+ #define PRIORITY_QUEUE_IDENTITY_INDEX_THRESHOLD 32
23
+
24
+ enum {
25
+ PRIORITY_QUEUE_UNINITIALIZED = 0,
26
+ PRIORITY_QUEUE_PUBLISHING = 1,
27
+ PRIORITY_QUEUE_PUBLISHED = 2,
28
+ PRIORITY_QUEUE_INITIALIZED = 3,
29
+ };
30
+
31
+ static inline bool
32
+ priority_queue_publication_busy(rb_atomic_t state)
33
+ {
34
+ return state == PRIORITY_QUEUE_PUBLISHING ||
35
+ state == PRIORITY_QUEUE_PUBLISHED;
36
+ }
37
+
38
+ typedef struct priority_queue_value priority_queue_value_t;
39
+ typedef struct priority_queue_bucket priority_queue_bucket_t;
40
+ typedef struct priority_queue_identity_index priority_queue_identity_index_t;
41
+
42
+ /* Build the snapshot through primitive String C APIs: retain the nominal
43
+ * subclass, encoding, and instance variables, but neither copy singleton
44
+ * methods nor invoke Ruby callbacks. */
45
+ static VALUE
46
+ priority_queue_snapshot_string(VALUE string)
47
+ {
48
+ VALUE copy = rb_obj_alloc(rb_obj_class(string));
49
+ VALUE instance_variables;
50
+ long index;
51
+
52
+ rb_str_replace(copy, string);
53
+ instance_variables = rb_obj_instance_variables(string);
54
+ for (index = 0; index < RARRAY_LEN(instance_variables); index++) {
55
+ ID id = SYM2ID(RARRAY_AREF(instance_variables, index));
56
+ rb_ivar_set(copy, id, rb_ivar_get(string, id));
57
+ }
58
+ rb_obj_freeze(copy);
59
+ RB_GC_GUARD(instance_variables);
60
+ return copy;
61
+ }
62
+
63
+ struct priority_queue_value {
64
+ VALUE value;
65
+ priority_queue_value_t *previous;
66
+ priority_queue_value_t *next;
67
+ priority_queue_value_t *identity_next;
68
+ };
69
+
70
+ typedef struct priority_queue_tracked_value {
71
+ priority_queue_value_t value;
72
+ double enqueued_at;
73
+ struct priority_queue_tracked_value *age_previous;
74
+ struct priority_queue_tracked_value *age_next;
75
+ } priority_queue_tracked_value_t;
76
+
77
+ typedef struct {
78
+ VALUE identity;
79
+ priority_queue_value_t *head;
80
+ priority_queue_value_t *tail;
81
+ bool occupied;
82
+ } priority_queue_identity_slot_t;
83
+
84
+ struct priority_queue_identity_index {
85
+ priority_queue_identity_slot_t *slots;
86
+ size_t capacity;
87
+ size_t count;
88
+ };
89
+
90
+ /* dnode_t must remain the first member: Kazlib passes its address to the
91
+ * configured node-free callback, where it is cast back to this structure. */
92
+ struct priority_queue_bucket {
93
+ dnode_t node;
94
+ VALUE priority;
95
+ double float_priority;
96
+ priority_queue_value_t *head;
97
+ priority_queue_value_t *tail;
98
+ priority_queue_identity_index_t *identity_index;
99
+ size_t size;
100
+ };
101
+
102
+ static size_t
103
+ priority_queue_identity_hash(VALUE identity)
104
+ {
105
+ uintptr_t bits = (uintptr_t)identity;
106
+ #if UINTPTR_MAX > UINT32_MAX
107
+ bits ^= bits >> 33;
108
+ bits *= UINT64_C(0xff51afd7ed558ccd);
109
+ bits ^= bits >> 33;
110
+ bits *= UINT64_C(0xc4ceb9fe1a85ec53);
111
+ bits ^= bits >> 33;
112
+ #else
113
+ bits ^= bits >> 16;
114
+ bits *= UINT32_C(0x7feb352d);
115
+ bits ^= bits >> 15;
116
+ bits *= UINT32_C(0x846ca68b);
117
+ bits ^= bits >> 16;
118
+ #endif
119
+ return (size_t)bits;
120
+ }
121
+
122
+ static priority_queue_identity_index_t *
123
+ priority_queue_identity_index_allocate(size_t minimum_capacity)
124
+ {
125
+ priority_queue_identity_index_t *index;
126
+ size_t capacity = 64;
127
+
128
+ while (capacity < minimum_capacity) {
129
+ if (capacity > SIZE_MAX / 2) rb_memerror();
130
+ capacity *= 2;
131
+ }
132
+
133
+ index = malloc(sizeof(priority_queue_identity_index_t));
134
+ if (!index) rb_memerror();
135
+ index->slots = calloc(capacity, sizeof(priority_queue_identity_slot_t));
136
+ if (!index->slots) {
137
+ free(index);
138
+ rb_memerror();
139
+ }
140
+ index->capacity = capacity;
141
+ index->count = 0;
142
+ return index;
143
+ }
144
+
145
+ static void
146
+ priority_queue_identity_index_free(priority_queue_identity_index_t *index)
147
+ {
148
+ if (!index) return;
149
+ free(index->slots);
150
+ free(index);
151
+ }
152
+
153
+ static priority_queue_identity_slot_t *
154
+ priority_queue_identity_index_lookup(priority_queue_identity_index_t *index, VALUE identity)
155
+ {
156
+ size_t position;
157
+ size_t mask;
158
+
159
+ if (!index) return NULL;
160
+ mask = index->capacity - 1;
161
+ position = priority_queue_identity_hash(identity) & mask;
162
+ while (index->slots[position].occupied) {
163
+ if (index->slots[position].identity == identity) return &index->slots[position];
164
+ position = (position + 1) & mask;
165
+ }
166
+ return NULL;
167
+ }
168
+
169
+ static priority_queue_identity_slot_t *
170
+ priority_queue_identity_index_insert_slot(
171
+ priority_queue_identity_index_t *index,
172
+ VALUE identity,
173
+ priority_queue_value_t *head,
174
+ priority_queue_value_t *tail
175
+ )
176
+ {
177
+ size_t mask = index->capacity - 1;
178
+ size_t position = priority_queue_identity_hash(identity) & mask;
179
+ priority_queue_identity_slot_t *slot;
180
+
181
+ while (index->slots[position].occupied) position = (position + 1) & mask;
182
+ slot = &index->slots[position];
183
+ slot->identity = identity;
184
+ slot->head = head;
185
+ slot->tail = tail;
186
+ slot->occupied = true;
187
+ index->count++;
188
+ return slot;
189
+ }
190
+
191
+ static void
192
+ priority_queue_identity_index_resize(priority_queue_identity_index_t *index)
193
+ {
194
+ priority_queue_identity_slot_t *old_slots = index->slots;
195
+ size_t old_capacity = index->capacity;
196
+ size_t new_capacity;
197
+ size_t position;
198
+
199
+ if (old_capacity > SIZE_MAX / 2) rb_memerror();
200
+ new_capacity = old_capacity * 2;
201
+ index->slots = calloc(new_capacity, sizeof(priority_queue_identity_slot_t));
202
+ if (!index->slots) {
203
+ index->slots = old_slots;
204
+ rb_memerror();
205
+ }
206
+ index->capacity = new_capacity;
207
+ index->count = 0;
208
+ for (position = 0; position < old_capacity; position++) {
209
+ priority_queue_identity_slot_t *slot = &old_slots[position];
210
+ if (!slot->occupied) continue;
211
+ priority_queue_identity_index_insert_slot(
212
+ index,
213
+ slot->identity,
214
+ slot->head,
215
+ slot->tail
216
+ );
217
+ }
218
+ free(old_slots);
219
+ }
220
+
221
+ static void
222
+ priority_queue_identity_index_prepare_insert(
223
+ priority_queue_identity_index_t *index,
224
+ VALUE identity
225
+ )
226
+ {
227
+ if (!index || priority_queue_identity_index_lookup(index, identity)) return;
228
+ if (index->count + 1 > index->capacity / 2) {
229
+ priority_queue_identity_index_resize(index);
230
+ }
231
+ }
232
+
233
+ static void
234
+ priority_queue_identity_index_add(
235
+ priority_queue_identity_index_t *index,
236
+ priority_queue_value_t *entry
237
+ )
238
+ {
239
+ priority_queue_identity_slot_t *slot;
240
+
241
+ if (!index) return;
242
+ entry->identity_next = NULL;
243
+ slot = priority_queue_identity_index_lookup(index, entry->value);
244
+ if (!slot) {
245
+ priority_queue_identity_index_insert_slot(index, entry->value, entry, entry);
246
+ return;
247
+ }
248
+ slot->tail->identity_next = entry;
249
+ slot->tail = entry;
250
+ }
251
+
252
+ static void
253
+ priority_queue_identity_index_delete_slot(
254
+ priority_queue_identity_index_t *index,
255
+ priority_queue_identity_slot_t *deleted
256
+ )
257
+ {
258
+ size_t mask = index->capacity - 1;
259
+ size_t position = (size_t)(deleted - index->slots);
260
+
261
+ memset(deleted, 0, sizeof(*deleted));
262
+ index->count--;
263
+ position = (position + 1) & mask;
264
+ while (index->slots[position].occupied) {
265
+ priority_queue_identity_slot_t moved = index->slots[position];
266
+ memset(&index->slots[position], 0, sizeof(index->slots[position]));
267
+ index->count--;
268
+ priority_queue_identity_index_insert_slot(
269
+ index,
270
+ moved.identity,
271
+ moved.head,
272
+ moved.tail
273
+ );
274
+ position = (position + 1) & mask;
275
+ }
276
+ }
277
+
278
+ static void
279
+ priority_queue_identity_index_remove(
280
+ priority_queue_identity_index_t *index,
281
+ priority_queue_value_t *entry
282
+ )
283
+ {
284
+ priority_queue_identity_slot_t *slot;
285
+ priority_queue_value_t *previous = NULL;
286
+
287
+ if (!index) return;
288
+ slot = priority_queue_identity_index_lookup(index, entry->value);
289
+ if (!slot) rb_bug("priority queue identity index lost an entry");
290
+ if (slot->head != entry) {
291
+ for (previous = slot->head;
292
+ previous && previous->identity_next != entry;
293
+ previous = previous->identity_next) {
294
+ }
295
+ if (!previous) rb_bug("priority queue identity chain lost an entry");
296
+ }
297
+
298
+ if (previous) previous->identity_next = entry->identity_next;
299
+ else slot->head = entry->identity_next;
300
+ if (slot->tail == entry) slot->tail = previous;
301
+ entry->identity_next = NULL;
302
+ if (!slot->head) priority_queue_identity_index_delete_slot(index, slot);
303
+ }
304
+
305
+ static void
306
+ priority_queue_identity_index_build(priority_queue_bucket_t *bucket)
307
+ {
308
+ priority_queue_identity_index_t *index;
309
+ priority_queue_value_t *entry;
310
+ size_t minimum_capacity;
311
+
312
+ if (bucket->identity_index) return;
313
+ if (bucket->size > SIZE_MAX / 2) rb_memerror();
314
+ minimum_capacity = bucket->size * 2;
315
+ index = priority_queue_identity_index_allocate(minimum_capacity);
316
+ for (entry = bucket->head; entry; entry = entry->next) {
317
+ priority_queue_identity_index_add(index, entry);
318
+ }
319
+ bucket->identity_index = index;
320
+ }
321
+
322
+ static void
323
+ priority_queue_identity_index_rebuild(priority_queue_bucket_t *bucket)
324
+ {
325
+ priority_queue_identity_index_t *index = bucket->identity_index;
326
+ priority_queue_value_t *entry;
327
+
328
+ if (!index) return;
329
+ memset(index->slots, 0, index->capacity * sizeof(priority_queue_identity_slot_t));
330
+ index->count = 0;
331
+ for (entry = bucket->head; entry; entry = entry->next) {
332
+ entry->identity_next = NULL;
333
+ priority_queue_identity_index_add(index, entry);
334
+ }
335
+ }
336
+
337
+ static void
338
+ priority_queue_bucket_unlink(
339
+ priority_queue_bucket_t *bucket,
340
+ priority_queue_value_t *entry
341
+ )
342
+ {
343
+ if (entry->previous) entry->previous->next = entry->next;
344
+ else bucket->head = entry->next;
345
+ if (entry->next) entry->next->previous = entry->previous;
346
+ else bucket->tail = entry->previous;
347
+ bucket->size--;
348
+ }
349
+
350
+ typedef struct priority_queue_lock_waiter {
351
+ int read_fd;
352
+ int write_fd;
353
+ bool notified;
354
+ struct priority_queue_lock_waiter *next;
355
+ } priority_queue_lock_waiter_t;
356
+
357
+ typedef struct {
358
+ dict_t tree;
359
+ pthread_mutex_t lock;
360
+ VALUE owner_fiber;
361
+ VALUE owner_ruby_thread;
362
+ priority_queue_lock_waiter_t *waiters;
363
+ priority_queue_lock_waiter_t *last_waiter;
364
+ farce_unshared_wait_list_t unshared_lock_waiters;
365
+ VALUE unshared_self;
366
+ /* Deliberately omitted from dmark: publication must remember its owner
367
+ * without making that Fiber/Thread part of the shareable object graph. */
368
+ VALUE publication_owner_fiber;
369
+ VALUE publication_owner_ruby_thread;
370
+ VALUE signal;
371
+ containers_unshared_signal_t *unshared_signal;
372
+ size_t size;
373
+ size_t capacity;
374
+ priority_queue_tracked_value_t *age_head;
375
+ priority_queue_tracked_value_t *age_tail;
376
+ uint64_t generation;
377
+ bool bounded;
378
+ bool sealed;
379
+ bool closed;
380
+ bool track_age;
381
+ /* Only homogeneous built-in numeric keys can reuse an insertion search
382
+ * across notification. Mixed/custom comparisons retain the two-pass path. */
383
+ bool numeric_only;
384
+ rb_atomic_t publication_state;
385
+ bool mutex_initialized;
386
+ bool shared;
387
+ bool unshared_fiber_io;
388
+ } priority_queue_t;
389
+
390
+ static VALUE cPriorityQueue;
391
+ static VALUE eIsolationError;
392
+ static ID id_farce;
393
+ static ID id_closed_error;
394
+ static ID id_sealed_error;
395
+ static ID id_compare;
396
+ static ID id_broadcast;
397
+ static ID id_case_equal;
398
+
399
+ static double
400
+ priority_queue_monotonic_now(void)
401
+ {
402
+ struct timespec now;
403
+ #ifdef CLOCK_MONOTONIC
404
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
405
+ return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
406
+ }
407
+ #endif
408
+ struct timeval fallback;
409
+ gettimeofday(&fallback, NULL);
410
+ return (double)fallback.tv_sec + (double)fallback.tv_usec / 1000000.0;
411
+ }
412
+
413
+ static void
414
+ priority_queue_track_push(priority_queue_t *queue, priority_queue_value_t *value)
415
+ {
416
+ if (!queue->track_age) return;
417
+ priority_queue_tracked_value_t *tracked = (priority_queue_tracked_value_t *)value;
418
+ tracked->enqueued_at = priority_queue_monotonic_now();
419
+ tracked->age_previous = queue->age_tail;
420
+ tracked->age_next = NULL;
421
+ if (queue->age_tail) queue->age_tail->age_next = tracked;
422
+ else queue->age_head = tracked;
423
+ queue->age_tail = tracked;
424
+ queue->generation++;
425
+ }
426
+
427
+ static void
428
+ priority_queue_track_remove(priority_queue_t *queue, priority_queue_value_t *value)
429
+ {
430
+ if (!queue->track_age) return;
431
+ priority_queue_tracked_value_t *tracked = (priority_queue_tracked_value_t *)value;
432
+ if (tracked->age_previous) tracked->age_previous->age_next = tracked->age_next;
433
+ else queue->age_head = tracked->age_next;
434
+ if (tracked->age_next) tracked->age_next->age_previous = tracked->age_previous;
435
+ else queue->age_tail = tracked->age_previous;
436
+ queue->generation++;
437
+ }
438
+
439
+ static void priority_queue_wait_for_publication(priority_queue_t *queue);
440
+
441
+ typedef struct {
442
+ VALUE receiver;
443
+ VALUE argument;
444
+ bool equality;
445
+ int comparison;
446
+ } priority_queue_callback_t;
447
+
448
+ static VALUE
449
+ priority_queue_callback_body(VALUE opaque)
450
+ {
451
+ priority_queue_callback_t *callback = (priority_queue_callback_t *)opaque;
452
+ if (callback->equality) return rb_equal(callback->receiver, callback->argument);
453
+
454
+ VALUE result = rb_funcall(
455
+ callback->receiver,
456
+ id_compare,
457
+ 1,
458
+ callback->argument
459
+ );
460
+ /* rb_cmpint can itself call Ruby `>`/`<` methods on a non-Integer result.
461
+ * Keep the callback guard active through that conversion as well. */
462
+ callback->comparison = rb_cmpint(
463
+ result,
464
+ callback->receiver,
465
+ callback->argument
466
+ );
467
+ return Qnil;
468
+ }
469
+
470
+ static VALUE
471
+ priority_queue_call_ruby_callback(
472
+ VALUE receiver,
473
+ VALUE argument,
474
+ bool equality,
475
+ int *comparison
476
+ )
477
+ {
478
+ priority_queue_callback_t callback = {
479
+ .receiver = receiver,
480
+ .argument = argument,
481
+ .equality = equality,
482
+ .comparison = 0,
483
+ };
484
+
485
+ VALUE result = priority_queue_callback_body((VALUE)&callback);
486
+ if (comparison) *comparison = callback.comparison;
487
+ return result;
488
+ }
489
+
490
+ static int
491
+ priority_queue_compare(const void *left_pointer, const void *right_pointer, void *context)
492
+ {
493
+ VALUE left = (VALUE)left_pointer;
494
+ VALUE right = (VALUE)right_pointer;
495
+ int comparison;
496
+ (void)context;
497
+
498
+ if (FIXNUM_P(left) && FIXNUM_P(right)) {
499
+ intptr_t left_value = (intptr_t)left;
500
+ intptr_t right_value = (intptr_t)right;
501
+ if (left_value < right_value) return -1;
502
+ if (left_value > right_value) return 1;
503
+ return 0;
504
+ }
505
+ if (CLASS_OF(left) == rb_cString && CLASS_OF(right) == rb_cString) {
506
+ return rb_str_cmp(left, right);
507
+ }
508
+ if (CLASS_OF(left) == rb_cFloat && CLASS_OF(right) == rb_cFloat) {
509
+ double left_value = RFLOAT_VALUE(left);
510
+ double right_value = RFLOAT_VALUE(right);
511
+
512
+ /* Float#<=> returns nil for NaN, which rb_cmpint turns into the
513
+ * normal comparison ArgumentError. Keep that uncommon case on the
514
+ * Ruby callback path rather than treating NaN as an equal priority. */
515
+ if (!isnan(left_value) && !isnan(right_value)) {
516
+ if (left_value < right_value) return -1;
517
+ if (left_value > right_value) return 1;
518
+ return 0;
519
+ }
520
+ }
521
+
522
+ (void)priority_queue_call_ruby_callback(
523
+ left,
524
+ right,
525
+ false,
526
+ &comparison
527
+ );
528
+ return comparison;
529
+ }
530
+
531
+ static dnode_t *
532
+ priority_queue_allocate_tree_node(void *context)
533
+ {
534
+ priority_queue_bucket_t *bucket = calloc(1, sizeof(priority_queue_bucket_t));
535
+ (void)context;
536
+ return bucket ? &bucket->node : NULL;
537
+ }
538
+
539
+ static void
540
+ priority_queue_free_tree_node(dnode_t *node, void *context)
541
+ {
542
+ priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
543
+ priority_queue_value_t *entry = bucket->head;
544
+ (void)context;
545
+
546
+ priority_queue_identity_index_free(bucket->identity_index);
547
+ while (entry) {
548
+ priority_queue_value_t *next = entry->next;
549
+ free(entry);
550
+ entry = next;
551
+ }
552
+ free(bucket);
553
+ }
554
+
555
+ static void
556
+ priority_queue_mark(void *pointer)
557
+ {
558
+ priority_queue_t *queue = pointer;
559
+ dnode_t *node;
560
+
561
+ if (!queue) return;
562
+ rb_gc_mark_movable(queue->owner_fiber);
563
+ rb_gc_mark_movable(queue->owner_ruby_thread);
564
+ rb_gc_mark_movable(queue->signal);
565
+ rb_gc_mark_movable(queue->unshared_self);
566
+ farce_unshared_wait_mark(&queue->unshared_lock_waiters);
567
+ for (node = dict_first(&queue->tree); node; node = dict_next(&queue->tree, node)) {
568
+ priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
569
+ priority_queue_value_t *entry;
570
+ rb_gc_mark_movable(bucket->priority);
571
+ for (entry = bucket->head; entry; entry = entry->next) {
572
+ rb_gc_mark_movable(entry->value);
573
+ }
574
+ }
575
+ }
576
+
577
+ static void
578
+ priority_queue_compact(void *pointer)
579
+ {
580
+ priority_queue_t *queue = pointer;
581
+ dnode_t *node;
582
+
583
+ if (!queue) return;
584
+ queue->owner_fiber = rb_gc_location(queue->owner_fiber);
585
+ queue->owner_ruby_thread = rb_gc_location(queue->owner_ruby_thread);
586
+ /* The VM already roots the running publisher. dcompact only refreshes the
587
+ * unmarked identity handles if that Fiber or Thread moved. */
588
+ queue->publication_owner_fiber =
589
+ rb_gc_location(queue->publication_owner_fiber);
590
+ queue->publication_owner_ruby_thread =
591
+ rb_gc_location(queue->publication_owner_ruby_thread);
592
+ queue->signal = rb_gc_location(queue->signal);
593
+ queue->unshared_self = rb_gc_location(queue->unshared_self);
594
+ for (node = dict_first(&queue->tree); node; node = dict_next(&queue->tree, node)) {
595
+ priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
596
+ priority_queue_value_t *entry;
597
+ bucket->priority = rb_gc_location(bucket->priority);
598
+ node->dict_key = (const void *)bucket->priority;
599
+ for (entry = bucket->head; entry; entry = entry->next) {
600
+ entry->value = rb_gc_location(entry->value);
601
+ }
602
+ priority_queue_identity_index_rebuild(bucket);
603
+ }
604
+ }
605
+
606
+ static void
607
+ priority_queue_free(void *pointer)
608
+ {
609
+ priority_queue_t *queue = pointer;
610
+ if (!queue) return;
611
+ dict_free_nodes(&queue->tree);
612
+ if (queue->mutex_initialized) pthread_mutex_destroy(&queue->lock);
613
+ ruby_xfree(queue);
614
+ }
615
+
616
+ static size_t
617
+ priority_queue_memsize(const void *pointer)
618
+ {
619
+ const priority_queue_t *queue = pointer;
620
+ dnode_t *node;
621
+ size_t size;
622
+
623
+ if (!queue) return 0;
624
+ size = sizeof(priority_queue_t)
625
+ + (size_t)dict_count((dict_t *)&queue->tree) * sizeof(priority_queue_bucket_t)
626
+ + queue->size * (queue->track_age ?
627
+ sizeof(priority_queue_tracked_value_t) : sizeof(priority_queue_value_t));
628
+ for (node = dict_first((dict_t *)&queue->tree);
629
+ node;
630
+ node = dict_next((dict_t *)&queue->tree, node)) {
631
+ priority_queue_bucket_t *bucket = (priority_queue_bucket_t *)node;
632
+ priority_queue_identity_index_t *index = bucket->identity_index;
633
+ if (!index) continue;
634
+ size += sizeof(priority_queue_identity_index_t)
635
+ + index->capacity * sizeof(priority_queue_identity_slot_t);
636
+ }
637
+ return size;
638
+ }
639
+
640
+ static const rb_data_type_t priority_queue_type = {
641
+ .wrap_struct_name = "Farce::Internal::PriorityQueue",
642
+ .function = {
643
+ .dmark = priority_queue_mark,
644
+ .dfree = priority_queue_free,
645
+ .dsize = priority_queue_memsize,
646
+ .dcompact = priority_queue_compact,
647
+ },
648
+ .flags = RUBY_TYPED_FROZEN_SHAREABLE,
649
+ };
650
+
651
+ static const rb_data_type_t unshared_priority_queue_type = {
652
+ .wrap_struct_name = "Farce::Internal::UnsharedPriorityQueue",
653
+ .function = {
654
+ .dmark = priority_queue_mark,
655
+ .dfree = priority_queue_free,
656
+ .dsize = priority_queue_memsize,
657
+ .dcompact = priority_queue_compact,
658
+ },
659
+ .parent = &priority_queue_type,
660
+ };
661
+
662
+ /* The GVL protects the native fields within one Ractor. Keep the logical
663
+ * owner and waiter protocol because comparisons and notifications can yield. */
664
+ static inline void priority_queue_mutex_lock(priority_queue_t *queue)
665
+ {
666
+ if (queue->shared) pthread_mutex_lock(&queue->lock);
667
+ }
668
+
669
+ static inline void priority_queue_mutex_unlock(priority_queue_t *queue)
670
+ {
671
+ if (queue->shared) pthread_mutex_unlock(&queue->lock);
672
+ }
673
+
674
+ static VALUE
675
+ priority_queue_allocate_type(VALUE klass, bool shared)
676
+ {
677
+ priority_queue_t *queue;
678
+ VALUE object = TypedData_Make_Struct(
679
+ klass,
680
+ priority_queue_t,
681
+ shared ? &priority_queue_type : &unshared_priority_queue_type,
682
+ queue
683
+ );
684
+ int error;
685
+
686
+ memset(queue, 0, sizeof(*queue));
687
+ queue->shared = shared;
688
+ queue->unshared_fiber_io = FARCE_UNSHARED_FIBER_IO;
689
+ queue->owner_fiber = Qnil;
690
+ queue->owner_ruby_thread = Qnil;
691
+ queue->publication_owner_fiber = Qnil;
692
+ queue->publication_owner_ruby_thread = Qnil;
693
+ queue->signal = Qnil;
694
+ queue->unshared_self = shared ? Qnil : object;
695
+ queue->numeric_only = true;
696
+ RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_UNINITIALIZED);
697
+ dict_init(&queue->tree, priority_queue_compare);
698
+ dict_set_allocator(
699
+ &queue->tree,
700
+ priority_queue_allocate_tree_node,
701
+ priority_queue_free_tree_node,
702
+ queue
703
+ );
704
+ if (shared) {
705
+ error = pthread_mutex_init(&queue->lock, NULL);
706
+ if (error) rb_syserr_fail(error, "pthread_mutex_init");
707
+ queue->mutex_initialized = true;
708
+ }
709
+ return object;
710
+ }
711
+
712
+ static VALUE priority_queue_allocate(VALUE klass) { return priority_queue_allocate_type(klass, true); }
713
+ static VALUE unshared_priority_queue_allocate(VALUE klass) { return priority_queue_allocate_type(klass, false); }
714
+
715
+ static priority_queue_t *
716
+ priority_queue_get_raw(VALUE self)
717
+ {
718
+ priority_queue_t *queue;
719
+
720
+ if (rb_typeddata_is_kind_of(self, &priority_queue_type)) {
721
+ TypedData_Get_Struct(self, priority_queue_t, &priority_queue_type, queue);
722
+ return queue;
723
+ }
724
+ rb_raise(rb_eTypeError, "wrong priority queue type");
725
+ }
726
+
727
+ static priority_queue_t *
728
+ priority_queue_get(VALUE self)
729
+ {
730
+ priority_queue_t *queue = priority_queue_get_raw(self);
731
+
732
+ rb_atomic_t state = RUBY_ATOMIC_LOAD(queue->publication_state);
733
+
734
+ /* Primitive freezing can make a clean typed object Ractor-visible before
735
+ * recursive publication returns. Always join an active publication, even
736
+ * if an async exception is committing it, then recheck so a failed
737
+ * traversal cannot leak a usable half-published queue. The steady-state
738
+ * path is one atomic load and does not touch the physical mutex. */
739
+ if (priority_queue_publication_busy(state)) {
740
+ priority_queue_wait_for_publication(queue);
741
+ state = RUBY_ATOMIC_LOAD(queue->publication_state);
742
+ }
743
+ if (state != PRIORITY_QUEUE_INITIALIZED) {
744
+ rb_raise(rb_eRuntimeError, "uninitialized priority queue");
745
+ }
746
+ return queue;
747
+ }
748
+
749
+ static void
750
+ priority_queue_lock_set_fd_flags(int fd)
751
+ {
752
+ #ifdef _WIN32
753
+ (void)fd;
754
+ #else
755
+ int flags = fcntl(fd, F_GETFL, 0);
756
+ if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
757
+ flags = fcntl(fd, F_GETFD, 0);
758
+ if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
759
+ #endif
760
+ }
761
+
762
+ /* Called with queue->lock held. A logical unlock only needs to make one
763
+ * waiter runnable. The selected waiter remains linked until ensure cleanup. */
764
+ static void
765
+ priority_queue_lock_notify_one_locked(priority_queue_t *queue)
766
+ {
767
+ if (!queue->shared) {
768
+ /* Ownership has already been released. Notify the original cohort
769
+ * together so cancellation or synchronous unblock cannot strand the
770
+ * next waiter. Reentrant operations can register for a later unlock. */
771
+ farce_unshared_wait_notify_all(&queue->unshared_lock_waiters);
772
+ }
773
+ unsigned char byte = 1;
774
+ priority_queue_lock_waiter_t *waiter;
775
+
776
+ for (waiter = queue->waiters; waiter; waiter = waiter->next) {
777
+ ssize_t result;
778
+ if (waiter->notified) continue;
779
+ do {
780
+ result = write(waiter->write_fd, &byte, 1);
781
+ } while (result < 0 && errno == EINTR);
782
+ if (result == 1 ||
783
+ (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
784
+ waiter->notified = true;
785
+ return;
786
+ }
787
+ }
788
+ }
789
+
790
+ typedef struct {
791
+ priority_queue_t *queue;
792
+ priority_queue_lock_waiter_t waiter;
793
+ } priority_queue_lock_wait_context_t;
794
+
795
+ static bool
796
+ priority_queue_lock_wait_for_descriptor(int fd)
797
+ {
798
+ return containers_wait_for_readable(fd, Qnil);
799
+ }
800
+
801
+ static VALUE
802
+ priority_queue_lock_wait_body(VALUE opaque)
803
+ {
804
+ priority_queue_lock_wait_context_t *context =
805
+ (priority_queue_lock_wait_context_t *)opaque;
806
+ bool awakened = priority_queue_lock_wait_for_descriptor(context->waiter.read_fd);
807
+ return awakened ? Qtrue : Qfalse;
808
+ }
809
+
810
+ static VALUE
811
+ priority_queue_lock_wait_cleanup(VALUE opaque)
812
+ {
813
+ priority_queue_lock_wait_context_t *context =
814
+ (priority_queue_lock_wait_context_t *)opaque;
815
+ priority_queue_lock_waiter_t *previous = NULL;
816
+ priority_queue_lock_waiter_t **link;
817
+
818
+ priority_queue_mutex_lock(context->queue);
819
+ for (link = &context->queue->waiters; *link; link = &(*link)->next) {
820
+ if (*link == &context->waiter) {
821
+ *link = context->waiter.next;
822
+ if (context->queue->last_waiter == &context->waiter) {
823
+ context->queue->last_waiter = previous;
824
+ }
825
+ break;
826
+ }
827
+ previous = *link;
828
+ }
829
+ /* A notification only makes this waiter runnable; it does not reserve
830
+ * ownership. Pass the baton whenever a notified waiter leaves while the
831
+ * queue is still ownerless. This covers both interruption inside rb_io_wait
832
+ * and cancellation after a normal wake but before the acquisition loop can
833
+ * claim ownership, so later waiters cannot be stranded. */
834
+ if (context->waiter.notified &&
835
+ NIL_P(context->queue->owner_fiber) &&
836
+ !priority_queue_publication_busy(
837
+ RUBY_ATOMIC_LOAD(context->queue->publication_state)
838
+ )) {
839
+ priority_queue_lock_notify_one_locked(context->queue);
840
+ }
841
+ priority_queue_mutex_unlock(context->queue);
842
+ close(context->waiter.read_fd);
843
+ close(context->waiter.write_fd);
844
+ return Qnil;
845
+ }
846
+
847
+ /* Called with queue->lock held and always returns with it released. */
848
+ static void
849
+ priority_queue_lock_wait_once(priority_queue_t *queue)
850
+ {
851
+ if (!queue->shared && (!queue->unshared_fiber_io || NIL_P(rb_fiber_scheduler_current()))) {
852
+ (void)farce_unshared_wait(&queue->unshared_lock_waiters, queue->unshared_self, false, 0);
853
+ return;
854
+ }
855
+ int descriptors[2];
856
+ priority_queue_lock_wait_context_t context;
857
+
858
+ if (pipe(descriptors) != 0) {
859
+ priority_queue_mutex_unlock(queue);
860
+ rb_sys_fail("pipe");
861
+ }
862
+ priority_queue_lock_set_fd_flags(descriptors[0]);
863
+ priority_queue_lock_set_fd_flags(descriptors[1]);
864
+
865
+ context = (priority_queue_lock_wait_context_t){
866
+ .queue = queue,
867
+ .waiter = {
868
+ .read_fd = descriptors[0],
869
+ .write_fd = descriptors[1],
870
+ .notified = false,
871
+ .next = NULL,
872
+ },
873
+ };
874
+ if (queue->last_waiter) queue->last_waiter->next = &context.waiter;
875
+ else queue->waiters = &context.waiter;
876
+ queue->last_waiter = &context.waiter;
877
+ priority_queue_mutex_unlock(queue);
878
+ (void)rb_ensure(
879
+ priority_queue_lock_wait_body,
880
+ (VALUE)&context,
881
+ priority_queue_lock_wait_cleanup,
882
+ (VALUE)&context
883
+ );
884
+ }
885
+
886
+ static void
887
+ priority_queue_lock(priority_queue_t *queue, bool *acquired)
888
+ {
889
+ VALUE current = rb_fiber_current();
890
+ VALUE current_thread = rb_thread_current();
891
+ VALUE scheduler = rb_fiber_scheduler_current();
892
+
893
+ for (;;) {
894
+ bool publication_busy;
895
+
896
+ priority_queue_mutex_lock(queue);
897
+ publication_busy = priority_queue_publication_busy(
898
+ RUBY_ATOMIC_LOAD(queue->publication_state)
899
+ );
900
+ if (queue->owner_fiber == current ||
901
+ (publication_busy && queue->publication_owner_fiber == current)) {
902
+ priority_queue_mutex_unlock(queue);
903
+ rb_raise(rb_eThreadError, "deadlock; recursive priority queue access");
904
+ }
905
+ if (NIL_P(queue->owner_fiber) && !publication_busy) {
906
+ queue->owner_fiber = current;
907
+ queue->owner_ruby_thread = current_thread;
908
+ *acquired = true;
909
+ priority_queue_mutex_unlock(queue);
910
+ return;
911
+ }
912
+ if ((queue->owner_ruby_thread == current_thread ||
913
+ (publication_busy &&
914
+ queue->publication_owner_ruby_thread == current_thread)) &&
915
+ NIL_P(scheduler)) {
916
+ priority_queue_mutex_unlock(queue);
917
+ rb_raise(
918
+ rb_eThreadError,
919
+ "deadlock; priority queue contention between unscheduled fibers"
920
+ );
921
+ }
922
+ priority_queue_lock_wait_once(queue);
923
+ }
924
+ }
925
+
926
+ static void
927
+ priority_queue_unlock(priority_queue_t *queue)
928
+ {
929
+ rb_atomic_t state;
930
+
931
+ priority_queue_mutex_lock(queue);
932
+ state = RUBY_ATOMIC_LOAD(queue->publication_state);
933
+ if (state == PRIORITY_QUEUE_PUBLISHED) {
934
+ RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_INITIALIZED);
935
+ }
936
+ else if (state == PRIORITY_QUEUE_PUBLISHING) {
937
+ RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_UNINITIALIZED);
938
+ }
939
+ queue->owner_fiber = Qnil;
940
+ queue->owner_ruby_thread = Qnil;
941
+ queue->publication_owner_fiber = Qnil;
942
+ queue->publication_owner_ruby_thread = Qnil;
943
+ priority_queue_lock_notify_one_locked(queue);
944
+ priority_queue_mutex_unlock(queue);
945
+ }
946
+
947
+ typedef struct {
948
+ priority_queue_t *queue;
949
+ bool acquired;
950
+ } priority_queue_publication_wait_t;
951
+
952
+ static VALUE
953
+ priority_queue_publication_wait_body(VALUE opaque)
954
+ {
955
+ priority_queue_publication_wait_t *wait =
956
+ (priority_queue_publication_wait_t *)opaque;
957
+
958
+ priority_queue_lock(wait->queue, &wait->acquired);
959
+ return Qnil;
960
+ }
961
+
962
+ static VALUE
963
+ priority_queue_publication_wait_ensure(VALUE opaque)
964
+ {
965
+ priority_queue_publication_wait_t *wait =
966
+ (priority_queue_publication_wait_t *)opaque;
967
+
968
+ if (wait->acquired) priority_queue_unlock(wait->queue);
969
+ return Qnil;
970
+ }
971
+
972
+ static void
973
+ priority_queue_wait_for_publication(priority_queue_t *queue)
974
+ {
975
+ priority_queue_publication_wait_t wait = {
976
+ .queue = queue,
977
+ .acquired = false,
978
+ };
979
+
980
+ /* The unmarked publication owner makes recursive callbacks fail with the
981
+ * ordinary ThreadError, while other Ractors and scheduler-managed Fibers
982
+ * park until the outer operation unlocks. */
983
+ (void)rb_ensure(
984
+ priority_queue_publication_wait_body,
985
+ (VALUE)&wait,
986
+ priority_queue_publication_wait_ensure,
987
+ (VALUE)&wait
988
+ );
989
+ }
990
+
991
+ typedef VALUE (*priority_queue_operation_t)(VALUE opaque);
992
+
993
+ typedef struct {
994
+ priority_queue_t *queue;
995
+ priority_queue_operation_t operation;
996
+ VALUE argument;
997
+ bool acquired;
998
+ } priority_queue_call_t;
999
+
1000
+ static VALUE
1001
+ priority_queue_call_body(VALUE opaque)
1002
+ {
1003
+ priority_queue_call_t *call = (priority_queue_call_t *)opaque;
1004
+ priority_queue_lock(call->queue, &call->acquired);
1005
+ return call->operation(call->argument);
1006
+ }
1007
+
1008
+ static VALUE
1009
+ priority_queue_unlock_ensure(VALUE opaque)
1010
+ {
1011
+ priority_queue_call_t *call = (priority_queue_call_t *)opaque;
1012
+
1013
+ if (call->acquired) priority_queue_unlock(call->queue);
1014
+ return Qnil;
1015
+ }
1016
+
1017
+ static VALUE
1018
+ priority_queue_call(
1019
+ priority_queue_t *queue,
1020
+ priority_queue_operation_t operation,
1021
+ VALUE argument
1022
+ )
1023
+ {
1024
+ priority_queue_call_t call = {
1025
+ .queue = queue,
1026
+ .operation = operation,
1027
+ .argument = argument,
1028
+ .acquired = false,
1029
+ };
1030
+
1031
+ return rb_ensure(
1032
+ priority_queue_call_body,
1033
+ (VALUE)&call,
1034
+ priority_queue_unlock_ensure,
1035
+ (VALUE)&call
1036
+ );
1037
+ }
1038
+
1039
+ static void
1040
+ priority_queue_check_shareable(VALUE value)
1041
+ {
1042
+ if (!rb_ractor_shareable_p(value)) {
1043
+ rb_raise(eIsolationError, "value is not shareable: %" PRIsVALUE, rb_inspect(value));
1044
+ }
1045
+ }
1046
+
1047
+ /* Notification is deliberately issued while the queue operation still owns
1048
+ * its lock and before the state commit. A woken waiter cannot inspect the queue
1049
+ * until the commit and unlock complete. Callers must finish every allocation
1050
+ * and user callback before this point, then use only no-fail C mutations.
1051
+ *
1052
+ * The generic new-bucket insertion is the one conservative exception: Kazlib
1053
+ * repeats comparisons before linking the prepared node. If one raises, the
1054
+ * pending bucket ensure cleanup leaves the queue unchanged and the notification
1055
+ * is merely spurious; Kazlib never links a node before those comparisons end.
1056
+ * Homogeneous numeric keys reuse their search and commit without comparisons. */
1057
+ static void
1058
+ priority_queue_notify_before_commit(priority_queue_t *queue)
1059
+ {
1060
+ if (queue->unshared_signal) containers_unshared_signal_notify(queue->unshared_signal);
1061
+ else if (!NIL_P(queue->signal)) rb_funcall(queue->signal, id_broadcast, 0);
1062
+ }
1063
+
1064
+ RBIMPL_ATTR_NORETURN()
1065
+ static void
1066
+ priority_queue_raise_closed(void)
1067
+ {
1068
+ VALUE farce = rb_const_get(rb_cObject, id_farce);
1069
+ rb_raise(rb_const_get(farce, id_closed_error), "queue is closed");
1070
+ }
1071
+
1072
+ RBIMPL_ATTR_NORETURN()
1073
+ static void
1074
+ priority_queue_raise_sealed(void)
1075
+ {
1076
+ VALUE farce = rb_const_get(rb_cObject, id_farce);
1077
+ rb_raise(rb_const_get(farce, id_sealed_error), "queue is sealed");
1078
+ }
1079
+
1080
+ typedef struct {
1081
+ priority_queue_t *queue;
1082
+ VALUE self;
1083
+ VALUE signal;
1084
+ size_t capacity;
1085
+ bool bounded;
1086
+ bool track_age;
1087
+ } priority_queue_initialize_t;
1088
+
1089
+ static void
1090
+ priority_queue_check_initializable(VALUE self, priority_queue_t *queue)
1091
+ {
1092
+ rb_atomic_t state = RUBY_ATOMIC_LOAD(queue->publication_state);
1093
+
1094
+ if (state == PRIORITY_QUEUE_INITIALIZED) {
1095
+ rb_raise(rb_eRuntimeError, "priority queue is already initialized");
1096
+ }
1097
+ rb_check_frozen(self);
1098
+
1099
+ /* Publication marks the object busy before native shareability is set. Reject the
1100
+ * narrow pre-publication window here so a competing initializer cannot enter
1101
+ * user coercion callbacks while publication is already in progress. */
1102
+ if (RUBY_ATOMIC_LOAD(queue->publication_state) !=
1103
+ PRIORITY_QUEUE_UNINITIALIZED) {
1104
+ rb_raise(rb_eRuntimeError, "priority queue is already initialized");
1105
+ }
1106
+ }
1107
+
1108
+ static void
1109
+ priority_queue_validate_references(VALUE self)
1110
+ {
1111
+ priority_queue_t *queue = priority_queue_get_raw(self);
1112
+
1113
+ containers_check_shareable(queue->owner_fiber);
1114
+ containers_check_shareable(queue->owner_ruby_thread);
1115
+ containers_check_shareable(queue->signal);
1116
+ containers_check_shareable(queue->unshared_self);
1117
+ if (queue->size != 0 || dict_count(&queue->tree) != 0 ||
1118
+ queue->unshared_lock_waiters.count != 0) {
1119
+ rb_raise(rb_eRuntimeError, "priority queue publication requires empty storage");
1120
+ }
1121
+ }
1122
+
1123
+ static VALUE
1124
+ priority_queue_freeze(VALUE self)
1125
+ {
1126
+ priority_queue_t *queue = priority_queue_get_raw(self);
1127
+ if (queue->shared) containers_raise_unfreezable(self);
1128
+ return rb_obj_freeze(self);
1129
+ }
1130
+
1131
+ static VALUE
1132
+ priority_queue_frozen_p(VALUE self)
1133
+ {
1134
+ priority_queue_t *queue = priority_queue_get_raw(self);
1135
+ return queue->shared ? Qfalse : rb_obj_frozen_p(self);
1136
+ }
1137
+
1138
+ static VALUE
1139
+ priority_queue_make_shareable(VALUE opaque)
1140
+ {
1141
+ priority_queue_initialize_t *initialization =
1142
+ (priority_queue_initialize_t *)opaque;
1143
+
1144
+ return containers_publish_native_with_references(
1145
+ initialization->self,
1146
+ priority_queue_validate_references
1147
+ );
1148
+ }
1149
+
1150
+ static VALUE
1151
+ priority_queue_finish_publication(VALUE opaque)
1152
+ {
1153
+ priority_queue_initialize_t *initialization =
1154
+ (priority_queue_initialize_t *)opaque;
1155
+ bool shareable = rb_ractor_shareable_p(initialization->self);
1156
+
1157
+ /* An async exception can arrive after rb_ractor_make_shareable has set the
1158
+ * shareable flag. In that case the native fields already form a coherent
1159
+ * queue, so publish initialized before propagating it. */
1160
+ priority_queue_mutex_lock(initialization->queue);
1161
+ if (shareable) {
1162
+ RUBY_ATOMIC_SET(
1163
+ initialization->queue->publication_state,
1164
+ PRIORITY_QUEUE_PUBLISHED
1165
+ );
1166
+ }
1167
+ priority_queue_mutex_unlock(initialization->queue);
1168
+ return Qnil;
1169
+ }
1170
+
1171
+ static VALUE
1172
+ priority_queue_publish_shareable(priority_queue_initialize_t *initialization)
1173
+ {
1174
+ priority_queue_t *queue = initialization->queue;
1175
+
1176
+ /* Install the gate before native publication can make this typed object
1177
+ * Ractor-visible. Move the logical owner out of dmark, while retaining it
1178
+ * for callback reentry and deadlock detection. */
1179
+ priority_queue_mutex_lock(queue);
1180
+ RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_PUBLISHING);
1181
+ queue->publication_owner_fiber = queue->owner_fiber;
1182
+ queue->publication_owner_ruby_thread = queue->owner_ruby_thread;
1183
+ queue->owner_fiber = Qnil;
1184
+ queue->owner_ruby_thread = Qnil;
1185
+ priority_queue_mutex_unlock(queue);
1186
+ return rb_ensure(
1187
+ priority_queue_make_shareable,
1188
+ (VALUE)initialization,
1189
+ priority_queue_finish_publication,
1190
+ (VALUE)initialization
1191
+ );
1192
+ }
1193
+
1194
+ static VALUE
1195
+ priority_queue_initialize_commit(VALUE opaque)
1196
+ {
1197
+ priority_queue_initialize_t *initialization =
1198
+ (priority_queue_initialize_t *)opaque;
1199
+ priority_queue_t *queue = initialization->queue;
1200
+
1201
+ /* Capacity coercion and signal protocol checks above this commit can run
1202
+ * arbitrary Ruby. They may recursively initialize or freeze this object,
1203
+ * and two threads may finish parsing concurrently. The synchronized queue
1204
+ * serializes this authoritative recheck and the following no-fail field
1205
+ * assignments so a losing initializer cannot overwrite the winner. */
1206
+ if (RUBY_ATOMIC_LOAD(queue->publication_state) !=
1207
+ PRIORITY_QUEUE_UNINITIALIZED) {
1208
+ rb_raise(rb_eRuntimeError, "priority queue is already initialized");
1209
+ }
1210
+ rb_check_frozen(initialization->self);
1211
+
1212
+ containers_unshared_signal_t *unshared_signal =
1213
+ containers_unshared_signal_get_if_exact(initialization->signal);
1214
+ bool fiber_io = containers_unshared_signal_uses_fiber_io(initialization->signal);
1215
+ queue->unshared_signal = unshared_signal;
1216
+ queue->unshared_fiber_io = fiber_io;
1217
+ queue->bounded = initialization->bounded;
1218
+ queue->capacity = initialization->capacity;
1219
+ queue->track_age = initialization->track_age;
1220
+ queue->signal = initialization->signal;
1221
+ /* Publication installs its logical gate before marking the object shareable
1222
+ * and keeps that whole transition under nested ensure cleanup. */
1223
+ if (queue->shared) return priority_queue_publish_shareable(initialization);
1224
+ rb_obj_freeze(initialization->self);
1225
+ RUBY_ATOMIC_SET(queue->publication_state, PRIORITY_QUEUE_INITIALIZED);
1226
+ return initialization->self;
1227
+ }
1228
+
1229
+ static VALUE
1230
+ priority_queue_initialize(int argc, VALUE *argv, VALUE self)
1231
+ {
1232
+ VALUE keywords = Qnil;
1233
+ VALUE keyword_values[3] = {Qundef, Qundef, Qundef};
1234
+ ID keyword_ids[] = {rb_intern("capacity"), rb_intern("signal"), rb_intern("track_age")};
1235
+ priority_queue_t *queue = priority_queue_get_raw(self);
1236
+ priority_queue_initialize_t initialization = {
1237
+ .queue = queue,
1238
+ .self = self,
1239
+ .signal = Qnil,
1240
+ .capacity = 0,
1241
+ .bounded = false,
1242
+ };
1243
+ rb_scan_args(argc, argv, "0:", &keywords);
1244
+ if (!NIL_P(keywords)) {
1245
+ rb_get_kwargs(keywords, keyword_ids, 0, 3, keyword_values);
1246
+ }
1247
+ priority_queue_check_initializable(self, queue);
1248
+
1249
+ if (keyword_values[0] == Qundef || keyword_values[0] == Qnil) {
1250
+ initialization.bounded = false;
1251
+ initialization.capacity = 0;
1252
+ }
1253
+ else {
1254
+ VALUE capacity_value = rb_to_int(keyword_values[0]);
1255
+ long long capacity = NUM2LL(capacity_value);
1256
+ if (capacity <= 0) rb_raise(rb_eArgError, "capacity must be positive or nil");
1257
+ if ((unsigned long long)capacity > (unsigned long long)SIZE_MAX) {
1258
+ rb_raise(rb_eArgError, "capacity is too large");
1259
+ }
1260
+ initialization.bounded = true;
1261
+ initialization.capacity = (size_t)capacity;
1262
+ }
1263
+
1264
+ if (keyword_values[1] != Qundef && !NIL_P(keyword_values[1])) {
1265
+ if (queue->shared) priority_queue_check_shareable(keyword_values[1]);
1266
+ if (!rb_respond_to(keyword_values[1], id_broadcast)) {
1267
+ rb_raise(rb_eTypeError, "signal must respond to #broadcast");
1268
+ }
1269
+ initialization.signal = keyword_values[1];
1270
+ }
1271
+ initialization.track_age = keyword_values[2] != Qundef && RTEST(keyword_values[2]);
1272
+
1273
+ /* Capacity coercion and signal inspection can invoke arbitrary Ruby. A
1274
+ * sibling Fiber may pause there, then be resumed while another initializer
1275
+ * recursively freezes this receiver for Ractor publication. Reject that
1276
+ * resumed initializer before it can wait on the publisher's logical lock.
1277
+ * The commit retains its synchronized recheck for ordinary thread races. */
1278
+ priority_queue_check_initializable(self, queue);
1279
+
1280
+ return priority_queue_call(
1281
+ queue,
1282
+ priority_queue_initialize_commit,
1283
+ (VALUE)&initialization
1284
+ );
1285
+ }
1286
+
1287
+ static VALUE
1288
+ priority_queue_initialize_copy(VALUE self, VALUE other)
1289
+ {
1290
+ (void)self;
1291
+ (void)other;
1292
+ rb_raise(rb_eTypeError, "priority queues cannot be copied");
1293
+ }
1294
+
1295
+ typedef struct {
1296
+ priority_queue_t *queue;
1297
+ VALUE self;
1298
+ VALUE priority;
1299
+ VALUE stored_priority;
1300
+ VALUE value;
1301
+ priority_queue_bucket_t *pending_bucket;
1302
+ priority_queue_value_t *pending_entry;
1303
+ } priority_queue_push_t;
1304
+
1305
+ static VALUE
1306
+ priority_queue_push_cleanup(VALUE opaque)
1307
+ {
1308
+ priority_queue_push_t *push = (priority_queue_push_t *)opaque;
1309
+ if (push->pending_bucket) {
1310
+ priority_queue_free_tree_node(&push->pending_bucket->node, push->queue);
1311
+ push->pending_bucket = NULL;
1312
+ }
1313
+ if (push->pending_entry) {
1314
+ free(push->pending_entry);
1315
+ push->pending_entry = NULL;
1316
+ }
1317
+ return Qnil;
1318
+ }
1319
+
1320
+ static VALUE
1321
+ priority_queue_push_body(VALUE opaque)
1322
+ {
1323
+ priority_queue_push_t *push = (priority_queue_push_t *)opaque;
1324
+ priority_queue_t *queue = push->queue;
1325
+ dnode_t *node;
1326
+ priority_queue_bucket_t *bucket;
1327
+ priority_queue_value_t *entry;
1328
+ dnode_t *parent = NULL;
1329
+ int position = -1;
1330
+ bool numeric = FIXNUM_P(push->priority) ||
1331
+ (CLASS_OF(push->priority) == rb_cFloat && !isnan(RFLOAT_VALUE(push->priority)));
1332
+ bool reuse_position = queue->numeric_only && numeric &&
1333
+ (dict_isempty(&queue->tree) ||
1334
+ FIXNUM_P(push->priority) == FIXNUM_P((VALUE)dnode_getkey(queue->tree.dict_nilnode.dict_left)));
1335
+
1336
+ if (queue->closed) priority_queue_raise_closed();
1337
+ if (queue->sealed) priority_queue_raise_sealed();
1338
+ if (queue->bounded && queue->size >= queue->capacity) return Qfalse;
1339
+
1340
+ if (reuse_position && !FIXNUM_P(push->priority)) {
1341
+ /* A homogeneous Float tree needs no Ruby dispatch, type tests, or
1342
+ * unboxing of stored keys in this search. All NaNs use the slow path. */
1343
+ double priority = RFLOAT_VALUE(push->priority);
1344
+ dnode_t *nil = &queue->tree.dict_nilnode;
1345
+ dnode_t *where = nil->dict_left;
1346
+ parent = nil;
1347
+ while (where != nil) {
1348
+ double stored = ((priority_queue_bucket_t *)where)->float_priority;
1349
+ parent = where;
1350
+ position = priority < stored ? -1 : priority > stored ? 1 : 0;
1351
+ if (!position) break;
1352
+ where = position < 0 ? where->dict_left : where->dict_right;
1353
+ }
1354
+ node = where == nil ? NULL : where;
1355
+ }
1356
+ else {
1357
+ node = reuse_position
1358
+ ? dict_lookup_position(&queue->tree, (const void *)push->priority, &parent, &position)
1359
+ : dict_lookup(&queue->tree, (const void *)push->priority);
1360
+ }
1361
+ if (node) {
1362
+ bucket = (priority_queue_bucket_t *)node;
1363
+ priority_queue_identity_index_prepare_insert(bucket->identity_index, push->value);
1364
+ entry = calloc(1, queue->track_age ? sizeof(priority_queue_tracked_value_t) : sizeof(priority_queue_value_t));
1365
+ if (!entry) rb_memerror();
1366
+ entry->value = push->value;
1367
+ entry->previous = bucket->tail;
1368
+ entry->next = NULL;
1369
+ entry->identity_next = NULL;
1370
+ push->pending_entry = entry;
1371
+ }
1372
+ else {
1373
+ bucket = calloc(1, sizeof(priority_queue_bucket_t));
1374
+ entry = calloc(1, queue->track_age ? sizeof(priority_queue_tracked_value_t) : sizeof(priority_queue_value_t));
1375
+ if (!bucket || !entry) {
1376
+ free(bucket);
1377
+ free(entry);
1378
+ rb_memerror();
1379
+ }
1380
+ entry->value = push->value;
1381
+ entry->previous = NULL;
1382
+ entry->next = NULL;
1383
+ entry->identity_next = NULL;
1384
+ bucket->head = entry;
1385
+ bucket->tail = entry;
1386
+ bucket->size = 1;
1387
+ /* Own both allocations before copying a String priority: that copy can
1388
+ * raise, and the ensure handler must then release the partial bucket. */
1389
+ push->pending_bucket = bucket;
1390
+ if (RB_TYPE_P(push->priority, T_STRING)) {
1391
+ push->stored_priority = priority_queue_snapshot_string(push->priority);
1392
+ }
1393
+ else {
1394
+ push->stored_priority = push->priority;
1395
+ }
1396
+ bucket->priority = push->stored_priority;
1397
+ if (CLASS_OF(push->priority) == rb_cFloat) {
1398
+ bucket->float_priority = RFLOAT_VALUE(push->priority);
1399
+ }
1400
+ /* The bucket embeds its dnode and is recovered by address, so Kazlib's
1401
+ * auxiliary data slot is intentionally unused. Keeping it NULL also
1402
+ * means an unstable comparator's duplicate-insert fallback cannot
1403
+ * leave a pointer to the pending bucket after ensure frees it. */
1404
+ dnode_init(&bucket->node, NULL);
1405
+ bucket->node.dict_key = (const void *)bucket->priority;
1406
+ priority_queue_notify_before_commit(queue);
1407
+ bool inserted = reuse_position
1408
+ ? dict_insert_at(&queue->tree, &bucket->node, bucket->node.dict_key, parent, position)
1409
+ : dict_insert(&queue->tree, &bucket->node, bucket->node.dict_key);
1410
+ if (!inserted) {
1411
+ rb_raise(rb_eRuntimeError, "priority comparator changed during insertion");
1412
+ }
1413
+ push->pending_bucket = NULL;
1414
+ if (!reuse_position) queue->numeric_only = false;
1415
+ queue->size++;
1416
+ priority_queue_track_push(queue, entry);
1417
+ return Qtrue;
1418
+ }
1419
+
1420
+ priority_queue_notify_before_commit(queue);
1421
+ if (bucket->tail) bucket->tail->next = entry;
1422
+ else bucket->head = entry;
1423
+ bucket->tail = entry;
1424
+ bucket->size++;
1425
+ priority_queue_identity_index_add(bucket->identity_index, entry);
1426
+ queue->size++;
1427
+ priority_queue_track_push(queue, entry);
1428
+ push->pending_entry = NULL;
1429
+ return Qtrue;
1430
+ }
1431
+
1432
+ static VALUE
1433
+ priority_queue_push_locked(VALUE opaque)
1434
+ {
1435
+ return rb_ensure(
1436
+ priority_queue_push_body,
1437
+ opaque,
1438
+ priority_queue_push_cleanup,
1439
+ opaque
1440
+ );
1441
+ }
1442
+
1443
+ /* A Ractor-confined operation may run under the GVL alone when it cannot call
1444
+ * Ruby. Never bypass a suspended callback's logical owner. The caller must
1445
+ * prove comparison/notification callbacks are impossible on this path. */
1446
+ static inline bool
1447
+ priority_queue_can_run_direct(priority_queue_t *queue)
1448
+ {
1449
+ return !queue->shared && NIL_P(queue->owner_fiber) &&
1450
+ (NIL_P(queue->signal) ||
1451
+ (queue->unshared_signal && !containers_unshared_signal_has_waiters(queue->unshared_signal)));
1452
+ }
1453
+
1454
+ static VALUE
1455
+ priority_queue_push(VALUE self, VALUE priority, VALUE value)
1456
+ {
1457
+ priority_queue_t *queue = priority_queue_get(self);
1458
+ priority_queue_push_t push = {
1459
+ .queue = queue,
1460
+ .self = self,
1461
+ .priority = priority,
1462
+ .stored_priority = priority,
1463
+ .value = value,
1464
+ .pending_bucket = NULL,
1465
+ .pending_entry = NULL,
1466
+ };
1467
+ if (queue->shared) priority_queue_check_shareable(priority);
1468
+ if (queue->shared) priority_queue_check_shareable(value);
1469
+ if (priority_queue_can_run_direct(queue) && queue->numeric_only &&
1470
+ (FIXNUM_P(priority) || (CLASS_OF(priority) == rb_cFloat && !isnan(RFLOAT_VALUE(priority)))) &&
1471
+ (dict_isempty(&queue->tree) ||
1472
+ FIXNUM_P(priority) == FIXNUM_P((VALUE)dnode_getkey(queue->tree.dict_nilnode.dict_left)))) {
1473
+ /* Retain allocation cleanup, but there is no callback lock to release.
1474
+ * The homogeneous numeric path uses dict_insert_at without dispatch. */
1475
+ return priority_queue_push_locked((VALUE)&push);
1476
+ }
1477
+ return priority_queue_call(
1478
+ queue,
1479
+ priority_queue_push_locked,
1480
+ (VALUE)&push
1481
+ );
1482
+ }
1483
+
1484
+ typedef enum {
1485
+ PRIORITY_QUEUE_POP,
1486
+ PRIORITY_QUEUE_PEEK,
1487
+ PRIORITY_QUEUE_PEEK_PRIORITY,
1488
+ } priority_queue_read_kind_t;
1489
+
1490
+ typedef struct {
1491
+ priority_queue_t *queue;
1492
+ priority_queue_read_kind_t kind;
1493
+ VALUE result;
1494
+ VALUE latest_priority;
1495
+ bool empty;
1496
+ bool last;
1497
+ bool limited;
1498
+ } priority_queue_read_t;
1499
+
1500
+ static VALUE
1501
+ priority_queue_read_body(VALUE opaque)
1502
+ {
1503
+ priority_queue_read_t *read = (priority_queue_read_t *)opaque;
1504
+ priority_queue_t *queue = read->queue;
1505
+ dnode_t *node;
1506
+ priority_queue_bucket_t *bucket;
1507
+ priority_queue_value_t *entry;
1508
+
1509
+ if (queue->closed) priority_queue_raise_closed();
1510
+ node = read->last ? dict_last(&queue->tree) : dict_first(&queue->tree);
1511
+ if (!node) {
1512
+ read->empty = true;
1513
+ return Qnil;
1514
+ }
1515
+
1516
+ /* Test and remove under the same logical lock: a timer consumer must not
1517
+ * pop a future entry if another consumer took the due one after a peek. */
1518
+ if (read->limited && priority_queue_compare(
1519
+ dnode_getkey(node), (const void *)read->latest_priority, queue) > 0) {
1520
+ read->empty = true;
1521
+ return Qnil;
1522
+ }
1523
+
1524
+ bucket = (priority_queue_bucket_t *)node;
1525
+ if (read->kind == PRIORITY_QUEUE_PEEK_PRIORITY) {
1526
+ read->result = (VALUE)dnode_getkey(node);
1527
+ return read->result;
1528
+ }
1529
+
1530
+ read->result = bucket->head->value;
1531
+ if (read->kind == PRIORITY_QUEUE_PEEK) return read->result;
1532
+
1533
+ entry = bucket->head;
1534
+ priority_queue_notify_before_commit(queue);
1535
+ priority_queue_identity_index_remove(bucket->identity_index, entry);
1536
+ priority_queue_bucket_unlink(bucket, entry);
1537
+ priority_queue_track_remove(queue, entry);
1538
+ free(entry);
1539
+ if (--queue->size == 0) {
1540
+ queue->numeric_only = true;
1541
+ if (queue->sealed) queue->closed = true;
1542
+ }
1543
+ if (bucket->size == 0) dict_delete_free(&queue->tree, node);
1544
+ return read->result;
1545
+ }
1546
+
1547
+ static VALUE
1548
+ priority_queue_read(VALUE self, priority_queue_read_kind_t kind, bool last)
1549
+ {
1550
+ priority_queue_t *queue = priority_queue_get(self);
1551
+ priority_queue_read_t read = {
1552
+ .queue = queue,
1553
+ .kind = kind,
1554
+ .result = Qnil,
1555
+ .empty = false,
1556
+ .last = last,
1557
+ };
1558
+ if (priority_queue_can_run_direct(queue)) {
1559
+ priority_queue_read_body((VALUE)&read);
1560
+ }
1561
+ else {
1562
+ priority_queue_call(queue, priority_queue_read_body, (VALUE)&read);
1563
+ }
1564
+ if (read.empty) return rb_block_given_p() ? rb_yield_values(0) : Qnil;
1565
+ return read.result;
1566
+ }
1567
+
1568
+ static VALUE
1569
+ priority_queue_pop(VALUE self)
1570
+ {
1571
+ return priority_queue_read(self, PRIORITY_QUEUE_POP, false);
1572
+ }
1573
+
1574
+ static VALUE
1575
+ priority_queue_pop_before(VALUE self, VALUE latest_priority)
1576
+ {
1577
+ priority_queue_t *queue = priority_queue_get(self);
1578
+ if (queue->shared) priority_queue_check_shareable(latest_priority);
1579
+ priority_queue_read_t read = {
1580
+ .queue = queue,
1581
+ .kind = PRIORITY_QUEUE_POP,
1582
+ .result = Qnil,
1583
+ .latest_priority = latest_priority,
1584
+ .limited = true,
1585
+ };
1586
+ bool direct = priority_queue_can_run_direct(queue);
1587
+ if (direct) {
1588
+ dnode_t *first = dict_first(&queue->tree);
1589
+ VALUE first_priority = first ? (VALUE)dnode_getkey(first) : Qnil;
1590
+ direct = !first ||
1591
+ (FIXNUM_P(latest_priority) && FIXNUM_P(first_priority)) ||
1592
+ (CLASS_OF(latest_priority) == rb_cFloat && !isnan(RFLOAT_VALUE(latest_priority)) &&
1593
+ CLASS_OF(first_priority) == rb_cFloat && !isnan(RFLOAT_VALUE(first_priority)));
1594
+ }
1595
+ if (direct) {
1596
+ priority_queue_read_body((VALUE)&read);
1597
+ }
1598
+ else {
1599
+ priority_queue_call(queue, priority_queue_read_body, (VALUE)&read);
1600
+ }
1601
+ if (read.empty) return rb_block_given_p() ? rb_yield_values(0) : Qnil;
1602
+ return read.result;
1603
+ }
1604
+
1605
+ static VALUE
1606
+ priority_queue_pop_last(VALUE self)
1607
+ {
1608
+ return priority_queue_read(self, PRIORITY_QUEUE_POP, true);
1609
+ }
1610
+
1611
+ static VALUE
1612
+ priority_queue_peek(VALUE self)
1613
+ {
1614
+ return priority_queue_read(self, PRIORITY_QUEUE_PEEK, false);
1615
+ }
1616
+
1617
+ static VALUE
1618
+ priority_queue_peek_last(VALUE self)
1619
+ {
1620
+ return priority_queue_read(self, PRIORITY_QUEUE_PEEK, true);
1621
+ }
1622
+
1623
+ static VALUE
1624
+ priority_queue_peek_priority(VALUE self)
1625
+ {
1626
+ return priority_queue_read(self, PRIORITY_QUEUE_PEEK_PRIORITY, false);
1627
+ }
1628
+
1629
+ static VALUE
1630
+ priority_queue_peek_last_priority(VALUE self)
1631
+ {
1632
+ return priority_queue_read(self, PRIORITY_QUEUE_PEEK_PRIORITY, true);
1633
+ }
1634
+
1635
+ typedef struct {
1636
+ priority_queue_t *queue;
1637
+ VALUE self;
1638
+ VALUE priority;
1639
+ VALUE value;
1640
+ bool match;
1641
+ } priority_queue_delete_t;
1642
+
1643
+ static VALUE
1644
+ priority_queue_delete_body(VALUE opaque)
1645
+ {
1646
+ priority_queue_delete_t *deletion = (priority_queue_delete_t *)opaque;
1647
+ priority_queue_t *queue = deletion->queue;
1648
+ dnode_t *node;
1649
+ priority_queue_bucket_t *bucket;
1650
+ priority_queue_value_t *entry;
1651
+
1652
+ if (queue->closed) priority_queue_raise_closed();
1653
+ node = dict_lookup(&queue->tree, (const void *)deletion->priority);
1654
+ if (!node) return Qfalse;
1655
+ bucket = (priority_queue_bucket_t *)node;
1656
+
1657
+ for (entry = bucket->head; entry; entry = entry->next) {
1658
+ VALUE matched = deletion->match
1659
+ ? rb_funcall(deletion->value, id_case_equal, 1, entry->value)
1660
+ : priority_queue_call_ruby_callback(
1661
+ entry->value,
1662
+ deletion->value,
1663
+ true,
1664
+ NULL
1665
+ );
1666
+ if (!RTEST(matched)) continue;
1667
+ priority_queue_notify_before_commit(queue);
1668
+ priority_queue_identity_index_remove(bucket->identity_index, entry);
1669
+ priority_queue_bucket_unlink(bucket, entry);
1670
+ priority_queue_track_remove(queue, entry);
1671
+ free(entry);
1672
+ if (--queue->size == 0) {
1673
+ queue->numeric_only = true;
1674
+ if (queue->sealed) queue->closed = true;
1675
+ }
1676
+ if (bucket->size == 0) dict_delete_free(&queue->tree, node);
1677
+ return Qtrue;
1678
+ }
1679
+ return Qfalse;
1680
+ }
1681
+
1682
+ static VALUE
1683
+ priority_queue_delete(VALUE self, VALUE priority, VALUE value)
1684
+ {
1685
+ priority_queue_t *queue = priority_queue_get(self);
1686
+ priority_queue_delete_t deletion = {
1687
+ .queue = queue,
1688
+ .self = self,
1689
+ .priority = priority,
1690
+ .value = value,
1691
+ };
1692
+ if (queue->shared) priority_queue_check_shareable(priority);
1693
+ if (queue->shared) priority_queue_check_shareable(value);
1694
+ return priority_queue_call(
1695
+ queue,
1696
+ priority_queue_delete_body,
1697
+ (VALUE)&deletion
1698
+ );
1699
+ }
1700
+
1701
+ /* Match from the supplied pattern's side. Public queues can now store their
1702
+ * managed values directly and allocate a comparison probe only on deletion. */
1703
+ static VALUE
1704
+ priority_queue_delete_match(VALUE self, VALUE priority, VALUE pattern)
1705
+ {
1706
+ priority_queue_t *queue = priority_queue_get(self);
1707
+ priority_queue_delete_t deletion = {
1708
+ .queue = queue,
1709
+ .self = self,
1710
+ .priority = priority,
1711
+ .value = pattern,
1712
+ .match = true,
1713
+ };
1714
+ if (queue->shared) priority_queue_check_shareable(priority);
1715
+ if (queue->shared) priority_queue_check_shareable(pattern);
1716
+ return priority_queue_call(queue, priority_queue_delete_body, (VALUE)&deletion);
1717
+ }
1718
+
1719
+ static VALUE
1720
+ priority_queue_delete_identity_body(VALUE opaque)
1721
+ {
1722
+ priority_queue_delete_t *deletion = (priority_queue_delete_t *)opaque;
1723
+ priority_queue_t *queue = deletion->queue;
1724
+ dnode_t *node;
1725
+ priority_queue_bucket_t *bucket;
1726
+ priority_queue_value_t *entry = NULL;
1727
+
1728
+ if (queue->closed) priority_queue_raise_closed();
1729
+ node = dict_lookup(&queue->tree, (const void *)deletion->priority);
1730
+ if (!node) return Qfalse;
1731
+ bucket = (priority_queue_bucket_t *)node;
1732
+
1733
+ if (!bucket->identity_index &&
1734
+ bucket->size >= PRIORITY_QUEUE_IDENTITY_INDEX_THRESHOLD) {
1735
+ priority_queue_identity_index_build(bucket);
1736
+ }
1737
+ if (bucket->identity_index) {
1738
+ priority_queue_identity_slot_t *slot = priority_queue_identity_index_lookup(
1739
+ bucket->identity_index,
1740
+ deletion->value
1741
+ );
1742
+ if (slot) entry = slot->head;
1743
+ }
1744
+ else {
1745
+ for (entry = bucket->head; entry; entry = entry->next) {
1746
+ if (entry->value == deletion->value) break;
1747
+ }
1748
+ }
1749
+
1750
+ if (!entry) return Qfalse;
1751
+ priority_queue_notify_before_commit(queue);
1752
+ priority_queue_identity_index_remove(bucket->identity_index, entry);
1753
+ priority_queue_bucket_unlink(bucket, entry);
1754
+ priority_queue_track_remove(queue, entry);
1755
+ free(entry);
1756
+ if (--queue->size == 0) {
1757
+ queue->numeric_only = true;
1758
+ if (queue->sealed) queue->closed = true;
1759
+ }
1760
+ if (bucket->size == 0) dict_delete_free(&queue->tree, node);
1761
+ return Qtrue;
1762
+ }
1763
+
1764
+ static VALUE
1765
+ priority_queue_delete_identity(VALUE self, VALUE priority, VALUE value)
1766
+ {
1767
+ priority_queue_t *queue = priority_queue_get(self);
1768
+ priority_queue_delete_t deletion = {
1769
+ .queue = queue,
1770
+ .self = self,
1771
+ .priority = priority,
1772
+ .value = value,
1773
+ };
1774
+ if (queue->shared) priority_queue_check_shareable(priority);
1775
+ if (queue->shared) priority_queue_check_shareable(value);
1776
+ return priority_queue_call(
1777
+ queue,
1778
+ priority_queue_delete_identity_body,
1779
+ (VALUE)&deletion
1780
+ );
1781
+ }
1782
+
1783
+ typedef struct {
1784
+ priority_queue_t *queue;
1785
+ VALUE self;
1786
+ } priority_queue_self_operation_t;
1787
+
1788
+ static VALUE
1789
+ priority_queue_size_body(VALUE opaque)
1790
+ {
1791
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1792
+ return SIZET2NUM(operation->queue->size);
1793
+ }
1794
+
1795
+ static VALUE
1796
+ priority_queue_size(VALUE self)
1797
+ {
1798
+ priority_queue_t *queue = priority_queue_get(self);
1799
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1800
+ if (priority_queue_can_run_direct(queue)) return priority_queue_size_body((VALUE)&operation);
1801
+ return priority_queue_call(
1802
+ queue,
1803
+ priority_queue_size_body,
1804
+ (VALUE)&operation
1805
+ );
1806
+ }
1807
+
1808
+ static VALUE
1809
+ priority_queue_empty_body(VALUE opaque)
1810
+ {
1811
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1812
+ return operation->queue->size == 0 ? Qtrue : Qfalse;
1813
+ }
1814
+
1815
+ static VALUE
1816
+ priority_queue_empty(VALUE self)
1817
+ {
1818
+ priority_queue_t *queue = priority_queue_get(self);
1819
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1820
+ if (priority_queue_can_run_direct(queue)) return priority_queue_empty_body((VALUE)&operation);
1821
+ return priority_queue_call(
1822
+ queue,
1823
+ priority_queue_empty_body,
1824
+ (VALUE)&operation
1825
+ );
1826
+ }
1827
+
1828
+ static VALUE
1829
+ priority_queue_clear_body(VALUE opaque)
1830
+ {
1831
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1832
+ priority_queue_notify_before_commit(operation->queue);
1833
+ dict_free_nodes(&operation->queue->tree);
1834
+ if (operation->queue->size > 0 && operation->queue->track_age) operation->queue->generation++;
1835
+ operation->queue->size = 0;
1836
+ operation->queue->age_head = NULL;
1837
+ operation->queue->age_tail = NULL;
1838
+ operation->queue->numeric_only = true;
1839
+ if (operation->queue->sealed) operation->queue->closed = true;
1840
+ return operation->self;
1841
+ }
1842
+
1843
+ static VALUE
1844
+ priority_queue_seal_body(VALUE opaque)
1845
+ {
1846
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1847
+ if (!operation->queue->sealed) {
1848
+ priority_queue_notify_before_commit(operation->queue);
1849
+ operation->queue->sealed = true;
1850
+ if (operation->queue->track_age) operation->queue->generation++;
1851
+ if (operation->queue->size == 0) operation->queue->closed = true;
1852
+ }
1853
+ return operation->self;
1854
+ }
1855
+
1856
+ static VALUE
1857
+ priority_queue_seal(VALUE self)
1858
+ {
1859
+ priority_queue_t *queue = priority_queue_get(self);
1860
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1861
+ if (priority_queue_can_run_direct(queue)) return priority_queue_seal_body((VALUE)&operation);
1862
+ return priority_queue_call(queue, priority_queue_seal_body, (VALUE)&operation);
1863
+ }
1864
+
1865
+ static VALUE
1866
+ priority_queue_clear(VALUE self)
1867
+ {
1868
+ priority_queue_t *queue = priority_queue_get(self);
1869
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1870
+ if (priority_queue_can_run_direct(queue)) return priority_queue_clear_body((VALUE)&operation);
1871
+ return priority_queue_call(
1872
+ queue,
1873
+ priority_queue_clear_body,
1874
+ (VALUE)&operation
1875
+ );
1876
+ }
1877
+
1878
+ static VALUE
1879
+ priority_queue_close_body(VALUE opaque)
1880
+ {
1881
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1882
+ priority_queue_notify_before_commit(operation->queue);
1883
+ if (!operation->queue->closed && operation->queue->track_age) operation->queue->generation++;
1884
+ operation->queue->sealed = true;
1885
+ operation->queue->closed = true;
1886
+ return operation->self;
1887
+ }
1888
+
1889
+ static VALUE
1890
+ priority_queue_close(VALUE self)
1891
+ {
1892
+ priority_queue_t *queue = priority_queue_get(self);
1893
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1894
+ if (priority_queue_can_run_direct(queue)) return priority_queue_close_body((VALUE)&operation);
1895
+ return priority_queue_call(
1896
+ queue,
1897
+ priority_queue_close_body,
1898
+ (VALUE)&operation
1899
+ );
1900
+ }
1901
+
1902
+ static VALUE
1903
+ priority_queue_closed_body(VALUE opaque)
1904
+ {
1905
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1906
+ return operation->queue->closed ? Qtrue : Qfalse;
1907
+ }
1908
+
1909
+ static VALUE
1910
+ priority_queue_closed(VALUE self)
1911
+ {
1912
+ priority_queue_t *queue = priority_queue_get(self);
1913
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1914
+ if (priority_queue_can_run_direct(queue)) return priority_queue_closed_body((VALUE)&operation);
1915
+ return priority_queue_call(
1916
+ queue,
1917
+ priority_queue_closed_body,
1918
+ (VALUE)&operation
1919
+ );
1920
+ }
1921
+
1922
+ static VALUE
1923
+ priority_queue_sealed_body(VALUE opaque)
1924
+ {
1925
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1926
+ return operation->queue->sealed ? Qtrue : Qfalse;
1927
+ }
1928
+
1929
+ static VALUE
1930
+ priority_queue_sealed(VALUE self)
1931
+ {
1932
+ priority_queue_t *queue = priority_queue_get(self);
1933
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1934
+ if (priority_queue_can_run_direct(queue)) return priority_queue_sealed_body((VALUE)&operation);
1935
+ return priority_queue_call(queue, priority_queue_sealed_body, (VALUE)&operation);
1936
+ }
1937
+
1938
+ static VALUE
1939
+ priority_queue_age_tracking(VALUE self)
1940
+ {
1941
+ return priority_queue_get(self)->track_age ? Qtrue : Qfalse;
1942
+ }
1943
+
1944
+ static VALUE
1945
+ priority_queue_generation_body(VALUE opaque)
1946
+ {
1947
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1948
+ return ULL2NUM(operation->queue->generation);
1949
+ }
1950
+
1951
+ static VALUE
1952
+ priority_queue_generation(VALUE self)
1953
+ {
1954
+ priority_queue_t *queue = priority_queue_get(self);
1955
+ if (!queue->track_age) return Qnil;
1956
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1957
+ if (priority_queue_can_run_direct(queue)) return priority_queue_generation_body((VALUE)&operation);
1958
+ return priority_queue_call(queue, priority_queue_generation_body, (VALUE)&operation);
1959
+ }
1960
+
1961
+ static VALUE
1962
+ priority_queue_oldest_body(VALUE opaque)
1963
+ {
1964
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1965
+ priority_queue_tracked_value_t *head = operation->queue->age_head;
1966
+ return head ? DBL2NUM(head->enqueued_at) : Qnil;
1967
+ }
1968
+
1969
+ static VALUE
1970
+ priority_queue_oldest_enqueued_at(VALUE self)
1971
+ {
1972
+ priority_queue_t *queue = priority_queue_get(self);
1973
+ if (!queue->track_age) return Qnil;
1974
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1975
+ if (priority_queue_can_run_direct(queue)) return priority_queue_oldest_body((VALUE)&operation);
1976
+ return priority_queue_call(queue, priority_queue_oldest_body, (VALUE)&operation);
1977
+ }
1978
+
1979
+ static VALUE
1980
+ priority_queue_oldest_age_body(VALUE opaque)
1981
+ {
1982
+ priority_queue_self_operation_t *operation = (priority_queue_self_operation_t *)opaque;
1983
+ priority_queue_tracked_value_t *head = operation->queue->age_head;
1984
+ return head ? DBL2NUM(priority_queue_monotonic_now() - head->enqueued_at) : Qnil;
1985
+ }
1986
+
1987
+ static VALUE
1988
+ priority_queue_oldest_age(VALUE self)
1989
+ {
1990
+ priority_queue_t *queue = priority_queue_get(self);
1991
+ if (!queue->track_age) return Qnil;
1992
+ priority_queue_self_operation_t operation = {.queue = queue, .self = self};
1993
+ if (priority_queue_can_run_direct(queue)) return priority_queue_oldest_age_body((VALUE)&operation);
1994
+ return priority_queue_call(queue, priority_queue_oldest_age_body, (VALUE)&operation);
1995
+ }
1996
+
1997
+ static VALUE
1998
+ priority_queue_capacity(VALUE self)
1999
+ {
2000
+ priority_queue_t *queue = priority_queue_get(self);
2001
+ return queue->bounded ? SIZET2NUM(queue->capacity) : Qnil;
2002
+ }
2003
+
2004
+ static void
2005
+ priority_queue_define_methods(VALUE klass)
2006
+ {
2007
+ rb_define_private_method(klass, "initialize", priority_queue_initialize, -1);
2008
+ rb_define_private_method(klass, "initialize_copy", priority_queue_initialize_copy, 1);
2009
+ rb_define_method(klass, "push", priority_queue_push, 2);
2010
+ rb_define_method(klass, "pop", priority_queue_pop, 0);
2011
+ rb_define_method(klass, "pop_before", priority_queue_pop_before, 1);
2012
+ rb_define_method(klass, "pop_last", priority_queue_pop_last, 0);
2013
+ rb_define_method(klass, "peek", priority_queue_peek, 0);
2014
+ rb_define_method(klass, "peek_last", priority_queue_peek_last, 0);
2015
+ rb_define_method(klass, "peek_priority", priority_queue_peek_priority, 0);
2016
+ rb_define_method(klass, "peek_last_priority", priority_queue_peek_last_priority, 0);
2017
+ rb_define_method(klass, "delete", priority_queue_delete, 2);
2018
+ rb_define_method(klass, "delete_match", priority_queue_delete_match, 2);
2019
+ rb_define_method(klass, "delete_identity", priority_queue_delete_identity, 2);
2020
+ rb_define_method(klass, "size", priority_queue_size, 0);
2021
+ rb_define_method(klass, "empty?", priority_queue_empty, 0);
2022
+ rb_define_method(klass, "clear", priority_queue_clear, 0);
2023
+ rb_define_method(klass, "close", priority_queue_close, 0);
2024
+ rb_define_method(klass, "seal", priority_queue_seal, 0);
2025
+ rb_define_method(klass, "closed?", priority_queue_closed, 0);
2026
+ rb_define_method(klass, "sealed?", priority_queue_sealed, 0);
2027
+ rb_define_method(klass, "age_tracking?", priority_queue_age_tracking, 0);
2028
+ rb_define_method(klass, "generation", priority_queue_generation, 0);
2029
+ rb_define_method(klass, "oldest_enqueued_at", priority_queue_oldest_enqueued_at, 0);
2030
+ rb_define_method(klass, "oldest_age", priority_queue_oldest_age, 0);
2031
+ rb_define_method(klass, "capacity", priority_queue_capacity, 0);
2032
+ rb_define_method(klass, "freeze", priority_queue_freeze, 0);
2033
+ rb_define_method(klass, "frozen?", priority_queue_frozen_p, 0);
2034
+ }
2035
+
2036
+ void
2037
+ containers_init_priority_queue(VALUE namespace)
2038
+ {
2039
+ ID id_ractor = rb_intern("Ractor");
2040
+ VALUE ractor;
2041
+
2042
+ id_compare = rb_intern("<=>");
2043
+ id_broadcast = rb_intern("broadcast");
2044
+ id_case_equal = rb_intern("===");
2045
+ id_farce = rb_intern("Farce");
2046
+ id_closed_error = rb_intern("ClosedQueueError");
2047
+ id_sealed_error = rb_intern("SealedQueueError");
2048
+ ractor = rb_const_get(rb_cObject, id_ractor);
2049
+ eIsolationError = rb_const_get(ractor, rb_intern("IsolationError"));
2050
+
2051
+ cPriorityQueue = rb_define_class_under(namespace, "PriorityQueue", rb_cObject);
2052
+ rb_define_alloc_func(cPriorityQueue, priority_queue_allocate);
2053
+ priority_queue_define_methods(cPriorityQueue);
2054
+ VALUE unshared = rb_define_class_under(namespace, "UnsharedPriorityQueue", cPriorityQueue);
2055
+ rb_define_alloc_func(unshared, unshared_priority_queue_allocate);
2056
+ }