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.
- package/package.json +4 -3
- package/src/tigerbeetle/scripts/fuzz_loop.sh +1 -1
- package/src/tigerbeetle/scripts/pre-commit.sh +2 -2
- package/src/tigerbeetle/scripts/validate_docs.sh +17 -0
- package/src/tigerbeetle/src/benchmark.zig +25 -11
- package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
- package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
- package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
- package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
- package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -256
- package/src/tigerbeetle/src/c/tb_client.h +18 -4
- package/src/tigerbeetle/src/c/tb_client.zig +88 -26
- package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
- package/src/tigerbeetle/src/c/test.zig +371 -1
- package/src/tigerbeetle/src/cli.zig +36 -6
- package/src/tigerbeetle/src/config.zig +10 -1
- package/src/tigerbeetle/src/demo.zig +2 -1
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
- package/src/tigerbeetle/src/ewah.zig +11 -33
- package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
- package/src/tigerbeetle/src/lsm/README.md +97 -3
- package/src/tigerbeetle/src/lsm/compaction.zig +32 -7
- package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +34 -32
- package/src/tigerbeetle/src/lsm/grid.zig +39 -21
- package/src/tigerbeetle/src/lsm/groove.zig +1 -0
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +3 -3
- package/src/tigerbeetle/src/lsm/level_iterator.zig +1 -1
- package/src/tigerbeetle/src/lsm/manifest.zig +13 -0
- package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -49
- package/src/tigerbeetle/src/lsm/manifest_log.zig +173 -335
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
- package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
- package/src/tigerbeetle/src/lsm/segmented_array.zig +24 -15
- package/src/tigerbeetle/src/lsm/table.zig +32 -20
- package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
- package/src/tigerbeetle/src/lsm/table_iterator.zig +4 -5
- package/src/tigerbeetle/src/lsm/test.zig +13 -2
- package/src/tigerbeetle/src/lsm/tree.zig +45 -7
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +36 -32
- package/src/tigerbeetle/src/main.zig +55 -2
- package/src/tigerbeetle/src/message_bus.zig +18 -7
- package/src/tigerbeetle/src/message_pool.zig +8 -2
- package/src/tigerbeetle/src/ring_buffer.zig +7 -3
- package/src/tigerbeetle/src/simulator.zig +38 -11
- package/src/tigerbeetle/src/state_machine.zig +47 -22
- package/src/tigerbeetle/src/test/accounting/workload.zig +9 -5
- package/src/tigerbeetle/src/test/cluster.zig +15 -33
- package/src/tigerbeetle/src/test/conductor.zig +2 -1
- package/src/tigerbeetle/src/test/network.zig +45 -19
- package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
- package/src/tigerbeetle/src/test/state_checker.zig +5 -7
- package/src/tigerbeetle/src/test/storage.zig +453 -110
- package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
- package/src/tigerbeetle/src/tigerbeetle.zig +1 -0
- package/src/tigerbeetle/src/unit_tests.zig +6 -1
- package/src/tigerbeetle/src/util.zig +97 -11
- package/src/tigerbeetle/src/vopr.zig +2 -1
- package/src/tigerbeetle/src/vsr/client.zig +8 -3
- package/src/tigerbeetle/src/vsr/journal.zig +280 -202
- package/src/tigerbeetle/src/vsr/replica.zig +169 -31
- package/src/tigerbeetle/src/vsr/superblock.zig +356 -629
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -6
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +414 -151
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +44 -9
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
- package/src/tigerbeetle/src/vsr.zig +19 -5
- package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
- package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
- package/src/tigerbeetle/src/vopr_hub/README.md +0 -58
- package/src/tigerbeetle/src/vopr_hub/SETUP.md +0 -199
- package/src/tigerbeetle/src/vopr_hub/go.mod +0 -3
- package/src/tigerbeetle/src/vopr_hub/main.go +0 -1022
- package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +0 -3
- package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +0 -403
|
@@ -1 +1,371 @@
|
|
|
1
|
-
|
|
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
|
|
26
|
-
\\
|
|
27
|
-
\\
|
|
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
|
-
\\
|
|
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 '
|
|
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 '
|
|
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
|
-
|
|
65
|
+
util.copy_disjoint(.inexact, u8, message.buffer[@sizeOf(Header)..], body);
|
|
65
66
|
|
|
66
67
|
client.request(
|
|
67
68
|
0,
|
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|