tigerbeetle 0.0.34 → 0.0.37

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/ext/tb_client/extconf.rb +13 -13
  4. data/ext/tb_client/tigerbeetle/LICENSE +177 -0
  5. data/ext/tb_client/tigerbeetle/build.zig +2327 -0
  6. data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
  7. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
  8. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
  9. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
  10. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
  11. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
  12. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
  13. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
  14. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
  15. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
  16. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
  17. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
  18. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
  19. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
  20. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
  21. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
  22. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
  23. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
  24. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
  25. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
  26. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
  27. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
  28. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
  29. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
  30. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
  31. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
  32. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
  33. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
  34. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
  35. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
  36. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
  37. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
  38. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
  39. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
  40. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
  41. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
  42. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
  43. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
  44. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
  45. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
  46. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
  47. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
  48. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
  49. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
  50. data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
  51. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
  52. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
  53. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
  54. data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
  55. data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
  56. data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
  57. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
  58. data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
  59. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
  60. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
  61. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
  62. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
  63. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
  64. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
  65. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
  66. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
  67. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
  68. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
  69. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
  70. data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
  71. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
  72. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
  73. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
  74. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
  75. data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
  76. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
  77. data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
  78. data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
  79. data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
  80. data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
  81. data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
  82. data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
  83. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
  84. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
  85. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
  86. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
  87. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
  88. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
  89. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
  90. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
  91. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
  92. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
  93. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
  94. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
  95. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
  96. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
  97. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
  98. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
  99. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
  100. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
  101. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
  102. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
  103. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
  104. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
  105. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
  106. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
  107. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
  108. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
  109. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
  110. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
  111. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
  112. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
  113. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
  114. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
  115. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
  116. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
  117. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
  118. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
  119. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
  120. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
  121. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
  122. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
  123. data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
  124. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
  125. data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
  126. data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
  127. data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
  128. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
  129. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
  130. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
  131. data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
  132. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
  133. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
  134. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
  135. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
  136. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
  137. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
  138. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
  139. data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
  140. data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
  141. data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
  142. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
  143. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
  144. data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
  145. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
  146. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
  147. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
  148. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
  149. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
  150. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
  151. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
  152. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
  153. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
  154. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
  155. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
  156. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
  157. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
  158. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
  159. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
  160. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
  161. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
  162. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
  163. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
  164. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
  165. data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
  166. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
  167. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
  168. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
  169. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
  170. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
  171. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
  172. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
  173. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
  174. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
  175. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
  176. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
  177. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
  178. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
  179. data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
  180. data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
  181. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
  182. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
  183. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
  184. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
  185. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
  186. data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
  187. data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
  188. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
  189. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
  190. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
  191. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
  192. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
  193. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
  194. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
  195. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
  196. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
  197. data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
  198. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
  199. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
  200. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
  201. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
  202. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
  203. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
  204. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
  205. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
  206. data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
  207. data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
  208. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
  209. data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
  210. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
  211. data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
  212. data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
  213. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
  214. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
  215. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
  216. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
  217. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
  218. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
  219. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
  220. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
  221. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
  222. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
  223. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
  224. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
  225. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
  226. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
  227. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
  228. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
  229. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
  230. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
  231. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
  232. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
  233. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
  234. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
  235. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
  236. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
  237. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
  238. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
  239. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
  240. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
  241. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
  242. data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
  243. data/lib/tb_client/shared_lib.rb +12 -5
  244. data/lib/tigerbeetle/client.rb +1 -1
  245. data/lib/tigerbeetle/platforms.rb +9 -0
  246. data/lib/tigerbeetle/version.rb +2 -2
  247. data/tigerbeetle.gemspec +22 -5
  248. metadata +242 -3
  249. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,933 @@
1
+ const std = @import("std");
2
+ const testing = std.testing;
3
+ const assert = std.debug.assert;
4
+
5
+ const stdx = @import("stdx");
6
+ const constants = @import("../constants.zig");
7
+ const fixtures = @import("../testing/fixtures.zig");
8
+ const fuzz = @import("../testing/fuzz.zig");
9
+ const vsr = @import("../vsr.zig");
10
+ const schema = @import("schema.zig");
11
+ const ratio = stdx.PRNG.ratio;
12
+
13
+ const log = std.log.scoped(.lsm_tree_fuzz);
14
+
15
+ const ScratchMemory = @import("scratch_memory.zig").ScratchMemory;
16
+ const Direction = @import("../direction.zig").Direction;
17
+ const TimeSim = @import("../testing/time.zig").TimeSim;
18
+ const Storage = @import("../testing/storage.zig").Storage;
19
+ const GridType = @import("../vsr/grid.zig").GridType;
20
+ const NodePool = @import("node_pool.zig").NodePoolType(constants.lsm_manifest_node_size, 16);
21
+ const TableUsage = @import("table.zig").TableUsage;
22
+ const TableType = @import("table.zig").TableType;
23
+ const ManifestLog = @import("manifest_log.zig").ManifestLogType(Storage);
24
+ const ManifestLogPace = @import("manifest_log.zig").Pace;
25
+ const snapshot_min_for_table_output = @import("compaction.zig").snapshot_min_for_table_output;
26
+ const compaction_op_min = @import("compaction.zig").compaction_op_min;
27
+ const compaction_block_count_beat_min = @import("compaction.zig").compaction_block_count_beat_min;
28
+ const ResourcePool = @import("compaction.zig").ResourcePoolType(Grid);
29
+
30
+ const Grid = @import("../vsr/grid.zig").GridType(Storage);
31
+ const SuperBlock = vsr.SuperBlockType(Storage);
32
+ const ScanBuffer = @import("scan_buffer.zig").ScanBuffer;
33
+ const TreeType = @import("tree.zig").TreeType;
34
+ const ScanTreeType = @import("scan_tree.zig").ScanTreeType;
35
+ const SortedSegmentedArrayType = @import("./segmented_array.zig").SortedSegmentedArrayType;
36
+
37
+ const Value = packed struct(u128) {
38
+ id: u64,
39
+ value: u63,
40
+ tombstone_bit: u1 = 0,
41
+
42
+ comptime {
43
+ assert(@bitSizeOf(Value) == @sizeOf(Value) * 8);
44
+ }
45
+
46
+ inline fn key_from_value(value: *const Value) u64 {
47
+ return value.id;
48
+ }
49
+
50
+ const sentinel_key = std.math.maxInt(u64);
51
+
52
+ inline fn tombstone(value: *const Value) bool {
53
+ return value.tombstone_bit != 0;
54
+ }
55
+
56
+ inline fn tombstone_from_key(key: u64) Value {
57
+ return Value{
58
+ .id = key,
59
+ .value = 0,
60
+ .tombstone_bit = 1,
61
+ };
62
+ }
63
+ };
64
+
65
+ const FuzzOpTag = std.meta.Tag(FuzzOp);
66
+ const FuzzOp = union(enum) {
67
+ const Scan = struct {
68
+ snapshot: u64,
69
+ min: u64,
70
+ max: u64,
71
+ direction: Direction,
72
+ };
73
+
74
+ compact: struct {
75
+ op: u64,
76
+ beats_remaining: u64,
77
+ checkpoint: bool,
78
+ },
79
+ put: Value,
80
+ remove: Value,
81
+ get: u64,
82
+ scan: Scan,
83
+ };
84
+
85
+ const batch_size_max = constants.message_size_max - @sizeOf(vsr.Header);
86
+ const commit_entries_max = @divFloor(batch_size_max, @sizeOf(Value));
87
+ const value_count_max = constants.lsm_compaction_ops * commit_entries_max;
88
+ const table_count_max = @import("tree.zig").table_count_max;
89
+
90
+ const node_count = 1024;
91
+ const scan_results_max = 4096;
92
+ const events_max = 10_000_000;
93
+
94
+ // We must call compact after every 'batch'.
95
+ // Every `lsm_compaction_ops` batches may put/remove `value_count_max` values.
96
+ // Every `FuzzOp.put` issues one remove and one put.
97
+ const puts_since_compact_max = @divTrunc(commit_entries_max, 2);
98
+
99
+ fn EnvironmentType(comptime table_usage: TableUsage) type {
100
+ return struct {
101
+ const Environment = @This();
102
+
103
+ const Table = TableType(
104
+ u64,
105
+ Value,
106
+ Value.key_from_value,
107
+ Value.sentinel_key,
108
+ Value.tombstone,
109
+ Value.tombstone_from_key,
110
+ value_count_max,
111
+ table_usage,
112
+ );
113
+ const Tree = TreeType(Table, Storage);
114
+ const ScanTree = ScanTreeType(*Environment, Tree, Storage);
115
+
116
+ const State = enum {
117
+ init,
118
+ tree_init,
119
+ manifest_log_open,
120
+ fuzzing,
121
+ tree_compact,
122
+ manifest_log_compact,
123
+ grid_checkpoint,
124
+ superblock_checkpoint,
125
+ grid_checkpoint_durable,
126
+ tree_lookup,
127
+ scan_tree,
128
+ };
129
+
130
+ state: State,
131
+ storage: *Storage,
132
+ time_sim: TimeSim,
133
+ trace: Storage.Tracer,
134
+ superblock: SuperBlock,
135
+ superblock_context: SuperBlock.Context,
136
+ grid: Grid,
137
+ manifest_log: ManifestLog,
138
+ node_pool: NodePool,
139
+ tree: Tree,
140
+ scan_tree: ScanTree,
141
+ lookup_context: Tree.LookupContext,
142
+ lookup_value: ?Value,
143
+ scan_buffer: ScanBuffer,
144
+ scan_results: []Value,
145
+ scan_results_count: u32,
146
+ scan_results_model: []Value,
147
+ compaction_exhausted: bool = false,
148
+ radix_buffer: ScratchMemory,
149
+
150
+ pool: ResourcePool,
151
+
152
+ pub fn run(
153
+ gpa: std.mem.Allocator,
154
+ storage: *Storage,
155
+ block_count: u32,
156
+ fuzz_ops: []const FuzzOp,
157
+ ) !void {
158
+ var env: Environment = undefined;
159
+ env.state = .init;
160
+ env.storage = storage;
161
+
162
+ env.time_sim = fixtures.init_time(.{});
163
+ env.trace = try fixtures.init_tracer(gpa, env.time_sim.time(), .{});
164
+ defer env.trace.deinit(gpa);
165
+
166
+ env.superblock = try fixtures.init_superblock(gpa, env.storage, .{});
167
+ defer env.superblock.deinit(gpa);
168
+
169
+ env.grid = try fixtures.init_grid(gpa, &env.trace, &env.superblock, .{
170
+ // Grid.mark_checkpoint_not_durable releases the FreeSet checkpoints blocks into
171
+ // FreeSet.blocks_released_prior_checkpoint_durability.
172
+ .blocks_released_prior_checkpoint_durability_max = 0,
173
+ });
174
+ defer env.grid.deinit(gpa);
175
+
176
+ try env.manifest_log.init(
177
+ gpa,
178
+ &env.grid,
179
+ &ManifestLogPace.init(.{
180
+ .tree_count = 1,
181
+ .tables_max = table_count_max,
182
+ .compact_extra_blocks = constants.lsm_manifest_compact_extra_blocks,
183
+ }),
184
+ );
185
+ defer env.manifest_log.deinit(gpa);
186
+
187
+ try env.node_pool.init(gpa, node_count);
188
+ defer env.node_pool.deinit(gpa);
189
+
190
+ env.tree = undefined;
191
+ env.lookup_value = null;
192
+
193
+ try env.scan_buffer.init(gpa, .{ .index = 0 });
194
+ defer env.scan_buffer.deinit(gpa);
195
+
196
+ env.scan_results = try gpa.alloc(Value, scan_results_max);
197
+ env.scan_results_count = 0;
198
+ defer gpa.free(env.scan_results);
199
+
200
+ env.scan_results_model = try gpa.alloc(Value, scan_results_max);
201
+ defer gpa.free(env.scan_results_model);
202
+
203
+ env.pool = try ResourcePool.init(gpa, block_count);
204
+ defer env.pool.deinit(gpa);
205
+
206
+ env.radix_buffer = try .init(gpa, value_count_max * @sizeOf(Value));
207
+ defer env.radix_buffer.deinit(gpa);
208
+
209
+ try env.open_then_apply(gpa, fuzz_ops);
210
+ }
211
+
212
+ fn change_state(env: *Environment, current_state: State, next_state: State) void {
213
+ assert(env.state == current_state);
214
+ env.state = next_state;
215
+ }
216
+
217
+ fn tick_until_state_change(
218
+ env: *Environment,
219
+ current_state: State,
220
+ next_state: State,
221
+ ) void {
222
+ // Sometimes operations complete synchronously so we might already be in next_state
223
+ // before ticking.
224
+ assert(env.state == current_state or env.state == next_state);
225
+ const safety_counter = 10_000_000;
226
+ for (0..safety_counter) |_| {
227
+ if (env.state != current_state) break;
228
+ env.storage.run();
229
+ } else unreachable;
230
+ assert(env.state == next_state);
231
+ }
232
+
233
+ pub fn open_then_apply(
234
+ env: *Environment,
235
+ gpa: std.mem.Allocator,
236
+ fuzz_ops: []const FuzzOp,
237
+ ) !void {
238
+ fixtures.open_superblock(&env.superblock);
239
+ fixtures.open_grid(&env.grid);
240
+
241
+ env.change_state(.init, .tree_init);
242
+ // The first checkpoint is trivially durable.
243
+ env.grid.free_set.mark_checkpoint_durable();
244
+
245
+ try env.tree.init(gpa, &env.node_pool, &env.grid, &env.radix_buffer, .{
246
+ .id = 1,
247
+ .name = "Key.Value",
248
+ }, .{
249
+ .batch_value_count_limit = commit_entries_max,
250
+ });
251
+ defer env.tree.deinit(gpa);
252
+
253
+ env.change_state(.tree_init, .manifest_log_open);
254
+
255
+ env.tree.open_commence(&env.manifest_log);
256
+ env.manifest_log.open(manifest_log_open_event, manifest_log_open_callback);
257
+
258
+ env.tick_until_state_change(.manifest_log_open, .fuzzing);
259
+ env.tree.open_complete();
260
+
261
+ try env.apply(gpa, fuzz_ops);
262
+ }
263
+
264
+ fn manifest_log_open_event(
265
+ manifest_log: *ManifestLog,
266
+ table: *const schema.ManifestNode.TableInfo,
267
+ ) void {
268
+ _ = manifest_log;
269
+ _ = table;
270
+
271
+ // This ManifestLog is only opened during setup, when it has no blocks.
272
+ unreachable;
273
+ }
274
+
275
+ fn manifest_log_open_callback(manifest_log: *ManifestLog) void {
276
+ const env: *Environment = @alignCast(@fieldParentPtr("manifest_log", manifest_log));
277
+ env.change_state(.manifest_log_open, .fuzzing);
278
+ }
279
+
280
+ pub fn compact(env: *Environment, op: u64, beats_remaining: u64) void {
281
+ const half_bar = @divExact(constants.lsm_compaction_ops, 2);
282
+ if (op < half_bar) return;
283
+ const compaction_beat = op % constants.lsm_compaction_ops;
284
+
285
+ const first_beat = compaction_beat == 0;
286
+ const half_beat = compaction_beat == half_bar;
287
+
288
+ const last_half_beat = compaction_beat == half_bar - 1;
289
+ const last_beat = compaction_beat == constants.lsm_compaction_ops - 1;
290
+
291
+ if (last_beat or last_half_beat) assert(beats_remaining == 1);
292
+
293
+ assert(env.pool.idle());
294
+ if (first_beat or half_beat) {
295
+ assert(env.pool.blocks_acquired() == 0);
296
+ }
297
+
298
+ var compactions_active: [constants.lsm_levels]*Tree.Compaction = undefined;
299
+ var compactions_active_count: usize = 0;
300
+ for (&env.tree.compactions) |*compaction| {
301
+ if ((compaction_beat < half_bar) == (compaction.level_b % 2 == 1)) {
302
+ compactions_active[compactions_active_count] = compaction;
303
+ compactions_active_count += 1;
304
+ }
305
+ }
306
+ assert(compactions_active_count <= stdx.div_ceil(constants.lsm_levels, 2));
307
+ const compactions_slice = compactions_active[0..compactions_active_count];
308
+
309
+ // 1 since we may have partially finished index/value blocks from the previous beat.
310
+ var beat_value_blocks_max: u64 = 1;
311
+ var beat_index_blocks_max: u64 = 1;
312
+
313
+ for (compactions_slice) |compaction| {
314
+ if (first_beat or half_beat) _ = compaction.bar_commence(op);
315
+
316
+ const input_values_remaining_bar =
317
+ compaction.quotas.bar - compaction.quotas.bar_done;
318
+ const input_values_remaining_beat =
319
+ stdx.div_ceil(input_values_remaining_bar, beats_remaining);
320
+
321
+ compaction.beat_commence(input_values_remaining_beat);
322
+
323
+ // The +1 is for imperfections in pacing our immutable table, which
324
+ // might cause us to overshoot by a single block (limited to 1 due
325
+ // to how the immutable table values are consumed.)
326
+ beat_value_blocks_max += stdx.div_ceil(
327
+ compaction.quotas.beat,
328
+ Table.layout.block_value_count_max,
329
+ ) + 1;
330
+
331
+ beat_index_blocks_max += stdx.div_ceil(
332
+ beat_value_blocks_max,
333
+ Table.value_block_count_max,
334
+ );
335
+ }
336
+
337
+ env.pool.grid_reservation = env.grid.reserve(
338
+ beat_value_blocks_max + beat_index_blocks_max,
339
+ );
340
+
341
+ for (compactions_slice) |compaction| {
342
+ assert(env.pool.idle());
343
+ env.change_state(.fuzzing, .tree_compact);
344
+
345
+ switch (compaction.compaction_dispatch_enter(.{
346
+ .pool = &env.pool,
347
+ .callback = compact_callback,
348
+ })) {
349
+ .pending => env.tick_until_state_change(.tree_compact, .fuzzing),
350
+ .ready => env.change_state(.tree_compact, .fuzzing),
351
+ }
352
+ assert(env.pool.idle());
353
+ }
354
+
355
+ env.grid.forfeit(env.pool.grid_reservation.?);
356
+
357
+ if (last_beat or last_half_beat) {
358
+ assert(env.pool.blocks_acquired() == 0);
359
+
360
+ if (op >= constants.lsm_compaction_ops) {
361
+ env.change_state(.fuzzing, .manifest_log_compact);
362
+ env.manifest_log.compact(manifest_log_compact_callback, op);
363
+ env.tick_until_state_change(.manifest_log_compact, .fuzzing);
364
+ }
365
+
366
+ for (compactions_slice) |compaction| {
367
+ compaction.bar_complete();
368
+ }
369
+
370
+ if (op >= constants.lsm_compaction_ops) {
371
+ env.manifest_log.compact_end();
372
+ }
373
+ }
374
+
375
+ if (last_beat) {
376
+ env.tree.compact();
377
+ env.tree.swap_mutable_and_immutable(
378
+ snapshot_min_for_table_output(compaction_op_min(op)),
379
+ );
380
+
381
+ // Ensure tables haven't overflowed.
382
+ env.tree.manifest.assert_level_table_counts();
383
+ }
384
+ }
385
+
386
+ fn compact_callback(pool: *ResourcePool, _: u16, _: u64) void {
387
+ const env: *Environment = @alignCast(@fieldParentPtr("pool", pool));
388
+ env.change_state(.tree_compact, .fuzzing);
389
+ }
390
+
391
+ fn manifest_log_compact_callback(manifest_log: *ManifestLog) void {
392
+ const env: *Environment = @alignCast(@fieldParentPtr("manifest_log", manifest_log));
393
+ env.change_state(.manifest_log_compact, .fuzzing);
394
+ }
395
+
396
+ pub fn checkpoint(env: *Environment, op: u64) void {
397
+ env.tree.assert_between_bars();
398
+
399
+ env.change_state(.fuzzing, .grid_checkpoint);
400
+ env.grid.checkpoint(grid_checkpoint_callback);
401
+ env.tick_until_state_change(.grid_checkpoint, .superblock_checkpoint);
402
+
403
+ const checkpoint_op = op - constants.lsm_compaction_ops;
404
+ env.superblock.checkpoint(superblock_checkpoint_callback, &env.superblock_context, .{
405
+ .header = header: {
406
+ var header = vsr.Header.Prepare.root(fixtures.cluster);
407
+ header.op = checkpoint_op;
408
+ header.set_checksum();
409
+ break :header header;
410
+ },
411
+ .view_attributes = null,
412
+ .manifest_references = std.mem.zeroes(vsr.SuperBlockManifestReferences),
413
+ .free_set_references = .{
414
+ .blocks_acquired = env.grid
415
+ .free_set_checkpoint_blocks_acquired.checkpoint_reference(),
416
+ .blocks_released = env.grid
417
+ .free_set_checkpoint_blocks_released.checkpoint_reference(),
418
+ },
419
+ .client_sessions_reference = .{
420
+ .last_block_checksum = 0,
421
+ .last_block_address = 0,
422
+ .trailer_size = 0,
423
+ .checksum = vsr.checksum(&.{}),
424
+ },
425
+ .commit_max = checkpoint_op + 1,
426
+ .sync_op_min = 0,
427
+ .sync_op_max = 0,
428
+ .storage_size = vsr.superblock.data_file_size_min +
429
+ (env.grid.free_set.highest_address_acquired() orelse 0) * constants.block_size,
430
+ .release = vsr.Release.minimum,
431
+ });
432
+
433
+ env.tick_until_state_change(.superblock_checkpoint, .fuzzing);
434
+
435
+ // The fuzzer runs in a single process, all checkpoints are trivially durable. Use
436
+ // free_set.mark_checkpoint_durable() instead of grid.mark_checkpoint_durable(); the
437
+ // latter requires passing a callback, which is called synchronously in fuzzers anyway.
438
+ env.grid.mark_checkpoint_not_durable();
439
+ env.grid.free_set.mark_checkpoint_durable();
440
+ }
441
+
442
+ fn grid_checkpoint_callback(grid: *Grid) void {
443
+ const env: *Environment = @alignCast(@fieldParentPtr("grid", grid));
444
+ env.change_state(.grid_checkpoint, .superblock_checkpoint);
445
+ }
446
+
447
+ fn superblock_checkpoint_callback(superblock_context: *SuperBlock.Context) void {
448
+ const env: *Environment =
449
+ @alignCast(@fieldParentPtr("superblock_context", superblock_context));
450
+ env.change_state(.superblock_checkpoint, .fuzzing);
451
+ }
452
+
453
+ pub fn get(env: *Environment, key: u64, snapshot: u64) ?Value {
454
+ env.change_state(.fuzzing, .tree_lookup);
455
+ env.lookup_value = null;
456
+
457
+ if (env.tree.lookup_from_memory(key)) |value| {
458
+ get_callback(&env.lookup_context, Tree.unwrap_tombstone(value));
459
+ } else {
460
+ env.tree.lookup_from_levels_storage(.{
461
+ .callback = get_callback,
462
+ .context = &env.lookup_context,
463
+ .snapshot = snapshot,
464
+ .key = key,
465
+ .level_min = 0,
466
+ });
467
+ }
468
+
469
+ env.tick_until_state_change(.tree_lookup, .fuzzing);
470
+ return env.lookup_value;
471
+ }
472
+
473
+ fn get_callback(lookup_context: *Tree.LookupContext, value: ?*const Value) void {
474
+ const env: *Environment = @alignCast(@fieldParentPtr("lookup_context", lookup_context));
475
+ assert(env.lookup_value == null);
476
+ env.lookup_value = if (value) |val| val.* else null;
477
+ env.change_state(.tree_lookup, .fuzzing);
478
+ }
479
+
480
+ pub fn scan(
481
+ env: *Environment,
482
+ key_min: u64,
483
+ key_max: u64,
484
+ direction: Direction,
485
+ snapshot: u64,
486
+ ) []Value {
487
+ assert(key_min <= key_max);
488
+
489
+ env.change_state(.fuzzing, .scan_tree);
490
+ env.scan_tree = ScanTree.init(
491
+ &env.tree,
492
+ &env.scan_buffer,
493
+ snapshot,
494
+ key_min,
495
+ key_max,
496
+ direction,
497
+ );
498
+
499
+ env.scan_results_count = 0;
500
+ env.scan_tree.read(env, on_scan_read);
501
+ env.tick_until_state_change(.scan_tree, .fuzzing);
502
+
503
+ return env.scan_results[0..env.scan_results_count];
504
+ }
505
+
506
+ fn on_scan_read(env: *Environment, scan_tree: *ScanTree) void {
507
+ while (scan_tree.next() catch |err| switch (err) {
508
+ error.Pending => return scan_tree.read(env, on_scan_read),
509
+ }) |value| {
510
+ if (env.scan_results_count == scan_results_max) break;
511
+
512
+ env.scan_results[env.scan_results_count] = value;
513
+ env.scan_results_count += 1;
514
+ }
515
+
516
+ env.change_state(.scan_tree, .fuzzing);
517
+ }
518
+
519
+ pub fn apply(env: *Environment, gpa: std.mem.Allocator, fuzz_ops: []const FuzzOp) !void {
520
+ var model: Model = undefined;
521
+ try model.init(gpa, table_usage);
522
+ defer model.deinit(gpa);
523
+
524
+ var value_count: u64 = 0;
525
+ for (fuzz_ops, 0..) |fuzz_op, fuzz_op_index| {
526
+ assert(env.state == .fuzzing);
527
+ log.debug("Running fuzz_ops[{}/{}] == {}", .{
528
+ fuzz_op_index, fuzz_ops.len, fuzz_op,
529
+ });
530
+
531
+ const storage_size_used = env.storage.size_used();
532
+ log.debug("storage.size_used = {}/{}", .{ storage_size_used, env.storage.size });
533
+
534
+ const model_size = model.count() * @sizeOf(Value);
535
+ log.debug("space_amplification = {d:.2}", .{
536
+ @as(f64, @floatFromInt(storage_size_used)) /
537
+ @as(f64, @floatFromInt(model_size)),
538
+ });
539
+
540
+ // If the fuzzer manages to fill up the whole tree (unlikely, but possible), it
541
+ // continues to only issue read operations.
542
+ const tree_full = tree_full: {
543
+ const last_level = &env.tree.manifest.levels[constants.lsm_levels - 1];
544
+ const last_level_tables_max =
545
+ std.math.pow(u32, constants.lsm_growth_factor, constants.lsm_levels);
546
+ if (last_level.table_count_visible == last_level_tables_max) {
547
+ // Sanity-check that this doesn't happen after too few ops.
548
+ assert(value_count >= last_level_tables_max);
549
+ break :tree_full true;
550
+ } else {
551
+ break :tree_full false;
552
+ }
553
+ };
554
+
555
+ // Apply fuzz_op to the tree and the model.
556
+ switch (fuzz_op) {
557
+ .compact => |c| {
558
+ if (tree_full) continue;
559
+ env.compact(c.op, c.beats_remaining);
560
+ if (c.checkpoint) env.checkpoint(c.op);
561
+ },
562
+ .put => |value| {
563
+ if (tree_full) continue;
564
+ value_count += 2;
565
+ if (table_usage == .secondary_index) {
566
+ // Secondary index requires that the key implies the value (typically
567
+ // key ≡ value), and that there are no updates.
568
+ const canonical_value: Value = .{
569
+ .id = value.id,
570
+ .value = 0,
571
+ .tombstone_bit = value.tombstone_bit,
572
+ };
573
+ if (model.contains(&canonical_value)) {
574
+ env.tree.remove(&canonical_value);
575
+ }
576
+ env.tree.put(&canonical_value);
577
+ try model.put(&canonical_value);
578
+ } else {
579
+ env.tree.put(&value);
580
+ try model.put(&value);
581
+ }
582
+ },
583
+ .remove => |value| {
584
+ if (tree_full) continue;
585
+ value_count += 1;
586
+ if (table_usage == .secondary_index and !model.contains(&value)) {
587
+ // Not allowed to remove non-present keys
588
+ } else {
589
+ env.tree.remove(&value);
590
+ model.remove(&value);
591
+ }
592
+ },
593
+ .get => |key| {
594
+ // Get account from lsm.
595
+ const tree_value = env.get(key, fuzz_op_index);
596
+
597
+ // Compare result to model.
598
+ const model_value = model.get(key);
599
+ if (model_value == null) {
600
+ assert(tree_value == null);
601
+ } else {
602
+ assert(stdx.equal_bytes(Value, &model_value.?, &tree_value.?));
603
+ }
604
+ },
605
+ .scan => |scan_range| try env.apply_scan(&model, scan_range),
606
+ }
607
+ }
608
+ }
609
+
610
+ fn apply_scan(env: *Environment, model: *const Model, scan_range: FuzzOp.Scan) !void {
611
+ assert(scan_range.min <= scan_range.max);
612
+
613
+ const tree_values = env.scan(
614
+ scan_range.min,
615
+ scan_range.max,
616
+ scan_range.direction,
617
+ scan_range.snapshot,
618
+ );
619
+
620
+ const model_values = model.scan(
621
+ scan_range.min,
622
+ scan_range.max,
623
+ scan_range.direction,
624
+ env.scan_results_model,
625
+ );
626
+
627
+ // Unlike the model, the tree can return some amount of tombstones in the result set.
628
+ // They must be filtered out before comparison!
629
+ var tombstone_count: usize = 0;
630
+ for (tree_values, 0..) |tree_value, index| {
631
+ assert(scan_range.min <= Table.key_from_value(&tree_value));
632
+ assert(Table.key_from_value(&tree_value) <= scan_range.max);
633
+ if (Table.tombstone(&tree_value)) {
634
+ tombstone_count += 1;
635
+ } else {
636
+ if (tombstone_count > 0) {
637
+ tree_values[index - tombstone_count] = tree_value;
638
+ }
639
+ }
640
+ }
641
+
642
+ const tombstone_evicted = (model_values.len + tombstone_count) -| scan_results_max;
643
+ try testing.expectEqualSlices(
644
+ Value,
645
+ tree_values[0 .. tree_values.len - tombstone_count],
646
+ model_values[0 .. model_values.len - tombstone_evicted],
647
+ );
648
+ assert(tree_values.len >= model_values.len);
649
+ }
650
+ };
651
+ }
652
+
653
+ // A tree is a sorted set. The ideal model would have been an in-memory B-tree, but there isn't
654
+ // one in Zig's standard library. Use a SortedSegmentedArray instead which is essentially a stunted
655
+ // B-tree one-level deep.
656
+ const Model = struct {
657
+ const Array = SortedSegmentedArrayType(
658
+ Value,
659
+ NodePool,
660
+ events_max,
661
+ u64,
662
+ Value.key_from_value,
663
+ .{ .verify = false },
664
+ );
665
+
666
+ table_usage: TableUsage,
667
+ node_pool: NodePool,
668
+ array: Array,
669
+
670
+ fn init(model: *Model, gpa: std.mem.Allocator, table_usage: TableUsage) !void {
671
+ model.* = .{
672
+ .table_usage = table_usage,
673
+
674
+ .node_pool = undefined,
675
+ .array = undefined,
676
+ };
677
+
678
+ const model_node_count = stdx.div_ceil(
679
+ events_max * @sizeOf(Value),
680
+ NodePool.node_size,
681
+ );
682
+
683
+ try model.node_pool.init(gpa, model_node_count);
684
+ errdefer model.node_pool.deinit(gpa);
685
+
686
+ model.array = try Array.init(gpa);
687
+ errdefer model.array.deinit(gpa, &model.node_pool);
688
+ }
689
+
690
+ fn deinit(model: *Model, gpa: std.mem.Allocator) void {
691
+ model.array.deinit(gpa, &model.node_pool);
692
+ model.node_pool.deinit(gpa);
693
+ model.* = undefined;
694
+ }
695
+
696
+ fn count(model: *const Model) u32 {
697
+ return model.array.len();
698
+ }
699
+
700
+ fn contains(model: *Model, value: *const Value) bool {
701
+ return model.get(Value.key_from_value(value)) != null;
702
+ }
703
+
704
+ fn get(model: *const Model, key: u64) ?Value {
705
+ const cursor = model.array.search(key);
706
+ if (cursor.node == model.array.node_count) return null;
707
+ if (cursor.relative_index == model.array.node_elements(cursor.node).len) return null;
708
+ const cursor_element = model.array.element_at_cursor(cursor);
709
+ if (Value.key_from_value(&cursor_element) == key) {
710
+ return cursor_element;
711
+ } else {
712
+ return null;
713
+ }
714
+ }
715
+
716
+ fn scan(
717
+ model: *const Model,
718
+ key_min: u64,
719
+ key_max: u64,
720
+ direction: Direction,
721
+ result: []Value,
722
+ ) []Value {
723
+ var result_count: usize = 0;
724
+ switch (direction) {
725
+ .ascending => {
726
+ const cursor = model.array.search(key_min);
727
+ var it = model.array.iterator_from_cursor(cursor, .ascending);
728
+ while (it.next()) |element| {
729
+ const element_key = Value.key_from_value(element);
730
+ if (element_key <= key_max) {
731
+ assert(element_key >= key_min);
732
+ result[result_count] = element.*;
733
+ result_count += 1;
734
+ if (result_count == result.len) break;
735
+ } else {
736
+ break;
737
+ }
738
+ }
739
+ },
740
+ .descending => {
741
+ const cursor = model.array.search(key_max);
742
+ var it = model.array.iterator_from_cursor(cursor, .descending);
743
+ while (it.next()) |element| {
744
+ const element_key = Value.key_from_value(element);
745
+ if (element_key >= key_min) {
746
+ if (element_key <= key_max) {
747
+ result[result_count] = element.*;
748
+ result_count += 1;
749
+ if (result_count == result.len) break;
750
+ }
751
+ } else {
752
+ break;
753
+ }
754
+ }
755
+ },
756
+ }
757
+ return result[0..result_count];
758
+ }
759
+
760
+ fn put(model: *Model, value: *const Value) !void {
761
+ model.remove(value);
762
+ _ = model.array.insert_element(&model.node_pool, value.*);
763
+ }
764
+
765
+ fn remove(model: *Model, value: *const Value) void {
766
+ const key = Value.key_from_value(value);
767
+ const cursor = model.array.search(key);
768
+ if (cursor.node == model.array.node_count) return;
769
+ if (cursor.relative_index == model.array.node_elements(cursor.node).len) return;
770
+
771
+ if (Value.key_from_value(&model.array.element_at_cursor(cursor)) == key) {
772
+ model.array.remove_elements(
773
+ &model.node_pool,
774
+ model.array.absolute_index_for_cursor(cursor),
775
+ 1,
776
+ );
777
+ }
778
+ }
779
+ };
780
+
781
+ fn random_id(prng: *stdx.PRNG) u64 {
782
+ return fuzz.random_id(prng, u64, .{
783
+ .average_hot = 8,
784
+ .average_cold = value_count_max * table_count_max,
785
+ });
786
+ }
787
+
788
+ pub fn generate_fuzz_ops(
789
+ gpa: std.mem.Allocator,
790
+ prng: *stdx.PRNG,
791
+ fuzz_op_count: usize,
792
+ ) ![]const FuzzOp {
793
+ log.info("fuzz_op_count = {}", .{fuzz_op_count});
794
+
795
+ const fuzz_ops = try gpa.alloc(FuzzOp, fuzz_op_count);
796
+ errdefer gpa.free(fuzz_ops);
797
+
798
+ const fuzz_op_weights = stdx.PRNG.EnumWeightsType(FuzzOpTag){
799
+ // Maybe compact more often than forced to by `puts_since_compact`.
800
+ .compact = if (prng.boolean()) 0 else 1,
801
+ // Always do puts, and always more puts than removes.
802
+ .put = constants.lsm_compaction_ops * 2,
803
+ // Maybe do some removes.
804
+ .remove = if (prng.boolean()) 0 else constants.lsm_compaction_ops,
805
+ // Maybe do some gets.
806
+ .get = if (prng.boolean()) 0 else constants.lsm_compaction_ops,
807
+ // Maybe do some scans.
808
+ .scan = if (prng.boolean()) 0 else constants.lsm_compaction_ops,
809
+ };
810
+ log.info("fuzz_op_weights = {:.2}", .{fuzz_op_weights});
811
+
812
+ log.info("puts_since_compact_max = {}", .{puts_since_compact_max});
813
+
814
+ var op: u64 = 1;
815
+ var persisted_op: u64 = 0;
816
+ var puts_since_compact: usize = 0;
817
+ for (fuzz_ops) |*fuzz_op| {
818
+ const fuzz_op_tag = if (puts_since_compact >= puts_since_compact_max)
819
+ // We have to compact before doing any other operations.
820
+ FuzzOpTag.compact
821
+ else
822
+ // Otherwise pick a random FuzzOp.
823
+ prng.enum_weighted(FuzzOpTag, fuzz_op_weights);
824
+ fuzz_op.* = switch (fuzz_op_tag) {
825
+ .compact => action: {
826
+ const action = generate_compact(prng, .{
827
+ .op = op,
828
+ .persisted_op = persisted_op,
829
+ });
830
+ if (action.compact.checkpoint) {
831
+ persisted_op = op - constants.lsm_compaction_ops;
832
+ }
833
+ op += 1;
834
+ break :action action;
835
+ },
836
+ .put => FuzzOp{ .put = .{
837
+ .id = random_id(prng),
838
+ .value = prng.int(u63),
839
+ } },
840
+ .remove => FuzzOp{ .remove = .{
841
+ .id = random_id(prng),
842
+ .value = prng.int(u63),
843
+ } },
844
+ .get => FuzzOp{ .get = random_id(prng) },
845
+ .scan => blk: {
846
+ const min = random_id(prng);
847
+ const max = min + random_id(prng);
848
+ const direction = prng.enum_uniform(Direction);
849
+ assert(min <= max);
850
+ break :blk FuzzOp{
851
+ .scan = .{
852
+ .min = min,
853
+ .max = max,
854
+ .direction = direction,
855
+ .snapshot = op,
856
+ },
857
+ };
858
+ },
859
+ };
860
+ switch (fuzz_op.*) {
861
+ .compact => puts_since_compact = 0,
862
+ // Tree.remove() works by inserting a tombstone, so it counts as a put.
863
+ .put, .remove => puts_since_compact += 1,
864
+ .get, .scan => {},
865
+ }
866
+ }
867
+
868
+ return fuzz_ops;
869
+ }
870
+
871
+ fn generate_compact(
872
+ prng: *stdx.PRNG,
873
+ options: struct { op: u64, persisted_op: u64 },
874
+ ) FuzzOp {
875
+ const half_bar = @divExact(constants.lsm_compaction_ops, 2);
876
+ const checkpoint =
877
+ // Can only checkpoint on the last beat of the bar.
878
+ options.op % constants.lsm_compaction_ops == constants.lsm_compaction_ops - 1 and
879
+ options.op > constants.lsm_compaction_ops and
880
+ // Checkpoint at the normal rate.
881
+ // TODO Make LSM (and this fuzzer) unaware of VSR's checkpoint schedule.
882
+ options.op == vsr.Checkpoint.trigger_for_checkpoint(
883
+ vsr.Checkpoint.checkpoint_after(options.persisted_op),
884
+ );
885
+ return FuzzOp{ .compact = .{
886
+ .op = options.op,
887
+ .checkpoint = checkpoint,
888
+ .beats_remaining = prng.range_inclusive(u64, 1, half_bar - options.op % half_bar),
889
+ } };
890
+ }
891
+
892
+ pub fn main(gpa: std.mem.Allocator, fuzz_args: fuzz.FuzzArgs) !void {
893
+ var prng = stdx.PRNG.from_seed(fuzz_args.seed);
894
+
895
+ const table_usage = prng.enum_uniform(TableUsage);
896
+ log.info("table_usage={}", .{table_usage});
897
+
898
+ const block_count_min =
899
+ stdx.div_ceil(constants.lsm_levels, 2) * compaction_block_count_beat_min;
900
+
901
+ const block_count = if (prng.chance(ratio(1, 5)))
902
+ block_count_min
903
+ else
904
+ prng.range_inclusive(u32, block_count_min, block_count_min * 16);
905
+
906
+ const fuzz_op_count = @min(
907
+ fuzz_args.events_max orelse events_max,
908
+ fuzz.random_int_exponential(&prng, usize, 1E6),
909
+ );
910
+
911
+ const fuzz_ops = try generate_fuzz_ops(gpa, &prng, fuzz_op_count);
912
+ defer gpa.free(fuzz_ops);
913
+
914
+ var storage = try fixtures.init_storage(gpa, .{
915
+ .size = constants.storage_size_limit_default,
916
+ .seed = prng.int(u64),
917
+ .read_latency_min = .{ .ns = 0 },
918
+ .read_latency_mean = fuzz.range_inclusive_ms(&prng, 0, 200),
919
+ .write_latency_min = .{ .ns = 0 },
920
+ .write_latency_mean = fuzz.range_inclusive_ms(&prng, 0, 200),
921
+ });
922
+ defer storage.deinit(gpa);
923
+
924
+ try fixtures.storage_format(gpa, &storage, .{});
925
+
926
+ switch (table_usage) {
927
+ inline else => |usage| {
928
+ try EnvironmentType(usage).run(gpa, &storage, block_count, fuzz_ops);
929
+ },
930
+ }
931
+
932
+ log.info("Passed!", .{});
933
+ }