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,1450 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const stdx = @import("stdx");
|
|
4
|
+
const assert = std.debug.assert;
|
|
5
|
+
const maybe = stdx.maybe;
|
|
6
|
+
const log = std.log.scoped(.amqp);
|
|
7
|
+
|
|
8
|
+
const vsr = @import("../vsr.zig");
|
|
9
|
+
const constants = vsr.constants;
|
|
10
|
+
const IO = vsr.io.IO;
|
|
11
|
+
|
|
12
|
+
const spec = @import("./amqp/spec.zig");
|
|
13
|
+
const protocol = @import("./amqp/protocol.zig");
|
|
14
|
+
const types = @import("./amqp/types.zig");
|
|
15
|
+
const fatal = protocol.fatal;
|
|
16
|
+
|
|
17
|
+
const ErrorCodes = protocol.ErrorCodes;
|
|
18
|
+
const Channel = protocol.Channel;
|
|
19
|
+
|
|
20
|
+
pub const Decoder = protocol.Decoder;
|
|
21
|
+
pub const Encoder = protocol.Encoder;
|
|
22
|
+
|
|
23
|
+
pub const ConnectOptions = types.ConnectOptions;
|
|
24
|
+
pub const ExchangeDeclareOptions = types.ExchangeDeclareOptions;
|
|
25
|
+
pub const QueueDeclareOptions = types.QueueDeclareOptions;
|
|
26
|
+
pub const BasicPublishOptions = types.BasicPublishOptions;
|
|
27
|
+
pub const GetMessagePropertiesResult = types.GetMessagePropertiesResult;
|
|
28
|
+
pub const GetMessageOptions = types.GetMessageOptions;
|
|
29
|
+
pub const BasicNackOptions = types.BasicNackOptions;
|
|
30
|
+
|
|
31
|
+
pub const tcp_port_default = protocol.tcp_port_default;
|
|
32
|
+
pub const frame_min_size = protocol.frame_min_size;
|
|
33
|
+
|
|
34
|
+
/// AMQP (Advanced Message Queuing Protocol) 0.9.1 client.
|
|
35
|
+
/// - Uses TigerBeetle's IO interface.
|
|
36
|
+
/// - Single channel only.
|
|
37
|
+
/// - Batched publishing with fixed buffers.
|
|
38
|
+
/// - Limited consumer capabilities.
|
|
39
|
+
/// - Implements only the methods required by TigerBeetle.
|
|
40
|
+
/// - No error handling: **CAN PANIC**.
|
|
41
|
+
pub const Client = struct {
|
|
42
|
+
pub const Callback = *const fn (self: *Client) void;
|
|
43
|
+
pub const GetMessagePropertiesCallback = *const fn (
|
|
44
|
+
self: *Client,
|
|
45
|
+
result: ?GetMessagePropertiesResult,
|
|
46
|
+
) Decoder.Error!void;
|
|
47
|
+
pub const GetMessageBodyCallback = *const fn (
|
|
48
|
+
self: *Client,
|
|
49
|
+
body: []const u8,
|
|
50
|
+
) Decoder.Error!void;
|
|
51
|
+
|
|
52
|
+
io: *IO,
|
|
53
|
+
fd: ?IO.socket_t = null,
|
|
54
|
+
reply_timeout_ticks: u64,
|
|
55
|
+
|
|
56
|
+
receive_buffer: ReceiveBuffer,
|
|
57
|
+
receive_completion: IO.Completion = undefined,
|
|
58
|
+
|
|
59
|
+
send_buffer: SendBuffer,
|
|
60
|
+
send_completion: IO.Completion = undefined,
|
|
61
|
+
|
|
62
|
+
heartbeat: union(enum) {
|
|
63
|
+
idle,
|
|
64
|
+
sending: IO.Completion,
|
|
65
|
+
} = .idle,
|
|
66
|
+
|
|
67
|
+
action: union(enum) {
|
|
68
|
+
none,
|
|
69
|
+
connect: struct {
|
|
70
|
+
options: ConnectOptions,
|
|
71
|
+
phase: enum {
|
|
72
|
+
dial,
|
|
73
|
+
handshake,
|
|
74
|
+
auth,
|
|
75
|
+
connection_open,
|
|
76
|
+
channel_open,
|
|
77
|
+
confirm_select,
|
|
78
|
+
},
|
|
79
|
+
callback: Callback,
|
|
80
|
+
},
|
|
81
|
+
close: Callback,
|
|
82
|
+
queue_declare: Callback,
|
|
83
|
+
exchange_declare: Callback,
|
|
84
|
+
get_message: GetMessagePropertiesCallback,
|
|
85
|
+
message_body_pending: struct {
|
|
86
|
+
body_size: u64,
|
|
87
|
+
},
|
|
88
|
+
get_message_body: struct {
|
|
89
|
+
body_size: u64,
|
|
90
|
+
callback: GetMessageBodyCallback,
|
|
91
|
+
},
|
|
92
|
+
nack: Callback,
|
|
93
|
+
publish_enqueue: struct {
|
|
94
|
+
count: u32 = 0,
|
|
95
|
+
},
|
|
96
|
+
publish: struct {
|
|
97
|
+
callback: Callback,
|
|
98
|
+
phase: enum { sending, awaiting_confirmation } = .sending,
|
|
99
|
+
},
|
|
100
|
+
} = .none,
|
|
101
|
+
|
|
102
|
+
awaiter: union(enum) {
|
|
103
|
+
none,
|
|
104
|
+
/// Flushes the current send buffer and invokes the callback upon completion.
|
|
105
|
+
/// Invariant: the send buffer must be ready to be flushed.
|
|
106
|
+
send_and_forget: *const fn (self: *Client) void,
|
|
107
|
+
/// Flushes the current send buffer and invokes the callback when a synchronous
|
|
108
|
+
/// AMQP method is received.
|
|
109
|
+
/// Invariant: the send buffer must be ready to be flushed.
|
|
110
|
+
send_and_await_reply: struct {
|
|
111
|
+
channel: Channel,
|
|
112
|
+
state: union(enum) {
|
|
113
|
+
sending,
|
|
114
|
+
awaiting: struct {
|
|
115
|
+
duration_ticks: u64 = 0,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
callback: *const fn (self: *Client, reply: spec.ClientMethod) Decoder.Error!void,
|
|
119
|
+
},
|
|
120
|
+
/// Invokes the callback when an AMQP header frame is received, containing information
|
|
121
|
+
/// about the incoming message.
|
|
122
|
+
/// Invariant: the send buffer must be empty.
|
|
123
|
+
await_content_header: struct {
|
|
124
|
+
channel: Channel,
|
|
125
|
+
duration_ticks: u64 = 0,
|
|
126
|
+
delivery_tag: u64,
|
|
127
|
+
message_count: u32,
|
|
128
|
+
callback: *const fn (
|
|
129
|
+
self: *Client,
|
|
130
|
+
delivery_tag: u64,
|
|
131
|
+
message_count: u32,
|
|
132
|
+
header: Decoder.Header,
|
|
133
|
+
) Decoder.Error!void,
|
|
134
|
+
},
|
|
135
|
+
/// Invokes the callback when an AMQP body frame is received.
|
|
136
|
+
/// Invariant: the send buffer must be empty.
|
|
137
|
+
await_body: struct {
|
|
138
|
+
channel: Channel,
|
|
139
|
+
duration_ticks: u64 = 0,
|
|
140
|
+
callback: *const fn (
|
|
141
|
+
self: *Client,
|
|
142
|
+
body: []const u8,
|
|
143
|
+
) Decoder.Error!void,
|
|
144
|
+
},
|
|
145
|
+
} = .none,
|
|
146
|
+
|
|
147
|
+
publish_confirms: Confirms,
|
|
148
|
+
|
|
149
|
+
pub fn init(
|
|
150
|
+
allocator: std.mem.Allocator,
|
|
151
|
+
options: struct {
|
|
152
|
+
io: *IO,
|
|
153
|
+
message_count_max: u32,
|
|
154
|
+
message_body_size_max: u32,
|
|
155
|
+
reply_timeout_ticks: u64,
|
|
156
|
+
},
|
|
157
|
+
) !Client {
|
|
158
|
+
assert(options.message_count_max > 0);
|
|
159
|
+
assert(options.message_body_size_max > 0);
|
|
160
|
+
assert(options.reply_timeout_ticks > 0);
|
|
161
|
+
|
|
162
|
+
// The worst-case size required to write a frame containing the message body.
|
|
163
|
+
const body_frame_size = Encoder.FrameHeader.size_total +
|
|
164
|
+
options.message_body_size_max +
|
|
165
|
+
@sizeOf(protocol.FrameEnd);
|
|
166
|
+
|
|
167
|
+
const frame_size = @max(frame_min_size, body_frame_size);
|
|
168
|
+
|
|
169
|
+
// Large messages are not expected, but we must be able to receive at least
|
|
170
|
+
// the same frame size we send.
|
|
171
|
+
const receive_buffer = try allocator.alloc(u8, frame_size);
|
|
172
|
+
errdefer allocator.free(receive_buffer);
|
|
173
|
+
|
|
174
|
+
// When publishing messages, the method and header frames (including metadata)
|
|
175
|
+
// are sent in addition to the body frame.
|
|
176
|
+
// TODO: The size could be calculated more efficiently if the variable data had
|
|
177
|
+
// a known size, otherwise, it’s constrained only by the maximum frame size.
|
|
178
|
+
const send_buffer_size = 3 * frame_size * options.message_count_max;
|
|
179
|
+
const send_buffer = try allocator.alloc(u8, send_buffer_size);
|
|
180
|
+
errdefer allocator.free(send_buffer);
|
|
181
|
+
|
|
182
|
+
var publish_confirms: Confirms = try Confirms.init(allocator, options.message_count_max);
|
|
183
|
+
errdefer publish_confirms.deinit(allocator);
|
|
184
|
+
|
|
185
|
+
return .{
|
|
186
|
+
.io = options.io,
|
|
187
|
+
.receive_buffer = ReceiveBuffer.init(receive_buffer),
|
|
188
|
+
.send_buffer = SendBuffer.init(send_buffer),
|
|
189
|
+
.reply_timeout_ticks = options.reply_timeout_ticks,
|
|
190
|
+
.publish_confirms = publish_confirms,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
pub fn deinit(
|
|
195
|
+
self: *Client,
|
|
196
|
+
allocator: std.mem.Allocator,
|
|
197
|
+
) void {
|
|
198
|
+
if (self.fd) |fd| {
|
|
199
|
+
self.io.close_socket(fd);
|
|
200
|
+
self.fd = null;
|
|
201
|
+
}
|
|
202
|
+
self.publish_confirms.deinit(allocator);
|
|
203
|
+
allocator.free(self.send_buffer.buffer);
|
|
204
|
+
allocator.free(self.receive_buffer.buffer);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
pub fn connect(self: *Client, callback: Callback, options: ConnectOptions) !void {
|
|
208
|
+
assert(self.fd == null);
|
|
209
|
+
assert(self.awaiter == .none);
|
|
210
|
+
assert(self.send_buffer.state == .idle);
|
|
211
|
+
assert(self.action == .none);
|
|
212
|
+
self.action = .{ .connect = .{
|
|
213
|
+
.options = options,
|
|
214
|
+
.phase = .dial,
|
|
215
|
+
.callback = callback,
|
|
216
|
+
} };
|
|
217
|
+
|
|
218
|
+
self.fd = try self.io.open_socket_tcp(
|
|
219
|
+
options.host.any.family,
|
|
220
|
+
.{
|
|
221
|
+
// Keeping the default value.
|
|
222
|
+
// Large buffers can cause latency issues.
|
|
223
|
+
.sndbuf = 0,
|
|
224
|
+
.rcvbuf = 0,
|
|
225
|
+
.keepalive = if (constants.tcp_keepalive) .{
|
|
226
|
+
.keepidle = constants.tcp_keepidle,
|
|
227
|
+
.keepintvl = constants.tcp_keepintvl,
|
|
228
|
+
.keepcnt = constants.tcp_keepcnt,
|
|
229
|
+
} else null,
|
|
230
|
+
.user_timeout_ms = constants.tcp_user_timeout_ms,
|
|
231
|
+
.nodelay = constants.tcp_nodelay,
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
errdefer self.io.close_socket(self.fd);
|
|
235
|
+
|
|
236
|
+
self.io.connect(
|
|
237
|
+
*Client,
|
|
238
|
+
self,
|
|
239
|
+
struct {
|
|
240
|
+
fn continuation(
|
|
241
|
+
context: *Client,
|
|
242
|
+
completion: *IO.Completion,
|
|
243
|
+
result: IO.ConnectError!void,
|
|
244
|
+
) void {
|
|
245
|
+
_ = completion;
|
|
246
|
+
_ = result catch fatal("Connection refused.", .{});
|
|
247
|
+
assert(context.action == .connect);
|
|
248
|
+
assert(context.action.connect.phase == .dial);
|
|
249
|
+
|
|
250
|
+
// Start receiving and send the AMQP protocol header:
|
|
251
|
+
context.receive();
|
|
252
|
+
|
|
253
|
+
const encoder = context.send_buffer.encoder();
|
|
254
|
+
encoder.write_bytes(protocol.protocol_header);
|
|
255
|
+
context.action.connect.phase = .handshake;
|
|
256
|
+
context.send_and_await_reply(.global, &connect_dispatch);
|
|
257
|
+
}
|
|
258
|
+
}.continuation,
|
|
259
|
+
&self.receive_completion,
|
|
260
|
+
self.fd.?,
|
|
261
|
+
options.host,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn connect_dispatch(self: *Client, reply: spec.ClientMethod) Decoder.Error!void {
|
|
266
|
+
assert(self.awaiter == .none);
|
|
267
|
+
assert(self.send_buffer.state == .idle);
|
|
268
|
+
assert(self.action == .connect);
|
|
269
|
+
const connection_options = self.action.connect.options;
|
|
270
|
+
|
|
271
|
+
switch (reply) {
|
|
272
|
+
.connection_start => |args| {
|
|
273
|
+
assert(self.action.connect.phase == .handshake);
|
|
274
|
+
|
|
275
|
+
log.info("Connection start received:", .{});
|
|
276
|
+
log.info("version {}.{}", .{ args.version_major, args.version_minor });
|
|
277
|
+
log.info("locales {s}", .{args.locales});
|
|
278
|
+
log.info("mechanisms {s}", .{args.mechanisms});
|
|
279
|
+
try log_table("server_properties", args.server_properties);
|
|
280
|
+
|
|
281
|
+
if (args.version_major != protocol.version.major or
|
|
282
|
+
args.version_minor != protocol.version.minor)
|
|
283
|
+
{
|
|
284
|
+
fatal("Unsuported AMQP server version {}.{}", .{
|
|
285
|
+
args.version_major,
|
|
286
|
+
args.version_minor,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (std.mem.indexOfPosLinear(
|
|
291
|
+
u8,
|
|
292
|
+
args.mechanisms,
|
|
293
|
+
0,
|
|
294
|
+
types.SASLPlainAuth.mechanism,
|
|
295
|
+
) == null) {
|
|
296
|
+
fatal(
|
|
297
|
+
\\AMQP server does not support {s} authentication.
|
|
298
|
+
\\Supported methods: {s}.
|
|
299
|
+
, .{
|
|
300
|
+
types.SASLPlainAuth.mechanism,
|
|
301
|
+
args.mechanisms,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const plain_auth: types.SASLPlainAuth = .{
|
|
306
|
+
.user_name = connection_options.user_name,
|
|
307
|
+
.password = connection_options.password,
|
|
308
|
+
};
|
|
309
|
+
const method: spec.ServerMethod = .{ .connection_start_ok = .{
|
|
310
|
+
.client_properties = connection_options.properties.table(),
|
|
311
|
+
.mechanism = types.SASLPlainAuth.mechanism,
|
|
312
|
+
.response = plain_auth.response(),
|
|
313
|
+
.locale = connection_options.locale orelse first: {
|
|
314
|
+
var iterator = std.mem.splitScalar(u8, args.locales, ' ');
|
|
315
|
+
break :first iterator.next().?;
|
|
316
|
+
},
|
|
317
|
+
} };
|
|
318
|
+
|
|
319
|
+
const encoder = self.send_buffer.encoder();
|
|
320
|
+
method.encode(.global, encoder);
|
|
321
|
+
self.action.connect.phase = .auth;
|
|
322
|
+
self.send_and_await_reply(.global, &connect_dispatch);
|
|
323
|
+
},
|
|
324
|
+
.connection_secure => fatal(
|
|
325
|
+
"Connection secure not supported.",
|
|
326
|
+
.{},
|
|
327
|
+
),
|
|
328
|
+
.connection_tune => |args| {
|
|
329
|
+
assert(self.action.connect.phase == .auth);
|
|
330
|
+
|
|
331
|
+
log.info("Connection tune received:", .{});
|
|
332
|
+
log.info("channel_max {}", .{args.channel_max});
|
|
333
|
+
log.info("frame_max {}", .{args.frame_max});
|
|
334
|
+
log.info("heartbeat {}", .{args.heartbeat});
|
|
335
|
+
// Zero indicates no specified limit.
|
|
336
|
+
assert(args.frame_max == 0 or args.frame_max >= frame_min_size);
|
|
337
|
+
maybe(args.channel_max == 0);
|
|
338
|
+
maybe(args.heartbeat == 0);
|
|
339
|
+
|
|
340
|
+
const encoder = self.send_buffer.encoder();
|
|
341
|
+
|
|
342
|
+
// Since `tune-ok` has no reply (send-and-forget),
|
|
343
|
+
// we can flush it together with `open`.
|
|
344
|
+
const method_tune_ok: spec.ServerMethod = .{
|
|
345
|
+
.connection_tune_ok = .{
|
|
346
|
+
.channel_max = 1,
|
|
347
|
+
// Don't override `frame_max`. RabbitMQ 4.1 requires frame sizes larger
|
|
348
|
+
// than those specified in the AMQP spec.
|
|
349
|
+
// https://www.rabbitmq.com/blog/2025/04/15/rabbitmq-4.1.0-is-released#initial-amqp-0-9-1-maximum-frame-size
|
|
350
|
+
.frame_max = args.frame_max,
|
|
351
|
+
.heartbeat = if (args.heartbeat == 0)
|
|
352
|
+
// Zero means the server does not want a heartbeat.
|
|
353
|
+
0
|
|
354
|
+
else
|
|
355
|
+
connection_options.heartbeat_seconds orelse args.heartbeat,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
method_tune_ok.encode(.global, encoder);
|
|
359
|
+
|
|
360
|
+
const method_open: spec.ServerMethod = .{ .connection_open = .{
|
|
361
|
+
.virtual_host = connection_options.vhost,
|
|
362
|
+
} };
|
|
363
|
+
method_open.encode(.global, encoder);
|
|
364
|
+
self.action.connect.phase = .connection_open;
|
|
365
|
+
self.send_and_await_reply(.global, &connect_dispatch);
|
|
366
|
+
},
|
|
367
|
+
.connection_open_ok => {
|
|
368
|
+
assert(self.action.connect.phase == .connection_open);
|
|
369
|
+
|
|
370
|
+
const method: spec.ServerMethod = .{ .channel_open = .{} };
|
|
371
|
+
method.encode(.current, self.send_buffer.encoder());
|
|
372
|
+
self.action.connect.phase = .channel_open;
|
|
373
|
+
self.send_and_await_reply(.current, &connect_dispatch);
|
|
374
|
+
},
|
|
375
|
+
.channel_open_ok => {
|
|
376
|
+
assert(self.action.connect.phase == .channel_open);
|
|
377
|
+
|
|
378
|
+
// Enabling the `confirm` mode on the channel.
|
|
379
|
+
// https://www.rabbitmq.com/docs/confirms#publisher-confirms
|
|
380
|
+
const method: spec.ServerMethod = .{ .confirm_select = .{ .nowait = false } };
|
|
381
|
+
method.encode(.current, self.send_buffer.encoder());
|
|
382
|
+
self.action.connect.phase = .confirm_select;
|
|
383
|
+
self.send_and_await_reply(.current, &connect_dispatch);
|
|
384
|
+
},
|
|
385
|
+
.confirm_select_ok => {
|
|
386
|
+
assert(self.action.connect.phase == .confirm_select);
|
|
387
|
+
|
|
388
|
+
const callback = self.action.connect.callback;
|
|
389
|
+
self.action = .none;
|
|
390
|
+
callback(self);
|
|
391
|
+
},
|
|
392
|
+
else => fatal(
|
|
393
|
+
"Unexpected AMQP method received during connection: {s}",
|
|
394
|
+
.{@tagName(reply)},
|
|
395
|
+
),
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
pub fn exchange_declare(
|
|
400
|
+
self: *Client,
|
|
401
|
+
callback: Callback,
|
|
402
|
+
options: ExchangeDeclareOptions,
|
|
403
|
+
) void {
|
|
404
|
+
assert(self.action == .none);
|
|
405
|
+
self.action = .{ .exchange_declare = callback };
|
|
406
|
+
|
|
407
|
+
const method: spec.ServerMethod = .{
|
|
408
|
+
.exchange_declare = .{
|
|
409
|
+
.exchange = options.exchange,
|
|
410
|
+
.internal = options.internal,
|
|
411
|
+
.passive = options.passive,
|
|
412
|
+
.durable = options.durable,
|
|
413
|
+
.type = options.type,
|
|
414
|
+
.auto_delete = options.auto_delete,
|
|
415
|
+
.no_wait = false, // Always await the reply.
|
|
416
|
+
.arguments = null,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
method.encode(.current, self.send_buffer.encoder());
|
|
420
|
+
self.send_and_await_reply(
|
|
421
|
+
.current,
|
|
422
|
+
&struct {
|
|
423
|
+
fn dispatch(context: *Client, reply: spec.ClientMethod) Decoder.Error!void {
|
|
424
|
+
assert(reply == .exchange_declare_ok);
|
|
425
|
+
assert(context.action == .exchange_declare);
|
|
426
|
+
const exchange_declare_callback = context.action.exchange_declare;
|
|
427
|
+
context.action = .none;
|
|
428
|
+
exchange_declare_callback(context);
|
|
429
|
+
}
|
|
430
|
+
}.dispatch,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
pub fn queue_declare(self: *Client, callback: Callback, options: QueueDeclareOptions) void {
|
|
435
|
+
assert(self.action == .none);
|
|
436
|
+
self.action = .{ .queue_declare = callback };
|
|
437
|
+
|
|
438
|
+
const method: spec.ServerMethod = .{
|
|
439
|
+
.queue_declare = .{
|
|
440
|
+
.queue = options.queue,
|
|
441
|
+
.passive = options.passive,
|
|
442
|
+
.durable = options.durable,
|
|
443
|
+
.exclusive = options.exclusive,
|
|
444
|
+
.auto_delete = options.auto_delete,
|
|
445
|
+
.no_wait = false, // Always await the reply.
|
|
446
|
+
.arguments = options.arguments.table(),
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
method.encode(.current, self.send_buffer.encoder());
|
|
450
|
+
self.send_and_await_reply(
|
|
451
|
+
.current,
|
|
452
|
+
&struct {
|
|
453
|
+
fn dispatch(context: *Client, reply: spec.ClientMethod) Decoder.Error!void {
|
|
454
|
+
assert(reply == .queue_declare_ok);
|
|
455
|
+
assert(context.action == .queue_declare);
|
|
456
|
+
|
|
457
|
+
const queue_declare_callback = context.action.queue_declare;
|
|
458
|
+
context.action = .none;
|
|
459
|
+
queue_declare_callback(context);
|
|
460
|
+
}
|
|
461
|
+
}.dispatch,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// Enqueue a message to be sent by `publish_send()`.
|
|
466
|
+
pub fn publish_enqueue(self: *Client, options: BasicPublishOptions) void {
|
|
467
|
+
assert(self.awaiter == .none);
|
|
468
|
+
if (self.action == .none) self.action = .{ .publish_enqueue = .{} };
|
|
469
|
+
|
|
470
|
+
assert(self.action == .publish_enqueue);
|
|
471
|
+
self.action.publish_enqueue.count += 1;
|
|
472
|
+
|
|
473
|
+
// To send a message with metadata and payload, the following `Frames` must be written:
|
|
474
|
+
const encoder = self.send_buffer.encoder();
|
|
475
|
+
|
|
476
|
+
// 1. Method frame — contains the `Basic.Publish` method arguments.
|
|
477
|
+
const method: spec.ServerMethod = .{ .basic_publish = .{
|
|
478
|
+
.exchange = options.exchange,
|
|
479
|
+
.routing_key = options.routing_key,
|
|
480
|
+
.mandatory = options.mandatory,
|
|
481
|
+
.immediate = options.immediate,
|
|
482
|
+
} };
|
|
483
|
+
method.encode(.current, encoder);
|
|
484
|
+
|
|
485
|
+
// 2. Header frame — contains the `Basic` properties and custom headers.
|
|
486
|
+
encoder.begin_frame(.{
|
|
487
|
+
.type = .header,
|
|
488
|
+
.channel = .current,
|
|
489
|
+
});
|
|
490
|
+
encoder.begin_header(.{
|
|
491
|
+
.class = method.method_header().class,
|
|
492
|
+
.weight = 0,
|
|
493
|
+
});
|
|
494
|
+
options.properties.encode(encoder);
|
|
495
|
+
encoder.finish_frame(.header);
|
|
496
|
+
|
|
497
|
+
if (options.body) |body| {
|
|
498
|
+
// 3. Body frame (optional) — contains the message payload.
|
|
499
|
+
// This could be split into N frames, but we only support single-frame bodies.
|
|
500
|
+
encoder.begin_frame(.{
|
|
501
|
+
.type = .body,
|
|
502
|
+
.channel = .current,
|
|
503
|
+
});
|
|
504
|
+
const body_size = body.write(encoder.buffer[encoder.index..]);
|
|
505
|
+
encoder.index += body_size;
|
|
506
|
+
encoder.finish_header(body_size);
|
|
507
|
+
encoder.finish_frame(.body);
|
|
508
|
+
} else {
|
|
509
|
+
// No body frame.
|
|
510
|
+
encoder.finish_header(0);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/// Sends all messages enqueued so far by `publish_enqueue()`.
|
|
515
|
+
pub fn publish_send(
|
|
516
|
+
self: *Client,
|
|
517
|
+
callback: Callback,
|
|
518
|
+
) void {
|
|
519
|
+
assert(self.awaiter == .none);
|
|
520
|
+
assert(self.send_buffer.state == .writing);
|
|
521
|
+
assert(self.action == .publish_enqueue);
|
|
522
|
+
assert(self.action.publish_enqueue.count > 0);
|
|
523
|
+
|
|
524
|
+
self.publish_confirms.wait(self.action.publish_enqueue.count);
|
|
525
|
+
self.action = .{ .publish = .{ .callback = callback } };
|
|
526
|
+
self.send_and_forget(&struct {
|
|
527
|
+
fn dispatch(context: *Client) void {
|
|
528
|
+
assert(context.action == .publish);
|
|
529
|
+
assert(context.action.publish.phase == .sending);
|
|
530
|
+
context.action.publish.phase = .awaiting_confirmation;
|
|
531
|
+
}
|
|
532
|
+
}.dispatch);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/// Uses a polling model to retrieve a message (`Basic.Get`).
|
|
536
|
+
/// The callback is invoked with either `null` properties if the queue is empty,
|
|
537
|
+
/// or with the properties of the first available message.
|
|
538
|
+
/// N.B.: The message body is not retrieved.
|
|
539
|
+
/// The method `get_message_body` **MUST** be called if `has_body == true`.
|
|
540
|
+
pub fn get_message(
|
|
541
|
+
self: *Client,
|
|
542
|
+
callback: GetMessagePropertiesCallback,
|
|
543
|
+
options: GetMessageOptions,
|
|
544
|
+
) void {
|
|
545
|
+
assert(self.action == .none);
|
|
546
|
+
self.action = .{ .get_message = callback };
|
|
547
|
+
|
|
548
|
+
const method: spec.ServerMethod = .{ .basic_get = .{
|
|
549
|
+
.queue = options.queue,
|
|
550
|
+
.no_ack = options.no_ack,
|
|
551
|
+
} };
|
|
552
|
+
method.encode(.current, self.send_buffer.encoder());
|
|
553
|
+
self.send_and_await_reply(.current, &get_message_dispatch);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
fn get_message_dispatch(self: *Client, reply: spec.ClientMethod) Decoder.Error!void {
|
|
557
|
+
assert(self.action == .get_message);
|
|
558
|
+
assert(self.awaiter == .none);
|
|
559
|
+
switch (reply) {
|
|
560
|
+
.basic_get_empty => {
|
|
561
|
+
const get_header_callback = self.action.get_message;
|
|
562
|
+
self.action = .none;
|
|
563
|
+
try get_header_callback(self, null);
|
|
564
|
+
},
|
|
565
|
+
.basic_get_ok => |get_ok| self.awaiter = .{ .await_content_header = .{
|
|
566
|
+
.channel = .current,
|
|
567
|
+
.delivery_tag = get_ok.delivery_tag,
|
|
568
|
+
.message_count = get_ok.message_count,
|
|
569
|
+
.callback = &struct {
|
|
570
|
+
fn dispatch(
|
|
571
|
+
context: *Client,
|
|
572
|
+
delivery_tag: u64,
|
|
573
|
+
message_count: u32,
|
|
574
|
+
header: Decoder.Header,
|
|
575
|
+
) Decoder.Error!void {
|
|
576
|
+
assert(context.action == .get_message);
|
|
577
|
+
assert(header.body_size <= protocol.frame_min_size);
|
|
578
|
+
const properties = try Decoder.BasicProperties.decode(
|
|
579
|
+
header.property_flags,
|
|
580
|
+
header.properties,
|
|
581
|
+
);
|
|
582
|
+
const get_message_callback = context.action.get_message;
|
|
583
|
+
const has_body = header.body_size > 0;
|
|
584
|
+
context.action = if (has_body) .{
|
|
585
|
+
.message_body_pending = .{
|
|
586
|
+
.body_size = header.body_size,
|
|
587
|
+
},
|
|
588
|
+
} else .none;
|
|
589
|
+
try get_message_callback(context, .{
|
|
590
|
+
.delivery_tag = delivery_tag,
|
|
591
|
+
.message_count = message_count,
|
|
592
|
+
.properties = properties,
|
|
593
|
+
.has_body = has_body,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}.dispatch,
|
|
597
|
+
} },
|
|
598
|
+
else => fatal(
|
|
599
|
+
"Unexpected AMQP method received during get_message: {s}",
|
|
600
|
+
.{@tagName(reply)},
|
|
601
|
+
),
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
pub fn get_message_body(
|
|
606
|
+
self: *Client,
|
|
607
|
+
callback: GetMessageBodyCallback,
|
|
608
|
+
) void {
|
|
609
|
+
assert(self.action == .message_body_pending);
|
|
610
|
+
assert(self.action.message_body_pending.body_size <= protocol.frame_min_size);
|
|
611
|
+
const body_size = self.action.message_body_pending.body_size;
|
|
612
|
+
self.action = .{ .get_message_body = .{
|
|
613
|
+
.body_size = body_size,
|
|
614
|
+
.callback = callback,
|
|
615
|
+
} };
|
|
616
|
+
self.awaiter = .{ .await_body = .{
|
|
617
|
+
.channel = .current,
|
|
618
|
+
.callback = &struct {
|
|
619
|
+
fn dispatch(
|
|
620
|
+
context: *Client,
|
|
621
|
+
body: []const u8,
|
|
622
|
+
) Decoder.Error!void {
|
|
623
|
+
assert(context.action == .get_message_body);
|
|
624
|
+
assert(context.action.get_message_body.body_size == body.len);
|
|
625
|
+
const get_message_body_callback = context.action.get_message_body.callback;
|
|
626
|
+
context.action = .none;
|
|
627
|
+
try get_message_body_callback(context, body);
|
|
628
|
+
}
|
|
629
|
+
}.dispatch,
|
|
630
|
+
} };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/// Rejects a message.
|
|
634
|
+
pub fn nack(self: *Client, callback: Callback, options: BasicNackOptions) void {
|
|
635
|
+
assert(self.awaiter == .none);
|
|
636
|
+
assert(self.action == .none);
|
|
637
|
+
self.action = .{ .nack = callback };
|
|
638
|
+
|
|
639
|
+
const method: spec.ServerMethod = .{ .basic_nack = .{
|
|
640
|
+
.delivery_tag = options.delivery_tag,
|
|
641
|
+
.requeue = options.requeue,
|
|
642
|
+
.multiple = options.multiple,
|
|
643
|
+
} };
|
|
644
|
+
method.encode(.current, self.send_buffer.encoder());
|
|
645
|
+
self.send_and_forget(&struct {
|
|
646
|
+
fn dispatch(context: *Client) void {
|
|
647
|
+
assert(context.action == .nack);
|
|
648
|
+
const nack_callback = context.action.nack;
|
|
649
|
+
context.action = .none;
|
|
650
|
+
nack_callback(context);
|
|
651
|
+
}
|
|
652
|
+
}.dispatch);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
fn send_and_await_reply(
|
|
656
|
+
self: *Client,
|
|
657
|
+
channel: Channel,
|
|
658
|
+
callback: *const fn (self: *Client, reply: spec.ClientMethod) Decoder.Error!void,
|
|
659
|
+
) void {
|
|
660
|
+
assert(self.awaiter == .none);
|
|
661
|
+
assert(self.send_buffer.state == .writing);
|
|
662
|
+
self.awaiter = .{ .send_and_await_reply = .{
|
|
663
|
+
.channel = channel,
|
|
664
|
+
.state = .sending,
|
|
665
|
+
.callback = callback,
|
|
666
|
+
} };
|
|
667
|
+
self.send();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
fn send_and_forget(
|
|
671
|
+
self: *Client,
|
|
672
|
+
callback: *const fn (self: *Client) void,
|
|
673
|
+
) void {
|
|
674
|
+
assert(self.awaiter == .none);
|
|
675
|
+
assert(self.send_buffer.state == .writing);
|
|
676
|
+
self.awaiter = .{ .send_and_forget = callback };
|
|
677
|
+
self.send();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
fn send(self: *Client) void {
|
|
681
|
+
switch (self.awaiter) {
|
|
682
|
+
.send_and_forget,
|
|
683
|
+
.send_and_await_reply,
|
|
684
|
+
=> {
|
|
685
|
+
self.io.send(
|
|
686
|
+
*Client,
|
|
687
|
+
self,
|
|
688
|
+
send_callback,
|
|
689
|
+
&self.send_completion,
|
|
690
|
+
self.fd.?,
|
|
691
|
+
self.send_buffer.flush(),
|
|
692
|
+
);
|
|
693
|
+
},
|
|
694
|
+
.none, .await_content_header, .await_body => unreachable,
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
fn send_callback(
|
|
699
|
+
self: *Client,
|
|
700
|
+
completion: *IO.Completion,
|
|
701
|
+
result: IO.SendError!usize,
|
|
702
|
+
) void {
|
|
703
|
+
_ = completion;
|
|
704
|
+
assert(self.awaiter == .send_and_forget or self.awaiter == .send_and_await_reply);
|
|
705
|
+
const size = result catch |err| fatal(
|
|
706
|
+
"Network error: {s}",
|
|
707
|
+
.{@errorName(err)},
|
|
708
|
+
);
|
|
709
|
+
if (self.send_buffer.remaining(size)) |remaining| {
|
|
710
|
+
return self.io.send(
|
|
711
|
+
*Client,
|
|
712
|
+
self,
|
|
713
|
+
send_callback,
|
|
714
|
+
&self.send_completion,
|
|
715
|
+
self.fd.?,
|
|
716
|
+
remaining,
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
assert(self.send_buffer.state == .idle);
|
|
720
|
+
|
|
721
|
+
switch (self.awaiter) {
|
|
722
|
+
.send_and_forget => |callback| {
|
|
723
|
+
self.awaiter = .none;
|
|
724
|
+
callback(self);
|
|
725
|
+
},
|
|
726
|
+
.send_and_await_reply => |*awaiter| {
|
|
727
|
+
assert(awaiter.state == .sending);
|
|
728
|
+
awaiter.state = .{ .awaiting = .{} };
|
|
729
|
+
},
|
|
730
|
+
.none, .await_content_header, .await_body => unreachable,
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
fn receive(self: *Client) void {
|
|
735
|
+
assert(self.fd != null);
|
|
736
|
+
assert(self.receive_buffer.state == .idle);
|
|
737
|
+
self.io.recv(
|
|
738
|
+
*Client,
|
|
739
|
+
self,
|
|
740
|
+
receive_callback,
|
|
741
|
+
&self.receive_completion,
|
|
742
|
+
self.fd.?,
|
|
743
|
+
self.receive_buffer.begin_receive(),
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
fn receive_callback(
|
|
748
|
+
self: *Client,
|
|
749
|
+
completion: *IO.Completion,
|
|
750
|
+
result: IO.RecvError!usize,
|
|
751
|
+
) void {
|
|
752
|
+
_ = completion;
|
|
753
|
+
assert(self.receive_buffer.state == .receiving);
|
|
754
|
+
|
|
755
|
+
const size: usize = result catch |err| fatal("Network error: {}.", .{err});
|
|
756
|
+
// No bytes received means that the AMQP server closed the connection.
|
|
757
|
+
if (size == 0) fatal(
|
|
758
|
+
"The server closed the connection unexpectedly.",
|
|
759
|
+
.{},
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
var decoder = self.receive_buffer.end_receive(size);
|
|
763
|
+
assert(decoder.buffer.len > 0);
|
|
764
|
+
var processed_index_last: usize = 0;
|
|
765
|
+
while (!decoder.empty()) {
|
|
766
|
+
self.process(&decoder) catch |err| switch (err) {
|
|
767
|
+
error.BufferExhausted => {
|
|
768
|
+
// The buffer ended before the entire frame could be parsed.
|
|
769
|
+
break;
|
|
770
|
+
},
|
|
771
|
+
error.Unexpected => fatal(
|
|
772
|
+
"Invalid command received.",
|
|
773
|
+
.{},
|
|
774
|
+
),
|
|
775
|
+
};
|
|
776
|
+
processed_index_last = decoder.index;
|
|
777
|
+
}
|
|
778
|
+
assert(processed_index_last <= decoder.buffer.len);
|
|
779
|
+
const receive_buffer = self.receive_buffer.end_decode(processed_index_last);
|
|
780
|
+
|
|
781
|
+
self.io.recv(
|
|
782
|
+
*Client,
|
|
783
|
+
self,
|
|
784
|
+
receive_callback,
|
|
785
|
+
&self.receive_completion,
|
|
786
|
+
self.fd.?,
|
|
787
|
+
receive_buffer,
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
fn process(self: *Client, decoder: *Decoder) Decoder.Error!void {
|
|
792
|
+
const frame_header = try decoder.read_frame_header();
|
|
793
|
+
switch (frame_header.type) {
|
|
794
|
+
.method => {
|
|
795
|
+
const method_header = try decoder.read_method_header();
|
|
796
|
+
try self.process_method(frame_header, method_header, decoder);
|
|
797
|
+
},
|
|
798
|
+
.header => {
|
|
799
|
+
const header = try decoder.read_header(frame_header.size);
|
|
800
|
+
try self.process_header(frame_header, header);
|
|
801
|
+
},
|
|
802
|
+
.body => {
|
|
803
|
+
const body = try decoder.read_body(frame_header.size);
|
|
804
|
+
try self.process_body(frame_header, body);
|
|
805
|
+
},
|
|
806
|
+
.heartbeat => {
|
|
807
|
+
try decoder.read_frame_end();
|
|
808
|
+
self.send_heartbeat();
|
|
809
|
+
},
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
fn process_method(
|
|
814
|
+
self: *Client,
|
|
815
|
+
frame_header: Decoder.FrameHeader,
|
|
816
|
+
method_header: protocol.MethodHeader,
|
|
817
|
+
decoder: *Decoder,
|
|
818
|
+
) Decoder.Error!void {
|
|
819
|
+
assert(frame_header.type == .method);
|
|
820
|
+
const client_method = try spec.ClientMethod.decode(method_header, decoder);
|
|
821
|
+
switch (client_method) {
|
|
822
|
+
inline .connection_close, .channel_close => |close_reason, tag| {
|
|
823
|
+
const error_code: ErrorCodes = @enumFromInt(close_reason.reply_code);
|
|
824
|
+
if (std.meta.intToEnum(
|
|
825
|
+
spec.ServerMethod.Tag,
|
|
826
|
+
@as(u32, @bitCast(protocol.MethodHeader{
|
|
827
|
+
.class = close_reason.class_id,
|
|
828
|
+
.method = close_reason.method_id,
|
|
829
|
+
})),
|
|
830
|
+
) catch null) |server_method| {
|
|
831
|
+
fatal(
|
|
832
|
+
"Operation cannot be completed: method={s} {s}={s}",
|
|
833
|
+
.{
|
|
834
|
+
@tagName(server_method),
|
|
835
|
+
@tagName(error_code),
|
|
836
|
+
close_reason.reply_text,
|
|
837
|
+
},
|
|
838
|
+
);
|
|
839
|
+
} else {
|
|
840
|
+
fatal(
|
|
841
|
+
switch (tag) {
|
|
842
|
+
.connection_close => "Connection closed: {s}={s}",
|
|
843
|
+
.channel_close => "Channel closed: {s}={s}",
|
|
844
|
+
else => comptime unreachable,
|
|
845
|
+
},
|
|
846
|
+
.{
|
|
847
|
+
@tagName(error_code),
|
|
848
|
+
close_reason.reply_text,
|
|
849
|
+
},
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
.basic_return => |basic_return| {
|
|
854
|
+
const soft_error: ErrorCodes = @enumFromInt(basic_return.reply_code);
|
|
855
|
+
fatal(
|
|
856
|
+
"Message cannot be delivered: exchange=\"{s}\" routing_key=\"{s}\" {s}={s}",
|
|
857
|
+
.{
|
|
858
|
+
basic_return.exchange,
|
|
859
|
+
basic_return.routing_key,
|
|
860
|
+
@tagName(soft_error),
|
|
861
|
+
basic_return.reply_text,
|
|
862
|
+
},
|
|
863
|
+
);
|
|
864
|
+
},
|
|
865
|
+
// Channel flow is not supported, but the command can be ignored.
|
|
866
|
+
// It's up to the server to evict clients that do not respect
|
|
867
|
+
// the flow control directive.
|
|
868
|
+
.channel_flow => |channel_flow| return log.warn(
|
|
869
|
+
"Channel flow ignored: active={}",
|
|
870
|
+
.{channel_flow.active},
|
|
871
|
+
),
|
|
872
|
+
.connection_blocked,
|
|
873
|
+
.connection_unblocked,
|
|
874
|
+
.basic_deliver,
|
|
875
|
+
=> fatal(
|
|
876
|
+
"AMQP operation not supported: {s} channel={}",
|
|
877
|
+
.{ @tagName(client_method), frame_header.channel },
|
|
878
|
+
),
|
|
879
|
+
.basic_ack => |basic_ack| {
|
|
880
|
+
// Processing acks in "publish confirms" mode.
|
|
881
|
+
if (self.action == .publish) {
|
|
882
|
+
const publish = &self.action.publish;
|
|
883
|
+
// Confirmations can be received while sending a batch of messages.
|
|
884
|
+
if (publish.phase == .sending) assert(self.awaiter == .send_and_forget);
|
|
885
|
+
if (self.publish_confirms.confirm(basic_ack)) {
|
|
886
|
+
assert(self.awaiter == .none);
|
|
887
|
+
assert(publish.phase == .awaiting_confirmation);
|
|
888
|
+
const publish_callback = publish.callback;
|
|
889
|
+
self.action = .none;
|
|
890
|
+
publish_callback(self);
|
|
891
|
+
}
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
.basic_nack => fatal(
|
|
896
|
+
"Message was rejected by the AMQP server: {s} channel={}",
|
|
897
|
+
.{ @tagName(client_method), frame_header.channel },
|
|
898
|
+
),
|
|
899
|
+
else => {},
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
switch (self.awaiter) {
|
|
903
|
+
.send_and_await_reply => |awaiter| {
|
|
904
|
+
if (awaiter.state == .awaiting and
|
|
905
|
+
awaiter.channel == frame_header.channel)
|
|
906
|
+
{
|
|
907
|
+
self.awaiter = .none;
|
|
908
|
+
return try awaiter.callback(self, client_method);
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
else => {},
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
fatal(
|
|
915
|
+
"Unexpected AMQP method: {s} channel={}",
|
|
916
|
+
.{ @tagName(client_method), frame_header.channel },
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
fn process_header(
|
|
921
|
+
self: *Client,
|
|
922
|
+
frame_header: Decoder.FrameHeader,
|
|
923
|
+
header: Decoder.Header,
|
|
924
|
+
) Decoder.Error!void {
|
|
925
|
+
assert(frame_header.type == .header);
|
|
926
|
+
maybe(header.body_size == 0);
|
|
927
|
+
if (self.awaiter == .await_content_header) {
|
|
928
|
+
const awaiter = self.awaiter.await_content_header;
|
|
929
|
+
if (frame_header.channel == awaiter.channel) {
|
|
930
|
+
self.awaiter = .none;
|
|
931
|
+
return try awaiter.callback(
|
|
932
|
+
self,
|
|
933
|
+
awaiter.delivery_tag,
|
|
934
|
+
awaiter.message_count,
|
|
935
|
+
header,
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
fatal(
|
|
940
|
+
"Unexpected message header: channel={} class={} body_size={}",
|
|
941
|
+
.{
|
|
942
|
+
frame_header.channel,
|
|
943
|
+
header.class,
|
|
944
|
+
header.body_size,
|
|
945
|
+
},
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
fn process_body(
|
|
950
|
+
self: *Client,
|
|
951
|
+
frame_header: Decoder.FrameHeader,
|
|
952
|
+
body: []const u8,
|
|
953
|
+
) Decoder.Error!void {
|
|
954
|
+
assert(frame_header.type == .body);
|
|
955
|
+
assert(body.len > 0);
|
|
956
|
+
if (self.awaiter == .await_body) {
|
|
957
|
+
const awaiter = self.awaiter.await_body;
|
|
958
|
+
if (frame_header.channel == awaiter.channel) {
|
|
959
|
+
self.awaiter = .none;
|
|
960
|
+
return try awaiter.callback(
|
|
961
|
+
self,
|
|
962
|
+
body,
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
fatal(
|
|
967
|
+
"Unexpected message body: channel={} body_size={}",
|
|
968
|
+
.{
|
|
969
|
+
frame_header.channel,
|
|
970
|
+
body.len,
|
|
971
|
+
},
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
fn send_heartbeat(self: *Client) void {
|
|
976
|
+
assert(self.fd != null);
|
|
977
|
+
if (self.heartbeat == .sending) return;
|
|
978
|
+
|
|
979
|
+
log.info("Heartbeat", .{});
|
|
980
|
+
|
|
981
|
+
const heartbeat_message: [8]u8 = comptime heartbeat: {
|
|
982
|
+
var buffer: [8]u8 = undefined;
|
|
983
|
+
var encoder = Encoder.init(&buffer);
|
|
984
|
+
encoder.begin_frame(.{
|
|
985
|
+
.type = .heartbeat,
|
|
986
|
+
.channel = .global,
|
|
987
|
+
});
|
|
988
|
+
encoder.finish_frame(.heartbeat);
|
|
989
|
+
assert(encoder.index == buffer.len);
|
|
990
|
+
break :heartbeat buffer;
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
assert(self.heartbeat == .idle);
|
|
994
|
+
self.heartbeat = .{ .sending = undefined };
|
|
995
|
+
self.io.send(
|
|
996
|
+
*Client,
|
|
997
|
+
self,
|
|
998
|
+
on_heartbeat_callback,
|
|
999
|
+
&self.heartbeat.sending,
|
|
1000
|
+
self.fd.?,
|
|
1001
|
+
&heartbeat_message,
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
fn on_heartbeat_callback(
|
|
1006
|
+
self: *Client,
|
|
1007
|
+
completion: *IO.Completion,
|
|
1008
|
+
result: IO.SendError!usize,
|
|
1009
|
+
) void {
|
|
1010
|
+
assert(self.heartbeat == .sending);
|
|
1011
|
+
self.heartbeat = .idle;
|
|
1012
|
+
_ = completion;
|
|
1013
|
+
_ = result catch |err| fatal("Network error: {}", .{err});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
pub fn tick(self: *Client) void {
|
|
1017
|
+
const duration_ticks: u64 = switch (self.awaiter) {
|
|
1018
|
+
.none, .send_and_forget => return,
|
|
1019
|
+
.send_and_await_reply => |*awaiter| ticks: {
|
|
1020
|
+
if (awaiter.state == .sending) return;
|
|
1021
|
+
assert(awaiter.state == .awaiting);
|
|
1022
|
+
awaiter.state.awaiting.duration_ticks += 1;
|
|
1023
|
+
break :ticks awaiter.state.awaiting.duration_ticks;
|
|
1024
|
+
},
|
|
1025
|
+
inline .await_content_header, .await_body => |*awaiter| ticks: {
|
|
1026
|
+
awaiter.duration_ticks += 1;
|
|
1027
|
+
break :ticks awaiter.duration_ticks;
|
|
1028
|
+
},
|
|
1029
|
+
};
|
|
1030
|
+
assert(self.action != .none);
|
|
1031
|
+
if (duration_ticks > self.reply_timeout_ticks) {
|
|
1032
|
+
fatal(
|
|
1033
|
+
"Operation {s} timed out. No reply received from the AMQP server.",
|
|
1034
|
+
.{@tagName(self.action)},
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
const ReceiveBuffer = struct {
|
|
1041
|
+
buffer: []u8,
|
|
1042
|
+
state: union(enum) {
|
|
1043
|
+
idle,
|
|
1044
|
+
receiving: struct {
|
|
1045
|
+
non_consumed: usize,
|
|
1046
|
+
},
|
|
1047
|
+
decoding: struct {
|
|
1048
|
+
size: usize,
|
|
1049
|
+
},
|
|
1050
|
+
},
|
|
1051
|
+
|
|
1052
|
+
fn init(buffer: []u8) ReceiveBuffer {
|
|
1053
|
+
assert(buffer.len >= frame_min_size);
|
|
1054
|
+
return .{
|
|
1055
|
+
.buffer = buffer,
|
|
1056
|
+
.state = .idle,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
fn begin_receive(self: *ReceiveBuffer) []u8 {
|
|
1061
|
+
switch (self.state) {
|
|
1062
|
+
.idle => {
|
|
1063
|
+
self.state = .{
|
|
1064
|
+
.receiving = .{ .non_consumed = 0 },
|
|
1065
|
+
};
|
|
1066
|
+
return self.buffer;
|
|
1067
|
+
},
|
|
1068
|
+
.decoding, .receiving => unreachable,
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
fn end_receive(self: *ReceiveBuffer, size: usize) Decoder {
|
|
1073
|
+
assert(size > 0);
|
|
1074
|
+
switch (self.state) {
|
|
1075
|
+
.idle, .decoding => unreachable,
|
|
1076
|
+
.receiving => |receive_state| {
|
|
1077
|
+
const total_size = size + receive_state.non_consumed;
|
|
1078
|
+
assert(total_size <= self.buffer.len);
|
|
1079
|
+
maybe(receive_state.non_consumed == 0);
|
|
1080
|
+
self.state = .{ .decoding = .{ .size = total_size } };
|
|
1081
|
+
return Decoder.init(self.buffer[0..total_size]);
|
|
1082
|
+
},
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
fn end_decode(self: *ReceiveBuffer, processed_last_index: usize) []u8 {
|
|
1087
|
+
maybe(processed_last_index == 0);
|
|
1088
|
+
assert(self.state == .decoding);
|
|
1089
|
+
const decoding_state = self.state.decoding;
|
|
1090
|
+
if (processed_last_index == decoding_state.size) {
|
|
1091
|
+
self.state = .{
|
|
1092
|
+
.receiving = .{ .non_consumed = 0 },
|
|
1093
|
+
};
|
|
1094
|
+
return self.buffer;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
assert(processed_last_index < decoding_state.size);
|
|
1098
|
+
const remaining = self.buffer[processed_last_index..decoding_state.size];
|
|
1099
|
+
assert(remaining.len < self.buffer.len);
|
|
1100
|
+
if (processed_last_index > 0) {
|
|
1101
|
+
stdx.copy_left(.inexact, u8, self.buffer, remaining);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
self.state = .{
|
|
1105
|
+
.receiving = .{ .non_consumed = remaining.len },
|
|
1106
|
+
};
|
|
1107
|
+
return self.buffer[remaining.len..];
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
const SendBuffer = struct {
|
|
1112
|
+
buffer: []u8,
|
|
1113
|
+
state: union(enum) {
|
|
1114
|
+
idle,
|
|
1115
|
+
writing: Encoder,
|
|
1116
|
+
sending: struct {
|
|
1117
|
+
size: usize,
|
|
1118
|
+
progress: usize,
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
|
|
1122
|
+
fn init(buffer: []u8) SendBuffer {
|
|
1123
|
+
assert(buffer.len >= frame_min_size);
|
|
1124
|
+
return .{
|
|
1125
|
+
.buffer = buffer,
|
|
1126
|
+
.state = .idle,
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
fn encoder(self: *SendBuffer) *Encoder {
|
|
1131
|
+
switch (self.state) {
|
|
1132
|
+
.idle => {
|
|
1133
|
+
self.state = .{ .writing = Encoder.init(self.buffer) };
|
|
1134
|
+
return &self.state.writing;
|
|
1135
|
+
},
|
|
1136
|
+
.writing => |*current| return current,
|
|
1137
|
+
.sending => unreachable,
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
fn flush(self: *SendBuffer) []const u8 {
|
|
1142
|
+
switch (self.state) {
|
|
1143
|
+
.idle, .sending => unreachable,
|
|
1144
|
+
.writing => |*current| {
|
|
1145
|
+
assert(current.index > 0);
|
|
1146
|
+
const size = current.index;
|
|
1147
|
+
self.state = .{ .sending = .{
|
|
1148
|
+
.size = size,
|
|
1149
|
+
.progress = 0,
|
|
1150
|
+
} };
|
|
1151
|
+
|
|
1152
|
+
return self.buffer[0..size];
|
|
1153
|
+
},
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
fn remaining(self: *SendBuffer, written_bytes: usize) ?[]const u8 {
|
|
1158
|
+
switch (self.state) {
|
|
1159
|
+
.idle, .writing => unreachable,
|
|
1160
|
+
.sending => |*send_state| {
|
|
1161
|
+
send_state.progress += written_bytes;
|
|
1162
|
+
if (send_state.progress == send_state.size) {
|
|
1163
|
+
self.state = .idle;
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
assert(send_state.progress < send_state.size);
|
|
1168
|
+
return self.buffer[send_state.progress..send_state.size];
|
|
1169
|
+
},
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
fn log_table(name: []const u8, table: Decoder.Table) Decoder.Error!void {
|
|
1175
|
+
var iterator = table.iterator();
|
|
1176
|
+
while (try iterator.next()) |entry| {
|
|
1177
|
+
switch (entry.value) {
|
|
1178
|
+
.string => |str| log.info("{s} {s}:{s}", .{
|
|
1179
|
+
name,
|
|
1180
|
+
entry.key,
|
|
1181
|
+
str,
|
|
1182
|
+
}),
|
|
1183
|
+
.field_table => |field_table| try log_table(entry.key, field_table),
|
|
1184
|
+
inline else => |any| log.info("{s} {s}:{any}", .{
|
|
1185
|
+
name,
|
|
1186
|
+
entry.key,
|
|
1187
|
+
any,
|
|
1188
|
+
}),
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/// Implements the RabbitMQ Publisher Confirms acknowledgment logic.
|
|
1194
|
+
/// Both the broker and the client count messages.
|
|
1195
|
+
/// Counting starts at 1 on the first `confirm_select`.
|
|
1196
|
+
/// https://www.rabbitmq.com/docs/confirms#publisher-confirms
|
|
1197
|
+
/// https://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms
|
|
1198
|
+
const Confirms = struct {
|
|
1199
|
+
processed: std.DynamicBitSetUnmanaged,
|
|
1200
|
+
state: union(enum) {
|
|
1201
|
+
idle: struct {
|
|
1202
|
+
sequence: u64,
|
|
1203
|
+
},
|
|
1204
|
+
waiting: struct {
|
|
1205
|
+
count: u32,
|
|
1206
|
+
sequence_initial: u64,
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1209
|
+
|
|
1210
|
+
fn init(allocator: std.mem.Allocator, capacity: u32) !Confirms {
|
|
1211
|
+
assert(capacity > 0);
|
|
1212
|
+
const processed = try std.DynamicBitSetUnmanaged.initEmpty(allocator, capacity);
|
|
1213
|
+
return .{
|
|
1214
|
+
.state = .{ .idle = .{ .sequence = 1 } },
|
|
1215
|
+
.processed = processed,
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
fn deinit(self: *Confirms, allocator: std.mem.Allocator) void {
|
|
1220
|
+
assert(self.state == .idle);
|
|
1221
|
+
assert(self.processed.count() == 0);
|
|
1222
|
+
self.processed.deinit(allocator);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/// Waits until `count` published messages have been acknowledged by the server.
|
|
1226
|
+
fn wait(self: *Confirms, count: u32) void {
|
|
1227
|
+
assert(count > 0);
|
|
1228
|
+
assert(count <= self.processed.capacity());
|
|
1229
|
+
assert(self.processed.count() == 0);
|
|
1230
|
+
assert(self.state == .idle);
|
|
1231
|
+
|
|
1232
|
+
const sequence = self.state.idle.sequence;
|
|
1233
|
+
assert(sequence > 0);
|
|
1234
|
+
self.state = .{ .waiting = .{
|
|
1235
|
+
.count = count,
|
|
1236
|
+
.sequence_initial = sequence,
|
|
1237
|
+
} };
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/// Confirms that the server has received and fsync'ed a batch of published messages.
|
|
1241
|
+
/// Returns `true` if there are no more messages pending acknowledgment.
|
|
1242
|
+
fn confirm(self: *Confirms, ack: std.meta.TagPayload(spec.ClientMethod, .basic_ack)) bool {
|
|
1243
|
+
assert(self.state == .waiting);
|
|
1244
|
+
|
|
1245
|
+
const state = self.state.waiting;
|
|
1246
|
+
assert(state.count > 0);
|
|
1247
|
+
assert(state.sequence_initial > 0);
|
|
1248
|
+
|
|
1249
|
+
// The server must not use a zero value for delivery tags.
|
|
1250
|
+
// Zero is reserved for client use, meaning "all messages so far received".
|
|
1251
|
+
// https://www.rabbitmq.com/docs/specification#rules
|
|
1252
|
+
assert(ack.delivery_tag > 0);
|
|
1253
|
+
assert(ack.delivery_tag >= state.sequence_initial);
|
|
1254
|
+
assert(ack.delivery_tag < state.sequence_initial + state.count);
|
|
1255
|
+
|
|
1256
|
+
const range: std.bit_set.Range = range: {
|
|
1257
|
+
const index = ack.delivery_tag - state.sequence_initial;
|
|
1258
|
+
// Published messages will be confirmed only once.
|
|
1259
|
+
assert(!self.processed.isSet(index));
|
|
1260
|
+
const start: usize = start: {
|
|
1261
|
+
if (!ack.multiple) break :start index; // Single message.
|
|
1262
|
+
|
|
1263
|
+
// Finds the first unconfirmed delivery tag to acknowledge
|
|
1264
|
+
// all pending messages up to `ack.delivery_tag`.
|
|
1265
|
+
var iterator = self.processed.iterator(.{
|
|
1266
|
+
.direction = .forward,
|
|
1267
|
+
.kind = .unset,
|
|
1268
|
+
});
|
|
1269
|
+
const unconfirmed_index = iterator.next().?;
|
|
1270
|
+
assert(unconfirmed_index <= index);
|
|
1271
|
+
break :start unconfirmed_index;
|
|
1272
|
+
};
|
|
1273
|
+
break :range .{
|
|
1274
|
+
.start = start,
|
|
1275
|
+
.end = index + 1, // +1 to be inclusive.
|
|
1276
|
+
};
|
|
1277
|
+
};
|
|
1278
|
+
self.processed.setRangeValue(range, true);
|
|
1279
|
+
|
|
1280
|
+
log.debug("basic_ack: delivery_tag={} multiple={} count={} confirmed={}", .{
|
|
1281
|
+
ack.delivery_tag,
|
|
1282
|
+
ack.multiple,
|
|
1283
|
+
state.count,
|
|
1284
|
+
self.processed.count(),
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
if (self.processed.count() == state.count) {
|
|
1288
|
+
self.processed.unsetAll();
|
|
1289
|
+
self.state = .{ .idle = .{
|
|
1290
|
+
.sequence = state.sequence_initial + state.count,
|
|
1291
|
+
} };
|
|
1292
|
+
return true;
|
|
1293
|
+
}
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
const testing = std.testing;
|
|
1299
|
+
|
|
1300
|
+
test "amqp: SendBuffer" {
|
|
1301
|
+
const buffer = try testing.allocator.alloc(u8, frame_min_size);
|
|
1302
|
+
defer testing.allocator.free(buffer);
|
|
1303
|
+
|
|
1304
|
+
var prng: stdx.PRNG = stdx.PRNG.from_seed_testing();
|
|
1305
|
+
var send_buffer = SendBuffer.init(buffer);
|
|
1306
|
+
for (0..4096) |_| {
|
|
1307
|
+
const Element = u64;
|
|
1308
|
+
const element_count = prng.range_inclusive(
|
|
1309
|
+
usize,
|
|
1310
|
+
1,
|
|
1311
|
+
@divExact(buffer.len, @sizeOf(Element)),
|
|
1312
|
+
);
|
|
1313
|
+
// Zero the unused memory so we can assert it wasn't modified by the encoder.
|
|
1314
|
+
@memset(buffer[element_count * @sizeOf(Element) ..], 0);
|
|
1315
|
+
|
|
1316
|
+
try testing.expect(send_buffer.state == .idle);
|
|
1317
|
+
for (0..element_count) |index| {
|
|
1318
|
+
var encoder = send_buffer.encoder();
|
|
1319
|
+
try testing.expect(send_buffer.state == .writing);
|
|
1320
|
+
try testing.expectEqual(index * @sizeOf(Element), encoder.index);
|
|
1321
|
+
|
|
1322
|
+
var element: Element = undefined;
|
|
1323
|
+
prng.fill(std.mem.asBytes(&element));
|
|
1324
|
+
encoder.write_int(Element, element);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
const flush_slice = send_buffer.flush();
|
|
1328
|
+
try testing.expect(send_buffer.state == .sending);
|
|
1329
|
+
try testing.expectEqual(element_count * @sizeOf(Element), flush_slice.len);
|
|
1330
|
+
try testing.expectEqualSlices(
|
|
1331
|
+
u8,
|
|
1332
|
+
buffer[0 .. element_count * @sizeOf(Element)],
|
|
1333
|
+
flush_slice,
|
|
1334
|
+
);
|
|
1335
|
+
try testing.expect(stdx.zeroed(buffer[element_count * @sizeOf(Element) ..]));
|
|
1336
|
+
|
|
1337
|
+
var progress: usize = 0;
|
|
1338
|
+
while (progress < flush_slice.len) {
|
|
1339
|
+
const remaining_count = flush_slice.len - progress;
|
|
1340
|
+
const written = prng.range_inclusive(usize, 1, remaining_count);
|
|
1341
|
+
progress += written;
|
|
1342
|
+
|
|
1343
|
+
if (send_buffer.remaining(written)) |remaining_slice| {
|
|
1344
|
+
try testing.expectEqual(flush_slice.len - progress, remaining_slice.len);
|
|
1345
|
+
try testing.expectEqualSlices(
|
|
1346
|
+
u8,
|
|
1347
|
+
flush_slice[progress..],
|
|
1348
|
+
remaining_slice,
|
|
1349
|
+
);
|
|
1350
|
+
} else {
|
|
1351
|
+
try testing.expect(send_buffer.state == .idle);
|
|
1352
|
+
try testing.expectEqual(flush_slice.len, progress);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
test "amqp: ReceiveBuffer" {
|
|
1359
|
+
const ratio = stdx.PRNG.ratio;
|
|
1360
|
+
|
|
1361
|
+
const buffer = try testing.allocator.alloc(u8, frame_min_size);
|
|
1362
|
+
defer testing.allocator.free(buffer);
|
|
1363
|
+
|
|
1364
|
+
var receive_buffer = ReceiveBuffer.init(buffer);
|
|
1365
|
+
try testing.expect(receive_buffer.state == .idle);
|
|
1366
|
+
|
|
1367
|
+
const receive_slice = receive_buffer.begin_receive();
|
|
1368
|
+
try testing.expect(receive_buffer.state == .receiving);
|
|
1369
|
+
try testing.expectEqual(buffer.len, receive_slice.len);
|
|
1370
|
+
|
|
1371
|
+
var prng = stdx.PRNG.from_seed_testing();
|
|
1372
|
+
prng.fill(receive_slice);
|
|
1373
|
+
|
|
1374
|
+
var decoded_remain: usize = 0;
|
|
1375
|
+
for (0..4096) |_| {
|
|
1376
|
+
const receive_size: usize = prng.range_inclusive(usize, 1, buffer.len - decoded_remain);
|
|
1377
|
+
const size = receive_size + decoded_remain;
|
|
1378
|
+
|
|
1379
|
+
const decoder = receive_buffer.end_receive(receive_size);
|
|
1380
|
+
try testing.expect(receive_buffer.state == .decoding);
|
|
1381
|
+
try testing.expectEqual(size, decoder.buffer.len);
|
|
1382
|
+
try testing.expectEqualSlices(u8, buffer[0..size], decoder.buffer);
|
|
1383
|
+
|
|
1384
|
+
const decoded_count = if (prng.chance(ratio(10, 100))) size else prng.range_inclusive(
|
|
1385
|
+
usize,
|
|
1386
|
+
1,
|
|
1387
|
+
size,
|
|
1388
|
+
);
|
|
1389
|
+
decoded_remain = size - decoded_count;
|
|
1390
|
+
const receive_slice_next = receive_buffer.end_decode(decoded_count);
|
|
1391
|
+
try testing.expect(receive_buffer.state == .receiving);
|
|
1392
|
+
try testing.expectEqual(buffer.len - decoded_remain, receive_slice_next.len);
|
|
1393
|
+
try testing.expectEqualSlices(u8, buffer[decoded_remain..], receive_slice_next);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
test "amqp: Confirms" {
|
|
1398
|
+
// Confirmations can be out of order, for example:
|
|
1399
|
+
// Pending tags Ack
|
|
1400
|
+
// [1,2,3,4,5,6,7,8,9,10] -> tag=1 multiple=true
|
|
1401
|
+
// [2,3,4,5,6,7,8,9,10] -> tag=3 multiple=false
|
|
1402
|
+
// [2,4,5,6,7,8,9,10] -> tag=2 multiple=false
|
|
1403
|
+
// [4,5,6,7,8,9,10] -> tag=5 multiple=true
|
|
1404
|
+
// [6,7,8,9,10] -> tag=7 multiple=false
|
|
1405
|
+
// [6,8,9,10] -> tag=10 multiple=true
|
|
1406
|
+
// [] -> finished
|
|
1407
|
+
var confirms = try Confirms.init(testing.allocator, 10);
|
|
1408
|
+
defer confirms.deinit(testing.allocator);
|
|
1409
|
+
|
|
1410
|
+
try testing.expect(confirms.state == .idle);
|
|
1411
|
+
try testing.expect(confirms.state.idle.sequence == 1);
|
|
1412
|
+
|
|
1413
|
+
confirms.wait(10);
|
|
1414
|
+
try testing.expect(confirms.state == .waiting);
|
|
1415
|
+
try testing.expectEqual(@as(usize, 0), confirms.processed.count());
|
|
1416
|
+
|
|
1417
|
+
try testing.expectEqual(false, confirms.confirm(.{ .delivery_tag = 1, .multiple = true }));
|
|
1418
|
+
try testing.expect(confirms.state == .waiting);
|
|
1419
|
+
try testing.expectEqual(@as(usize, 1), confirms.processed.count());
|
|
1420
|
+
|
|
1421
|
+
try testing.expectEqual(false, confirms.confirm(.{ .delivery_tag = 3, .multiple = false }));
|
|
1422
|
+
try testing.expect(confirms.state == .waiting);
|
|
1423
|
+
try testing.expectEqual(@as(usize, 2), confirms.processed.count());
|
|
1424
|
+
|
|
1425
|
+
try testing.expectEqual(false, confirms.confirm(.{ .delivery_tag = 2, .multiple = false }));
|
|
1426
|
+
try testing.expect(confirms.state == .waiting);
|
|
1427
|
+
try testing.expectEqual(@as(usize, 3), confirms.processed.count());
|
|
1428
|
+
|
|
1429
|
+
try testing.expectEqual(false, confirms.confirm(.{ .delivery_tag = 5, .multiple = true }));
|
|
1430
|
+
try testing.expect(confirms.state == .waiting);
|
|
1431
|
+
try testing.expectEqual(@as(usize, 5), confirms.processed.count());
|
|
1432
|
+
|
|
1433
|
+
try testing.expectEqual(false, confirms.confirm(.{ .delivery_tag = 7, .multiple = false }));
|
|
1434
|
+
try testing.expect(confirms.state == .waiting);
|
|
1435
|
+
try testing.expectEqual(@as(usize, 6), confirms.processed.count());
|
|
1436
|
+
|
|
1437
|
+
try testing.expectEqual(true, confirms.confirm(.{ .delivery_tag = 10, .multiple = true }));
|
|
1438
|
+
try testing.expect(confirms.state == .idle);
|
|
1439
|
+
try testing.expectEqual(@as(usize, 0), confirms.processed.count());
|
|
1440
|
+
try testing.expect(confirms.state.idle.sequence == 11);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
test "amqp: spec" {
|
|
1444
|
+
// Sanity check to ensure the spec hasn't been manually modified.
|
|
1445
|
+
// Checking the hash to avoid downloading the XML from external sources during CI.
|
|
1446
|
+
try testing.expectEqual(
|
|
1447
|
+
269315715514333185404011239500341468006,
|
|
1448
|
+
vsr.checksum(@embedFile("amqp/spec.zig")),
|
|
1449
|
+
);
|
|
1450
|
+
}
|