tigerbeetle-node 0.5.2 → 0.8.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 (61) hide show
  1. package/README.md +97 -78
  2. package/dist/benchmark.js +96 -94
  3. package/dist/benchmark.js.map +1 -1
  4. package/dist/index.d.ts +82 -82
  5. package/dist/index.js +74 -93
  6. package/dist/index.js.map +1 -1
  7. package/dist/test.js +134 -111
  8. package/dist/test.js.map +1 -1
  9. package/package.json +3 -2
  10. package/scripts/download_node_headers.sh +3 -1
  11. package/src/benchmark.ts +114 -118
  12. package/src/index.ts +102 -111
  13. package/src/node.zig +55 -63
  14. package/src/test.ts +146 -125
  15. package/src/tigerbeetle/scripts/benchmark.bat +46 -0
  16. package/src/tigerbeetle/scripts/benchmark.sh +5 -0
  17. package/src/tigerbeetle/scripts/install_zig.bat +109 -109
  18. package/src/tigerbeetle/scripts/install_zig.sh +8 -4
  19. package/src/tigerbeetle/scripts/vopr.bat +47 -47
  20. package/src/tigerbeetle/scripts/vopr.sh +2 -2
  21. package/src/tigerbeetle/src/benchmark.zig +65 -102
  22. package/src/tigerbeetle/src/cli.zig +39 -18
  23. package/src/tigerbeetle/src/config.zig +44 -25
  24. package/src/tigerbeetle/src/demo.zig +2 -15
  25. package/src/tigerbeetle/src/demo_01_create_accounts.zig +10 -10
  26. package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
  27. package/src/tigerbeetle/src/{demo_04_create_transfers_two_phase_commit.zig → demo_04_create_pending_transfers.zig} +18 -12
  28. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +37 -0
  29. package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +24 -0
  30. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +1 -1
  31. package/src/tigerbeetle/src/io/benchmark.zig +24 -49
  32. package/src/tigerbeetle/src/io/darwin.zig +175 -44
  33. package/src/tigerbeetle/src/io/linux.zig +177 -72
  34. package/src/tigerbeetle/src/io/test.zig +61 -39
  35. package/src/tigerbeetle/src/io/windows.zig +1161 -0
  36. package/src/tigerbeetle/src/io.zig +2 -0
  37. package/src/tigerbeetle/src/main.zig +31 -10
  38. package/src/tigerbeetle/src/message_bus.zig +49 -61
  39. package/src/tigerbeetle/src/message_pool.zig +66 -57
  40. package/src/tigerbeetle/src/ring_buffer.zig +55 -3
  41. package/src/tigerbeetle/src/simulator.zig +108 -12
  42. package/src/tigerbeetle/src/state_machine.zig +1813 -816
  43. package/src/tigerbeetle/src/storage.zig +0 -230
  44. package/src/tigerbeetle/src/test/cluster.zig +168 -38
  45. package/src/tigerbeetle/src/test/message_bus.zig +4 -3
  46. package/src/tigerbeetle/src/test/network.zig +13 -16
  47. package/src/tigerbeetle/src/test/packet_simulator.zig +14 -1
  48. package/src/tigerbeetle/src/test/state_checker.zig +6 -3
  49. package/src/tigerbeetle/src/test/state_machine.zig +8 -7
  50. package/src/tigerbeetle/src/test/storage.zig +99 -40
  51. package/src/tigerbeetle/src/tigerbeetle.zig +108 -101
  52. package/src/tigerbeetle/src/time.zig +58 -11
  53. package/src/tigerbeetle/src/vsr/client.zig +18 -32
  54. package/src/tigerbeetle/src/vsr/clock.zig +1 -1
  55. package/src/tigerbeetle/src/vsr/journal.zig +1388 -464
  56. package/src/tigerbeetle/src/vsr/replica.zig +1340 -576
  57. package/src/tigerbeetle/src/vsr.zig +452 -40
  58. package/src/translate.zig +10 -0
  59. package/src/tigerbeetle/src/demo_05_accept_transfers.zig +0 -23
  60. package/src/tigerbeetle/src/demo_06_reject_transfers.zig +0 -17
  61. package/src/tigerbeetle/src/format_test.zig +0 -69
@@ -1,9 +1,12 @@
1
1
  const std = @import("std");
2
2
  const builtin = @import("builtin");
3
- const assert = std.debug.assert;
4
- const is_darwin = builtin.target.isDarwin();
5
3
  const config = @import("./config.zig");
6
4
 
5
+ const os = std.os;
6
+ const assert = std.debug.assert;
7
+ const is_darwin = builtin.target.os.tag.isDarwin();
8
+ const is_windows = builtin.target.os.tag == .windows;
9
+
7
10
  pub const Time = struct {
8
11
  const Self = @This();
9
12
 
@@ -19,19 +22,44 @@ pub const Time = struct {
19
22
  /// system administrator manually changes the clock.
20
23
  pub fn monotonic(self: *Self) u64 {
21
24
  const m = blk: {
25
+ // Uses QueryPerformanceCounter() on windows due to it being the highest precision timer
26
+ // available while also accounting for time spent suspended by default:
27
+ // https://docs.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-queryunbiasedinterrupttime#remarks
28
+ if (is_windows) {
29
+ // QPF need not be globally cached either as it ends up being a load from read-only
30
+ // memory mapped to all processed by the kernel called KUSER_SHARED_DATA (See "QpcFrequency")
31
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data
32
+ // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
33
+ const qpc = os.windows.QueryPerformanceCounter();
34
+ const qpf = os.windows.QueryPerformanceFrequency();
35
+
36
+ // 10Mhz (1 qpc tick every 100ns) is a common QPF on modern systems.
37
+ // We can optimize towards this by converting to ns via a single multiply.
38
+ // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701
39
+ const common_qpf = 10_000_000;
40
+ if (qpf == common_qpf) break :blk qpc * (std.time.ns_per_s / common_qpf);
41
+
42
+ // Convert qpc to nanos using fixed point to avoid expensive extra divs and overflow.
43
+ const scale = (std.time.ns_per_s << 32) / qpf;
44
+ break :blk @truncate(u64, (@as(u96, qpc) * scale) >> 32);
45
+ }
46
+
22
47
  // Uses mach_continuous_time() instead of mach_absolute_time() as it counts while suspended.
23
48
  // https://developer.apple.com/documentation/kernel/1646199-mach_continuous_time
24
49
  // https://opensource.apple.com/source/Libc/Libc-1158.1.2/gen/clock_gettime.c.auto.html
25
50
  if (is_darwin) {
26
51
  const darwin = struct {
27
- const mach_timebase_info_t = std.os.darwin.mach_timebase_info_data;
28
- extern "c" fn mach_timebase_info(info: *mach_timebase_info_t) std.os.darwin.kern_return_t;
52
+ const mach_timebase_info_t = os.darwin.mach_timebase_info_data;
53
+ extern "c" fn mach_timebase_info(info: *mach_timebase_info_t) os.darwin.kern_return_t;
29
54
  extern "c" fn mach_continuous_time() u64;
30
55
  };
31
56
 
32
- const now = darwin.mach_continuous_time();
57
+ // mach_timebase_info() called through libc already does global caching for us
58
+ // https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/wrappers/mach_timebase_info.c.auto.html
33
59
  var info: darwin.mach_timebase_info_t = undefined;
34
60
  if (darwin.mach_timebase_info(&info) != 0) @panic("mach_timebase_info() failed");
61
+
62
+ const now = darwin.mach_continuous_time();
35
63
  return (now * info.numer) / info.denom;
36
64
  }
37
65
 
@@ -40,8 +68,8 @@ pub const Time = struct {
40
68
  // CLOCK_BOOTTIME is the same as CLOCK_MONOTONIC but includes elapsed time during a suspend.
41
69
  // For more detail and why CLOCK_MONOTONIC_RAW is even worse than CLOCK_MONOTONIC,
42
70
  // see https://github.com/ziglang/zig/pull/933#discussion_r656021295.
43
- var ts: std.os.timespec = undefined;
44
- std.os.clock_gettime(std.os.CLOCK.BOOTTIME, &ts) catch @panic("CLOCK_BOOTTIME required");
71
+ var ts: os.timespec = undefined;
72
+ os.clock_gettime(os.CLOCK.BOOTTIME, &ts) catch @panic("CLOCK_BOOTTIME required");
45
73
  break :blk @intCast(u64, ts.tv_sec) * std.time.ns_per_s + @intCast(u64, ts.tv_nsec);
46
74
  };
47
75
 
@@ -54,11 +82,30 @@ pub const Time = struct {
54
82
  /// A timestamp to measure real (i.e. wall clock) time, meaningful across systems, and reboots.
55
83
  /// This clock is affected by discontinuous jumps in the system time.
56
84
  pub fn realtime(_: *Self) i64 {
57
- // macos has supported clock_gettime() since 10.12:
58
- // https://opensource.apple.com/source/Libc/Libc-1158.1.2/gen/clock_gettime.3.auto.html
85
+ if (is_windows) {
86
+ const kernel32 = struct {
87
+ extern "kernel32" fn GetSystemTimePreciseAsFileTime(
88
+ lpFileTime: *os.windows.FILETIME,
89
+ ) callconv(os.windows.WINAPI) void;
90
+ };
91
+
92
+ var ft: os.windows.FILETIME = undefined;
93
+ kernel32.GetSystemTimePreciseAsFileTime(&ft);
94
+ const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
95
+
96
+ // FileTime is in units of 100 nanoseconds
97
+ // and uses the NTFS/Windows epoch of 1601-01-01 instead of Unix Epoch 1970-01-01.
98
+ const epoch_adjust = std.time.epoch.windows * (std.time.ns_per_s / 100);
99
+ return (@bitCast(i64, ft64) + epoch_adjust) * 100;
100
+ }
101
+
102
+ if (is_darwin) {
103
+ // macos has supported clock_gettime() since 10.12:
104
+ // https://opensource.apple.com/source/Libc/Libc-1158.1.2/gen/clock_gettime.3.auto.html
105
+ }
59
106
 
60
- var ts: std.os.timespec = undefined;
61
- std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts) catch unreachable;
107
+ var ts: os.timespec = undefined;
108
+ os.clock_gettime(os.CLOCK.REALTIME, &ts) catch unreachable;
62
109
  return @as(i64, ts.tv_sec) * std.time.ns_per_s + ts.tv_nsec;
63
110
  }
64
111
 
@@ -7,7 +7,8 @@ const vsr = @import("../vsr.zig");
7
7
  const Header = vsr.Header;
8
8
 
9
9
  const RingBuffer = @import("../ring_buffer.zig").RingBuffer;
10
- const Message = @import("../message_pool.zig").MessagePool.Message;
10
+ const message_pool = @import("../message_pool.zig");
11
+ const Message = message_pool.MessagePool.Message;
11
12
 
12
13
  const log = std.log.scoped(.client);
13
14
 
@@ -66,8 +67,7 @@ pub fn Client(comptime StateMachine: type, comptime MessageBus: type) type {
66
67
 
67
68
  /// A client is allowed at most one inflight request at a time at the protocol layer.
68
69
  /// We therefore queue any further concurrent requests made by the application layer.
69
- /// We must leave one message free to receive with.
70
- request_queue: RingBuffer(Request, config.message_bus_messages_max - 1) = .{},
70
+ request_queue: RingBuffer(Request, config.client_request_queue_max) = .{},
71
71
 
72
72
  /// The number of ticks without a reply before the client resends the inflight request.
73
73
  /// Dynamically adjusted as a function of recent request round-trip time.
@@ -188,25 +188,25 @@ pub fn Client(comptime StateMachine: type, comptime MessageBus: type) type {
188
188
  @tagName(operation),
189
189
  });
190
190
 
191
+ if (self.request_queue.full()) {
192
+ callback(user_data, operation, error.TooManyOutstandingRequests);
193
+ return;
194
+ }
195
+
191
196
  const was_empty = self.request_queue.empty();
192
197
 
193
- self.request_queue.push(.{
198
+ self.request_queue.push_assume_capacity(.{
194
199
  .user_data = user_data,
195
200
  .callback = callback,
196
201
  .message = message.ref(),
197
- }) catch |err| switch (err) {
198
- error.NoSpaceLeft => {
199
- callback(user_data, operation, error.TooManyOutstandingRequests);
200
- return;
201
- },
202
- };
202
+ });
203
203
 
204
204
  // If the queue was empty, then there is no request inflight and we must send this one:
205
205
  if (was_empty) self.send_request_for_the_first_time(message);
206
206
  }
207
207
 
208
208
  /// Acquires a message from the message bus if one is available.
209
- pub fn get_message(self: *Self) ?*Message {
209
+ pub fn get_message(self: *Self) *Message {
210
210
  return self.message_bus.get_message();
211
211
  }
212
212
 
@@ -383,12 +383,12 @@ pub fn Client(comptime StateMachine: type, comptime MessageBus: type) type {
383
383
  }
384
384
 
385
385
  /// The caller owns the returned message, if any, which has exactly 1 reference.
386
- fn create_message_from_header(self: *Self, header: Header) ?*Message {
386
+ fn create_message_from_header(self: *Self, header: Header) *Message {
387
387
  assert(header.client == self.id);
388
388
  assert(header.cluster == self.cluster);
389
389
  assert(header.size == @sizeOf(Header));
390
390
 
391
- const message = self.message_bus.pool.get_header_only_message() orelse return null;
391
+ const message = self.message_bus.pool.get_message();
392
392
  defer self.message_bus.unref(message);
393
393
 
394
394
  message.header.* = header;
@@ -402,8 +402,7 @@ pub fn Client(comptime StateMachine: type, comptime MessageBus: type) type {
402
402
  fn register(self: *Self) void {
403
403
  if (self.request_number > 0) return;
404
404
 
405
- var message = self.message_bus.get_message() orelse
406
- @panic("register: no message available to register a session with the cluster");
405
+ const message = self.message_bus.get_message();
407
406
  defer self.message_bus.unref(message);
408
407
 
409
408
  // We will set parent, context, view and checksums only when sending for the first time:
@@ -422,37 +421,24 @@ pub fn Client(comptime StateMachine: type, comptime MessageBus: type) type {
422
421
 
423
422
  assert(self.request_queue.empty());
424
423
 
425
- self.request_queue.push(.{
424
+ self.request_queue.push_assume_capacity(.{
426
425
  .user_data = 0,
427
426
  .callback = undefined,
428
427
  .message = message.ref(),
429
- }) catch |err| switch (err) {
430
- error.NoSpaceLeft => unreachable, // This is the first request.
431
- };
428
+ });
432
429
 
433
430
  self.send_request_for_the_first_time(message);
434
431
  }
435
432
 
436
433
  fn send_header_to_replica(self: *Self, replica: u8, header: Header) void {
437
- const message = self.create_message_from_header(header) orelse {
438
- log.err("{}: no header-only message available, dropping message to replica {}", .{
439
- self.id,
440
- replica,
441
- });
442
- return;
443
- };
434
+ const message = self.create_message_from_header(header);
444
435
  defer self.message_bus.unref(message);
445
436
 
446
437
  self.send_message_to_replica(replica, message);
447
438
  }
448
439
 
449
440
  fn send_header_to_replicas(self: *Self, header: Header) void {
450
- const message = self.create_message_from_header(header) orelse {
451
- log.err("{}: no header-only message available, dropping message to replicas", .{
452
- self.id,
453
- });
454
- return;
455
- };
441
+ const message = self.create_message_from_header(header);
456
442
  defer self.message_bus.unref(message);
457
443
 
458
444
  var replica: u8 = 0;
@@ -744,7 +744,7 @@ test "fuzz test" {
744
744
  const allocator = &arena_allocator.allocator;
745
745
  const ticks_max: u64 = 1_000_000;
746
746
  const clock_count: u8 = 3;
747
- const SystemTime = @import("../time.zig").Time;
747
+ const SystemTime = @import("../test/time.zig").Time;
748
748
  var system_time = SystemTime{};
749
749
  var seed = @intCast(u64, system_time.realtime());
750
750
  var min_sync_error: u64 = 1_000_000_000;