tigerbeetle-node 0.11.7 → 0.11.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/.client.node.sha256 +1 -1
  2. package/package.json +4 -3
  3. package/scripts/build_lib.sh +29 -0
  4. package/src/node.zig +1 -1
  5. package/src/tigerbeetle/scripts/validate_docs.sh +7 -1
  6. package/src/tigerbeetle/src/benchmark.zig +3 -3
  7. package/src/tigerbeetle/src/config.zig +29 -16
  8. package/src/tigerbeetle/src/constants.zig +30 -9
  9. package/src/tigerbeetle/src/ewah.zig +5 -5
  10. package/src/tigerbeetle/src/ewah_fuzz.zig +1 -1
  11. package/src/tigerbeetle/src/lsm/binary_search.zig +1 -1
  12. package/src/tigerbeetle/src/lsm/bloom_filter.zig +1 -1
  13. package/src/tigerbeetle/src/lsm/compaction.zig +34 -21
  14. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +85 -103
  15. package/src/tigerbeetle/src/lsm/grid.zig +19 -13
  16. package/src/tigerbeetle/src/lsm/manifest_log.zig +8 -10
  17. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +12 -8
  18. package/src/tigerbeetle/src/lsm/merge_iterator.zig +1 -1
  19. package/src/tigerbeetle/src/lsm/segmented_array.zig +17 -17
  20. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +1 -1
  21. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +1 -1
  22. package/src/tigerbeetle/src/lsm/table.zig +8 -20
  23. package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
  24. package/src/tigerbeetle/src/lsm/table_iterator.zig +3 -3
  25. package/src/tigerbeetle/src/lsm/table_mutable.zig +14 -2
  26. package/src/tigerbeetle/src/lsm/tree.zig +31 -5
  27. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +86 -114
  28. package/src/tigerbeetle/src/message_bus.zig +4 -4
  29. package/src/tigerbeetle/src/message_pool.zig +7 -10
  30. package/src/tigerbeetle/src/ring_buffer.zig +22 -12
  31. package/src/tigerbeetle/src/simulator.zig +360 -214
  32. package/src/tigerbeetle/src/state_machine/auditor.zig +5 -5
  33. package/src/tigerbeetle/src/state_machine/workload.zig +3 -3
  34. package/src/tigerbeetle/src/state_machine.zig +190 -178
  35. package/src/tigerbeetle/src/{util.zig → stdx.zig} +2 -0
  36. package/src/tigerbeetle/src/storage.zig +13 -6
  37. package/src/tigerbeetle/src/{test → testing/cluster}/message_bus.zig +3 -3
  38. package/src/tigerbeetle/src/{test → testing/cluster}/network.zig +46 -22
  39. package/src/tigerbeetle/src/testing/cluster/state_checker.zig +169 -0
  40. package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +202 -0
  41. package/src/tigerbeetle/src/testing/cluster.zig +537 -0
  42. package/src/tigerbeetle/src/{test → testing}/fuzz.zig +0 -0
  43. package/src/tigerbeetle/src/testing/hash_log.zig +66 -0
  44. package/src/tigerbeetle/src/{test → testing}/id.zig +0 -0
  45. package/src/tigerbeetle/src/testing/packet_simulator.zig +365 -0
  46. package/src/tigerbeetle/src/{test → testing}/priority_queue.zig +1 -1
  47. package/src/tigerbeetle/src/testing/reply_sequence.zig +139 -0
  48. package/src/tigerbeetle/src/{test → testing}/state_machine.zig +3 -1
  49. package/src/tigerbeetle/src/testing/storage.zig +754 -0
  50. package/src/tigerbeetle/src/{test → testing}/table.zig +21 -0
  51. package/src/tigerbeetle/src/{test → testing}/time.zig +0 -0
  52. package/src/tigerbeetle/src/tigerbeetle.zig +2 -0
  53. package/src/tigerbeetle/src/tracer.zig +3 -3
  54. package/src/tigerbeetle/src/unit_tests.zig +4 -4
  55. package/src/tigerbeetle/src/vopr.zig +2 -2
  56. package/src/tigerbeetle/src/vsr/client.zig +16 -9
  57. package/src/tigerbeetle/src/vsr/clock.zig +93 -53
  58. package/src/tigerbeetle/src/vsr/journal.zig +29 -14
  59. package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +2 -2
  60. package/src/tigerbeetle/src/vsr/replica.zig +1383 -774
  61. package/src/tigerbeetle/src/vsr/replica_format.zig +2 -2
  62. package/src/tigerbeetle/src/vsr/superblock.zig +59 -43
  63. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -7
  64. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +1 -1
  65. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +1 -1
  66. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +15 -7
  67. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +38 -19
  68. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +1 -1
  69. package/src/tigerbeetle/src/vsr.zig +6 -4
  70. package/src/tigerbeetle/src/demo.zig +0 -132
  71. package/src/tigerbeetle/src/demo_01_create_accounts.zig +0 -35
  72. package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +0 -7
  73. package/src/tigerbeetle/src/demo_03_create_transfers.zig +0 -37
  74. package/src/tigerbeetle/src/demo_04_create_pending_transfers.zig +0 -61
  75. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +0 -37
  76. package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +0 -24
  77. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +0 -7
  78. package/src/tigerbeetle/src/test/cluster.zig +0 -352
  79. package/src/tigerbeetle/src/test/conductor.zig +0 -366
  80. package/src/tigerbeetle/src/test/packet_simulator.zig +0 -398
  81. package/src/tigerbeetle/src/test/state_checker.zig +0 -169
  82. package/src/tigerbeetle/src/test/storage.zig +0 -864
  83. package/src/tigerbeetle/src/test/storage_checker.zig +0 -204
@@ -0,0 +1,365 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+ const math = std.math;
4
+
5
+ const log = std.log.scoped(.packet_simulator);
6
+ const vsr = @import("../vsr.zig");
7
+ const PriorityQueue = @import("./priority_queue.zig").PriorityQueue;
8
+ const fuzz = @import("./fuzz.zig");
9
+
10
+ pub const PacketSimulatorOptions = struct {
11
+ replica_count: u8,
12
+ client_count: u8,
13
+ seed: u64,
14
+
15
+ /// Mean for the exponential distribution used to calculate forward delay.
16
+ one_way_delay_mean: u64,
17
+ one_way_delay_min: u64,
18
+
19
+ packet_loss_probability: u8 = 0,
20
+ packet_replay_probability: u8 = 0,
21
+
22
+ /// How the partitions should be generated
23
+ partition_mode: PartitionMode = .none,
24
+
25
+ /// Probability per tick that a partition will occur
26
+ partition_probability: u8 = 0,
27
+
28
+ /// Probability per tick that a partition will resolve
29
+ unpartition_probability: u8 = 0,
30
+
31
+ /// Minimum time a partition lasts
32
+ partition_stability: u32 = 0,
33
+
34
+ /// Minimum time the cluster is fully connected until it is partitioned again
35
+ unpartition_stability: u32 = 0,
36
+
37
+ /// The maximum number of in-flight packets a path can have before packets are randomly dropped.
38
+ path_maximum_capacity: u8,
39
+
40
+ /// Mean for the exponential distribution used to calculate how long a path is clogged for.
41
+ path_clog_duration_mean: u64,
42
+ path_clog_probability: u8,
43
+ };
44
+
45
+ pub const Path = struct {
46
+ source: u8,
47
+ target: u8,
48
+ };
49
+
50
+ /// Determines how the partitions are created. Partitions
51
+ /// are two-way, i.e. if i cannot communicate with j, then
52
+ /// j cannot communicate with i.
53
+ ///
54
+ /// Only replicas are partitioned. There will always be exactly two partitions.
55
+ pub const PartitionMode = enum {
56
+ /// Disable automatic partitioning.
57
+ none,
58
+
59
+ /// Draws the size of the partition uniformly at random from (1, n-1).
60
+ /// Replicas are randomly assigned a partition.
61
+ uniform_size,
62
+
63
+ /// Assigns each node to a partition uniformly at random. This biases towards
64
+ /// equal-size partitions.
65
+ uniform_partition,
66
+
67
+ /// Isolates exactly one replica.
68
+ isolate_single,
69
+ };
70
+
71
+ pub fn PacketSimulatorType(comptime Packet: type) type {
72
+ return struct {
73
+ const Self = @This();
74
+
75
+ const LinkPacket = struct {
76
+ expiry: u64,
77
+ callback: fn (packet: Packet, path: Path) void,
78
+ packet: Packet,
79
+ };
80
+
81
+ const Link = struct {
82
+ queue: PriorityQueue(LinkPacket, void, Self.order_packets),
83
+ /// When false, packets sent on the path are not delivered.
84
+ enabled: bool = true,
85
+ /// We can arbitrary clog a path until a tick.
86
+ clogged_till: u64 = 0,
87
+ };
88
+
89
+ options: PacketSimulatorOptions,
90
+ prng: std.rand.DefaultPrng,
91
+ ticks: u64 = 0,
92
+
93
+ /// A send and receive path between each node in the network.
94
+ /// Indexed by path_index().
95
+ links: []Link,
96
+
97
+ /// Scratch space for automatically generating partitions.
98
+ /// The "source of truth" for partitions is links[*].enabled.
99
+ auto_partition: []bool,
100
+ auto_partition_active: bool,
101
+ auto_partition_replicas: []u8,
102
+ auto_partition_stability: u32,
103
+
104
+ pub fn init(allocator: std.mem.Allocator, options: PacketSimulatorOptions) !Self {
105
+ assert(options.replica_count > 0);
106
+ assert(options.one_way_delay_mean >= options.one_way_delay_min);
107
+
108
+ const node_count_ = options.replica_count + options.client_count;
109
+ const links = try allocator.alloc(Link, @as(usize, node_count_) * node_count_);
110
+ errdefer allocator.free(links);
111
+
112
+ for (links) |*link, i| {
113
+ errdefer for (links[0..i]) |l| l.queue.deinit();
114
+
115
+ const queue = PriorityQueue(LinkPacket, void, Self.order_packets).init(allocator, {});
116
+ try link.queue.ensureTotalCapacity(options.path_maximum_capacity);
117
+ link.* = .{ .queue = queue };
118
+ }
119
+ errdefer for (links) |link| link.queue.deinit();
120
+
121
+ const auto_partition = try allocator.alloc(bool, @as(usize, options.replica_count));
122
+ errdefer allocator.free(auto_partition);
123
+ std.mem.set(bool, auto_partition, false);
124
+
125
+ const auto_partition_replicas = try allocator.alloc(u8, @as(usize, options.replica_count));
126
+ errdefer allocator.free(auto_partition_replicas);
127
+ for (auto_partition_replicas) |*replica, i| replica.* = @intCast(u8, i);
128
+
129
+ return Self{
130
+ .options = options,
131
+ .prng = std.rand.DefaultPrng.init(options.seed),
132
+ .links = links,
133
+
134
+ .auto_partition_active = false,
135
+ .auto_partition = auto_partition,
136
+ .auto_partition_replicas = auto_partition_replicas,
137
+ .auto_partition_stability = options.unpartition_stability,
138
+ };
139
+ }
140
+
141
+ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
142
+ for (self.links) |*link| {
143
+ while (link.queue.peek()) |_| link.queue.remove().packet.deinit();
144
+ link.queue.deinit();
145
+ }
146
+
147
+ allocator.free(self.links);
148
+ allocator.free(self.auto_partition);
149
+ allocator.free(self.auto_partition_replicas);
150
+ }
151
+
152
+ fn order_packets(context: void, a: LinkPacket, b: LinkPacket) math.Order {
153
+ _ = context;
154
+
155
+ return math.order(a.expiry, b.expiry);
156
+ }
157
+
158
+ fn node_count(self: Self) usize {
159
+ return self.options.replica_count + self.options.client_count;
160
+ }
161
+
162
+ fn path_index(self: Self, path: Path) usize {
163
+ assert(path.source < self.node_count());
164
+ assert(path.target < self.node_count());
165
+
166
+ return @as(usize, path.source) * self.node_count() + path.target;
167
+ }
168
+
169
+ fn should_drop(self: *Self) bool {
170
+ return self.prng.random().uintAtMost(u8, 100) < self.options.packet_loss_probability;
171
+ }
172
+
173
+ fn is_clogged(self: *Self, path: Path) bool {
174
+ return self.links[self.path_index(path)].clogged_till > self.ticks;
175
+ }
176
+
177
+ fn should_clog(self: *Self, path: Path) bool {
178
+ _ = path;
179
+
180
+ return self.prng.random().uintAtMost(u8, 100) < self.options.path_clog_probability;
181
+ }
182
+
183
+ fn clog_for(self: *Self, path: Path, ticks: u64) void {
184
+ const clog_expiry = &self.links[self.path_index(path)].clogged_till;
185
+ clog_expiry.* = self.ticks + ticks;
186
+ log.debug("Path path.source={} path.target={} clogged for ticks={}", .{
187
+ path.source,
188
+ path.target,
189
+ ticks,
190
+ });
191
+ }
192
+
193
+ fn should_replay(self: *Self) bool {
194
+ return self.prng.random().uintAtMost(u8, 100) < self.options.packet_replay_probability;
195
+ }
196
+
197
+ fn should_partition(self: *Self) bool {
198
+ return self.prng.random().uintAtMost(u8, 100) < self.options.partition_probability;
199
+ }
200
+
201
+ fn should_unpartition(self: *Self) bool {
202
+ return self.prng.random().uintAtMost(u8, 100) < self.options.unpartition_probability;
203
+ }
204
+
205
+ /// Return a value produced using an exponential distribution with
206
+ /// the minimum and mean specified in self.options
207
+ fn one_way_delay(self: *Self) u64 {
208
+ const min = self.options.one_way_delay_min;
209
+ const mean = self.options.one_way_delay_mean;
210
+ return min + fuzz.random_int_exponential(self.prng.random(), u64, mean - min);
211
+ }
212
+
213
+ /// Partitions the network. Guaranteed to isolate at least one replica.
214
+ fn auto_partition_network(self: *Self) void {
215
+ assert(self.options.replica_count > 1);
216
+
217
+ var partition = self.auto_partition;
218
+ switch (self.options.partition_mode) {
219
+ .none => std.mem.set(bool, partition, false),
220
+ .uniform_size => {
221
+ // Exclude cases partition_size == 0 and partition_size == replica_count
222
+ const partition_size =
223
+ 1 + self.prng.random().uintAtMost(u8, self.options.replica_count - 2);
224
+ self.prng.random().shuffle(u8, self.auto_partition_replicas);
225
+ for (self.auto_partition_replicas) |r, i| {
226
+ partition[r] = i < partition_size;
227
+ }
228
+ },
229
+ .uniform_partition => {
230
+ var only_same = true;
231
+ partition[0] = self.prng.random().uintLessThan(u8, 2) == 1;
232
+
233
+ var i: usize = 1;
234
+ while (i < self.options.replica_count) : (i += 1) {
235
+ partition[i] = self.prng.random().uintLessThan(u8, 2) == 1;
236
+ only_same =
237
+ only_same and (partition[i - 1] == partition[i]);
238
+ }
239
+
240
+ if (only_same) {
241
+ const n = self.prng.random().uintLessThan(u8, self.options.replica_count);
242
+ self.auto_partition[n] = true;
243
+ }
244
+ },
245
+ .isolate_single => {
246
+ std.mem.set(bool, partition, false);
247
+ const n = self.prng.random().uintLessThan(u8, self.options.replica_count);
248
+ partition[n] = true;
249
+ },
250
+ }
251
+
252
+ self.auto_partition_active = true;
253
+ self.auto_partition_stability = self.options.partition_stability;
254
+
255
+ var from: u8 = 0;
256
+ while (from < self.node_count()) : (from += 1) {
257
+ var to: u8 = 0;
258
+ while (to < self.node_count()) : (to += 1) {
259
+ const path = .{ .source = from, .target = to };
260
+ self.links[self.path_index(path)].enabled = from >= self.options.replica_count or
261
+ to >= self.options.replica_count or partition[from] == partition[to];
262
+ }
263
+ }
264
+ }
265
+
266
+ pub fn tick(self: *Self) void {
267
+ self.ticks += 1;
268
+
269
+ if (self.auto_partition_stability > 0) {
270
+ self.auto_partition_stability -= 1;
271
+ } else {
272
+ if (self.auto_partition_active) {
273
+ if (self.should_unpartition()) {
274
+ self.auto_partition_active = false;
275
+ self.auto_partition_stability = self.options.unpartition_stability;
276
+ std.mem.set(bool, self.auto_partition, false);
277
+ for (self.links) |*link| link.enabled = true;
278
+ log.warn("unpartitioned network: partition={d}", .{self.auto_partition});
279
+ }
280
+ } else {
281
+ if (self.options.replica_count > 1 and self.should_partition()) {
282
+ self.auto_partition_network();
283
+ log.warn("partitioned network: partition={d}", .{self.auto_partition});
284
+ }
285
+ }
286
+ }
287
+
288
+ var from: u8 = 0;
289
+ while (from < self.node_count()) : (from += 1) {
290
+ var to: u8 = 0;
291
+ while (to < self.node_count()) : (to += 1) {
292
+ const path = .{ .source = from, .target = to };
293
+ if (self.is_clogged(path)) continue;
294
+
295
+ const queue = &self.links[self.path_index(path)].queue;
296
+ while (queue.peek()) |*link_packet| {
297
+ if (link_packet.expiry > self.ticks) break;
298
+ _ = queue.remove();
299
+
300
+ if (!self.links[self.path_index(path)].enabled) {
301
+ log.warn("dropped packet (different partitions): from={} to={}", .{ from, to });
302
+ link_packet.packet.deinit();
303
+ continue;
304
+ }
305
+
306
+ if (self.should_drop()) {
307
+ log.warn("dropped packet from={} to={}", .{ from, to });
308
+ link_packet.packet.deinit();
309
+ continue;
310
+ }
311
+
312
+ if (self.should_replay()) {
313
+ self.submit_packet(link_packet.packet, link_packet.callback, path);
314
+
315
+ log.debug("replayed packet from={} to={}", .{ from, to });
316
+
317
+ link_packet.callback(link_packet.packet, path);
318
+ } else {
319
+ log.debug("delivering packet from={} to={}", .{ from, to });
320
+ link_packet.callback(link_packet.packet, path);
321
+ link_packet.packet.deinit();
322
+ }
323
+ }
324
+
325
+ const reverse_path: Path = .{ .source = to, .target = from };
326
+
327
+ if (self.should_clog(reverse_path)) {
328
+ log.debug("reverse path clogged", .{});
329
+ const ticks = fuzz.random_int_exponential(
330
+ self.prng.random(),
331
+ u64,
332
+ self.options.path_clog_duration_mean,
333
+ );
334
+ self.clog_for(reverse_path, ticks);
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ pub fn submit_packet(
341
+ self: *Self,
342
+ packet: Packet,
343
+ callback: fn (packet: Packet, path: Path) void,
344
+ path: Path,
345
+ ) void {
346
+ const queue = &self.links[self.path_index(path)].queue;
347
+ var queue_length = queue.count();
348
+ if (queue_length + 1 > self.options.path_maximum_capacity) {
349
+ const index = self.prng.random().uintLessThanBiased(u64, queue_length);
350
+ const link_packet = queue.removeIndex(index);
351
+ link_packet.packet.deinit();
352
+ log.warn("submit_packet: {} reached capacity, dropped packet={}", .{
353
+ path,
354
+ index,
355
+ });
356
+ }
357
+
358
+ queue.add(.{
359
+ .expiry = self.ticks + self.one_way_delay(),
360
+ .packet = packet,
361
+ .callback = callback,
362
+ }) catch unreachable;
363
+ }
364
+ };
365
+ }
@@ -1,7 +1,7 @@
1
1
  // Backport PriorityQueue fixes from 0.10 to 0.9.
2
2
  // https://github.com/ziglang/zig/blob/d925d19cfcabd96fdc4459e11ecb85a4f42ec655/lib/std/priority_queue.zig
3
3
  //
4
- // In particular, the conductor hits this bug:
4
+ // In particular, the ReplySequence hits this bug:
5
5
  // https://github.com/ziglang/zig/commit/7deae071014237e995ec3017825f7534305ec0c4
6
6
  //
7
7
  // Also https://github.com/ziglang/zig/commit/d925d19cfcabd96fdc4459e11ecb85a4f42ec655
@@ -0,0 +1,139 @@
1
+ //! Replies from the cluster may arrive out-of-order; the ReplySequence reassembles them in the
2
+ //! correct order (by ascending op number).
3
+ const std = @import("std");
4
+ const assert = std.debug.assert;
5
+
6
+ const vsr = @import("../vsr.zig");
7
+ const stdx = @import("../stdx.zig");
8
+ const constants = @import("../constants.zig");
9
+ const IdPermutation = @import("id.zig").IdPermutation;
10
+ const MessagePool = @import("../message_pool.zig").MessagePool;
11
+ const Message = MessagePool.Message;
12
+ const Client = @import("cluster.zig").Client;
13
+ const StateMachine = @import("cluster.zig").StateMachine;
14
+
15
+ // TODO(zig) This won't be necessary in Zig 0.10.
16
+ const PriorityQueue = @import("./priority_queue.zig").PriorityQueue;
17
+
18
+ /// Both messages belong to the ReplySequence's `MessagePool`.
19
+ const PendingReply = struct {
20
+ client_index: usize,
21
+ request: *Message,
22
+ reply: *Message,
23
+
24
+ /// `PendingReply`s are ordered by ascending reply op.
25
+ fn compare(context: void, a: PendingReply, b: PendingReply) std.math.Order {
26
+ _ = context;
27
+ return std.math.order(a.reply.header.op, b.reply.header.op);
28
+ }
29
+ };
30
+
31
+ const PendingReplyQueue = PriorityQueue(PendingReply, void, PendingReply.compare);
32
+
33
+ pub const ReplySequence = struct {
34
+ /// Reply messages (from cluster to client) may be reordered during transit.
35
+ /// The ReplySequence must reassemble them in the original order (ascending op/commit
36
+ /// number) before handing them off to the Workload for verification.
37
+ ///
38
+ /// `ReplySequence.stalled_queue` hold replies (and corresponding requests) that are
39
+ /// waiting to be processed.
40
+ pub const stalled_queue_capacity =
41
+ constants.clients_max * constants.client_request_queue_max * 2;
42
+
43
+ message_pool: MessagePool,
44
+
45
+ /// The next op to be verified.
46
+ /// Starts at 1, because op=0 is the root.
47
+ stalled_op: u64 = 1,
48
+
49
+ /// The list of messages waiting to be verified (the reply for a lower op has not yet arrived).
50
+ /// Includes `register` messages.
51
+ stalled_queue: PendingReplyQueue,
52
+
53
+ pub fn init(allocator: std.mem.Allocator) !ReplySequence {
54
+ // *2 for PendingReply.request and PendingReply.reply.
55
+ var message_pool = try MessagePool.init_capacity(allocator, stalled_queue_capacity * 2);
56
+ errdefer message_pool.deinit(allocator);
57
+
58
+ var stalled_queue = PendingReplyQueue.init(allocator, {});
59
+ errdefer stalled_queue.deinit();
60
+ try stalled_queue.ensureTotalCapacity(stalled_queue_capacity);
61
+
62
+ return ReplySequence{
63
+ .message_pool = message_pool,
64
+ .stalled_queue = stalled_queue,
65
+ };
66
+ }
67
+
68
+ pub fn deinit(sequence: *ReplySequence, allocator: std.mem.Allocator) void {
69
+ while (sequence.stalled_queue.removeOrNull()) |pending| {
70
+ sequence.message_pool.unref(pending.request);
71
+ sequence.message_pool.unref(pending.reply);
72
+ }
73
+ sequence.stalled_queue.deinit();
74
+ sequence.message_pool.deinit(allocator);
75
+ }
76
+
77
+ pub fn empty(sequence: *const ReplySequence) bool {
78
+ return sequence.stalled_queue.len == 0;
79
+ }
80
+
81
+ pub fn free(sequence: ReplySequence) usize {
82
+ return stalled_queue_capacity - sequence.stalled_queue.len;
83
+ }
84
+
85
+ pub fn insert(
86
+ sequence: *ReplySequence,
87
+ client_index: usize,
88
+ request_message: *Message,
89
+ reply_message: *Message,
90
+ ) void {
91
+ assert(request_message.header.invalid() == null);
92
+ assert(request_message.header.command == .request);
93
+
94
+ assert(reply_message.header.invalid() == null);
95
+ assert(reply_message.header.request == request_message.header.request);
96
+ assert(reply_message.header.op >= sequence.stalled_op);
97
+ assert(reply_message.header.command == .reply);
98
+ assert(reply_message.header.operation == request_message.header.operation);
99
+
100
+ sequence.stalled_queue.add(.{
101
+ .client_index = client_index,
102
+ .request = sequence.clone_message(request_message),
103
+ .reply = sequence.clone_message(reply_message),
104
+ }) catch unreachable;
105
+ }
106
+
107
+ pub fn peek(sequence: *ReplySequence) ?PendingReply {
108
+ assert(sequence.stalled_queue.len <= stalled_queue_capacity);
109
+
110
+ const commit = sequence.stalled_queue.peek() orelse return null;
111
+ if (commit.reply.header.op == sequence.stalled_op) {
112
+ return commit;
113
+ } else {
114
+ assert(commit.reply.header.op > sequence.stalled_op);
115
+ return null;
116
+ }
117
+ }
118
+
119
+ pub fn next(sequence: *ReplySequence) void {
120
+ const commit = sequence.stalled_queue.remove();
121
+ assert(commit.reply.header.op == sequence.stalled_op);
122
+
123
+ sequence.stalled_op += 1;
124
+ sequence.message_pool.unref(commit.reply);
125
+ sequence.message_pool.unref(commit.request);
126
+ }
127
+
128
+ /// Copy the message from a Client's MessagePool to the ReplySequence's MessagePool.
129
+ ///
130
+ /// The client has a finite amount of messages in its pool, and the ReplySequence needs to hold
131
+ /// onto requests/replies until all preceeding requests/replies have arrived.
132
+ ///
133
+ /// Returns the ReplySequence's message.
134
+ fn clone_message(sequence: *ReplySequence, message_client: *const Message) *Message {
135
+ const message_sequence = sequence.message_pool.get_message();
136
+ stdx.copy_disjoint(.exact, u8, message_sequence.buffer, message_client.buffer);
137
+ return message_sequence;
138
+ }
139
+ };
@@ -91,12 +91,14 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
91
91
  state_machine: *StateMachine,
92
92
  client: u128,
93
93
  op: u64,
94
+ timestamp: u64,
94
95
  operation: Operation,
95
96
  input: []const u8,
96
97
  output: []u8,
97
98
  ) usize {
98
99
  _ = state_machine;
99
100
  _ = client;
101
+ _ = timestamp;
100
102
  _ = input;
101
103
  _ = output;
102
104
  assert(op != 0);
@@ -148,7 +150,7 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
148
150
  state_machine.grid.write_block(
149
151
  next_tick_callback,
150
152
  &state_machine.grid_write,
151
- state_machine.grid_block,
153
+ &state_machine.grid_block,
152
154
  address,
153
155
  );
154
156
  }