tigerbeetle 0.0.36 → 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.
- checksums.yaml +4 -4
- data/ext/tb_client/extconf.rb +13 -13
- data/ext/tb_client/tigerbeetle/LICENSE +177 -0
- data/ext/tb_client/tigerbeetle/build.zig +2327 -0
- data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
- data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
- data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
- data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
- data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
- data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
- data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
- data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
- data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
- data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
- data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
- data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
- data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
- data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
- data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
- data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
- data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
- data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
- data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
- data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
- data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
- data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
- data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
- data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
- data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
- data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
- data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
- data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
- data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
- data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
- data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
- data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
- data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
- data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
- data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
- data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
- data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
- data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
- data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
- data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
- data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
- data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
- data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
- data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
- data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
- data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
- data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
- data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
- data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
- data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
- data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
- data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
- data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
- data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
- data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
- data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
- data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
- data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
- data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
- data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
- data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
- data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
- data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
- data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
- data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
- data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
- data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
- data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
- data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
- data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
- data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
- data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
- data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
- data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
- data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
- data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
- data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
- data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
- data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
- data/lib/tb_client/shared_lib.rb +12 -5
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +1 -1
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,1381 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const assert = std.debug.assert;
|
|
3
|
+
const mem = std.mem;
|
|
4
|
+
|
|
5
|
+
const DynamicBitSetUnmanaged = std.bit_set.DynamicBitSetUnmanaged;
|
|
6
|
+
const MaskInt = DynamicBitSetUnmanaged.MaskInt;
|
|
7
|
+
|
|
8
|
+
const vsr = @import("../vsr.zig");
|
|
9
|
+
const stdx = vsr.stdx;
|
|
10
|
+
const KiB = stdx.KiB;
|
|
11
|
+
const ewah = vsr.ewah(FreeSet.Word);
|
|
12
|
+
const constants = vsr.constants;
|
|
13
|
+
|
|
14
|
+
const div_ceil = stdx.div_ceil;
|
|
15
|
+
const maybe = stdx.maybe;
|
|
16
|
+
|
|
17
|
+
/// This is logically a range of addresses within the FreeSet, but its actual fields are block
|
|
18
|
+
/// indexes for ease of calculation.
|
|
19
|
+
///
|
|
20
|
+
/// A reservation covers a range of both free and acquired blocks — when it is first created,
|
|
21
|
+
/// it is guaranteed to cover exactly as many free blocks as were requested by `reserve()`.
|
|
22
|
+
pub const Reservation = struct {
|
|
23
|
+
block_base: usize,
|
|
24
|
+
block_count: usize,
|
|
25
|
+
/// An identifier for each reservation cycle, to verify that old reservations are not reused.
|
|
26
|
+
session: usize,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/// The 0 address is reserved for usage as a sentinel and will never be returned by acquire().
|
|
30
|
+
///
|
|
31
|
+
/// Concurrent callers must reserve free blocks before acquiring them to ensure that
|
|
32
|
+
/// acquisition order is deterministic despite concurrent jobs acquiring blocks in
|
|
33
|
+
/// nondeterministic order.
|
|
34
|
+
///
|
|
35
|
+
/// The reservation lifecycle is:
|
|
36
|
+
///
|
|
37
|
+
/// 1. Reserve: In deterministic order, each job (e.g. compaction) calls `reserve()` to
|
|
38
|
+
/// reserve the upper bound of blocks that it may need to acquire to complete.
|
|
39
|
+
/// 2. Acquire: The jobs run concurrently. Each job acquires blocks only from its respective
|
|
40
|
+
/// reservation (via `acquire()`).
|
|
41
|
+
/// 3. Forfeit: When a job finishes, it calls `forfeit()` to drop its reservation.
|
|
42
|
+
/// 4. Done: When all pending reservations are forfeited, the reserved (but unacquired) space
|
|
43
|
+
/// is reclaimed.
|
|
44
|
+
///
|
|
45
|
+
pub const FreeSet = struct {
|
|
46
|
+
pub const Word = u64;
|
|
47
|
+
pub const BitsetKind = enum {
|
|
48
|
+
blocks_acquired,
|
|
49
|
+
blocks_released,
|
|
50
|
+
};
|
|
51
|
+
const BlocksReleasedPriorCheckpointDurability = std.AutoArrayHashMapUnmanaged(u64, void);
|
|
52
|
+
|
|
53
|
+
// Free set is stored in the grid (see `CheckpointTrailer`) and is not available until the
|
|
54
|
+
// relevant blocks are fetched from disk (or other replicas) and decoded.
|
|
55
|
+
//
|
|
56
|
+
// Without the free set, only blocks belonging to the free set might be read and no blocks can
|
|
57
|
+
// be written.
|
|
58
|
+
opened: bool = false,
|
|
59
|
+
|
|
60
|
+
/// Whether the current checkpoint is durable.
|
|
61
|
+
checkpoint_durable: bool = false,
|
|
62
|
+
|
|
63
|
+
/// If a shard has any free blocks, the corresponding index bit is zero.
|
|
64
|
+
/// If a shard has no free blocks, the corresponding index bit is one.
|
|
65
|
+
index: DynamicBitSetUnmanaged,
|
|
66
|
+
|
|
67
|
+
/// The maximum number of blocks the free set is allowed to reserve (driven by --limit-storage).
|
|
68
|
+
blocks_count_limit: u64,
|
|
69
|
+
|
|
70
|
+
/// Set bits indicate acquired blocks; unset bits indicate free blocks.
|
|
71
|
+
blocks_acquired: DynamicBitSetUnmanaged,
|
|
72
|
+
|
|
73
|
+
/// Set bits indicate blocks released in the current checkpoint, to be freed when the next
|
|
74
|
+
/// checkpoint becomes durable.
|
|
75
|
+
blocks_released: DynamicBitSetUnmanaged,
|
|
76
|
+
|
|
77
|
+
/// Temporarily holds blocks released prior durability of the current checkpoint, to be freed
|
|
78
|
+
/// when the next checkpoint becomes durable. These blocks are moved to blocks_released once the
|
|
79
|
+
/// current checkpoint becomes durable.
|
|
80
|
+
blocks_released_prior_checkpoint_durability: BlocksReleasedPriorCheckpointDurability,
|
|
81
|
+
|
|
82
|
+
/// The number of blocks that are reserved, counting both acquired and free blocks
|
|
83
|
+
/// from the start of `blocks_acquired`.
|
|
84
|
+
/// Alternatively, the index of the first non-reserved block in `blocks_acquired`.
|
|
85
|
+
reservation_blocks: usize = 0,
|
|
86
|
+
|
|
87
|
+
/// The number of active reservations.
|
|
88
|
+
reservation_count: usize = 0,
|
|
89
|
+
|
|
90
|
+
/// Verify that when the caller transitions from creating reservations to forfeiting them,
|
|
91
|
+
/// all reservations must be forfeited before additional reservations are made.
|
|
92
|
+
reservation_state: enum {
|
|
93
|
+
reserving,
|
|
94
|
+
forfeiting,
|
|
95
|
+
} = .reserving,
|
|
96
|
+
|
|
97
|
+
/// Verifies that reservations are not allocated from or forfeited when they should not be.
|
|
98
|
+
reservation_session: usize = 1,
|
|
99
|
+
|
|
100
|
+
// Each shard is 8 cache lines because the CPU line fill buffer can fetch 10 lines in parallel.
|
|
101
|
+
// And 8 is fast for division when computing the shard of a block.
|
|
102
|
+
// Since the shard is scanned sequentially, the prefetching amortizes the cost of the single
|
|
103
|
+
// cache miss. It also reduces the size of the index.
|
|
104
|
+
//
|
|
105
|
+
// e.g. 10TiB disk ÷ 64KiB/block ÷ 512*8 blocks/shard ÷ 8 shards/byte = 5120B index
|
|
106
|
+
const shard_cache_lines = 8;
|
|
107
|
+
pub const shard_bits = shard_cache_lines * constants.cache_line_size * @bitSizeOf(u8);
|
|
108
|
+
comptime {
|
|
109
|
+
assert(shard_bits == 4096);
|
|
110
|
+
assert(@bitSizeOf(MaskInt) == 64);
|
|
111
|
+
// Ensure there are no wasted padding bits at the end of the index.
|
|
112
|
+
assert(shard_bits % @bitSizeOf(MaskInt) == 0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pub fn init(allocator: mem.Allocator, options: struct {
|
|
116
|
+
grid_size_limit: usize,
|
|
117
|
+
blocks_released_prior_checkpoint_durability_max: usize,
|
|
118
|
+
}) !FreeSet {
|
|
119
|
+
const blocks_count = block_count_max(options.grid_size_limit);
|
|
120
|
+
assert(blocks_count % shard_bits == 0);
|
|
121
|
+
assert(blocks_count % @bitSizeOf(Word) == 0);
|
|
122
|
+
|
|
123
|
+
// Every block bit is covered by exactly one index bit.
|
|
124
|
+
const shards_count = @divExact(blocks_count, shard_bits);
|
|
125
|
+
var index = try DynamicBitSetUnmanaged.initEmpty(allocator, shards_count);
|
|
126
|
+
errdefer index.deinit(allocator);
|
|
127
|
+
|
|
128
|
+
var blocks_acquired = try DynamicBitSetUnmanaged.initEmpty(allocator, blocks_count);
|
|
129
|
+
errdefer blocks_acquired.deinit(allocator);
|
|
130
|
+
|
|
131
|
+
var blocks_released = try DynamicBitSetUnmanaged.initEmpty(allocator, blocks_count);
|
|
132
|
+
errdefer blocks_released.deinit(allocator);
|
|
133
|
+
|
|
134
|
+
var released_prior_checkpoint_durability: BlocksReleasedPriorCheckpointDurability = .{};
|
|
135
|
+
try released_prior_checkpoint_durability.ensureTotalCapacity(
|
|
136
|
+
allocator,
|
|
137
|
+
options.blocks_released_prior_checkpoint_durability_max +
|
|
138
|
+
// `blocks_released` and `blocks_acquired` encoded in the CheckpointTrailer are
|
|
139
|
+
// released at checkpoint (see `mark_checkpoint_not_durable` in grid.zig).
|
|
140
|
+
2 * vsr.checkpoint_trailer.block_count_for_trailer_size(
|
|
141
|
+
ewah.encode_size_max(blocks_count),
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
errdefer released_prior_checkpoint_durability.deinit();
|
|
145
|
+
|
|
146
|
+
assert(index.count() == 0);
|
|
147
|
+
assert(blocks_acquired.count() == 0);
|
|
148
|
+
assert(blocks_released.count() == 0);
|
|
149
|
+
assert(released_prior_checkpoint_durability.count() == 0);
|
|
150
|
+
|
|
151
|
+
return FreeSet{
|
|
152
|
+
.index = index,
|
|
153
|
+
.blocks_count_limit = @divFloor(options.grid_size_limit, constants.block_size),
|
|
154
|
+
.blocks_acquired = blocks_acquired,
|
|
155
|
+
.blocks_released = blocks_released,
|
|
156
|
+
.blocks_released_prior_checkpoint_durability = released_prior_checkpoint_durability,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
pub fn deinit(set: *FreeSet, allocator: mem.Allocator) void {
|
|
160
|
+
set.index.deinit(allocator);
|
|
161
|
+
set.blocks_acquired.deinit(allocator);
|
|
162
|
+
set.blocks_released.deinit(allocator);
|
|
163
|
+
set.blocks_released_prior_checkpoint_durability.deinit(allocator);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
pub fn reset(set: *FreeSet) void {
|
|
167
|
+
for ([_]*DynamicBitSetUnmanaged{
|
|
168
|
+
&set.index,
|
|
169
|
+
&set.blocks_acquired,
|
|
170
|
+
&set.blocks_released,
|
|
171
|
+
}) |bitset| {
|
|
172
|
+
var it = bitset.iterator(.{});
|
|
173
|
+
while (it.next()) |bit| bitset.unset(bit);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
set.blocks_released_prior_checkpoint_durability.clearRetainingCapacity();
|
|
177
|
+
|
|
178
|
+
set.* = .{
|
|
179
|
+
.index = set.index,
|
|
180
|
+
.blocks_count_limit = set.blocks_count_limit,
|
|
181
|
+
.blocks_acquired = set.blocks_acquired,
|
|
182
|
+
.blocks_released = set.blocks_released,
|
|
183
|
+
.blocks_released_prior_checkpoint_durability = set
|
|
184
|
+
.blocks_released_prior_checkpoint_durability,
|
|
185
|
+
.reservation_session = set.reservation_session +% 1,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
assert(set.index.count() == 0);
|
|
189
|
+
assert(set.blocks_acquired.count() == 0);
|
|
190
|
+
assert(set.blocks_released.count() == 0);
|
|
191
|
+
assert(set.blocks_released_prior_checkpoint_durability.count() == 0);
|
|
192
|
+
|
|
193
|
+
assert(!set.opened);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// Opens a free set. Needs two inputs:
|
|
197
|
+
///
|
|
198
|
+
/// - the byte buffers with the ewah-encoded acquired and released bitsets,
|
|
199
|
+
/// - the list of block addresses used to store both the encoded bitsets in the grid.
|
|
200
|
+
///
|
|
201
|
+
/// Block addresses themselves are not a part of the encoded bitset for acquired blocks,
|
|
202
|
+
/// see CheckpointTrailer for details.
|
|
203
|
+
pub fn open(set: *FreeSet, options: struct {
|
|
204
|
+
encoded: struct {
|
|
205
|
+
blocks_acquired: []const []align(@alignOf(Word)) const u8,
|
|
206
|
+
blocks_released: []const []align(@alignOf(Word)) const u8,
|
|
207
|
+
},
|
|
208
|
+
free_set_block_addresses: struct {
|
|
209
|
+
blocks_acquired: []const u64,
|
|
210
|
+
blocks_released: []const u64,
|
|
211
|
+
},
|
|
212
|
+
}) void {
|
|
213
|
+
assert(!set.opened);
|
|
214
|
+
assert((options.encoded.blocks_acquired.len == 0 and
|
|
215
|
+
options.encoded.blocks_released.len == 0) ==
|
|
216
|
+
(options.free_set_block_addresses.blocks_acquired.len == 0 and
|
|
217
|
+
options.free_set_block_addresses.blocks_released.len == 0));
|
|
218
|
+
set.decode_chunks(
|
|
219
|
+
options.encoded.blocks_acquired,
|
|
220
|
+
options.encoded.blocks_released,
|
|
221
|
+
);
|
|
222
|
+
set.mark_released(options.free_set_block_addresses.blocks_acquired);
|
|
223
|
+
set.mark_released(options.free_set_block_addresses.blocks_released);
|
|
224
|
+
set.opened = true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// A shortcut to initialize an empty free set for tests.
|
|
228
|
+
pub fn init_empty(allocator: mem.Allocator, blocks_count: usize) !FreeSet {
|
|
229
|
+
comptime assert(constants.verify);
|
|
230
|
+
var set = try init(allocator, .{
|
|
231
|
+
.grid_size_limit = blocks_count * constants.block_size,
|
|
232
|
+
.blocks_released_prior_checkpoint_durability_max = 0,
|
|
233
|
+
});
|
|
234
|
+
errdefer set.deinit(allocator);
|
|
235
|
+
|
|
236
|
+
assert(!set.opened);
|
|
237
|
+
assert(!set.checkpoint_durable);
|
|
238
|
+
return set;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// A shortcut to initialize and open an empty free set for tests.
|
|
242
|
+
pub fn open_empty(allocator: mem.Allocator, blocks_count: usize) !FreeSet {
|
|
243
|
+
comptime assert(constants.verify);
|
|
244
|
+
var set = try init(allocator, .{
|
|
245
|
+
.grid_size_limit = blocks_count * constants.block_size,
|
|
246
|
+
.blocks_released_prior_checkpoint_durability_max = 0,
|
|
247
|
+
});
|
|
248
|
+
errdefer set.deinit(allocator);
|
|
249
|
+
|
|
250
|
+
set.open(.{
|
|
251
|
+
.encoded = .{ .blocks_acquired = &.{}, .blocks_released = &.{} },
|
|
252
|
+
.free_set_block_addresses = .{ .blocks_acquired = &.{}, .blocks_released = &.{} },
|
|
253
|
+
});
|
|
254
|
+
// Mark checkpoint as durable so tests use blocks_released for block releases.
|
|
255
|
+
// blocks_released_prior_checkpoint_durable is required to ensure correctness across
|
|
256
|
+
// multiple replicas, while tests check the following flows in a single process:
|
|
257
|
+
// * Block acquisition-release
|
|
258
|
+
// * Bitset encoding-decoding
|
|
259
|
+
set.checkpoint_durable = true;
|
|
260
|
+
|
|
261
|
+
assert(set.opened);
|
|
262
|
+
assert(set.count_free() == blocks_count);
|
|
263
|
+
assert(set.count_released() == 0);
|
|
264
|
+
return set;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fn verify_index(set: *const FreeSet) void {
|
|
268
|
+
for (0..set.index.bit_length) |shard| {
|
|
269
|
+
assert((set.find_free_block_in_shard(shard) == null) == set.index.isSet(shard));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// Returns the number of active reservations.
|
|
274
|
+
pub fn count_reservations(set: FreeSet) usize {
|
|
275
|
+
assert(set.opened);
|
|
276
|
+
return set.reservation_count;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// Returns the number of free blocks.
|
|
280
|
+
pub fn count_free(set: FreeSet) usize {
|
|
281
|
+
assert(set.opened);
|
|
282
|
+
return set.blocks_acquired.capacity() - set.blocks_acquired.count();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Returns the number of acquired blocks.
|
|
286
|
+
pub fn count_acquired(set: FreeSet) usize {
|
|
287
|
+
assert(set.opened);
|
|
288
|
+
return set.blocks_acquired.count();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// Returns the number of released blocks.
|
|
292
|
+
pub fn count_released(set: FreeSet) usize {
|
|
293
|
+
assert(set.opened);
|
|
294
|
+
return set.blocks_released.count() +
|
|
295
|
+
set.blocks_released_prior_checkpoint_durability.count();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Returns the address of the highest acquired block.
|
|
299
|
+
pub fn highest_address_acquired(set: FreeSet) ?u64 {
|
|
300
|
+
assert(set.opened);
|
|
301
|
+
var it = set.blocks_acquired.iterator(.{
|
|
302
|
+
.kind = .set,
|
|
303
|
+
.direction = .reverse,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (it.next()) |block| {
|
|
307
|
+
const address = block + 1;
|
|
308
|
+
return address;
|
|
309
|
+
} else {
|
|
310
|
+
// All blocks are free.
|
|
311
|
+
assert(set.blocks_acquired.count() == 0);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// Returns the address of the highest released block.
|
|
317
|
+
pub fn highest_address_released(set: FreeSet) ?u64 {
|
|
318
|
+
assert(set.opened);
|
|
319
|
+
var it = set.blocks_released.iterator(.{
|
|
320
|
+
.kind = .set,
|
|
321
|
+
.direction = .reverse,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (it.next()) |block| {
|
|
325
|
+
const address = block + 1;
|
|
326
|
+
return address;
|
|
327
|
+
} else {
|
|
328
|
+
assert(set.count_released() == 0);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// Reserve `reserve_count` free blocks. The blocks are not acquired yet.
|
|
334
|
+
///
|
|
335
|
+
/// Invariants:
|
|
336
|
+
///
|
|
337
|
+
/// - If a reservation is returned, it covers exactly `reserve_count` free blocks, along with
|
|
338
|
+
/// any interleaved already-acquired blocks.
|
|
339
|
+
/// - Active reservations are exclusive (i.e. disjoint).
|
|
340
|
+
/// (A reservation is active until `forfeit()` is called.)
|
|
341
|
+
///
|
|
342
|
+
/// Returns null if there are not enough blocks free and vacant.
|
|
343
|
+
/// Returns a reservation which can be used with `acquire()`:
|
|
344
|
+
/// - The caller should consider the returned Reservation as opaque and immutable.
|
|
345
|
+
/// - Each `reserve()` call which returns a non-null Reservation must correspond to exactly one
|
|
346
|
+
/// `forfeit()` call.
|
|
347
|
+
pub fn reserve(set: *FreeSet, reserve_count: usize) ?Reservation {
|
|
348
|
+
assert(set.opened);
|
|
349
|
+
assert(set.reservation_state == .reserving);
|
|
350
|
+
assert(reserve_count > 0);
|
|
351
|
+
|
|
352
|
+
const shard_start = find_bit(
|
|
353
|
+
set.index,
|
|
354
|
+
@divFloor(set.reservation_blocks, shard_bits),
|
|
355
|
+
set.index.bit_length,
|
|
356
|
+
.unset,
|
|
357
|
+
) orelse return null;
|
|
358
|
+
|
|
359
|
+
// The reservation may cover (and ignore) already-acquired blocks due to fragmentation.
|
|
360
|
+
var block = @max(shard_start * shard_bits, set.reservation_blocks);
|
|
361
|
+
for (0..reserve_count) |_| {
|
|
362
|
+
block = 1 + (find_bit(
|
|
363
|
+
set.blocks_acquired,
|
|
364
|
+
block,
|
|
365
|
+
set.blocks_acquired.bit_length,
|
|
366
|
+
.unset,
|
|
367
|
+
) orelse return null);
|
|
368
|
+
|
|
369
|
+
// The free block from the `blocks_acquired` bit set may be past the total number of
|
|
370
|
+
// blocks that this free set is allowed to acquire (see `block_count_max`).
|
|
371
|
+
if (block > set.blocks_count_limit) return null;
|
|
372
|
+
}
|
|
373
|
+
const block_base = set.reservation_blocks;
|
|
374
|
+
const block_count = block - set.reservation_blocks;
|
|
375
|
+
set.reservation_blocks += block_count;
|
|
376
|
+
set.reservation_count += 1;
|
|
377
|
+
|
|
378
|
+
return Reservation{
|
|
379
|
+
.block_base = block_base,
|
|
380
|
+
.block_count = block_count,
|
|
381
|
+
.session = set.reservation_session,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// After invoking `forfeit()`, the reservation must never be used again.
|
|
386
|
+
pub fn forfeit(set: *FreeSet, reservation: Reservation) void {
|
|
387
|
+
assert(set.opened);
|
|
388
|
+
assert(set.reservation_session == reservation.session);
|
|
389
|
+
|
|
390
|
+
set.reservation_count -= 1;
|
|
391
|
+
if (set.reservation_count == 0) {
|
|
392
|
+
// All reservations have been dropped.
|
|
393
|
+
set.reservation_blocks = 0;
|
|
394
|
+
set.reservation_session +%= 1;
|
|
395
|
+
set.reservation_state = .reserving;
|
|
396
|
+
} else {
|
|
397
|
+
set.reservation_state = .forfeiting;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/// Marks a free block from the reservation as allocated, and returns the address.
|
|
402
|
+
/// The reservation must not have been forfeited yet.
|
|
403
|
+
/// The reservation must belong to the current cycle of reservations.
|
|
404
|
+
///
|
|
405
|
+
/// Invariants:
|
|
406
|
+
///
|
|
407
|
+
/// - An acquired block cannot be acquired again until it has been released and the release
|
|
408
|
+
/// has been checkpointed.
|
|
409
|
+
///
|
|
410
|
+
/// Returns null if no free block is available in the reservation.
|
|
411
|
+
pub fn acquire(set: *FreeSet, reservation: Reservation) ?u64 {
|
|
412
|
+
assert(set.opened);
|
|
413
|
+
assert(set.reservation_count > 0);
|
|
414
|
+
assert(reservation.block_count > 0);
|
|
415
|
+
assert(reservation.block_base < set.reservation_blocks);
|
|
416
|
+
assert(reservation.block_base + reservation.block_count <= set.reservation_blocks);
|
|
417
|
+
assert(reservation.session == set.reservation_session);
|
|
418
|
+
|
|
419
|
+
const shard_start = find_bit(
|
|
420
|
+
set.index,
|
|
421
|
+
@divFloor(reservation.block_base, shard_bits),
|
|
422
|
+
div_ceil(reservation.block_base + reservation.block_count, shard_bits),
|
|
423
|
+
.unset,
|
|
424
|
+
) orelse return null;
|
|
425
|
+
assert(!set.index.isSet(shard_start));
|
|
426
|
+
|
|
427
|
+
const reservation_start = @max(
|
|
428
|
+
shard_start * shard_bits,
|
|
429
|
+
reservation.block_base,
|
|
430
|
+
);
|
|
431
|
+
const reservation_end = reservation.block_base + reservation.block_count;
|
|
432
|
+
const block = find_bit(
|
|
433
|
+
set.blocks_acquired,
|
|
434
|
+
reservation_start,
|
|
435
|
+
reservation_end,
|
|
436
|
+
.unset,
|
|
437
|
+
) orelse return null;
|
|
438
|
+
assert(block >= reservation.block_base);
|
|
439
|
+
assert(block <= reservation.block_base + reservation.block_count);
|
|
440
|
+
assert(!set.blocks_acquired.isSet(block));
|
|
441
|
+
assert(!set.blocks_released.isSet(block));
|
|
442
|
+
assert(!set.blocks_released_prior_checkpoint_durability.contains(block));
|
|
443
|
+
|
|
444
|
+
// Even if "shard_start" has free blocks, we might acquire our block from a later shard.
|
|
445
|
+
// (This is possible because our reservation begins part-way through the shard.)
|
|
446
|
+
const shard = @divFloor(block, shard_bits);
|
|
447
|
+
maybe(shard == shard_start);
|
|
448
|
+
assert(shard >= shard_start);
|
|
449
|
+
|
|
450
|
+
set.blocks_acquired.set(block);
|
|
451
|
+
// Update the index when every block in the shard is acquired.
|
|
452
|
+
if (set.find_free_block_in_shard(shard) == null) set.index.set(shard);
|
|
453
|
+
const address = block + 1;
|
|
454
|
+
return address;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
fn find_free_block_in_shard(set: FreeSet, shard: usize) ?usize {
|
|
458
|
+
maybe(set.opened);
|
|
459
|
+
const shard_start = shard * shard_bits;
|
|
460
|
+
const shard_end = shard_start + shard_bits;
|
|
461
|
+
assert(shard_start < set.blocks_acquired.bit_length);
|
|
462
|
+
|
|
463
|
+
return find_bit(set.blocks_acquired, shard_start, shard_end, .unset);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
pub fn is_free(set: FreeSet, address: u64) bool {
|
|
467
|
+
if (set.opened) {
|
|
468
|
+
const block = address - 1;
|
|
469
|
+
return !set.blocks_acquired.isSet(block);
|
|
470
|
+
} else {
|
|
471
|
+
// When the free set is not open, conservatively assume that the block is acquired.
|
|
472
|
+
//
|
|
473
|
+
// This path is hit only when the replica opens the free set, reading its blocks from
|
|
474
|
+
// the grid.
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
pub fn is_released(set: *const FreeSet, address: u64) bool {
|
|
480
|
+
assert(set.opened);
|
|
481
|
+
const block = address - 1;
|
|
482
|
+
return set.blocks_released_prior_checkpoint_durability.contains(block) or
|
|
483
|
+
set.blocks_released.isSet(block);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/// Returns `true` if the block at the given address would be freed when the current checkpoint
|
|
487
|
+
/// becomes durable (when checkpoint_durable is set to `true`).
|
|
488
|
+
///
|
|
489
|
+
/// Calling this function is only valid while the current checkpoint is not durable. During this
|
|
490
|
+
/// period, blocks are marked as released in `blocks_released_prior_checkpoint_durability`;
|
|
491
|
+
/// `blocks_released` remains unchanged and contains blocks released during the previous
|
|
492
|
+
/// checkpoint interval.
|
|
493
|
+
pub fn to_be_freed_at_checkpoint_durability(set: *const FreeSet, address: u64) bool {
|
|
494
|
+
const block = address - 1;
|
|
495
|
+
|
|
496
|
+
assert(set.opened);
|
|
497
|
+
assert(!set.checkpoint_durable);
|
|
498
|
+
|
|
499
|
+
// Block address must be acquired, but is not necessarily released.
|
|
500
|
+
assert(set.blocks_acquired.isSet(block));
|
|
501
|
+
assert(!set.blocks_released.isSet(block) or
|
|
502
|
+
!set.blocks_released_prior_checkpoint_durability.contains(block));
|
|
503
|
+
maybe(set.blocks_released.isSet(block));
|
|
504
|
+
maybe(set.blocks_released_prior_checkpoint_durability.contains(block));
|
|
505
|
+
|
|
506
|
+
return set.blocks_released.isSet(block);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/// Leave the address acquired for now, but free it when the next checkpoint becomes durable.
|
|
510
|
+
/// This ensures that it will not be overwritten during the current checkpoint — the block may
|
|
511
|
+
/// still be needed if we crash and recover from the current checkpoint.
|
|
512
|
+
/// (TODO) If the block was created since the last checkpoint then it's safe to free
|
|
513
|
+
/// immediately. This may reduce space amplification, especially for smaller datasets.
|
|
514
|
+
/// (Note: This must be careful not to release while any reservations are held
|
|
515
|
+
/// to avoid making the reservation's acquire()s nondeterministic).
|
|
516
|
+
pub fn release(set: *FreeSet, address: u64) void {
|
|
517
|
+
assert(set.opened);
|
|
518
|
+
|
|
519
|
+
const block = address - 1;
|
|
520
|
+
assert(set.blocks_acquired.isSet(block));
|
|
521
|
+
assert(!set.blocks_released.isSet(block));
|
|
522
|
+
assert(!set.blocks_released_prior_checkpoint_durability.contains(block));
|
|
523
|
+
|
|
524
|
+
// `blocks_released` remains unchanged while the current checkpoint is not durable,
|
|
525
|
+
// since it contains blocks released in the previous checkpoint. These blocks must not be
|
|
526
|
+
// freed till the current checkpoint is durable, so as to maintain the durability of these
|
|
527
|
+
// blocks on a commit quorum of replicas.
|
|
528
|
+
if (set.checkpoint_durable) {
|
|
529
|
+
set.blocks_released.set(block);
|
|
530
|
+
} else {
|
|
531
|
+
set.blocks_released_prior_checkpoint_durability.putAssumeCapacity(block, {});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/// Mark the given addresses as allocated in the current checkpoint, but free in the next one.
|
|
536
|
+
///
|
|
537
|
+
/// This is used only when reading a free set from the grid. On disk representation of the
|
|
538
|
+
/// free set doesn't include the blocks storing the free set itself, and these blocks must be
|
|
539
|
+
/// manually patched in after decoding. As the next checkpoint will have a completely different
|
|
540
|
+
/// free set, the blocks can be simultaneously released.
|
|
541
|
+
fn mark_released(set: *FreeSet, addresses: []const u64) void {
|
|
542
|
+
assert(!set.opened);
|
|
543
|
+
assert(!set.checkpoint_durable);
|
|
544
|
+
|
|
545
|
+
var address_previous: u64 = 0;
|
|
546
|
+
for (addresses) |address| {
|
|
547
|
+
assert(address > 0);
|
|
548
|
+
|
|
549
|
+
// Assert that addresses are sorted and unique. Sortedness is not a requirement, but
|
|
550
|
+
// a consequence of "first free" allocation algorithm.
|
|
551
|
+
assert(address > address_previous);
|
|
552
|
+
address_previous = address;
|
|
553
|
+
|
|
554
|
+
const block = address - 1;
|
|
555
|
+
|
|
556
|
+
assert(!set.blocks_acquired.isSet(block));
|
|
557
|
+
assert(!set.blocks_released.isSet(block));
|
|
558
|
+
assert(!set.blocks_released_prior_checkpoint_durability.contains(block));
|
|
559
|
+
|
|
560
|
+
set.blocks_acquired.set(block);
|
|
561
|
+
|
|
562
|
+
const shard = @divFloor(block, shard_bits);
|
|
563
|
+
// Update the index when every block in the shard is acquired.
|
|
564
|
+
if (set.find_free_block_in_shard(shard) == null) set.index.set(shard);
|
|
565
|
+
|
|
566
|
+
set.blocks_released_prior_checkpoint_durability.putAssumeCapacity(block, {});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/// Given the address, marks an acquired block as free.
|
|
571
|
+
fn free(set: *FreeSet, address: u64) void {
|
|
572
|
+
assert(set.opened);
|
|
573
|
+
assert(set.checkpoint_durable);
|
|
574
|
+
|
|
575
|
+
const block = address - 1;
|
|
576
|
+
assert(set.blocks_acquired.isSet(block));
|
|
577
|
+
assert(set.blocks_released.isSet(block));
|
|
578
|
+
assert(!set.blocks_released_prior_checkpoint_durability.contains(block));
|
|
579
|
+
|
|
580
|
+
assert(set.reservation_count == 0);
|
|
581
|
+
assert(set.reservation_blocks == 0);
|
|
582
|
+
|
|
583
|
+
set.index.unset(@divFloor(block, shard_bits));
|
|
584
|
+
set.blocks_acquired.unset(block);
|
|
585
|
+
set.blocks_released.unset(block);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
pub fn mark_checkpoint_not_durable(set: *FreeSet) void {
|
|
589
|
+
assert(set.opened);
|
|
590
|
+
assert(set.checkpoint_durable);
|
|
591
|
+
assert(set.blocks_released_prior_checkpoint_durability.count() == 0);
|
|
592
|
+
set.checkpoint_durable = false;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/// Now that the checkpoint is durable on a commit quorum of replicas:
|
|
596
|
+
/// 1. Mark the current checkpoint as durable.
|
|
597
|
+
/// 2. Mark all released blocks in `blocks_released` as free.
|
|
598
|
+
/// 3. Move released blocks from `blocks_released_prior_checkpoint_durability` to
|
|
599
|
+
/// `blocks_released`.
|
|
600
|
+
pub fn mark_checkpoint_durable(set: *FreeSet) void {
|
|
601
|
+
assert(set.opened);
|
|
602
|
+
assert(!set.checkpoint_durable);
|
|
603
|
+
|
|
604
|
+
set.checkpoint_durable = true;
|
|
605
|
+
|
|
606
|
+
var it = set.blocks_released.iterator(.{ .kind = .set });
|
|
607
|
+
while (it.next()) |block| set.free(block + 1);
|
|
608
|
+
|
|
609
|
+
assert(set.blocks_released.count() == 0);
|
|
610
|
+
|
|
611
|
+
// Block releases from the current checkpoint that were temporarily recorded in
|
|
612
|
+
// blocks_released_prior_checkpoint_durability can now be moved to blocks_released.
|
|
613
|
+
while (set.blocks_released_prior_checkpoint_durability.pop()) |block_entry| {
|
|
614
|
+
const block = block_entry.key;
|
|
615
|
+
set.blocks_released.set(block);
|
|
616
|
+
}
|
|
617
|
+
assert(set.blocks_released_prior_checkpoint_durability.count() == 0);
|
|
618
|
+
|
|
619
|
+
// Index verification is O(blocks.bit_length) so do it only when checkpoint is marked
|
|
620
|
+
// durable, which is also linear (as we free released blocks in `blocks_released`).
|
|
621
|
+
set.verify_index();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/// Decodes the compressed bitset chunks in `source_chunks` into `target_bitset`.
|
|
625
|
+
/// Panics if the `source_chunks` encoding is invalid.
|
|
626
|
+
fn decode(
|
|
627
|
+
set: *FreeSet,
|
|
628
|
+
target_bitset: FreeSet.BitsetKind,
|
|
629
|
+
source_chunks: []const []align(@alignOf(Word)) const u8,
|
|
630
|
+
) void {
|
|
631
|
+
assert(!set.opened);
|
|
632
|
+
assert(!set.checkpoint_durable);
|
|
633
|
+
|
|
634
|
+
var source_size: usize = 0;
|
|
635
|
+
|
|
636
|
+
for (source_chunks) |source_chunk| source_size += source_chunk.len;
|
|
637
|
+
|
|
638
|
+
const target_bitset_words = switch (target_bitset) {
|
|
639
|
+
.blocks_acquired => bit_set_masks(set.blocks_acquired),
|
|
640
|
+
.blocks_released => bit_set_masks(set.blocks_released),
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
var decoder = ewah.decode_chunks(target_bitset_words, source_size);
|
|
644
|
+
|
|
645
|
+
var words_decoded: usize = 0;
|
|
646
|
+
for (source_chunks) |source_chunk| {
|
|
647
|
+
words_decoded += decoder.decode_chunk(source_chunk);
|
|
648
|
+
}
|
|
649
|
+
assert(decoder.done());
|
|
650
|
+
|
|
651
|
+
assert(@bitSizeOf(Word) == @bitSizeOf(MaskInt));
|
|
652
|
+
assert(words_decoded * @bitSizeOf(Word) <= set.blocks_acquired.bit_length);
|
|
653
|
+
|
|
654
|
+
// The encoder does not encode trailing 0s, so everything past words_decoded must be zeroed.
|
|
655
|
+
assert(stdx.zeroed(std.mem.sliceAsBytes(target_bitset_words[words_decoded..])));
|
|
656
|
+
// TODO: uncomment on the next release:
|
|
657
|
+
// if (words_decoded > 0) assert(target_bitset_words[words_decoded - 1] != 0);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
pub fn decode_chunks(
|
|
661
|
+
set: *FreeSet,
|
|
662
|
+
source_chunks_blocks_acquired: []const []align(@alignOf(Word)) const u8,
|
|
663
|
+
source_chunks_blocks_released: []const []align(@alignOf(Word)) const u8,
|
|
664
|
+
) void {
|
|
665
|
+
assert(!set.opened);
|
|
666
|
+
assert(!set.checkpoint_durable);
|
|
667
|
+
|
|
668
|
+
// Verify that this FreeSet is entirely unallocated.
|
|
669
|
+
assert(set.index.count() == 0);
|
|
670
|
+
assert(set.blocks_acquired.count() == 0);
|
|
671
|
+
assert(set.blocks_released.count() == 0);
|
|
672
|
+
assert(set.blocks_released_prior_checkpoint_durability.count() == 0);
|
|
673
|
+
|
|
674
|
+
assert(set.reservation_count == 0);
|
|
675
|
+
assert(set.reservation_blocks == 0);
|
|
676
|
+
|
|
677
|
+
set.decode(.blocks_acquired, source_chunks_blocks_acquired);
|
|
678
|
+
set.decode(.blocks_released, source_chunks_blocks_released);
|
|
679
|
+
|
|
680
|
+
for (0..set.index.bit_length) |shard| {
|
|
681
|
+
if (set.find_free_block_in_shard(shard) == null) set.index.set(shard);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
set.verify_index();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/// Returns the number of blocks that the free set can physically reference via the acquired
|
|
688
|
+
/// and released bitsets. Logically, the limit on the number of blocks that can be acquired by
|
|
689
|
+
/// the free set is imposed by --limit-storage.
|
|
690
|
+
pub fn block_count_max(grid_size_limit: usize) usize {
|
|
691
|
+
const block_count_limit = @divFloor(grid_size_limit, constants.block_size);
|
|
692
|
+
return stdx.div_ceil(block_count_limit, shard_bits) * shard_bits;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/// Returns the maximum number of bytes needed for encoding the acquired/released bitset.
|
|
696
|
+
pub fn encode_size_max(set: *const FreeSet) usize {
|
|
697
|
+
assert(set.blocks_acquired.bit_length == set.blocks_released.bit_length);
|
|
698
|
+
|
|
699
|
+
const blocks_count = set.blocks_acquired.bit_length;
|
|
700
|
+
assert(blocks_count % shard_bits == 0);
|
|
701
|
+
assert(blocks_count % @bitSizeOf(usize) == 0);
|
|
702
|
+
|
|
703
|
+
return ewah.encode_size_max(@divExact(blocks_count, @bitSizeOf(Word)));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
fn encode(
|
|
707
|
+
set: *const FreeSet,
|
|
708
|
+
source_bitset: FreeSet.BitsetKind,
|
|
709
|
+
target_chunks: []const []align(@alignOf(Word)) u8,
|
|
710
|
+
) usize {
|
|
711
|
+
assert(set.opened);
|
|
712
|
+
assert(set.checkpoint_durable);
|
|
713
|
+
|
|
714
|
+
var encoder = switch (source_bitset) {
|
|
715
|
+
.blocks_acquired => ewah.encode_chunks(bit_set_masks(set.blocks_acquired)),
|
|
716
|
+
.blocks_released => ewah.encode_chunks(bit_set_masks(set.blocks_released)),
|
|
717
|
+
};
|
|
718
|
+
defer assert(encoder.done());
|
|
719
|
+
|
|
720
|
+
var bytes_encoded_total: u64 = 0;
|
|
721
|
+
for (target_chunks) |chunk| {
|
|
722
|
+
const bytes_encoded =
|
|
723
|
+
@as(u32, @intCast(encoder.encode_chunk(chunk)));
|
|
724
|
+
assert(bytes_encoded > 0);
|
|
725
|
+
|
|
726
|
+
bytes_encoded_total += bytes_encoded;
|
|
727
|
+
|
|
728
|
+
if (encoder.done()) break;
|
|
729
|
+
} else unreachable;
|
|
730
|
+
|
|
731
|
+
// Don't explicitly encode trailing zeros to ensure that the encoding is the same regardless
|
|
732
|
+
// of the runtime-configurable capacity of the bit set (driven by --limit-storage).
|
|
733
|
+
const bytes_trailing_zero_runs = encoder.trailing_zero_runs_count * @sizeOf(ewah.Marker);
|
|
734
|
+
|
|
735
|
+
return bytes_encoded_total - bytes_trailing_zero_runs;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
pub fn encode_chunks(
|
|
739
|
+
set: *const FreeSet,
|
|
740
|
+
target_chunks_blocks_acquired: []const []align(@alignOf(Word)) u8,
|
|
741
|
+
target_chunks_blocks_released: []const []align(@alignOf(Word)) u8,
|
|
742
|
+
) struct { encoded_size_blocks_acquired: u64, encoded_size_blocks_released: u64 } {
|
|
743
|
+
assert(set.opened);
|
|
744
|
+
assert(set.checkpoint_durable);
|
|
745
|
+
assert(set.reservation_count == 0);
|
|
746
|
+
assert(set.reservation_blocks == 0);
|
|
747
|
+
|
|
748
|
+
return .{
|
|
749
|
+
.encoded_size_blocks_acquired = set.encode(
|
|
750
|
+
.blocks_acquired,
|
|
751
|
+
target_chunks_blocks_acquired,
|
|
752
|
+
),
|
|
753
|
+
.encoded_size_blocks_released = set.encode(
|
|
754
|
+
.blocks_released,
|
|
755
|
+
target_chunks_blocks_released,
|
|
756
|
+
),
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
fn bit_set_masks(bit_set: DynamicBitSetUnmanaged) []MaskInt {
|
|
762
|
+
const len = div_ceil(bit_set.bit_length, @bitSizeOf(MaskInt));
|
|
763
|
+
return bit_set.masks[0..len];
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
test "FreeSet block shard count" {
|
|
767
|
+
if (constants.block_size != 64 * KiB) return;
|
|
768
|
+
const blocks_in_tb = @divExact(1 << 40, constants.block_size);
|
|
769
|
+
try test_block_shards_count(5120 * 8, 10 * blocks_in_tb);
|
|
770
|
+
try test_block_shards_count(5120 * 8 - 1, 10 * blocks_in_tb - FreeSet.shard_bits);
|
|
771
|
+
try test_block_shards_count(1, FreeSet.shard_bits); // Must be at least one index bit.
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
fn test_block_shards_count(expect_shards_count: usize, blocks_count: usize) !void {
|
|
775
|
+
const gpa = std.testing.allocator;
|
|
776
|
+
|
|
777
|
+
var set = try FreeSet.open_empty(gpa, blocks_count);
|
|
778
|
+
defer set.deinit(gpa);
|
|
779
|
+
|
|
780
|
+
try std.testing.expectEqual(expect_shards_count, set.index.bit_length);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
test "FreeSet highest_address_acquired" {
|
|
784
|
+
const expectEqual = std.testing.expectEqual;
|
|
785
|
+
const blocks_count = FreeSet.shard_bits;
|
|
786
|
+
const gpa = std.testing.allocator;
|
|
787
|
+
|
|
788
|
+
var set = try FreeSet.open_empty(gpa, blocks_count);
|
|
789
|
+
defer set.deinit(gpa);
|
|
790
|
+
|
|
791
|
+
{
|
|
792
|
+
const reservation = set.reserve(6).?;
|
|
793
|
+
defer set.forfeit(reservation);
|
|
794
|
+
|
|
795
|
+
try expectEqual(@as(?u64, null), set.highest_address_acquired());
|
|
796
|
+
try expectEqual(@as(?u64, 1), set.acquire(reservation));
|
|
797
|
+
try expectEqual(@as(?u64, 2), set.acquire(reservation));
|
|
798
|
+
try expectEqual(@as(?u64, 3), set.acquire(reservation));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
try expectEqual(@as(?u64, 3), set.highest_address_acquired());
|
|
802
|
+
|
|
803
|
+
set.release(2);
|
|
804
|
+
set.free(2);
|
|
805
|
+
try expectEqual(@as(?u64, 3), set.highest_address_acquired());
|
|
806
|
+
|
|
807
|
+
set.release(3);
|
|
808
|
+
set.free(3);
|
|
809
|
+
try expectEqual(@as(?u64, 1), set.highest_address_acquired());
|
|
810
|
+
|
|
811
|
+
set.release(1);
|
|
812
|
+
set.free(1);
|
|
813
|
+
try expectEqual(@as(?u64, null), set.highest_address_acquired());
|
|
814
|
+
|
|
815
|
+
{
|
|
816
|
+
const reservation = set.reserve(6).?;
|
|
817
|
+
defer set.forfeit(reservation);
|
|
818
|
+
|
|
819
|
+
try expectEqual(@as(?u64, 1), set.acquire(reservation));
|
|
820
|
+
try expectEqual(@as(?u64, 2), set.acquire(reservation));
|
|
821
|
+
try expectEqual(@as(?u64, 3), set.acquire(reservation));
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
{
|
|
825
|
+
set.release(3);
|
|
826
|
+
try expectEqual(@as(?u64, 3), set.highest_address_acquired());
|
|
827
|
+
|
|
828
|
+
set.free(3);
|
|
829
|
+
try expectEqual(@as(?u64, 2), set.highest_address_acquired());
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
test "FreeSet acquire/release" {
|
|
834
|
+
try test_acquire_release(FreeSet.shard_bits);
|
|
835
|
+
try test_acquire_release(2 * FreeSet.shard_bits);
|
|
836
|
+
try test_acquire_release(63 * FreeSet.shard_bits);
|
|
837
|
+
try test_acquire_release(64 * FreeSet.shard_bits);
|
|
838
|
+
try test_acquire_release(65 * FreeSet.shard_bits);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
fn test_acquire_release(blocks_count: usize) !void {
|
|
842
|
+
const gpa = std.testing.allocator;
|
|
843
|
+
const expectEqual = std.testing.expectEqual;
|
|
844
|
+
// Acquire everything, then release, then acquire again.
|
|
845
|
+
var set = try FreeSet.open_empty(gpa, blocks_count);
|
|
846
|
+
defer set.deinit(gpa);
|
|
847
|
+
|
|
848
|
+
var empty = try FreeSet.open_empty(gpa, blocks_count);
|
|
849
|
+
defer empty.deinit(gpa);
|
|
850
|
+
|
|
851
|
+
{
|
|
852
|
+
const reservation = set.reserve(blocks_count).?;
|
|
853
|
+
defer set.forfeit(reservation);
|
|
854
|
+
|
|
855
|
+
for (0..blocks_count) |i| {
|
|
856
|
+
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
857
|
+
}
|
|
858
|
+
try expectEqual(@as(?u64, null), set.acquire(reservation));
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
try expectEqual(@as(u64, set.blocks_acquired.bit_length), set.count_acquired());
|
|
862
|
+
try expectEqual(@as(u64, 0), set.count_free());
|
|
863
|
+
|
|
864
|
+
{
|
|
865
|
+
for (0..blocks_count) |i| {
|
|
866
|
+
set.release(@as(u64, i + 1));
|
|
867
|
+
set.free(@as(u64, i + 1));
|
|
868
|
+
}
|
|
869
|
+
try expect_free_set_equal(empty, set);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
try expectEqual(@as(u64, 0), set.count_acquired());
|
|
873
|
+
try expectEqual(@as(u64, set.blocks_acquired.bit_length), set.count_free());
|
|
874
|
+
|
|
875
|
+
{
|
|
876
|
+
const reservation = set.reserve(blocks_count).?;
|
|
877
|
+
defer set.forfeit(reservation);
|
|
878
|
+
|
|
879
|
+
for (0..blocks_count) |i| {
|
|
880
|
+
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
881
|
+
}
|
|
882
|
+
try expectEqual(@as(?u64, null), set.acquire(reservation));
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
test "FreeSet.reserve/acquire" {
|
|
887
|
+
const gpa = std.testing.allocator;
|
|
888
|
+
const blocks_count_total = 4096;
|
|
889
|
+
var set = try FreeSet.open_empty(gpa, blocks_count_total);
|
|
890
|
+
defer set.deinit(gpa);
|
|
891
|
+
|
|
892
|
+
// At most `blocks_count_total` blocks are initially available for reservation.
|
|
893
|
+
try std.testing.expectEqual(set.reserve(blocks_count_total + 1), null);
|
|
894
|
+
const r1 = set.reserve(blocks_count_total - 1);
|
|
895
|
+
const r2 = set.reserve(1);
|
|
896
|
+
try std.testing.expectEqual(set.reserve(1), null);
|
|
897
|
+
set.forfeit(r1.?);
|
|
898
|
+
set.forfeit(r2.?);
|
|
899
|
+
|
|
900
|
+
var address: usize = 1; // Start at 1 because addresses are >0.
|
|
901
|
+
{
|
|
902
|
+
const reservation = set.reserve(2).?;
|
|
903
|
+
defer set.forfeit(reservation);
|
|
904
|
+
|
|
905
|
+
try std.testing.expectEqual(set.acquire(reservation), address + 0);
|
|
906
|
+
try std.testing.expectEqual(set.acquire(reservation), address + 1);
|
|
907
|
+
try std.testing.expectEqual(set.acquire(reservation), null);
|
|
908
|
+
}
|
|
909
|
+
address += 2;
|
|
910
|
+
|
|
911
|
+
{
|
|
912
|
+
// Blocks are acquired from the target reservation.
|
|
913
|
+
const reservation_1 = set.reserve(2).?;
|
|
914
|
+
const reservation_2 = set.reserve(2).?;
|
|
915
|
+
defer set.forfeit(reservation_1);
|
|
916
|
+
defer set.forfeit(reservation_2);
|
|
917
|
+
|
|
918
|
+
try std.testing.expectEqual(set.acquire(reservation_1), address + 0);
|
|
919
|
+
try std.testing.expectEqual(set.acquire(reservation_2), address + 2);
|
|
920
|
+
try std.testing.expectEqual(set.acquire(reservation_1), address + 1);
|
|
921
|
+
try std.testing.expectEqual(set.acquire(reservation_1), null);
|
|
922
|
+
try std.testing.expectEqual(set.acquire(reservation_2), address + 3);
|
|
923
|
+
try std.testing.expectEqual(set.acquire(reservation_2), null);
|
|
924
|
+
}
|
|
925
|
+
address += 4;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
test "FreeSet checkpoint" {
|
|
929
|
+
const gpa = std.testing.allocator;
|
|
930
|
+
const expectEqual = std.testing.expectEqual;
|
|
931
|
+
const blocks_count = FreeSet.shard_bits;
|
|
932
|
+
var set = try FreeSet.open_empty(gpa, blocks_count);
|
|
933
|
+
defer set.deinit(gpa);
|
|
934
|
+
|
|
935
|
+
var empty = try FreeSet.open_empty(gpa, blocks_count);
|
|
936
|
+
defer empty.deinit(gpa);
|
|
937
|
+
|
|
938
|
+
var full = try FreeSet.open_empty(gpa, blocks_count);
|
|
939
|
+
defer full.deinit(gpa);
|
|
940
|
+
|
|
941
|
+
{
|
|
942
|
+
// Acquire all of `full`'s blocks.
|
|
943
|
+
const reservation = full.reserve(blocks_count).?;
|
|
944
|
+
defer full.forfeit(reservation);
|
|
945
|
+
|
|
946
|
+
for (0..full.blocks_acquired.bit_length) |i| {
|
|
947
|
+
try expectEqual(@as(?u64, i + 1), full.acquire(reservation));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
{
|
|
952
|
+
// Acquire & stage-release every block.
|
|
953
|
+
const reservation = set.reserve(blocks_count).?;
|
|
954
|
+
defer set.forfeit(reservation);
|
|
955
|
+
|
|
956
|
+
for (0..set.blocks_acquired.bit_length) |i| {
|
|
957
|
+
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
958
|
+
set.release(i + 1);
|
|
959
|
+
|
|
960
|
+
// These count functions treat staged blocks as acquired.
|
|
961
|
+
try expectEqual(@as(u64, i + 1), set.count_acquired());
|
|
962
|
+
try expectEqual(@as(u64, set.blocks_acquired.bit_length - i - 1), set.count_free());
|
|
963
|
+
}
|
|
964
|
+
// All blocks are still acquired, though staged to release at the next checkpoint.
|
|
965
|
+
try expectEqual(@as(?u64, null), set.acquire(reservation));
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Perform checkpoint-related operations.
|
|
969
|
+
set.mark_checkpoint_not_durable();
|
|
970
|
+
set.mark_checkpoint_durable();
|
|
971
|
+
|
|
972
|
+
try expect_free_set_equal(empty, set);
|
|
973
|
+
try expectEqual(@as(usize, 0), set.blocks_released.count());
|
|
974
|
+
|
|
975
|
+
{
|
|
976
|
+
// Allocate & stage-release all blocks again.
|
|
977
|
+
const reservation = set.reserve(blocks_count).?;
|
|
978
|
+
defer set.forfeit(reservation);
|
|
979
|
+
|
|
980
|
+
for (0..set.blocks_acquired.bit_length) |i| {
|
|
981
|
+
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
982
|
+
set.release(i + 1);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const set_encoded_blocks_acquired = try gpa.alignedAlloc(
|
|
987
|
+
u8,
|
|
988
|
+
@alignOf(FreeSet.Word),
|
|
989
|
+
set.encode_size_max(),
|
|
990
|
+
);
|
|
991
|
+
const set_encoded_blocks_released = try gpa.alignedAlloc(
|
|
992
|
+
u8,
|
|
993
|
+
@alignOf(FreeSet.Word),
|
|
994
|
+
set.encode_size_max(),
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
defer gpa.free(set_encoded_blocks_acquired);
|
|
998
|
+
defer gpa.free(set_encoded_blocks_released);
|
|
999
|
+
|
|
1000
|
+
var set_decoded = try FreeSet.init_empty(gpa, blocks_count);
|
|
1001
|
+
|
|
1002
|
+
defer set_decoded.deinit(gpa);
|
|
1003
|
+
|
|
1004
|
+
{
|
|
1005
|
+
const free_set_encoded = set.encode_chunks(
|
|
1006
|
+
&.{set_encoded_blocks_acquired},
|
|
1007
|
+
&.{set_encoded_blocks_released},
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
set_decoded.decode_chunks(
|
|
1011
|
+
&.{set_encoded_blocks_acquired[0..free_set_encoded.encoded_size_blocks_acquired]},
|
|
1012
|
+
&.{set_encoded_blocks_released[0..free_set_encoded.encoded_size_blocks_released]},
|
|
1013
|
+
);
|
|
1014
|
+
try expect_free_set_equal(set, set_decoded);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
{
|
|
1018
|
+
const free_set_encoded = full.encode_chunks(
|
|
1019
|
+
&.{set_encoded_blocks_acquired},
|
|
1020
|
+
&.{set_encoded_blocks_released},
|
|
1021
|
+
);
|
|
1022
|
+
|
|
1023
|
+
set_decoded.reset();
|
|
1024
|
+
set_decoded.decode_chunks(
|
|
1025
|
+
&.{set_encoded_blocks_acquired[0..free_set_encoded.encoded_size_blocks_acquired]},
|
|
1026
|
+
&.{set_encoded_blocks_released[0..free_set_encoded.encoded_size_blocks_released]},
|
|
1027
|
+
);
|
|
1028
|
+
try expect_free_set_equal(full, set_decoded);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
test "FreeSet encode, decode, encode" {
|
|
1033
|
+
const shard_bits = FreeSet.shard_bits / @bitSizeOf(usize);
|
|
1034
|
+
const gpa = std.testing.allocator;
|
|
1035
|
+
|
|
1036
|
+
// Uniform.
|
|
1037
|
+
try test_encode(&.{.{ .fill = .uniform_ones, .words = shard_bits }});
|
|
1038
|
+
try test_encode(&.{.{ .fill = .uniform_zeros, .words = shard_bits }});
|
|
1039
|
+
try test_encode(&.{.{ .fill = .literal, .words = shard_bits }});
|
|
1040
|
+
try test_encode(&.{.{ .fill = .uniform_ones, .words = std.math.maxInt(u16) + 1 }});
|
|
1041
|
+
|
|
1042
|
+
// Mixed.
|
|
1043
|
+
try test_encode(&.{
|
|
1044
|
+
.{ .fill = .uniform_ones, .words = shard_bits / 4 },
|
|
1045
|
+
.{ .fill = .uniform_zeros, .words = shard_bits / 4 },
|
|
1046
|
+
.{ .fill = .literal, .words = shard_bits / 4 },
|
|
1047
|
+
.{ .fill = .uniform_ones, .words = shard_bits / 4 },
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
// Random.
|
|
1051
|
+
const seed = std.crypto.random.int(u64);
|
|
1052
|
+
var prng = stdx.PRNG.from_seed(seed);
|
|
1053
|
+
|
|
1054
|
+
const fills = [_]TestPatternFill{ .uniform_ones, .uniform_zeros, .literal };
|
|
1055
|
+
for (0..10) |_| {
|
|
1056
|
+
var patterns = std.ArrayList(TestPattern).init(gpa);
|
|
1057
|
+
defer patterns.deinit();
|
|
1058
|
+
|
|
1059
|
+
for (0..shard_bits) |_| {
|
|
1060
|
+
try patterns.append(.{
|
|
1061
|
+
.fill = fills[prng.index(fills)],
|
|
1062
|
+
.words = 1,
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
try test_encode(patterns.items);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const TestPattern = struct {
|
|
1070
|
+
fill: TestPatternFill,
|
|
1071
|
+
words: usize,
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
const TestPatternFill = enum { uniform_ones, uniform_zeros, literal };
|
|
1075
|
+
|
|
1076
|
+
fn test_encode(patterns: []const TestPattern) !void {
|
|
1077
|
+
const gpa = std.testing.allocator;
|
|
1078
|
+
const seed = std.crypto.random.int(u64);
|
|
1079
|
+
var prng = stdx.PRNG.from_seed(seed);
|
|
1080
|
+
|
|
1081
|
+
var blocks_count: usize = 0;
|
|
1082
|
+
for (patterns) |pattern| blocks_count += pattern.words * @bitSizeOf(usize);
|
|
1083
|
+
|
|
1084
|
+
var decoded_expect = try FreeSet.open_empty(gpa, blocks_count);
|
|
1085
|
+
defer decoded_expect.deinit(gpa);
|
|
1086
|
+
|
|
1087
|
+
{
|
|
1088
|
+
// The `index` will start out one-filled. Every pattern containing a zero will update the
|
|
1089
|
+
// corresponding index bit with a zero (probably multiple times) to ensure it ends up synced
|
|
1090
|
+
// with `blocks`.
|
|
1091
|
+
decoded_expect.index.toggleAll();
|
|
1092
|
+
assert(decoded_expect.index.count() == decoded_expect.index.capacity());
|
|
1093
|
+
|
|
1094
|
+
// Fill the bitset according to the patterns.
|
|
1095
|
+
var blocks = bit_set_masks(decoded_expect.blocks_acquired);
|
|
1096
|
+
var blocks_offset: usize = 0;
|
|
1097
|
+
for (patterns) |pattern| {
|
|
1098
|
+
for (0..pattern.words) |_| {
|
|
1099
|
+
blocks[blocks_offset] = switch (pattern.fill) {
|
|
1100
|
+
.uniform_ones => ~@as(usize, 0),
|
|
1101
|
+
.uniform_zeros => 0,
|
|
1102
|
+
.literal => prng.range_inclusive(usize, 1, std.math.maxInt(usize) - 1),
|
|
1103
|
+
};
|
|
1104
|
+
const index_bit = blocks_offset * @bitSizeOf(usize) / FreeSet.shard_bits;
|
|
1105
|
+
if (pattern.fill != .uniform_ones) decoded_expect.index.unset(index_bit);
|
|
1106
|
+
blocks_offset += 1;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
assert(blocks_offset == blocks.len);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
var encoded = try gpa.alignedAlloc(
|
|
1113
|
+
u8,
|
|
1114
|
+
@alignOf(FreeSet.Word),
|
|
1115
|
+
decoded_expect.encode_size_max(),
|
|
1116
|
+
);
|
|
1117
|
+
defer gpa.free(encoded);
|
|
1118
|
+
|
|
1119
|
+
try std.testing.expectEqual(encoded.len % 8, 0);
|
|
1120
|
+
const encoded_length = decoded_expect.encode(.blocks_acquired, &.{encoded});
|
|
1121
|
+
|
|
1122
|
+
var decoded_actual = try FreeSet.init_empty(gpa, blocks_count);
|
|
1123
|
+
defer decoded_actual.deinit(gpa);
|
|
1124
|
+
|
|
1125
|
+
decoded_actual.decode_chunks(&.{encoded[0..encoded_length]}, &.{});
|
|
1126
|
+
try expect_free_set_equal(decoded_expect, decoded_actual);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
fn expect_free_set_equal(a: FreeSet, b: FreeSet) !void {
|
|
1130
|
+
try expect_bit_set_equal(a.blocks_acquired, b.blocks_acquired);
|
|
1131
|
+
try expect_bit_set_equal(a.blocks_released, b.blocks_released);
|
|
1132
|
+
try expect_bit_set_equal(a.index, b.index);
|
|
1133
|
+
|
|
1134
|
+
try std.testing.expectEqual(
|
|
1135
|
+
a.blocks_released_prior_checkpoint_durability.count(),
|
|
1136
|
+
b.blocks_released_prior_checkpoint_durability.count(),
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
for (
|
|
1140
|
+
a.blocks_released_prior_checkpoint_durability.keys(),
|
|
1141
|
+
b.blocks_released_prior_checkpoint_durability.keys(),
|
|
1142
|
+
) |address_a, address_b| {
|
|
1143
|
+
assert(address_a == address_b);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
fn expect_bit_set_equal(a: DynamicBitSetUnmanaged, b: DynamicBitSetUnmanaged) !void {
|
|
1148
|
+
try std.testing.expectEqual(a.bit_length, b.bit_length);
|
|
1149
|
+
const a_masks = bit_set_masks(a);
|
|
1150
|
+
const b_masks = bit_set_masks(b);
|
|
1151
|
+
for (a_masks, 0..) |aw, i| try std.testing.expectEqual(aw, b_masks[i]);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
test "FreeSet decode small bitset into large bitset" {
|
|
1155
|
+
const gpa = std.testing.allocator;
|
|
1156
|
+
const shard_bits = FreeSet.shard_bits;
|
|
1157
|
+
var small_set = try FreeSet.open_empty(gpa, shard_bits);
|
|
1158
|
+
defer small_set.deinit(gpa);
|
|
1159
|
+
|
|
1160
|
+
{
|
|
1161
|
+
// Set up a small bitset (with blocks_count==shard_bits) with no free blocks.
|
|
1162
|
+
const reservation = small_set.reserve(small_set.blocks_acquired.bit_length).?;
|
|
1163
|
+
defer small_set.forfeit(reservation);
|
|
1164
|
+
|
|
1165
|
+
for (0..small_set.blocks_acquired.bit_length) |_| {
|
|
1166
|
+
_ = small_set.acquire(reservation);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
var small_buffer = try gpa.alignedAlloc(
|
|
1171
|
+
u8,
|
|
1172
|
+
@alignOf(usize),
|
|
1173
|
+
small_set.encode_size_max(),
|
|
1174
|
+
);
|
|
1175
|
+
defer gpa.free(small_buffer);
|
|
1176
|
+
|
|
1177
|
+
const small_buffer_written = small_set.encode(.blocks_acquired, &.{small_buffer});
|
|
1178
|
+
|
|
1179
|
+
// Decode the serialized small bitset into a larger bitset (with blocks_count==2*shard_bits).
|
|
1180
|
+
var big_set = try FreeSet.init_empty(gpa, 2 * shard_bits);
|
|
1181
|
+
defer big_set.deinit(gpa);
|
|
1182
|
+
|
|
1183
|
+
big_set.decode(.blocks_acquired, &.{small_buffer[0..small_buffer_written]});
|
|
1184
|
+
big_set.opened = true;
|
|
1185
|
+
|
|
1186
|
+
for (0..2 * shard_bits) |block| {
|
|
1187
|
+
const address = block + 1;
|
|
1188
|
+
try std.testing.expectEqual(shard_bits <= block, big_set.is_free(address));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
test "FreeSet encode/decode manual" {
|
|
1193
|
+
const encoded_expect = mem.sliceAsBytes(&[_]usize{
|
|
1194
|
+
// Mask 1: run of 2 words of 0s, then 3 literals
|
|
1195
|
+
0 | (2 << 1) | (3 << 32),
|
|
1196
|
+
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 1
|
|
1197
|
+
0b01010101_01010101_01010101_01010101_01010101_01010101_01010101_01010101, // literal 2
|
|
1198
|
+
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 3
|
|
1199
|
+
// Mask 2: run of 59 words of 1s, then 0 literals
|
|
1200
|
+
//
|
|
1201
|
+
// 59 is chosen so that because the blocks_count must be a multiple of the shard size:
|
|
1202
|
+
// shard_bits = 4096 bits = 64 words × 64 bits/word = (2+3+59)*64
|
|
1203
|
+
1 | ((64 - 5) << 1),
|
|
1204
|
+
});
|
|
1205
|
+
const decoded_expect = [_]usize{
|
|
1206
|
+
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000, // run 1
|
|
1207
|
+
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000,
|
|
1208
|
+
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 1
|
|
1209
|
+
0b01010101_01010101_01010101_01010101_01010101_01010101_01010101_01010101, // literal 2
|
|
1210
|
+
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 3
|
|
1211
|
+
} ++ ([1]usize{~@as(usize, 0)} ** (64 - 5));
|
|
1212
|
+
const blocks_count = decoded_expect.len * @bitSizeOf(usize);
|
|
1213
|
+
|
|
1214
|
+
const gpa = std.testing.allocator;
|
|
1215
|
+
// Test decode.
|
|
1216
|
+
var decoded_actual = try FreeSet.init_empty(gpa, blocks_count);
|
|
1217
|
+
defer decoded_actual.deinit(gpa);
|
|
1218
|
+
|
|
1219
|
+
decoded_actual.decode(.blocks_acquired, &.{encoded_expect});
|
|
1220
|
+
|
|
1221
|
+
try std.testing.expectEqual(
|
|
1222
|
+
decoded_expect.len,
|
|
1223
|
+
bit_set_masks(decoded_actual.blocks_acquired).len,
|
|
1224
|
+
);
|
|
1225
|
+
try std.testing.expectEqualSlices(
|
|
1226
|
+
usize,
|
|
1227
|
+
&decoded_expect,
|
|
1228
|
+
bit_set_masks(decoded_actual.blocks_acquired),
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
// Test encode.
|
|
1232
|
+
const encoded_actual = try gpa.alignedAlloc(
|
|
1233
|
+
u8,
|
|
1234
|
+
@alignOf(usize),
|
|
1235
|
+
decoded_actual.encode_size_max(),
|
|
1236
|
+
);
|
|
1237
|
+
defer gpa.free(encoded_actual);
|
|
1238
|
+
|
|
1239
|
+
// Pretend `opened` and `checkpoint_durable` are True as it is asserted in `encode`.
|
|
1240
|
+
decoded_actual.opened = true;
|
|
1241
|
+
decoded_actual.checkpoint_durable = true;
|
|
1242
|
+
const encoded_actual_length = decoded_actual.encode(.blocks_acquired, &.{encoded_actual});
|
|
1243
|
+
try std.testing.expectEqual(encoded_expect.len, encoded_actual_length);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/// Returns the index of the first set/unset bit (relative to the start of the bitset) within
|
|
1247
|
+
/// the range bit_min…bit_max (inclusive…exclusive).
|
|
1248
|
+
fn find_bit(
|
|
1249
|
+
bit_set: DynamicBitSetUnmanaged,
|
|
1250
|
+
bit_min: usize,
|
|
1251
|
+
bit_max: usize,
|
|
1252
|
+
comptime bit_kind: std.bit_set.IteratorOptions.Type,
|
|
1253
|
+
) ?usize {
|
|
1254
|
+
assert(bit_max >= bit_min);
|
|
1255
|
+
assert(bit_max <= bit_set.bit_length);
|
|
1256
|
+
|
|
1257
|
+
const word_start = @divFloor(bit_min, @bitSizeOf(MaskInt)); // Inclusive.
|
|
1258
|
+
const word_offset = @mod(bit_min, @bitSizeOf(MaskInt));
|
|
1259
|
+
const word_end = div_ceil(bit_max, @bitSizeOf(MaskInt)); // Exclusive.
|
|
1260
|
+
const words_total = div_ceil(bit_set.bit_length, @bitSizeOf(MaskInt));
|
|
1261
|
+
if (word_end == word_start) return null;
|
|
1262
|
+
assert(word_end > word_start);
|
|
1263
|
+
|
|
1264
|
+
// Only iterate over the subset of bits that were requested.
|
|
1265
|
+
var iterator = bit_set.iterator(.{ .kind = bit_kind });
|
|
1266
|
+
iterator.words_remain = bit_set.masks[word_start + 1 .. word_end];
|
|
1267
|
+
|
|
1268
|
+
const mask = ~@as(MaskInt, 0);
|
|
1269
|
+
var word = bit_set.masks[word_start];
|
|
1270
|
+
if (bit_kind == .unset) word = ~word;
|
|
1271
|
+
iterator.bits_remain = word & std.math.shl(MaskInt, mask, word_offset);
|
|
1272
|
+
|
|
1273
|
+
if (word_end != words_total) iterator.last_word_mask = mask;
|
|
1274
|
+
|
|
1275
|
+
const b = bit_min - word_offset + (iterator.next() orelse return null);
|
|
1276
|
+
return if (b < bit_max) b else null;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
test "find_bit" {
|
|
1280
|
+
var prng = stdx.PRNG.from_seed_testing();
|
|
1281
|
+
|
|
1282
|
+
const gpa = std.testing.allocator;
|
|
1283
|
+
for (1..(@bitSizeOf(std.DynamicBitSetUnmanaged.MaskInt) * 4) + 1) |bit_length| {
|
|
1284
|
+
var bit_set = try std.DynamicBitSetUnmanaged.initEmpty(gpa, bit_length);
|
|
1285
|
+
defer bit_set.deinit(gpa);
|
|
1286
|
+
|
|
1287
|
+
const p = prng.int_inclusive(usize, 100);
|
|
1288
|
+
|
|
1289
|
+
for (0..bit_length) |b| bit_set.setValue(b, p < prng.int_inclusive(usize, 100));
|
|
1290
|
+
|
|
1291
|
+
for (0..20) |_| try test_find_bit(&prng, bit_set, .set);
|
|
1292
|
+
for (20..40) |_| try test_find_bit(&prng, bit_set, .unset);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
fn test_find_bit(
|
|
1297
|
+
prng: *stdx.PRNG,
|
|
1298
|
+
bit_set: DynamicBitSetUnmanaged,
|
|
1299
|
+
comptime bit_kind: std.bit_set.IteratorOptions.Type,
|
|
1300
|
+
) !void {
|
|
1301
|
+
const bit_min = prng.int_inclusive(usize, bit_set.bit_length - 1);
|
|
1302
|
+
const bit_max = prng.range_inclusive(usize, bit_min, bit_set.bit_length);
|
|
1303
|
+
assert(bit_max >= bit_min);
|
|
1304
|
+
assert(bit_max <= bit_set.bit_length);
|
|
1305
|
+
|
|
1306
|
+
const bit_actual = find_bit(bit_set, bit_min, bit_max, bit_kind);
|
|
1307
|
+
if (bit_actual) |bit| {
|
|
1308
|
+
assert(bit_set.isSet(bit) == (bit_kind == .set));
|
|
1309
|
+
assert(bit >= bit_min);
|
|
1310
|
+
assert(bit < bit_max);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
var iterator = bit_set.iterator(.{ .kind = bit_kind });
|
|
1314
|
+
while (iterator.next()) |bit| {
|
|
1315
|
+
if (bit_min <= bit and bit < bit_max) {
|
|
1316
|
+
try std.testing.expectEqual(bit_actual, bit);
|
|
1317
|
+
break;
|
|
1318
|
+
}
|
|
1319
|
+
} else {
|
|
1320
|
+
try std.testing.expectEqual(bit_actual, null);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
test "FreeSet.acquire part-way through a shard" {
|
|
1325
|
+
const gpa = std.testing.allocator;
|
|
1326
|
+
var set = try FreeSet.open_empty(gpa, FreeSet.shard_bits * 3);
|
|
1327
|
+
defer set.deinit(gpa);
|
|
1328
|
+
|
|
1329
|
+
const reservation_a = set.reserve(1).?;
|
|
1330
|
+
defer set.forfeit(reservation_a);
|
|
1331
|
+
|
|
1332
|
+
const reservation_b = set.reserve(2 * FreeSet.shard_bits).?;
|
|
1333
|
+
defer set.forfeit(reservation_b);
|
|
1334
|
+
|
|
1335
|
+
// Acquire all of reservation B.
|
|
1336
|
+
// At the end, the first shard still has a bit free (reserved by A).
|
|
1337
|
+
for (0..reservation_b.block_count) |i| {
|
|
1338
|
+
const address = set.acquire(reservation_b).?;
|
|
1339
|
+
try std.testing.expectEqual(address - 1, reservation_a.block_count + i);
|
|
1340
|
+
set.verify_index();
|
|
1341
|
+
}
|
|
1342
|
+
try std.testing.expectEqual(set.acquire(reservation_b), null);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
test "FreeSet decode big bitset into small bitset" {
|
|
1346
|
+
const shard_bits = FreeSet.shard_bits;
|
|
1347
|
+
|
|
1348
|
+
const gpa = std.testing.allocator;
|
|
1349
|
+
var big_set = try FreeSet.open_empty(gpa, 2 * shard_bits);
|
|
1350
|
+
defer big_set.deinit(gpa);
|
|
1351
|
+
|
|
1352
|
+
{
|
|
1353
|
+
// Set up a big bitset (with blocks_count==2*shard_bits) with half the blocks free.
|
|
1354
|
+
const acquired_block_count = @divFloor(big_set.blocks_acquired.bit_length, 2);
|
|
1355
|
+
const reservation = big_set.reserve(acquired_block_count).?;
|
|
1356
|
+
defer big_set.forfeit(reservation);
|
|
1357
|
+
|
|
1358
|
+
for (0..acquired_block_count) |_| {
|
|
1359
|
+
_ = big_set.acquire(reservation);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
var big_buffer = try gpa.alignedAlloc(
|
|
1364
|
+
u8,
|
|
1365
|
+
@alignOf(usize),
|
|
1366
|
+
big_set.encode_size_max(),
|
|
1367
|
+
);
|
|
1368
|
+
defer gpa.free(big_buffer);
|
|
1369
|
+
|
|
1370
|
+
const big_buffer_written = big_set.encode(.blocks_acquired, &.{big_buffer});
|
|
1371
|
+
|
|
1372
|
+
// Decode the serialized big bitset into a smaller bitset (with blocks_count==shard_bits).
|
|
1373
|
+
var small_set = try FreeSet.init_empty(gpa, shard_bits);
|
|
1374
|
+
defer small_set.deinit(gpa);
|
|
1375
|
+
|
|
1376
|
+
small_set.decode(.blocks_acquired, &.{big_buffer[0..big_buffer_written]});
|
|
1377
|
+
for (0..shard_bits) |block| {
|
|
1378
|
+
const address = block + 1;
|
|
1379
|
+
try std.testing.expectEqual(big_set.is_free(address), false);
|
|
1380
|
+
}
|
|
1381
|
+
}
|