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.
Files changed (34) hide show
  1. package/README.md +3 -4
  2. package/package.json +1 -1
  3. package/src/node.zig +2 -12
  4. package/src/tigerbeetle/scripts/benchmark.bat +46 -0
  5. package/src/tigerbeetle/scripts/install_zig.bat +2 -2
  6. package/src/tigerbeetle/scripts/install_zig.sh +1 -1
  7. package/src/tigerbeetle/scripts/vopr.sh +2 -2
  8. package/src/tigerbeetle/src/benchmark.zig +2 -6
  9. package/src/tigerbeetle/src/cli.zig +39 -18
  10. package/src/tigerbeetle/src/config.zig +24 -9
  11. package/src/tigerbeetle/src/demo.zig +1 -1
  12. package/src/tigerbeetle/src/io/benchmark.zig +24 -49
  13. package/src/tigerbeetle/src/io/darwin.zig +175 -44
  14. package/src/tigerbeetle/src/io/linux.zig +177 -72
  15. package/src/tigerbeetle/src/io/test.zig +61 -39
  16. package/src/tigerbeetle/src/io/windows.zig +1161 -0
  17. package/src/tigerbeetle/src/io.zig +2 -0
  18. package/src/tigerbeetle/src/main.zig +13 -8
  19. package/src/tigerbeetle/src/message_bus.zig +49 -61
  20. package/src/tigerbeetle/src/message_pool.zig +63 -57
  21. package/src/tigerbeetle/src/ring_buffer.zig +7 -0
  22. package/src/tigerbeetle/src/simulator.zig +4 -4
  23. package/src/tigerbeetle/src/storage.zig +0 -230
  24. package/src/tigerbeetle/src/test/cluster.zig +3 -6
  25. package/src/tigerbeetle/src/test/message_bus.zig +4 -3
  26. package/src/tigerbeetle/src/test/network.zig +13 -16
  27. package/src/tigerbeetle/src/test/state_checker.zig +3 -2
  28. package/src/tigerbeetle/src/tigerbeetle.zig +5 -3
  29. package/src/tigerbeetle/src/time.zig +58 -11
  30. package/src/tigerbeetle/src/vsr/client.zig +18 -32
  31. package/src/tigerbeetle/src/vsr/clock.zig +1 -1
  32. package/src/tigerbeetle/src/vsr/journal.zig +2 -6
  33. package/src/tigerbeetle/src/vsr/replica.zig +146 -169
  34. 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
- else => return null, // TODO Add validators for all commands.
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
- var addresses = try allocator.alloc(std.net.Address, config.replicas_max);
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 == config.replicas_max) return error.AddressLimitExceeded;
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;