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
@@ -20,12 +20,20 @@ const vsr = @import("vsr.zig");
20
20
  const Replica = vsr.Replica(StateMachine, MessageBus, Storage, Time);
21
21
 
22
22
  pub fn main() !void {
23
- var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
24
- const arena = &arena_allocator.allocator;
23
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
24
+ defer arena.deinit();
25
25
 
26
- switch (cli.parse_args(arena)) {
27
- .init => |args| try init(arena, args.cluster, args.replica, args.dir_fd),
28
- .start => |args| try start(arena, args.cluster, args.replica, args.addresses, args.dir_fd),
26
+ const allocator = arena.allocator();
27
+
28
+ switch (cli.parse_args(allocator)) {
29
+ .init => |args| try init(args.cluster, args.replica, args.dir_fd),
30
+ .start => |args| try start(
31
+ allocator,
32
+ args.cluster,
33
+ args.replica,
34
+ args.addresses,
35
+ args.dir_fd,
36
+ ),
29
37
  }
30
38
  }
31
39
 
@@ -34,7 +42,7 @@ const filename_fmt = "cluster_{d:0>10}_replica_{d:0>3}.tigerbeetle";
34
42
  const filename_len = fmt.count(filename_fmt, .{ 0, 0 });
35
43
 
36
44
  /// Create a .tigerbeetle data file for the given args and exit
37
- fn init(arena: *mem.Allocator, cluster: u32, replica: u8, dir_fd: os.fd_t) !void {
45
+ fn init(cluster: u32, replica: u8, dir_fd: os.fd_t) !void {
38
46
  // Add 1 for the terminating null byte
39
47
  var buffer: [filename_len + 1]u8 = undefined;
40
48
  const filename = fmt.bufPrintZ(&buffer, filename_fmt, .{ cluster, replica }) catch unreachable;
@@ -53,7 +61,7 @@ fn init(arena: *mem.Allocator, cluster: u32, replica: u8, dir_fd: os.fd_t) !void
53
61
 
54
62
  /// Run as a replica server defined by the given args
55
63
  fn start(
56
- arena: *mem.Allocator,
64
+ allocator: mem.Allocator,
57
65
  cluster: u32,
58
66
  replica_index: u8,
59
67
  addresses: []std.net.Address,
@@ -75,14 +83,14 @@ fn start(
75
83
  );
76
84
  var io = try IO.init(128, 0);
77
85
  var state_machine = try StateMachine.init(
78
- arena,
86
+ allocator,
79
87
  config.accounts_max,
80
88
  config.transfers_max,
81
89
  config.commits_max,
82
90
  );
83
91
  var storage = try Storage.init(config.journal_size_max, storage_fd, &io);
84
92
  var message_bus = try MessageBus.init(
85
- arena,
93
+ allocator,
86
94
  cluster,
87
95
  addresses,
88
96
  replica_index,
@@ -90,7 +98,7 @@ fn start(
90
98
  );
91
99
  var time: Time = .{};
92
100
  var replica = try Replica.init(
93
- arena,
101
+ allocator,
94
102
  cluster,
95
103
  @intCast(u8, addresses.len),
96
104
  replica_index,
@@ -1,10 +1,10 @@
1
1
  const std = @import("std");
2
+ const builtin = @import("builtin");
2
3
  const assert = std.debug.assert;
3
4
  const mem = std.mem;
4
5
  const os = std.os;
5
6
 
6
- const is_darwin = std.Target.current.isDarwin();
7
- const sock_flags = os.SOCK_CLOEXEC | (if (is_darwin) os.SOCK_NONBLOCK else 0);
7
+ const is_linux = builtin.target.os.tag == .linux;
8
8
 
9
9
  const config = @import("config.zig");
10
10
  const log = std.log.scoped(.message_bus);
@@ -54,8 +54,8 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
54
54
 
55
55
  /// The callback to be called when a message is received. Use set_on_message() to set
56
56
  /// with type safety for the context pointer.
57
- on_message_callback: ?fn (context: ?*c_void, message: *Message) void = null,
58
- on_message_context: ?*c_void = null,
57
+ on_message_callback: ?fn (context: ?*anyopaque, message: *Message) void = null,
58
+ on_message_context: ?*anyopaque = null,
59
59
 
60
60
  /// This slice is allocated with a fixed size in the init function and never reallocated.
61
61
  connections: []Connection,
@@ -75,7 +75,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
75
75
 
76
76
  /// Initialize the MessageBus for the given cluster, configuration and replica/client process.
77
77
  pub fn init(
78
- allocator: *mem.Allocator,
78
+ allocator: mem.Allocator,
79
79
  cluster: u32,
80
80
  configuration: []std.net.Address,
81
81
  process: switch (process_type) {
@@ -124,7 +124,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
124
124
 
125
125
  // Pre-allocate enough memory to hold all possible connections in the client map.
126
126
  if (process_type == .replica) {
127
- try bus.process.clients.ensureCapacity(allocator, config.connections_max);
127
+ try bus.process.clients.ensureTotalCapacity(allocator, config.connections_max);
128
128
  }
129
129
 
130
130
  return bus;
@@ -140,7 +140,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
140
140
  assert(bus.on_message_context == null);
141
141
 
142
142
  bus.on_message_callback = struct {
143
- fn wrapper(_context: ?*c_void, message: *Message) void {
143
+ fn wrapper(_context: ?*anyopaque, message: *Message) void {
144
144
  on_message(@intToPtr(Context, @ptrToInt(_context)), message);
145
145
  }
146
146
  }.wrapper;
@@ -148,13 +148,13 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
148
148
  }
149
149
 
150
150
  /// TODO This is required by the Client.
151
- pub fn deinit(bus: *Self) void {}
151
+ pub fn deinit(_: *Self) void {}
152
152
 
153
153
  fn init_tcp(address: std.net.Address) !os.socket_t {
154
- const fd = try os.socket(
154
+ const fd = try IO.openSocket(
155
155
  address.any.family,
156
- os.SOCK_STREAM | sock_flags,
157
- os.IPPROTO_TCP,
156
+ os.SOCK.STREAM,
157
+ os.IPPROTO.TCP,
158
158
  );
159
159
  errdefer os.close(fd);
160
160
 
@@ -164,71 +164,55 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
164
164
  }
165
165
  }.set;
166
166
 
167
- // darwin doesn't support os.MSG_NOSIGNAL, but instead a socket option to avoid SIGPIPE.
168
- if (is_darwin) {
169
- try set(fd, os.SOL_SOCKET, os.SO_NOSIGPIPE, 1);
170
- }
171
-
172
- // Set tcp recv buffer size
173
167
  if (config.tcp_rcvbuf > 0) rcvbuf: {
174
- if (!is_darwin) {
168
+ if (is_linux) {
175
169
  // Requires CAP_NET_ADMIN privilege (settle for SO_RCVBUF in case of an EPERM):
176
- if (set(fd, os.SOL_SOCKET, os.SO_RCVBUFFORCE, config.tcp_rcvbuf)) |_| {
170
+ if (set(fd, os.SOL.SOCKET, os.SO.RCVBUFFORCE, config.tcp_rcvbuf)) |_| {
177
171
  break :rcvbuf;
178
172
  } else |err| switch (err) {
179
173
  error.PermissionDenied => {},
180
174
  else => |e| return e,
181
175
  }
182
176
  }
183
- try set(fd, os.SOL_SOCKET, os.SO_RCVBUF, config.tcp_rcvbuf);
177
+ try set(fd, os.SOL.SOCKET, os.SO.RCVBUF, config.tcp_rcvbuf);
184
178
  }
185
179
 
186
- // Set tcp send buffer size
187
180
  if (config.tcp_sndbuf > 0) sndbuf: {
188
- if (!is_darwin) {
181
+ if (is_linux) {
189
182
  // Requires CAP_NET_ADMIN privilege (settle for SO_SNDBUF in case of an EPERM):
190
- if (set(fd, os.SOL_SOCKET, os.SO_SNDBUFFORCE, config.tcp_sndbuf)) |_| {
183
+ if (set(fd, os.SOL.SOCKET, os.SO.SNDBUFFORCE, config.tcp_sndbuf)) |_| {
191
184
  break :sndbuf;
192
185
  } else |err| switch (err) {
193
186
  error.PermissionDenied => {},
194
187
  else => |e| return e,
195
188
  }
196
189
  }
197
- try set(fd, os.SOL_SOCKET, os.SO_SNDBUF, config.tcp_sndbuf);
190
+ try set(fd, os.SOL.SOCKET, os.SO.SNDBUF, config.tcp_sndbuf);
198
191
  }
199
192
 
200
- // Set tcp keep alive
201
193
  if (config.tcp_keepalive) {
202
- try set(fd, os.SOL_SOCKET, os.SO_KEEPALIVE, 1);
203
- if (!is_darwin) {
204
- try set(fd, os.IPPROTO_TCP, os.TCP_KEEPIDLE, config.tcp_keepidle);
205
- try set(fd, os.IPPROTO_TCP, os.TCP_KEEPINTVL, config.tcp_keepintvl);
206
- try set(fd, os.IPPROTO_TCP, os.TCP_KEEPCNT, config.tcp_keepcnt);
194
+ try set(fd, os.SOL.SOCKET, os.SO.KEEPALIVE, 1);
195
+ if (is_linux) {
196
+ try set(fd, os.IPPROTO.TCP, os.TCP.KEEPIDLE, config.tcp_keepidle);
197
+ try set(fd, os.IPPROTO.TCP, os.TCP.KEEPINTVL, config.tcp_keepintvl);
198
+ try set(fd, os.IPPROTO.TCP, os.TCP.KEEPCNT, config.tcp_keepcnt);
207
199
  }
208
200
  }
209
201
 
210
- // Set tcp user timeout
211
202
  if (config.tcp_user_timeout > 0) {
212
- if (!is_darwin) {
213
- try set(fd, os.IPPROTO_TCP, os.TCP_USER_TIMEOUT, config.tcp_user_timeout);
203
+ if (is_linux) {
204
+ try set(fd, os.IPPROTO.TCP, os.TCP.USER_TIMEOUT, config.tcp_user_timeout);
214
205
  }
215
206
  }
216
207
 
217
208
  // Set tcp no-delay
218
209
  if (config.tcp_nodelay) {
219
- const TCP_NODELAY: ?u32 = if (@hasDecl(os, "TCP_NODELAY"))
220
- @as(u32, os.TCP_NODELAY)
221
- else if (is_darwin)
222
- @as(u32, 1)
223
- else
224
- null;
225
-
226
- if (TCP_NODELAY) |tcp_nodelay| {
227
- try set(fd, os.IPPROTO_TCP, tcp_nodelay, 1);
210
+ if (is_linux) {
211
+ try set(fd, os.IPPROTO.TCP, os.TCP.NODELAY, 1);
228
212
  }
229
213
  }
230
214
 
231
- try set(fd, os.SOL_SOCKET, os.SO_REUSEADDR, 1);
215
+ try set(fd, os.SOL.SOCKET, os.SO.REUSEADDR, 1);
232
216
  try os.bind(fd, &address.any, address.getOsSockLen());
233
217
  try os.listen(fd, config.tcp_backlog);
234
218
 
@@ -289,7 +273,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
289
273
  if (connection.state == .terminating) return;
290
274
  }
291
275
 
292
- log.notice("all connections in use but not all replicas are connected, " ++
276
+ log.info("all connections in use but not all replicas are connected, " ++
293
277
  "attempting to disconnect a client", .{});
294
278
  for (bus.connections) |*connection| {
295
279
  if (connection.peer == .client) {
@@ -298,7 +282,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
298
282
  }
299
283
  }
300
284
 
301
- log.notice("failed to disconnect a client as no peer was a known client, " ++
285
+ log.info("failed to disconnect a client as no peer was a known client, " ++
302
286
  "attempting to disconnect an unknown peer.", .{});
303
287
  for (bus.connections) |*connection| {
304
288
  if (connection.peer == .unknown) {
@@ -332,7 +316,6 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
332
316
  on_accept,
333
317
  &bus.process.accept_completion,
334
318
  bus.process.accept_fd,
335
- sock_flags,
336
319
  );
337
320
  }
338
321
 
@@ -341,6 +324,8 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
341
324
  completion: *IO.Completion,
342
325
  result: IO.AcceptError!os.socket_t,
343
326
  ) void {
327
+ _ = completion;
328
+
344
329
  comptime assert(process_type == .replica);
345
330
  assert(bus.process.accept_connection != null);
346
331
  defer bus.process.accept_connection = null;
@@ -459,7 +444,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
459
444
  // The first replica's network address family determines the
460
445
  // family for all other replicas:
461
446
  const family = bus.configuration[0].any.family;
462
- connection.fd = os.socket(family, os.SOCK_STREAM | sock_flags, 0) catch return;
447
+ connection.fd = IO.openSocket(family, os.SOCK.STREAM, os.IPPROTO.TCP) catch return;
463
448
  connection.peer = .{ .replica = replica };
464
449
  connection.state = .connecting;
465
450
  bus.connections_used += 1;
@@ -469,7 +454,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
469
454
 
470
455
  var attempts = &bus.replicas_connect_attempts[replica];
471
456
  const ms = vsr.exponential_backoff_with_jitter(
472
- &bus.prng,
457
+ bus.prng.random(),
473
458
  config.connection_delay_min_ms,
474
459
  config.connection_delay_max_ms,
475
460
  attempts.*,
@@ -600,7 +585,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
600
585
  connection.send_queue.push(message.ref()) catch |err| switch (err) {
601
586
  error.NoSpaceLeft => {
602
587
  bus.unref(message);
603
- log.notice("message queue for peer {} full, dropping message", .{
588
+ log.info("message queue for peer {} full, dropping message", .{
604
589
  connection.peer,
605
590
  });
606
591
  return;
@@ -633,12 +618,12 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
633
618
  //
634
619
  // TODO: Investigate differences between shutdown() on Linux vs Darwin.
635
620
  // Especially how this interacts with our assumptions around pending I/O.
636
- const rc = os.system.shutdown(connection.fd, os.SHUT_RDWR);
621
+ const rc = os.system.shutdown(connection.fd, os.SHUT.RDWR);
637
622
  switch (os.errno(rc)) {
638
- 0 => {},
639
- os.EBADF => unreachable,
640
- os.EINVAL => unreachable,
641
- os.ENOTCONN => {
623
+ .SUCCESS => {},
624
+ .BADF => unreachable,
625
+ .INVAL => unreachable,
626
+ .NOTCONN => {
642
627
  // This should only happen if we for some reason decide to terminate
643
628
  // a connection while a connect operation is in progress.
644
629
  // This is fine though, we simply continue with the logic below and
@@ -653,7 +638,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
653
638
  //assert(connection.recv_submitted);
654
639
  //assert(!connection.send_submitted);
655
640
  },
656
- os.ENOTSOCK => unreachable,
641
+ .NOTSOCK => unreachable,
657
642
  else => |err| os.unexpectedErrno(err) catch {},
658
643
  }
659
644
  },
@@ -886,7 +871,6 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
886
871
  &connection.recv_completion,
887
872
  connection.fd,
888
873
  connection.recv_message.?.buffer[connection.recv_progress..config.message_size_max],
889
- if (is_darwin) 0 else os.MSG_NOSIGNAL,
890
874
  );
891
875
  }
892
876
 
@@ -919,7 +903,7 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
919
903
  assert(connection.peer == .client or connection.peer == .replica);
920
904
  assert(connection.state == .connected);
921
905
  assert(connection.fd != -1);
922
- const message = connection.send_queue.peek() orelse return;
906
+ const message = connection.send_queue.head() orelse return;
923
907
  assert(!connection.send_submitted);
924
908
  connection.send_submitted = true;
925
909
  bus.io.send(
@@ -929,7 +913,6 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
929
913
  &connection.send_completion,
930
914
  connection.fd,
931
915
  message.buffer[connection.send_progress..message.header.size],
932
- if (is_darwin) 0 else os.MSG_NOSIGNAL,
933
916
  );
934
917
  }
935
918
 
@@ -949,9 +932,9 @@ fn MessageBusImpl(comptime process_type: ProcessType) type {
949
932
  connection.terminate(bus, .shutdown);
950
933
  return;
951
934
  };
952
- assert(connection.send_progress <= connection.send_queue.peek_ptr().?.*.header.size);
935
+ assert(connection.send_progress <= connection.send_queue.head().?.header.size);
953
936
  // If the message has been fully sent, move on to the next one.
954
- if (connection.send_progress == connection.send_queue.peek_ptr().?.*.header.size) {
937
+ if (connection.send_progress == connection.send_queue.head().?.header.size) {
955
938
  connection.send_progress = 0;
956
939
  const message = connection.send_queue.pop().?;
957
940
  bus.unref(message);
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const builtin = @import("builtin");
2
3
  const assert = std.debug.assert;
3
4
  const mem = std.mem;
4
5
 
@@ -51,7 +52,7 @@ pub const MessagePool = struct {
51
52
  /// List of currently usused header-sized messages
52
53
  header_only_free_list: ?*Message,
53
54
 
54
- pub fn init(allocator: *mem.Allocator) error{OutOfMemory}!MessagePool {
55
+ pub fn init(allocator: mem.Allocator) error{OutOfMemory}!MessagePool {
55
56
  var ret: MessagePool = .{
56
57
  .free_list = null,
57
58
  .header_only_free_list = null,
@@ -119,7 +120,7 @@ pub const MessagePool = struct {
119
120
  pub fn unref(pool: *MessagePool, message: *Message) void {
120
121
  message.references -= 1;
121
122
  if (message.references == 0) {
122
- if (std.builtin.mode == .Debug) mem.set(u8, message.buffer, undefined);
123
+ if (builtin.mode == .Debug) mem.set(u8, message.buffer, undefined);
123
124
  if (message.header_only()) {
124
125
  message.next = pool.header_only_free_list;
125
126
  pool.header_only_free_list = message;
@@ -14,51 +14,84 @@ pub fn RingBuffer(comptime T: type, comptime size: usize) type {
14
14
  /// The number of items in the buffer.
15
15
  count: usize = 0,
16
16
 
17
- /// Add an element to the RingBuffer. Returns an error if the buffer
18
- /// is already full and the element could not be added.
19
- pub fn push(self: *Self, item: T) error{NoSpaceLeft}!void {
20
- if (self.full()) return error.NoSpaceLeft;
21
- self.buffer[(self.index + self.count) % self.buffer.len] = item;
22
- self.count += 1;
17
+ // TODO add doc comments to these functions:
18
+ pub inline fn head(self: Self) ?T {
19
+ if (self.empty()) return null;
20
+ return self.buffer[self.index];
23
21
  }
24
22
 
25
- /// Return, but do not remove, the next item, if any.
26
- pub fn peek(self: *Self) ?T {
27
- return (self.peek_ptr() orelse return null).*;
23
+ pub inline fn head_ptr(self: *Self) ?*T {
24
+ if (self.empty()) return null;
25
+ return &self.buffer[self.index];
28
26
  }
29
27
 
30
- /// Return a pointer to, but do not remove, the next item, if any.
31
- pub fn peek_ptr(self: *Self) ?*T {
28
+ pub inline fn tail(self: Self) ?T {
32
29
  if (self.empty()) return null;
33
- return &self.buffer[self.index];
30
+ return self.buffer[(self.index + self.count - 1) % self.buffer.len];
34
31
  }
35
32
 
36
- /// Remove and return the next item, if any.
37
- pub fn pop(self: *Self) ?T {
33
+ pub inline fn tail_ptr(self: *Self) ?*T {
38
34
  if (self.empty()) return null;
39
- defer {
40
- self.index = (self.index + 1) % self.buffer.len;
41
- self.count -= 1;
42
- }
43
- return self.buffer[self.index];
35
+ return &self.buffer[(self.index + self.count - 1) % self.buffer.len];
36
+ }
37
+
38
+ pub inline fn next_tail(self: Self) ?T {
39
+ if (self.full()) return null;
40
+ return self.buffer[(self.index + self.count) % self.buffer.len];
41
+ }
42
+
43
+ pub inline fn next_tail_ptr(self: *Self) ?*T {
44
+ if (self.full()) return null;
45
+ return &self.buffer[(self.index + self.count) % self.buffer.len];
46
+ }
47
+
48
+ pub inline fn advance_head(self: *Self) void {
49
+ self.index += 1;
50
+ self.index %= self.buffer.len;
51
+ self.count -= 1;
52
+ }
53
+
54
+ pub inline fn advance_tail(self: *Self) void {
55
+ assert(self.count < self.buffer.len);
56
+ self.count += 1;
44
57
  }
45
58
 
46
59
  /// Returns whether the ring buffer is completely full.
47
- pub fn full(self: *const Self) bool {
60
+ pub inline fn full(self: Self) bool {
48
61
  return self.count == self.buffer.len;
49
62
  }
50
63
 
51
64
  /// Returns whether the ring buffer is completely empty.
52
- pub fn empty(self: *const Self) bool {
65
+ pub inline fn empty(self: Self) bool {
53
66
  return self.count == 0;
54
67
  }
55
68
 
69
+ // Higher level, less error-prone wrappers:
70
+
71
+ /// Add an element to the RingBuffer. Returns an error if the buffer
72
+ /// is already full and the element could not be added.
73
+ pub fn push(self: *Self, item: T) error{NoSpaceLeft}!void {
74
+ const ptr = self.next_tail_ptr() orelse return error.NoSpaceLeft;
75
+ ptr.* = item;
76
+ self.advance_tail();
77
+ }
78
+
79
+ /// Remove and return the next item, if any.
80
+ pub fn pop(self: *Self) ?T {
81
+ const result = self.head() orelse return null;
82
+ self.advance_head();
83
+ return result;
84
+ }
85
+
56
86
  pub const Iterator = struct {
57
87
  ring: *Self,
58
88
  count: usize = 0,
59
89
 
60
90
  pub fn next(it: *Iterator) ?T {
61
- return (it.next_ptr() orelse return null).*;
91
+ assert(it.count <= it.ring.count);
92
+ if (it.count == it.ring.count) return null;
93
+ defer it.count += 1;
94
+ return it.ring.buffer[(it.ring.index + it.count) % it.ring.buffer.len];
62
95
  }
63
96
 
64
97
  pub fn next_ptr(it: *Iterator) ?*T {
@@ -79,43 +112,6 @@ pub fn RingBuffer(comptime T: type, comptime size: usize) type {
79
112
 
80
113
  const testing = std.testing;
81
114
 
82
- test "push/peek/pop/full/empty" {
83
- var fifo = RingBuffer(u32, 3){};
84
-
85
- try testing.expect(!fifo.full());
86
- try testing.expect(fifo.empty());
87
-
88
- try fifo.push(1);
89
- try testing.expectEqual(@as(?u32, 1), fifo.peek());
90
-
91
- try testing.expect(!fifo.full());
92
- try testing.expect(!fifo.empty());
93
-
94
- try fifo.push(2);
95
- try testing.expectEqual(@as(?u32, 1), fifo.peek());
96
-
97
- try fifo.push(3);
98
- try testing.expectError(error.NoSpaceLeft, fifo.push(4));
99
-
100
- try testing.expect(fifo.full());
101
- try testing.expect(!fifo.empty());
102
-
103
- try testing.expectEqual(@as(?u32, 1), fifo.peek());
104
- try testing.expectEqual(@as(?u32, 1), fifo.pop());
105
-
106
- try testing.expect(!fifo.full());
107
- try testing.expect(!fifo.empty());
108
-
109
- fifo.peek_ptr().?.* += 1000;
110
-
111
- try testing.expectEqual(@as(?u32, 1002), fifo.pop());
112
- try testing.expectEqual(@as(?u32, 3), fifo.pop());
113
- try testing.expectEqual(@as(?u32, null), fifo.pop());
114
-
115
- try testing.expect(!fifo.full());
116
- try testing.expect(fifo.empty());
117
- }
118
-
119
115
  fn test_iterator(comptime T: type, ring: *T, values: []const u32) !void {
120
116
  const ring_index = ring.index;
121
117
 
@@ -133,22 +129,41 @@ fn test_iterator(comptime T: type, ring: *T, values: []const u32) !void {
133
129
  try testing.expectEqual(ring_index, ring.index);
134
130
  }
135
131
 
136
- test "iterator" {
132
+ test "RingBuffer: low level interface" {
137
133
  const Ring = RingBuffer(u32, 2);
138
134
 
139
135
  var ring = Ring{};
140
136
  try test_iterator(Ring, &ring, &[_]u32{});
141
137
 
142
- try ring.push(0);
138
+ try testing.expectEqual(@as(?u32, null), ring.head());
139
+ try testing.expectEqual(@as(?*u32, null), ring.head_ptr());
140
+ try testing.expectEqual(@as(?u32, null), ring.tail());
141
+ try testing.expectEqual(@as(?*u32, null), ring.tail_ptr());
142
+
143
+ ring.next_tail_ptr().?.* = 0;
144
+ ring.advance_tail();
145
+ try testing.expectEqual(@as(?u32, 0), ring.tail());
146
+ try testing.expectEqual(@as(u32, 0), ring.tail_ptr().?.*);
143
147
  try test_iterator(Ring, &ring, &[_]u32{0});
144
148
 
145
- try ring.push(1);
149
+ ring.next_tail_ptr().?.* = 1;
150
+ ring.advance_tail();
151
+ try testing.expectEqual(@as(?u32, 1), ring.tail());
152
+ try testing.expectEqual(@as(u32, 1), ring.tail_ptr().?.*);
146
153
  try test_iterator(Ring, &ring, &[_]u32{ 0, 1 });
147
154
 
148
- try testing.expectEqual(@as(?u32, 0), ring.pop());
155
+ try testing.expectEqual(@as(?u32, null), ring.next_tail());
156
+ try testing.expectEqual(@as(?*u32, null), ring.next_tail_ptr());
157
+
158
+ try testing.expectEqual(@as(?u32, 0), ring.head());
159
+ try testing.expectEqual(@as(u32, 0), ring.head_ptr().?.*);
160
+ ring.advance_head();
149
161
  try test_iterator(Ring, &ring, &[_]u32{1});
150
162
 
151
- try ring.push(2);
163
+ ring.next_tail_ptr().?.* = 2;
164
+ ring.advance_tail();
165
+ try testing.expectEqual(@as(?u32, 2), ring.tail());
166
+ try testing.expectEqual(@as(u32, 2), ring.tail_ptr().?.*);
152
167
  try test_iterator(Ring, &ring, &[_]u32{ 1, 2 });
153
168
 
154
169
  var iterator = ring.iterator();
@@ -156,15 +171,67 @@ test "iterator" {
156
171
  item_ptr.* += 1000;
157
172
  }
158
173
 
159
- try testing.expectEqual(@as(?u32, 1001), ring.pop());
174
+ try testing.expectEqual(@as(?u32, 1001), ring.head());
175
+ try testing.expectEqual(@as(u32, 1001), ring.head_ptr().?.*);
176
+ ring.advance_head();
160
177
  try test_iterator(Ring, &ring, &[_]u32{1002});
161
178
 
162
- try ring.push(3);
179
+ ring.next_tail_ptr().?.* = 3;
180
+ ring.advance_tail();
181
+ try testing.expectEqual(@as(?u32, 3), ring.tail());
182
+ try testing.expectEqual(@as(u32, 3), ring.tail_ptr().?.*);
163
183
  try test_iterator(Ring, &ring, &[_]u32{ 1002, 3 });
164
184
 
165
- try testing.expectEqual(@as(?u32, 1002), ring.pop());
185
+ try testing.expectEqual(@as(?u32, 1002), ring.head());
186
+ try testing.expectEqual(@as(u32, 1002), ring.head_ptr().?.*);
187
+ ring.advance_head();
166
188
  try test_iterator(Ring, &ring, &[_]u32{3});
167
189
 
168
- try testing.expectEqual(@as(?u32, 3), ring.pop());
190
+ try testing.expectEqual(@as(?u32, 3), ring.head());
191
+ try testing.expectEqual(@as(u32, 3), ring.head_ptr().?.*);
192
+ ring.advance_head();
169
193
  try test_iterator(Ring, &ring, &[_]u32{});
194
+
195
+ try testing.expectEqual(@as(?u32, null), ring.head());
196
+ try testing.expectEqual(@as(?*u32, null), ring.head_ptr());
197
+ try testing.expectEqual(@as(?u32, null), ring.tail());
198
+ try testing.expectEqual(@as(?*u32, null), ring.tail_ptr());
199
+ }
200
+
201
+ test "RingBuffer: push/pop high level interface" {
202
+ var fifo = RingBuffer(u32, 3){};
203
+
204
+ try testing.expect(!fifo.full());
205
+ try testing.expect(fifo.empty());
206
+
207
+ try fifo.push(1);
208
+ try testing.expectEqual(@as(?u32, 1), fifo.head());
209
+
210
+ try testing.expect(!fifo.full());
211
+ try testing.expect(!fifo.empty());
212
+
213
+ try fifo.push(2);
214
+ try testing.expectEqual(@as(?u32, 1), fifo.head());
215
+
216
+ try fifo.push(3);
217
+ try testing.expectError(error.NoSpaceLeft, fifo.push(4));
218
+
219
+ try testing.expect(fifo.full());
220
+ try testing.expect(!fifo.empty());
221
+
222
+ try testing.expectEqual(@as(?u32, 1), fifo.head());
223
+ try testing.expectEqual(@as(?u32, 1), fifo.pop());
224
+
225
+ try testing.expect(!fifo.full());
226
+ try testing.expect(!fifo.empty());
227
+
228
+ try fifo.push(4);
229
+
230
+ try testing.expectEqual(@as(?u32, 2), fifo.pop());
231
+ try testing.expectEqual(@as(?u32, 3), fifo.pop());
232
+ try testing.expectEqual(@as(?u32, 4), fifo.pop());
233
+ try testing.expectEqual(@as(?u32, null), fifo.pop());
234
+
235
+ try testing.expect(!fifo.full());
236
+ try testing.expect(fifo.empty());
170
237
  }