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
@@ -87,7 +87,7 @@ pub const Command = union(enum) {
87
87
 
88
88
  /// Parse the command line arguments passed to the tigerbeetle binary.
89
89
  /// Exits the program with a non-zero exit code if an error is found.
90
- pub fn parse_args(allocator: *std.mem.Allocator) Command {
90
+ pub fn parse_args(allocator: std.mem.Allocator) Command {
91
91
  var maybe_cluster: ?[]const u8 = null;
92
92
  var maybe_replica: ?[]const u8 = null;
93
93
  var maybe_addresses: ?[]const u8 = null;
@@ -118,8 +118,10 @@ pub fn parse_args(allocator: *std.mem.Allocator) Command {
118
118
  } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
119
119
  std.io.getStdOut().writeAll(usage) catch os.exit(1);
120
120
  os.exit(0);
121
- } else {
121
+ } else if (mem.startsWith(u8, arg, "--")) {
122
122
  fatal("unexpected argument: '{s}'", .{arg});
123
+ } else {
124
+ fatal("unexpected argument: '{s}' (must start with '--')", .{arg});
123
125
  }
124
126
  }
125
127
 
@@ -130,7 +132,7 @@ pub fn parse_args(allocator: *std.mem.Allocator) Command {
130
132
  const replica = parse_replica(raw_replica);
131
133
 
132
134
  const dir_path = maybe_directory orelse config.directory;
133
- const dir_fd = os.openZ(dir_path, os.O_CLOEXEC | os.O_RDONLY, 0) catch |err|
135
+ const dir_fd = os.openZ(dir_path, os.O.CLOEXEC | os.O.RDONLY, 0) catch |err|
134
136
  fatal("failed to open directory '{s}': {}", .{ dir_path, err });
135
137
 
136
138
  switch (command) {
@@ -193,7 +195,7 @@ fn parse_cluster(raw_cluster: []const u8) u32 {
193
195
  }
194
196
 
195
197
  /// Parse and allocate the addresses returning a slice into that array.
196
- fn parse_addresses(allocator: *std.mem.Allocator, raw_addresses: []const u8) []net.Address {
198
+ fn parse_addresses(allocator: std.mem.Allocator, raw_addresses: []const u8) []net.Address {
197
199
  return vsr.parse_addresses(allocator, raw_addresses) catch |err| switch (err) {
198
200
  error.AddressHasTrailingComma => fatal("--addresses: invalid trailing comma", .{}),
199
201
  error.AddressLimitExceeded => {
@@ -1,8 +1,8 @@
1
1
  /// Whether development or production:
2
2
  pub const deployment_environment = .development;
3
3
 
4
- /// The maximum log level in increasing order of verbosity (emergency=0, debug=7):
5
- pub const log_level = 6;
4
+ /// The maximum log level in increasing order of verbosity (emergency=0, debug=3):
5
+ pub const log_level = 2;
6
6
 
7
7
  /// The maximum number of replicas allowed in a cluster.
8
8
  pub const replicas_max = 6;
@@ -1,7 +1,16 @@
1
1
  const std = @import("std");
2
2
  const assert = std.debug.assert;
3
3
 
4
- usingnamespace @import("tigerbeetle.zig");
4
+ const config = @import("config.zig");
5
+
6
+ const tb = @import("tigerbeetle.zig");
7
+ const Account = tb.Account;
8
+ const Transfer = tb.Transfer;
9
+ const Commit = tb.Commit;
10
+
11
+ const CreateAccountsResult = tb.CreateAccountsResult;
12
+ const CreateTransfersResult = tb.CreateTransfersResult;
13
+ const CommitTransfersResult = tb.CommitTransfersResult;
5
14
 
6
15
  const IO = @import("io.zig").IO;
7
16
  const MessageBus = @import("message_bus.zig").MessageBusClient;
@@ -13,96 +22,117 @@ const Client = vsr.Client(StateMachine, MessageBus);
13
22
 
14
23
  pub const log_level: std.log.Level = .alert;
15
24
 
16
- pub const Demo = struct {
17
- pub fn request(
18
- operation: StateMachine.Operation,
19
- batch: anytype,
20
- on_reply: fn (
21
- user_data: u128,
22
- operation: StateMachine.Operation,
23
- results: Client.Error![]const u8,
24
- ) void,
25
- ) !void {
26
- const allocator = std.heap.page_allocator;
27
- const client_id = std.crypto.random.int(u128);
28
- const cluster_id: u32 = 0;
29
- var addresses = [_]std.net.Address{try std.net.Address.parseIp4("127.0.0.1", config.port)};
30
-
31
- var io = try IO.init(32, 0);
32
- defer io.deinit();
33
-
34
- var message_bus = try MessageBus.init(allocator, cluster_id, &addresses, client_id, &io);
35
- defer message_bus.deinit();
36
-
37
- var client = try Client.init(
38
- allocator,
39
- client_id,
40
- cluster_id,
41
- @intCast(u8, addresses.len),
42
- &message_bus,
43
- );
44
- defer client.deinit();
45
-
46
- message_bus.set_on_message(*Client, &client, Client.on_message);
47
-
48
- var message = client.get_message() orelse unreachable;
49
- defer client.unref(message);
50
-
51
- const body = std.mem.asBytes(&batch);
52
- std.mem.copy(u8, message.buffer[@sizeOf(Header)..], body);
53
-
54
- client.request(
55
- 0,
56
- on_reply,
57
- operation,
58
- message,
59
- body.len,
60
- );
61
-
62
- while (client.request_queue.count > 0) {
63
- client.tick();
64
- try io.run_for_ns(config.tick_ms * std.time.ns_per_ms);
65
- }
66
- }
67
-
68
- pub fn on_create_accounts(
25
+ pub fn request(
26
+ operation: StateMachine.Operation,
27
+ batch: anytype,
28
+ on_reply: fn (
69
29
  user_data: u128,
70
30
  operation: StateMachine.Operation,
71
31
  results: Client.Error![]const u8,
72
- ) void {
73
- print_results(CreateAccountsResult, results);
32
+ ) void,
33
+ ) !void {
34
+ const allocator = std.heap.page_allocator;
35
+ const client_id = std.crypto.random.int(u128);
36
+ const cluster_id: u32 = 0;
37
+ var addresses = [_]std.net.Address{try std.net.Address.parseIp4("127.0.0.1", config.port)};
38
+
39
+ var io = try IO.init(32, 0);
40
+ defer io.deinit();
41
+
42
+ var message_bus = try MessageBus.init(allocator, cluster_id, &addresses, client_id, &io);
43
+ defer message_bus.deinit();
44
+
45
+ var client = try Client.init(
46
+ allocator,
47
+ client_id,
48
+ cluster_id,
49
+ @intCast(u8, addresses.len),
50
+ &message_bus,
51
+ );
52
+ defer client.deinit();
53
+
54
+ message_bus.set_on_message(*Client, &client, Client.on_message);
55
+
56
+ var message = client.get_message() orelse unreachable;
57
+ defer client.unref(message);
58
+
59
+ const body = std.mem.asBytes(&batch);
60
+ std.mem.copy(u8, message.buffer[@sizeOf(Header)..], body);
61
+
62
+ client.request(
63
+ 0,
64
+ on_reply,
65
+ operation,
66
+ message,
67
+ body.len,
68
+ );
69
+
70
+ while (client.request_queue.count > 0) {
71
+ client.tick();
72
+ try io.run_for_ns(config.tick_ms * std.time.ns_per_ms);
74
73
  }
75
-
76
- pub fn on_lookup_accounts(
77
- user_data: u128,
78
- operation: StateMachine.Operation,
79
- results: Client.Error![]const u8,
80
- ) void {
81
- print_results(Account, results);
82
- }
83
-
84
- pub fn on_create_transfers(
85
- user_data: u128,
86
- operation: StateMachine.Operation,
87
- results: Client.Error![]const u8,
88
- ) void {
89
- print_results(CreateTransfersResult, results);
90
- }
91
-
92
- pub fn on_commit_transfers(
93
- user_data: u128,
94
- operation: StateMachine.Operation,
95
- results: Client.Error![]const u8,
96
- ) void {
97
- print_results(CommitTransfersResult, results);
98
- }
99
-
100
- fn print_results(comptime Results: type, results: Client.Error![]const u8) void {
101
- const body = results catch unreachable;
102
- const slice = std.mem.bytesAsSlice(Results, body);
103
- for (slice) |result| {
104
- std.debug.print("{}\n", .{result});
105
- }
106
- if (slice.len == 0) std.debug.print("OK\n", .{});
74
+ }
75
+
76
+ pub fn on_create_accounts(
77
+ user_data: u128,
78
+ operation: StateMachine.Operation,
79
+ results: Client.Error![]const u8,
80
+ ) void {
81
+ _ = user_data;
82
+ _ = operation;
83
+
84
+ print_results(CreateAccountsResult, results);
85
+ }
86
+
87
+ pub fn on_lookup_accounts(
88
+ user_data: u128,
89
+ operation: StateMachine.Operation,
90
+ results: Client.Error![]const u8,
91
+ ) void {
92
+ _ = user_data;
93
+ _ = operation;
94
+
95
+ print_results(Account, results);
96
+ }
97
+
98
+ pub fn on_lookup_transfers(
99
+ user_data: u128,
100
+ operation: StateMachine.Operation,
101
+ results: Client.Error![]const u8,
102
+ ) void {
103
+ _ = user_data;
104
+ _ = operation;
105
+
106
+ print_results(Transfer, results);
107
+ }
108
+
109
+ pub fn on_create_transfers(
110
+ user_data: u128,
111
+ operation: StateMachine.Operation,
112
+ results: Client.Error![]const u8,
113
+ ) void {
114
+ _ = user_data;
115
+ _ = operation;
116
+
117
+ print_results(CreateTransfersResult, results);
118
+ }
119
+
120
+ pub fn on_commit_transfers(
121
+ user_data: u128,
122
+ operation: StateMachine.Operation,
123
+ results: Client.Error![]const u8,
124
+ ) void {
125
+ _ = user_data;
126
+ _ = operation;
127
+
128
+ print_results(CommitTransfersResult, results);
129
+ }
130
+
131
+ fn print_results(comptime Results: type, results: Client.Error![]const u8) void {
132
+ const body = results catch unreachable;
133
+ const slice = std.mem.bytesAsSlice(Results, body);
134
+ for (slice) |result| {
135
+ std.debug.print("{}\n", .{result});
107
136
  }
108
- };
137
+ if (slice.len == 0) std.debug.print("OK\n", .{});
138
+ }
@@ -1,5 +1,7 @@
1
- usingnamespace @import("tigerbeetle.zig");
2
- usingnamespace @import("demo.zig");
1
+ const tb = @import("tigerbeetle.zig");
2
+ const demo = @import("demo.zig");
3
+
4
+ const Account = tb.Account;
3
5
 
4
6
  pub fn main() !void {
5
7
  const accounts = [_]Account{
@@ -29,5 +31,5 @@ pub fn main() !void {
29
31
  },
30
32
  };
31
33
 
32
- try Demo.request(.create_accounts, accounts, Demo.on_create_accounts);
34
+ try demo.request(.create_accounts, accounts, demo.on_create_accounts);
33
35
  }
@@ -1,8 +1,7 @@
1
- usingnamespace @import("tigerbeetle.zig");
2
- usingnamespace @import("demo.zig");
1
+ const demo = @import("demo.zig");
3
2
 
4
3
  pub fn main() !void {
5
4
  const ids = [_]u128{ 1, 2 };
6
5
 
7
- try Demo.request(.lookup_accounts, ids, Demo.on_lookup_accounts);
6
+ try demo.request(.lookup_accounts, ids, demo.on_lookup_accounts);
8
7
  }
@@ -1,5 +1,7 @@
1
- usingnamespace @import("tigerbeetle.zig");
2
- usingnamespace @import("demo.zig");
1
+ const tb = @import("tigerbeetle.zig");
2
+ const demo = @import("demo.zig");
3
+
4
+ const Transfer = tb.Transfer;
3
5
 
4
6
  pub fn main() !void {
5
7
  const transfers = [_]Transfer{
@@ -16,5 +18,5 @@ pub fn main() !void {
16
18
  },
17
19
  };
18
20
 
19
- try Demo.request(.create_transfers, transfers, Demo.on_create_transfers);
21
+ try demo.request(.create_transfers, transfers, demo.on_create_transfers);
20
22
  }
@@ -1,7 +1,9 @@
1
1
  const std = @import("std");
2
2
 
3
- usingnamespace @import("tigerbeetle.zig");
4
- usingnamespace @import("demo.zig");
3
+ const tb = @import("tigerbeetle.zig");
4
+ const demo = @import("demo.zig");
5
+
6
+ const Transfer = tb.Transfer;
5
7
 
6
8
  pub fn main() !void {
7
9
  const transfers = [_]Transfer{
@@ -49,5 +51,5 @@ pub fn main() !void {
49
51
  },
50
52
  };
51
53
 
52
- try Demo.request(.create_transfers, transfers, Demo.on_create_transfers);
54
+ try demo.request(.create_transfers, transfers, demo.on_create_transfers);
53
55
  }
@@ -1,5 +1,7 @@
1
- usingnamespace @import("tigerbeetle.zig");
2
- usingnamespace @import("demo.zig");
1
+ const tb = @import("tigerbeetle.zig");
2
+ const demo = @import("demo.zig");
3
+
4
+ const Commit = tb.Commit;
3
5
 
4
6
  pub fn main() !void {
5
7
  const commits = [_]Commit{
@@ -17,5 +19,5 @@ pub fn main() !void {
17
19
  },
18
20
  };
19
21
 
20
- try Demo.request(.commit_transfers, commits, Demo.on_commit_transfers);
22
+ try demo.request(.commit_transfers, commits, demo.on_commit_transfers);
21
23
  }
@@ -1,5 +1,7 @@
1
- usingnamespace @import("tigerbeetle.zig");
2
- usingnamespace @import("demo.zig");
1
+ const tb = @import("tigerbeetle.zig");
2
+ const demo = @import("demo.zig");
3
+
4
+ const Commit = tb.Commit;
3
5
 
4
6
  pub fn main() !void {
5
7
  const commits = [_]Commit{
@@ -11,5 +13,5 @@ pub fn main() !void {
11
13
  },
12
14
  };
13
15
 
14
- try Demo.request(.commit_transfers, commits, Demo.on_commit_transfers);
16
+ try demo.request(.commit_transfers, commits, demo.on_commit_transfers);
15
17
  }
@@ -0,0 +1,7 @@
1
+ const demo = @import("demo.zig");
2
+
3
+ pub fn main() !void {
4
+ const ids = [_]u128{ 1000, 1001, 1002 };
5
+
6
+ try demo.request(.lookup_transfers, ids, demo.on_lookup_transfers);
7
+ }
@@ -0,0 +1,238 @@
1
+ const std = @import("std");
2
+ const os = std.os;
3
+ const assert = std.debug.assert;
4
+
5
+ const Time = @import("../time.zig").Time;
6
+ const IO = @import("../io.zig").IO;
7
+
8
+ // 1 MB: larger than socket buffer so forces io_pending on darwin
9
+ // Configure this value to smaller amounts to test IO scheduling overhead
10
+ const buffer_size = 1 * 1024 * 1024;
11
+
12
+ // max time for the benchmark to run
13
+ const run_duration = 1 * std.time.ns_per_s;
14
+
15
+ pub fn main() !void {
16
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
17
+ const allocator = &gpa.allocator;
18
+ defer {
19
+ const leaks = gpa.deinit();
20
+ assert(!leaks);
21
+ }
22
+
23
+ const buffer = try allocator.alloc(u8, buffer_size * 2);
24
+ defer allocator.free(buffer);
25
+ std.mem.set(u8, buffer, 0);
26
+
27
+ var timer = Time{};
28
+ const started = timer.monotonic();
29
+ var self = Context{
30
+ .io = try IO.init(32, 0),
31
+ .timer = &timer,
32
+ .started = started,
33
+ .current = started,
34
+ .tx = .{ .buffer = buffer[0 * buffer_size ..][0..buffer_size] },
35
+ .rx = .{ .buffer = buffer[1 * buffer_size ..][0..buffer_size] },
36
+ };
37
+
38
+ defer {
39
+ self.io.deinit();
40
+ const elapsed_ns = self.current - started;
41
+ const transferred_mb = @intToFloat(f64, self.transferred) / 1024 / 1024;
42
+
43
+ std.debug.print("IO throughput test: took {}ms @ {d:.2} MB/s\n", .{
44
+ elapsed_ns / std.time.ns_per_ms,
45
+ transferred_mb / (@intToFloat(f64, elapsed_ns) / std.time.ns_per_s),
46
+ });
47
+ }
48
+
49
+ // Setup the server socket
50
+ self.server.fd = try IO.openSocket(os.AF_INET, os.SOCK_STREAM, os.IPPROTO_TCP);
51
+ defer os.closeSocket(self.server.fd);
52
+
53
+ const address = try std.net.Address.parseIp4("127.0.0.1", 3131);
54
+ try os.setsockopt(
55
+ self.server.fd,
56
+ os.SOL_SOCKET,
57
+ os.SO_REUSEADDR,
58
+ &std.mem.toBytes(@as(c_int, 1)),
59
+ );
60
+ try os.bind(self.server.fd, &address.any, address.getOsSockLen());
61
+ try os.listen(self.server.fd, 1);
62
+
63
+ // Start accepting the client
64
+ self.io.accept(
65
+ *Context,
66
+ &self,
67
+ Context.on_accept,
68
+ &self.server.completion,
69
+ self.server.fd,
70
+ );
71
+
72
+ // Setup the client connection
73
+ self.tx.socket.fd = try IO.openSocket(os.AF_INET, os.SOCK_STREAM, os.IPPROTO_TCP);
74
+ defer os.closeSocket(self.tx.socket.fd);
75
+
76
+ self.io.connect(
77
+ *Context,
78
+ &self,
79
+ Context.on_connect,
80
+ &self.tx.socket.completion,
81
+ self.tx.socket.fd,
82
+ address,
83
+ );
84
+
85
+ // Run the IO loop, calling either tick() or run_for_ns() at "pseudo-random"
86
+ // to benchmark each io-driving execution path
87
+ var tick: usize = 0xdeadbeef;
88
+ while (self.is_running()) : (tick +%= 1) {
89
+ if (tick % 61 == 0) {
90
+ const timeout_ns = tick % (10 * std.time.ns_per_ms);
91
+ try self.io.run_for_ns(@intCast(u63, timeout_ns));
92
+ } else {
93
+ try self.io.tick();
94
+ }
95
+ }
96
+
97
+ // Assert that everything is connected
98
+ assert(self.server.fd != -1);
99
+ assert(self.tx.socket.fd != -1);
100
+ assert(self.rx.socket.fd != -1);
101
+
102
+ // Close the accepted client socket.
103
+ // The actual client socket + server socket are closed by defer
104
+ os.closeSocket(self.rx.socket.fd);
105
+ }
106
+
107
+ const Context = struct {
108
+ io: IO,
109
+ tx: Pipe,
110
+ rx: Pipe,
111
+ timer: *Time,
112
+ started: u64,
113
+ current: u64,
114
+ server: Socket = .{},
115
+ transferred: u64 = 0,
116
+
117
+ const Socket = struct {
118
+ fd: os.socket_t = -1,
119
+ completion: IO.Completion = undefined,
120
+ };
121
+ const Pipe = struct {
122
+ socket: Socket = .{},
123
+ buffer: []u8,
124
+ transferred: usize = 0,
125
+ };
126
+
127
+ fn is_running(self: Context) bool {
128
+ // Make sure that we're connected
129
+ if (self.rx.socket.fd == -1) return true;
130
+
131
+ // Make sure that we haven't run too long as configured
132
+ const elapsed = self.current - self.started;
133
+ return elapsed < run_duration;
134
+ }
135
+
136
+ fn on_accept(
137
+ self: *Context,
138
+ completion: *IO.Completion,
139
+ result: IO.AcceptError!os.socket_t,
140
+ ) void {
141
+ assert(self.rx.socket.fd == -1);
142
+ assert(&self.server.completion == completion);
143
+ self.rx.socket.fd = result catch |err| std.debug.panic("accept error {}", .{err});
144
+
145
+ // Start reading data from the accepted client socket
146
+ assert(self.rx.transferred == 0);
147
+ self.do_transfer("rx", .read, 0);
148
+ }
149
+
150
+ fn on_connect(
151
+ self: *Context,
152
+ completion: *IO.Completion,
153
+ result: IO.ConnectError!void,
154
+ ) void {
155
+ assert(self.tx.socket.fd != -1);
156
+ assert(&self.tx.socket.completion == completion);
157
+
158
+ // Start sending data to the server's accepted client
159
+ assert(self.tx.transferred == 0);
160
+ self.do_transfer("tx", .write, 0);
161
+ }
162
+
163
+ const TransferType = enum {
164
+ read = 0,
165
+ write = 1,
166
+ };
167
+
168
+ fn do_transfer(
169
+ self: *Context,
170
+ comptime pipe_name: []const u8,
171
+ comptime transfer_type: TransferType,
172
+ bytes: usize,
173
+ ) void {
174
+ // The type of IO to perform and what type of IO to perform next (after the current one completes).
175
+ const transfer_info = switch (transfer_type) {
176
+ .read => .{
177
+ .IoError = IO.RecvError,
178
+ .io_func = "recv",
179
+ .next = TransferType.write,
180
+ },
181
+ .write => .{
182
+ .IoError = IO.SendError,
183
+ .io_func = "send",
184
+ .next = TransferType.read,
185
+ },
186
+ };
187
+
188
+ assert(bytes <= buffer_size);
189
+ self.transferred += bytes;
190
+
191
+ // Check in with the benchmark timer to stop sending/receiving data
192
+ self.current = self.timer.monotonic();
193
+ if (!self.is_running())
194
+ return;
195
+
196
+ // Select which connection (tx or rx) depending on the type of transfer
197
+ const pipe = &@field(self, pipe_name);
198
+ pipe.transferred += bytes;
199
+ assert(pipe.transferred <= pipe.buffer.len);
200
+
201
+ // There's still more data to transfer on the connection
202
+ if (pipe.transferred < pipe.buffer.len) {
203
+ // Callback which calls this function again when data is transferred.
204
+ // Effectively loops back above.
205
+ const on_transfer = struct {
206
+ fn on_transfer(
207
+ _self: *Context,
208
+ completion: *IO.Completion,
209
+ result: transfer_info.IoError!usize,
210
+ ) void {
211
+ const _bytes = result catch |err| {
212
+ std.debug.panic("{s} error: {}", .{ transfer_info.io_func, err });
213
+ };
214
+ assert(&@field(_self, pipe_name).socket.completion == completion);
215
+ _self.do_transfer(pipe_name, transfer_type, _bytes);
216
+ }
217
+ }.on_transfer;
218
+
219
+ // Perform the IO with the callback for the completion
220
+ return @field(self.io, transfer_info.io_func)(
221
+ *Context,
222
+ self,
223
+ on_transfer,
224
+ &pipe.socket.completion,
225
+ pipe.socket.fd,
226
+ pipe.buffer[pipe.transferred..],
227
+ );
228
+ }
229
+
230
+ // This transfer type completed transferring all the bytes.
231
+ // Now, switch the transfer type (transfer_info.next).
232
+ // This means if we read to the buffer, now we write it out.
233
+ // Inversely, if we wrote the buffer, now we read it back.
234
+ // This is basically a modified echo benchmark.
235
+ pipe.transferred = 0;
236
+ self.do_transfer(pipe_name, transfer_info.next, 0);
237
+ }
238
+ };