tigerbeetle-node 0.11.0 → 0.11.1

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 (80) hide show
  1. package/package.json +4 -3
  2. package/src/tigerbeetle/scripts/fuzz_loop.sh +1 -1
  3. package/src/tigerbeetle/scripts/pre-commit.sh +2 -2
  4. package/src/tigerbeetle/scripts/validate_docs.sh +17 -0
  5. package/src/tigerbeetle/src/benchmark.zig +25 -11
  6. package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
  7. package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
  8. package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
  9. package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
  10. package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -256
  11. package/src/tigerbeetle/src/c/tb_client.h +18 -4
  12. package/src/tigerbeetle/src/c/tb_client.zig +88 -26
  13. package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
  14. package/src/tigerbeetle/src/c/test.zig +371 -1
  15. package/src/tigerbeetle/src/cli.zig +36 -6
  16. package/src/tigerbeetle/src/config.zig +10 -1
  17. package/src/tigerbeetle/src/demo.zig +2 -1
  18. package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
  19. package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
  20. package/src/tigerbeetle/src/ewah.zig +11 -33
  21. package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
  22. package/src/tigerbeetle/src/lsm/README.md +97 -3
  23. package/src/tigerbeetle/src/lsm/compaction.zig +32 -7
  24. package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
  25. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +34 -32
  26. package/src/tigerbeetle/src/lsm/grid.zig +39 -21
  27. package/src/tigerbeetle/src/lsm/groove.zig +1 -0
  28. package/src/tigerbeetle/src/lsm/k_way_merge.zig +3 -3
  29. package/src/tigerbeetle/src/lsm/level_iterator.zig +1 -1
  30. package/src/tigerbeetle/src/lsm/manifest.zig +13 -0
  31. package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -49
  32. package/src/tigerbeetle/src/lsm/manifest_log.zig +173 -335
  33. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
  34. package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
  35. package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
  36. package/src/tigerbeetle/src/lsm/segmented_array.zig +24 -15
  37. package/src/tigerbeetle/src/lsm/table.zig +32 -20
  38. package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
  39. package/src/tigerbeetle/src/lsm/table_iterator.zig +4 -5
  40. package/src/tigerbeetle/src/lsm/test.zig +13 -2
  41. package/src/tigerbeetle/src/lsm/tree.zig +45 -7
  42. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +36 -32
  43. package/src/tigerbeetle/src/main.zig +55 -2
  44. package/src/tigerbeetle/src/message_bus.zig +18 -7
  45. package/src/tigerbeetle/src/message_pool.zig +8 -2
  46. package/src/tigerbeetle/src/ring_buffer.zig +7 -3
  47. package/src/tigerbeetle/src/simulator.zig +38 -11
  48. package/src/tigerbeetle/src/state_machine.zig +47 -22
  49. package/src/tigerbeetle/src/test/accounting/workload.zig +9 -5
  50. package/src/tigerbeetle/src/test/cluster.zig +15 -33
  51. package/src/tigerbeetle/src/test/conductor.zig +2 -1
  52. package/src/tigerbeetle/src/test/network.zig +45 -19
  53. package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
  54. package/src/tigerbeetle/src/test/state_checker.zig +5 -7
  55. package/src/tigerbeetle/src/test/storage.zig +453 -110
  56. package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
  57. package/src/tigerbeetle/src/tigerbeetle.zig +1 -0
  58. package/src/tigerbeetle/src/unit_tests.zig +6 -1
  59. package/src/tigerbeetle/src/util.zig +97 -11
  60. package/src/tigerbeetle/src/vopr.zig +2 -1
  61. package/src/tigerbeetle/src/vsr/client.zig +8 -3
  62. package/src/tigerbeetle/src/vsr/journal.zig +280 -202
  63. package/src/tigerbeetle/src/vsr/replica.zig +169 -31
  64. package/src/tigerbeetle/src/vsr/superblock.zig +356 -629
  65. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -6
  66. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +414 -151
  67. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
  68. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
  69. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +44 -9
  70. package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
  71. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
  72. package/src/tigerbeetle/src/vsr.zig +19 -5
  73. package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
  74. package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
  75. package/src/tigerbeetle/src/vopr_hub/README.md +0 -58
  76. package/src/tigerbeetle/src/vopr_hub/SETUP.md +0 -199
  77. package/src/tigerbeetle/src/vopr_hub/go.mod +0 -3
  78. package/src/tigerbeetle/src/vopr_hub/main.go +0 -1022
  79. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +0 -3
  80. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +0 -403
@@ -12,6 +12,7 @@ const log = std.log.scoped(.message_bus);
12
12
  const vsr = @import("vsr.zig");
13
13
  const Header = vsr.Header;
14
14
 
15
+ const util = @import("util.zig");
15
16
  const RingBuffer = @import("ring_buffer.zig").RingBuffer;
16
17
  const IO = @import("io.zig").IO;
17
18
  const MessagePool = @import("message_pool.zig").MessagePool;
@@ -44,7 +45,7 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
44
45
  io: *IO,
45
46
 
46
47
  cluster: u32,
47
- configuration: []std.net.Address,
48
+ configuration: []const std.net.Address,
48
49
 
49
50
  process: switch (process_type) {
50
51
  .replica => struct {
@@ -84,7 +85,7 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
84
85
  prng: std.rand.DefaultPrng,
85
86
 
86
87
  pub const Options = struct {
87
- configuration: []std.net.Address,
88
+ configuration: []const std.net.Address,
88
89
  io: *IO,
89
90
  };
90
91
 
@@ -145,8 +146,19 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
145
146
  return bus;
146
147
  }
147
148
 
148
- /// TODO This is required by the Client.
149
- pub fn deinit(_: *Self, _: std.mem.Allocator) void {}
149
+ pub fn deinit(bus: *Self, allocator: std.mem.Allocator) void {
150
+ if (process_type == .replica) {
151
+ bus.process.clients.deinit(allocator);
152
+ }
153
+
154
+ for (bus.connections) |*connection| {
155
+ if (connection.recv_message) |message| bus.unref(message);
156
+ while (connection.send_queue.pop()) |message| bus.unref(message);
157
+ }
158
+ allocator.free(bus.connections);
159
+ allocator.free(bus.replicas);
160
+ allocator.free(bus.replicas_connect_attempts);
161
+ }
150
162
 
151
163
  fn init_tcp(io: *IO, address: std.net.Address) !os.socket_t {
152
164
  const fd = try io.open_socket(
@@ -738,7 +750,7 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
738
750
  if (connection.recv_progress == header.size) return connection.recv_message.?.ref();
739
751
 
740
752
  const message = bus.get_message();
741
- mem.copy(u8, message.buffer, data[0..header.size]);
753
+ util.copy_disjoint(.inexact, u8, message.buffer, data[0..header.size]);
742
754
  return message;
743
755
  }
744
756
 
@@ -811,7 +823,6 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
811
823
  }
812
824
 
813
825
  /// Acquires a free message if necessary and then calls `recv()`.
814
- /// Terminates the connection if a free message cannot be obtained.
815
826
  /// If the connection has a `recv_message` and the message being parsed is
816
827
  /// at pole position then calls `recv()` immediately, otherwise copies any
817
828
  /// partially received message into a new Message and sets `recv_message`,
@@ -831,7 +842,7 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
831
842
  assert(connection.recv_progress > 0);
832
843
  assert(connection.recv_parsed > 0);
833
844
  const data = recv_message.buffer[connection.recv_parsed..connection.recv_progress];
834
- mem.copy(u8, new_message.buffer, data);
845
+ util.copy_disjoint(.inexact, u8, new_message.buffer, data);
835
846
  connection.recv_progress = data.len;
836
847
  connection.recv_parsed = 0;
837
848
  } else {
@@ -37,7 +37,6 @@ pub const messages_max_replica = messages_max: {
37
37
  // Handle Replica.commit_op's reply:
38
38
  // (This is separate from the burst +1 because they may occur concurrently).
39
39
  sum += 1;
40
- sum += 20; // TODO Our network simulator allows up to 20 messages for path_capacity_max.
41
40
 
42
41
  break :messages_max sum;
43
42
  };
@@ -51,7 +50,6 @@ pub const messages_max_client = messages_max: {
51
50
  sum += config.client_request_queue_max; // Client.request_queue
52
51
  // Handle bursts (e.g. Connection.parse_message, or sending a ping when the send queue is full).
53
52
  sum += 1;
54
- sum += 20; // TODO Our network simulator allows up to 20 messages for path_capacity_max.
55
53
 
56
54
  break :messages_max sum;
57
55
  };
@@ -86,6 +84,8 @@ pub const MessagePool = struct {
86
84
  /// List of currently unused messages.
87
85
  free_list: ?*Message,
88
86
 
87
+ messages_max: usize,
88
+
89
89
  pub fn init(allocator: mem.Allocator, process_type: vsr.ProcessType) error{OutOfMemory}!MessagePool {
90
90
  return MessagePool.init_capacity(allocator, switch (process_type) {
91
91
  .replica => messages_max_replica,
@@ -96,6 +96,7 @@ pub const MessagePool = struct {
96
96
  pub fn init_capacity(allocator: mem.Allocator, messages_max: usize) error{OutOfMemory}!MessagePool {
97
97
  var pool: MessagePool = .{
98
98
  .free_list = null,
99
+ .messages_max = messages_max,
99
100
  };
100
101
  {
101
102
  var i: usize = 0;
@@ -121,11 +122,16 @@ pub const MessagePool = struct {
121
122
 
122
123
  /// Frees all messages that were unused or returned to the pool via unref().
123
124
  pub fn deinit(pool: *MessagePool, allocator: mem.Allocator) void {
125
+ var free_count: usize = 0;
124
126
  while (pool.free_list) |message| {
125
127
  pool.free_list = message.next;
126
128
  allocator.free(message.buffer);
127
129
  allocator.destroy(message);
130
+ free_count += 1;
128
131
  }
132
+ // If the MessagePool is being deinitialized, all messages should have already been
133
+ // released to the pool.
134
+ assert(free_count == pool.messages_max);
129
135
  }
130
136
 
131
137
  /// Get an unused message with a buffer of config.message_size_max.
@@ -3,15 +3,19 @@ const assert = std.debug.assert;
3
3
  const math = std.math;
4
4
  const mem = std.mem;
5
5
 
6
+ const util = @import("util.zig");
7
+
6
8
  /// A First In, First Out ring buffer holding at most `count_max` elements.
7
9
  pub fn RingBuffer(
8
10
  comptime T: type,
9
- comptime count_max: usize,
11
+ comptime count_max_: usize,
10
12
  comptime buffer_type: enum { array, pointer },
11
13
  ) type {
12
14
  return struct {
13
15
  const Self = @This();
14
16
 
17
+ pub const count_max = count_max_;
18
+
15
19
  buffer: switch (buffer_type) {
16
20
  .array => [count_max]T,
17
21
  .pointer => *[count_max]T,
@@ -145,8 +149,8 @@ pub fn RingBuffer(
145
149
  const pre_wrap_count = math.min(items.len, self.buffer.len - pre_wrap_start);
146
150
  const post_wrap_count = items.len - pre_wrap_count;
147
151
 
148
- mem.copy(T, self.buffer[pre_wrap_start..], items[0..pre_wrap_count]);
149
- mem.copy(T, self.buffer[0..post_wrap_count], items[pre_wrap_count..]);
152
+ util.copy_disjoint(.inexact, T, self.buffer[pre_wrap_start..], items[0..pre_wrap_count]);
153
+ util.copy_disjoint(.exact, T, self.buffer[0..post_wrap_count], items[pre_wrap_count..]);
150
154
 
151
155
  self.count += items.len;
152
156
  }
@@ -14,6 +14,7 @@ const ClusterOptions = @import("test/cluster.zig").ClusterOptions;
14
14
  const Replica = @import("test/cluster.zig").Replica;
15
15
  const StateMachine = @import("test/cluster.zig").StateMachine;
16
16
  const StateChecker = @import("test/state_checker.zig").StateChecker;
17
+ const StorageChecker = @import("test/storage_checker.zig").StorageChecker;
17
18
  const PartitionMode = @import("test/packet_simulator.zig").PartitionMode;
18
19
  const MessageBus = @import("test/message_bus.zig").MessageBus;
19
20
  const auditor = @import("test/accounting/auditor.zig");
@@ -28,8 +29,7 @@ const output = std.log.scoped(.state_checker);
28
29
  /// This will run much slower but will trace all logic across the cluster.
29
30
  const log_state_transitions_only = builtin.mode != .Debug;
30
31
 
31
- const log_health = std.log.scoped(.health);
32
- const log_faults = std.log.scoped(.faults);
32
+ const log_simulator = std.log.scoped(.simulator);
33
33
 
34
34
  /// You can fine tune your log levels even further (debug/info/notice/warn/err/crit/alert/emerg):
35
35
  pub const log_level: std.log.Level = if (log_state_transitions_only) .info else .debug;
@@ -41,6 +41,7 @@ const cluster_id = 0;
41
41
 
42
42
  var cluster: *Cluster = undefined;
43
43
  var state_checker: *StateChecker = undefined;
44
+ var storage_checker: *StorageChecker = undefined;
44
45
 
45
46
  pub fn main() !void {
46
47
  comptime {
@@ -103,7 +104,9 @@ pub fn main() !void {
103
104
  // TODO Compute an upper-bound for this based on requests_committed_max.
104
105
  .grid_size_max = 1024 * 1024 * 256,
105
106
  .seed = random.int(u64),
106
- .on_change_state = on_change_replica,
107
+ .on_change_state = on_replica_change_state,
108
+ .on_compact = on_replica_compact,
109
+ .on_checkpoint = on_replica_checkpoint,
107
110
  .network_options = .{
108
111
  .packet_simulator_options = .{
109
112
  .replica_count = replica_count,
@@ -134,11 +137,17 @@ pub fn main() !void {
134
137
  .write_latency_mean = 3 + random.uintLessThan(u16, 100),
135
138
  .read_fault_probability = random.uintLessThan(u8, 10),
136
139
  .write_fault_probability = random.uintLessThan(u8, 10),
140
+ // TODO Allow WAL faults on crash when replica_count=1 when redundant-header-repair
141
+ // is implemented after recovering with decision=fix. Otherwise we can end up with
142
+ // multiple crashes faulting first a redundant headers, then a prepare, upgrading
143
+ // a decision=fix to decision=vsr.
144
+ .crash_fault_probability = if (replica_count == 1) 0 else 80 + random.uintLessThan(u8, 21),
145
+ .faulty_superblock = true,
137
146
  },
138
147
  .health_options = .{
139
- .crash_probability = 0.0001,
148
+ .crash_probability = 0.000001,
140
149
  .crash_stability = random.uintLessThan(u32, 1_000),
141
- .restart_probability = 0.01,
150
+ .restart_probability = 0.0001,
142
151
  .restart_stability = random.uintLessThan(u32, 1_000),
143
152
  },
144
153
  .state_machine_options = .{
@@ -292,6 +301,12 @@ pub fn main() !void {
292
301
  );
293
302
  defer state_checker.deinit();
294
303
 
304
+ storage_checker = try allocator.create(StorageChecker);
305
+ defer allocator.destroy(storage_checker);
306
+
307
+ storage_checker.* = StorageChecker.init(allocator);
308
+ defer storage_checker.deinit();
309
+
295
310
  // The minimum number of healthy replicas required for a crashed replica to be able to recover.
296
311
  const replica_normal_min = replicas: {
297
312
  if (replica_count == 1) {
@@ -330,14 +345,14 @@ pub fn main() !void {
330
345
  // complete the VSR recovery protocol either.
331
346
  if (cluster.health[replica] == .up and crashes == 0) {
332
347
  if (storage.faulty) {
333
- log_faults.debug("{}: disable storage faults", .{replica});
348
+ log_simulator.debug("{}: disable storage faults", .{replica});
334
349
  storage.faulty = false;
335
350
  }
336
351
  } else {
337
352
  // When a journal recovers for the first time, enable its storage faults.
338
353
  // Future crashes will recover in the presence of faults.
339
354
  if (!storage.faulty) {
340
- log_faults.debug("{}: enable storage faults", .{replica});
355
+ log_simulator.debug("{}: enable storage faults", .{replica});
341
356
  storage.faulty = true;
342
357
  }
343
358
  }
@@ -364,7 +379,7 @@ pub fn main() !void {
364
379
  }
365
380
 
366
381
  if (!try cluster.crash_replica(replica.replica)) continue;
367
- log_health.debug("{}: crash replica", .{replica.replica});
382
+ log_simulator.debug("{}: crash replica", .{replica.replica});
368
383
  crashes -= 1;
369
384
  },
370
385
  .down => |*ticks| {
@@ -375,7 +390,7 @@ pub fn main() !void {
375
390
  assert(replica.status == .recovering);
376
391
  if (ticks.* == 0 and chance_f64(random, health_options.restart_probability)) {
377
392
  cluster.health[replica.replica] = .{ .up = health_options.restart_stability };
378
- log_health.debug("{}: restart replica", .{replica.replica});
393
+ log_simulator.debug("{}: restart replica", .{replica.replica});
379
394
  }
380
395
  },
381
396
  }
@@ -416,7 +431,7 @@ fn fatal(exit_code: ExitCode, comptime fmt_string: []const u8, args: anytype) no
416
431
  /// Returns true, `p` percent of the time, else false.
417
432
  fn chance_f64(random: std.rand.Random, p: f64) bool {
418
433
  assert(p <= 100.0);
419
- return random.float(f64) < p;
434
+ return random.float(f64) * 100.0 < p;
420
435
  }
421
436
 
422
437
  /// Returns the next argument for the simulator or null (if none available)
@@ -425,12 +440,24 @@ fn args_next(args: *std.process.ArgIterator, allocator: std.mem.Allocator) ?[:0]
425
440
  return err_or_bytes catch @panic("Unable to extract next value from args");
426
441
  }
427
442
 
428
- fn on_change_replica(replica: *Replica) void {
443
+ fn on_replica_change_state(replica: *const Replica) void {
429
444
  state_checker.check_state(replica.replica) catch |err| {
430
445
  fatal(.correctness, "state checker error: {}", .{err});
431
446
  };
432
447
  }
433
448
 
449
+ fn on_replica_compact(replica: *const Replica) void {
450
+ storage_checker.replica_compact(replica) catch |err| {
451
+ fatal(.correctness, "storage checker error: {}", .{err});
452
+ };
453
+ }
454
+
455
+ fn on_replica_checkpoint(replica: *const Replica) void {
456
+ storage_checker.replica_checkpoint(replica) catch |err| {
457
+ fatal(.correctness, "storage checker error: {}", .{err});
458
+ };
459
+ }
460
+
434
461
  /// Returns a random partitioning mode, excluding .custom
435
462
  fn random_partition_mode(random: std.rand.Random) PartitionMode {
436
463
  const typeInfo = @typeInfo(PartitionMode).Enum;
@@ -75,10 +75,10 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
75
75
  pub const lookup_transfers = operation_batch_max(.lookup_transfers);
76
76
 
77
77
  comptime {
78
- assert(create_accounts >= 0);
79
- assert(create_transfers >= 0);
80
- assert(lookup_accounts >= 0);
81
- assert(lookup_transfers >= 0);
78
+ assert(create_accounts > 0);
79
+ assert(create_transfers > 0);
80
+ assert(lookup_accounts > 0);
81
+ assert(lookup_transfers > 0);
82
82
  }
83
83
 
84
84
  fn operation_batch_max(comptime operation: Operation) usize {
@@ -571,7 +571,7 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
571
571
 
572
572
  if (self.get_account(a.id)) |e| return create_account_exists(a, e);
573
573
 
574
- self.forest.grooves.accounts.put(a);
574
+ self.forest.grooves.accounts.put_no_clobber(a);
575
575
 
576
576
  self.commit_timestamp = a.timestamp;
577
577
  return .ok;
@@ -653,6 +653,7 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
653
653
  if (sum_overflows(t.amount, cr.credits_pending + cr.credits_posted)) {
654
654
  return .overflows_credits;
655
655
  }
656
+ if (sum_overflows(t.timestamp, t.timeout)) return .overflows_timeout;
656
657
 
657
658
  if (dr.debits_exceed_credits(t.amount)) return .exceeds_credits;
658
659
  if (cr.credits_exceed_debits(t.amount)) return .exceeds_debits;
@@ -1074,23 +1075,19 @@ const TestContext = struct {
1074
1075
  allocator,
1075
1076
  4096,
1076
1077
  .{
1077
- .seed = 0,
1078
1078
  .read_latency_min = 0,
1079
1079
  .read_latency_mean = 0,
1080
1080
  .write_latency_min = 0,
1081
1081
  .write_latency_mean = 0,
1082
- .read_fault_probability = 0,
1083
- .write_fault_probability = 0,
1084
- },
1085
- 0,
1086
- .{
1087
- .first_offset = 0,
1088
- .period = 0,
1089
1082
  },
1090
1083
  );
1091
1084
  errdefer ctx.storage.deinit(allocator);
1092
1085
 
1093
- ctx.message_pool = .{ .free_list = null };
1086
+ ctx.message_pool = .{
1087
+ .free_list = null,
1088
+ .messages_max = 0,
1089
+ };
1090
+ errdefer ctx.message_pool.deinit(allocator);
1094
1091
 
1095
1092
  ctx.superblock = try SuperBlock.init(allocator, &ctx.storage, &ctx.message_pool);
1096
1093
  errdefer ctx.superblock.deinit(allocator);
@@ -1837,7 +1834,7 @@ test "create/lookup/rollback transfers" {
1837
1834
  .id = 1,
1838
1835
  .debit_account_id = 100,
1839
1836
  .credit_account_id = 200,
1840
- .timeout = 1,
1837
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1841
1838
  .ledger = 0,
1842
1839
  .code = 0,
1843
1840
  .amount = 0,
@@ -1850,7 +1847,7 @@ test "create/lookup/rollback transfers" {
1850
1847
  .id = 1,
1851
1848
  .debit_account_id = 100,
1852
1849
  .credit_account_id = 200,
1853
- .timeout = 1,
1850
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1854
1851
  .ledger = 0,
1855
1852
  .code = 0,
1856
1853
  .flags = .{ .pending = true },
@@ -1864,7 +1861,7 @@ test "create/lookup/rollback transfers" {
1864
1861
  .id = 1,
1865
1862
  .debit_account_id = 100,
1866
1863
  .credit_account_id = 200,
1867
- .timeout = 1,
1864
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1868
1865
  .ledger = 100,
1869
1866
  .code = 0,
1870
1867
  .flags = .{ .pending = true },
@@ -1878,7 +1875,7 @@ test "create/lookup/rollback transfers" {
1878
1875
  .id = 1,
1879
1876
  .debit_account_id = 100,
1880
1877
  .credit_account_id = 200,
1881
- .timeout = 1,
1878
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1882
1879
  .ledger = 100,
1883
1880
  .code = 1,
1884
1881
  .flags = .{ .pending = true },
@@ -1892,7 +1889,7 @@ test "create/lookup/rollback transfers" {
1892
1889
  .id = 1,
1893
1890
  .debit_account_id = 100,
1894
1891
  .credit_account_id = 200,
1895
- .timeout = 1,
1892
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1896
1893
  .ledger = 100,
1897
1894
  .code = 1,
1898
1895
  .flags = .{ .pending = true },
@@ -1906,7 +1903,7 @@ test "create/lookup/rollback transfers" {
1906
1903
  .id = 1,
1907
1904
  .debit_account_id = 1,
1908
1905
  .credit_account_id = 200,
1909
- .timeout = 1,
1906
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1910
1907
  .ledger = 100,
1911
1908
  .code = 1,
1912
1909
  .flags = .{ .pending = true },
@@ -1920,9 +1917,11 @@ test "create/lookup/rollback transfers" {
1920
1917
  .id = 1,
1921
1918
  .debit_account_id = 1,
1922
1919
  .credit_account_id = 2,
1920
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1923
1921
  .ledger = 100,
1924
1922
  .code = 1,
1925
1923
  .amount = 1,
1924
+ .flags = .{ .pending = true },
1926
1925
  .timestamp = timestamp,
1927
1926
  }),
1928
1927
  },
@@ -1932,9 +1931,11 @@ test "create/lookup/rollback transfers" {
1932
1931
  .id = 1,
1933
1932
  .debit_account_id = 1,
1934
1933
  .credit_account_id = 3,
1934
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1935
1935
  .ledger = 100,
1936
1936
  .code = 1,
1937
1937
  .amount = 1,
1938
+ .flags = .{ .pending = true },
1938
1939
  .timestamp = timestamp,
1939
1940
  }),
1940
1941
  },
@@ -1944,7 +1945,7 @@ test "create/lookup/rollback transfers" {
1944
1945
  .id = 1,
1945
1946
  .debit_account_id = 1,
1946
1947
  .credit_account_id = 3,
1947
- .timeout = 30000,
1948
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1948
1949
  .ledger = 1,
1949
1950
  .code = 1,
1950
1951
  .flags = .{ .pending = true },
@@ -1958,7 +1959,7 @@ test "create/lookup/rollback transfers" {
1958
1959
  .id = 1,
1959
1960
  .debit_account_id = 1,
1960
1961
  .credit_account_id = 3,
1961
- .timeout = 30000,
1962
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1962
1963
  .ledger = 1,
1963
1964
  .code = 1,
1964
1965
  .flags = .{ .pending = true },
@@ -1972,8 +1973,10 @@ test "create/lookup/rollback transfers" {
1972
1973
  .id = 1,
1973
1974
  .debit_account_id = 1,
1974
1975
  .credit_account_id = 3,
1976
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1975
1977
  .ledger = 1,
1976
1978
  .code = 1,
1979
+ .flags = .{ .pending = true },
1977
1980
  .amount = math.maxInt(u64) - accounts[1 - 1].debits_posted + 1,
1978
1981
  .timestamp = timestamp,
1979
1982
  }),
@@ -1984,8 +1987,10 @@ test "create/lookup/rollback transfers" {
1984
1987
  .id = 1,
1985
1988
  .debit_account_id = 1,
1986
1989
  .credit_account_id = 3,
1990
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1987
1991
  .ledger = 1,
1988
1992
  .code = 1,
1993
+ .flags = .{ .pending = true },
1989
1994
  .amount = math.maxInt(u64) - accounts[3 - 1].credits_posted + 1,
1990
1995
  .timestamp = timestamp,
1991
1996
  }),
@@ -1996,8 +2001,10 @@ test "create/lookup/rollback transfers" {
1996
2001
  .id = 1,
1997
2002
  .debit_account_id = 1,
1998
2003
  .credit_account_id = 3,
2004
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
1999
2005
  .ledger = 1,
2000
2006
  .code = 1,
2007
+ .flags = .{ .pending = true },
2001
2008
  .amount = math.maxInt(u64) -
2002
2009
  accounts[1 - 1].debits_pending -
2003
2010
  accounts[1 - 1].debits_posted + 1,
@@ -2010,14 +2017,32 @@ test "create/lookup/rollback transfers" {
2010
2017
  .id = 1,
2011
2018
  .debit_account_id = 1,
2012
2019
  .credit_account_id = 3,
2020
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
2013
2021
  .ledger = 1,
2014
2022
  .code = 1,
2023
+ .flags = .{ .pending = true },
2015
2024
  .amount = math.maxInt(u64) -
2016
2025
  accounts[3 - 1].credits_pending -
2017
2026
  accounts[3 - 1].credits_posted + 1,
2018
2027
  .timestamp = timestamp,
2019
2028
  }),
2020
2029
  },
2030
+ .{
2031
+ .result = .overflows_timeout,
2032
+ .object = mem.zeroInit(Transfer, .{
2033
+ .id = 1,
2034
+ .debit_account_id = 4,
2035
+ .credit_account_id = 5,
2036
+ .timeout = (std.math.maxInt(u64) - timestamp) + 1,
2037
+ .ledger = 1,
2038
+ .code = 1,
2039
+ .flags = .{ .pending = true },
2040
+ .amount = accounts[4 - 1].credits_posted -
2041
+ accounts[4 - 1].debits_pending -
2042
+ accounts[4 - 1].debits_posted + 1,
2043
+ .timestamp = timestamp,
2044
+ }),
2045
+ },
2021
2046
  .{
2022
2047
  .result = .exceeds_credits,
2023
2048
  .object = mem.zeroInit(Transfer, .{
@@ -589,11 +589,15 @@ pub fn WorkloadType(comptime AccountingStateMachine: type) type {
589
589
  .single_phase => {},
590
590
  .pending => {
591
591
  transfer.flags = .{ .pending = true };
592
- transfer.timeout = std.math.max(1, fuzz.random_int_exponential(
593
- self.random,
594
- u64,
595
- self.options.pending_timeout_mean,
596
- ));
592
+ // Bound the timeout to ensure we never hit `overflows_timeout`.
593
+ transfer.timeout = 1 + std.math.min(
594
+ std.math.maxInt(u64) / 2,
595
+ fuzz.random_int_exponential(
596
+ self.random,
597
+ u64,
598
+ self.options.pending_timeout_mean,
599
+ ),
600
+ );
597
601
  },
598
602
  .post_pending, .void_pending => {
599
603
  // Don't depend on `HashMap.keyIterator()` being deterministic.
@@ -32,7 +32,9 @@ pub const ClusterOptions = struct {
32
32
  grid_size_max: usize,
33
33
 
34
34
  seed: u64,
35
- on_change_state: fn (replica: *Replica) void,
35
+ on_change_state: fn (replica: *const Replica) void,
36
+ on_compact: fn (replica: *const Replica) void,
37
+ on_checkpoint: fn (replica: *const Replica) void,
36
38
 
37
39
  network_options: NetworkOptions,
38
40
  storage_options: Storage.Options,
@@ -122,21 +124,22 @@ pub const Cluster = struct {
122
124
  };
123
125
 
124
126
  var buffer: [config.replicas_max]Storage.FaultyAreas = undefined;
125
- const faulty_areas = Storage.generate_faulty_areas(
127
+ const faulty_wal_areas = Storage.generate_faulty_wal_areas(
126
128
  prng,
127
129
  config.journal_size_max,
128
130
  options.replica_count,
129
131
  &buffer,
130
132
  );
131
- assert(faulty_areas.len == options.replica_count);
133
+ assert(faulty_wal_areas.len == options.replica_count);
132
134
 
133
135
  for (cluster.storages) |*storage, replica_index| {
136
+ var storage_options = options.storage_options;
137
+ storage_options.replica_index = @intCast(u8, replica_index);
138
+ storage_options.faulty_wal_areas = faulty_wal_areas[replica_index];
134
139
  storage.* = try Storage.init(
135
140
  allocator,
136
141
  superblock_zone_size + config.journal_size_max + options.grid_size_max,
137
- options.storage_options,
138
- @intCast(u8, replica_index),
139
- faulty_areas[replica_index],
142
+ storage_options,
140
143
  );
141
144
  }
142
145
  errdefer for (cluster.storages) |*storage| storage.deinit(allocator);
@@ -280,41 +283,18 @@ pub const Cluster = struct {
280
283
  const replica_time = replica.time;
281
284
  replica.deinit(cluster.allocator);
282
285
 
283
- // The message bus and network should be left alone, as messages
284
- // may still be inflight to/from this replica. However, we should
285
- // do a check to ensure that we aren't leaking any messages when
286
- // deinitializing the replica above.
287
- const packet_simulator = &cluster.network.packet_simulator;
288
- // The same message may be used for multiple network packets, so simply counting how
289
- // many packets are inflight from the replica is insufficient, we need to dedup them.
290
- var messages_in_network_set = std.AutoHashMap(*Message, void).init(cluster.allocator);
291
- defer messages_in_network_set.deinit();
292
-
293
- var target: u8 = 0;
294
- while (target < packet_simulator.options.node_count) : (target += 1) {
295
- const path = .{ .source = replica_index, .target = target };
296
- const queue = packet_simulator.path_queue(path);
297
- var it = queue.iterator();
298
- while (it.next()) |data| {
299
- try messages_in_network_set.put(data.packet.message, {});
300
- }
301
- }
302
-
303
- const messages_in_network = messages_in_network_set.count();
304
-
286
+ // Ensure that none of the replica's messages leaked when it was deinitialized.
305
287
  var messages_in_pool: usize = 0;
306
288
  const message_bus = cluster.network.get_message_bus(.{ .replica = replica_index });
307
289
  {
308
290
  var it = message_bus.pool.free_list;
309
291
  while (it) |message| : (it = message.next) messages_in_pool += 1;
310
292
  }
311
-
312
- const total_messages = message_pool.messages_max_replica;
313
- assert(messages_in_network + messages_in_pool == total_messages);
293
+ assert(messages_in_pool == message_pool.messages_max_replica);
314
294
 
315
295
  // Logically it would make more sense to run this during restart, not immediately following
316
- // the crash. But having it here allows the replica's MessageBus to initialized and start
317
- // queueing packets, or collecting packets that are dropped by the network.
296
+ // the crash. But having it here allows the replica's MessageBus to initialize and begin
297
+ // queueing packets.
318
298
  //
319
299
  // Pass the old replica's Time through to the new replica. It will continue to be tick
320
300
  // while the replica is crashed, to ensure the clocks don't desyncronize too far to recover.
@@ -362,6 +342,8 @@ pub const Cluster = struct {
362
342
  assert(replica.status == .recovering);
363
343
 
364
344
  replica.on_change_state = cluster.options.on_change_state;
345
+ replica.on_compact = cluster.options.on_compact;
346
+ replica.on_checkpoint = cluster.options.on_checkpoint;
365
347
  cluster.network.link(replica.message_bus.process, &replica.message_bus);
366
348
  }
367
349
  };
@@ -8,6 +8,7 @@ const assert = std.debug.assert;
8
8
  const log = std.log.scoped(.test_conductor);
9
9
 
10
10
  const vsr = @import("../vsr.zig");
11
+ const util = @import("../util.zig");
11
12
  const config = @import("../config.zig");
12
13
  const IdPermutation = @import("id.zig").IdPermutation;
13
14
  const MessagePool = @import("../message_pool.zig").MessagePool;
@@ -310,7 +311,7 @@ pub fn ConductorType(
310
311
  /// Returns the Conductor's message.
311
312
  fn clone_message(self: *Self, message_client: *const Message) *Message {
312
313
  const message_conductor = self.message_pool.get_message();
313
- std.mem.copy(u8, message_conductor.buffer, message_client.buffer);
314
+ util.copy_disjoint(.exact, u8, message_conductor.buffer, message_client.buffer);
314
315
  return message_conductor;
315
316
  }
316
317