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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- 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/client.rb +1 -1
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +2 -2
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
//! Fuzz message bus.
|
|
2
|
+
//!
|
|
3
|
+
//! Here's how it works:
|
|
4
|
+
//! 1. Generate ping messages, ping_client messages, and many random non-ping messages.
|
|
5
|
+
//! 2. Each of the non-ping messages has an intended source and destination.
|
|
6
|
+
//! 3. Until all of those non-ping messages are successfully delivered to their intended
|
|
7
|
+
//! destinations:
|
|
8
|
+
//! - nodes try to deliver their undelivered messages,
|
|
9
|
+
//! - replicas and clients send pings and ping_clients (respectively), to ensure peer
|
|
10
|
+
//! identification,
|
|
11
|
+
//! - replica nodes occasionally send ping_clients, to test peer misidentification handling.
|
|
12
|
+
const std = @import("std");
|
|
13
|
+
const assert = std.debug.assert;
|
|
14
|
+
const maybe = stdx.maybe;
|
|
15
|
+
const log = std.log.scoped(.message_bus_fuzz);
|
|
16
|
+
|
|
17
|
+
const vsr = @import("vsr.zig");
|
|
18
|
+
const constants = vsr.constants;
|
|
19
|
+
const stdx = vsr.stdx;
|
|
20
|
+
const MessageBus = vsr.message_bus.MessageBusType(IO);
|
|
21
|
+
const MessagePool = vsr.message_pool.MessagePool;
|
|
22
|
+
const Message = MessagePool.Message;
|
|
23
|
+
const MessageBuffer = @import("./message_buffer.zig").MessageBuffer;
|
|
24
|
+
const fuzz = @import("testing/fuzz.zig");
|
|
25
|
+
const ratio = stdx.PRNG.ratio;
|
|
26
|
+
const Ratio = stdx.PRNG.Ratio;
|
|
27
|
+
|
|
28
|
+
pub fn main(gpa: std.mem.Allocator, args: fuzz.FuzzArgs) !void {
|
|
29
|
+
const messages_max = args.events_max orelse 200;
|
|
30
|
+
// Usually we don't need nearly this many ticks, but certain combinations of errors can make
|
|
31
|
+
// delivering messages quite time consuming.
|
|
32
|
+
const ticks_max = messages_max * 10_000;
|
|
33
|
+
|
|
34
|
+
var prng = stdx.PRNG.from_seed(args.seed);
|
|
35
|
+
|
|
36
|
+
const replica_count = 3;
|
|
37
|
+
const clients_limit = 2;
|
|
38
|
+
const node_count = replica_count + clients_limit;
|
|
39
|
+
const message_bus_send_probability = ratio(prng.range_inclusive(u64, 1, 10), 10);
|
|
40
|
+
const message_bus_tick_probability = ratio(prng.range_inclusive(u64, 3, 10), 10);
|
|
41
|
+
// Ping often so that listener→connector connections are eventually identified correctly.
|
|
42
|
+
// (Otherwise those messages could stall forever with "no connection to..." errors).
|
|
43
|
+
// Note that this probability is conditional on sending a message.
|
|
44
|
+
const message_bus_ping_probability = ratio(prng.range_inclusive(u64, 2, 5), 10);
|
|
45
|
+
// Occasionally inject an incorrect ping, causing the connection peer type to be misidentified.
|
|
46
|
+
// Note that this probability is conditional on sending a ping.
|
|
47
|
+
const message_bus_ping_misdirect_probability = ratio(prng.range_inclusive(u64, 0, 3), 10);
|
|
48
|
+
|
|
49
|
+
const configuration = &.{
|
|
50
|
+
try std.net.Address.parseIp4("127.0.0.1", 3000),
|
|
51
|
+
try std.net.Address.parseIp4("127.0.0.1", 3001),
|
|
52
|
+
try std.net.Address.parseIp4("127.0.0.1", 3002),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const command_weights = weights: {
|
|
56
|
+
var command_weights = fuzz.random_enum_weights(&prng, vsr.Command);
|
|
57
|
+
// The message bus asserts that no message has command=reserved.
|
|
58
|
+
// Move the weight (rather than just zeroing it) so that we can't end up with total=0.
|
|
59
|
+
command_weights.prepare += command_weights.reserved;
|
|
60
|
+
// ping/ping_client are handled separately since they are used to identify/misidentify the
|
|
61
|
+
// connection.
|
|
62
|
+
command_weights.prepare += command_weights.ping;
|
|
63
|
+
command_weights.prepare += command_weights.ping_client;
|
|
64
|
+
|
|
65
|
+
command_weights.reserved = 0;
|
|
66
|
+
command_weights.ping = 0;
|
|
67
|
+
command_weights.ping_client = 0;
|
|
68
|
+
// requests are interesting since they may originate from both clients and replicas.
|
|
69
|
+
command_weights.request *= 20;
|
|
70
|
+
break :weights command_weights;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
inline for (std.meta.fields(@TypeOf(command_weights))) |field| {
|
|
74
|
+
log.info("command weight: {s} = {}", .{ field.name, @field(command_weights, field.name) });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
var message_pool = try MessagePool.init_capacity(gpa, 128);
|
|
78
|
+
defer message_pool.deinit(gpa);
|
|
79
|
+
|
|
80
|
+
var io = try IO.init(gpa, .{
|
|
81
|
+
.seed = prng.int(u64),
|
|
82
|
+
.recv_partial_probability = ratio(prng.int_inclusive(u64, 10), 10),
|
|
83
|
+
.recv_error_probability = ratio(prng.int_inclusive(u64, 1), 10),
|
|
84
|
+
.recv_after_shutdown_probability = ratio(prng.int_inclusive(u64, 10), 10),
|
|
85
|
+
.send_partial_probability = ratio(prng.int_inclusive(u64, 5), 10),
|
|
86
|
+
.send_corrupt_probability = ratio(prng.int_inclusive(u64, 10), 100),
|
|
87
|
+
.send_error_probability = ratio(prng.int_inclusive(u64, 1), 10),
|
|
88
|
+
.send_now_probability = ratio(prng.int_inclusive(u64, 10), 10),
|
|
89
|
+
.send_after_shutdown_probability = ratio(prng.int_inclusive(u64, 10), 10),
|
|
90
|
+
.close_error_probability = ratio(prng.int_inclusive(u64, 2), 10),
|
|
91
|
+
.shutdown_error_probability = ratio(prng.int_inclusive(u64, 2), 10),
|
|
92
|
+
.accept_error_probability = ratio(prng.int_inclusive(u64, 2), 10),
|
|
93
|
+
.connect_error_probability = ratio(prng.int_inclusive(u64, 2), 10),
|
|
94
|
+
});
|
|
95
|
+
defer io.deinit();
|
|
96
|
+
|
|
97
|
+
// Mapping from message checksum → intended message destination.
|
|
98
|
+
var messages_pending = std.AutoArrayHashMapUnmanaged(u128, MessagePending).empty;
|
|
99
|
+
defer messages_pending.deinit(gpa);
|
|
100
|
+
|
|
101
|
+
var nodes = try gpa.alloc(Node, replica_count + clients_limit);
|
|
102
|
+
defer gpa.free(nodes);
|
|
103
|
+
|
|
104
|
+
for (nodes[0..replica_count], 0..) |*node, i| {
|
|
105
|
+
errdefer for (nodes[0..i]) |*n| n.message_bus.deinit(gpa);
|
|
106
|
+
|
|
107
|
+
node.* = .{
|
|
108
|
+
.prng = &prng,
|
|
109
|
+
.options = .swarm(&prng),
|
|
110
|
+
.messages_pending = &messages_pending,
|
|
111
|
+
.message_bus = try MessageBus.init(
|
|
112
|
+
gpa,
|
|
113
|
+
.{ .replica = @intCast(i) },
|
|
114
|
+
&message_pool,
|
|
115
|
+
Node.on_messages_callback,
|
|
116
|
+
.{
|
|
117
|
+
.configuration = configuration,
|
|
118
|
+
.io = &io,
|
|
119
|
+
.clients_limit = clients_limit,
|
|
120
|
+
.trace = null,
|
|
121
|
+
},
|
|
122
|
+
),
|
|
123
|
+
.id = @intCast(i),
|
|
124
|
+
};
|
|
125
|
+
try node.message_bus.listen();
|
|
126
|
+
}
|
|
127
|
+
defer for (nodes[0..replica_count]) |*node| node.message_bus.deinit(gpa);
|
|
128
|
+
|
|
129
|
+
for (nodes[replica_count..], 0..) |*node, i| {
|
|
130
|
+
errdefer for (nodes[replica_count..][0..i]) |*n| n.message_bus.deinit(gpa);
|
|
131
|
+
|
|
132
|
+
node.* = .{
|
|
133
|
+
.prng = &prng,
|
|
134
|
+
.options = .swarm(&prng),
|
|
135
|
+
.messages_pending = &messages_pending,
|
|
136
|
+
.message_bus = try MessageBus.init(
|
|
137
|
+
gpa,
|
|
138
|
+
.{ .client = replica_count + i },
|
|
139
|
+
&message_pool,
|
|
140
|
+
Node.on_messages_callback,
|
|
141
|
+
.{
|
|
142
|
+
.configuration = configuration,
|
|
143
|
+
.io = &io,
|
|
144
|
+
.clients_limit = null,
|
|
145
|
+
.trace = null,
|
|
146
|
+
},
|
|
147
|
+
),
|
|
148
|
+
.id = @intCast(replica_count + i),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
defer for (nodes[replica_count..]) |*node| node.message_bus.deinit(gpa);
|
|
152
|
+
|
|
153
|
+
// Allocate extra for the pings and ping_clients, which are not counted by messages_max since we
|
|
154
|
+
// don't track their delivery. (Pings/ping_clients are used to identify (or misidentify!) the
|
|
155
|
+
// peer type of nodes).
|
|
156
|
+
var messages = try std.ArrayListAlignedUnmanaged(
|
|
157
|
+
[constants.message_size_max]u8,
|
|
158
|
+
constants.sector_size,
|
|
159
|
+
).initCapacity(gpa, messages_max + node_count);
|
|
160
|
+
defer messages.deinit(gpa);
|
|
161
|
+
|
|
162
|
+
for (0..replica_count) |replica| {
|
|
163
|
+
const message = messages.addOneAssumeCapacity();
|
|
164
|
+
const message_header: *vsr.Header =
|
|
165
|
+
@alignCast(std.mem.bytesAsValue(vsr.Header, message[0..@sizeOf(vsr.Header)]));
|
|
166
|
+
message_header.* = random_header(&prng, .ping);
|
|
167
|
+
message_header.replica = @intCast(replica);
|
|
168
|
+
message_header.set_checksum_body(message[@sizeOf(vsr.Header)..message_header.size]);
|
|
169
|
+
message_header.set_checksum();
|
|
170
|
+
|
|
171
|
+
assert(std.meta.eql(message_header.peer_type(), .{ .replica = @intCast(replica) }));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (0..clients_limit) |i| {
|
|
175
|
+
const client = replica_count + i;
|
|
176
|
+
const message = messages.addOneAssumeCapacity();
|
|
177
|
+
const message_header: *vsr.Header =
|
|
178
|
+
@alignCast(std.mem.bytesAsValue(vsr.Header, message[0..@sizeOf(vsr.Header)]));
|
|
179
|
+
message_header.* = random_header(&prng, .ping_client);
|
|
180
|
+
message_header.into(.ping_client).?.client = client;
|
|
181
|
+
message_header.set_checksum_body(message[@sizeOf(vsr.Header)..message_header.size]);
|
|
182
|
+
message_header.set_checksum();
|
|
183
|
+
|
|
184
|
+
assert(std.meta.eql(message_header.peer_type(), .{ .client = client }));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (0..messages_max) |_| {
|
|
188
|
+
const message = messages.addOneAssumeCapacity();
|
|
189
|
+
const message_header: *vsr.Header =
|
|
190
|
+
@alignCast(std.mem.bytesAsValue(vsr.Header, message[0..@sizeOf(vsr.Header)]));
|
|
191
|
+
const node_source: u8 = @intCast(prng.index(nodes));
|
|
192
|
+
|
|
193
|
+
const message_body_size: u32 =
|
|
194
|
+
@min(fuzz.random_int_exponential(&prng, u32, 256), constants.message_body_size_max);
|
|
195
|
+
const message_body = message[@sizeOf(vsr.Header)..][0..message_body_size];
|
|
196
|
+
|
|
197
|
+
message_header.* = random_header(&prng, .reserved);
|
|
198
|
+
message_header.size += message_body_size;
|
|
199
|
+
prng.fill(message_body);
|
|
200
|
+
|
|
201
|
+
// invalid() is only checked by replica, not message bus, so we can mostly ignore the
|
|
202
|
+
// command-specific header data. However, we need to keep it valid enough for peer_type() to
|
|
203
|
+
// be useful, otherwise the "replicas" will never actually connect.
|
|
204
|
+
if (nodes[node_source].message_bus.process == .replica) {
|
|
205
|
+
message_header.replica = node_source;
|
|
206
|
+
message_header.command = prng.enum_weighted(vsr.Command, command_weights);
|
|
207
|
+
if (message_header.into(.request)) |request_header| {
|
|
208
|
+
request_header.client = @max(1, fuzz.random_int_exponential(&prng, u128, 3));
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
message_header.replica = @intCast(prng.index(nodes[0..replica_count]));
|
|
212
|
+
message_header.command = .request;
|
|
213
|
+
message_header.into(.request).?.client = node_source;
|
|
214
|
+
}
|
|
215
|
+
message_header.set_checksum_body(message[@sizeOf(vsr.Header)..message_header.size]);
|
|
216
|
+
message_header.set_checksum();
|
|
217
|
+
|
|
218
|
+
const node_target: u8 = if (nodes[node_source].message_bus.process == .replica)
|
|
219
|
+
random_node(&prng, message_header.replica, node_count)
|
|
220
|
+
else
|
|
221
|
+
@intCast(prng.index(nodes[0..replica_count]));
|
|
222
|
+
try messages_pending.putNoClobber(gpa, message_header.checksum, .{
|
|
223
|
+
.buffer = message[0..message_header.size],
|
|
224
|
+
.source = node_source,
|
|
225
|
+
.target = node_target,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (0..ticks_max) |_| {
|
|
230
|
+
if (messages_pending.count() == 0) break;
|
|
231
|
+
|
|
232
|
+
if (prng.chance(message_bus_send_probability)) {
|
|
233
|
+
const message = message_pool.get_message(.reserved).base();
|
|
234
|
+
defer message_pool.unref(message);
|
|
235
|
+
|
|
236
|
+
if (prng.chance(message_bus_ping_probability)) {
|
|
237
|
+
const node_index = prng.index(nodes);
|
|
238
|
+
stdx.copy_disjoint(.inexact, u8, message.buffer, &messages.items[node_index]);
|
|
239
|
+
assert(message.header.command == .ping or message.header.command == .ping_client);
|
|
240
|
+
|
|
241
|
+
const target: u8 = if (message.header.command == .ping)
|
|
242
|
+
random_node(&prng, message.header.replica, node_count)
|
|
243
|
+
else
|
|
244
|
+
@intCast(prng.index(nodes[0..replica_count]));
|
|
245
|
+
|
|
246
|
+
if (message.header.command == .ping_client and
|
|
247
|
+
prng.chance(message_bus_ping_misdirect_probability))
|
|
248
|
+
{
|
|
249
|
+
nodes[random_node(&prng, target, node_count)].send_message(target, message);
|
|
250
|
+
} else {
|
|
251
|
+
nodes[node_index].send_message(target, message);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
const message_pending_index = prng.index(messages_pending.keys());
|
|
255
|
+
const message_pending = &messages_pending.values()[message_pending_index];
|
|
256
|
+
stdx.copy_disjoint(.inexact, u8, message.buffer, message_pending.buffer);
|
|
257
|
+
|
|
258
|
+
nodes[message_pending.source].send_message(message_pending.target, message);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
for (nodes) |*node| {
|
|
263
|
+
if (prng.chance(message_bus_tick_probability)) {
|
|
264
|
+
if (node.message_bus.process == .replica) {
|
|
265
|
+
node.message_bus.tick();
|
|
266
|
+
} else {
|
|
267
|
+
node.message_bus.tick_client();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (prng.chance(node.options.message_resume_probability)) {
|
|
271
|
+
node.message_bus.resume_receive();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
try io.run();
|
|
275
|
+
} else {
|
|
276
|
+
std.debug.panic("only {}/{} messages delivered", .{
|
|
277
|
+
messages_max - messages_pending.count(),
|
|
278
|
+
messages_max,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
assert(messages_pending.count() == 0);
|
|
282
|
+
|
|
283
|
+
log.info("Passed!", .{});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
fn random_header(prng: *stdx.PRNG, command: vsr.Command) vsr.Header {
|
|
287
|
+
return .{
|
|
288
|
+
.checksum = 0,
|
|
289
|
+
.checksum_padding = 0,
|
|
290
|
+
.checksum_body = 0,
|
|
291
|
+
.checksum_body_padding = 0,
|
|
292
|
+
.nonce_reserved = 0,
|
|
293
|
+
.cluster = prng.int(u128), // MessageBus doesn't check cluster.
|
|
294
|
+
.size = @sizeOf(vsr.Header),
|
|
295
|
+
.epoch = 0,
|
|
296
|
+
.view = prng.int(u32),
|
|
297
|
+
.release = vsr.Release.zero,
|
|
298
|
+
.protocol = vsr.Version,
|
|
299
|
+
.command = command,
|
|
300
|
+
.replica = 0,
|
|
301
|
+
.reserved_frame = @splat(0),
|
|
302
|
+
.reserved_command = @splat(0),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fn random_node(prng: *stdx.PRNG, node_exclude: u8, node_count: u8) u8 {
|
|
307
|
+
return (node_exclude + prng.range_inclusive(u8, 1, node_count - 1)) % node_count;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const NodeOptions = struct {
|
|
311
|
+
message_suspend_probability: Ratio,
|
|
312
|
+
message_resume_probability: Ratio,
|
|
313
|
+
|
|
314
|
+
pub fn swarm(prng: *stdx.PRNG) NodeOptions {
|
|
315
|
+
var result: NodeOptions = .{
|
|
316
|
+
.message_suspend_probability = ratio(prng.int_inclusive(u64, 5), 10),
|
|
317
|
+
.message_resume_probability = ratio(prng.range_inclusive(u64, 0, 10), 10),
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
if (result.message_suspend_probability.numerator > 0) {
|
|
321
|
+
result.message_resume_probability = ratio(prng.range_inclusive(u64, 2, 10), 10);
|
|
322
|
+
assert(result.message_resume_probability.numerator > 0);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const Node = struct {
|
|
330
|
+
prng: *stdx.PRNG,
|
|
331
|
+
options: NodeOptions,
|
|
332
|
+
messages_pending: *std.AutoArrayHashMapUnmanaged(u128, MessagePending),
|
|
333
|
+
message_bus: MessageBus,
|
|
334
|
+
id: u8,
|
|
335
|
+
|
|
336
|
+
fn send_message(node: *Node, target: u8, message: *Message) void {
|
|
337
|
+
if (target < node.message_bus.replicas_addresses.len) {
|
|
338
|
+
node.message_bus.send_message_to_replica(target, message);
|
|
339
|
+
} else {
|
|
340
|
+
assert(node.message_bus.process == .replica);
|
|
341
|
+
node.message_bus.send_message_to_client(target, message);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
fn on_messages_callback(message_bus: *MessageBus, buffer: *MessageBuffer) void {
|
|
346
|
+
const node: *Node = @fieldParentPtr("message_bus", message_bus);
|
|
347
|
+
while (buffer.next_header()) |header| {
|
|
348
|
+
assert(header.valid_checksum());
|
|
349
|
+
|
|
350
|
+
if (node.prng.chance(node.options.message_suspend_probability)) {
|
|
351
|
+
buffer.suspend_message(&header);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const message = buffer.consume_message(message_bus.pool, &header);
|
|
356
|
+
defer message_bus.unref(message);
|
|
357
|
+
|
|
358
|
+
assert(stdx.equal_bytes(vsr.Header, &header, message.header));
|
|
359
|
+
assert(message.header.valid_checksum_body(message.body_used()));
|
|
360
|
+
|
|
361
|
+
if (node.messages_pending.get(message.header.checksum)) |message_pending| {
|
|
362
|
+
const message_pending_checksum =
|
|
363
|
+
std.mem.bytesAsValue(u128, message_pending.buffer[0..@sizeOf(u128)]).*;
|
|
364
|
+
assert(message_pending_checksum == message.header.checksum);
|
|
365
|
+
|
|
366
|
+
if (message_pending.target == node.id) {
|
|
367
|
+
const message_removed =
|
|
368
|
+
node.messages_pending.swapRemove(message.header.checksum);
|
|
369
|
+
assert(message_removed);
|
|
370
|
+
} else {
|
|
371
|
+
// We aren't the intended recipient of this message.
|
|
372
|
+
//
|
|
373
|
+
// One of the following is true:
|
|
374
|
+
// - We are a replica that was misidentified as a client, either due to:
|
|
375
|
+
// - accidental misidentification due to a request we sent, or
|
|
376
|
+
// - deliberate misidentification a ping_client we sent.
|
|
377
|
+
// - We are a client that was misidentified as a *different* client, due to
|
|
378
|
+
// a "misdirected" ping_client we sent.
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
// Ignore duplicate messages.
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const MessagePending = struct {
|
|
388
|
+
buffer: []const u8,
|
|
389
|
+
source: u8,
|
|
390
|
+
target: u8,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const IO = struct {
|
|
394
|
+
gpa: std.mem.Allocator,
|
|
395
|
+
prng: stdx.PRNG,
|
|
396
|
+
options: Options,
|
|
397
|
+
|
|
398
|
+
servers: std.AutoArrayHashMapUnmanaged(socket_t, SocketServer) = .{},
|
|
399
|
+
connections: std.AutoArrayHashMapUnmanaged(socket_t, SocketConnection) = .{},
|
|
400
|
+
events: EventQueue,
|
|
401
|
+
ticks: u64 = 0,
|
|
402
|
+
fd_next: socket_t = 1,
|
|
403
|
+
/// Count the number of fds/sockets which have been created but not closed.
|
|
404
|
+
/// Unlike `connection.closed`, this is tracking whether sockets were explicitly closed by the
|
|
405
|
+
/// message bus.
|
|
406
|
+
fds_open: u32 = 0,
|
|
407
|
+
|
|
408
|
+
const posix = std.posix;
|
|
409
|
+
// We can't specify io/linux.zig since it won't compile on windows, which means that we are
|
|
410
|
+
// potentially using different error sets for different OS's, which means that fuzzer seeds are
|
|
411
|
+
// only reproducible on the same OS.
|
|
412
|
+
const RealIO = @import("io.zig").IO;
|
|
413
|
+
pub const AcceptError = RealIO.AcceptError;
|
|
414
|
+
pub const CloseError = RealIO.CloseError;
|
|
415
|
+
pub const ConnectError = RealIO.ConnectError;
|
|
416
|
+
pub const RecvError = RealIO.RecvError;
|
|
417
|
+
pub const SendError = RealIO.SendError;
|
|
418
|
+
pub const TimeoutError = RealIO.TimeoutError;
|
|
419
|
+
pub const socket_t = i32;
|
|
420
|
+
pub const fd_t = i32;
|
|
421
|
+
const EventQueue = std.PriorityQueue(Event, void, Event.less_than);
|
|
422
|
+
|
|
423
|
+
pub const Options = struct {
|
|
424
|
+
seed: u64 = 0,
|
|
425
|
+
recv_partial_probability: Ratio,
|
|
426
|
+
recv_error_probability: Ratio,
|
|
427
|
+
recv_after_shutdown_probability: Ratio,
|
|
428
|
+
send_partial_probability: Ratio,
|
|
429
|
+
send_corrupt_probability: Ratio,
|
|
430
|
+
send_error_probability: Ratio,
|
|
431
|
+
send_now_probability: Ratio,
|
|
432
|
+
send_after_shutdown_probability: Ratio,
|
|
433
|
+
close_error_probability: Ratio,
|
|
434
|
+
shutdown_error_probability: Ratio,
|
|
435
|
+
accept_error_probability: Ratio,
|
|
436
|
+
connect_error_probability: Ratio,
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const SocketServer = struct {
|
|
440
|
+
address: std.net.Address,
|
|
441
|
+
/// Invariant: completion.operation == .connect
|
|
442
|
+
backlog: std.ArrayListUnmanaged(*Completion) = .empty,
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const SocketConnection = struct {
|
|
446
|
+
shutdown_recv: bool = false,
|
|
447
|
+
shutdown_send: bool = false,
|
|
448
|
+
/// There is at most one send() and at most one recv() pending per socket.
|
|
449
|
+
pending_send: bool = false,
|
|
450
|
+
pending_recv: bool = false,
|
|
451
|
+
|
|
452
|
+
closed: bool = false,
|
|
453
|
+
remote: ?socket_t,
|
|
454
|
+
sending: std.ArrayListUnmanaged(u8) = .empty,
|
|
455
|
+
sending_offset: u32 = 0,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const Event = struct {
|
|
459
|
+
ready_at: stdx.Instant,
|
|
460
|
+
completion: *Completion,
|
|
461
|
+
|
|
462
|
+
fn less_than(_: void, a: Event, b: Event) std.math.Order {
|
|
463
|
+
return std.math.order(a.ready_at.ns, b.ready_at.ns);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const Operation = union(enum) {
|
|
468
|
+
accept: struct { socket: socket_t },
|
|
469
|
+
close: struct { fd: fd_t },
|
|
470
|
+
connect: struct { socket: socket_t, address: std.net.Address },
|
|
471
|
+
recv: struct { socket: socket_t, buffer: []u8 },
|
|
472
|
+
send: struct { socket: socket_t, buffer: []const u8 },
|
|
473
|
+
timeout,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
pub const Completion = struct {
|
|
477
|
+
context: *MessageBus,
|
|
478
|
+
callback: union(enum) {
|
|
479
|
+
accept: *const fn (*MessageBus, *Completion, AcceptError!socket_t) void,
|
|
480
|
+
close: *const fn (*MessageBus, *Completion, CloseError!void) void,
|
|
481
|
+
connect: *const fn (*MessageBus, *Completion, ConnectError!void) void,
|
|
482
|
+
recv: *const fn (*MessageBus, *Completion, RecvError!usize) void,
|
|
483
|
+
send: *const fn (*MessageBus, *Completion, SendError!usize) void,
|
|
484
|
+
timeout: *const fn (*MessageBus, *Completion, TimeoutError!void) void,
|
|
485
|
+
},
|
|
486
|
+
operation: Operation,
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
pub fn init(gpa: std.mem.Allocator, options: Options) !IO {
|
|
490
|
+
var events = EventQueue.init(gpa, {});
|
|
491
|
+
errdefer events.deinit();
|
|
492
|
+
|
|
493
|
+
return .{
|
|
494
|
+
.gpa = gpa,
|
|
495
|
+
.prng = stdx.PRNG.from_seed(options.seed),
|
|
496
|
+
.options = options,
|
|
497
|
+
.events = events,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
pub fn deinit(io: *IO) void {
|
|
502
|
+
// Servers were already cleaned up by io.close_socket().
|
|
503
|
+
assert(io.servers.count() == 0);
|
|
504
|
+
assert(io.fds_open == 0);
|
|
505
|
+
|
|
506
|
+
for (io.connections.values()) |*connection| {
|
|
507
|
+
assert(connection.closed);
|
|
508
|
+
connection.sending.deinit(io.gpa);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
io.events.deinit();
|
|
512
|
+
io.connections.deinit(io.gpa);
|
|
513
|
+
io.servers.deinit(io.gpa);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
pub fn run(io: *IO) !void {
|
|
517
|
+
while (try io.step()) {}
|
|
518
|
+
io.ticks += 1;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
fn step(io: *IO) !bool {
|
|
522
|
+
const event_peek = io.events.peek() orelse return false;
|
|
523
|
+
if (event_peek.ready_at.ns <= io.tick_instant().ns) {
|
|
524
|
+
const event = io.events.remove();
|
|
525
|
+
switch (try io.complete(event.completion)) {
|
|
526
|
+
.retry => io.enqueue(event.completion),
|
|
527
|
+
.done => {},
|
|
528
|
+
}
|
|
529
|
+
return true;
|
|
530
|
+
} else {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
fn complete(io: *IO, completion: *Completion) !enum { done, retry } {
|
|
536
|
+
const gpa = io.gpa;
|
|
537
|
+
switch (completion.operation) {
|
|
538
|
+
.accept => |operation| {
|
|
539
|
+
if (io.prng.chance(io.options.accept_error_probability)) {
|
|
540
|
+
const result: AcceptError!socket_t = io.prng.error_uniform(AcceptError);
|
|
541
|
+
completion.callback.accept(completion.context, completion, result);
|
|
542
|
+
return .done;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const server = io.servers.getPtr(operation.socket).?;
|
|
546
|
+
if (server.backlog.items.len == 0) return .retry;
|
|
547
|
+
|
|
548
|
+
const local_fd = io.fd_next;
|
|
549
|
+
io.fd_next += 1;
|
|
550
|
+
io.fds_open += 1;
|
|
551
|
+
|
|
552
|
+
const remote_completion =
|
|
553
|
+
server.backlog.swapRemove(io.prng.index(server.backlog.items));
|
|
554
|
+
const remote_fd = remote_completion.operation.connect.socket;
|
|
555
|
+
|
|
556
|
+
try io.connections.putNoClobber(io.gpa, local_fd, .{ .remote = remote_fd });
|
|
557
|
+
try io.connections.putNoClobber(io.gpa, remote_fd, .{ .remote = local_fd });
|
|
558
|
+
|
|
559
|
+
const result_accept: AcceptError!socket_t = local_fd;
|
|
560
|
+
const result_connect: ConnectError!void = {};
|
|
561
|
+
|
|
562
|
+
completion.callback.accept(
|
|
563
|
+
completion.context,
|
|
564
|
+
completion,
|
|
565
|
+
result_accept,
|
|
566
|
+
);
|
|
567
|
+
remote_completion.callback.connect(
|
|
568
|
+
remote_completion.context,
|
|
569
|
+
remote_completion,
|
|
570
|
+
result_connect,
|
|
571
|
+
);
|
|
572
|
+
},
|
|
573
|
+
.close => |operation| {
|
|
574
|
+
const local = io.connections.getPtr(operation.fd) orelse {
|
|
575
|
+
// close() was called but we didn't ever connect.
|
|
576
|
+
// (This happens when we injected an error into connect()).
|
|
577
|
+
const result: CloseError!void = {};
|
|
578
|
+
completion.callback.close(completion.context, completion, result);
|
|
579
|
+
return .done;
|
|
580
|
+
};
|
|
581
|
+
assert(local.closed);
|
|
582
|
+
|
|
583
|
+
if (io.prng.chance(io.options.close_error_probability)) {
|
|
584
|
+
const result: CloseError!void = io.prng.error_uniform(CloseError);
|
|
585
|
+
completion.callback.close(completion.context, completion, result);
|
|
586
|
+
} else {
|
|
587
|
+
const result: CloseError!void = {};
|
|
588
|
+
completion.callback.close(completion.context, completion, result);
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
.connect => |operation| {
|
|
592
|
+
if (io.prng.chance(io.options.connect_error_probability)) {
|
|
593
|
+
const result: ConnectError!void = io.prng.error_uniform(ConnectError);
|
|
594
|
+
completion.callback.connect(completion.context, completion, result);
|
|
595
|
+
return .done;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
for (io.servers.values()) |*server| {
|
|
599
|
+
if (server.address.eql(operation.address)) {
|
|
600
|
+
try server.backlog.append(gpa, completion);
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
// No one listening at that address.
|
|
605
|
+
const result: ConnectError!void = error.ConnectionRefused;
|
|
606
|
+
completion.callback.connect(completion.context, completion, result);
|
|
607
|
+
return .done;
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
.send => |operation| {
|
|
611
|
+
const sender = io.connections.getPtr(operation.socket).?;
|
|
612
|
+
assert(!sender.closed);
|
|
613
|
+
assert(sender.pending_send);
|
|
614
|
+
sender.pending_send = false;
|
|
615
|
+
|
|
616
|
+
if (io.prng.chance(io.options.send_error_probability)) {
|
|
617
|
+
const result: SendError!usize = io.prng.error_uniform(SendError);
|
|
618
|
+
completion.callback.send(completion.context, completion, result);
|
|
619
|
+
|
|
620
|
+
// If there is a pending recv(), we need to make sure that it fails (rather than
|
|
621
|
+
// potentially stalling and preventing MessageBus from calling shutdown/close).
|
|
622
|
+
sender.closed = true;
|
|
623
|
+
return .done;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (sender.shutdown_send) {
|
|
627
|
+
if (io.prng.chance(io.options.send_after_shutdown_probability)) {
|
|
628
|
+
// Sometimes allow the send() to complete even after shutdown().
|
|
629
|
+
} else {
|
|
630
|
+
const result: SendError!usize = error.BrokenPipe;
|
|
631
|
+
completion.callback.send(completion.context, completion, result);
|
|
632
|
+
return .done;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const send_size = if (io.prng.chance(io.options.send_partial_probability))
|
|
637
|
+
io.prng.range_inclusive(usize, 0, operation.buffer.len)
|
|
638
|
+
else
|
|
639
|
+
operation.buffer.len;
|
|
640
|
+
|
|
641
|
+
const send_buffer = operation.buffer[0..send_size];
|
|
642
|
+
try sender.sending.appendSlice(gpa, send_buffer);
|
|
643
|
+
|
|
644
|
+
if (io.prng.chance(io.options.send_corrupt_probability)) {
|
|
645
|
+
if (send_buffer.len == 0) {
|
|
646
|
+
try sender.sending.append(gpa, io.prng.int(u8));
|
|
647
|
+
} else {
|
|
648
|
+
const corrupt_byte = io.prng.index(send_buffer);
|
|
649
|
+
sender.sending.items[sender.sending.items.len - corrupt_byte - 1] ^=
|
|
650
|
+
io.prng.bit(u8);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const result: SendError!usize = send_size;
|
|
655
|
+
completion.callback.send(completion.context, completion, result);
|
|
656
|
+
},
|
|
657
|
+
.recv => |operation| {
|
|
658
|
+
const receiver = io.connections.getPtr(operation.socket).?;
|
|
659
|
+
assert(receiver.pending_recv);
|
|
660
|
+
// If we had a send() fail, then we must avoid requeueing this operation.
|
|
661
|
+
maybe(receiver.closed);
|
|
662
|
+
|
|
663
|
+
if (receiver.closed or io.prng.chance(io.options.recv_error_probability)) {
|
|
664
|
+
const result: RecvError!usize = io.prng.error_uniform(RecvError);
|
|
665
|
+
completion.callback.recv(completion.context, completion, result);
|
|
666
|
+
return .done;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (receiver.shutdown_recv) {
|
|
670
|
+
if (io.prng.chance(io.options.recv_after_shutdown_probability)) {
|
|
671
|
+
// Sometimes allow the recv() to complete even after shutdown().
|
|
672
|
+
} else {
|
|
673
|
+
const result: RecvError!usize = 0;
|
|
674
|
+
completion.callback.recv(completion.context, completion, result);
|
|
675
|
+
return .done;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const sender_fd = receiver.remote orelse return .retry;
|
|
680
|
+
const sender = io.connections.getPtr(sender_fd).?;
|
|
681
|
+
if (sender.closed) {
|
|
682
|
+
const result: RecvError!usize = error.ConnectionResetByPeer;
|
|
683
|
+
completion.callback.recv(completion.context, completion, result);
|
|
684
|
+
return .done;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
assert(sender.sending_offset <= sender.sending.items.len);
|
|
688
|
+
if (sender.sending_offset == sender.sending.items.len) {
|
|
689
|
+
if (sender.shutdown_send) {
|
|
690
|
+
receiver.shutdown_recv = true;
|
|
691
|
+
receiver.pending_recv = false;
|
|
692
|
+
// Connection was half-closed, and we have received all the buffered data,
|
|
693
|
+
// so now it can close.
|
|
694
|
+
const result: RecvError!usize = 0;
|
|
695
|
+
completion.callback.recv(completion.context, completion, result);
|
|
696
|
+
} else {
|
|
697
|
+
// Sender is still open, but has nothing to deliver right now.
|
|
698
|
+
return .retry;
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
const recv_size_max = @min(
|
|
702
|
+
operation.buffer.len,
|
|
703
|
+
sender.sending.items.len - sender.sending_offset,
|
|
704
|
+
);
|
|
705
|
+
const recv_size = if (io.prng.chance(io.options.recv_partial_probability))
|
|
706
|
+
io.prng.range_inclusive(u64, 1, recv_size_max)
|
|
707
|
+
else
|
|
708
|
+
recv_size_max;
|
|
709
|
+
assert(recv_size > 0);
|
|
710
|
+
assert(recv_size <= recv_size_max);
|
|
711
|
+
|
|
712
|
+
stdx.copy_disjoint(
|
|
713
|
+
.inexact,
|
|
714
|
+
u8,
|
|
715
|
+
operation.buffer[0..recv_size],
|
|
716
|
+
sender.sending.items[sender.sending_offset..][0..recv_size],
|
|
717
|
+
);
|
|
718
|
+
sender.sending_offset += @intCast(recv_size);
|
|
719
|
+
receiver.pending_recv = false;
|
|
720
|
+
|
|
721
|
+
const result: RecvError!usize = recv_size;
|
|
722
|
+
completion.callback.recv(completion.context, completion, result);
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
.timeout => {
|
|
726
|
+
const result: TimeoutError!void = {};
|
|
727
|
+
completion.callback.timeout(completion.context, completion, result);
|
|
728
|
+
},
|
|
729
|
+
}
|
|
730
|
+
return .done;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
pub fn open_socket_tcp(io: *IO, _: u32, _: RealIO.TCPOptions) !socket_t {
|
|
734
|
+
const socket = io.fd_next;
|
|
735
|
+
io.fd_next += 1;
|
|
736
|
+
io.fds_open += 1;
|
|
737
|
+
return @intCast(socket);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
pub fn listen(
|
|
741
|
+
io: *IO,
|
|
742
|
+
fd: socket_t,
|
|
743
|
+
address: std.net.Address,
|
|
744
|
+
_: RealIO.ListenOptions,
|
|
745
|
+
) !std.net.Address {
|
|
746
|
+
io.servers.putNoClobber(io.gpa, fd, .{ .address = address }) catch @panic("OOM");
|
|
747
|
+
return address;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
pub fn close_socket(io: *IO, socket: socket_t) void {
|
|
751
|
+
io.fds_open -= 1;
|
|
752
|
+
|
|
753
|
+
if (io.servers.getPtr(socket)) |server| {
|
|
754
|
+
assert(!io.connections.contains(socket));
|
|
755
|
+
server.backlog.deinit(io.gpa);
|
|
756
|
+
|
|
757
|
+
const server_removed = io.servers.swapRemove(socket);
|
|
758
|
+
assert(server_removed);
|
|
759
|
+
} else if (io.connections.getPtr(socket)) |connection| {
|
|
760
|
+
assert(!io.servers.contains(socket));
|
|
761
|
+
|
|
762
|
+
connection.closed = true;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
pub fn shutdown(io: *IO, socket: socket_t, how: posix.ShutdownHow) posix.ShutdownError!void {
|
|
767
|
+
if (io.prng.chance(io.options.shutdown_error_probability)) {
|
|
768
|
+
return io.prng.error_uniform(posix.ShutdownError);
|
|
769
|
+
} else {
|
|
770
|
+
if (how == .both or how == .recv) io.connections.getPtr(socket).?.shutdown_recv = false;
|
|
771
|
+
if (how == .both or how == .send) io.connections.getPtr(socket).?.shutdown_send = false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
pub fn accept(
|
|
776
|
+
io: *IO,
|
|
777
|
+
comptime Context: type,
|
|
778
|
+
context: Context,
|
|
779
|
+
comptime callback: fn (Context, *Completion, AcceptError!socket_t) void,
|
|
780
|
+
completion: *Completion,
|
|
781
|
+
socket: socket_t,
|
|
782
|
+
) void {
|
|
783
|
+
completion.* = .{
|
|
784
|
+
.context = context,
|
|
785
|
+
.callback = .{ .accept = callback },
|
|
786
|
+
.operation = .{ .accept = .{ .socket = socket } },
|
|
787
|
+
};
|
|
788
|
+
io.enqueue(completion);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
pub fn close(
|
|
792
|
+
io: *IO,
|
|
793
|
+
comptime Context: type,
|
|
794
|
+
context: Context,
|
|
795
|
+
comptime callback: fn (Context, *Completion, CloseError!void) void,
|
|
796
|
+
completion: *Completion,
|
|
797
|
+
fd: fd_t,
|
|
798
|
+
) void {
|
|
799
|
+
// There are no pending operations on this socket.
|
|
800
|
+
var events = io.events.iterator();
|
|
801
|
+
while (events.next()) |event| {
|
|
802
|
+
switch (event.completion.operation) {
|
|
803
|
+
inline .accept, .connect, .recv, .send => |data| assert(data.socket != fd),
|
|
804
|
+
.close => |data| assert(data.fd != fd),
|
|
805
|
+
.timeout => {},
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
io.fds_open -= 1;
|
|
810
|
+
if (io.connections.getPtr(fd)) |connection| {
|
|
811
|
+
connection.closed = true;
|
|
812
|
+
} else {
|
|
813
|
+
// We might be closing a socket which didn't ever connect().
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
completion.* = .{
|
|
817
|
+
.context = context,
|
|
818
|
+
.callback = .{ .close = callback },
|
|
819
|
+
.operation = .{ .close = .{ .fd = fd } },
|
|
820
|
+
};
|
|
821
|
+
io.enqueue(completion);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
pub fn connect(
|
|
825
|
+
io: *IO,
|
|
826
|
+
comptime Context: type,
|
|
827
|
+
context: Context,
|
|
828
|
+
comptime callback: fn (Context, *Completion, ConnectError!void) void,
|
|
829
|
+
completion: *Completion,
|
|
830
|
+
socket: socket_t,
|
|
831
|
+
address: std.net.Address,
|
|
832
|
+
) void {
|
|
833
|
+
completion.* = .{
|
|
834
|
+
.context = context,
|
|
835
|
+
.callback = .{ .connect = callback },
|
|
836
|
+
.operation = .{ .connect = .{ .socket = socket, .address = address } },
|
|
837
|
+
};
|
|
838
|
+
io.enqueue(completion);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
pub fn recv(
|
|
842
|
+
io: *IO,
|
|
843
|
+
comptime Context: type,
|
|
844
|
+
context: Context,
|
|
845
|
+
comptime callback: fn (Context, *Completion, RecvError!usize) void,
|
|
846
|
+
completion: *Completion,
|
|
847
|
+
socket: socket_t,
|
|
848
|
+
buffer: []u8,
|
|
849
|
+
) void {
|
|
850
|
+
const connection = io.connections.getPtr(socket).?;
|
|
851
|
+
assert(!connection.closed);
|
|
852
|
+
assert(!connection.pending_recv);
|
|
853
|
+
connection.pending_recv = true;
|
|
854
|
+
|
|
855
|
+
completion.* = .{
|
|
856
|
+
.context = context,
|
|
857
|
+
.callback = .{ .recv = callback },
|
|
858
|
+
.operation = .{ .recv = .{ .socket = socket, .buffer = buffer } },
|
|
859
|
+
};
|
|
860
|
+
io.enqueue(completion);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
pub fn send(
|
|
864
|
+
io: *IO,
|
|
865
|
+
comptime Context: type,
|
|
866
|
+
context: Context,
|
|
867
|
+
comptime callback: fn (Context, *Completion, SendError!usize) void,
|
|
868
|
+
completion: *Completion,
|
|
869
|
+
socket: socket_t,
|
|
870
|
+
buffer: []const u8,
|
|
871
|
+
) void {
|
|
872
|
+
const connection = io.connections.getPtr(socket).?;
|
|
873
|
+
assert(!connection.closed);
|
|
874
|
+
assert(!connection.pending_send);
|
|
875
|
+
connection.pending_send = true;
|
|
876
|
+
|
|
877
|
+
completion.* = .{
|
|
878
|
+
.context = context,
|
|
879
|
+
.callback = .{ .send = callback },
|
|
880
|
+
.operation = .{ .send = .{ .socket = socket, .buffer = buffer } },
|
|
881
|
+
};
|
|
882
|
+
io.enqueue(completion);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
pub fn send_now(io: *IO, socket: socket_t, buffer: []const u8) ?usize {
|
|
886
|
+
const sender = io.connections.getPtr(socket).?;
|
|
887
|
+
assert(!sender.closed);
|
|
888
|
+
assert(!sender.shutdown_send);
|
|
889
|
+
assert(!sender.pending_send);
|
|
890
|
+
|
|
891
|
+
if (!io.prng.chance(io.options.send_now_probability)) return null;
|
|
892
|
+
|
|
893
|
+
const send_size = if (io.prng.chance(io.options.send_partial_probability))
|
|
894
|
+
io.prng.index(buffer)
|
|
895
|
+
else
|
|
896
|
+
buffer.len;
|
|
897
|
+
|
|
898
|
+
sender.sending.appendSlice(io.gpa, buffer[0..send_size]) catch @panic("OOM");
|
|
899
|
+
return send_size;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
pub fn timeout(
|
|
903
|
+
io: *IO,
|
|
904
|
+
comptime Context: type,
|
|
905
|
+
context: Context,
|
|
906
|
+
comptime callback: fn (Context, *Completion, TimeoutError!void) void,
|
|
907
|
+
completion: *Completion,
|
|
908
|
+
nanoseconds: u63,
|
|
909
|
+
) void {
|
|
910
|
+
completion.* = .{
|
|
911
|
+
.context = context,
|
|
912
|
+
.callback = .{ .timeout = callback },
|
|
913
|
+
.operation = .timeout,
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
const jitter_mean = 1_000;
|
|
917
|
+
const jitter = fuzz.random_int_exponential(&io.prng, u64, jitter_mean);
|
|
918
|
+
io.events.add(.{
|
|
919
|
+
.completion = completion,
|
|
920
|
+
.ready_at = io.tick_instant().add(.{ .ns = (nanoseconds -| jitter_mean) + jitter }),
|
|
921
|
+
}) catch @panic("OOM");
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
fn tick_instant(io: *const IO) stdx.Instant {
|
|
925
|
+
return .{ .ns = io.ticks * constants.tick_ms * std.time.ns_per_ms };
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
fn enqueue(io: *IO, completion: *Completion) void {
|
|
929
|
+
const tick_ns = constants.tick_ms * std.time.ns_per_ms;
|
|
930
|
+
const delay_ns = fuzz.random_int_exponential(&io.prng, u64, 10 * tick_ns);
|
|
931
|
+
io.events.add(.{
|
|
932
|
+
.completion = completion,
|
|
933
|
+
.ready_at = io.tick_instant().add(.{ .ns = delay_ns }),
|
|
934
|
+
}) catch @panic("OOM");
|
|
935
|
+
}
|
|
936
|
+
};
|