tigerbeetle-node 0.4.2 → 0.5.2

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 (58) hide show
  1. package/README.md +19 -5
  2. package/dist/benchmark.js.map +1 -1
  3. package/dist/index.d.ts +18 -16
  4. package/dist/index.js +35 -13
  5. package/dist/index.js.map +1 -1
  6. package/dist/test.js +12 -0
  7. package/dist/test.js.map +1 -1
  8. package/package.json +2 -2
  9. package/scripts/postinstall.sh +2 -2
  10. package/src/benchmark.ts +2 -2
  11. package/src/index.ts +29 -4
  12. package/src/node.zig +120 -17
  13. package/src/test.ts +14 -0
  14. package/src/tigerbeetle/scripts/install.sh +1 -1
  15. package/src/tigerbeetle/scripts/install_zig.bat +109 -0
  16. package/src/tigerbeetle/scripts/install_zig.sh +4 -2
  17. package/src/tigerbeetle/scripts/lint.zig +8 -2
  18. package/src/tigerbeetle/scripts/vopr.bat +48 -0
  19. package/src/tigerbeetle/src/benchmark.zig +10 -8
  20. package/src/tigerbeetle/src/cli.zig +6 -4
  21. package/src/tigerbeetle/src/config.zig +2 -2
  22. package/src/tigerbeetle/src/demo.zig +119 -89
  23. package/src/tigerbeetle/src/demo_01_create_accounts.zig +5 -3
  24. package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +2 -3
  25. package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
  26. package/src/tigerbeetle/src/demo_04_create_transfers_two_phase_commit.zig +5 -3
  27. package/src/tigerbeetle/src/demo_05_accept_transfers.zig +5 -3
  28. package/src/tigerbeetle/src/demo_06_reject_transfers.zig +5 -3
  29. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +7 -0
  30. package/src/tigerbeetle/src/io/benchmark.zig +238 -0
  31. package/src/tigerbeetle/src/{io_darwin.zig → io/darwin.zig} +89 -124
  32. package/src/tigerbeetle/src/io/linux.zig +933 -0
  33. package/src/tigerbeetle/src/io/test.zig +621 -0
  34. package/src/tigerbeetle/src/io.zig +7 -1328
  35. package/src/tigerbeetle/src/main.zig +18 -10
  36. package/src/tigerbeetle/src/message_bus.zig +43 -60
  37. package/src/tigerbeetle/src/message_pool.zig +3 -2
  38. package/src/tigerbeetle/src/ring_buffer.zig +135 -68
  39. package/src/tigerbeetle/src/simulator.zig +41 -37
  40. package/src/tigerbeetle/src/state_machine.zig +851 -26
  41. package/src/tigerbeetle/src/storage.zig +49 -46
  42. package/src/tigerbeetle/src/test/cluster.zig +2 -2
  43. package/src/tigerbeetle/src/test/message_bus.zig +6 -6
  44. package/src/tigerbeetle/src/test/network.zig +3 -3
  45. package/src/tigerbeetle/src/test/packet_simulator.zig +32 -29
  46. package/src/tigerbeetle/src/test/state_checker.zig +2 -2
  47. package/src/tigerbeetle/src/test/state_machine.zig +4 -0
  48. package/src/tigerbeetle/src/test/storage.zig +39 -19
  49. package/src/tigerbeetle/src/test/time.zig +2 -2
  50. package/src/tigerbeetle/src/tigerbeetle.zig +6 -129
  51. package/src/tigerbeetle/src/time.zig +6 -5
  52. package/src/tigerbeetle/src/vsr/client.zig +11 -11
  53. package/src/tigerbeetle/src/vsr/clock.zig +26 -43
  54. package/src/tigerbeetle/src/vsr/journal.zig +7 -6
  55. package/src/tigerbeetle/src/vsr/marzullo.zig +6 -3
  56. package/src/tigerbeetle/src/vsr/replica.zig +51 -48
  57. package/src/tigerbeetle/src/vsr.zig +24 -20
  58. package/src/translate.zig +55 -55
@@ -1,19 +1,24 @@
1
1
  const std = @import("std");
2
2
  const os = std.os;
3
+ const mem = std.mem;
3
4
  const assert = std.debug.assert;
4
5
 
5
- const FIFO = @import("fifo.zig").FIFO;
6
- const Time = @import("time.zig").Time;
7
- const buffer_limit = @import("io.zig").buffer_limit;
6
+ const FIFO = @import("../fifo.zig").FIFO;
7
+ const Time = @import("../time.zig").Time;
8
+ const buffer_limit = @import("../io.zig").buffer_limit;
8
9
 
9
10
  pub const IO = struct {
10
11
  kq: os.fd_t,
11
12
  time: Time = .{},
13
+ io_inflight: usize = 0,
12
14
  timeouts: FIFO(Completion) = .{},
13
15
  completed: FIFO(Completion) = .{},
14
16
  io_pending: FIFO(Completion) = .{},
15
17
 
16
18
  pub fn init(entries: u12, flags: u32) !IO {
19
+ _ = entries;
20
+ _ = flags;
21
+
17
22
  const kq = try os.kqueue();
18
23
  assert(kq > -1);
19
24
  return IO{ .kq = kq };
@@ -27,9 +32,7 @@ pub const IO = struct {
27
32
 
28
33
  /// Pass all queued submissions to the kernel and peek for completions.
29
34
  pub fn tick(self: *IO) !void {
30
- // TODO This is a hack to block 1ms every 10ms tick on macOS while we fix `flush(false)`:
31
- // What we're seeing for the tigerbeetle-node client is that recv() syscalls never complete.
32
- return self.run_for_ns(std.time.ns_per_ms);
35
+ return self.flush(false);
33
36
  }
34
37
 
35
38
  /// Pass all queued submissions to the kernel and run for `nanoseconds`.
@@ -42,8 +45,11 @@ pub const IO = struct {
42
45
  fn callback(
43
46
  timed_out_ptr: *bool,
44
47
  _completion: *Completion,
45
- _result: TimeoutError!void,
48
+ result: TimeoutError!void,
46
49
  ) void {
50
+ _ = _completion;
51
+ _ = result catch unreachable;
52
+
47
53
  timed_out_ptr.* = true;
48
54
  }
49
55
  }.callback;
@@ -84,10 +90,13 @@ pub const IO = struct {
84
90
  // - tick() is non-blocking (wait_for_completions = false)
85
91
  // - run_for_ns() always submits a timeout
86
92
  if (change_events == 0 and self.completed.peek() == null) {
87
- if (!wait_for_completions) return;
88
- const timeout_ns = next_timeout orelse @panic("kevent() blocking forever");
89
- ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s);
90
- ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s);
93
+ if (wait_for_completions) {
94
+ const timeout_ns = next_timeout orelse @panic("kevent() blocking forever");
95
+ ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s);
96
+ ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s);
97
+ } else if (self.io_inflight == 0) {
98
+ return;
99
+ }
91
100
  }
92
101
 
93
102
  const new_events = try os.kevent(
@@ -103,8 +112,12 @@ pub const IO = struct {
103
112
  self.io_pending.in = null;
104
113
  }
105
114
 
115
+ self.io_inflight += change_events;
116
+ self.io_inflight -= new_events;
117
+
106
118
  for (events[0..new_events]) |event| {
107
119
  const completion = @intToPtr(*Completion, event.udata);
120
+ completion.next = null;
108
121
  self.completed.push(completion);
109
122
  }
110
123
  }
@@ -116,25 +129,25 @@ pub const IO = struct {
116
129
  }
117
130
  }
118
131
 
119
- fn flush_io(self: *IO, events: []os.Kevent, io_pending_top: *?*Completion) usize {
132
+ fn flush_io(_: *IO, events: []os.Kevent, io_pending_top: *?*Completion) usize {
120
133
  for (events) |*event, flushed| {
121
134
  const completion = io_pending_top.* orelse return flushed;
122
135
  io_pending_top.* = completion.next;
123
136
 
124
137
  const event_info = switch (completion.operation) {
125
- .accept => |op| [2]c_int{ op.socket, os.EVFILT_READ },
126
- .connect => |op| [2]c_int{ op.socket, os.EVFILT_WRITE },
127
- .read => |op| [2]c_int{ op.fd, os.EVFILT_READ },
128
- .write => |op| [2]c_int{ op.fd, os.EVFILT_WRITE },
129
- .recv => |op| [2]c_int{ op.socket, os.EVFILT_READ },
130
- .send => |op| [2]c_int{ op.socket, os.EVFILT_WRITE },
138
+ .accept => |op| [2]c_int{ op.socket, os.system.EVFILT_READ },
139
+ .connect => |op| [2]c_int{ op.socket, os.system.EVFILT_WRITE },
140
+ .read => |op| [2]c_int{ op.fd, os.system.EVFILT_READ },
141
+ .write => |op| [2]c_int{ op.fd, os.system.EVFILT_WRITE },
142
+ .recv => |op| [2]c_int{ op.socket, os.system.EVFILT_READ },
143
+ .send => |op| [2]c_int{ op.socket, os.system.EVFILT_WRITE },
131
144
  else => @panic("invalid completion operation queued for io"),
132
145
  };
133
146
 
134
147
  event.* = .{
135
148
  .ident = @intCast(u32, event_info[0]),
136
149
  .filter = @intCast(i16, event_info[1]),
137
- .flags = os.EV_ADD | os.EV_ENABLE | os.EV_ONESHOT,
150
+ .flags = os.system.EV_ADD | os.system.EV_ENABLE | os.system.EV_ONESHOT,
138
151
  .fflags = 0,
139
152
  .data = 0,
140
153
  .udata = @ptrToInt(completion),
@@ -174,7 +187,7 @@ pub const IO = struct {
174
187
  /// This struct holds the data needed for a single IO operation
175
188
  pub const Completion = struct {
176
189
  next: ?*Completion,
177
- context: ?*c_void,
190
+ context: ?*anyopaque,
178
191
  callback: fn (*IO, *Completion) void,
179
192
  operation: Operation,
180
193
  };
@@ -182,7 +195,6 @@ pub const IO = struct {
182
195
  const Operation = union(enum) {
183
196
  accept: struct {
184
197
  socket: os.socket_t,
185
- flags: u32,
186
198
  },
187
199
  close: struct {
188
200
  fd: os.fd_t,
@@ -194,13 +206,6 @@ pub const IO = struct {
194
206
  },
195
207
  fsync: struct {
196
208
  fd: os.fd_t,
197
- flags: u32,
198
- },
199
- openat: struct {
200
- fd: os.fd_t,
201
- path: [*:0]const u8,
202
- flags: u32,
203
- mode: os.mode_t,
204
209
  },
205
210
  read: struct {
206
211
  fd: os.fd_t,
@@ -212,13 +217,11 @@ pub const IO = struct {
212
217
  socket: os.socket_t,
213
218
  buf: [*]u8,
214
219
  len: u32,
215
- flags: u32,
216
220
  },
217
221
  send: struct {
218
222
  socket: os.socket_t,
219
223
  buf: [*]const u8,
220
224
  len: u32,
221
- flags: u32,
222
225
  },
223
226
  timeout: struct {
224
227
  expires: u64,
@@ -284,7 +287,7 @@ pub const IO = struct {
284
287
  }
285
288
  }
286
289
 
287
- pub const AcceptError = os.AcceptError;
290
+ pub const AcceptError = os.AcceptError || os.SetSockOptError;
288
291
 
289
292
  pub fn accept(
290
293
  self: *IO,
@@ -297,7 +300,6 @@ pub const IO = struct {
297
300
  ) void,
298
301
  completion: *Completion,
299
302
  socket: os.socket_t,
300
- flags: u32,
301
303
  ) void {
302
304
  self.submit(
303
305
  context,
@@ -306,11 +308,33 @@ pub const IO = struct {
306
308
  .accept,
307
309
  .{
308
310
  .socket = socket,
309
- .flags = flags,
310
311
  },
311
312
  struct {
312
313
  fn doOperation(op: anytype) AcceptError!os.socket_t {
313
- return os.accept(op.socket, null, null, op.flags);
314
+ const fd = try os.accept(
315
+ op.socket,
316
+ null,
317
+ null,
318
+ os.SOCK.NONBLOCK | os.SOCK.CLOEXEC,
319
+ );
320
+ errdefer os.close(fd);
321
+
322
+ // Darwin doesn't support os.MSG_NOSIGNAL to avoid getting SIGPIPE on socket send().
323
+ // Instead, it uses the SO_NOSIGPIPE socket option which does the same for all send()s.
324
+ os.setsockopt(
325
+ fd,
326
+ os.SOL.SOCKET,
327
+ os.SO.NOSIGPIPE,
328
+ &mem.toBytes(@as(c_int, 1)),
329
+ ) catch |err| return switch (err) {
330
+ error.TimeoutTooBig => unreachable,
331
+ error.PermissionDenied => error.NetworkSubsystemFailed,
332
+ error.AlreadyConnected => error.NetworkSubsystemFailed,
333
+ error.InvalidProtocolOption => error.ProtocolFailure,
334
+ else => |e| e,
335
+ };
336
+
337
+ return fd;
314
338
  }
315
339
  },
316
340
  );
@@ -346,10 +370,10 @@ pub const IO = struct {
346
370
  struct {
347
371
  fn doOperation(op: anytype) CloseError!void {
348
372
  return switch (os.errno(os.system.close(op.fd))) {
349
- 0 => {},
350
- os.EBADF => error.FileDescriptorInvalid,
351
- os.EINTR => {}, // A success, see https://github.com/ziglang/zig/issues/2425
352
- os.EIO => error.InputOutput,
373
+ .SUCCESS => {},
374
+ .BADF => error.FileDescriptorInvalid,
375
+ .INTR => {}, // A success, see https://github.com/ziglang/zig/issues/2425
376
+ .IO => error.InputOutput,
353
377
  else => |errno| os.unexpectedErrno(errno),
354
378
  };
355
379
  }
@@ -398,14 +422,7 @@ pub const IO = struct {
398
422
  );
399
423
  }
400
424
 
401
- pub const FsyncError = error{
402
- FileDescriptorInvalid,
403
- DiskQuota,
404
- ArgumentsInvalid,
405
- InputOutput,
406
- NoSpaceLeft,
407
- ReadOnlyFileSystem,
408
- } || os.UnexpectedError;
425
+ pub const FsyncError = os.SyncError;
409
426
 
410
427
  pub fn fsync(
411
428
  self: *IO,
@@ -418,7 +435,6 @@ pub const IO = struct {
418
435
  ) void,
419
436
  completion: *Completion,
420
437
  fd: os.fd_t,
421
- flags: u32,
422
438
  ) void {
423
439
  self.submit(
424
440
  context,
@@ -427,66 +443,10 @@ pub const IO = struct {
427
443
  .fsync,
428
444
  .{
429
445
  .fd = fd,
430
- .flags = flags,
431
446
  },
432
447
  struct {
433
448
  fn doOperation(op: anytype) FsyncError!void {
434
- _ = os.fcntl(op.fd, os.F_FULLFSYNC, 1) catch return os.fsync(op.fd);
435
- }
436
- },
437
- );
438
- }
439
-
440
- pub const OpenatError = error{
441
- AccessDenied,
442
- FileDescriptorInvalid,
443
- DeviceBusy,
444
- PathAlreadyExists,
445
- FileTooBig,
446
- ArgumentsInvalid,
447
- IsDir,
448
- SymLinkLoop,
449
- ProcessFdQuotaExceeded,
450
- NameTooLong,
451
- SystemFdQuotaExceeded,
452
- NoDevice,
453
- FileNotFound,
454
- SystemResources,
455
- NoSpaceLeft,
456
- NotDir,
457
- FileLocksNotSupported,
458
- WouldBlock,
459
- } || os.UnexpectedError;
460
-
461
- pub fn openat(
462
- self: *IO,
463
- comptime Context: type,
464
- context: Context,
465
- comptime callback: fn (
466
- context: Context,
467
- completion: *Completion,
468
- result: OpenatError!os.fd_t,
469
- ) void,
470
- completion: *Completion,
471
- fd: os.fd_t,
472
- path: [*:0]const u8,
473
- flags: u32,
474
- mode: os.mode_t,
475
- ) void {
476
- self.submit(
477
- context,
478
- callback,
479
- completion,
480
- .openat,
481
- .{
482
- .fd = fd,
483
- .path = path,
484
- .mode = mode,
485
- .flags = flags,
486
- },
487
- struct {
488
- fn doOperation(op: anytype) OpenatError!os.fd_t {
489
- return os.openatZ(op.fd, op.path, op.flags, op.mode);
449
+ _ = os.fcntl(op.fd, os.F.FULLFSYNC, 1) catch return os.fsync(op.fd);
490
450
  }
491
451
  },
492
452
  );
@@ -538,20 +498,20 @@ pub const IO = struct {
538
498
  @bitCast(isize, op.offset),
539
499
  );
540
500
  return switch (os.errno(rc)) {
541
- 0 => @intCast(usize, rc),
542
- os.EINTR => continue,
543
- os.EAGAIN => error.WouldBlock,
544
- os.EBADF => error.NotOpenForReading,
545
- os.ECONNRESET => error.ConnectionResetByPeer,
546
- os.EFAULT => unreachable,
547
- os.EINVAL => error.Alignment,
548
- os.EIO => error.InputOutput,
549
- os.EISDIR => error.IsDir,
550
- os.ENOBUFS => error.SystemResources,
551
- os.ENOMEM => error.SystemResources,
552
- os.ENXIO => error.Unseekable,
553
- os.EOVERFLOW => error.Unseekable,
554
- os.ESPIPE => error.Unseekable,
501
+ .SUCCESS => @intCast(usize, rc),
502
+ .INTR => continue,
503
+ .AGAIN => error.WouldBlock,
504
+ .BADF => error.NotOpenForReading,
505
+ .CONNRESET => error.ConnectionResetByPeer,
506
+ .FAULT => unreachable,
507
+ .INVAL => error.Alignment,
508
+ .IO => error.InputOutput,
509
+ .ISDIR => error.IsDir,
510
+ .NOBUFS => error.SystemResources,
511
+ .NOMEM => error.SystemResources,
512
+ .NXIO => error.Unseekable,
513
+ .OVERFLOW => error.Unseekable,
514
+ .SPIPE => error.Unseekable,
555
515
  else => |err| os.unexpectedErrno(err),
556
516
  };
557
517
  }
@@ -574,7 +534,6 @@ pub const IO = struct {
574
534
  completion: *Completion,
575
535
  socket: os.socket_t,
576
536
  buffer: []u8,
577
- flags: u32,
578
537
  ) void {
579
538
  self.submit(
580
539
  context,
@@ -585,11 +544,10 @@ pub const IO = struct {
585
544
  .socket = socket,
586
545
  .buf = buffer.ptr,
587
546
  .len = @intCast(u32, buffer_limit(buffer.len)),
588
- .flags = flags,
589
547
  },
590
548
  struct {
591
549
  fn doOperation(op: anytype) RecvError!usize {
592
- return os.recv(op.socket, op.buf[0..op.len], op.flags);
550
+ return os.recv(op.socket, op.buf[0..op.len], 0);
593
551
  }
594
552
  },
595
553
  );
@@ -609,7 +567,6 @@ pub const IO = struct {
609
567
  completion: *Completion,
610
568
  socket: os.socket_t,
611
569
  buffer: []const u8,
612
- flags: u32,
613
570
  ) void {
614
571
  self.submit(
615
572
  context,
@@ -620,11 +577,10 @@ pub const IO = struct {
620
577
  .socket = socket,
621
578
  .buf = buffer.ptr,
622
579
  .len = @intCast(u32, buffer_limit(buffer.len)),
623
- .flags = flags,
624
580
  },
625
581
  struct {
626
582
  fn doOperation(op: anytype) SendError!usize {
627
- return os.send(op.socket, op.buf[0..op.len], op.flags);
583
+ return os.send(op.socket, op.buf[0..op.len], 0);
628
584
  }
629
585
  },
630
586
  );
@@ -694,4 +650,13 @@ pub const IO = struct {
694
650
  },
695
651
  );
696
652
  }
653
+
654
+ pub fn openSocket(family: u32, sock_type: u32, protocol: u32) !os.socket_t {
655
+ const fd = try os.socket(family, sock_type | os.SOCK.NONBLOCK, protocol);
656
+ errdefer os.close(fd);
657
+
658
+ // darwin doesn't support os.MSG_NOSIGNAL, but instead a socket option to avoid SIGPIPE.
659
+ try os.setsockopt(fd, os.SOL.SOCKET, os.SO.NOSIGPIPE, &mem.toBytes(@as(c_int, 1)));
660
+ return fd;
661
+ }
697
662
  };