tigerbeetle-node 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +4 -3
  2. package/src/tigerbeetle/scripts/fuzz_loop.sh +1 -1
  3. package/src/tigerbeetle/scripts/pre-commit.sh +2 -2
  4. package/src/tigerbeetle/scripts/validate_docs.sh +17 -0
  5. package/src/tigerbeetle/src/benchmark.zig +25 -11
  6. package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
  7. package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
  8. package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
  9. package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
  10. package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -256
  11. package/src/tigerbeetle/src/c/tb_client.h +18 -4
  12. package/src/tigerbeetle/src/c/tb_client.zig +88 -26
  13. package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
  14. package/src/tigerbeetle/src/c/test.zig +371 -1
  15. package/src/tigerbeetle/src/cli.zig +36 -6
  16. package/src/tigerbeetle/src/config.zig +10 -1
  17. package/src/tigerbeetle/src/demo.zig +2 -1
  18. package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
  19. package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
  20. package/src/tigerbeetle/src/ewah.zig +11 -33
  21. package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
  22. package/src/tigerbeetle/src/lsm/README.md +97 -3
  23. package/src/tigerbeetle/src/lsm/compaction.zig +32 -7
  24. package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
  25. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +34 -32
  26. package/src/tigerbeetle/src/lsm/grid.zig +39 -21
  27. package/src/tigerbeetle/src/lsm/groove.zig +1 -0
  28. package/src/tigerbeetle/src/lsm/k_way_merge.zig +3 -3
  29. package/src/tigerbeetle/src/lsm/level_iterator.zig +1 -1
  30. package/src/tigerbeetle/src/lsm/manifest.zig +13 -0
  31. package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -49
  32. package/src/tigerbeetle/src/lsm/manifest_log.zig +173 -335
  33. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
  34. package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
  35. package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
  36. package/src/tigerbeetle/src/lsm/segmented_array.zig +24 -15
  37. package/src/tigerbeetle/src/lsm/table.zig +32 -20
  38. package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
  39. package/src/tigerbeetle/src/lsm/table_iterator.zig +4 -5
  40. package/src/tigerbeetle/src/lsm/test.zig +13 -2
  41. package/src/tigerbeetle/src/lsm/tree.zig +45 -7
  42. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +36 -32
  43. package/src/tigerbeetle/src/main.zig +55 -2
  44. package/src/tigerbeetle/src/message_bus.zig +18 -7
  45. package/src/tigerbeetle/src/message_pool.zig +8 -2
  46. package/src/tigerbeetle/src/ring_buffer.zig +7 -3
  47. package/src/tigerbeetle/src/simulator.zig +38 -11
  48. package/src/tigerbeetle/src/state_machine.zig +47 -22
  49. package/src/tigerbeetle/src/test/accounting/workload.zig +9 -5
  50. package/src/tigerbeetle/src/test/cluster.zig +15 -33
  51. package/src/tigerbeetle/src/test/conductor.zig +2 -1
  52. package/src/tigerbeetle/src/test/network.zig +45 -19
  53. package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
  54. package/src/tigerbeetle/src/test/state_checker.zig +5 -7
  55. package/src/tigerbeetle/src/test/storage.zig +453 -110
  56. package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
  57. package/src/tigerbeetle/src/tigerbeetle.zig +1 -0
  58. package/src/tigerbeetle/src/unit_tests.zig +6 -1
  59. package/src/tigerbeetle/src/util.zig +97 -11
  60. package/src/tigerbeetle/src/vopr.zig +2 -1
  61. package/src/tigerbeetle/src/vsr/client.zig +8 -3
  62. package/src/tigerbeetle/src/vsr/journal.zig +280 -202
  63. package/src/tigerbeetle/src/vsr/replica.zig +169 -31
  64. package/src/tigerbeetle/src/vsr/superblock.zig +356 -629
  65. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -6
  66. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +414 -151
  67. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
  68. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
  69. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +44 -9
  70. package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
  71. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
  72. package/src/tigerbeetle/src/vsr.zig +19 -5
  73. package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
  74. package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
  75. package/src/tigerbeetle/src/vopr_hub/README.md +0 -58
  76. package/src/tigerbeetle/src/vopr_hub/SETUP.md +0 -199
  77. package/src/tigerbeetle/src/vopr_hub/go.mod +0 -3
  78. package/src/tigerbeetle/src/vopr_hub/main.go +0 -1022
  79. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +0 -3
  80. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +0 -403
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tigerbeetle-node",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "TigerBeetle Node.js client",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -22,7 +22,8 @@
22
22
  "src/index.ts",
23
23
  "src/node.zig",
24
24
  "src/test.ts",
25
- "src/tigerbeetle/src",
25
+ "src/tigerbeetle/src/*.zig",
26
+ "src/tigerbeetle/src/{c,io,lsm,test,vsr}",
26
27
  "src/tigerbeetle/scripts",
27
28
  "src/translate.zig",
28
29
  "tsconfig.json"
@@ -39,7 +40,7 @@
39
40
  "build": "npm run build_tsc && npm run build_lib",
40
41
  "build_tsc": "./node_modules/typescript/bin/tsc",
41
42
  "build_lib": "mkdir -p dist && zig/zig build-lib -mcpu=baseline -OReleaseSafe -dynamic -lc -isystem build/node-$(node --version)/include/node src/node.zig -fallow-shlib-undefined -femit-bin=dist/client.node",
42
- "prepack": "git submodule deinit --all && git submodule update --init && npm run build_tsc",
43
+ "prepack": "npm run build_tsc",
43
44
  "clean": "rm -rf build dist node_modules src/zig-cache zig"
44
45
  },
45
46
  "author": "TigerBeetle, Inc",
@@ -2,7 +2,7 @@
2
2
  set -eu
3
3
 
4
4
  # Repeatedly runs some zig build command with different seeds and stores the output in the current directory.
5
- # Eg `fuzz_repeatedly.sh lsm_forest_fuzz` will run `zig build lsm_forest_fuzz -- seed $SEED > fuzz_lsm_forest_fuzz_${SEED}`
5
+ # Eg `fuzz_repeatedly.sh fuzz_lsm_forest` will run `zig build fuzz_lsm_forest -- seed $SEED > fuzz_lsm_forest_fuzz_${SEED}`
6
6
  # Use ./fuzz_unique_errors.sh to analyze the results.
7
7
 
8
8
  FUZZ_COMMAND=$1
@@ -4,6 +4,6 @@
4
4
  # ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
5
5
 
6
6
  set -euo pipefail
7
- cd "$(dirname "$0")/../.."
7
+ cd "$(git rev-parse --show-toplevel)"
8
8
 
9
- zig fmt --check .
9
+ zig fmt --check .
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # This script builds the docs website for the currently checked out
6
+ # branch.
7
+
8
+ git clone https://github.com/tigerbeetledb/docs docs_website
9
+ # Try to grab branch from Github Actions CI.
10
+ # See also: https://docs.github.com/en/actions/learn-github-actions/environment-variables.
11
+ BRANCH="$GITHUB_HEAD_REF"
12
+ if [[ -z "$BRANCH" ]]; then
13
+ # Otherwise fall back to git rev-parse
14
+ BRANCH=$(git rev-parse --abbrev-ref HEAD)
15
+ fi
16
+ ( cd docs_website && npm install && ./scripts/build.sh "$BRANCH" )
17
+ rm -rf docs_website
@@ -9,6 +9,7 @@ pub const log_level: std.log.Level = .err;
9
9
  const cli = @import("cli.zig");
10
10
  const IO = @import("io.zig").IO;
11
11
 
12
+ const util = @import("util.zig");
12
13
  const Storage = @import("storage.zig").Storage;
13
14
  const MessagePool = @import("message_pool.zig").MessagePool;
14
15
  const MessageBus = @import("message_bus.zig").MessageBusClient;
@@ -40,7 +41,7 @@ var accounts = [_]tb.Account{
40
41
  .user_data = 0,
41
42
  .reserved = [_]u8{0} ** 48,
42
43
  .ledger = 2,
43
- .code = 0,
44
+ .code = 1,
44
45
  .flags = .{},
45
46
  .debits_pending = 0,
46
47
  .debits_posted = 0,
@@ -52,7 +53,7 @@ var accounts = [_]tb.Account{
52
53
  .user_data = 0,
53
54
  .reserved = [_]u8{0} ** 48,
54
55
  .ledger = 2,
55
- .code = 0,
56
+ .code = 1,
56
57
  .flags = .{},
57
58
  .debits_pending = 0,
58
59
  .debits_posted = 0,
@@ -104,17 +105,18 @@ pub fn main() !void {
104
105
 
105
106
  for (transfers) |*transfer, index| {
106
107
  transfer.* = .{
107
- .id = index,
108
+ .id = index + 1,
108
109
  .debit_account_id = accounts[0].id,
109
110
  .credit_account_id = accounts[1].id,
110
- .pending_id = 0,
111
111
  .user_data = 0,
112
112
  .reserved = 0,
113
- .code = 0,
113
+ .pending_id = 0,
114
+ .timeout = 0,
114
115
  .ledger = 2,
116
+ .code = 1,
115
117
  .flags = .{},
116
118
  .amount = 1,
117
- .timeout = 0,
119
+ .timestamp = 0,
118
120
  };
119
121
  }
120
122
 
@@ -209,7 +211,8 @@ const TimedQueue = struct {
209
211
  const message = self.client.get_message();
210
212
  defer self.client.unref(message);
211
213
 
212
- std.mem.copy(
214
+ util.copy_disjoint(
215
+ .inexact,
213
216
  u8,
214
217
  message.buffer[@sizeOf(vsr.Header)..],
215
218
  std.mem.sliceAsBytes(starting_batch.data),
@@ -240,8 +243,6 @@ const TimedQueue = struct {
240
243
  @panic("Client returned error during benchmarking.");
241
244
  };
242
245
 
243
- log.debug("response={s}", .{std.mem.bytesAsSlice(tb.CreateAccountsResult, value)});
244
-
245
246
  const self: *TimedQueue = @intToPtr(*TimedQueue, @intCast(usize, user_data));
246
247
  const completed_batch: ?Batch = self.batches.pop();
247
248
  assert(completed_batch != null);
@@ -253,8 +254,20 @@ const TimedQueue = struct {
253
254
  });
254
255
  const latency = now - self.batch_start.?;
255
256
  switch (operation) {
256
- .create_accounts => {},
257
+ .create_accounts => {
258
+ const create_accounts_results = std.mem.bytesAsSlice(tb.CreateAccountsResult, value);
259
+ if (create_accounts_results.len > 0) {
260
+ log.err("CreateAccountsResults={any}", .{create_accounts_results});
261
+ @panic("Unexpected result creating accounts.");
262
+ }
263
+ },
257
264
  .create_transfers => {
265
+ const create_transfers_results = std.mem.bytesAsSlice(tb.CreateTransfersResult, value);
266
+ if (create_transfers_results.len > 0) {
267
+ log.err("CreateTransfersResults={any}", .{create_transfers_results});
268
+ @panic("Unexpected result creating transfers.");
269
+ }
270
+
258
271
  if (latency > self.transfers_latency_max) {
259
272
  self.transfers_latency_max = latency;
260
273
  }
@@ -266,7 +279,8 @@ const TimedQueue = struct {
266
279
  const message = self.client.get_message();
267
280
  defer self.client.unref(message);
268
281
 
269
- std.mem.copy(
282
+ util.copy_disjoint(
283
+ .inexact,
270
284
  u8,
271
285
  message.buffer[@sizeOf(vsr.Header)..],
272
286
  std.mem.sliceAsBytes(next_batch.data),
@@ -1,4 +1,22 @@
1
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_context);
7
+
8
+ const util = @import("../../util.zig");
9
+ const vsr = @import("../../vsr.zig");
10
+ const Header = vsr.Header;
11
+
12
+ const IO = @import("../../io.zig").IO;
13
+ const message_pool = @import("../../message_pool.zig");
14
+
15
+ const MessagePool = message_pool.MessagePool;
16
+ const Message = MessagePool.Message;
17
+
18
+ const Packet = @import("packet.zig").Packet;
19
+ const Signal = @import("signal.zig").Signal;
2
20
  const ThreadType = @import("thread.zig").ThreadType;
3
21
 
4
22
  const api = @import("../tb_client.zig");
@@ -13,15 +31,64 @@ pub const ContextImplementation = struct {
13
31
  deinit_fn: fn (*ContextImplementation) void,
14
32
  };
15
33
 
34
+ pub const Error = std.mem.Allocator.Error || error{
35
+ Unexpected,
36
+ AddressInvalid,
37
+ AddressLimitExceeded,
38
+ PacketsCountInvalid,
39
+ SystemResources,
40
+ NetworkSubsystemFailed,
41
+ };
42
+
16
43
  pub fn ContextType(
17
- comptime StateMachine: type,
18
- comptime MessageBus: type,
44
+ comptime Client: type,
19
45
  ) type {
20
46
  return struct {
21
47
  const Context = @This();
22
- const Thread = ThreadType(StateMachine, MessageBus);
48
+ const Thread = ThreadType(Context);
49
+
50
+ const UserData = extern struct {
51
+ self: *Context,
52
+ packet: *Packet,
53
+ };
54
+
55
+ comptime {
56
+ assert(@sizeOf(UserData) == @sizeOf(u128));
57
+ }
58
+
59
+ fn operation_event_size(op: u8) ?usize {
60
+ const allowed_operations = [_]Client.StateMachine.Operation{
61
+ .create_accounts,
62
+ .create_transfers,
63
+ .lookup_accounts,
64
+ .lookup_transfers,
65
+ };
66
+
67
+ inline for (allowed_operations) |operation| {
68
+ if (op == @enumToInt(operation)) {
69
+ return @sizeOf(Client.StateMachine.Event(operation));
70
+ }
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ const PacketError = Client.Error || error{
77
+ TooMuchData,
78
+ InvalidOperation,
79
+ InvalidDataSize,
80
+ };
23
81
 
24
82
  allocator: std.mem.Allocator,
83
+ client_id: u128,
84
+ packets: []Packet,
85
+
86
+ addresses: []const std.net.Address,
87
+ io: IO,
88
+ message_pool: MessagePool,
89
+ messages_available: usize,
90
+ client: Client,
91
+
25
92
  on_completion_ctx: usize,
26
93
  on_completion_fn: tb_completion_t,
27
94
  implementation: ContextImplementation,
@@ -29,51 +96,199 @@ pub fn ContextType(
29
96
 
30
97
  pub fn init(
31
98
  allocator: std.mem.Allocator,
32
- out_tb_client: *tb_client_t,
33
- out_packets: *tb_packet_list_t,
34
99
  cluster_id: u32,
35
- addresses_ptr: [*:0]const u8,
36
- addresses_len: u32,
37
- num_packets: u32,
100
+ addresses: []const u8,
101
+ packets_count: u32,
38
102
  on_completion_ctx: usize,
39
103
  on_completion_fn: tb_completion_t,
40
- ) tb_status_t {
41
- const context = allocator.create(Context) catch return .out_of_memory;
104
+ ) Error!*Context {
105
+ var context = try allocator.create(Context);
106
+ errdefer allocator.destroy(context);
107
+
42
108
  context.allocator = allocator;
109
+ context.client_id = std.crypto.random.int(u128);
110
+ log.debug("{}: init: initializing", .{context.client_id});
111
+
112
+ const packets_count_max = 4096;
113
+ if (packets_count > packets_count_max) {
114
+ return error.PacketsCountInvalid;
115
+ }
116
+
117
+ log.debug("{}: init: allocating tb_packets", .{context.client_id});
118
+ context.packets = try context.allocator.alloc(Packet, packets_count);
119
+ errdefer context.allocator.free(context.packets);
120
+
121
+ log.debug("{}: init: parsing vsr addresses: {s}", .{ context.client_id, addresses });
122
+ context.addresses = vsr.parse_addresses(
123
+ context.allocator,
124
+ addresses,
125
+ config.replicas_max,
126
+ ) catch |err| return switch (err) {
127
+ error.AddressLimitExceeded => error.AddressLimitExceeded,
128
+ else => error.AddressInvalid,
129
+ };
130
+ errdefer context.allocator.free(context.addresses);
131
+
132
+ log.debug("{}: init: initializing IO", .{context.client_id});
133
+ context.io = IO.init(32, 0) catch |err| {
134
+ log.err("{}: failed to initialize IO: {s}", .{
135
+ context.client_id,
136
+ @errorName(err),
137
+ });
138
+ return switch (err) {
139
+ error.ProcessFdQuotaExceeded => error.SystemResources,
140
+ error.Unexpected => error.Unexpected,
141
+ else => unreachable,
142
+ };
143
+ };
144
+ errdefer context.io.deinit();
145
+
146
+ log.debug("{}: init: initializing MessagePool", .{context.client_id});
147
+ context.message_pool = try MessagePool.init(allocator, .client);
148
+ errdefer context.message_pool.deinit(context.allocator);
149
+
150
+ log.debug("{}: init: initializing client (cluster_id={}, addresses={any})", .{
151
+ context.client_id,
152
+ cluster_id,
153
+ context.addresses,
154
+ });
155
+ context.client = try Client.init(
156
+ allocator,
157
+ context.client_id,
158
+ cluster_id,
159
+ @intCast(u8, context.addresses.len),
160
+ &context.message_pool,
161
+ .{
162
+ .configuration = context.addresses,
163
+ .io = &context.io,
164
+ },
165
+ );
166
+ errdefer context.client.deinit(context.allocator);
167
+
168
+ context.messages_available = config.client_request_queue_max;
43
169
  context.on_completion_ctx = on_completion_ctx;
44
170
  context.on_completion_fn = on_completion_fn;
45
-
46
- out_tb_client.* = api.context_to_client(&context.implementation);
47
171
  context.implementation = .{
48
172
  .submit_fn = Context.on_submit,
49
173
  .deinit_fn = Context.on_deinit,
50
174
  };
51
175
 
52
- const addresses = @ptrCast([*]const u8, addresses_ptr)[0..addresses_len];
53
- context.thread.init(
54
- allocator,
55
- cluster_id,
56
- addresses,
57
- num_packets,
58
- Context.on_completion,
59
- ) catch |err| {
60
- allocator.destroy(context);
61
- return switch (err) {
62
- error.Unexpected => .unexpected,
63
- error.OutOfMemory => .out_of_memory,
64
- error.InvalidAddress => .invalid_address,
65
- error.SystemResources => .system_resources,
66
- error.NetworkSubsystemFailed => .network_subsystem,
176
+ log.debug("{}: init: initializing thread", .{context.client_id});
177
+ try context.thread.init(context);
178
+ errdefer context.thread.deinit(context.allocator);
179
+
180
+ return context;
181
+ }
182
+
183
+ pub fn deinit(self: *Context) void {
184
+ self.thread.deinit();
185
+
186
+ self.client.deinit(self.allocator);
187
+ self.message_pool.deinit(self.allocator);
188
+ self.io.deinit();
189
+
190
+ self.allocator.free(self.addresses);
191
+ self.allocator.free(self.packets);
192
+ self.allocator.destroy(self);
193
+ }
194
+
195
+ pub fn tick(self: *Context) void {
196
+ self.client.tick();
197
+ }
198
+
199
+ pub fn run(self: *Context) void {
200
+ while (!self.thread.signal.is_shutdown()) {
201
+ self.tick();
202
+ self.io.run_for_ns(config.tick_ms * std.time.ns_per_ms) catch |err| {
203
+ log.err("{}: IO.run() failed: {s}", .{
204
+ self.client_id,
205
+ @errorName(err),
206
+ });
207
+ @panic("IO.run() failed");
67
208
  };
209
+ }
210
+ }
211
+
212
+ pub fn request(self: *Context, packet: *Packet) void {
213
+ const message = self.message_pool.get_message();
214
+ defer self.message_pool.unref(message);
215
+ self.messages_available -= 1;
216
+
217
+ // Get the size of each request structure in the packet.data:
218
+ const event_size: usize = operation_event_size(packet.operation) orelse {
219
+ return self.on_complete(packet, error.InvalidOperation);
68
220
  };
69
221
 
70
- var list = tb_packet_list_t{};
71
- for (context.thread.packets) |*packet| {
72
- list.push(tb_packet_list_t.from(packet));
222
+ // Make sure the packet.data size is correct:
223
+ const readable = @ptrCast([*]const u8, packet.data)[0..packet.data_size];
224
+ if (readable.len == 0 or readable.len % event_size != 0) {
225
+ return self.on_complete(packet, error.InvalidDataSize);
73
226
  }
74
227
 
75
- out_packets.* = list;
76
- return .success;
228
+ // Make sure the packet.data wouldn't overflow a message:
229
+ const writable = message.buffer[@sizeOf(Header)..][0..config.message_body_size_max];
230
+ if (readable.len > writable.len) {
231
+ return self.on_complete(packet, error.TooMuchData);
232
+ }
233
+
234
+ // Write the packet data to the message:
235
+ util.copy_disjoint(.inexact, u8, writable, readable);
236
+ const wrote = readable.len;
237
+
238
+ // Submit the message for processing:
239
+ self.client.request(
240
+ @bitCast(u128, UserData{
241
+ .self = self,
242
+ .packet = packet,
243
+ }),
244
+ Context.on_result,
245
+ @intToEnum(Client.StateMachine.Operation, packet.operation),
246
+ message,
247
+ wrote,
248
+ );
249
+ }
250
+
251
+ fn on_result(
252
+ raw_user_data: u128,
253
+ op: Client.StateMachine.Operation,
254
+ results: Client.Error![]const u8,
255
+ ) void {
256
+ const user_data = @bitCast(UserData, raw_user_data);
257
+ const self = user_data.self;
258
+ const packet = user_data.packet;
259
+
260
+ assert(packet.operation == @enumToInt(op));
261
+ self.on_complete(packet, results);
262
+ }
263
+
264
+ fn on_complete(
265
+ self: *Context,
266
+ packet: *Packet,
267
+ result: PacketError![]const u8,
268
+ ) void {
269
+ self.messages_available += 1;
270
+ assert(self.messages_available <= config.client_request_queue_max);
271
+
272
+ // Signal to resume sending requests that was waiting for available messages.
273
+ if (self.messages_available == 1) self.thread.signal.notify();
274
+
275
+ const tb_client = api.context_to_client(&self.implementation);
276
+ const bytes = result catch |err| {
277
+ packet.status = switch (err) {
278
+ // If there's too many requests, (re)try submitting the packet later.
279
+ error.TooManyOutstandingRequests => {
280
+ return self.thread.retry.push(Packet.List.from(packet));
281
+ },
282
+ error.TooMuchData => .too_much_data,
283
+ error.InvalidOperation => .invalid_operation,
284
+ error.InvalidDataSize => .invalid_data_size,
285
+ };
286
+ return (self.on_completion_fn)(self.on_completion_ctx, tb_client, packet, null, 0);
287
+ };
288
+
289
+ // The packet completed normally.
290
+ packet.status = .ok;
291
+ (self.on_completion_fn)(self.on_completion_ctx, tb_client, packet, bytes.ptr, @intCast(u32, bytes.len));
77
292
  }
78
293
 
79
294
  fn on_submit(implementation: *ContextImplementation, packets: *tb_packet_list_t) void {
@@ -83,21 +298,7 @@ pub fn ContextType(
83
298
 
84
299
  fn on_deinit(implementation: *ContextImplementation) void {
85
300
  const context = @fieldParentPtr(Context, "implementation", implementation);
86
- context.thread.deinit();
87
- context.allocator.destroy(context);
88
- }
89
-
90
- fn on_completion(thread: *Thread, packet: *tb_packet_t, result: ?[]const u8) void {
91
- const context = @fieldParentPtr(Context, "thread", thread);
92
- const tb_client = api.context_to_client(&context.implementation);
93
-
94
- context.on_completion_fn(
95
- context.on_completion_ctx,
96
- tb_client,
97
- packet,
98
- if (result) |r| r.ptr else null,
99
- if (result) |r| @intCast(u32, r.len) else 0,
100
- );
301
+ context.deinit();
101
302
  }
102
303
  };
103
304
  }
@@ -0,0 +1,108 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+ const mem = std.mem;
4
+
5
+ const config = @import("../../config.zig");
6
+ const vsr = @import("../../vsr.zig");
7
+ const Header = vsr.Header;
8
+
9
+ const RingBuffer = @import("../../ring_buffer.zig").RingBuffer;
10
+ const MessagePool = @import("../../message_pool.zig").MessagePool;
11
+ const Message = @import("../../message_pool.zig").MessagePool.Message;
12
+
13
+ pub fn EchoClient(comptime StateMachine_: type, comptime MessageBus: type) type {
14
+ return struct {
15
+ const Self = @This();
16
+
17
+ // Exposing the same types the real client does:
18
+ pub usingnamespace blk: {
19
+ const Client = @import("../../vsr/client.zig").Client(StateMachine_, MessageBus);
20
+ break :blk struct {
21
+ pub const StateMachine = Client.StateMachine;
22
+ pub const Error = Client.Error;
23
+ pub const Request = Client.Request;
24
+ };
25
+ };
26
+
27
+ request_queue: RingBuffer(Self.Request, config.client_request_queue_max, .array) = .{},
28
+ message_pool: *MessagePool,
29
+
30
+ pub fn init(
31
+ allocator: mem.Allocator,
32
+ id: u128,
33
+ cluster: u32,
34
+ replica_count: u8,
35
+ message_pool: *MessagePool,
36
+ message_bus_options: MessageBus.Options,
37
+ ) !Self {
38
+ _ = allocator;
39
+ _ = id;
40
+ _ = cluster;
41
+ _ = replica_count;
42
+ _ = message_bus_options;
43
+
44
+ return Self{
45
+ .message_pool = message_pool,
46
+ };
47
+ }
48
+
49
+ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
50
+ _ = allocator;
51
+ // Drains all pending requests before deiniting.
52
+ self.reply();
53
+ }
54
+
55
+ pub fn tick(self: *Self) void {
56
+ self.reply();
57
+ }
58
+
59
+ pub fn request(
60
+ self: *Self,
61
+ user_data: u128,
62
+ callback: Self.Request.Callback,
63
+ operation: Self.StateMachine.Operation,
64
+ message: *Message,
65
+ message_body_size: usize,
66
+ ) void {
67
+ message.header.* = .{
68
+ .client = 0,
69
+ .request = 0,
70
+ .cluster = 0,
71
+ .command = .request,
72
+ .operation = vsr.Operation.from(Self.StateMachine, operation),
73
+ .size = @intCast(u32, @sizeOf(Header) + message_body_size),
74
+ };
75
+
76
+ if (self.request_queue.full()) {
77
+ callback(user_data, operation, error.TooManyOutstandingRequests);
78
+ return;
79
+ }
80
+
81
+ self.request_queue.push_assume_capacity(.{
82
+ .user_data = user_data,
83
+ .callback = callback,
84
+ .message = message.ref(),
85
+ });
86
+ }
87
+
88
+ pub fn get_message(self: *Self) *Message {
89
+ return self.message_pool.get_message();
90
+ }
91
+
92
+ pub fn unref(self: *Self, message: *Message) void {
93
+ self.message_pool.unref(message);
94
+ }
95
+
96
+ fn reply(self: *Self) void {
97
+ while (self.request_queue.pop()) |inflight| {
98
+ defer self.unref(inflight.message);
99
+
100
+ inflight.callback(
101
+ inflight.user_data,
102
+ inflight.message.header.operation.cast(Self.StateMachine),
103
+ inflight.message.body(),
104
+ );
105
+ }
106
+ }
107
+ };
108
+ }
@@ -4,11 +4,11 @@ const Atomic = std.atomic.Atomic;
4
4
 
5
5
  pub const Packet = extern struct {
6
6
  next: ?*Packet,
7
- user_data: usize,
7
+ user_data: ?*anyopaque,
8
8
  operation: u8,
9
9
  status: Status,
10
10
  data_size: u32,
11
- data: [*]const u8,
11
+ data: ?*anyopaque,
12
12
 
13
13
  pub const Status = enum(u8) {
14
14
  ok,
@@ -8,12 +8,10 @@ const Atomic = std.atomic.Atomic;
8
8
 
9
9
  const log = std.log.scoped(.tb_client_signal);
10
10
 
11
- /// A Signal is a way to trigger a registered callback on a tigerbeetle IO instace
11
+ /// A Signal is a way to trigger a registered callback on a tigerbeetle IO instance
12
12
  /// when notification occurs from another thread.
13
13
  /// It does this by using OS sockets (which are thread safe)
14
14
  /// to resolve IO.Completions on the tigerbeetle thread.
15
- ///
16
- /// TODO: implement a simpler version of this eventually..
17
15
  pub const Signal = struct {
18
16
  io: *IO,
19
17
  server_socket: os.socket_t,
@@ -50,7 +48,7 @@ pub const Signal = struct {
50
48
 
51
49
  // Windows requires that the socket is bound before listening
52
50
  if (builtin.target.os.tag == .windows) {
53
- var addr = std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 0); // zero port lets the OS choose
51
+ const addr = std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 0); // zero port lets the OS choose
54
52
  os.bind(self.server_socket, &addr.any, addr.getOsSockLen()) catch |err| {
55
53
  log.err("failed to bind the server socket to a local random port: {}", .{err});
56
54
  return switch (err) {