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
@@ -1 +1,371 @@
1
- // TODO
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+
4
+ const testing = std.testing;
5
+
6
+ const c = @cImport(@cInclude("tb_client.h"));
7
+
8
+ const util = @import("../util.zig");
9
+ const config = @import("../config.zig");
10
+ const Packet = @import("tb_client/packet.zig").Packet;
11
+
12
+ const Mutex = std.Thread.Mutex;
13
+ const Condition = std.Thread.Condition;
14
+
15
+ fn RequestContextType(comptime request_size_max: comptime_int) type {
16
+ return struct {
17
+ const Self = @This();
18
+
19
+ completion: *Completion,
20
+ sent_data: [request_size_max]u8 = undefined,
21
+ sent_data_size: u32,
22
+ packet: *Packet = undefined,
23
+ reply: ?struct {
24
+ tb_context: usize,
25
+ tb_client: c.tb_client_t,
26
+ tb_packet: *c.tb_packet_t,
27
+ result: ?[request_size_max]u8,
28
+ result_len: u32,
29
+ } = null,
30
+
31
+ pub fn on_complete(
32
+ tb_context: usize,
33
+ tb_client: c.tb_client_t,
34
+ tb_packet: [*c]c.tb_packet_t,
35
+ result_ptr: [*c]const u8,
36
+ result_len: u32,
37
+ ) callconv(.C) void {
38
+ var self = @ptrCast(*Self, @alignCast(@alignOf(*Self), tb_packet.*.user_data.?));
39
+ defer self.completion.complete();
40
+
41
+ self.reply = .{
42
+ .tb_context = tb_context,
43
+ .tb_client = tb_client,
44
+ .tb_packet = tb_packet,
45
+ .result = if (result_ptr != null and result_len > 0) blk: {
46
+ // Copy the message's body to the context buffer:
47
+ assert(result_len <= request_size_max);
48
+ var writable: [request_size_max]u8 = undefined;
49
+ const readable = @ptrCast([*]const u8, result_ptr.?);
50
+ util.copy_disjoint(.inexact, u8, &writable, readable[0..result_len]);
51
+ break :blk writable;
52
+ } else null,
53
+ .result_len = result_len,
54
+ };
55
+ }
56
+ };
57
+ }
58
+
59
+ // Notifies the main thread when all pending requests are completed.
60
+ const Completion = struct {
61
+ const Self = @This();
62
+
63
+ pending: usize,
64
+ mutex: Mutex = .{},
65
+ cond: Condition = .{},
66
+
67
+ pub fn complete(self: *Self) void {
68
+ self.mutex.lock();
69
+ defer self.mutex.unlock();
70
+
71
+ assert(self.pending > 0);
72
+ self.pending -= 1;
73
+ self.cond.signal();
74
+ }
75
+
76
+ pub fn wait_pending(self: *Self) void {
77
+ self.mutex.lock();
78
+ defer self.mutex.unlock();
79
+
80
+ while (self.pending > 0)
81
+ self.cond.wait(&self.mutex);
82
+ }
83
+ };
84
+
85
+ // When initialized with tb_client_init_echo, the c_client uses a test context that echoes
86
+ // the data back without creating an actual client or connecting to a cluster.
87
+ //
88
+ // This same test should be implemented by all the target programming languages, asserting that:
89
+ // 1. the c_client api was initialized correctly.
90
+ // 2. the application can submit messages and receive replies through the completion callback.
91
+ // 3. the data marshaling is correct, and exactly the same data sent was received back.
92
+ test "c_client echo" {
93
+ // Using the create_accounts operation for this test.
94
+ const RequestContext = RequestContextType(config.message_body_size_max);
95
+ const create_accounts_operation: u8 = c.TB_OP_CREATE_ACCOUNTS;
96
+ const event_size = @sizeOf(c.tb_account_t);
97
+ const event_request_max = @divFloor(config.message_body_size_max, event_size);
98
+
99
+ // Initializing an echo client for testing purposes.
100
+ // We ensure that the retry mechanism is being tested
101
+ // by allowing more simultaneous packets than "client_request_queue_max".
102
+ var tb_client: c.tb_client_t = undefined;
103
+ var tb_packet_list: c.tb_packet_list_t = undefined;
104
+ const cluster_id = 0;
105
+ const address = "3000";
106
+ const packets_count: u32 = config.client_request_queue_max * 2;
107
+ const tb_context: usize = 42;
108
+ const result = c.tb_client_init_echo(
109
+ &tb_client,
110
+ &tb_packet_list,
111
+ cluster_id,
112
+ address,
113
+ @intCast(u32, address.len),
114
+ packets_count,
115
+ tb_context,
116
+ RequestContext.on_complete,
117
+ );
118
+
119
+ try testing.expectEqual(@as(c_uint, c.TB_STATUS_SUCCESS), result);
120
+ defer c.tb_client_deinit(tb_client);
121
+
122
+ var packet_list = @bitCast(Packet.List, tb_packet_list);
123
+ var prng = std.rand.DefaultPrng.init(tb_context);
124
+
125
+ var requests: []RequestContext = try testing.allocator.alloc(RequestContext, packets_count);
126
+ defer testing.allocator.free(requests);
127
+
128
+ // Repeating the same test multiple times to stress the
129
+ // cycle of message exhaustion followed by completions.
130
+ const repetitions_max = 100;
131
+ var repetition: u32 = 0;
132
+ while (repetition < repetitions_max) : (repetition += 1) {
133
+ var completion = Completion{ .pending = packets_count };
134
+
135
+ // Submitting some random data to be echoed back:
136
+ for (requests) |*request| {
137
+ request.* = .{
138
+ .completion = &completion,
139
+ .sent_data_size = prng.random().intRangeAtMost(u32, 1, event_request_max) * event_size,
140
+ };
141
+ prng.random().bytes(request.sent_data[0..request.sent_data_size]);
142
+
143
+ request.packet = blk: {
144
+ var packet = packet_list.pop().?;
145
+ packet.operation = create_accounts_operation;
146
+ packet.user_data = request;
147
+ packet.data = &request.sent_data;
148
+ packet.data_size = request.sent_data_size;
149
+ packet.next = null;
150
+ packet.status = .ok;
151
+ break :blk packet;
152
+ };
153
+
154
+ var list = @bitCast(c.tb_packet_list_t, Packet.List.from(request.packet));
155
+ c.tb_client_submit(tb_client, &list);
156
+ }
157
+
158
+ // Waiting until the c_client thread has processed all submitted requests:
159
+ completion.wait_pending();
160
+
161
+ // Checking if the received echo matches the data we sent:
162
+ for (requests) |*request| {
163
+ defer packet_list.push(Packet.List.from(request.packet));
164
+
165
+ try testing.expect(request.reply != null);
166
+ try testing.expectEqual(tb_context, request.reply.?.tb_context);
167
+ try testing.expectEqual(tb_client, request.reply.?.tb_client);
168
+ try testing.expectEqual(c.TB_PACKET_OK, @enumToInt(request.packet.status));
169
+ try testing.expectEqual(@ptrToInt(request.packet), @ptrToInt(request.reply.?.tb_packet));
170
+ try testing.expect(request.reply.?.result != null);
171
+ try testing.expectEqual(request.sent_data_size, request.reply.?.result_len);
172
+
173
+ const sent_data = request.sent_data[0..request.sent_data_size];
174
+ const reply = request.reply.?.result.?[0..request.reply.?.result_len];
175
+ try testing.expectEqualSlices(u8, sent_data, reply);
176
+ }
177
+ }
178
+ }
179
+
180
+ // Asserts the validation rules associated with the "TB_STATUS" enum.
181
+ test "c_client tb_status" {
182
+ const assert_status = struct {
183
+ pub fn action(
184
+ packets_count: u32,
185
+ addresses: []const u8,
186
+ expected_status: c_uint,
187
+ ) !void {
188
+ var tb_client: c.tb_client_t = undefined;
189
+ var tb_packet_list: c.tb_packet_list_t = undefined;
190
+ const cluster_id = 0;
191
+ const tb_context: usize = 0;
192
+ const result = c.tb_client_init_echo(
193
+ &tb_client,
194
+ &tb_packet_list,
195
+ cluster_id,
196
+ addresses.ptr,
197
+ @intCast(u32, addresses.len),
198
+ packets_count,
199
+ tb_context,
200
+ RequestContextType(0).on_complete,
201
+ );
202
+ defer if (result == c.TB_STATUS_SUCCESS) c.tb_client_deinit(tb_client);
203
+
204
+ try testing.expectEqual(expected_status, result);
205
+ }
206
+ }.action;
207
+
208
+ // Valid addresses and packets count should return TB_STATUS_SUCCESS:
209
+ try assert_status(0, "3000", c.TB_STATUS_SUCCESS);
210
+ try assert_status(1, "3000", c.TB_STATUS_SUCCESS);
211
+ try assert_status(32, "127.0.0.1", c.TB_STATUS_SUCCESS);
212
+ try assert_status(128, "127.0.0.1:3000", c.TB_STATUS_SUCCESS);
213
+ try assert_status(512, "3000,3001,3002", c.TB_STATUS_SUCCESS);
214
+ try assert_status(1024, "127.0.0.1,127.0.0.2,172.0.0.3", c.TB_STATUS_SUCCESS);
215
+ try assert_status(4096, "127.0.0.1:3000,127.0.0.1:3002,127.0.0.1:3003", c.TB_STATUS_SUCCESS);
216
+
217
+ // Invalid or empty address should return "TB_STATUS_ADDRESS_INVALID":
218
+ try assert_status(1, "invalid", c.TB_STATUS_ADDRESS_INVALID);
219
+ try assert_status(1, "", c.TB_STATUS_ADDRESS_INVALID);
220
+
221
+ // More addresses thant "replicas_max" should return "TB_STATUS_ADDRESS_LIMIT_EXCEEDED":
222
+ try assert_status(
223
+ 1,
224
+ ("3000," ** config.replicas_max) ++ "3001",
225
+ c.TB_STATUS_ADDRESS_LIMIT_EXCEEDED,
226
+ );
227
+
228
+ // Packets count greater than 4096 should return "TB_STATUS_INVALID_PACKETS_COUNT":
229
+ try assert_status(4097, "3000", c.TB_STATUS_PACKETS_COUNT_INVALID);
230
+ try assert_status(std.math.maxInt(u32), "3000", c.TB_STATUS_PACKETS_COUNT_INVALID);
231
+
232
+ // All other status are not testable.
233
+ }
234
+
235
+ // Asserts the validation rules associated with the "TB_PACKET_STATUS" enum.
236
+ test "c_client tb_packet_status" {
237
+ const RequestContext = RequestContextType(config.message_body_size_max);
238
+
239
+ var tb_client: c.tb_client_t = undefined;
240
+ var tb_packet_list: c.tb_packet_list_t = undefined;
241
+ const cluster_id = 0;
242
+ const address = "3000";
243
+ const packets_count = 1;
244
+ const tb_context: usize = 42;
245
+ const result = c.tb_client_init_echo(
246
+ &tb_client,
247
+ &tb_packet_list,
248
+ cluster_id,
249
+ address,
250
+ @intCast(u32, address.len),
251
+ packets_count,
252
+ tb_context,
253
+ RequestContext.on_complete,
254
+ );
255
+
256
+ try testing.expectEqual(@as(c_uint, c.TB_STATUS_SUCCESS), result);
257
+ defer c.tb_client_deinit(tb_client);
258
+
259
+ const assert_result = struct {
260
+ // Asserts if the packet's status matches the expected status
261
+ // for a given operation and request_size.
262
+ pub fn action(
263
+ client: c.tb_client_t,
264
+ packet_list: *Packet.List,
265
+ operation: u8,
266
+ request_size: u32,
267
+ tb_packet_status_expected: c_int,
268
+ ) !void {
269
+ var completion = Completion{ .pending = 1 };
270
+ var request = RequestContext{
271
+ .completion = &completion,
272
+ .sent_data_size = request_size,
273
+ };
274
+
275
+ request.packet = blk: {
276
+ var packet = packet_list.pop().?;
277
+ packet.operation = operation;
278
+ packet.user_data = &request;
279
+ packet.data = &request.sent_data;
280
+ packet.data_size = request_size;
281
+ packet.next = null;
282
+ packet.status = .ok;
283
+ break :blk packet;
284
+ };
285
+ defer packet_list.push(Packet.List.from(request.packet));
286
+
287
+ var list = @bitCast(c.tb_packet_list_t, Packet.List.from(request.packet));
288
+ c.tb_client_submit(client, &list);
289
+
290
+ completion.wait_pending();
291
+
292
+ try testing.expect(request.reply != null);
293
+ try testing.expectEqual(tb_context, request.reply.?.tb_context);
294
+ try testing.expectEqual(client, request.reply.?.tb_client);
295
+ try testing.expectEqual(@ptrToInt(request.packet), @ptrToInt(request.reply.?.tb_packet));
296
+ try testing.expectEqual(tb_packet_status_expected, @enumToInt(request.packet.status));
297
+ }
298
+ }.action;
299
+
300
+ var packet_list = @ptrCast(*Packet.List, &tb_packet_list);
301
+
302
+ // Messages larger than config.message_body_size_max should return "too_much_data":
303
+ try assert_result(
304
+ tb_client,
305
+ packet_list,
306
+ c.TB_OP_CREATE_TRANSFERS,
307
+ config.message_body_size_max + @sizeOf(c.tb_transfer_t),
308
+ c.TB_PACKET_TOO_MUCH_DATA,
309
+ );
310
+
311
+ // All reserved and unknown operations should return "invalid_operation":
312
+ try assert_result(
313
+ tb_client,
314
+ packet_list,
315
+ 0,
316
+ @sizeOf(u128),
317
+ c.TB_PACKET_INVALID_OPERATION,
318
+ );
319
+ try assert_result(
320
+ tb_client,
321
+ packet_list,
322
+ 1,
323
+ @sizeOf(u128),
324
+ c.TB_PACKET_INVALID_OPERATION,
325
+ );
326
+ try assert_result(
327
+ tb_client,
328
+ packet_list,
329
+ 2,
330
+ @sizeOf(u128),
331
+ c.TB_PACKET_INVALID_OPERATION,
332
+ );
333
+ try assert_result(
334
+ tb_client,
335
+ packet_list,
336
+ 99,
337
+ @sizeOf(u128),
338
+ c.TB_PACKET_INVALID_OPERATION,
339
+ );
340
+
341
+ // Messages length 0 or not a multiple of the event size
342
+ // should return "invalid_data_size":
343
+ try assert_result(
344
+ tb_client,
345
+ packet_list,
346
+ c.TB_OP_CREATE_ACCOUNTS,
347
+ 0,
348
+ c.TB_PACKET_INVALID_DATA_SIZE,
349
+ );
350
+ try assert_result(
351
+ tb_client,
352
+ packet_list,
353
+ c.TB_OP_CREATE_TRANSFERS,
354
+ @sizeOf(c.tb_transfer_t) - 1,
355
+ c.TB_PACKET_INVALID_DATA_SIZE,
356
+ );
357
+ try assert_result(
358
+ tb_client,
359
+ packet_list,
360
+ c.TB_OP_LOOKUP_TRANSFERS,
361
+ @sizeOf(u128) + 1,
362
+ c.TB_PACKET_INVALID_DATA_SIZE,
363
+ );
364
+ try assert_result(
365
+ tb_client,
366
+ packet_list,
367
+ c.TB_OP_LOOKUP_ACCOUNTS,
368
+ @sizeOf(u128) * 2.5,
369
+ c.TB_PACKET_INVALID_DATA_SIZE,
370
+ );
371
+ }
@@ -20,13 +20,17 @@ const usage = fmt.comptimePrint(
20
20
  \\
21
21
  \\ tigerbeetle start --addresses=<addresses> <path>
22
22
  \\
23
+ \\ tigerbeetle version [--version]
24
+ \\
23
25
  \\Commands:
24
26
  \\
25
- \\ format Create a TigerBeetle replica data file at <path>.
26
- \\ The --cluster and --replica arguments are required.
27
- \\ Each TigerBeetle replica must have its own data file.
27
+ \\ format Create a TigerBeetle replica data file at <path>.
28
+ \\ The --cluster and --replica arguments are required.
29
+ \\ Each TigerBeetle replica must have its own data file.
30
+ \\
31
+ \\ start Run a TigerBeetle replica from the data file at <path>.
28
32
  \\
29
- \\ start Run a TigerBeetle replica from the data file at <path>.
33
+ \\ version Print the TigerBeetle build version and the compile-time config values.
30
34
  \\
31
35
  \\Options:
32
36
  \\
@@ -46,6 +50,10 @@ const usage = fmt.comptimePrint(
46
50
  \\ Either the IPv4 address or port number (but not both) may be omitted,
47
51
  \\ in which case a default of {[default_address]s} or {[default_port]d}
48
52
  \\ will be used.
53
+ \\ "addresses[i]" corresponds to replica "i".
54
+ \\
55
+ \\ --verbose
56
+ \\ Print compile-time configuration along with the build version.
49
57
  \\
50
58
  \\Examples:
51
59
  \\
@@ -59,6 +67,8 @@ const usage = fmt.comptimePrint(
59
67
  \\
60
68
  \\ tigerbeetle start --addresses=192.168.0.1,192.168.0.2,192.168.0.3 7_0.tigerbeetle
61
69
  \\
70
+ \\ tigerbeetle version --verbose
71
+ \\
62
72
  , .{
63
73
  .default_address = config.address,
64
74
  .default_port = config.port,
@@ -77,11 +87,15 @@ pub const Command = union(enum) {
77
87
  memory: u64,
78
88
  path: [:0]const u8,
79
89
  },
90
+ version: struct {
91
+ verbose: bool,
92
+ },
80
93
 
81
94
  pub fn deinit(command: Command, allocator: std.mem.Allocator) void {
82
95
  var args_allocated = switch (command) {
83
96
  .format => |cmd| cmd.args_allocated,
84
97
  .start => |cmd| cmd.args_allocated,
98
+ .version => return,
85
99
  };
86
100
 
87
101
  for (args_allocated.items) |arg| allocator.free(arg);
@@ -97,6 +111,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
97
111
  var replica: ?[]const u8 = null;
98
112
  var addresses: ?[]const u8 = null;
99
113
  var memory: ?[]const u8 = null;
114
+ var verbose: ?bool = null;
100
115
 
101
116
  var args = try std.process.argsWithAllocator(allocator);
102
117
  defer args.deinit();
@@ -110,7 +125,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
110
125
  assert(did_skip);
111
126
 
112
127
  const raw_command = try (args.next(allocator) orelse
113
- fatal("no command provided, expected 'start' or 'format'", .{}));
128
+ fatal("no command provided, expected 'start', 'format', or 'version'", .{}));
114
129
  defer allocator.free(raw_command);
115
130
 
116
131
  if (mem.eql(u8, raw_command, "-h") or mem.eql(u8, raw_command, "--help")) {
@@ -118,7 +133,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
118
133
  os.exit(0);
119
134
  }
120
135
  const command = meta.stringToEnum(meta.Tag(Command), raw_command) orelse
121
- fatal("unknown command '{s}', expected 'start' or 'format'", .{raw_command});
136
+ fatal("unknown command '{s}', expected 'start', 'format', or 'version'", .{raw_command});
122
137
 
123
138
  while (args.next(allocator)) |parsed_arg| {
124
139
  const arg = try parsed_arg;
@@ -132,6 +147,8 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
132
147
  addresses = parse_flag("--addresses", arg);
133
148
  } else if (mem.startsWith(u8, arg, "--memory")) {
134
149
  memory = parse_flag("--memory", arg);
150
+ } else if (mem.eql(u8, arg, "--verbose")) {
151
+ verbose = true;
135
152
  } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
136
153
  std.io.getStdOut().writeAll(usage) catch os.exit(1);
137
154
  os.exit(0);
@@ -145,9 +162,21 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
145
162
  }
146
163
 
147
164
  switch (command) {
165
+ .version => {
166
+ if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
167
+ if (memory != null) fatal("--memory: supported only by 'start' command", .{});
168
+ if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
169
+ if (replica != null) fatal("--replica: supported only by 'format' command", .{});
170
+ if (path != null) fatal("unexpected path", .{});
171
+
172
+ return Command{
173
+ .version = .{ .verbose = verbose orelse false },
174
+ };
175
+ },
148
176
  .format => {
149
177
  if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
150
178
  if (memory != null) fatal("--memory: supported only by 'start' command", .{});
179
+ if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
151
180
 
152
181
  return Command{
153
182
  .format = .{
@@ -161,6 +190,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
161
190
  .start => {
162
191
  if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
163
192
  if (replica != null) fatal("--replica: supported only by 'format' command", .{});
193
+ if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
164
194
 
165
195
  return Command{
166
196
  .start = .{
@@ -213,7 +213,6 @@ pub const io_depth_write = 8;
213
213
 
214
214
  /// The number of redundant copies of the superblock in the superblock storage zone.
215
215
  /// This must be either { 4, 6, 8 }, i.e. an even number, for more efficient flexible quorums.
216
- /// This is further multiplied by two to support copy-on-write across copy sets.
217
216
  ///
218
217
  /// The superblock contains local state for the replica and therefore cannot be replicated remotely.
219
218
  /// Loss of the superblock would represent loss of the replica and so it must be protected.
@@ -239,6 +238,9 @@ pub const superblock_copies = 4;
239
238
  pub const size_max = 16 * 1024 * 1024 * 1024 * 1024;
240
239
 
241
240
  /// The unit of read/write access to LSM manifest and LSM table blocks in the block storage zone.
241
+ ///
242
+ /// - A lower block size increases the memory overhead of table metadata, due to smaller/more tables.
243
+ /// - A higher block size increases space amplification due to partially-filled blocks.
242
244
  pub const block_size = 64 * 1024;
243
245
 
244
246
  pub const block_count_max = @divExact(16 * 1024 * 1024 * 1024 * 1024, block_size);
@@ -261,6 +263,8 @@ pub const lsm_growth_factor = 8;
261
263
  /// The maximum key size for an LSM tree in bytes.
262
264
  pub const lsm_key_size_max = 32;
263
265
 
266
+ /// The maximum cumulative size of a table — computed as the sum of the size of the index block,
267
+ /// filter blocks, and data blocks.
264
268
  pub const lsm_table_size_max = 64 * 1024 * 1024;
265
269
 
266
270
  /// Size of nodes used by the LSM tree manifest implementation.
@@ -358,8 +362,13 @@ comptime {
358
362
 
359
363
  // The WAL format requires messages to be a multiple of the sector size.
360
364
  assert(message_size_max % sector_size == 0);
365
+ assert(message_size_max >= @sizeOf(vsr.Header));
361
366
  assert(message_size_max >= sector_size);
362
367
 
368
+ assert(superblock_copies % 2 == 0);
369
+ assert(superblock_copies >= 4);
370
+ assert(superblock_copies <= 8);
371
+
363
372
  // ManifestLog serializes the level as a u7.
364
373
  assert(lsm_levels > 0);
365
374
  assert(lsm_levels <= std.math.maxInt(u7));
@@ -10,6 +10,7 @@ const Transfer = tb.Transfer;
10
10
  const CreateAccountsResult = tb.CreateAccountsResult;
11
11
  const CreateTransfersResult = tb.CreateTransfersResult;
12
12
 
13
+ const util = @import("util.zig");
13
14
  const IO = @import("io.zig").IO;
14
15
  const Storage = @import("storage.zig").Storage;
15
16
  const MessagePool = @import("message_pool.zig").MessagePool;
@@ -61,7 +62,7 @@ pub fn request(
61
62
  defer client.unref(message);
62
63
 
63
64
  const body = std.mem.asBytes(&batch);
64
- std.mem.copy(u8, message.buffer[@sizeOf(Header)..], body);
65
+ util.copy_disjoint(.inexact, u8, message.buffer[@sizeOf(Header)..], body);
65
66
 
66
67
  client.request(
67
68
  0,
@@ -15,7 +15,7 @@ pub fn main() !void {
15
15
  .debits_pending = 0,
16
16
  .debits_posted = 0,
17
17
  .credits_pending = 0,
18
- .credits_posted = 10000, // Let's start with some liquidity.
18
+ .credits_posted = 0,
19
19
  },
20
20
  Account{
21
21
  .id = 2,
@@ -7,6 +7,19 @@ pub fn main() !void {
7
7
  const transfers = [_]Transfer{
8
8
  Transfer{
9
9
  .id = 1,
10
+ .debit_account_id = 2,
11
+ .credit_account_id = 1,
12
+ .user_data = 0,
13
+ .reserved = 0,
14
+ .pending_id = 0,
15
+ .timeout = 0,
16
+ .ledger = 710, // Let's use the ISO-4217 Code Number for ZAR
17
+ .code = 1,
18
+ .flags = .{},
19
+ .amount = 10000, // Let's start with some liquidity in account 1.
20
+ },
21
+ Transfer{
22
+ .id = 2,
10
23
  .debit_account_id = 1,
11
24
  .credit_account_id = 2,
12
25
  .user_data = 0,
@@ -2,7 +2,9 @@ const std = @import("std");
2
2
  const assert = std.debug.assert;
3
3
  const math = std.math;
4
4
  const mem = std.mem;
5
- const div_ceil = @import("util.zig").div_ceil;
5
+ const util = @import("util.zig");
6
+ const div_ceil = util.div_ceil;
7
+ const disjoint_slices = util.disjoint_slices;
6
8
 
7
9
  /// Encode or decode a bitset using Daniel Lemire's EWAH codec.
8
10
  /// ("Histogram-Aware Sorting for Enhanced Word-Aligned Compression in Bitmap Indexes")
@@ -58,10 +60,12 @@ pub fn ewah(comptime Word: type) type {
58
60
 
59
61
  /// Decodes the compressed bitset in `source` into `target_words`.
60
62
  /// Returns the number of *words* written to `target_words`.
63
+ // TODO Refactor to return an error when `source` is invalid,
64
+ // so that we can test invalid encodings.
61
65
  pub fn decode(source: []align(@alignOf(Word)) const u8, target_words: []Word) usize {
62
66
  assert(source.len % @sizeOf(Word) == 0);
63
67
  assert(source.len >= @sizeOf(Marker));
64
- assert(is_disjoint(u8, Word, source, target_words));
68
+ assert(disjoint_slices(u8, Word, source, target_words));
65
69
 
66
70
  const source_words = mem.bytesAsSlice(Word, source);
67
71
  var source_index: usize = 0;
@@ -75,7 +79,8 @@ pub fn ewah(comptime Word: type) type {
75
79
  if (marker.uniform_bit == 1) ~@as(Word, 0) else 0,
76
80
  );
77
81
  target_index += marker.uniform_word_count;
78
- std.mem.copy(
82
+ util.copy_disjoint(
83
+ .exact,
79
84
  Word,
80
85
  target_words[target_index..][0..marker.literal_word_count],
81
86
  source_words[source_index..][0..marker.literal_word_count],
@@ -92,7 +97,7 @@ pub fn ewah(comptime Word: type) type {
92
97
  pub fn encode(source_words: []const Word, target: []align(@alignOf(Word)) u8) usize {
93
98
  assert(target.len >= @sizeOf(Marker));
94
99
  assert(target.len == encode_size_max(source_words.len));
95
- assert(is_disjoint(Word, u8, source_words, target));
100
+ assert(disjoint_slices(Word, u8, source_words, target));
96
101
 
97
102
  const target_words = mem.bytesAsSlice(Word, target);
98
103
  std.mem.set(Word, target_words, 0);
@@ -126,7 +131,8 @@ pub fn ewah(comptime Word: type) type {
126
131
  .literal_word_count = @intCast(MarkerLiteralCount, literal_word_count),
127
132
  });
128
133
  target_index += 1;
129
- std.mem.copy(
134
+ util.copy_disjoint(
135
+ .exact,
130
136
  Word,
131
137
  target_words[target_index..][0..literal_word_count],
132
138
  source_words[source_index..][0..literal_word_count],
@@ -154,34 +160,6 @@ pub fn ewah(comptime Word: type) type {
154
160
  };
155
161
  }
156
162
 
157
- fn is_disjoint(comptime A: type, comptime B: type, a: []const A, b: []const B) bool {
158
- return @ptrToInt(a.ptr) + a.len * @sizeOf(A) <= @ptrToInt(b.ptr) or
159
- @ptrToInt(b.ptr) + b.len * @sizeOf(B) <= @ptrToInt(a.ptr);
160
- }
161
-
162
- test "is_disjoint" {
163
- const a = try std.testing.allocator.alignedAlloc(u8, @sizeOf(u32), 8 * @sizeOf(u32));
164
- defer std.testing.allocator.free(a);
165
-
166
- const b = try std.testing.allocator.alloc(u32, 8);
167
- defer std.testing.allocator.free(b);
168
-
169
- try std.testing.expectEqual(true, is_disjoint(u8, u32, a, b));
170
- try std.testing.expectEqual(true, is_disjoint(u32, u8, b, a));
171
-
172
- try std.testing.expectEqual(true, is_disjoint(u8, u8, a, a[0..0]));
173
- try std.testing.expectEqual(true, is_disjoint(u32, u32, b, b[0..0]));
174
-
175
- try std.testing.expectEqual(false, is_disjoint(u8, u8, a, a[0..1]));
176
- try std.testing.expectEqual(false, is_disjoint(u8, u8, a, a[a.len - 1 .. a.len]));
177
-
178
- try std.testing.expectEqual(false, is_disjoint(u32, u32, b, b[0..1]));
179
- try std.testing.expectEqual(false, is_disjoint(u32, u32, b, b[b.len - 1 .. b.len]));
180
-
181
- try std.testing.expectEqual(false, is_disjoint(u8, u32, a, std.mem.bytesAsSlice(u32, a)));
182
- try std.testing.expectEqual(false, is_disjoint(u32, u8, b, std.mem.sliceAsBytes(b)));
183
- }
184
-
185
163
  test "ewah Word=u8 decode→encode→decode" {
186
164
  try test_decode_with_word(u8);
187
165