tigerbeetle-node 0.11.8 → 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 +5 -2
  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
@@ -4,7 +4,7 @@ const allocator = testing.allocator;
4
4
  const assert = std.debug.assert;
5
5
 
6
6
  const constants = @import("../constants.zig");
7
- const fuzz = @import("../test/fuzz.zig");
7
+ const fuzz = @import("../testing/fuzz.zig");
8
8
  const vsr = @import("../vsr.zig");
9
9
 
10
10
  const log = std.log.scoped(.lsm_tree_fuzz);
@@ -13,7 +13,7 @@ const tracer = @import("../tracer.zig");
13
13
  const MessagePool = @import("../message_pool.zig").MessagePool;
14
14
  const Transfer = @import("../tigerbeetle.zig").Transfer;
15
15
  const Account = @import("../tigerbeetle.zig").Account;
16
- const Storage = @import("../test/storage.zig").Storage;
16
+ const Storage = @import("../testing/storage.zig").Storage;
17
17
  const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
18
18
  .message_body_size_max = constants.message_body_size_max,
19
19
  });
@@ -62,6 +62,7 @@ const Key = packed struct {
62
62
  }
63
63
  };
64
64
 
65
+ const FuzzOpTag = std.meta.Tag(FuzzOp);
65
66
  const FuzzOp = union(enum) {
66
67
  // TODO Test range queries.
67
68
  compact: struct {
@@ -72,14 +73,13 @@ const FuzzOp = union(enum) {
72
73
  remove: Key.Value,
73
74
  get: Key,
74
75
  };
75
- const FuzzOpTag = std.meta.Tag(FuzzOp);
76
76
 
77
- const cluster = 32;
78
- const replica = 4;
79
- const node_count = 1024;
80
77
  const batch_size_max = constants.message_size_max - @sizeOf(vsr.Header);
81
78
  const commit_entries_max = @divFloor(batch_size_max, @sizeOf(Key.Value));
82
79
 
80
+ const cluster = 32;
81
+ const replica = 4;
82
+ const node_count = 1024;
83
83
  const tree_options = .{
84
84
  .commit_entries_max = commit_entries_max,
85
85
  // This is the smallest size that set_associative_cache will allow us.
@@ -87,7 +87,6 @@ const tree_options = .{
87
87
  };
88
88
 
89
89
  const puts_since_compact_max = commit_entries_max;
90
-
91
90
  const compacts_per_checkpoint = std.math.divCeil(
92
91
  usize,
93
92
  constants.journal_slot_count,
@@ -98,6 +97,7 @@ fn EnvironmentType(comptime table_usage: TableUsage) type {
98
97
  return struct {
99
98
  const Environment = @This();
100
99
 
100
+ const Tree = @import("tree.zig").TreeType(Table, Storage, "Key.Value");
101
101
  const Table = TableType(
102
102
  Key,
103
103
  Key.Value,
@@ -108,17 +108,17 @@ fn EnvironmentType(comptime table_usage: TableUsage) type {
108
108
  Key.tombstone_from_key,
109
109
  table_usage,
110
110
  );
111
- const Tree = @import("tree.zig").TreeType(Table, Storage, "Key.Value");
112
111
 
113
112
  const State = enum {
114
- uninit,
115
113
  init,
116
- formatted,
114
+ superblock_format,
117
115
  superblock_open,
116
+ tree_init,
118
117
  tree_open,
119
- tree_compacting,
120
- tree_checkpointing,
121
- superblock_checkpointing,
118
+ fuzzing,
119
+ tree_compact,
120
+ tree_checkpoint,
121
+ superblock_checkpoint,
122
122
  tree_lookup,
123
123
  };
124
124
 
@@ -126,70 +126,40 @@ fn EnvironmentType(comptime table_usage: TableUsage) type {
126
126
  storage: *Storage,
127
127
  message_pool: MessagePool,
128
128
  superblock: SuperBlock,
129
- superblock_context: SuperBlock.Context = undefined,
129
+ superblock_context: SuperBlock.Context,
130
130
  grid: Grid,
131
131
  node_pool: NodePool,
132
132
  tree: Tree,
133
- // We need @fieldParentPtr() of tree, so we can't use an optional Tree.
134
- tree_exists: bool,
135
- lookup_context: Tree.LookupContext = undefined,
136
- lookup_value: ?*const Key.Value = null,
137
- checkpoint_op: ?u64 = null,
138
-
139
- fn init(env: *Environment, storage: *Storage) !void {
140
- env.state = .uninit;
133
+ lookup_context: Tree.LookupContext,
134
+ lookup_value: ?*const Key.Value,
135
+ checkpoint_op: ?u64,
141
136
 
137
+ pub fn run(storage: *Storage, fuzz_ops: []const FuzzOp) !void {
138
+ var env: Environment = undefined;
139
+ env.state = .init;
142
140
  env.storage = storage;
143
- errdefer env.storage.deinit(allocator);
144
141
 
145
142
  env.message_pool = try MessagePool.init(allocator, .replica);
146
- errdefer env.message_pool.deinit(allocator);
143
+ defer env.message_pool.deinit(allocator);
147
144
 
148
145
  env.superblock = try SuperBlock.init(allocator, .{
149
146
  .storage = env.storage,
150
147
  .storage_size_limit = constants.storage_size_max,
151
148
  .message_pool = &env.message_pool,
152
149
  });
153
- errdefer env.superblock.deinit(allocator);
150
+ defer env.superblock.deinit(allocator);
154
151
 
155
152
  env.grid = try Grid.init(allocator, &env.superblock);
156
- errdefer env.grid.deinit(allocator);
153
+ defer env.grid.deinit(allocator);
157
154
 
158
155
  env.node_pool = try NodePool.init(allocator, node_count);
159
- errdefer env.node_pool.deinit(allocator);
156
+ defer env.node_pool.deinit(allocator);
160
157
 
161
- // Tree must be initialized with an open superblock.
162
158
  env.tree = undefined;
163
- env.tree_exists = false;
164
-
165
- env.state = .init;
166
- }
167
-
168
- fn deinit(env: *Environment) void {
169
- assert(env.state != .uninit);
170
-
171
- if (env.tree_exists) {
172
- env.tree.deinit(allocator);
173
- env.tree_exists = false;
174
- }
175
- env.node_pool.deinit(allocator);
176
- env.grid.deinit(allocator);
177
- env.superblock.deinit(allocator);
178
- env.message_pool.deinit(allocator);
179
-
180
- env.state = .uninit;
181
- }
159
+ env.lookup_value = null;
160
+ env.checkpoint_op = null;
182
161
 
183
- fn tick(env: *Environment) void {
184
- // env.grid.tick();
185
- env.storage.tick();
186
- }
187
-
188
- fn tick_until_state_change(env: *Environment, current_state: State, next_state: State) void {
189
- // Sometimes IO completes synchronously (eg if cached), so we might already be in next_state before ticking.
190
- assert(env.state == current_state or env.state == next_state);
191
- while (env.state == current_state) env.tick();
192
- assert(env.state == next_state);
162
+ try env.open_then_apply(fuzz_ops);
193
163
  }
194
164
 
195
165
  fn change_state(env: *Environment, current_state: State, next_state: State) void {
@@ -197,123 +167,129 @@ fn EnvironmentType(comptime table_usage: TableUsage) type {
197
167
  env.state = next_state;
198
168
  }
199
169
 
200
- pub fn format(storage: *Storage) !void {
201
- var env: Environment = undefined;
202
-
203
- try env.init(storage);
204
- defer env.deinit();
170
+ fn tick_until_state_change(env: *Environment, current_state: State, next_state: State) void {
171
+ // Sometimes operations complete synchronously so we might already be in next_state before ticking.
172
+ //assert(env.state == current_state or env.state == next_state);
173
+ while (env.state == current_state) env.storage.tick();
174
+ assert(env.state == next_state);
175
+ }
205
176
 
206
- assert(env.state == .init);
177
+ pub fn open_then_apply(env: *Environment, fuzz_ops: []const FuzzOp) !void {
178
+ env.change_state(.init, .superblock_format);
207
179
  env.superblock.format(superblock_format_callback, &env.superblock_context, .{
208
180
  .cluster = cluster,
209
181
  .replica = replica,
210
182
  });
211
- env.tick_until_state_change(.init, .formatted);
183
+
184
+ env.tick_until_state_change(.superblock_format, .superblock_open);
185
+ env.superblock.open(superblock_open_callback, &env.superblock_context);
186
+
187
+ env.tick_until_state_change(.superblock_open, .tree_init);
188
+ env.tree = try Tree.init(allocator, &env.node_pool, &env.grid, tree_options);
189
+ defer env.tree.deinit(allocator);
190
+
191
+ env.change_state(.tree_init, .tree_open);
192
+ env.tree.open(tree_open_callback);
193
+
194
+ env.tick_until_state_change(.tree_open, .fuzzing);
195
+ try env.apply(fuzz_ops);
212
196
  }
213
197
 
214
198
  fn superblock_format_callback(superblock_context: *SuperBlock.Context) void {
215
199
  const env = @fieldParentPtr(@This(), "superblock_context", superblock_context);
216
- env.change_state(.init, .formatted);
217
- }
218
-
219
- pub fn open(env: *Environment) void {
220
- assert(env.state == .init);
221
- env.superblock.open(superblock_open_callback, &env.superblock_context);
222
- env.tick_until_state_change(.init, .tree_open);
200
+ env.change_state(.superblock_format, .superblock_open);
223
201
  }
224
202
 
225
203
  fn superblock_open_callback(superblock_context: *SuperBlock.Context) void {
226
204
  const env = @fieldParentPtr(@This(), "superblock_context", superblock_context);
227
- env.change_state(.init, .superblock_open);
228
- env.tree = Tree.init(allocator, &env.node_pool, &env.grid, tree_options) catch unreachable;
229
- env.tree_exists = true;
230
- env.tree.open(tree_open_callback);
205
+ env.change_state(.superblock_open, .tree_init);
231
206
  }
232
207
 
233
208
  fn tree_open_callback(tree: *Tree) void {
234
209
  const env = @fieldParentPtr(@This(), "tree", tree);
235
- env.change_state(.superblock_open, .tree_open);
210
+ env.change_state(.tree_open, .fuzzing);
236
211
  }
237
212
 
238
213
  pub fn compact(env: *Environment, op: u64) void {
239
- env.change_state(.tree_open, .tree_compacting);
214
+ env.change_state(.fuzzing, .tree_compact);
240
215
  env.tree.compact(tree_compact_callback, op);
241
- env.tick_until_state_change(.tree_compacting, .tree_open);
216
+ env.tick_until_state_change(.tree_compact, .fuzzing);
242
217
  }
243
218
 
244
219
  fn tree_compact_callback(tree: *Tree) void {
245
220
  const env = @fieldParentPtr(@This(), "tree", tree);
246
- env.change_state(.tree_compacting, .tree_open);
221
+ env.change_state(.tree_compact, .fuzzing);
247
222
  }
248
223
 
249
224
  pub fn checkpoint(env: *Environment, op: u64) void {
225
+ assert(env.checkpoint_op == null);
250
226
  env.checkpoint_op = op - constants.lsm_batch_multiple;
251
- env.change_state(.tree_open, .tree_checkpointing);
227
+
228
+ env.change_state(.fuzzing, .tree_checkpoint);
252
229
  env.tree.checkpoint(tree_checkpoint_callback);
253
- env.tick_until_state_change(.tree_checkpointing, .superblock_checkpointing);
254
- env.tick_until_state_change(.superblock_checkpointing, .tree_open);
230
+ env.tick_until_state_change(.tree_checkpoint, .superblock_checkpoint);
231
+ env.tick_until_state_change(.superblock_checkpoint, .fuzzing);
255
232
  }
256
233
 
257
234
  fn tree_checkpoint_callback(tree: *Tree) void {
258
235
  const env = @fieldParentPtr(@This(), "tree", tree);
259
- env.change_state(.tree_checkpointing, .superblock_checkpointing);
236
+ const op = env.checkpoint_op.?;
237
+ env.checkpoint_op = null;
238
+
239
+ env.change_state(.tree_checkpoint, .superblock_checkpoint);
260
240
  env.superblock.checkpoint(superblock_checkpoint_callback, &env.superblock_context, .{
261
241
  .commit_min_checksum = env.superblock.working.vsr_state.commit_min_checksum + 1,
262
- .commit_min = env.checkpoint_op.?,
263
- .commit_max = env.checkpoint_op.? + 1,
264
- .view_normal = 0,
242
+ .commit_min = op,
243
+ .commit_max = op + 1,
244
+ .log_view = 0,
265
245
  .view = 0,
266
246
  });
267
- env.checkpoint_op = null;
268
247
  }
269
248
 
270
249
  fn superblock_checkpoint_callback(superblock_context: *SuperBlock.Context) void {
271
250
  const env = @fieldParentPtr(@This(), "superblock_context", superblock_context);
272
- env.change_state(.superblock_checkpointing, .tree_open);
251
+ env.change_state(.superblock_checkpoint, .fuzzing);
273
252
  }
274
253
 
275
- fn get(env: *Environment, key: Key) ?*const Key.Value {
254
+ pub fn get(env: *Environment, key: Key) ?*const Key.Value {
255
+ env.change_state(.fuzzing, .tree_lookup);
256
+
276
257
  if (env.tree.lookup_from_memory(env.tree.lookup_snapshot_max, key)) |value| {
258
+ env.change_state(.tree_lookup, .fuzzing);
277
259
  return Tree.unwrap_tombstone(value);
278
- } else {
279
- env.change_state(.tree_open, .tree_lookup);
280
- env.lookup_context = undefined;
281
- env.lookup_value = null;
282
- env.tree.lookup_from_levels(get_callback, &env.lookup_context, env.tree.lookup_snapshot_max, key);
283
- env.tick_until_state_change(.tree_lookup, .tree_open);
284
- return env.lookup_value;
285
260
  }
261
+
262
+ env.lookup_value = null;
263
+ env.tree.lookup_from_levels(get_callback, &env.lookup_context, env.tree.lookup_snapshot_max, key);
264
+ env.tick_until_state_change(.tree_lookup, .fuzzing);
265
+ return env.lookup_value;
286
266
  }
287
267
 
288
268
  fn get_callback(lookup_context: *Tree.LookupContext, value: ?*const Key.Value) void {
289
269
  const env = @fieldParentPtr(Environment, "lookup_context", lookup_context);
290
270
  assert(env.lookup_value == null);
291
271
  env.lookup_value = value;
292
- env.change_state(.tree_lookup, .tree_open);
272
+ env.change_state(.tree_lookup, .fuzzing);
293
273
  }
294
274
 
295
- fn run(storage: *Storage, fuzz_ops: []const FuzzOp) !void {
296
- var env: Environment = undefined;
297
-
298
- try env.init(storage);
299
- defer env.deinit();
300
-
301
- // Open the superblock then tree.
302
- env.open();
303
-
275
+ pub fn apply(env: *Environment, fuzz_ops: []const FuzzOp) !void {
304
276
  // The tree should behave like a simple key-value data-structure.
305
277
  // We'll compare it to a hash map.
306
278
  var model = std.hash_map.AutoHashMap(Key, Key.Value).init(allocator);
307
279
  defer model.deinit();
308
280
 
309
281
  for (fuzz_ops) |fuzz_op, fuzz_op_index| {
282
+ assert(env.state == .fuzzing);
310
283
  log.debug("Running fuzz_ops[{}/{}] == {}", .{ fuzz_op_index, fuzz_ops.len, fuzz_op });
311
- const storage_size_used = storage.size_used();
312
- log.debug("storage.size_used = {}/{}", .{ storage_size_used, storage.size });
284
+
285
+ const storage_size_used = env.storage.size_used();
286
+ log.debug("storage.size_used = {}/{}", .{ storage_size_used, env.storage.size });
287
+
313
288
  const model_size = model.count() * @sizeOf(Key.Value);
314
289
  log.debug("space_amplification = {d:.2}", .{
315
290
  @intToFloat(f64, storage_size_used) / @intToFloat(f64, model_size),
316
291
  });
292
+
317
293
  // Apply fuzz_op to the tree and the model.
318
294
  switch (fuzz_op) {
319
295
  .compact => |compact| {
@@ -489,14 +465,10 @@ pub fn main() !void {
489
465
  // TODO Use inline switch after upgrading to zig 0.10
490
466
  switch (table_usage) {
491
467
  .general => {
492
- const Environment = EnvironmentType(.general);
493
- try Environment.format(&storage);
494
- try Environment.run(&storage, fuzz_ops);
468
+ try EnvironmentType(.general).run(&storage, fuzz_ops);
495
469
  },
496
470
  .secondary_index => {
497
- const Environment = EnvironmentType(.secondary_index);
498
- try Environment.format(&storage);
499
- try Environment.run(&storage, fuzz_ops);
471
+ try EnvironmentType(.secondary_index).run(&storage, fuzz_ops);
500
472
  },
501
473
  }
502
474
 
@@ -12,7 +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
+ const stdx = @import("stdx.zig");
16
16
  const RingBuffer = @import("ring_buffer.zig").RingBuffer;
17
17
  const IO = @import("io.zig").IO;
18
18
  const MessagePool = @import("message_pool.zig").MessagePool;
@@ -750,7 +750,7 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
750
750
  if (connection.recv_progress == header.size) return connection.recv_message.?.ref();
751
751
 
752
752
  const message = bus.get_message();
753
- util.copy_disjoint(.inexact, u8, message.buffer, data[0..header.size]);
753
+ stdx.copy_disjoint(.inexact, u8, message.buffer, data[0..header.size]);
754
754
  return message;
755
755
  }
756
756
 
@@ -771,7 +771,7 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
771
771
  const sector_ceil = vsr.sector_ceil(message.header.size);
772
772
  if (message.header.size != sector_ceil) {
773
773
  assert(message.header.size < sector_ceil);
774
- assert(message.buffer.len == constants.message_size_max + constants.sector_size);
774
+ assert(message.buffer.len == constants.message_size_max);
775
775
  mem.set(u8, message.buffer[message.header.size..sector_ceil], 0);
776
776
  }
777
777
  }
@@ -842,7 +842,7 @@ fn MessageBusType(comptime process_type: vsr.ProcessType) type {
842
842
  assert(connection.recv_progress > 0);
843
843
  assert(connection.recv_parsed > 0);
844
844
  const data = recv_message.buffer[connection.recv_parsed..connection.recv_progress];
845
- util.copy_disjoint(.inexact, u8, new_message.buffer, data);
845
+ stdx.copy_disjoint(.inexact, u8, new_message.buffer, data);
846
846
  connection.recv_progress = data.len;
847
847
  connection.recv_parsed = 0;
848
848
  } else {
@@ -13,10 +13,6 @@ comptime {
13
13
  assert(constants.message_size_max % constants.sector_size == 0);
14
14
  }
15
15
 
16
- /// Add an extra sector_size bytes to allow a partially received subsequent
17
- /// message to be shifted to make space for 0 padding to vsr.sector_ceil.
18
- const message_size_max_padded = constants.message_size_max + constants.sector_size;
19
-
20
16
  /// The number of full-sized messages allocated at initialization by the replica message pool.
21
17
  /// There must be enough messages to ensure that the replica can always progress, to avoid deadlock.
22
18
  pub const messages_max_replica = messages_max: {
@@ -26,7 +22,8 @@ pub const messages_max_replica = messages_max: {
26
22
  sum += constants.journal_iops_write_max; // Journal writes
27
23
  sum += constants.clients_max; // SuperBlock.client_table
28
24
  sum += 1; // Replica.loopback_queue
29
- sum += constants.pipeline_max; // Replica.pipeline
25
+ sum += constants.pipeline_prepare_queue_max; // Replica.Pipeline{Queue|Cache}
26
+ sum += constants.pipeline_request_queue_max; // Replica.Pipeline{Queue|Cache}
30
27
  sum += 1; // Replica.commit_prepare
31
28
  // Replica.do_view_change_from_all_replicas quorum:
32
29
  // Replica.recovery_response_quorum is only used for recovery and does not increase the limit.
@@ -67,7 +64,7 @@ pub const MessagePool = struct {
67
64
  pub const Message = struct {
68
65
  // TODO: replace this with a header() function to save memory
69
66
  header: *Header,
70
- buffer: []align(constants.sector_size) u8,
67
+ buffer: *align(constants.sector_size) [constants.message_size_max]u8,
71
68
  references: u32 = 0,
72
69
  next: ?*Message,
73
70
 
@@ -105,13 +102,13 @@ pub const MessagePool = struct {
105
102
  const buffer = try allocator.allocAdvanced(
106
103
  u8,
107
104
  constants.sector_size,
108
- message_size_max_padded,
105
+ constants.message_size_max,
109
106
  .exact,
110
107
  );
111
108
  const message = try allocator.create(Message);
112
109
  message.* = .{
113
110
  .header = mem.bytesAsValue(Header, buffer[0..@sizeOf(Header)]),
114
- .buffer = buffer,
111
+ .buffer = buffer[0..constants.message_size_max],
115
112
  .next = pool.free_list,
116
113
  };
117
114
  pool.free_list = message;
@@ -126,7 +123,7 @@ pub const MessagePool = struct {
126
123
  var free_count: usize = 0;
127
124
  while (pool.free_list) |message| {
128
125
  pool.free_list = message.next;
129
- allocator.free(message.buffer);
126
+ allocator.free(@as([]const u8, message.buffer));
130
127
  allocator.destroy(message);
131
128
  free_count += 1;
132
129
  }
@@ -151,7 +148,7 @@ pub const MessagePool = struct {
151
148
  pub fn unref(pool: *MessagePool, message: *Message) void {
152
149
  message.references -= 1;
153
150
  if (message.references == 0) {
154
- if (builtin.mode == .Debug) mem.set(u8, message.buffer, undefined);
151
+ mem.set(u8, message.buffer, 0);
155
152
  message.next = pool.free_list;
156
153
  pool.free_list = message;
157
154
  }
@@ -3,7 +3,7 @@ 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");
6
+ const stdx = @import("stdx.zig");
7
7
 
8
8
  /// A First In, First Out ring buffer holding at most `count_max` elements.
9
9
  pub fn RingBuffer(
@@ -47,36 +47,38 @@ pub fn RingBuffer(
47
47
 
48
48
  // TODO Add doc comments to these functions:
49
49
  pub inline fn head(self: Self) ?T {
50
- if (self.empty()) return null;
50
+ if (count_max == 0 or self.empty()) return null;
51
51
  return self.buffer[self.index];
52
52
  }
53
53
 
54
54
  pub inline fn head_ptr(self: *Self) ?*T {
55
- if (self.empty()) return null;
55
+ if (count_max == 0 or self.empty()) return null;
56
56
  return &self.buffer[self.index];
57
57
  }
58
58
 
59
59
  pub inline fn head_ptr_const(self: *const Self) ?*const T {
60
- if (self.empty()) return null;
60
+ if (count_max == 0 or self.empty()) return null;
61
61
  return &self.buffer[self.index];
62
62
  }
63
63
 
64
64
  pub inline fn tail(self: Self) ?T {
65
- if (self.empty()) return null;
65
+ if (count_max == 0 or self.empty()) return null;
66
66
  return self.buffer[(self.index + self.count - 1) % self.buffer.len];
67
67
  }
68
68
 
69
69
  pub inline fn tail_ptr(self: *Self) ?*T {
70
- if (self.empty()) return null;
70
+ if (count_max == 0 or self.empty()) return null;
71
71
  return &self.buffer[(self.index + self.count - 1) % self.buffer.len];
72
72
  }
73
73
 
74
74
  pub inline fn tail_ptr_const(self: *const Self) ?*const T {
75
- if (self.empty()) return null;
75
+ if (count_max == 0 or self.empty()) return null;
76
76
  return &self.buffer[(self.index + self.count - 1) % self.buffer.len];
77
77
  }
78
78
 
79
79
  pub inline fn get_ptr(self: *Self, index: usize) ?*T {
80
+ if (count_max == 0) unreachable;
81
+
80
82
  if (index < self.count) {
81
83
  return &self.buffer[(self.index + index) % self.buffer.len];
82
84
  } else {
@@ -86,17 +88,17 @@ pub fn RingBuffer(
86
88
  }
87
89
 
88
90
  pub inline fn next_tail(self: Self) ?T {
89
- if (self.full()) return null;
91
+ if (count_max == 0 or self.full()) return null;
90
92
  return self.buffer[(self.index + self.count) % self.buffer.len];
91
93
  }
92
94
 
93
95
  pub inline fn next_tail_ptr(self: *Self) ?*T {
94
- if (self.full()) return null;
96
+ if (count_max == 0 or self.full()) return null;
95
97
  return &self.buffer[(self.index + self.count) % self.buffer.len];
96
98
  }
97
99
 
98
100
  pub inline fn next_tail_ptr_const(self: *const Self) ?*const T {
99
- if (self.full()) return null;
101
+ if (count_max == 0 or self.full()) return null;
100
102
  return &self.buffer[(self.index + self.count) % self.buffer.len];
101
103
  }
102
104
 
@@ -143,14 +145,15 @@ pub fn RingBuffer(
143
145
  }
144
146
 
145
147
  pub fn push_slice(self: *Self, items: []const T) error{NoSpaceLeft}!void {
148
+ if (count_max == 0) return error.NoSpaceLeft;
146
149
  if (self.count + items.len > self.buffer.len) return error.NoSpaceLeft;
147
150
 
148
151
  const pre_wrap_start = (self.index + self.count) % self.buffer.len;
149
152
  const pre_wrap_count = math.min(items.len, self.buffer.len - pre_wrap_start);
150
153
  const post_wrap_count = items.len - pre_wrap_count;
151
154
 
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..]);
155
+ stdx.copy_disjoint(.inexact, T, self.buffer[pre_wrap_start..], items[0..pre_wrap_count]);
156
+ stdx.copy_disjoint(.exact, T, self.buffer[0..post_wrap_count], items[pre_wrap_count..]);
154
157
 
155
158
  self.count += items.len;
156
159
  }
@@ -174,6 +177,7 @@ pub fn RingBuffer(
174
177
  count: usize = 0,
175
178
 
176
179
  pub fn next(it: *Iterator) ?T {
180
+ if (count_max == 0) return null;
177
181
  // TODO Use next_ptr() internally to avoid duplicating this code.
178
182
  assert(it.count <= it.ring.count);
179
183
  if (it.count == it.ring.count) return null;
@@ -183,6 +187,7 @@ pub fn RingBuffer(
183
187
 
184
188
  pub fn next_ptr(it: *Iterator) ?*const T {
185
189
  assert(it.count <= it.ring.count);
190
+ if (count_max == 0) return null;
186
191
  if (it.count == it.ring.count) return null;
187
192
  defer it.count += 1;
188
193
  return &it.ring.buffer[(it.ring.index + it.count) % it.ring.buffer.len];
@@ -201,6 +206,7 @@ pub fn RingBuffer(
201
206
 
202
207
  pub fn next_ptr(it: *IteratorMutable) ?*T {
203
208
  assert(it.count <= it.ring.count);
209
+ if (count_max == 0) return null;
204
210
  if (it.count == it.ring.count) return null;
205
211
  defer it.count += 1;
206
212
  return &it.ring.buffer[(it.ring.index + it.count) % it.ring.buffer.len];
@@ -387,3 +393,7 @@ test "RingBuffer: pop_tail" {
387
393
  try testing.expectEqual(@as(?u32, null), lifo.pop_tail());
388
394
  try testing.expect(lifo.empty());
389
395
  }
396
+
397
+ test "RingBuffer: count_max=0" {
398
+ std.testing.refAllDecls(RingBuffer(u32, 0, .array));
399
+ }