tigerbeetle-node 0.9.0 → 0.11.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 (112) hide show
  1. package/README.md +305 -103
  2. package/dist/index.d.ts +70 -67
  3. package/dist/index.js +70 -67
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -6
  6. package/scripts/download_node_headers.sh +14 -7
  7. package/src/index.ts +11 -10
  8. package/src/node.zig +22 -20
  9. package/src/tigerbeetle/scripts/benchmark.bat +4 -3
  10. package/src/tigerbeetle/scripts/benchmark.sh +25 -10
  11. package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
  12. package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
  13. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
  14. package/src/tigerbeetle/scripts/install.sh +20 -4
  15. package/src/tigerbeetle/scripts/install_zig.bat +5 -1
  16. package/src/tigerbeetle/scripts/install_zig.sh +32 -26
  17. package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
  18. package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
  19. package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
  20. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
  21. package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +12 -3
  22. package/src/tigerbeetle/src/benchmark.zig +19 -9
  23. package/src/tigerbeetle/src/benchmark_array_search.zig +317 -0
  24. package/src/tigerbeetle/src/benchmarks/perf.zig +299 -0
  25. package/src/tigerbeetle/src/c/tb_client/context.zig +103 -0
  26. package/src/tigerbeetle/src/c/tb_client/packet.zig +80 -0
  27. package/src/tigerbeetle/src/c/tb_client/signal.zig +288 -0
  28. package/src/tigerbeetle/src/c/tb_client/thread.zig +328 -0
  29. package/src/tigerbeetle/src/c/tb_client.h +221 -0
  30. package/src/tigerbeetle/src/c/tb_client.zig +104 -0
  31. package/src/tigerbeetle/src/c/test.zig +1 -0
  32. package/src/tigerbeetle/src/cli.zig +143 -84
  33. package/src/tigerbeetle/src/config.zig +161 -20
  34. package/src/tigerbeetle/src/demo.zig +14 -8
  35. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +2 -2
  36. package/src/tigerbeetle/src/ewah.zig +318 -0
  37. package/src/tigerbeetle/src/ewah_benchmark.zig +121 -0
  38. package/src/tigerbeetle/src/eytzinger_benchmark.zig +317 -0
  39. package/src/tigerbeetle/src/fifo.zig +17 -1
  40. package/src/tigerbeetle/src/io/darwin.zig +12 -10
  41. package/src/tigerbeetle/src/io/linux.zig +25 -9
  42. package/src/tigerbeetle/src/io/windows.zig +13 -9
  43. package/src/tigerbeetle/src/iops.zig +101 -0
  44. package/src/tigerbeetle/src/lsm/README.md +214 -0
  45. package/src/tigerbeetle/src/lsm/binary_search.zig +341 -0
  46. package/src/tigerbeetle/src/lsm/bloom_filter.zig +125 -0
  47. package/src/tigerbeetle/src/lsm/compaction.zig +557 -0
  48. package/src/tigerbeetle/src/lsm/composite_key.zig +77 -0
  49. package/src/tigerbeetle/src/lsm/direction.zig +11 -0
  50. package/src/tigerbeetle/src/lsm/eytzinger.zig +587 -0
  51. package/src/tigerbeetle/src/lsm/forest.zig +204 -0
  52. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
  53. package/src/tigerbeetle/src/lsm/grid.zig +549 -0
  54. package/src/tigerbeetle/src/lsm/groove.zig +1002 -0
  55. package/src/tigerbeetle/src/lsm/k_way_merge.zig +474 -0
  56. package/src/tigerbeetle/src/lsm/level_iterator.zig +315 -0
  57. package/src/tigerbeetle/src/lsm/manifest.zig +580 -0
  58. package/src/tigerbeetle/src/lsm/manifest_level.zig +925 -0
  59. package/src/tigerbeetle/src/lsm/manifest_log.zig +953 -0
  60. package/src/tigerbeetle/src/lsm/node_pool.zig +231 -0
  61. package/src/tigerbeetle/src/lsm/posted_groove.zig +387 -0
  62. package/src/tigerbeetle/src/lsm/segmented_array.zig +1318 -0
  63. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
  64. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
  65. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +894 -0
  66. package/src/tigerbeetle/src/lsm/table.zig +967 -0
  67. package/src/tigerbeetle/src/lsm/table_immutable.zig +203 -0
  68. package/src/tigerbeetle/src/lsm/table_iterator.zig +306 -0
  69. package/src/tigerbeetle/src/lsm/table_mutable.zig +174 -0
  70. package/src/tigerbeetle/src/lsm/test.zig +423 -0
  71. package/src/tigerbeetle/src/lsm/tree.zig +1090 -0
  72. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
  73. package/src/tigerbeetle/src/main.zig +141 -109
  74. package/src/tigerbeetle/src/message_bus.zig +49 -48
  75. package/src/tigerbeetle/src/message_pool.zig +22 -12
  76. package/src/tigerbeetle/src/ring_buffer.zig +126 -30
  77. package/src/tigerbeetle/src/simulator.zig +205 -140
  78. package/src/tigerbeetle/src/state_machine.zig +1268 -721
  79. package/src/tigerbeetle/src/static_allocator.zig +65 -0
  80. package/src/tigerbeetle/src/storage.zig +40 -14
  81. package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
  82. package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
  83. package/src/tigerbeetle/src/test/cluster.zig +104 -88
  84. package/src/tigerbeetle/src/test/conductor.zig +365 -0
  85. package/src/tigerbeetle/src/test/fuzz.zig +121 -0
  86. package/src/tigerbeetle/src/test/id.zig +89 -0
  87. package/src/tigerbeetle/src/test/message_bus.zig +15 -24
  88. package/src/tigerbeetle/src/test/network.zig +26 -17
  89. package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
  90. package/src/tigerbeetle/src/test/state_checker.zig +94 -68
  91. package/src/tigerbeetle/src/test/state_machine.zig +135 -69
  92. package/src/tigerbeetle/src/test/storage.zig +78 -28
  93. package/src/tigerbeetle/src/tigerbeetle.zig +19 -16
  94. package/src/tigerbeetle/src/unit_tests.zig +15 -0
  95. package/src/tigerbeetle/src/util.zig +51 -0
  96. package/src/tigerbeetle/src/vopr.zig +494 -0
  97. package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
  98. package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
  99. package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
  100. package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
  101. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
  102. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
  103. package/src/tigerbeetle/src/vsr/client.zig +34 -7
  104. package/src/tigerbeetle/src/vsr/journal.zig +164 -174
  105. package/src/tigerbeetle/src/vsr/replica.zig +1602 -651
  106. package/src/tigerbeetle/src/vsr/superblock.zig +1761 -0
  107. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +255 -0
  108. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +644 -0
  109. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +561 -0
  110. package/src/tigerbeetle/src/vsr.zig +118 -170
  111. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  112. package/src/tigerbeetle/scripts/vopr.sh +0 -33
@@ -0,0 +1,288 @@
1
+ const std = @import("std");
2
+ const builtin = @import("builtin");
3
+ const IO = @import("../../io.zig").IO;
4
+
5
+ const os = std.os;
6
+ const assert = std.debug.assert;
7
+ const Atomic = std.atomic.Atomic;
8
+
9
+ const log = std.log.scoped(.tb_client_signal);
10
+
11
+ /// A Signal is a way to trigger a registered callback on a tigerbeetle IO instace
12
+ /// when notification occurs from another thread.
13
+ /// It does this by using OS sockets (which are thread safe)
14
+ /// to resolve IO.Completions on the tigerbeetle thread.
15
+ ///
16
+ /// TODO: implement a simpler version of this eventually..
17
+ pub const Signal = struct {
18
+ io: *IO,
19
+ server_socket: os.socket_t,
20
+ accept_socket: os.socket_t,
21
+ connect_socket: os.socket_t,
22
+
23
+ completion: IO.Completion,
24
+ recv_buffer: [1]u8,
25
+ send_buffer: [1]u8,
26
+
27
+ on_signal_fn: fn (*Signal) void,
28
+ state: Atomic(enum(u8) {
29
+ running,
30
+ waiting,
31
+ notified,
32
+ shutdown,
33
+ }),
34
+
35
+ pub fn init(self: *Signal, io: *IO, on_signal_fn: fn (*Signal) void) !void {
36
+ self.io = io;
37
+ self.server_socket = os.socket(
38
+ os.AF.INET,
39
+ os.SOCK.STREAM | os.SOCK.NONBLOCK,
40
+ os.IPPROTO.TCP,
41
+ ) catch |err| {
42
+ log.err("failed to create signal server socket: {}", .{err});
43
+ return switch (err) {
44
+ error.PermissionDenied, error.ProtocolNotSupported, error.SocketTypeNotSupported, error.AddressFamilyNotSupported, error.ProtocolFamilyNotAvailable => error.NetworkSubsystemFailed,
45
+ error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, error.SystemResources => error.SystemResources,
46
+ error.Unexpected => error.Unexpected,
47
+ };
48
+ };
49
+ errdefer os.closeSocket(self.server_socket);
50
+
51
+ // Windows requires that the socket is bound before listening
52
+ if (builtin.target.os.tag == .windows) {
53
+ var addr = std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 0); // zero port lets the OS choose
54
+ os.bind(self.server_socket, &addr.any, addr.getOsSockLen()) catch |err| {
55
+ log.err("failed to bind the server socket to a local random port: {}", .{err});
56
+ return switch (err) {
57
+ error.AccessDenied => unreachable,
58
+ error.AlreadyBound => unreachable,
59
+ error.AddressInUse, error.AddressNotAvailable => unreachable,
60
+ error.SymLinkLoop => unreachable,
61
+ error.NameTooLong => unreachable,
62
+ error.FileNotFound, error.FileDescriptorNotASocket => unreachable,
63
+ error.NotDir => unreachable,
64
+ error.ReadOnlyFileSystem => unreachable,
65
+ error.SystemResources, error.NetworkSubsystemFailed, error.Unexpected => |e| e,
66
+ };
67
+ };
68
+ }
69
+
70
+ os.listen(self.server_socket, 1) catch |err| {
71
+ log.err("failed to listen on signal server socket: {}", .{err});
72
+ return switch (err) {
73
+ error.AddressInUse => unreachable,
74
+ error.FileDescriptorNotASocket => unreachable,
75
+ error.AlreadyConnected => unreachable,
76
+ error.SocketNotBound => unreachable,
77
+ error.OperationNotSupported, error.NetworkSubsystemFailed => error.NetworkSubsystemFailed,
78
+ error.SystemResources => error.SystemResources,
79
+ error.Unexpected => error.Unexpected,
80
+ };
81
+ };
82
+
83
+ var addr = std.net.Address.initIp4(undefined, undefined);
84
+ var addr_len = addr.getOsSockLen();
85
+ os.getsockname(self.server_socket, &addr.any, &addr_len) catch |err| {
86
+ log.err("failed to get address of signal server socket: {}", .{err});
87
+ return switch (err) {
88
+ error.SocketNotBound => unreachable,
89
+ error.FileDescriptorNotASocket => unreachable,
90
+ error.SystemResources => error.SystemResources,
91
+ error.NetworkSubsystemFailed => error.NetworkSubsystemFailed,
92
+ error.Unexpected => error.Unexpected,
93
+ };
94
+ };
95
+
96
+ self.connect_socket = self.io.open_socket(
97
+ os.AF.INET,
98
+ os.SOCK.STREAM,
99
+ os.IPPROTO.TCP,
100
+ ) catch |err| {
101
+ log.err("failed to create signal connect socket: {}", .{err});
102
+ return error.Unexpected;
103
+ };
104
+ errdefer os.closeSocket(self.connect_socket);
105
+
106
+ // Tracks when the connect_socket connects to the server_socket
107
+ const DoConnect = struct {
108
+ result: IO.ConnectError!void = undefined,
109
+ completion: IO.Completion = undefined,
110
+ is_connected: bool = false,
111
+
112
+ fn on_connect(
113
+ do_connect: *@This(),
114
+ _completion: *IO.Completion,
115
+ result: IO.ConnectError!void,
116
+ ) void {
117
+ assert(&do_connect.completion == _completion);
118
+ assert(!do_connect.is_connected);
119
+ do_connect.is_connected = true;
120
+ do_connect.result = result;
121
+ }
122
+ };
123
+
124
+ var do_connect = DoConnect{};
125
+ self.io.connect(
126
+ *DoConnect,
127
+ &do_connect,
128
+ DoConnect.on_connect,
129
+ &do_connect.completion,
130
+ self.connect_socket,
131
+ addr,
132
+ );
133
+
134
+ // Wait for the connect_socket to connect to the server_socket.
135
+ self.accept_socket = IO.INVALID_SOCKET;
136
+ while (!do_connect.is_connected or self.accept_socket == IO.INVALID_SOCKET) {
137
+ self.io.tick() catch |err| {
138
+ log.err("failed to tick IO when setting up signal: {}", .{err});
139
+ return error.Unexpected;
140
+ };
141
+
142
+ // Try to accept the connection from the connect_socket as the accept_socket
143
+ if (self.accept_socket == IO.INVALID_SOCKET) {
144
+ self.accept_socket = os.accept(self.server_socket, null, null, 0) catch |e| switch (e) {
145
+ error.WouldBlock => continue,
146
+ error.ConnectionAborted => unreachable,
147
+ error.ConnectionResetByPeer => unreachable,
148
+ error.FileDescriptorNotASocket => unreachable,
149
+ error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, error.SystemResources => return error.SystemResources,
150
+ error.SocketNotListening => unreachable,
151
+ error.BlockedByFirewall => unreachable,
152
+ error.ProtocolFailure, error.OperationNotSupported, error.NetworkSubsystemFailed => return error.NetworkSubsystemFailed,
153
+ error.Unexpected => return error.Unexpected,
154
+ };
155
+ }
156
+ }
157
+
158
+ _ = do_connect.result catch |err| {
159
+ log.err("failed to connect on signal client socket: {}", .{err});
160
+ return error.Unexpected;
161
+ };
162
+
163
+ assert(do_connect.is_connected);
164
+ assert(self.accept_socket != IO.INVALID_SOCKET);
165
+ assert(self.connect_socket != IO.INVALID_SOCKET);
166
+
167
+ self.completion = undefined;
168
+ self.recv_buffer = undefined;
169
+ self.send_buffer = undefined;
170
+
171
+ self.state = @TypeOf(self.state).init(.running);
172
+ self.on_signal_fn = on_signal_fn;
173
+ self.wait();
174
+ }
175
+
176
+ pub fn deinit(self: *Signal) void {
177
+ os.closeSocket(self.server_socket);
178
+ os.closeSocket(self.accept_socket);
179
+ os.closeSocket(self.connect_socket);
180
+ }
181
+
182
+ /// Schedules the on_signal callback to be invoked on the IO thread.
183
+ /// Safe to call from multiple threads.
184
+ pub fn notify(self: *Signal) void {
185
+ if (self.state.swap(.notified, .Release) == .waiting) {
186
+ self.wake();
187
+ }
188
+ }
189
+
190
+ /// Stops the signal from firing on_signal callbacks on the IO thread.
191
+ /// Safe to call from multiple threads.
192
+ pub fn shutdown(self: *Signal) void {
193
+ if (self.state.swap(.shutdown, .Release) == .waiting) {
194
+ self.wake();
195
+ }
196
+ }
197
+
198
+ /// Return true if the Signal was marked disabled and should no longer fire on_signal callbacks.
199
+ /// Safe to call from multiple threads.
200
+ pub fn is_shutdown(self: *const Signal) bool {
201
+ return self.state.load(.Acquire) == .shutdown;
202
+ }
203
+
204
+ fn wake(self: *Signal) void {
205
+ assert(self.accept_socket != IO.INVALID_SOCKET);
206
+ self.send_buffer[0] = 0;
207
+
208
+ // TODO: use os.send() instead when it gets fixed for windows
209
+ if (builtin.target.os.tag != .windows) {
210
+ _ = os.send(self.accept_socket, &self.send_buffer, 0) catch unreachable;
211
+ return;
212
+ }
213
+
214
+ const buf: []const u8 = &self.send_buffer;
215
+ const rc = os.windows.sendto(self.accept_socket, buf.ptr, buf.len, 0, null, 0);
216
+ assert(rc != os.windows.ws2_32.SOCKET_ERROR);
217
+ }
218
+
219
+ fn wait(self: *Signal) void {
220
+ const state = self.state.compareAndSwap(
221
+ .running,
222
+ .waiting,
223
+ .Acquire,
224
+ .Acquire,
225
+ ) orelse return self.io.recv(
226
+ *Signal,
227
+ self,
228
+ on_recv,
229
+ &self.completion,
230
+ self.connect_socket,
231
+ &self.recv_buffer,
232
+ );
233
+
234
+ switch (state) {
235
+ .running => unreachable, // Not possible due to CAS semantics.
236
+ .waiting => unreachable, // We should be the only ones who could've started waiting.
237
+ .notified => {}, // A thread woke us up before we started waiting so reschedule below.
238
+ .shutdown => return, // A thread shut down the signal before we started waiting.
239
+ }
240
+
241
+ self.io.timeout(
242
+ *Signal,
243
+ self,
244
+ on_timeout,
245
+ &self.completion,
246
+ 0, // zero-timeout functions as a yield
247
+ );
248
+ }
249
+
250
+ fn on_recv(
251
+ self: *Signal,
252
+ completion: *IO.Completion,
253
+ result: IO.RecvError!usize,
254
+ ) void {
255
+ assert(completion == &self.completion);
256
+ _ = result catch |err| std.debug.panic("Signal recv error: {}", .{err});
257
+ self.on_signal();
258
+ }
259
+
260
+ fn on_timeout(
261
+ self: *Signal,
262
+ completion: *IO.Completion,
263
+ result: IO.TimeoutError!void,
264
+ ) void {
265
+ assert(completion == &self.completion);
266
+ _ = result catch |err| std.debug.panic("Signal timeout error: {}", .{err});
267
+ self.on_signal();
268
+ }
269
+
270
+ fn on_signal(self: *Signal) void {
271
+ const state = self.state.compareAndSwap(
272
+ .notified,
273
+ .running,
274
+ .Acquire,
275
+ .Acquire,
276
+ ) orelse {
277
+ (self.on_signal_fn)(self);
278
+ return self.wait();
279
+ };
280
+
281
+ switch (state) {
282
+ .running => unreachable, // Multiple racing calls to on_signal().
283
+ .waiting => unreachable, // on_signal() called without transitioning to a waking state.
284
+ .notified => unreachable, // Not possible due to CAS semantics.
285
+ .shutdown => return, // A thread shut down the signal before we started running.
286
+ }
287
+ }
288
+ };
@@ -0,0 +1,328 @@
1
+ const std = @import("std");
2
+ const os = std.os;
3
+ const assert = std.debug.assert;
4
+
5
+ const config = @import("../../config.zig");
6
+ const log = std.log.scoped(.tb_client);
7
+
8
+ const vsr = @import("../../vsr.zig");
9
+ const Header = vsr.Header;
10
+
11
+ const IO = @import("../../io.zig").IO;
12
+ const message_pool = @import("../../message_pool.zig");
13
+
14
+ const MessagePool = message_pool.MessagePool;
15
+ const Message = MessagePool.Message;
16
+
17
+ const Packet = @import("packet.zig").Packet;
18
+ const Signal = @import("signal.zig").Signal;
19
+
20
+ pub fn ThreadType(
21
+ comptime StateMachine: type,
22
+ comptime MessageBus: type,
23
+ ) type {
24
+ return struct {
25
+ pub const Client = vsr.Client(StateMachine, MessageBus);
26
+ pub const Operation = StateMachine.Operation;
27
+
28
+ fn operation_size_of(op: u8) ?usize {
29
+ const allowed_operations = [_]Operation{
30
+ .create_accounts,
31
+ .create_transfers,
32
+ .lookup_accounts,
33
+ .lookup_transfers,
34
+ };
35
+
36
+ inline for (allowed_operations) |operation| {
37
+ if (op == @enumToInt(operation)) {
38
+ return @sizeOf(StateMachine.Event(operation));
39
+ }
40
+ }
41
+
42
+ return null;
43
+ }
44
+
45
+ /////////////////////////////////////////////////////////////////////////
46
+
47
+ const Self = @This();
48
+
49
+ allocator: std.mem.Allocator,
50
+ client_id: u128,
51
+ packets: []Packet,
52
+
53
+ addresses: []std.net.Address,
54
+ io: IO,
55
+ message_pool: MessagePool,
56
+ message_bus: MessageBus,
57
+ client: Client,
58
+
59
+ retry: Packet.List,
60
+ submitted: Packet.Stack,
61
+ available_messages: usize,
62
+ on_completion_fn: CompletionFn,
63
+
64
+ signal: Signal,
65
+ thread: std.Thread,
66
+
67
+ pub const CompletionFn = fn (
68
+ client_thread: *Self,
69
+ packet: *Packet,
70
+ result: ?[]const u8,
71
+ ) void;
72
+
73
+ pub const Error = error{
74
+ Unexpected,
75
+ OutOfMemory,
76
+ InvalidAddress,
77
+ SystemResources,
78
+ NetworkSubsystemFailed,
79
+ };
80
+
81
+ pub fn init(
82
+ self: *Self,
83
+ allocator: std.mem.Allocator,
84
+ cluster_id: u32,
85
+ addresses: []const u8,
86
+ num_packets: u32,
87
+ on_completion_fn: CompletionFn,
88
+ ) Error!void {
89
+ self.allocator = allocator;
90
+ self.client_id = std.crypto.random.int(u128);
91
+ log.debug("init: initializing client_id={}.", .{self.client_id});
92
+
93
+ log.debug("init: allocating tb_packets.", .{});
94
+ self.packets = self.allocator.alloc(Packet, num_packets) catch |err| {
95
+ log.err("failed to allocate tb_packets: {}", .{err});
96
+ return Error.OutOfMemory;
97
+ };
98
+ errdefer self.allocator.free(self.packets);
99
+
100
+ log.debug("init: parsing vsr addresses.", .{});
101
+ const address_limit = std.mem.count(u8, addresses, ",") + 1;
102
+ self.addresses = vsr.parse_addresses(self.allocator, addresses, address_limit) catch |err| {
103
+ log.err("failed to parse addresses: {}.", .{err});
104
+ return Error.InvalidAddress;
105
+ };
106
+ errdefer self.allocator.free(self.addresses);
107
+
108
+ log.debug("init: initializing IO.", .{});
109
+ self.io = IO.init(32, 0) catch |err| {
110
+ log.err("failed to initialize IO: {}.", .{err});
111
+ return switch (err) {
112
+ error.ProcessFdQuotaExceeded => error.SystemResources,
113
+ error.Unexpected => error.Unexpected,
114
+ else => unreachable,
115
+ };
116
+ };
117
+ errdefer self.io.deinit();
118
+
119
+ log.debug("init: initializing MessagePool", .{});
120
+ self.message_pool = MessagePool.init(allocator, .client) catch |err| {
121
+ log.err("failed to initialize MessagePool: {}", .{err});
122
+ return err;
123
+ };
124
+ errdefer self.message_pool.deinit(self.allocator);
125
+
126
+ log.debug("init: initializing MessageBus.", .{});
127
+ self.message_bus = MessageBus.init(
128
+ self.allocator,
129
+ cluster_id,
130
+ .{ .client = self.client_id },
131
+ &self.message_pool,
132
+ Client.on_message,
133
+ .{
134
+ .configuration = self.addresses,
135
+ .io = &self.io,
136
+ },
137
+ ) catch |err| {
138
+ log.err("failed to initialize message bus: {}.", .{err});
139
+ return err;
140
+ };
141
+ errdefer self.message_bus.deinit(self.allocator);
142
+
143
+ log.debug("init: Initializing client(cluster_id={d}, client_id={d}, addresses={o})", .{
144
+ cluster_id,
145
+ self.client_id,
146
+ self.addresses,
147
+ });
148
+ self.client = Client.init(
149
+ allocator,
150
+ self.client_id,
151
+ cluster_id,
152
+ @intCast(u8, self.addresses.len),
153
+ &self.message_pool,
154
+ .{
155
+ .configuration = self.addresses,
156
+ .io = &self.io,
157
+ },
158
+ ) catch |err| {
159
+ log.err("failed to initalize client: {}", .{err});
160
+ return err;
161
+ };
162
+ errdefer self.client.deinit(self.allocator);
163
+
164
+ self.retry = .{};
165
+ self.submitted = .{};
166
+ self.available_messages = message_pool.messages_max_client;
167
+ self.on_completion_fn = on_completion_fn;
168
+
169
+ log.debug("init: initializing Signal.", .{});
170
+ self.signal.init(&self.io, Self.on_signal) catch |err| {
171
+ log.err("failed to initialize Signal: {}.", .{err});
172
+ return err;
173
+ };
174
+ errdefer self.signal.deinit();
175
+
176
+ log.debug("init: spawning Context thread.", .{});
177
+ self.thread = std.Thread.spawn(.{}, Self.run, .{self}) catch |err| {
178
+ log.err("failed to spawn context thread: {}.", .{err});
179
+ return switch (err) {
180
+ error.Unexpected => error.Unexpected,
181
+ error.OutOfMemory => error.OutOfMemory,
182
+ error.SystemResources, error.ThreadQuotaExceeded, error.LockedMemoryLimitExceeded => error.SystemResources,
183
+ };
184
+ };
185
+ }
186
+
187
+ pub fn deinit(self: *Self) void {
188
+ self.signal.shutdown();
189
+ self.thread.join();
190
+ self.signal.deinit();
191
+
192
+ self.client.deinit(self.allocator);
193
+ self.message_bus.deinit(self.allocator);
194
+ self.message_pool.deinit(self.allocator);
195
+ self.io.deinit();
196
+
197
+ self.allocator.free(self.addresses);
198
+ self.allocator.free(self.packets);
199
+ self.* = undefined;
200
+ }
201
+
202
+ pub fn submit(self: *Self, list: Packet.List) void {
203
+ if (list.peek() == null) return;
204
+ self.submitted.push(list);
205
+ self.signal.notify();
206
+ }
207
+
208
+ fn run(self: *Self) void {
209
+ while (!self.signal.is_shutdown()) {
210
+ self.client.tick();
211
+ self.io.run_for_ns(config.tick_ms * std.time.ns_per_ms) catch |err| {
212
+ log.err("IO.run() failed with {}", .{err});
213
+ return;
214
+ };
215
+ }
216
+ }
217
+
218
+ fn on_signal(signal: *Signal) void {
219
+ const self = @fieldParentPtr(Self, "signal", signal);
220
+ self.client.tick();
221
+
222
+ // Consume all of retry here to avoid infinite loop
223
+ // if the code below pushes to self.retry while we're dequeueing.
224
+ var pending = self.retry;
225
+ self.retry = .{};
226
+
227
+ // The loop below can exit early without processing all of pending
228
+ // if available_messages becomes zero.
229
+ // In such a case we need to restore self.retry we consumed above
230
+ // with those that weren't processed.
231
+ defer {
232
+ pending.push(self.retry);
233
+ self.retry = pending;
234
+ }
235
+
236
+ // Process packets from either pending or submitted as long as we have messages.
237
+ while (self.available_messages > 0) {
238
+ const packet = pending.pop() orelse self.submitted.pop() orelse break;
239
+ const message = self.client.get_message();
240
+ defer self.client.unref(message);
241
+
242
+ self.available_messages -= 1;
243
+ self.request(packet, message);
244
+ }
245
+ }
246
+
247
+ fn request(self: *Self, packet: *Packet, message: *Message) void {
248
+ // Get the size of each request structure in the packet.data
249
+ const request_size: usize = operation_size_of(packet.operation) orelse {
250
+ return self.on_complete(packet, error.InvalidOperation);
251
+ };
252
+
253
+ // Make sure the packet.data size is correct.
254
+ const readable = packet.data[0..packet.data_size];
255
+ if (readable.len == 0 or readable.len % request_size != 0) {
256
+ return self.on_complete(packet, error.InvalidDataSize);
257
+ }
258
+
259
+ // Make sure the packet.data wouldn't overflow a message.
260
+ const writable = message.buffer[@sizeOf(Header)..];
261
+ if (readable.len > writable.len) {
262
+ return self.on_complete(packet, error.TooMuchData);
263
+ }
264
+
265
+ // Write the packet data to the message
266
+ std.mem.copy(u8, writable, readable);
267
+ const wrote = readable.len;
268
+
269
+ // .. and submit the message for processing
270
+ self.client.request(
271
+ @bitCast(u128, UserData{
272
+ .self = self,
273
+ .packet = packet,
274
+ }),
275
+ Self.on_result,
276
+ @intToEnum(Operation, packet.operation),
277
+ message,
278
+ wrote,
279
+ );
280
+ }
281
+
282
+ const UserData = packed struct {
283
+ self: *Self,
284
+ packet: *Packet,
285
+ };
286
+
287
+ fn on_result(raw_user_data: u128, op: Operation, results: Client.Error![]const u8) void {
288
+ const user_data = @bitCast(UserData, raw_user_data);
289
+ const self = user_data.self;
290
+ const packet = user_data.packet;
291
+
292
+ assert(packet.operation == @enumToInt(op));
293
+ self.on_complete(packet, results);
294
+ }
295
+
296
+ const PacketError = Client.Error || error{
297
+ TooMuchData,
298
+ InvalidOperation,
299
+ InvalidDataSize,
300
+ };
301
+
302
+ fn on_complete(
303
+ self: *Self,
304
+ packet: *Packet,
305
+ result: PacketError![]const u8,
306
+ ) void {
307
+ assert(self.available_messages < message_pool.messages_max_client);
308
+ self.available_messages += 1;
309
+
310
+ const bytes = result catch |err| {
311
+ packet.status = switch (err) {
312
+ // If there's too many requests, (re)try submitting the packet later
313
+ error.TooManyOutstandingRequests => {
314
+ return self.retry.push(Packet.List.from(packet));
315
+ },
316
+ error.TooMuchData => .too_much_data,
317
+ error.InvalidOperation => .invalid_operation,
318
+ error.InvalidDataSize => .invalid_data_size,
319
+ };
320
+ return self.on_completion_fn(self, packet, null);
321
+ };
322
+
323
+ // The packet completed normally
324
+ packet.status = .ok;
325
+ self.on_completion_fn(self, packet, bytes);
326
+ }
327
+ };
328
+ }