farce 0.0.1.alpha2-x86_64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +26 -0
  3. data/CONTRIBUTING.md +71 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +1524 -0
  6. data/SECURITY.md +10 -0
  7. data/docs/benchmarks.md +185 -0
  8. data/docs/gems/dry-types.md +290 -0
  9. data/docs/gems/msgpack.md +70 -0
  10. data/docs/gems/ractor-shim.md +63 -0
  11. data/docs/modes.md +628 -0
  12. data/docs/scopes.md +649 -0
  13. data/docs/variants.md +346 -0
  14. data/ext/ext_helper.rb +20 -0
  15. data/ext/farce/README.md +21 -0
  16. data/ext/farce/atom.c +1023 -0
  17. data/ext/farce/bounded_map.c +1683 -0
  18. data/ext/farce/containers.h +77 -0
  19. data/ext/farce/counter.c +399 -0
  20. data/ext/farce/darwin.c +100 -0
  21. data/ext/farce/depend +12 -0
  22. data/ext/farce/dict.c +1523 -0
  23. data/ext/farce/dict.h +152 -0
  24. data/ext/farce/drivers.c +237 -0
  25. data/ext/farce/exchanger.c +299 -0
  26. data/ext/farce/extconf.rb +99 -0
  27. data/ext/farce/farce.c +359 -0
  28. data/ext/farce/flag.c +288 -0
  29. data/ext/farce/io.c +338 -0
  30. data/ext/farce/lock.c +510 -0
  31. data/ext/farce/map.c +2240 -0
  32. data/ext/farce/priority_queue.c +2056 -0
  33. data/ext/farce/queue.c +1059 -0
  34. data/ext/farce/reactor.c +820 -0
  35. data/ext/farce/reactor.h +103 -0
  36. data/ext/farce/shareable.h +31 -0
  37. data/ext/farce/signal.c +350 -0
  38. data/ext/farce/transaction.c +354 -0
  39. data/ext/farce/transaction.h +40 -0
  40. data/ext/farce/tree_map.c +1953 -0
  41. data/ext/farce/trie.c +2020 -0
  42. data/ext/farce/unshareable.c +155 -0
  43. data/ext/farce/unshared_io_pool.h +234 -0
  44. data/ext/farce/unshared_signal.c +263 -0
  45. data/ext/farce/unshared_wait.h +193 -0
  46. data/ext/farce/unsupported.c +6 -0
  47. data/ext/farce/vector.c +1195 -0
  48. data/ext/farce/weak_map.c +1714 -0
  49. data/ext/java/org/farce/BoundedMap.java +394 -0
  50. data/ext/java/org/farce/FiberScheduler.java +141 -0
  51. data/ext/java/org/farce/PriorityKey.java +45 -0
  52. data/ext/java/org/farce/PriorityQueue.java +373 -0
  53. data/ext/java/org/farce/QueueSignal.java +28 -0
  54. data/ext/rebind/README.md +14 -0
  55. data/ext/rebind/extconf.rb +10 -0
  56. data/ext/rebind/rebind.c +186 -0
  57. data/lib/farce/_yard/internal.rb +13 -0
  58. data/lib/farce/_yard/macros.rb +61 -0
  59. data/lib/farce/_yard/ractor.rb +46 -0
  60. data/lib/farce/abstract/atom.rb +186 -0
  61. data/lib/farce/abstract/bounded_map.rb +232 -0
  62. data/lib/farce/abstract/collection.rb +151 -0
  63. data/lib/farce/abstract/concurrent_map.rb +381 -0
  64. data/lib/farce/abstract/counter.rb +193 -0
  65. data/lib/farce/abstract/duplicable_map.rb +229 -0
  66. data/lib/farce/abstract/exchanger.rb +26 -0
  67. data/lib/farce/abstract/flag.rb +71 -0
  68. data/lib/farce/abstract/lazy.rb +115 -0
  69. data/lib/farce/abstract/lease.rb +104 -0
  70. data/lib/farce/abstract/lease_map.rb +261 -0
  71. data/lib/farce/abstract/lease_pool.rb +91 -0
  72. data/lib/farce/abstract/lfu_map.rb +20 -0
  73. data/lib/farce/abstract/lru_map.rb +25 -0
  74. data/lib/farce/abstract/map.rb +345 -0
  75. data/lib/farce/abstract/molecule.rb +245 -0
  76. data/lib/farce/abstract/port.rb +74 -0
  77. data/lib/farce/abstract/priority_queue.rb +117 -0
  78. data/lib/farce/abstract/queue.rb +269 -0
  79. data/lib/farce/abstract/scheduler.rb +111 -0
  80. data/lib/farce/abstract/set.rb +910 -0
  81. data/lib/farce/abstract/sorted_set.rb +172 -0
  82. data/lib/farce/abstract/timer_queue.rb +136 -0
  83. data/lib/farce/abstract/tree_map.rb +269 -0
  84. data/lib/farce/abstract/value.rb +68 -0
  85. data/lib/farce/abstract/vector.rb +1126 -0
  86. data/lib/farce/abstract/weak_atom.rb +32 -0
  87. data/lib/farce/abstract/weak_key_map.rb +12 -0
  88. data/lib/farce/abstract/weak_map.rb +13 -0
  89. data/lib/farce/abstract/weak_set.rb +12 -0
  90. data/lib/farce/abstract/weak_value_map.rb +12 -0
  91. data/lib/farce/abstract.rb +17 -0
  92. data/lib/farce/atom.rb +293 -0
  93. data/lib/farce/class_mirror.rb +87 -0
  94. data/lib/farce/clock.rb +121 -0
  95. data/lib/farce/config.rb +229 -0
  96. data/lib/farce/counter.rb +71 -0
  97. data/lib/farce/deduper.rb +122 -0
  98. data/lib/farce/engine/jruby/bounded_map.rb +314 -0
  99. data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
  100. data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
  101. data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
  102. data/lib/farce/engine/jruby/map.rb +505 -0
  103. data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
  104. data/lib/farce/engine/jruby/signal.rb +147 -0
  105. data/lib/farce/engine/jruby.rb +63 -0
  106. data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
  107. data/lib/farce/engine/jvm/counter.rb +102 -0
  108. data/lib/farce/engine/jvm/extension.rb +32 -0
  109. data/lib/farce/engine/jvm/farce.jar +0 -0
  110. data/lib/farce/engine/jvm/flag.rb +79 -0
  111. data/lib/farce/engine/jvm/priority_queue.rb +217 -0
  112. data/lib/farce/engine/jvm/tree_map.rb +350 -0
  113. data/lib/farce/engine/jvm/types.rb +180 -0
  114. data/lib/farce/engine/jvm.rb +19 -0
  115. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  116. data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
  117. data/lib/farce/engine/ruby/3.4/port.rb +186 -0
  118. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
  119. data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
  120. data/lib/farce/engine/ruby/3.4/rebind.so +0 -0
  121. data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
  122. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  123. data/lib/farce/engine/ruby/4.0/port.rb +19 -0
  124. data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
  125. data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
  126. data/lib/farce/engine/ruby/4.0/rebind.so +0 -0
  127. data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
  128. data/lib/farce/engine/ruby/4.1/port.rb +21 -0
  129. data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
  130. data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
  131. data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
  132. data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
  133. data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
  134. data/lib/farce/engine/ruby/shared/lease.rb +26 -0
  135. data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
  136. data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
  137. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
  138. data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
  139. data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
  140. data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
  141. data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
  142. data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
  143. data/lib/farce/engine/ruby/shared/vault.rb +144 -0
  144. data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
  145. data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
  146. data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
  147. data/lib/farce/engine/ruby.rb +130 -0
  148. data/lib/farce/engine/shared/atom.rb +10 -0
  149. data/lib/farce/engine/shared/exchanger.rb +130 -0
  150. data/lib/farce/engine/shared/identity_key.rb +22 -0
  151. data/lib/farce/engine/shared/lease.rb +10 -0
  152. data/lib/farce/engine/shared/lease_pool.rb +10 -0
  153. data/lib/farce/engine/shared/main_scheduler.rb +20 -0
  154. data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
  155. data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
  156. data/lib/farce/engine/shared/port.rb +44 -0
  157. data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
  158. data/lib/farce/engine/shared/proxy_owner.rb +43 -0
  159. data/lib/farce/engine/shared/queue.rb +233 -0
  160. data/lib/farce/engine/shared/ractor_methods.rb +73 -0
  161. data/lib/farce/engine/shared/rebindable.rb +15 -0
  162. data/lib/farce/engine/shared/strict_atom.rb +73 -0
  163. data/lib/farce/engine/shared/strict_map.rb +117 -0
  164. data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
  165. data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
  166. data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
  167. data/lib/farce/engine/shared/trie.rb +317 -0
  168. data/lib/farce/engine/shared/trie_builder.rb +199 -0
  169. data/lib/farce/engine/shared/unshareable.rb +14 -0
  170. data/lib/farce/engine/shared/unshared_atom.rb +256 -0
  171. data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
  172. data/lib/farce/engine/shared/unshared_queue.rb +18 -0
  173. data/lib/farce/engine/shared/unshared_signal.rb +21 -0
  174. data/lib/farce/engine/shared/unshared_vector.rb +377 -0
  175. data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
  176. data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
  177. data/lib/farce/engine/shared/vault.rb +31 -0
  178. data/lib/farce/engine/shared/vector.rb +83 -0
  179. data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
  180. data/lib/farce/engine/shared/weak_atom.rb +20 -0
  181. data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
  182. data/lib/farce/engine/shared/weak_map/index.rb +249 -0
  183. data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
  184. data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
  185. data/lib/farce/engine/shared.rb +63 -0
  186. data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
  187. data/lib/farce/engine/truffleruby/lock.rb +121 -0
  188. data/lib/farce/engine/truffleruby/map.rb +656 -0
  189. data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
  190. data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
  191. data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
  192. data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
  193. data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
  194. data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
  195. data/lib/farce/engine/truffleruby/signal.rb +77 -0
  196. data/lib/farce/engine/truffleruby.rb +148 -0
  197. data/lib/farce/envelope.rb +335 -0
  198. data/lib/farce/error.rb +35 -0
  199. data/lib/farce/exchanger.rb +41 -0
  200. data/lib/farce/flag.rb +27 -0
  201. data/lib/farce/integrations/active_support/blank.rb +68 -0
  202. data/lib/farce/integrations/active_support/clock.rb +17 -0
  203. data/lib/farce/integrations/active_support/duplicable.rb +78 -0
  204. data/lib/farce/integrations/active_support/map.rb +175 -0
  205. data/lib/farce/integrations/active_support/set.rb +60 -0
  206. data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
  207. data/lib/farce/integrations/active_support/vector.rb +231 -0
  208. data/lib/farce/integrations/active_support.rb +9 -0
  209. data/lib/farce/integrations/activesupport.rb +5 -0
  210. data/lib/farce/integrations/bson.rb +108 -0
  211. data/lib/farce/integrations/cbor.rb +51 -0
  212. data/lib/farce/integrations/concurrent.rb +112 -0
  213. data/lib/farce/integrations/dry-types.rb +5 -0
  214. data/lib/farce/integrations/dry_types.rb +694 -0
  215. data/lib/farce/integrations/json.rb +7 -0
  216. data/lib/farce/integrations/msgpack.rb +112 -0
  217. data/lib/farce/integrations/oj.rb +69 -0
  218. data/lib/farce/integrations/psych.rb +191 -0
  219. data/lib/farce/integrations/ractor-sharing.rb +5 -0
  220. data/lib/farce/integrations/ractor-tmvar.rb +5 -0
  221. data/lib/farce/integrations/ractor_sharing.rb +172 -0
  222. data/lib/farce/integrations/ractor_tmvar.rb +64 -0
  223. data/lib/farce/integrations/shared/to_json.rb +42 -0
  224. data/lib/farce/integrations/sorted_set.rb +11 -0
  225. data/lib/farce/integrations/weakref.rb +38 -0
  226. data/lib/farce/integrations/yajl.rb +10 -0
  227. data/lib/farce/integrations.rb +156 -0
  228. data/lib/farce/internal/_frozen_config.rb +11 -0
  229. data/lib/farce/internal/autoloads.rb +47 -0
  230. data/lib/farce/internal/blocking_priority_queue.rb +122 -0
  231. data/lib/farce/internal/converter.rb +106 -0
  232. data/lib/farce/internal/copyable.rb +31 -0
  233. data/lib/farce/internal/delegation.rb +20 -0
  234. data/lib/farce/internal/external_transaction.rb +59 -0
  235. data/lib/farce/internal/fake_ractor.rb +179 -0
  236. data/lib/farce/internal/freeze.rb +118 -0
  237. data/lib/farce/internal/inspect.rb +157 -0
  238. data/lib/farce/internal/key_lock_map.rb +29 -0
  239. data/lib/farce/internal/key_normalizer.rb +289 -0
  240. data/lib/farce/internal/lease_initialization.rb +115 -0
  241. data/lib/farce/internal/lease_map.rb +441 -0
  242. data/lib/farce/internal/lease_pool_state.rb +228 -0
  243. data/lib/farce/internal/lease_state.rb +342 -0
  244. data/lib/farce/internal/lease_waiting.rb +13 -0
  245. data/lib/farce/internal/managed_queue.rb +26 -0
  246. data/lib/farce/internal/map_value_modes.rb +227 -0
  247. data/lib/farce/internal/marshal_support.rb +227 -0
  248. data/lib/farce/internal/mixin.rb +20 -0
  249. data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
  250. data/lib/farce/internal/noncopyable.rb +17 -0
  251. data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
  252. data/lib/farce/internal/pool_supervisor.rb +41 -0
  253. data/lib/farce/internal/pool_worker.rb +61 -0
  254. data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
  255. data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
  256. data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
  257. data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
  258. data/lib/farce/internal/portable_transaction.rb +164 -0
  259. data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
  260. data/lib/farce/internal/reservation_waiting.rb +24 -0
  261. data/lib/farce/internal/scheduled_task.rb +48 -0
  262. data/lib/farce/internal/scheduler_io.rb +171 -0
  263. data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
  264. data/lib/farce/internal/select_scheduler.rb +235 -0
  265. data/lib/farce/internal/storage.rb +162 -0
  266. data/lib/farce/internal/strict_lease.rb +30 -0
  267. data/lib/farce/internal/strict_lease_map.rb +20 -0
  268. data/lib/farce/internal/strict_lease_pool.rb +29 -0
  269. data/lib/farce/internal/thread_pool.rb +166 -0
  270. data/lib/farce/internal/transaction_conflict.rb +9 -0
  271. data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
  272. data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
  273. data/lib/farce/internal/undefined.rb +22 -0
  274. data/lib/farce/internal/unshared_lease.rb +29 -0
  275. data/lib/farce/internal/unshared_lease_pool.rb +34 -0
  276. data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
  277. data/lib/farce/internal/value_serialization.rb +13 -0
  278. data/lib/farce/internal/weak_map_value_modes.rb +58 -0
  279. data/lib/farce/internal/weak_mode_manager.rb +26 -0
  280. data/lib/farce/internal.rb +129 -0
  281. data/lib/farce/lazy.rb +100 -0
  282. data/lib/farce/lazy_ref.rb +40 -0
  283. data/lib/farce/lease.rb +28 -0
  284. data/lib/farce/lease_map.rb +34 -0
  285. data/lib/farce/lease_pool.rb +29 -0
  286. data/lib/farce/lfu_map.rb +53 -0
  287. data/lib/farce/local/atom.rb +23 -0
  288. data/lib/farce/local/counter.rb +88 -0
  289. data/lib/farce/local/flag.rb +65 -0
  290. data/lib/farce/local/lazy.rb +38 -0
  291. data/lib/farce/local/lazy_ref.rb +36 -0
  292. data/lib/farce/local/lease.rb +77 -0
  293. data/lib/farce/local/lease_map.rb +80 -0
  294. data/lib/farce/local/lease_pool.rb +38 -0
  295. data/lib/farce/local/lfu_map.rb +55 -0
  296. data/lib/farce/local/lru_map.rb +62 -0
  297. data/lib/farce/local/map.rb +47 -0
  298. data/lib/farce/local/molecule.rb +45 -0
  299. data/lib/farce/local/priority_queue.rb +53 -0
  300. data/lib/farce/local/queue.rb +32 -0
  301. data/lib/farce/local/scoped.rb +166 -0
  302. data/lib/farce/local/set.rb +18 -0
  303. data/lib/farce/local/sorted_set.rb +50 -0
  304. data/lib/farce/local/timer_queue.rb +41 -0
  305. data/lib/farce/local/tree_map.rb +47 -0
  306. data/lib/farce/local/vector.rb +30 -0
  307. data/lib/farce/local/weak_atom.rb +23 -0
  308. data/lib/farce/local/weak_key_map.rb +39 -0
  309. data/lib/farce/local/weak_map.rb +39 -0
  310. data/lib/farce/local/weak_set.rb +18 -0
  311. data/lib/farce/local/weak_value_map.rb +39 -0
  312. data/lib/farce/local.rb +35 -0
  313. data/lib/farce/lock.rb +54 -0
  314. data/lib/farce/lru_map.rb +63 -0
  315. data/lib/farce/map.rb +58 -0
  316. data/lib/farce/mode_manager.rb +142 -0
  317. data/lib/farce/molecule.rb +60 -0
  318. data/lib/farce/mutable.rb +171 -0
  319. data/lib/farce/pool.rb +332 -0
  320. data/lib/farce/port.rb +134 -0
  321. data/lib/farce/priority_queue.rb +70 -0
  322. data/lib/farce/proxy/register.rb +101 -0
  323. data/lib/farce/proxy/supervisor.rb +137 -0
  324. data/lib/farce/proxy/wrapper.rb +49 -0
  325. data/lib/farce/proxy.rb +291 -0
  326. data/lib/farce/queue.rb +60 -0
  327. data/lib/farce/ractor.rb +269 -0
  328. data/lib/farce/read_write_lock.rb +256 -0
  329. data/lib/farce/reference.rb +158 -0
  330. data/lib/farce/resolv/dns.rb +42 -0
  331. data/lib/farce/resolv.rb +85 -0
  332. data/lib/farce/scheduler.rb +536 -0
  333. data/lib/farce/set.rb +39 -0
  334. data/lib/farce/shareable.rb +150 -0
  335. data/lib/farce/signal.rb +97 -0
  336. data/lib/farce/sorted_set.rb +36 -0
  337. data/lib/farce/strict/atom.rb +34 -0
  338. data/lib/farce/strict/counter.rb +10 -0
  339. data/lib/farce/strict/exchanger.rb +25 -0
  340. data/lib/farce/strict/flag.rb +10 -0
  341. data/lib/farce/strict/lazy.rb +26 -0
  342. data/lib/farce/strict/lazy_ref.rb +26 -0
  343. data/lib/farce/strict/lease.rb +27 -0
  344. data/lib/farce/strict/lease_map.rb +33 -0
  345. data/lib/farce/strict/lease_pool.rb +28 -0
  346. data/lib/farce/strict/lfu_map.rb +27 -0
  347. data/lib/farce/strict/lru_map.rb +28 -0
  348. data/lib/farce/strict/map.rb +54 -0
  349. data/lib/farce/strict/molecule.rb +16 -0
  350. data/lib/farce/strict/port.rb +40 -0
  351. data/lib/farce/strict/priority_queue.rb +20 -0
  352. data/lib/farce/strict/queue.rb +17 -0
  353. data/lib/farce/strict/set.rb +14 -0
  354. data/lib/farce/strict/sorted_set.rb +19 -0
  355. data/lib/farce/strict/timer_queue.rb +13 -0
  356. data/lib/farce/strict/tree_map.rb +27 -0
  357. data/lib/farce/strict/vector.rb +24 -0
  358. data/lib/farce/strict/weak_atom.rb +31 -0
  359. data/lib/farce/strict/weak_key_map.rb +49 -0
  360. data/lib/farce/strict/weak_map.rb +49 -0
  361. data/lib/farce/strict/weak_set.rb +14 -0
  362. data/lib/farce/strict/weak_value_map.rb +48 -0
  363. data/lib/farce/strict.rb +28 -0
  364. data/lib/farce/system.rb +40 -0
  365. data/lib/farce/thread_scheduler.rb +70 -0
  366. data/lib/farce/timer_queue.rb +66 -0
  367. data/lib/farce/transaction/atom.rb +73 -0
  368. data/lib/farce/transaction/map.rb +25 -0
  369. data/lib/farce/transaction/map_operations.rb +143 -0
  370. data/lib/farce/transaction/molecule.rb +90 -0
  371. data/lib/farce/transaction/mutable.rb +61 -0
  372. data/lib/farce/transaction/set.rb +17 -0
  373. data/lib/farce/transaction/set_operations.rb +48 -0
  374. data/lib/farce/transaction/sorted_set.rb +18 -0
  375. data/lib/farce/transaction/tree_map.rb +78 -0
  376. data/lib/farce/transaction/vector.rb +139 -0
  377. data/lib/farce/transaction/wrapper.rb +202 -0
  378. data/lib/farce/transaction.rb +343 -0
  379. data/lib/farce/tree_map.rb +56 -0
  380. data/lib/farce/unsafe/lfu_map.rb +36 -0
  381. data/lib/farce/unsafe/lru_map.rb +36 -0
  382. data/lib/farce/unsafe/tree_map.rb +21 -0
  383. data/lib/farce/unsafe.rb +29 -0
  384. data/lib/farce/unshareable.rb +67 -0
  385. data/lib/farce/unshared/atom.rb +24 -0
  386. data/lib/farce/unshared/counter.rb +10 -0
  387. data/lib/farce/unshared/flag.rb +10 -0
  388. data/lib/farce/unshared/lazy.rb +38 -0
  389. data/lib/farce/unshared/lazy_ref.rb +26 -0
  390. data/lib/farce/unshared/lease.rb +27 -0
  391. data/lib/farce/unshared/lease_map.rb +26 -0
  392. data/lib/farce/unshared/lease_pool.rb +25 -0
  393. data/lib/farce/unshared/lfu_map.rb +20 -0
  394. data/lib/farce/unshared/lru_map.rb +20 -0
  395. data/lib/farce/unshared/map.rb +49 -0
  396. data/lib/farce/unshared/molecule.rb +16 -0
  397. data/lib/farce/unshared/priority_queue.rb +16 -0
  398. data/lib/farce/unshared/queue.rb +27 -0
  399. data/lib/farce/unshared/set.rb +14 -0
  400. data/lib/farce/unshared/sorted_set.rb +28 -0
  401. data/lib/farce/unshared/timer_queue.rb +16 -0
  402. data/lib/farce/unshared/tree_map.rb +21 -0
  403. data/lib/farce/unshared/vector.rb +19 -0
  404. data/lib/farce/unshared/weak_atom.rb +31 -0
  405. data/lib/farce/unshared/weak_key_map.rb +44 -0
  406. data/lib/farce/unshared/weak_map.rb +44 -0
  407. data/lib/farce/unshared/weak_set.rb +14 -0
  408. data/lib/farce/unshared/weak_value_map.rb +43 -0
  409. data/lib/farce/unshared.rb +28 -0
  410. data/lib/farce/vector.rb +228 -0
  411. data/lib/farce/version.rb +8 -0
  412. data/lib/farce/walker/definitions.rb +186 -0
  413. data/lib/farce/walker/modification.rb +355 -0
  414. data/lib/farce/walker.rb +319 -0
  415. data/lib/farce/weak_atom.rb +121 -0
  416. data/lib/farce/weak_key_map.rb +31 -0
  417. data/lib/farce/weak_map.rb +33 -0
  418. data/lib/farce/weak_ref.rb +67 -0
  419. data/lib/farce/weak_set.rb +82 -0
  420. data/lib/farce/weak_value.rb +195 -0
  421. data/lib/farce/weak_value_map.rb +32 -0
  422. data/lib/farce.rb +519 -0
  423. metadata +468 -0
data/ext/farce/map.c ADDED
@@ -0,0 +1,2240 @@
1
+ #include "containers.h"
2
+ #include "transaction.h"
3
+ #include "ruby/fiber/scheduler.h"
4
+ #include "ruby/io.h"
5
+
6
+ #include <errno.h>
7
+ #include <fcntl.h>
8
+ #include <math.h>
9
+ #include <stdint.h>
10
+ #include <stdatomic.h>
11
+ #include <stdlib.h>
12
+ #include <time.h>
13
+ #include <unistd.h>
14
+
15
+ #define MAP_INITIAL_CAPACITY 16
16
+
17
+ static VALUE cMap;
18
+ static VALUE cUnsharedMap;
19
+ static VALUE map_keep;
20
+ static VALUE map_delete_token;
21
+ static VALUE cUnsharedKeyLockMap;
22
+ static VALUE cSharedKeyLockMap;
23
+ static VALUE cStrictMapFacade;
24
+ static VALUE cUnsharedMapFacade;
25
+ static VALUE cModeMapFacade;
26
+ static VALUE cMapEnvelope;
27
+ static VALUE map_nil_value;
28
+ static VALUE eIsolationError;
29
+ static const rb_data_type_t map_type;
30
+ static ID id_map;
31
+ static ID id_unwrap_value;
32
+ static ID id_wrap_value;
33
+
34
+
35
+ typedef enum { MAP_EMPTY = 0, MAP_OCCUPIED = 1, MAP_TOMBSTONE = 2 } map_slot_state_t;
36
+
37
+ typedef struct {
38
+ VALUE key;
39
+ VALUE value;
40
+ st_index_t hash;
41
+ map_slot_state_t state;
42
+ } map_slot_t;
43
+
44
+ typedef struct map_waiter {
45
+ int read_fd;
46
+ int write_fd;
47
+ bool notified;
48
+ struct map_waiter *next;
49
+ } map_waiter_t;
50
+
51
+ typedef struct map_reservation {
52
+ VALUE key;
53
+ st_index_t hash;
54
+ VALUE fiber;
55
+ VALUE thread;
56
+ bool invalidated;
57
+ struct map_reservation *next;
58
+ } map_reservation_t;
59
+
60
+ typedef struct {
61
+ pthread_mutex_t lock;
62
+ map_slot_t *slots;
63
+ size_t capacity;
64
+ size_t size;
65
+ size_t tombstones;
66
+ uint64_t generation;
67
+ uint64_t iteration_epoch;
68
+ map_waiter_t *waiters;
69
+ bool compare_keys_by_identity;
70
+ bool compare_values_by_identity;
71
+ bool shareable;
72
+ uint8_t comparing;
73
+ VALUE comparing_owner;
74
+ VALUE comparing_thread;
75
+ map_reservation_t *reservations;
76
+ atomic_size_t reservation_count;
77
+ bool initialized;
78
+ } map_t;
79
+
80
+ enum { KEY_LOCK_UNINITIALIZED, KEY_LOCK_INITIALIZING, KEY_LOCK_PUBLISHING, KEY_LOCK_INITIALIZED };
81
+
82
+ /* Keep the map core first so its GC, waiter and reservation helpers can be
83
+ * reused without changing the public Map layout or publication protocol. */
84
+ typedef struct {
85
+ map_t core;
86
+ atomic_int state;
87
+ } key_lock_map_t;
88
+
89
+ typedef struct {
90
+ bool finite;
91
+ double deadline;
92
+ } map_timeout_t;
93
+
94
+ typedef struct {
95
+ VALUE fiber;
96
+ VALUE thread;
97
+ VALUE scheduler;
98
+ } map_execution_context_t;
99
+
100
+ /* Ruby's current Fiber/Thread accessors may allocate. Capture their values
101
+ * before taking a native mutex so a Ractor GC barrier can never wait on a
102
+ * peer that is blocked on that mutex. */
103
+ static map_execution_context_t
104
+ map_current_execution_context(void)
105
+ {
106
+ map_execution_context_t context = {
107
+ .fiber = rb_fiber_current(),
108
+ .thread = rb_thread_current(),
109
+ .scheduler = rb_fiber_scheduler_current(),
110
+ };
111
+ return context;
112
+ }
113
+
114
+ static double
115
+ map_monotonic_now(void)
116
+ {
117
+ struct timespec now;
118
+ #ifdef CLOCK_MONOTONIC
119
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
120
+ return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
121
+ }
122
+ #endif
123
+ struct timeval fallback;
124
+ gettimeofday(&fallback, NULL);
125
+ return (double)fallback.tv_sec + (double)fallback.tv_usec / 1000000.0;
126
+ }
127
+
128
+ static map_timeout_t
129
+ map_parse_timeout(VALUE timeout)
130
+ {
131
+ map_timeout_t parsed = {.finite = false, .deadline = 0};
132
+ if (NIL_P(timeout)) return parsed;
133
+ double seconds = NUM2DBL(timeout);
134
+ if (!isfinite(seconds) || seconds < 0) {
135
+ rb_raise(rb_eArgError, "timeout must be a finite, non-negative number or nil");
136
+ }
137
+ parsed.finite = true;
138
+ parsed.deadline = map_monotonic_now() + seconds;
139
+ return parsed;
140
+ }
141
+
142
+ static void
143
+ map_set_fd_flags(int fd)
144
+ {
145
+ #ifdef _WIN32
146
+ (void)fd;
147
+ #else
148
+ int flags = fcntl(fd, F_GETFL, 0);
149
+ if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
150
+ flags = fcntl(fd, F_GETFD, 0);
151
+ if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
152
+ #endif
153
+ }
154
+
155
+ static void
156
+ map_waiter_initialize(map_waiter_t *waiter)
157
+ {
158
+ int descriptors[2];
159
+ if (pipe(descriptors) != 0) rb_sys_fail("pipe");
160
+ waiter->read_fd = descriptors[0];
161
+ waiter->write_fd = descriptors[1];
162
+ waiter->notified = false;
163
+ waiter->next = NULL;
164
+ map_set_fd_flags(waiter->read_fd);
165
+ map_set_fd_flags(waiter->write_fd);
166
+ }
167
+
168
+ static void
169
+ map_notify_waiters_locked(map_t *map)
170
+ {
171
+ unsigned char byte = 1;
172
+ map->generation++;
173
+ for (map_waiter_t *waiter = map->waiters; waiter; waiter = waiter->next) {
174
+ if (waiter->notified) continue;
175
+ ssize_t result;
176
+ do {
177
+ result = write(waiter->write_fd, &byte, 1);
178
+ } while (result < 0 && errno == EINTR);
179
+ if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
180
+ waiter->notified = true;
181
+ }
182
+ }
183
+ }
184
+
185
+ static void
186
+ map_mark(void *pointer)
187
+ {
188
+ map_t *map = pointer;
189
+ rb_gc_mark_movable(map->comparing_owner);
190
+ rb_gc_mark_movable(map->comparing_thread);
191
+ for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
192
+ rb_gc_mark_movable(reservation->key);
193
+ rb_gc_mark_movable(reservation->fiber);
194
+ rb_gc_mark_movable(reservation->thread);
195
+ }
196
+ if (!map->slots) return;
197
+ for (size_t index = 0; index < map->capacity; index++) {
198
+ if (map->slots[index].state == MAP_OCCUPIED) {
199
+ rb_gc_mark_movable(map->slots[index].key);
200
+ rb_gc_mark_movable(map->slots[index].value);
201
+ }
202
+ }
203
+ }
204
+
205
+ static void
206
+ map_compact(void *pointer)
207
+ {
208
+ map_t *map = pointer;
209
+ map->comparing_owner = rb_gc_location(map->comparing_owner);
210
+ map->comparing_thread = rb_gc_location(map->comparing_thread);
211
+ for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
212
+ reservation->key = rb_gc_location(reservation->key);
213
+ reservation->fiber = rb_gc_location(reservation->fiber);
214
+ reservation->thread = rb_gc_location(reservation->thread);
215
+ }
216
+ if (!map->slots) return;
217
+ for (size_t index = 0; index < map->capacity; index++) {
218
+ if (map->slots[index].state == MAP_OCCUPIED) {
219
+ map->slots[index].key = rb_gc_location(map->slots[index].key);
220
+ map->slots[index].value = rb_gc_location(map->slots[index].value);
221
+ }
222
+ }
223
+ }
224
+
225
+ static void
226
+ map_validate_references(VALUE self)
227
+ {
228
+ map_t *map;
229
+ TypedData_Get_Struct(self, map_t, &map_type, map);
230
+ for (size_t index = 0; index < map->capacity; index++) {
231
+ if (map->slots[index].state != MAP_OCCUPIED) continue;
232
+ containers_check_shareable(map->slots[index].key);
233
+ containers_check_shareable(map->slots[index].value);
234
+ }
235
+ }
236
+
237
+ static void
238
+ map_free(void *pointer)
239
+ {
240
+ map_t *map = pointer;
241
+ pthread_mutex_destroy(&map->lock);
242
+ free(map->slots);
243
+ while (map->reservations) {
244
+ map_reservation_t *reservation = map->reservations;
245
+ map->reservations = reservation->next;
246
+ free(reservation);
247
+ }
248
+ ruby_xfree(map);
249
+ }
250
+
251
+ static size_t
252
+ map_memsize(const void *pointer)
253
+ {
254
+ const map_t *map = pointer;
255
+ if (!map) return 0;
256
+ size_t reservations = atomic_load_explicit(&map->reservation_count, memory_order_relaxed);
257
+ return sizeof(map_t) + map->capacity * sizeof(map_slot_t) + reservations * sizeof(map_reservation_t);
258
+ }
259
+
260
+ static const rb_data_type_t map_type = {
261
+ .wrap_struct_name = "Ractor::Containers::Map",
262
+ .function = {
263
+ .dmark = map_mark,
264
+ .dfree = map_free,
265
+ .dsize = map_memsize,
266
+ .dcompact = map_compact,
267
+ },
268
+ .flags = RUBY_TYPED_FROZEN_SHAREABLE,
269
+ };
270
+
271
+ static const rb_data_type_t unshared_map_type = {
272
+ .wrap_struct_name = "Farce::Internal::UnsharedMap",
273
+ .function = {
274
+ .dmark = map_mark,
275
+ .dfree = map_free,
276
+ .dsize = map_memsize,
277
+ .dcompact = map_compact,
278
+ },
279
+ .parent = &map_type,
280
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
281
+ };
282
+
283
+ static size_t
284
+ key_lock_memsize(const void *pointer)
285
+ {
286
+ if (!pointer) return 0;
287
+ return map_memsize(pointer) + sizeof(key_lock_map_t) - sizeof(map_t);
288
+ }
289
+
290
+ /* Local reservations may retain arbitrary Ruby keys. They must never use the
291
+ * frozen-shareable Map type, including while a loader is running. */
292
+ static const rb_data_type_t unshared_key_lock_type = {
293
+ .wrap_struct_name = "Farce::Internal::UnsharedKeyLockMap",
294
+ .function = {
295
+ .dmark = map_mark,
296
+ .dfree = map_free,
297
+ .dsize = key_lock_memsize,
298
+ .dcompact = map_compact,
299
+ },
300
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
301
+ };
302
+
303
+ static void
304
+ shared_key_lock_mark(void *pointer)
305
+ {
306
+ /* Reservations are transient synchronization state, not part of the
307
+ * shareability graph. Their keys are validated before insertion, while
308
+ * their Fiber and Thread owners are intentionally unshareable. Excluding
309
+ * them from non-GC reachability also keeps concurrent Ractor shareability
310
+ * traversals from walking a reservation that another Ractor can remove. */
311
+ if (rb_during_gc()) map_mark(pointer);
312
+ }
313
+
314
+ static const rb_data_type_t shared_key_lock_type = {
315
+ .wrap_struct_name = "Farce::Internal::SharedKeyLockMap",
316
+ .function = {
317
+ .dmark = shared_key_lock_mark,
318
+ .dfree = map_free,
319
+ .dsize = key_lock_memsize,
320
+ .dcompact = map_compact,
321
+ },
322
+ .flags = RUBY_TYPED_FROZEN_SHAREABLE,
323
+ };
324
+
325
+ static map_slot_t *
326
+ allocate_slots(size_t capacity)
327
+ {
328
+ map_slot_t *slots = calloc(capacity, sizeof(map_slot_t));
329
+ if (!slots) rb_memerror();
330
+ return slots;
331
+ }
332
+
333
+ static void
334
+ map_initialize_storage(map_t *map)
335
+ {
336
+ pthread_mutex_init(&map->lock, NULL);
337
+ map->slots = NULL;
338
+ map->capacity = 0;
339
+ map->size = 0;
340
+ map->tombstones = 0;
341
+ map->generation = 0;
342
+ map->iteration_epoch = 0;
343
+ map->waiters = NULL;
344
+ map->compare_keys_by_identity = false;
345
+ map->compare_values_by_identity = false;
346
+ map->shareable = false;
347
+ map->comparing = false;
348
+ map->comparing_owner = Qnil;
349
+ map->comparing_thread = Qnil;
350
+ map->reservations = NULL;
351
+ atomic_init(&map->reservation_count, 0);
352
+ map->initialized = false;
353
+ }
354
+
355
+ static VALUE
356
+ map_allocate(VALUE klass)
357
+ {
358
+ map_t *map;
359
+ VALUE object = TypedData_Make_Struct(klass, map_t, &map_type, map);
360
+ map_initialize_storage(map);
361
+ map->shareable = true;
362
+ return object;
363
+ }
364
+
365
+ static VALUE
366
+ unshared_map_allocate(VALUE klass)
367
+ {
368
+ map_t *map;
369
+ VALUE object = TypedData_Make_Struct(klass, map_t, &unshared_map_type, map);
370
+ map_initialize_storage(map);
371
+ VALUE guard = rb_path2class("Farce::Internal::Unshareable");
372
+ rb_funcall(guard, rb_intern("pin_to_current_ractor"), 1, object);
373
+ return object;
374
+ }
375
+
376
+ static VALUE
377
+ key_lock_allocate(VALUE klass, const rb_data_type_t *type)
378
+ {
379
+ key_lock_map_t *map;
380
+ VALUE object = TypedData_Make_Struct(klass, key_lock_map_t, type, map);
381
+ map_initialize_storage(&map->core);
382
+ atomic_init(&map->state, KEY_LOCK_UNINITIALIZED);
383
+ return object;
384
+ }
385
+
386
+ static VALUE
387
+ shared_key_lock_allocate(VALUE klass)
388
+ {
389
+ return key_lock_allocate(klass, &shared_key_lock_type);
390
+ }
391
+
392
+ static VALUE
393
+ unshared_key_lock_allocate(VALUE klass)
394
+ {
395
+ VALUE self = key_lock_allocate(klass, &unshared_key_lock_type);
396
+ VALUE guard = rb_path2class("Farce::Internal::Unshareable");
397
+ rb_funcall(guard, rb_intern("pin_to_current_ractor"), 1, self);
398
+ return self;
399
+ }
400
+
401
+ static map_t *
402
+ get_map(VALUE self)
403
+ {
404
+ map_t *map;
405
+ TypedData_Get_Struct(self, map_t, &map_type, map);
406
+ if (!map->initialized) rb_raise(rb_eRuntimeError, "uninitialized Map");
407
+ return map;
408
+ }
409
+
410
+ typedef struct {
411
+ map_t *map;
412
+ map_waiter_t *waiter;
413
+ map_timeout_t *timeout;
414
+ bool registered;
415
+ } map_wait_context_t;
416
+
417
+ static VALUE
418
+ map_wait_body(VALUE opaque)
419
+ {
420
+ map_wait_context_t *context = (map_wait_context_t *)opaque;
421
+ VALUE wait_timeout = Qnil;
422
+ if (context->timeout->finite) {
423
+ double remaining = context->timeout->deadline - map_monotonic_now();
424
+ if (remaining <= 0) return Qfalse;
425
+ wait_timeout = DBL2NUM(remaining);
426
+ }
427
+ return containers_wait_for_readable(context->waiter->read_fd, wait_timeout) ? Qtrue : Qfalse;
428
+ }
429
+
430
+ static VALUE
431
+ map_wait_cleanup(VALUE opaque)
432
+ {
433
+ map_wait_context_t *context = (map_wait_context_t *)opaque;
434
+ if (context->registered) {
435
+ pthread_mutex_lock(&context->map->lock);
436
+ map_waiter_t **cursor = &context->map->waiters;
437
+ while (*cursor && *cursor != context->waiter) cursor = &(*cursor)->next;
438
+ if (*cursor) *cursor = context->waiter->next;
439
+ pthread_mutex_unlock(&context->map->lock);
440
+ context->registered = false;
441
+ }
442
+ close(context->waiter->read_fd);
443
+ close(context->waiter->write_fd);
444
+ return Qnil;
445
+ }
446
+
447
+ static bool
448
+ map_wait_once(map_t *map, uint64_t generation, map_timeout_t *timeout)
449
+ {
450
+ map_waiter_t waiter;
451
+ map_waiter_initialize(&waiter);
452
+
453
+ pthread_mutex_lock(&map->lock);
454
+ if (map->generation != generation) {
455
+ pthread_mutex_unlock(&map->lock);
456
+ close(waiter.read_fd);
457
+ close(waiter.write_fd);
458
+ return true;
459
+ }
460
+ waiter.next = map->waiters;
461
+ map->waiters = &waiter;
462
+ pthread_mutex_unlock(&map->lock);
463
+
464
+ map_wait_context_t context = {
465
+ .map = map,
466
+ .waiter = &waiter,
467
+ .timeout = timeout,
468
+ .registered = true,
469
+ };
470
+ return RTEST(rb_ensure(map_wait_body, (VALUE)&context, map_wait_cleanup, (VALUE)&context));
471
+ }
472
+
473
+ /* Acquire the short native state mutex, waiting outside the runtime lock if a
474
+ * key equality callback currently owns the map's logical critical section.
475
+ * The common path is one branch after pthread_mutex_lock. */
476
+ static void
477
+ map_lock_state(map_t *map, const map_execution_context_t *context)
478
+ {
479
+ map_timeout_t timeout = {.finite = false, .deadline = 0};
480
+
481
+ for (;;) {
482
+ pthread_mutex_lock(&map->lock);
483
+ if (!map->comparing) return;
484
+
485
+ if (map->comparing_owner == context->fiber) {
486
+ pthread_mutex_unlock(&map->lock);
487
+ rb_raise(rb_eThreadError, "recursive map access from key equality");
488
+ }
489
+ if (map->comparing_thread == context->thread && NIL_P(context->scheduler)) {
490
+ pthread_mutex_unlock(&map->lock);
491
+ rb_raise(
492
+ rb_eThreadError,
493
+ "deadlock; map key equality is owned by another unscheduled fiber"
494
+ );
495
+ }
496
+
497
+ uint64_t generation = map->generation;
498
+ pthread_mutex_unlock(&map->lock);
499
+ (void)map_wait_once(map, generation, &timeout);
500
+ }
501
+ }
502
+
503
+ static VALUE
504
+ map_timeout_result(void)
505
+ {
506
+ return rb_block_given_p() ? rb_yield_values(0) : Qnil;
507
+ }
508
+
509
+ static st_index_t
510
+ map_key_hash(map_t *map, VALUE key)
511
+ {
512
+ if (map->compare_keys_by_identity) {
513
+ return (st_index_t)NUM2ULL(rb_obj_id(key));
514
+ }
515
+ return (st_index_t)NUM2LONG(rb_hash(key));
516
+ }
517
+
518
+ typedef struct {
519
+ VALUE left;
520
+ VALUE right;
521
+ } map_eql_arguments_t;
522
+
523
+ static VALUE
524
+ map_eql_protected(VALUE opaque)
525
+ {
526
+ map_eql_arguments_t *arguments = (map_eql_arguments_t *)opaque;
527
+ return rb_eql(arguments->left, arguments->right) ? Qtrue : Qfalse;
528
+ }
529
+
530
+ /* Called with map->lock held and returns with it held unless equality raises.
531
+ * The logical comparison gate preserves the original operation-wide
532
+ * exclusion while rb_io_wait lets other Threads/Ractors/Fibers park without
533
+ * starving the Ruby callback. */
534
+ static bool
535
+ map_keys_equal(
536
+ map_t *map,
537
+ VALUE left,
538
+ VALUE right,
539
+ const map_execution_context_t *context
540
+ )
541
+ {
542
+ if (left == right) return true;
543
+ if (map->compare_keys_by_identity) return left == right;
544
+
545
+ map_eql_arguments_t arguments = {.left = left, .right = right};
546
+ int state = 0;
547
+
548
+ map->comparing = true;
549
+ map->comparing_owner = context->fiber;
550
+ map->comparing_thread = context->thread;
551
+ pthread_mutex_unlock(&map->lock);
552
+
553
+ VALUE result = rb_protect(map_eql_protected, (VALUE)&arguments, &state);
554
+
555
+ pthread_mutex_lock(&map->lock);
556
+ map->comparing = false;
557
+ map->comparing_owner = Qnil;
558
+ map->comparing_thread = Qnil;
559
+ map_notify_waiters_locked(map);
560
+ if (state) {
561
+ pthread_mutex_unlock(&map->lock);
562
+ rb_jump_tag(state);
563
+ }
564
+ return RTEST(result);
565
+ }
566
+
567
+ static size_t
568
+ map_find_slot(
569
+ map_t *map,
570
+ VALUE key,
571
+ st_index_t hash,
572
+ bool *found,
573
+ const map_execution_context_t *context
574
+ )
575
+ {
576
+ size_t mask = map->capacity - 1;
577
+ size_t first_tombstone = SIZE_MAX;
578
+
579
+ for (size_t offset = 0; offset < map->capacity; offset++) {
580
+ size_t index = ((size_t)hash + offset) & mask;
581
+ map_slot_t *slot = &map->slots[index];
582
+
583
+ if (slot->state == MAP_EMPTY) {
584
+ *found = false;
585
+ return first_tombstone == SIZE_MAX ? index : first_tombstone;
586
+ }
587
+ if (slot->state == MAP_TOMBSTONE) {
588
+ if (first_tombstone == SIZE_MAX) first_tombstone = index;
589
+ continue;
590
+ }
591
+ if (slot->hash == hash && map_keys_equal(map, slot->key, key, context)) {
592
+ *found = true;
593
+ return index;
594
+ }
595
+ }
596
+
597
+ *found = false;
598
+ return first_tombstone;
599
+ }
600
+
601
+ /* Resolve lookups that cannot call user equality code. This lets the common
602
+ * same-object hit avoid capturing Fiber and Thread state. Return false when
603
+ * an equal-hash key requires the full callback-safe comparison protocol. */
604
+ static bool
605
+ map_find_slot_without_equality(
606
+ map_t *map,
607
+ VALUE key,
608
+ st_index_t hash,
609
+ size_t *index,
610
+ bool *found
611
+ )
612
+ {
613
+ size_t mask = map->capacity - 1;
614
+ size_t first_tombstone = SIZE_MAX;
615
+
616
+ for (size_t offset = 0; offset < map->capacity; offset++) {
617
+ size_t candidate = ((size_t)hash + offset) & mask;
618
+ map_slot_t *slot = &map->slots[candidate];
619
+
620
+ if (slot->state == MAP_EMPTY) {
621
+ *index = first_tombstone == SIZE_MAX ? candidate : first_tombstone;
622
+ *found = false;
623
+ return true;
624
+ }
625
+ if (slot->state == MAP_TOMBSTONE) {
626
+ if (first_tombstone == SIZE_MAX) first_tombstone = candidate;
627
+ continue;
628
+ }
629
+ if (slot->hash != hash) continue;
630
+ if (slot->key == key) {
631
+ *index = candidate;
632
+ *found = true;
633
+ return true;
634
+ }
635
+ if (map->compare_keys_by_identity) continue;
636
+ /* CRuby's rb_eql uses built-in comparison for exact Strings. Keep
637
+ * subclasses and singleton classes on the callback-safe path. */
638
+ if (CLASS_OF(slot->key) == rb_cString && RB_TYPE_P(key, T_STRING)) {
639
+ if (RTEST(rb_str_equal(slot->key, key))) {
640
+ *index = candidate;
641
+ *found = true;
642
+ return true;
643
+ }
644
+ continue;
645
+ }
646
+ return false;
647
+ }
648
+
649
+ *index = first_tombstone;
650
+ *found = false;
651
+ return true;
652
+ }
653
+
654
+ /* Reservations use the same hash and equality rules as stored entries. They
655
+ * are kept separately so a missing key remains missing while user code runs. */
656
+ static map_reservation_t *
657
+ map_find_reservation_locked(
658
+ map_t *map,
659
+ VALUE key,
660
+ st_index_t hash,
661
+ const map_execution_context_t *context
662
+ )
663
+ {
664
+ for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
665
+ if (!reservation->invalidated && reservation->hash == hash &&
666
+ map_keys_equal(map, reservation->key, key, context)) {
667
+ return reservation;
668
+ }
669
+ }
670
+ return NULL;
671
+ }
672
+
673
+ static void
674
+ map_check_reservation_wait_locked(
675
+ map_t *map,
676
+ map_reservation_t *reservation,
677
+ const map_execution_context_t *context
678
+ )
679
+ {
680
+ if (reservation->fiber == context->fiber) {
681
+ pthread_mutex_unlock(&map->lock);
682
+ rb_raise(rb_eThreadError, "deadlock; recursive map access during an update");
683
+ }
684
+ if (reservation->thread == context->thread && NIL_P(context->scheduler)) {
685
+ pthread_mutex_unlock(&map->lock);
686
+ rb_raise(
687
+ rb_eThreadError,
688
+ "deadlock; map update is owned by another unscheduled fiber"
689
+ );
690
+ }
691
+ }
692
+
693
+ static bool
694
+ map_lock_for_key_with_timeout(
695
+ map_t *map,
696
+ VALUE key,
697
+ st_index_t hash,
698
+ map_timeout_t *timeout,
699
+ const map_execution_context_t *context
700
+ )
701
+ {
702
+ for (;;) {
703
+ map_lock_state(map, context);
704
+ map_reservation_t *reservation = map_find_reservation_locked(map, key, hash, context);
705
+ if (!reservation) return true;
706
+ map_check_reservation_wait_locked(map, reservation, context);
707
+ uint64_t generation = map->generation;
708
+ pthread_mutex_unlock(&map->lock);
709
+ if (!map_wait_once(map, generation, timeout)) return false;
710
+ }
711
+ }
712
+
713
+ static void
714
+ map_lock_for_key(
715
+ map_t *map,
716
+ VALUE key,
717
+ st_index_t hash,
718
+ const map_execution_context_t *context
719
+ )
720
+ {
721
+ map_timeout_t timeout = {.finite = false, .deadline = 0};
722
+ (void)map_lock_for_key_with_timeout(map, key, hash, &timeout, context);
723
+ }
724
+
725
+ static map_reservation_t *
726
+ map_begin_reservation_locked(
727
+ map_t *map,
728
+ VALUE key,
729
+ st_index_t hash,
730
+ const map_execution_context_t *context
731
+ )
732
+ {
733
+ map_reservation_t *reservation = malloc(sizeof(map_reservation_t));
734
+ if (!reservation) {
735
+ pthread_mutex_unlock(&map->lock);
736
+ rb_memerror();
737
+ }
738
+ reservation->key = key;
739
+ reservation->hash = hash;
740
+ reservation->fiber = context->fiber;
741
+ reservation->thread = context->thread;
742
+ reservation->invalidated = false;
743
+ reservation->next = map->reservations;
744
+ map->reservations = reservation;
745
+ atomic_fetch_add_explicit(&map->reservation_count, 1, memory_order_relaxed);
746
+ return reservation;
747
+ }
748
+
749
+ static void
750
+ map_finish_reservation_locked(map_t *map, map_reservation_t *reservation)
751
+ {
752
+ map_reservation_t **cursor = &map->reservations;
753
+ while (*cursor && *cursor != reservation) cursor = &(*cursor)->next;
754
+ if (*cursor) {
755
+ *cursor = reservation->next;
756
+ atomic_fetch_sub_explicit(&map->reservation_count, 1, memory_order_relaxed);
757
+ }
758
+ map_notify_waiters_locked(map);
759
+ free(reservation);
760
+ }
761
+
762
+ static void
763
+ map_invalidate_reservations_locked(map_t *map)
764
+ {
765
+ for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
766
+ reservation->invalidated = true;
767
+ }
768
+ }
769
+
770
+ static void
771
+ map_resize(map_t *map, size_t new_capacity)
772
+ {
773
+ map->iteration_epoch++;
774
+ map_slot_t *old_slots = map->slots;
775
+ size_t old_capacity = map->capacity;
776
+ map_slot_t *new_slots = calloc(new_capacity, sizeof(map_slot_t));
777
+ if (!new_slots) {
778
+ pthread_mutex_unlock(&map->lock);
779
+ rb_memerror();
780
+ }
781
+ map->slots = new_slots;
782
+ map->capacity = new_capacity;
783
+ map->size = 0;
784
+ map->tombstones = 0;
785
+
786
+ for (size_t index = 0; index < old_capacity; index++) {
787
+ map_slot_t *old = &old_slots[index];
788
+ if (old->state == MAP_OCCUPIED) {
789
+ size_t mask = map->capacity - 1;
790
+ size_t target = (size_t)old->hash & mask;
791
+ while (map->slots[target].state == MAP_OCCUPIED) {
792
+ target = (target + 1) & mask;
793
+ }
794
+ map->slots[target] = *old;
795
+ map->size++;
796
+ }
797
+ }
798
+ free(old_slots);
799
+ }
800
+
801
+ static void
802
+ map_prepare_insert(map_t *map)
803
+ {
804
+ if ((map->size + map->tombstones + 1) * 10 >= map->capacity * 7) {
805
+ map_resize(map, map->capacity * 2);
806
+ }
807
+ }
808
+
809
+ static VALUE
810
+ map_store_locked(
811
+ VALUE self,
812
+ map_t *map,
813
+ VALUE key,
814
+ VALUE value,
815
+ st_index_t hash,
816
+ const map_execution_context_t *context
817
+ )
818
+ {
819
+ bool found;
820
+ map_prepare_insert(map);
821
+ size_t index = map_find_slot(map, key, hash, &found, context);
822
+ if (RB_OBJ_FROZEN(self)) {
823
+ pthread_mutex_unlock(&map->lock);
824
+ rb_check_frozen(self);
825
+ }
826
+ map_slot_t *slot = &map->slots[index];
827
+ if (!found) {
828
+ if (slot->state == MAP_TOMBSTONE) map->tombstones--;
829
+ slot->key = key;
830
+ slot->hash = hash;
831
+ slot->state = MAP_OCCUPIED;
832
+ map->size++;
833
+ map->iteration_epoch++;
834
+ }
835
+ slot->value = value;
836
+ map_notify_waiters_locked(map);
837
+ return value;
838
+ }
839
+
840
+ static VALUE
841
+ map_normalize_key(map_t *map, VALUE key)
842
+ {
843
+ if (!map->compare_keys_by_identity && RB_TYPE_P(key, T_STRING) && !RB_OBJ_FROZEN(key)) {
844
+ key = containers_normalize_string_key(key);
845
+ }
846
+ return key;
847
+ }
848
+
849
+ static VALUE
850
+ map_prepare_key(map_t *map, VALUE key)
851
+ {
852
+ key = map_normalize_key(map, key);
853
+ if (map->shareable) containers_check_shareable(key);
854
+ return key;
855
+ }
856
+
857
+ static void
858
+ map_check_value(map_t *map, VALUE value)
859
+ {
860
+ if (map->shareable) containers_check_shareable(value);
861
+ }
862
+
863
+ typedef struct {
864
+ VALUE self;
865
+ map_t *map;
866
+ map_execution_context_t execution;
867
+ } map_init_context_t;
868
+
869
+ static int
870
+ map_initialize_entry(VALUE key, VALUE value, VALUE opaque)
871
+ {
872
+ map_init_context_t *context = (map_init_context_t *)opaque;
873
+ key = map_prepare_key(context->map, key);
874
+ map_check_value(context->map, value);
875
+ st_index_t hash = map_key_hash(context->map, key);
876
+ map_lock_state(context->map, &context->execution);
877
+ map_store_locked(context->self, context->map, key, value, hash, &context->execution);
878
+ pthread_mutex_unlock(&context->map->lock);
879
+ return ST_CONTINUE;
880
+ }
881
+
882
+ static VALUE
883
+ map_initialize(int argc, VALUE *argv, VALUE self)
884
+ {
885
+ VALUE mapping = Qnil;
886
+ VALUE keywords = Qnil;
887
+ ID keyword_ids[] = {
888
+ rb_intern("compare_by_identity"),
889
+ rb_intern("compare_keys_by_identity"),
890
+ rb_intern("compare_values_by_identity")
891
+ };
892
+ VALUE keyword_values[3] = {Qundef, Qundef, Qundef};
893
+ map_t *map;
894
+
895
+ rb_scan_args(argc, argv, "01:", &mapping, &keywords);
896
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 3, keyword_values);
897
+ TypedData_Get_Struct(self, map_t, &map_type, map);
898
+ if (map->initialized) rb_raise(rb_eRuntimeError, "Map is already initialized");
899
+
900
+ bool common = keyword_values[0] == Qundef
901
+ ? false
902
+ : containers_strict_bool(keyword_values[0], "compare_by_identity");
903
+ map->compare_keys_by_identity = keyword_values[1] == Qundef
904
+ ? common
905
+ : containers_strict_bool(keyword_values[1], "compare_keys_by_identity");
906
+ map->compare_values_by_identity = keyword_values[2] == Qundef
907
+ ? common
908
+ : containers_strict_bool(keyword_values[2], "compare_values_by_identity");
909
+ map->capacity = MAP_INITIAL_CAPACITY;
910
+ map->slots = allocate_slots(map->capacity);
911
+
912
+ if (!NIL_P(mapping)) {
913
+ Check_Type(mapping, T_HASH);
914
+ map_init_context_t context = {
915
+ .self = self,
916
+ .map = map,
917
+ .execution = map_current_execution_context(),
918
+ };
919
+ rb_hash_foreach(mapping, map_initialize_entry, (VALUE)&context);
920
+ }
921
+ map->initialized = true;
922
+ if (map->shareable) containers_publish_native_with_references(self, map_validate_references);
923
+ return self;
924
+ }
925
+
926
+ static VALUE
927
+ map_get(VALUE self, VALUE key)
928
+ {
929
+ map_t *map = get_map(self);
930
+ key = map_prepare_key(map, key);
931
+ st_index_t hash = map_key_hash(map, key);
932
+ bool found;
933
+ VALUE result = Qnil;
934
+ size_t index;
935
+
936
+ pthread_mutex_lock(&map->lock);
937
+ if (!map->comparing && map_find_slot_without_equality(map, key, hash, &index, &found)) {
938
+ if (found) result = map->slots[index].value;
939
+ pthread_mutex_unlock(&map->lock);
940
+ return result;
941
+ }
942
+ pthread_mutex_unlock(&map->lock);
943
+
944
+ map_execution_context_t execution = map_current_execution_context();
945
+ map_lock_state(map, &execution);
946
+ index = map_find_slot(map, key, hash, &found, &execution);
947
+ if (found) result = map->slots[index].value;
948
+ pthread_mutex_unlock(&map->lock);
949
+ return result;
950
+ }
951
+
952
+ static VALUE
953
+ map_check_mutation(VALUE self)
954
+ {
955
+ rb_check_frozen(self);
956
+ return self;
957
+ }
958
+
959
+ static VALUE
960
+ map_prepare_mutation_key(VALUE self, VALUE key)
961
+ {
962
+ rb_check_frozen(self);
963
+ map_t *map = get_map(self);
964
+ key = map_normalize_key(map, key);
965
+ if (map->shareable && !rb_ractor_shareable_p(key)) {
966
+ rb_raise(eIsolationError, "key must be Ractor-shareable");
967
+ }
968
+ return key;
969
+ }
970
+
971
+ static VALUE
972
+ map_fetch(int argc, VALUE *argv, VALUE self)
973
+ {
974
+ VALUE key;
975
+ VALUE default_value;
976
+ rb_scan_args(argc, argv, "11", &key, &default_value);
977
+
978
+ bool default_given = argc == 2;
979
+ bool block_given = rb_block_given_p();
980
+ if (block_given && default_given) {
981
+ rb_warn("block supersedes default value argument");
982
+ }
983
+
984
+ VALUE original_key = key;
985
+ map_t *map = get_map(self);
986
+ key = map_prepare_key(map, key);
987
+ st_index_t hash = map_key_hash(map, key);
988
+ bool found;
989
+ VALUE result = Qnil;
990
+ map_execution_context_t execution = map_current_execution_context();
991
+ map_lock_state(map, &execution);
992
+ size_t index = map_find_slot(map, key, hash, &found, &execution);
993
+ if (found) result = map->slots[index].value;
994
+ pthread_mutex_unlock(&map->lock);
995
+
996
+ if (found) return result;
997
+ if (block_given) return rb_yield(original_key);
998
+ if (default_given) return default_value;
999
+ containers_raise_key_error(self, original_key);
1000
+ return Qnil;
1001
+ }
1002
+
1003
+ static VALUE
1004
+ map_set(VALUE self, VALUE key, VALUE value)
1005
+ {
1006
+ rb_check_frozen(self);
1007
+ map_t *map = get_map(self);
1008
+ key = map_prepare_key(map, key);
1009
+ map_check_value(map, value);
1010
+ st_index_t hash = map_key_hash(map, key);
1011
+ rb_check_frozen(self);
1012
+
1013
+ /* Replacing an existing entry needs neither allocation nor execution
1014
+ * context when no callback or atomic update owns the map. Keep the
1015
+ * callback/reservation path below for all other cases. */
1016
+ bool found;
1017
+ size_t index;
1018
+ pthread_mutex_lock(&map->lock);
1019
+ if (!map->comparing && !map->reservations &&
1020
+ map_find_slot_without_equality(map, key, hash, &index, &found) && found) {
1021
+ if (RB_OBJ_FROZEN(self)) {
1022
+ pthread_mutex_unlock(&map->lock);
1023
+ rb_check_frozen(self);
1024
+ }
1025
+ map->slots[index].value = value;
1026
+ map_notify_waiters_locked(map);
1027
+ pthread_mutex_unlock(&map->lock);
1028
+ return value;
1029
+ }
1030
+ pthread_mutex_unlock(&map->lock);
1031
+
1032
+ map_execution_context_t execution = map_current_execution_context();
1033
+ map_lock_for_key(map, key, hash, &execution);
1034
+ map_store_locked(self, map, key, value, hash, &execution);
1035
+ pthread_mutex_unlock(&map->lock);
1036
+ return value;
1037
+ }
1038
+
1039
+ static bool
1040
+ map_is_backend(VALUE backend, const rb_data_type_t *type)
1041
+ {
1042
+ return RB_TYPE_P(backend, T_DATA) && RTYPEDDATA_P(backend) &&
1043
+ RTYPEDDATA_TYPE(backend) == type;
1044
+ }
1045
+
1046
+ /* The public direct maps retain Ruby fallbacks for subclasses and normalized
1047
+ * adapters. Ordinary access reaches the native backend without Ruby forwarding. */
1048
+ static const rb_data_type_t *
1049
+ map_facade_type(VALUE self)
1050
+ {
1051
+ VALUE klass = rb_obj_class(self);
1052
+ if (klass == cStrictMapFacade) return &map_type;
1053
+ if (klass == cUnsharedMapFacade) return &unshared_map_type;
1054
+ return NULL;
1055
+ }
1056
+
1057
+ static VALUE
1058
+ plain_map_get(VALUE self, VALUE key)
1059
+ {
1060
+ const rb_data_type_t *type = map_facade_type(self);
1061
+ if (!type) return rb_call_super(1, &key);
1062
+ VALUE backend = rb_ivar_get(self, id_map);
1063
+ if (map_is_backend(backend, type)) return map_get(backend, key);
1064
+ return rb_funcall(backend, rb_intern("[]"), 1, key);
1065
+ }
1066
+
1067
+ static VALUE
1068
+ plain_map_set(VALUE self, VALUE key, VALUE value)
1069
+ {
1070
+ const rb_data_type_t *type = map_facade_type(self);
1071
+ if (!type) {
1072
+ VALUE arguments[] = {key, value};
1073
+ return rb_call_super(2, arguments);
1074
+ }
1075
+ VALUE backend = rb_ivar_get(self, id_map);
1076
+ if (map_is_backend(backend, type)) return map_set(backend, key, value);
1077
+ return rb_funcall(backend, rb_intern("[]="), 2, key, value);
1078
+ }
1079
+
1080
+ /* Values already shareable need no mode conversion. Envelopes, normalized
1081
+ * backends, and subclasses retain the Ruby hooks that implement their policy. */
1082
+ static VALUE
1083
+ mode_map_get(VALUE self, VALUE key)
1084
+ {
1085
+ if (rb_obj_class(self) != cModeMapFacade) return rb_call_super(1, &key);
1086
+ VALUE backend = rb_ivar_get(self, id_map);
1087
+ if (!map_is_backend(backend, &map_type)) return rb_call_super(1, &key);
1088
+ VALUE result = map_get(backend, key);
1089
+ if (result == map_nil_value) return Qnil;
1090
+ if (rb_obj_is_kind_of(result, cMapEnvelope)) {
1091
+ return rb_funcall(self, id_unwrap_value, 1, result);
1092
+ }
1093
+ return result;
1094
+ }
1095
+
1096
+ static VALUE
1097
+ mode_map_set(VALUE self, VALUE key, VALUE value)
1098
+ {
1099
+ VALUE backend = rb_ivar_get(self, id_map);
1100
+ if (rb_obj_class(self) != cModeMapFacade || !map_is_backend(backend, &map_type)) {
1101
+ VALUE arguments[] = {key, value};
1102
+ return rb_call_super(2, arguments);
1103
+ }
1104
+ /* Preserve the key-specific error and validate before any value transfer. */
1105
+ key = map_prepare_mutation_key(backend, key);
1106
+ /* These are the positive fast checks from rb_ractor_shareable_p. A missing
1107
+ * flag means unknown, so let wrap_value classify and transfer it once.
1108
+ * Preclassifying here would traverse partly frozen graphs twice. */
1109
+ VALUE stored = RB_SPECIAL_CONST_P(value) || RB_OBJ_SHAREABLE_P(value)
1110
+ ? (NIL_P(value) ? map_nil_value : value)
1111
+ : rb_funcall(self, id_wrap_value, 1, value);
1112
+ map_set(backend, key, stored);
1113
+ return value;
1114
+ }
1115
+
1116
+ static VALUE
1117
+ map_prepare_access(VALUE namespace, VALUE klass, VALUE kind)
1118
+ {
1119
+ (void)namespace;
1120
+ Check_Type(klass, T_CLASS);
1121
+ Check_Type(kind, T_SYMBOL);
1122
+ ID policy_id = SYM2ID(kind);
1123
+ if (policy_id == rb_intern("modes")) {
1124
+ cModeMapFacade = klass;
1125
+ cMapEnvelope = rb_path2class("Farce::Envelope");
1126
+ VALUE policy = rb_path2class("Farce::Internal::MapValueModes");
1127
+ map_nil_value = rb_const_get(policy, rb_intern("NIL_VALUE"));
1128
+ rb_define_method(klass, "[]", mode_map_get, 1);
1129
+ rb_define_method(klass, "[]=", mode_map_set, 2);
1130
+ }
1131
+ else {
1132
+ if (policy_id == rb_intern("strict")) cStrictMapFacade = klass;
1133
+ else if (policy_id == rb_intern("unshared")) cUnsharedMapFacade = klass;
1134
+ else rb_raise(rb_eArgError, "unknown native map policy");
1135
+ rb_define_method(klass, "[]", plain_map_get, 1);
1136
+ rb_define_method(klass, "[]=", plain_map_set, 2);
1137
+ }
1138
+ return klass;
1139
+ }
1140
+
1141
+ static VALUE
1142
+ map_key_p(VALUE self, VALUE key)
1143
+ {
1144
+ map_t *map = get_map(self);
1145
+ key = map_prepare_key(map, key);
1146
+ st_index_t hash = map_key_hash(map, key);
1147
+ bool found;
1148
+ map_execution_context_t execution = map_current_execution_context();
1149
+ map_lock_state(map, &execution);
1150
+ (void)map_find_slot(map, key, hash, &found, &execution);
1151
+ pthread_mutex_unlock(&map->lock);
1152
+ return found ? Qtrue : Qfalse;
1153
+ }
1154
+
1155
+ static VALUE
1156
+ map_getkey(VALUE self, VALUE key)
1157
+ {
1158
+ map_t *map = get_map(self);
1159
+ key = map_prepare_key(map, key);
1160
+ st_index_t hash = map_key_hash(map, key);
1161
+ bool found;
1162
+ VALUE result = Qnil;
1163
+ map_execution_context_t execution = map_current_execution_context();
1164
+ map_lock_state(map, &execution);
1165
+ size_t index = map_find_slot(map, key, hash, &found, &execution);
1166
+ if (found) result = map->compare_keys_by_identity ? key : map->slots[index].key;
1167
+ pthread_mutex_unlock(&map->lock);
1168
+ return result;
1169
+ }
1170
+
1171
+ static VALUE
1172
+ map_delete(VALUE self, VALUE key)
1173
+ {
1174
+ rb_check_frozen(self);
1175
+ map_t *map = get_map(self);
1176
+ key = map_prepare_key(map, key);
1177
+ st_index_t hash = map_key_hash(map, key);
1178
+ rb_check_frozen(self);
1179
+ bool found;
1180
+ VALUE result = Qnil;
1181
+ map_execution_context_t execution = map_current_execution_context();
1182
+ map_lock_for_key(map, key, hash, &execution);
1183
+ size_t index = map_find_slot(map, key, hash, &found, &execution);
1184
+ if (RB_OBJ_FROZEN(self)) {
1185
+ pthread_mutex_unlock(&map->lock);
1186
+ rb_check_frozen(self);
1187
+ }
1188
+ if (found) {
1189
+ map_slot_t *slot = &map->slots[index];
1190
+ result = slot->value;
1191
+ slot->key = Qnil;
1192
+ slot->value = Qnil;
1193
+ slot->state = MAP_TOMBSTONE;
1194
+ map->size--;
1195
+ map->tombstones++;
1196
+ map_notify_waiters_locked(map);
1197
+ }
1198
+ pthread_mutex_unlock(&map->lock);
1199
+ return result;
1200
+ }
1201
+
1202
+ static VALUE
1203
+ map_clear(VALUE self)
1204
+ {
1205
+ rb_check_frozen(self);
1206
+ map_t *map = get_map(self);
1207
+ map_execution_context_t execution = map_current_execution_context();
1208
+ map_lock_state(map, &execution);
1209
+ map_invalidate_reservations_locked(map);
1210
+ for (size_t index = 0; index < map->capacity; index++) {
1211
+ map_slot_t *slot = &map->slots[index];
1212
+ slot->key = Qnil;
1213
+ slot->value = Qnil;
1214
+ slot->hash = 0;
1215
+ slot->state = MAP_EMPTY;
1216
+ }
1217
+ map->iteration_epoch++;
1218
+ map->size = 0;
1219
+ map->tombstones = 0;
1220
+ map_notify_waiters_locked(map);
1221
+ pthread_mutex_unlock(&map->lock);
1222
+ return self;
1223
+ }
1224
+
1225
+ typedef struct {
1226
+ VALUE self;
1227
+ map_t *map;
1228
+ map_execution_context_t execution;
1229
+ VALUE key;
1230
+ VALUE current;
1231
+ VALUE argument;
1232
+ VALUE replacement;
1233
+ st_index_t hash;
1234
+ map_reservation_t *reservation;
1235
+ bool identity;
1236
+ bool complete;
1237
+ bool present;
1238
+ } map_operation_t;
1239
+
1240
+ static VALUE
1241
+ map_operation_cleanup(VALUE opaque)
1242
+ {
1243
+ map_operation_t *operation = (map_operation_t *)opaque;
1244
+ if (!operation->complete) {
1245
+ map_lock_state(operation->map, &operation->execution);
1246
+ map_finish_reservation_locked(operation->map, operation->reservation);
1247
+ pthread_mutex_unlock(&operation->map->lock);
1248
+ }
1249
+ return Qnil;
1250
+ }
1251
+
1252
+ static VALUE
1253
+ map_store_body(VALUE opaque)
1254
+ {
1255
+ map_operation_t *operation = (map_operation_t *)opaque;
1256
+ VALUE result = rb_yield_values(0);
1257
+ map_check_value(operation->map, result);
1258
+ rb_check_frozen(operation->self);
1259
+ map_lock_state(operation->map, &operation->execution);
1260
+ bool stored = !operation->reservation->invalidated;
1261
+ if (stored) {
1262
+ map_store_locked(
1263
+ operation->self,
1264
+ operation->map,
1265
+ operation->key,
1266
+ result,
1267
+ operation->hash,
1268
+ &operation->execution
1269
+ );
1270
+ }
1271
+ operation->complete = true;
1272
+ map_finish_reservation_locked(operation->map, operation->reservation);
1273
+ pthread_mutex_unlock(&operation->map->lock);
1274
+ return stored ? result : Qnil;
1275
+ }
1276
+
1277
+ static VALUE
1278
+ map_store_if_absent(int argc, VALUE *argv, VALUE self)
1279
+ {
1280
+ rb_check_frozen(self);
1281
+ VALUE key;
1282
+ VALUE keywords = Qnil;
1283
+ VALUE keyword_values[1] = {Qundef};
1284
+ ID keyword_ids[] = {rb_intern("timeout")};
1285
+ rb_scan_args(argc, argv, "1:", &key, &keywords);
1286
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1287
+
1288
+ map_t *map = get_map(self);
1289
+ key = map_prepare_key(map, key);
1290
+ map_operation_t operation = {
1291
+ .self = self,
1292
+ .map = map,
1293
+ .execution = map_current_execution_context(),
1294
+ .key = key,
1295
+ .complete = false,
1296
+ };
1297
+ rb_need_block();
1298
+ operation.hash = map_key_hash(map, key);
1299
+ rb_check_frozen(self);
1300
+ map_timeout_t timeout = map_parse_timeout(
1301
+ keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1302
+ );
1303
+
1304
+ if (!map_lock_for_key_with_timeout(
1305
+ map, key, operation.hash, &timeout, &operation.execution
1306
+ )) return Qnil;
1307
+ if (RB_OBJ_FROZEN(self)) {
1308
+ pthread_mutex_unlock(&map->lock);
1309
+ rb_check_frozen(self);
1310
+ }
1311
+ bool found;
1312
+ size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
1313
+ if (RB_OBJ_FROZEN(self)) {
1314
+ pthread_mutex_unlock(&map->lock);
1315
+ rb_check_frozen(self);
1316
+ }
1317
+ if (found) {
1318
+ VALUE value = map->slots[index].value;
1319
+ pthread_mutex_unlock(&map->lock);
1320
+ return value;
1321
+ }
1322
+ operation.reservation = map_begin_reservation_locked(
1323
+ map, key, operation.hash, &operation.execution
1324
+ );
1325
+ pthread_mutex_unlock(&map->lock);
1326
+ return rb_ensure(map_store_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
1327
+ }
1328
+
1329
+ static VALUE
1330
+ map_cas_body(VALUE opaque)
1331
+ {
1332
+ map_operation_t *operation = (map_operation_t *)opaque;
1333
+ bool matches = operation->identity
1334
+ ? operation->current == operation->argument
1335
+ : RTEST(rb_equal(operation->current, operation->argument));
1336
+ rb_check_frozen(operation->self);
1337
+
1338
+ map_lock_state(operation->map, &operation->execution);
1339
+ bool replaced = matches && !operation->reservation->invalidated;
1340
+ if (replaced) {
1341
+ map_store_locked(
1342
+ operation->self,
1343
+ operation->map,
1344
+ operation->key,
1345
+ operation->replacement,
1346
+ operation->hash,
1347
+ &operation->execution
1348
+ );
1349
+ }
1350
+ operation->complete = true;
1351
+ map_finish_reservation_locked(operation->map, operation->reservation);
1352
+ pthread_mutex_unlock(&operation->map->lock);
1353
+ return replaced ? Qtrue : Qfalse;
1354
+ }
1355
+
1356
+ static VALUE
1357
+ map_compare_and_set(int argc, VALUE *argv, VALUE self)
1358
+ {
1359
+ rb_check_frozen(self);
1360
+ VALUE key;
1361
+ VALUE expected;
1362
+ VALUE replacement;
1363
+ VALUE keywords = Qnil;
1364
+ VALUE keyword_values[1] = {Qundef};
1365
+ ID keyword_ids[] = {rb_intern("timeout")};
1366
+ rb_scan_args(argc, argv, "3:", &key, &expected, &replacement, &keywords);
1367
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1368
+
1369
+ map_t *map = get_map(self);
1370
+ key = map_prepare_key(map, key);
1371
+ map_operation_t operation = {
1372
+ .self = self,
1373
+ .map = map,
1374
+ .execution = map_current_execution_context(),
1375
+ .key = key,
1376
+ .argument = expected,
1377
+ .replacement = replacement,
1378
+ .identity = map->compare_values_by_identity,
1379
+ .complete = false,
1380
+ };
1381
+ map_check_value(map, expected);
1382
+ map_check_value(map, replacement);
1383
+ operation.hash = map_key_hash(map, key);
1384
+ rb_check_frozen(self);
1385
+ map_timeout_t timeout = map_parse_timeout(
1386
+ keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1387
+ );
1388
+
1389
+ if (!map_lock_for_key_with_timeout(
1390
+ map, key, operation.hash, &timeout, &operation.execution
1391
+ )) return Qfalse;
1392
+ bool found;
1393
+ size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
1394
+ if (RB_OBJ_FROZEN(self)) {
1395
+ pthread_mutex_unlock(&map->lock);
1396
+ rb_check_frozen(self);
1397
+ }
1398
+ if (!found) {
1399
+ pthread_mutex_unlock(&map->lock);
1400
+ return Qfalse;
1401
+ }
1402
+ operation.current = map->slots[index].value;
1403
+ operation.reservation = map_begin_reservation_locked(
1404
+ map, key, operation.hash, &operation.execution
1405
+ );
1406
+ pthread_mutex_unlock(&map->lock);
1407
+ return rb_ensure(map_cas_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
1408
+ }
1409
+
1410
+ /* Decisions are interpreted before value validation. KEEP never writes storage. */
1411
+ static VALUE
1412
+ map_modify_body(VALUE opaque)
1413
+ {
1414
+ map_operation_t *operation = (map_operation_t *)opaque;
1415
+ VALUE value = rb_yield_values(2, operation->present ? Qtrue : Qfalse, operation->current);
1416
+ bool remove = value == map_delete_token;
1417
+ bool store = !remove && value != map_keep;
1418
+ if (store) map_check_value(operation->map, value);
1419
+ rb_check_frozen(operation->self);
1420
+ map_lock_state(operation->map, &operation->execution);
1421
+ bool changed = false;
1422
+ if (store && !operation->reservation->invalidated) {
1423
+ map_store_locked(operation->self, operation->map, operation->key, value,
1424
+ operation->hash, &operation->execution);
1425
+ changed = true;
1426
+ } else if (remove && !operation->reservation->invalidated) {
1427
+ bool found;
1428
+ size_t index = map_find_slot(operation->map, operation->key,
1429
+ operation->hash, &found, &operation->execution);
1430
+ if (RB_OBJ_FROZEN(operation->self)) {
1431
+ pthread_mutex_unlock(&operation->map->lock);
1432
+ rb_check_frozen(operation->self);
1433
+ }
1434
+ if (found) {
1435
+ map_slot_t *slot = &operation->map->slots[index];
1436
+ slot->key = Qnil;
1437
+ slot->value = Qnil;
1438
+ slot->state = MAP_TOMBSTONE;
1439
+ operation->map->size--;
1440
+ operation->map->tombstones++;
1441
+ map_notify_waiters_locked(operation->map);
1442
+ changed = true;
1443
+ }
1444
+ }
1445
+ operation->complete = true;
1446
+ map_finish_reservation_locked(operation->map, operation->reservation);
1447
+ pthread_mutex_unlock(&operation->map->lock);
1448
+ return changed ? Qtrue : Qfalse;
1449
+ }
1450
+
1451
+ static VALUE
1452
+ map_modify(VALUE self, VALUE key)
1453
+ {
1454
+ rb_check_frozen(self);
1455
+ rb_need_block();
1456
+ map_t *map = get_map(self);
1457
+ key = map_prepare_key(map, key);
1458
+ map_operation_t operation = {
1459
+ .self = self,
1460
+ .execution = map_current_execution_context(),
1461
+ .map = map,
1462
+ .key = key,
1463
+ .complete = false,
1464
+ };
1465
+ operation.hash = map_key_hash(map, key);
1466
+ rb_check_frozen(self);
1467
+ map_lock_for_key(map, key, operation.hash, &operation.execution);
1468
+ bool found;
1469
+ size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
1470
+ if (RB_OBJ_FROZEN(self)) {
1471
+ pthread_mutex_unlock(&map->lock);
1472
+ rb_check_frozen(self);
1473
+ }
1474
+ operation.present = found;
1475
+ operation.current = found ? map->slots[index].value : Qnil;
1476
+ operation.reservation = map_begin_reservation_locked(map, key, operation.hash, &operation.execution);
1477
+ pthread_mutex_unlock(&map->lock);
1478
+ return rb_ensure(map_modify_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
1479
+ }
1480
+
1481
+ static VALUE
1482
+ map_upsert_body(VALUE opaque)
1483
+ {
1484
+ map_operation_t *operation = (map_operation_t *)opaque;
1485
+ VALUE result = rb_yield(operation->current);
1486
+ map_check_value(operation->map, result);
1487
+ rb_check_frozen(operation->self);
1488
+ map_lock_state(operation->map, &operation->execution);
1489
+ bool stored = !operation->reservation->invalidated;
1490
+ if (stored) {
1491
+ map_store_locked(
1492
+ operation->self,
1493
+ operation->map,
1494
+ operation->key,
1495
+ result,
1496
+ operation->hash,
1497
+ &operation->execution
1498
+ );
1499
+ }
1500
+ operation->complete = true;
1501
+ map_finish_reservation_locked(operation->map, operation->reservation);
1502
+ pthread_mutex_unlock(&operation->map->lock);
1503
+ return stored ? result : Qnil;
1504
+ }
1505
+
1506
+ static VALUE
1507
+ map_upsert(int argc, VALUE *argv, VALUE self)
1508
+ {
1509
+ rb_check_frozen(self);
1510
+ VALUE key;
1511
+ VALUE initial;
1512
+ VALUE keywords = Qnil;
1513
+ VALUE keyword_values[1] = {Qundef};
1514
+ ID keyword_ids[] = {rb_intern("timeout")};
1515
+ rb_scan_args(argc, argv, "2:", &key, &initial, &keywords);
1516
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1517
+
1518
+ map_t *map = get_map(self);
1519
+ key = map_prepare_key(map, key);
1520
+ map_operation_t operation = {
1521
+ .self = self,
1522
+ .map = map,
1523
+ .execution = map_current_execution_context(),
1524
+ .key = key,
1525
+ .complete = false,
1526
+ };
1527
+ map_check_value(map, initial);
1528
+ rb_need_block();
1529
+ operation.hash = map_key_hash(map, key);
1530
+ rb_check_frozen(self);
1531
+ map_timeout_t timeout = map_parse_timeout(
1532
+ keyword_values[0] == Qundef ? Qnil : keyword_values[0]
1533
+ );
1534
+
1535
+ if (!map_lock_for_key_with_timeout(
1536
+ map, key, operation.hash, &timeout, &operation.execution
1537
+ )) return Qnil;
1538
+ bool found;
1539
+ size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
1540
+ if (RB_OBJ_FROZEN(self)) {
1541
+ pthread_mutex_unlock(&map->lock);
1542
+ rb_check_frozen(self);
1543
+ }
1544
+ if (!found) {
1545
+ map_store_locked(self, map, key, initial, operation.hash, &operation.execution);
1546
+ pthread_mutex_unlock(&map->lock);
1547
+ return initial;
1548
+ }
1549
+ operation.current = map->slots[index].value;
1550
+ operation.reservation = map_begin_reservation_locked(
1551
+ map, key, operation.hash, &operation.execution
1552
+ );
1553
+ pthread_mutex_unlock(&map->lock);
1554
+ return rb_ensure(map_upsert_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
1555
+ }
1556
+
1557
+ static VALUE
1558
+ map_compare_keys_by_identity_p(VALUE self)
1559
+ {
1560
+ return get_map(self)->compare_keys_by_identity ? Qtrue : Qfalse;
1561
+ }
1562
+
1563
+ static VALUE
1564
+ map_compare_values_by_identity_p(VALUE self)
1565
+ {
1566
+ return get_map(self)->compare_values_by_identity ? Qtrue : Qfalse;
1567
+ }
1568
+
1569
+ static VALUE
1570
+ map_size(VALUE self)
1571
+ {
1572
+ map_t *map = get_map(self);
1573
+ size_t size;
1574
+ map_execution_context_t execution = map_current_execution_context();
1575
+ map_lock_state(map, &execution);
1576
+ size = map->size;
1577
+ pthread_mutex_unlock(&map->lock);
1578
+ return SIZET2NUM(size);
1579
+ }
1580
+
1581
+ static VALUE
1582
+ map_enumerator_size(VALUE self, VALUE arguments, VALUE enumerator)
1583
+ {
1584
+ (void)arguments;
1585
+ (void)enumerator;
1586
+ return map_size(self);
1587
+ }
1588
+
1589
+ static VALUE
1590
+ map_entries_snapshot(map_t *map)
1591
+ {
1592
+ map_execution_context_t execution = map_current_execution_context();
1593
+ for (;;) {
1594
+ size_t capacity;
1595
+ map_lock_state(map, &execution);
1596
+ capacity = map->size;
1597
+ pthread_mutex_unlock(&map->lock);
1598
+
1599
+ long reserved_entries = (long)(capacity * 2);
1600
+ VALUE entries = rb_ary_new_capa(reserved_entries);
1601
+ rb_ary_resize(entries, reserved_entries);
1602
+ map_lock_state(map, &execution);
1603
+ if (map->size > capacity) {
1604
+ pthread_mutex_unlock(&map->lock);
1605
+ continue;
1606
+ }
1607
+ long entry_count = 0;
1608
+ VALUE *storage = RARRAY_PTR(entries);
1609
+ for (size_t index = 0; index < map->capacity; index++) {
1610
+ map_slot_t *slot = &map->slots[index];
1611
+ if (slot->state != MAP_OCCUPIED) continue;
1612
+ RB_OBJ_WRITE(entries, &storage[entry_count++], slot->key);
1613
+ RB_OBJ_WRITE(entries, &storage[entry_count++], slot->value);
1614
+ }
1615
+ pthread_mutex_unlock(&map->lock);
1616
+ if (entry_count < reserved_entries) rb_ary_resize(entries, entry_count);
1617
+ return entries;
1618
+ }
1619
+ }
1620
+
1621
+ static VALUE
1622
+ map_keys(VALUE self)
1623
+ {
1624
+ VALUE entries = map_entries_snapshot(get_map(self));
1625
+ long entry_count = RARRAY_LEN(entries);
1626
+ VALUE keys = rb_ary_new_capa(entry_count / 2);
1627
+ for (long index = 0; index < entry_count; index += 2) {
1628
+ rb_ary_push(keys, RARRAY_AREF(entries, index));
1629
+ }
1630
+ RB_GC_GUARD(entries);
1631
+ return keys;
1632
+ }
1633
+
1634
+ static VALUE
1635
+ map_each(VALUE self)
1636
+ {
1637
+ RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1638
+ VALUE entries = map_entries_snapshot(get_map(self));
1639
+ long entry_count = RARRAY_LEN(entries);
1640
+ for (long index = 0; index < entry_count; index += 2) {
1641
+ rb_yield(rb_assoc_new(RARRAY_AREF(entries, index), RARRAY_AREF(entries, index + 1)));
1642
+ }
1643
+ RB_GC_GUARD(entries);
1644
+ return self;
1645
+ }
1646
+
1647
+ /* A bounded live traversal. No native lock is held across a Ruby yield.
1648
+ * Insertions, rehashing and clearing invalidate the cursor. Deletions and
1649
+ * value replacement are permitted while the table layout stays unchanged. */
1650
+ static VALUE
1651
+ map_each_live(VALUE self)
1652
+ {
1653
+ RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1654
+ map_t *map = get_map(self);
1655
+ map_execution_context_t execution = map_current_execution_context();
1656
+ size_t cursor = 0;
1657
+ uint64_t epoch = 0;
1658
+ bool started = false;
1659
+ const long batch_slots = 64;
1660
+ VALUE batch = rb_ary_new_capa(batch_slots);
1661
+ rb_ary_resize(batch, batch_slots);
1662
+
1663
+ for (;;) {
1664
+ VALUE *storage = RARRAY_PTR(batch);
1665
+ for (long index = 0; index < batch_slots; index++) {
1666
+ RB_OBJ_WRITE(batch, &storage[index], Qnil);
1667
+ }
1668
+ map_lock_state(map, &execution);
1669
+ if (started && epoch != map->iteration_epoch) {
1670
+ pthread_mutex_unlock(&map->lock);
1671
+ rb_raise(rb_eRuntimeError, "map structurally changed during live iteration");
1672
+ }
1673
+ epoch = map->iteration_epoch;
1674
+ started = true;
1675
+ long length = 0;
1676
+ while (cursor < map->capacity && length < batch_slots) {
1677
+ map_slot_t *slot = &map->slots[cursor++];
1678
+ if (slot->state != MAP_OCCUPIED) continue;
1679
+ RB_OBJ_WRITE(batch, &storage[length++], slot->key);
1680
+ RB_OBJ_WRITE(batch, &storage[length++], slot->value);
1681
+ }
1682
+ pthread_mutex_unlock(&map->lock);
1683
+ if (length == 0) break;
1684
+ for (long index = 0; index < length; index += 2) {
1685
+ rb_yield(rb_assoc_new(RARRAY_AREF(batch, index), RARRAY_AREF(batch, index + 1)));
1686
+ }
1687
+ }
1688
+ RB_GC_GUARD(batch);
1689
+ return self;
1690
+ }
1691
+
1692
+ static VALUE
1693
+ map_each_key(VALUE self)
1694
+ {
1695
+ RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1696
+ VALUE entries = map_entries_snapshot(get_map(self));
1697
+ long entry_count = RARRAY_LEN(entries);
1698
+ for (long index = 0; index < entry_count; index += 2) {
1699
+ rb_yield(RARRAY_AREF(entries, index));
1700
+ }
1701
+ RB_GC_GUARD(entries);
1702
+ return self;
1703
+ }
1704
+
1705
+ static VALUE
1706
+ map_each_value(VALUE self)
1707
+ {
1708
+ RETURN_SIZED_ENUMERATOR(self, 0, NULL, map_enumerator_size);
1709
+ VALUE entries = map_entries_snapshot(get_map(self));
1710
+ long entry_count = RARRAY_LEN(entries);
1711
+ for (long index = 1; index < entry_count; index += 2) {
1712
+ rb_yield(RARRAY_AREF(entries, index));
1713
+ }
1714
+ RB_GC_GUARD(entries);
1715
+ return self;
1716
+ }
1717
+
1718
+ static VALUE
1719
+ map_get_with_timeout(int argc, VALUE *argv, VALUE self)
1720
+ {
1721
+ VALUE key;
1722
+ VALUE keywords = Qnil;
1723
+ VALUE keyword_values[1] = {Qundef};
1724
+ ID keyword_ids[] = {rb_intern("timeout")};
1725
+ rb_scan_args(argc, argv, "1:", &key, &keywords);
1726
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1727
+
1728
+ map_t *map = get_map(self);
1729
+ key = map_prepare_key(map, key);
1730
+ st_index_t hash = map_key_hash(map, key);
1731
+ map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1732
+ map_execution_context_t execution = map_current_execution_context();
1733
+ if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1734
+ return map_timeout_result();
1735
+ }
1736
+
1737
+ bool found;
1738
+ size_t index = map_find_slot(map, key, hash, &found, &execution);
1739
+ VALUE result = found ? map->slots[index].value : Qnil;
1740
+ pthread_mutex_unlock(&map->lock);
1741
+ return result;
1742
+ }
1743
+
1744
+ static VALUE
1745
+ map_store_with_timeout(int argc, VALUE *argv, VALUE self)
1746
+ {
1747
+ rb_check_frozen(self);
1748
+ VALUE key;
1749
+ VALUE value;
1750
+ VALUE keywords = Qnil;
1751
+ VALUE keyword_values[1] = {Qundef};
1752
+ ID keyword_ids[] = {rb_intern("timeout")};
1753
+ rb_scan_args(argc, argv, "2:", &key, &value, &keywords);
1754
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1755
+
1756
+ map_t *map = get_map(self);
1757
+ key = map_prepare_key(map, key);
1758
+ map_check_value(map, value);
1759
+ st_index_t hash = map_key_hash(map, key);
1760
+ rb_check_frozen(self);
1761
+ map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1762
+ map_execution_context_t execution = map_current_execution_context();
1763
+ if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1764
+ return map_timeout_result();
1765
+ }
1766
+ map_store_locked(self, map, key, value, hash, &execution);
1767
+ pthread_mutex_unlock(&map->lock);
1768
+ return value;
1769
+ }
1770
+
1771
+ static VALUE
1772
+ map_swap(int argc, VALUE *argv, VALUE self)
1773
+ {
1774
+ rb_check_frozen(self);
1775
+ VALUE key;
1776
+ VALUE replacement;
1777
+ VALUE keywords = Qnil;
1778
+ VALUE keyword_values[1] = {Qundef};
1779
+ ID keyword_ids[] = {rb_intern("timeout")};
1780
+ rb_scan_args(argc, argv, "2:", &key, &replacement, &keywords);
1781
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1782
+
1783
+ map_t *map = get_map(self);
1784
+ key = map_prepare_key(map, key);
1785
+ map_check_value(map, replacement);
1786
+ st_index_t hash = map_key_hash(map, key);
1787
+ rb_check_frozen(self);
1788
+ map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1789
+ map_execution_context_t execution = map_current_execution_context();
1790
+ if (!map_lock_for_key_with_timeout(map, key, hash, &timeout, &execution)) {
1791
+ return map_timeout_result();
1792
+ }
1793
+
1794
+ bool found;
1795
+ size_t index = map_find_slot(map, key, hash, &found, &execution);
1796
+ if (RB_OBJ_FROZEN(self)) {
1797
+ pthread_mutex_unlock(&map->lock);
1798
+ rb_check_frozen(self);
1799
+ }
1800
+ VALUE previous = found ? map->slots[index].value : Qnil;
1801
+ map_store_locked(self, map, key, replacement, hash, &execution);
1802
+ pthread_mutex_unlock(&map->lock);
1803
+ return previous;
1804
+ }
1805
+
1806
+ static bool
1807
+ map_values_equal(map_t *map, VALUE left, VALUE right)
1808
+ {
1809
+ return map->compare_values_by_identity ? left == right : RTEST(rb_equal(left, right));
1810
+ }
1811
+
1812
+ static VALUE
1813
+ map_wait_for_value(int argc, VALUE *argv, VALUE self, bool non_nil)
1814
+ {
1815
+ VALUE key;
1816
+ VALUE expected = Qnil;
1817
+ VALUE keywords = Qnil;
1818
+ VALUE keyword_values[1] = {Qundef};
1819
+ ID keyword_ids[] = {rb_intern("timeout")};
1820
+ if (non_nil) rb_scan_args(argc, argv, "1:", &key, &keywords);
1821
+ else rb_scan_args(argc, argv, "2:", &key, &expected, &keywords);
1822
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1823
+
1824
+ map_t *map = get_map(self);
1825
+ key = map_prepare_key(map, key);
1826
+ if (!non_nil) map_check_value(map, expected);
1827
+ st_index_t hash = map_key_hash(map, key);
1828
+ map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1829
+ map_execution_context_t execution = map_current_execution_context();
1830
+
1831
+ for (;;) {
1832
+ bool found;
1833
+ VALUE current = Qnil;
1834
+ uint64_t generation;
1835
+ map_lock_state(map, &execution);
1836
+ size_t index = map_find_slot(map, key, hash, &found, &execution);
1837
+ if (found) current = map->slots[index].value;
1838
+ generation = map->generation;
1839
+ pthread_mutex_unlock(&map->lock);
1840
+
1841
+ bool ready = non_nil ? !NIL_P(current) : !map_values_equal(map, current, expected);
1842
+ if (ready) return current;
1843
+
1844
+ map_lock_state(map, &execution);
1845
+ if (map->generation != generation) {
1846
+ pthread_mutex_unlock(&map->lock);
1847
+ continue;
1848
+ }
1849
+ map_reservation_t *reservation = map_find_reservation_locked(map, key, hash, &execution);
1850
+ if (reservation) map_check_reservation_wait_locked(map, reservation, &execution);
1851
+ pthread_mutex_unlock(&map->lock);
1852
+ if (!map_wait_once(map, generation, &timeout)) return map_timeout_result();
1853
+ }
1854
+ }
1855
+
1856
+ static VALUE
1857
+ map_wait_until_changed(int argc, VALUE *argv, VALUE self)
1858
+ {
1859
+ return map_wait_for_value(argc, argv, self, false);
1860
+ }
1861
+
1862
+ static VALUE
1863
+ map_wait_until_non_nil(int argc, VALUE *argv, VALUE self)
1864
+ {
1865
+ return map_wait_for_value(argc, argv, self, true);
1866
+ }
1867
+
1868
+ static VALUE
1869
+ map_update(int argc, VALUE *argv, VALUE self)
1870
+ {
1871
+ rb_check_frozen(self);
1872
+ VALUE key;
1873
+ VALUE keywords = Qnil;
1874
+ VALUE keyword_values[1] = {Qundef};
1875
+ ID keyword_ids[] = {rb_intern("timeout")};
1876
+ rb_scan_args(argc, argv, "1:", &key, &keywords);
1877
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
1878
+
1879
+ map_t *map = get_map(self);
1880
+ key = map_prepare_key(map, key);
1881
+ map_operation_t operation = {
1882
+ .self = self,
1883
+ .map = map,
1884
+ .execution = map_current_execution_context(),
1885
+ .key = key,
1886
+ .complete = false,
1887
+ };
1888
+ rb_need_block();
1889
+ operation.hash = map_key_hash(map, key);
1890
+ rb_check_frozen(self);
1891
+ map_timeout_t timeout = map_parse_timeout(keyword_values[0] == Qundef ? Qnil : keyword_values[0]);
1892
+
1893
+ if (!map_lock_for_key_with_timeout(
1894
+ map, key, operation.hash, &timeout, &operation.execution
1895
+ )) return Qnil;
1896
+ bool found;
1897
+ size_t index = map_find_slot(map, key, operation.hash, &found, &operation.execution);
1898
+ if (RB_OBJ_FROZEN(self)) {
1899
+ pthread_mutex_unlock(&map->lock);
1900
+ rb_check_frozen(self);
1901
+ }
1902
+ operation.current = found ? map->slots[index].value : Qnil;
1903
+ operation.reservation = map_begin_reservation_locked(
1904
+ map, key, operation.hash, &operation.execution
1905
+ );
1906
+ pthread_mutex_unlock(&map->lock);
1907
+ return rb_ensure(map_upsert_body, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
1908
+ }
1909
+
1910
+ static key_lock_map_t *
1911
+ key_lock_get_raw(VALUE self)
1912
+ {
1913
+ key_lock_map_t *map;
1914
+ if (rb_typeddata_is_kind_of(self, &unshared_key_lock_type)) {
1915
+ TypedData_Get_Struct(self, key_lock_map_t, &unshared_key_lock_type, map);
1916
+ }
1917
+ else {
1918
+ TypedData_Get_Struct(self, key_lock_map_t, &shared_key_lock_type, map);
1919
+ }
1920
+ return map;
1921
+ }
1922
+
1923
+ static void
1924
+ shared_key_lock_validate_references(VALUE self)
1925
+ {
1926
+ key_lock_map_t *storage;
1927
+ TypedData_Get_Struct(self, key_lock_map_t, &shared_key_lock_type, storage);
1928
+ map_t *map = &storage->core;
1929
+ for (size_t index = 0; index < map->capacity; index++) {
1930
+ if (map->slots[index].state != MAP_OCCUPIED) continue;
1931
+ containers_check_shareable(map->slots[index].key);
1932
+ containers_check_shareable(map->slots[index].value);
1933
+ }
1934
+ for (map_reservation_t *reservation = map->reservations; reservation; reservation = reservation->next) {
1935
+ containers_check_shareable(reservation->key);
1936
+ }
1937
+ }
1938
+
1939
+ static VALUE
1940
+ key_lock_publish(VALUE self)
1941
+ {
1942
+ return containers_publish_native_with_references(self, shared_key_lock_validate_references);
1943
+ }
1944
+
1945
+ static VALUE
1946
+ key_lock_finish_publication(VALUE self)
1947
+ {
1948
+ key_lock_map_t *map = key_lock_get_raw(self);
1949
+ int state = rb_ractor_shareable_p(self) ? KEY_LOCK_INITIALIZED : KEY_LOCK_UNINITIALIZED;
1950
+ atomic_store_explicit(&map->state, state, memory_order_release);
1951
+ return Qnil;
1952
+ }
1953
+
1954
+ static VALUE
1955
+ key_lock_initialize(int argc, VALUE *argv, VALUE self)
1956
+ {
1957
+ VALUE keywords = Qnil;
1958
+ ID ids[] = {
1959
+ rb_intern("compare_by_identity"),
1960
+ rb_intern("compare_keys_by_identity"),
1961
+ rb_intern("compare_values_by_identity")
1962
+ };
1963
+ VALUE values[3] = {Qundef, Qundef, Qundef};
1964
+ rb_scan_args(argc, argv, "0:", &keywords);
1965
+ if (!NIL_P(keywords)) rb_get_kwargs(keywords, ids, 0, 3, values);
1966
+ key_lock_map_t *map = key_lock_get_raw(self);
1967
+ if (atomic_load_explicit(&map->state, memory_order_acquire) != KEY_LOCK_UNINITIALIZED) {
1968
+ rb_raise(rb_eRuntimeError, "key lock map is already initialized");
1969
+ }
1970
+ rb_check_frozen(self);
1971
+ bool common = values[0] == Qundef ? false : containers_strict_bool(values[0], "compare_by_identity");
1972
+ bool keys = values[1] == Qundef ? common : containers_strict_bool(values[1], "compare_keys_by_identity");
1973
+ if (values[2] != Qundef) containers_strict_bool(values[2], "compare_values_by_identity");
1974
+ int expected = KEY_LOCK_UNINITIALIZED;
1975
+ if (!atomic_compare_exchange_strong(&map->state, &expected, KEY_LOCK_INITIALIZING)) {
1976
+ rb_raise(rb_eRuntimeError, "key lock map is already initialized");
1977
+ }
1978
+ map->core.compare_keys_by_identity = keys;
1979
+ if (rb_typeddata_is_kind_of(self, &unshared_key_lock_type)) {
1980
+ atomic_store_explicit(&map->state, KEY_LOCK_INITIALIZED, memory_order_release);
1981
+ return self;
1982
+ }
1983
+ atomic_store_explicit(&map->state, KEY_LOCK_PUBLISHING, memory_order_release);
1984
+ return rb_ensure(key_lock_publish, self, key_lock_finish_publication, self);
1985
+ }
1986
+
1987
+ static VALUE
1988
+ key_lock_yield(VALUE opaque)
1989
+ {
1990
+ (void)opaque;
1991
+ return rb_yield_values(0);
1992
+ }
1993
+
1994
+ static VALUE
1995
+ key_lock_synchronize(VALUE self, VALUE key)
1996
+ {
1997
+ key_lock_map_t *storage = key_lock_get_raw(self);
1998
+ int state = atomic_load_explicit(&storage->state, memory_order_acquire);
1999
+ bool shared = rb_typeddata_is_kind_of(self, &shared_key_lock_type);
2000
+ if (state != KEY_LOCK_INITIALIZED &&
2001
+ !(shared && state == KEY_LOCK_PUBLISHING && rb_ractor_shareable_p(self))) {
2002
+ rb_raise(rb_eRuntimeError, "uninitialized key lock map");
2003
+ }
2004
+ rb_need_block();
2005
+ if (shared) containers_check_shareable(key);
2006
+ map_t *map = &storage->core;
2007
+ map_operation_t operation = {
2008
+ .map = map,
2009
+ .execution = map_current_execution_context(),
2010
+ .key = key,
2011
+ .complete = false,
2012
+ };
2013
+ operation.hash = map_key_hash(map, key);
2014
+ map_lock_for_key(map, key, operation.hash, &operation.execution);
2015
+ operation.reservation = map_begin_reservation_locked(
2016
+ map, key, operation.hash, &operation.execution
2017
+ );
2018
+ pthread_mutex_unlock(&map->lock);
2019
+ return rb_ensure(key_lock_yield, (VALUE)&operation, map_operation_cleanup, (VALUE)&operation);
2020
+ }
2021
+
2022
+ static VALUE
2023
+ key_lock_initialize_copy(VALUE self, VALUE other)
2024
+ {
2025
+ (void)self;
2026
+ (void)other;
2027
+ rb_raise(rb_eTypeError, "key lock maps cannot be copied");
2028
+ }
2029
+
2030
+ static bool
2031
+ map_transaction_valid(farce_transaction_entry_t *entry)
2032
+ {
2033
+ map_t *map = entry->source_data;
2034
+ return map->generation == entry->version && !map->comparing && !map->reservations;
2035
+ }
2036
+
2037
+ static void
2038
+ map_transaction_apply(farce_transaction_entry_t *entry)
2039
+ {
2040
+ map_t *map = entry->source_data;
2041
+ map_t *copy = entry->working_data;
2042
+ map_slot_t *slots = map->slots;
2043
+ size_t capacity = map->capacity, size = map->size, tombstones = map->tombstones;
2044
+ map->slots = copy->slots;
2045
+ map->capacity = copy->capacity;
2046
+ map->size = copy->size;
2047
+ map->tombstones = copy->tombstones;
2048
+ map->iteration_epoch++;
2049
+ copy->slots = slots;
2050
+ copy->capacity = capacity;
2051
+ copy->size = size;
2052
+ copy->tombstones = tombstones;
2053
+ }
2054
+
2055
+ static void
2056
+ map_transaction_notify(farce_transaction_entry_t *entry)
2057
+ {
2058
+ map_notify_waiters_locked(entry->source_data);
2059
+ }
2060
+
2061
+ static void
2062
+ map_transaction_reserve(farce_transaction_entry_t *entry, VALUE fiber, VALUE thread)
2063
+ {
2064
+ map_t *map = entry->source_data;
2065
+ map->comparing = FARCE_TRANSACTION_RESERVED;
2066
+ map->comparing_owner = fiber;
2067
+ map->comparing_thread = thread;
2068
+ }
2069
+
2070
+ static void
2071
+ map_transaction_release(farce_transaction_entry_t *entry)
2072
+ {
2073
+ map_t *map = entry->source_data;
2074
+ map->comparing = false;
2075
+ map->comparing_owner = Qnil;
2076
+ map->comparing_thread = Qnil;
2077
+ map_notify_waiters_locked(map);
2078
+ }
2079
+
2080
+ static VALUE
2081
+ map_freeze(VALUE self)
2082
+ {
2083
+ map_t *map;
2084
+ TypedData_Get_Struct(self, map_t, &map_type, map);
2085
+ pthread_mutex_lock(&map->lock);
2086
+ if (map->comparing == FARCE_TRANSACTION_RESERVED) {
2087
+ pthread_mutex_unlock(&map->lock);
2088
+ map_execution_context_t context = map_current_execution_context();
2089
+ map_lock_state(map, &context);
2090
+ }
2091
+ rb_obj_freeze(self);
2092
+ pthread_mutex_unlock(&map->lock);
2093
+ return self;
2094
+ }
2095
+
2096
+ static const farce_transaction_ops_t map_transaction_ops = {
2097
+ map_transaction_valid, map_transaction_apply, map_transaction_notify,
2098
+ map_transaction_reserve, map_transaction_release,
2099
+ };
2100
+
2101
+ static bool
2102
+ map_transaction_size_valid(farce_transaction_entry_t *entry)
2103
+ {
2104
+ map_t *map = entry->source_data;
2105
+ return map->size == entry->version && !map->comparing && !map->reservations;
2106
+ }
2107
+
2108
+ static void
2109
+ map_transaction_size_noop(farce_transaction_entry_t *entry)
2110
+ {
2111
+ (void)entry;
2112
+ }
2113
+
2114
+ static const farce_transaction_ops_t map_transaction_size_ops = {
2115
+ map_transaction_size_valid, map_transaction_size_noop, map_transaction_size_noop,
2116
+ map_transaction_reserve, map_transaction_release,
2117
+ };
2118
+
2119
+ /* Count-only participants use the same commit lock without copying slots or
2120
+ * rejecting completed value replacements that leave cardinality unchanged. */
2121
+ static VALUE
2122
+ map_transaction_size_snapshot(VALUE self, VALUE size)
2123
+ {
2124
+ uint64_t expected = NUM2ULL(size);
2125
+ map_t *source = get_map(self);
2126
+ farce_transaction_entry_t *entry;
2127
+ VALUE result = farce_transaction_entry_new(
2128
+ self, Qnil, source, NULL, &source->lock, &map_transaction_size_ops, &entry
2129
+ );
2130
+ entry->version = expected;
2131
+ return result;
2132
+ }
2133
+
2134
+ static VALUE
2135
+ map_transaction_snapshot(VALUE self)
2136
+ {
2137
+ map_t *source = get_map(self);
2138
+ VALUE working = source->shareable ? map_allocate(cMap) : unshared_map_allocate(cUnsharedMap);
2139
+ map_t *copy;
2140
+ TypedData_Get_Struct(working, map_t, &map_type, copy);
2141
+ farce_transaction_entry_t *entry;
2142
+ VALUE result = farce_transaction_entry_new(
2143
+ self, working, source, copy, &source->lock, &map_transaction_ops, &entry
2144
+ );
2145
+ pthread_mutex_lock(&source->lock);
2146
+ copy->slots = malloc(source->capacity * sizeof(map_slot_t));
2147
+ if (!copy->slots) {
2148
+ pthread_mutex_unlock(&source->lock);
2149
+ rb_memerror();
2150
+ }
2151
+ memcpy(copy->slots, source->slots, source->capacity * sizeof(map_slot_t));
2152
+ copy->capacity = source->capacity;
2153
+ copy->size = source->size;
2154
+ copy->tombstones = source->tombstones;
2155
+ copy->compare_keys_by_identity = source->compare_keys_by_identity;
2156
+ copy->compare_values_by_identity = source->compare_values_by_identity;
2157
+ copy->initialized = true;
2158
+ entry->version = source->generation;
2159
+ pthread_mutex_unlock(&source->lock);
2160
+ return result;
2161
+ }
2162
+
2163
+ static void
2164
+ map_define_methods(VALUE klass)
2165
+ {
2166
+ rb_define_method(klass, "initialize", map_initialize, -1);
2167
+ rb_define_method(klass, "[]", map_get, 1);
2168
+ rb_define_method(klass, "[]=", map_set, 2);
2169
+ rb_define_method(klass, "check_mutation", map_check_mutation, 0);
2170
+ rb_define_method(klass, "prepare_mutation_key", map_prepare_mutation_key, 1);
2171
+ rb_define_method(klass, "fetch", map_fetch, -1);
2172
+ rb_define_method(klass, "get", map_get_with_timeout, -1);
2173
+ rb_define_method(klass, "store", map_store_with_timeout, -1);
2174
+ rb_define_method(klass, "swap", map_swap, -1);
2175
+ rb_define_method(klass, "update", map_update, -1);
2176
+ rb_define_method(klass, "modify", map_modify, 1);
2177
+ rb_define_method(klass, "wait_until_changed", map_wait_until_changed, -1);
2178
+ rb_define_method(klass, "wait_until_non_nil", map_wait_until_non_nil, -1);
2179
+ rb_define_method(klass, "store_if_absent", map_store_if_absent, -1);
2180
+ rb_define_method(klass, "key?", map_key_p, 1);
2181
+ rb_define_method(klass, "delete", map_delete, 1);
2182
+ rb_define_method(klass, "clear", map_clear, 0);
2183
+ rb_define_method(klass, "freeze", map_freeze, 0);
2184
+ rb_define_method(klass, "transaction_snapshot", map_transaction_snapshot, 0);
2185
+ rb_define_method(klass, "transaction_size_snapshot", map_transaction_size_snapshot, 1);
2186
+ rb_define_method(klass, "compare_and_set", map_compare_and_set, -1);
2187
+ rb_define_method(klass, "upsert", map_upsert, -1);
2188
+ rb_define_method(klass, "compare_keys_by_identity?", map_compare_keys_by_identity_p, 0);
2189
+ rb_define_method(klass, "compare_values_by_identity?", map_compare_values_by_identity_p, 0);
2190
+ rb_define_method(klass, "getkey", map_getkey, 1);
2191
+ rb_define_method(klass, "size", map_size, 0);
2192
+ rb_define_method(klass, "keys", map_keys, 0);
2193
+ rb_define_method(klass, "each", map_each, 0);
2194
+ rb_define_method(klass, "each_live", map_each_live, 0);
2195
+ rb_define_method(klass, "each_pair", map_each, 0);
2196
+ rb_define_method(klass, "each_key", map_each_key, 0);
2197
+ rb_define_method(klass, "each_value", map_each_value, 0);
2198
+ }
2199
+
2200
+
2201
+ void
2202
+ containers_init_map(VALUE namespace)
2203
+ {
2204
+ rb_global_variable(&cStrictMapFacade);
2205
+ rb_global_variable(&cUnsharedMapFacade);
2206
+ id_map = rb_intern("@map");
2207
+ id_unwrap_value = rb_intern("unwrap_value");
2208
+ id_wrap_value = rb_intern("wrap_value");
2209
+ rb_global_variable(&cModeMapFacade);
2210
+ rb_global_variable(&cMapEnvelope);
2211
+ rb_global_variable(&map_nil_value);
2212
+ eIsolationError = rb_const_get(rb_cRactor, rb_intern("IsolationError"));
2213
+ map_keep = rb_const_get(namespace, rb_intern("MAP_KEEP"));
2214
+ map_delete_token = rb_const_get(namespace, rb_intern("MAP_DELETE"));
2215
+ rb_global_variable(&map_keep);
2216
+ rb_global_variable(&map_delete_token);
2217
+ cUnsharedKeyLockMap = rb_define_class_under(namespace, "UnsharedKeyLockMap", rb_cObject);
2218
+ rb_define_alloc_func(cUnsharedKeyLockMap, unshared_key_lock_allocate);
2219
+ rb_define_method(cUnsharedKeyLockMap, "initialize", key_lock_initialize, -1);
2220
+ rb_define_method(cUnsharedKeyLockMap, "initialize_copy", key_lock_initialize_copy, 1);
2221
+ rb_define_method(cUnsharedKeyLockMap, "synchronize", key_lock_synchronize, 1);
2222
+
2223
+ cSharedKeyLockMap = rb_define_class_under(namespace, "SharedKeyLockMap", rb_cObject);
2224
+ rb_define_alloc_func(cSharedKeyLockMap, shared_key_lock_allocate);
2225
+ rb_define_method(cSharedKeyLockMap, "initialize", key_lock_initialize, -1);
2226
+ rb_define_method(cSharedKeyLockMap, "initialize_copy", key_lock_initialize_copy, 1);
2227
+ rb_define_method(cSharedKeyLockMap, "synchronize", key_lock_synchronize, 1);
2228
+ rb_define_method(cSharedKeyLockMap, "freeze", containers_raise_unfreezable, 0);
2229
+
2230
+ cMap = rb_define_class_under(namespace, "Map", rb_cObject);
2231
+ rb_define_alloc_func(cMap, map_allocate);
2232
+ map_define_methods(cMap);
2233
+
2234
+ cUnsharedMap = rb_define_class_under(namespace, "UnsharedMap", rb_cObject);
2235
+ rb_define_alloc_func(cUnsharedMap, unshared_map_allocate);
2236
+ map_define_methods(cUnsharedMap);
2237
+ rb_define_singleton_method(
2238
+ namespace, "prepare_map_access", map_prepare_access, 2
2239
+ );
2240
+ }