tigerbeetle-node 0.5.2 → 0.6.0
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/README.md +3 -4
- package/package.json +1 -1
- package/src/node.zig +2 -12
- package/src/tigerbeetle/scripts/benchmark.bat +46 -0
- package/src/tigerbeetle/scripts/install_zig.bat +2 -2
- package/src/tigerbeetle/scripts/install_zig.sh +1 -1
- package/src/tigerbeetle/scripts/vopr.sh +2 -2
- package/src/tigerbeetle/src/benchmark.zig +2 -6
- package/src/tigerbeetle/src/cli.zig +39 -18
- package/src/tigerbeetle/src/config.zig +24 -9
- package/src/tigerbeetle/src/demo.zig +1 -1
- package/src/tigerbeetle/src/io/benchmark.zig +24 -49
- package/src/tigerbeetle/src/io/darwin.zig +175 -44
- package/src/tigerbeetle/src/io/linux.zig +177 -72
- package/src/tigerbeetle/src/io/test.zig +61 -39
- package/src/tigerbeetle/src/io/windows.zig +1161 -0
- package/src/tigerbeetle/src/io.zig +2 -0
- package/src/tigerbeetle/src/main.zig +13 -8
- package/src/tigerbeetle/src/message_bus.zig +49 -61
- package/src/tigerbeetle/src/message_pool.zig +63 -57
- package/src/tigerbeetle/src/ring_buffer.zig +7 -0
- package/src/tigerbeetle/src/simulator.zig +4 -4
- package/src/tigerbeetle/src/storage.zig +0 -230
- package/src/tigerbeetle/src/test/cluster.zig +3 -6
- package/src/tigerbeetle/src/test/message_bus.zig +4 -3
- package/src/tigerbeetle/src/test/network.zig +13 -16
- package/src/tigerbeetle/src/test/state_checker.zig +3 -2
- package/src/tigerbeetle/src/tigerbeetle.zig +5 -3
- package/src/tigerbeetle/src/time.zig +58 -11
- package/src/tigerbeetle/src/vsr/client.zig +18 -32
- package/src/tigerbeetle/src/vsr/clock.zig +1 -1
- package/src/tigerbeetle/src/vsr/journal.zig +2 -6
- package/src/tigerbeetle/src/vsr/replica.zig +146 -169
- package/src/tigerbeetle/src/vsr.zig +263 -5
|
@@ -15,6 +15,8 @@ pub const Client = @import("vsr/client.zig").Client;
|
|
|
15
15
|
pub const Clock = @import("vsr/clock.zig").Clock;
|
|
16
16
|
pub const Journal = @import("vsr/journal.zig").Journal;
|
|
17
17
|
|
|
18
|
+
pub const ProcessType = enum { replica, client };
|
|
19
|
+
|
|
18
20
|
/// Viewstamped Replication protocol commands:
|
|
19
21
|
pub const Command = enum(u8) {
|
|
20
22
|
reserved,
|
|
@@ -223,10 +225,24 @@ pub const Header = packed struct {
|
|
|
223
225
|
if (self.epoch != 0) return "epoch != 0";
|
|
224
226
|
return switch (self.command) {
|
|
225
227
|
.reserved => self.invalid_reserved(),
|
|
228
|
+
.ping => self.invalid_ping(),
|
|
229
|
+
.pong => self.invalid_pong(),
|
|
226
230
|
.request => self.invalid_request(),
|
|
227
231
|
.prepare => self.invalid_prepare(),
|
|
228
232
|
.prepare_ok => self.invalid_prepare_ok(),
|
|
229
|
-
|
|
233
|
+
.reply => self.invalid_reply(),
|
|
234
|
+
.commit => self.invalid_commit(),
|
|
235
|
+
.start_view_change => self.invalid_start_view_change(),
|
|
236
|
+
.do_view_change => self.invalid_do_view_change(),
|
|
237
|
+
.start_view => self.invalid_start_view(),
|
|
238
|
+
.recovery => self.invalid_recovery(),
|
|
239
|
+
.recovery_response => self.invalid_recovery_response(),
|
|
240
|
+
.request_start_view => self.invalid_request_start_view(),
|
|
241
|
+
.request_headers => self.invalid_request_headers(),
|
|
242
|
+
.request_prepare => self.invalid_request_prepare(),
|
|
243
|
+
.headers => self.invalid_headers(),
|
|
244
|
+
.nack_prepare => self.invalid_nack_prepare(),
|
|
245
|
+
.eviction => self.invalid_eviction(),
|
|
230
246
|
};
|
|
231
247
|
}
|
|
232
248
|
|
|
@@ -246,6 +262,28 @@ pub const Header = packed struct {
|
|
|
246
262
|
return null;
|
|
247
263
|
}
|
|
248
264
|
|
|
265
|
+
fn invalid_ping(self: *const Header) ?[]const u8 {
|
|
266
|
+
assert(self.command == .ping);
|
|
267
|
+
if (self.parent != 0) return "parent != 0";
|
|
268
|
+
if (self.context != 0) return "context != 0";
|
|
269
|
+
if (self.request != 0) return "request != 0";
|
|
270
|
+
if (self.commit != 0) return "commit != 0";
|
|
271
|
+
if (self.offset != 0) return "offset != 0";
|
|
272
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
fn invalid_pong(self: *const Header) ?[]const u8 {
|
|
277
|
+
assert(self.command == .pong);
|
|
278
|
+
if (self.parent != 0) return "parent != 0";
|
|
279
|
+
if (self.client != 0) return "client != 0";
|
|
280
|
+
if (self.context != 0) return "context != 0";
|
|
281
|
+
if (self.request != 0) return "request != 0";
|
|
282
|
+
if (self.commit != 0) return "commit != 0";
|
|
283
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
249
287
|
fn invalid_request(self: *const Header) ?[]const u8 {
|
|
250
288
|
assert(self.command == .request);
|
|
251
289
|
if (self.client == 0) return "client == 0";
|
|
@@ -336,6 +374,154 @@ pub const Header = packed struct {
|
|
|
336
374
|
return null;
|
|
337
375
|
}
|
|
338
376
|
|
|
377
|
+
fn invalid_reply(self: *const Header) ?[]const u8 {
|
|
378
|
+
assert(self.command == .reply);
|
|
379
|
+
// Initialization within `client.zig` asserts that client `id` is greater than zero:
|
|
380
|
+
if (self.client == 0) return "client == 0";
|
|
381
|
+
if (self.context != 0) return "context != 0";
|
|
382
|
+
if (self.op != self.commit) return "op != commit";
|
|
383
|
+
if (self.operation == .register) {
|
|
384
|
+
// In this context, the commit number is the newly registered session number.
|
|
385
|
+
// The `0` commit number is reserved for cluster initialization.
|
|
386
|
+
if (self.commit == 0) return "commit == 0";
|
|
387
|
+
if (self.request != 0) return "request != 0";
|
|
388
|
+
} else {
|
|
389
|
+
if (self.commit == 0) return "commit == 0";
|
|
390
|
+
if (self.request == 0) return "request == 0";
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn invalid_commit(self: *const Header) ?[]const u8 {
|
|
396
|
+
assert(self.command == .commit);
|
|
397
|
+
if (self.parent != 0) return "parent != 0";
|
|
398
|
+
if (self.client != 0) return "client != 0";
|
|
399
|
+
if (self.request != 0) return "request != 0";
|
|
400
|
+
if (self.op != 0) return "op != 0";
|
|
401
|
+
if (self.offset != 0) return "offset != 0";
|
|
402
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
fn invalid_start_view_change(self: *const Header) ?[]const u8 {
|
|
407
|
+
assert(self.command == .start_view_change);
|
|
408
|
+
if (self.parent != 0) return "parent != 0";
|
|
409
|
+
if (self.client != 0) return "client != 0";
|
|
410
|
+
if (self.context != 0) return "context != 0";
|
|
411
|
+
if (self.request != 0) return "request != 0";
|
|
412
|
+
if (self.op != 0) return "op != 0";
|
|
413
|
+
if (self.commit != 0) return "commit != 0";
|
|
414
|
+
if (self.offset != 0) return "offset != 0";
|
|
415
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
fn invalid_do_view_change(self: *const Header) ?[]const u8 {
|
|
420
|
+
assert(self.command == .do_view_change);
|
|
421
|
+
if (self.parent != 0) return "parent != 0";
|
|
422
|
+
if (self.client != 0) return "client != 0";
|
|
423
|
+
if (self.context != 0) return "context != 0";
|
|
424
|
+
if (self.request != 0) return "request != 0";
|
|
425
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
fn invalid_start_view(self: *const Header) ?[]const u8 {
|
|
430
|
+
assert(self.command == .start_view);
|
|
431
|
+
if (self.parent != 0) return "parent != 0";
|
|
432
|
+
if (self.client != 0) return "client != 0";
|
|
433
|
+
if (self.context != 0) return "context != 0";
|
|
434
|
+
if (self.request != 0) return "request != 0";
|
|
435
|
+
if (self.offset != 0) return "offset != 0";
|
|
436
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
fn invalid_recovery(self: *const Header) ?[]const u8 {
|
|
441
|
+
assert(self.command == .recovery);
|
|
442
|
+
// TODO implement validation once the message is actually used.
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
fn invalid_recovery_response(self: *const Header) ?[]const u8 {
|
|
447
|
+
assert(self.command == .recovery_response);
|
|
448
|
+
// TODO implement validation once the message is actually used.
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
fn invalid_request_start_view(self: *const Header) ?[]const u8 {
|
|
453
|
+
assert(self.command == .request_start_view);
|
|
454
|
+
if (self.parent != 0) return "parent != 0";
|
|
455
|
+
if (self.client != 0) return "client != 0";
|
|
456
|
+
if (self.context != 0) return "context != 0";
|
|
457
|
+
if (self.request != 0) return "request != 0";
|
|
458
|
+
if (self.op != 0) return "op != 0";
|
|
459
|
+
if (self.commit != 0) return "commit != 0";
|
|
460
|
+
if (self.offset != 0) return "offset != 0";
|
|
461
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
fn invalid_request_headers(self: *const Header) ?[]const u8 {
|
|
466
|
+
assert(self.command == .request_headers);
|
|
467
|
+
if (self.parent != 0) return "parent != 0";
|
|
468
|
+
if (self.client != 0) return "client != 0";
|
|
469
|
+
if (self.context != 0) return "context != 0";
|
|
470
|
+
if (self.request != 0) return "request != 0";
|
|
471
|
+
if (self.offset != 0) return "offset != 0";
|
|
472
|
+
// TODO Add local recovery mechanism for repairing the cluster initialization "zero" op.
|
|
473
|
+
if (self.op == 0) return "op == 0";
|
|
474
|
+
if (self.commit > self.op) return "op_min > op_max";
|
|
475
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
fn invalid_request_prepare(self: *const Header) ?[]const u8 {
|
|
480
|
+
assert(self.command == .request_prepare);
|
|
481
|
+
if (self.parent != 0) return "parent != 0";
|
|
482
|
+
if (self.client != 0) return "client != 0";
|
|
483
|
+
if (self.request != 0) return "request != 0";
|
|
484
|
+
if (self.commit != 0) return "commit != 0";
|
|
485
|
+
if (self.offset != 0) return "offset != 0";
|
|
486
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
fn invalid_headers(self: *const Header) ?[]const u8 {
|
|
491
|
+
assert(self.command == .headers);
|
|
492
|
+
if (self.parent != 0) return "parent != 0";
|
|
493
|
+
if (self.client != 0) return "client != 0";
|
|
494
|
+
if (self.request != 0) return "request != 0";
|
|
495
|
+
if (self.op != 0) return "op != 0";
|
|
496
|
+
if (self.commit != 0) return "commit != 0";
|
|
497
|
+
if (self.offset != 0) return "offset != 0";
|
|
498
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
fn invalid_nack_prepare(self: *const Header) ?[]const u8 {
|
|
503
|
+
assert(self.command == .nack_prepare);
|
|
504
|
+
if (self.parent != 0) return "parent != 0";
|
|
505
|
+
if (self.client != 0) return "client != 0";
|
|
506
|
+
if (self.request != 0) return "request != 0";
|
|
507
|
+
if (self.commit != 0) return "commit != 0";
|
|
508
|
+
if (self.offset != 0) return "offset != 0";
|
|
509
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn invalid_eviction(self: *const Header) ?[]const u8 {
|
|
514
|
+
assert(self.command == .eviction);
|
|
515
|
+
if (self.parent != 0) return "parent != 0";
|
|
516
|
+
if (self.context != 0) return "context != 0";
|
|
517
|
+
if (self.request != 0) return "request != 0";
|
|
518
|
+
if (self.op != 0) return "op != 0";
|
|
519
|
+
if (self.commit != 0) return "commit != 0";
|
|
520
|
+
if (self.offset != 0) return "offset != 0";
|
|
521
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
|
|
339
525
|
/// Returns whether the immediate sender is a replica or client (if this can be determined).
|
|
340
526
|
/// Some commands such as .request or .prepare may be forwarded on to other replicas so that
|
|
341
527
|
/// Header.replica or Header.client only identifies the ultimate origin, not the latest peer.
|
|
@@ -546,17 +732,19 @@ test "exponential_backoff_with_jitter" {
|
|
|
546
732
|
/// * A replica's IP address may be changed without reconfiguration.
|
|
547
733
|
/// This does require that the user specify the same order to all replicas.
|
|
548
734
|
/// The caller owns the memory of the returned slice of addresses.
|
|
549
|
-
/// TODO Unit tests.
|
|
550
|
-
/// TODO Integrate into `src/cli.zig`.
|
|
551
735
|
pub fn parse_addresses(allocator: std.mem.Allocator, raw: []const u8) ![]std.net.Address {
|
|
552
|
-
|
|
736
|
+
return parse_addresses_limit(allocator, raw, config.replicas_max);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
fn parse_addresses_limit(allocator: std.mem.Allocator, raw: []const u8, max: usize) ![]std.net.Address {
|
|
740
|
+
var addresses = try allocator.alloc(std.net.Address, max);
|
|
553
741
|
errdefer allocator.free(addresses);
|
|
554
742
|
|
|
555
743
|
var index: usize = 0;
|
|
556
744
|
var comma_iterator = std.mem.split(u8, raw, ",");
|
|
557
745
|
while (comma_iterator.next()) |raw_address| : (index += 1) {
|
|
558
746
|
if (raw_address.len == 0) return error.AddressHasTrailingComma;
|
|
559
|
-
if (index ==
|
|
747
|
+
if (index == max) return error.AddressLimitExceeded;
|
|
560
748
|
|
|
561
749
|
var colon_iterator = std.mem.split(u8, raw_address, ":");
|
|
562
750
|
// The split iterator will always return non-null once, even if the delimiter is not found:
|
|
@@ -594,6 +782,76 @@ pub fn parse_addresses(allocator: std.mem.Allocator, raw: []const u8) ![]std.net
|
|
|
594
782
|
return addresses[0..index];
|
|
595
783
|
}
|
|
596
784
|
|
|
785
|
+
test "parse_addresses" {
|
|
786
|
+
const vectors_positive = &[_]struct {
|
|
787
|
+
raw: []const u8,
|
|
788
|
+
addresses: [3]std.net.Address,
|
|
789
|
+
}{
|
|
790
|
+
.{
|
|
791
|
+
// Test the minimum/maximum address/port.
|
|
792
|
+
.raw = "1.2.3.4:567,0.0.0.0:0,255.255.255.255:65535",
|
|
793
|
+
.addresses = [3]std.net.Address{
|
|
794
|
+
std.net.Address.initIp4([_]u8{ 1, 2, 3, 4 }, 567),
|
|
795
|
+
std.net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0),
|
|
796
|
+
std.net.Address.initIp4([_]u8{ 255, 255, 255, 255 }, 65535),
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
.{
|
|
800
|
+
// Addresses are not reordered.
|
|
801
|
+
.raw = "3.4.5.6:7777,200.3.4.5:6666,1.2.3.4:5555",
|
|
802
|
+
.addresses = [3]std.net.Address{
|
|
803
|
+
std.net.Address.initIp4([_]u8{ 3, 4, 5, 6 }, 7777),
|
|
804
|
+
std.net.Address.initIp4([_]u8{ 200, 3, 4, 5 }, 6666),
|
|
805
|
+
std.net.Address.initIp4([_]u8{ 1, 2, 3, 4 }, 5555),
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
.{
|
|
809
|
+
// Test default address and port.
|
|
810
|
+
.raw = "1.2.3.4:5,4321,2.3.4.5",
|
|
811
|
+
.addresses = [3]std.net.Address{
|
|
812
|
+
std.net.Address.initIp4([_]u8{ 1, 2, 3, 4 }, 5),
|
|
813
|
+
try std.net.Address.parseIp4(config.address, 4321),
|
|
814
|
+
std.net.Address.initIp4([_]u8{ 2, 3, 4, 5 }, config.port),
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const vectors_negative = &[_]struct {
|
|
820
|
+
raw: []const u8,
|
|
821
|
+
err: anyerror![]std.net.Address,
|
|
822
|
+
}{
|
|
823
|
+
.{ .raw = "", .err = error.AddressHasTrailingComma },
|
|
824
|
+
.{ .raw = "1.2.3.4:5,2.3.4.5:6,4.5.6.7:8", .err = error.AddressLimitExceeded },
|
|
825
|
+
.{ .raw = "1.2.3.4:7777,2.3.4.5:8888,", .err = error.AddressHasTrailingComma },
|
|
826
|
+
.{ .raw = "1.2.3.4:7777,2.3.4.5::8888", .err = error.AddressHasMoreThanOneColon },
|
|
827
|
+
.{ .raw = "1.2.3.4:5,A", .err = error.AddressInvalid }, // default port
|
|
828
|
+
.{ .raw = "1.2.3.4:5,2.a.4.5", .err = error.AddressInvalid }, // default port
|
|
829
|
+
.{ .raw = "1.2.3.4:5,2.a.4.5:6", .err = error.AddressInvalid }, // specified port
|
|
830
|
+
.{ .raw = "1.2.3.4:5,2.3.4.5:", .err = error.PortInvalid },
|
|
831
|
+
.{ .raw = "1.2.3.4:5,2.3.4.5:A", .err = error.PortInvalid },
|
|
832
|
+
.{ .raw = "1.2.3.4:5,65536", .err = error.PortOverflow }, // default address
|
|
833
|
+
.{ .raw = "1.2.3.4:5,2.3.4.5:65536", .err = error.PortOverflow },
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
for (vectors_positive) |vector| {
|
|
837
|
+
const addresses_actual = try parse_addresses_limit(std.testing.allocator, vector.raw, 3);
|
|
838
|
+
defer std.testing.allocator.free(addresses_actual);
|
|
839
|
+
|
|
840
|
+
try std.testing.expectEqual(addresses_actual.len, 3);
|
|
841
|
+
for (vector.addresses) |address_expect, i| {
|
|
842
|
+
const address_actual = addresses_actual[i];
|
|
843
|
+
try std.testing.expectEqual(address_expect.in.sa.family, address_actual.in.sa.family);
|
|
844
|
+
try std.testing.expectEqual(address_expect.in.sa.port, address_actual.in.sa.port);
|
|
845
|
+
try std.testing.expectEqual(address_expect.in.sa.addr, address_actual.in.sa.addr);
|
|
846
|
+
try std.testing.expectEqual(address_expect.in.sa.zero, address_actual.in.sa.zero);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
for (vectors_negative) |vector| {
|
|
851
|
+
try std.testing.expectEqual(vector.err, parse_addresses_limit(std.testing.allocator, vector.raw, 2));
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
597
855
|
pub fn sector_floor(offset: u64) u64 {
|
|
598
856
|
const sectors = math.divFloor(u64, offset, config.sector_size) catch unreachable;
|
|
599
857
|
return sectors * config.sector_size;
|