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.
- package/package.json +4 -3
- package/src/tigerbeetle/scripts/fuzz_loop.sh +1 -1
- package/src/tigerbeetle/scripts/pre-commit.sh +2 -2
- package/src/tigerbeetle/scripts/validate_docs.sh +17 -0
- package/src/tigerbeetle/src/benchmark.zig +25 -11
- package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
- package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
- package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
- package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
- package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -256
- package/src/tigerbeetle/src/c/tb_client.h +18 -4
- package/src/tigerbeetle/src/c/tb_client.zig +88 -26
- package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
- package/src/tigerbeetle/src/c/test.zig +371 -1
- package/src/tigerbeetle/src/cli.zig +36 -6
- package/src/tigerbeetle/src/config.zig +10 -1
- package/src/tigerbeetle/src/demo.zig +2 -1
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
- package/src/tigerbeetle/src/ewah.zig +11 -33
- package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
- package/src/tigerbeetle/src/lsm/README.md +97 -3
- package/src/tigerbeetle/src/lsm/compaction.zig +32 -7
- package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +34 -32
- package/src/tigerbeetle/src/lsm/grid.zig +39 -21
- package/src/tigerbeetle/src/lsm/groove.zig +1 -0
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +3 -3
- package/src/tigerbeetle/src/lsm/level_iterator.zig +1 -1
- package/src/tigerbeetle/src/lsm/manifest.zig +13 -0
- package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -49
- package/src/tigerbeetle/src/lsm/manifest_log.zig +173 -335
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
- package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
- package/src/tigerbeetle/src/lsm/segmented_array.zig +24 -15
- package/src/tigerbeetle/src/lsm/table.zig +32 -20
- package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
- package/src/tigerbeetle/src/lsm/table_iterator.zig +4 -5
- package/src/tigerbeetle/src/lsm/test.zig +13 -2
- package/src/tigerbeetle/src/lsm/tree.zig +45 -7
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +36 -32
- package/src/tigerbeetle/src/main.zig +55 -2
- package/src/tigerbeetle/src/message_bus.zig +18 -7
- package/src/tigerbeetle/src/message_pool.zig +8 -2
- package/src/tigerbeetle/src/ring_buffer.zig +7 -3
- package/src/tigerbeetle/src/simulator.zig +38 -11
- package/src/tigerbeetle/src/state_machine.zig +47 -22
- package/src/tigerbeetle/src/test/accounting/workload.zig +9 -5
- package/src/tigerbeetle/src/test/cluster.zig +15 -33
- package/src/tigerbeetle/src/test/conductor.zig +2 -1
- package/src/tigerbeetle/src/test/network.zig +45 -19
- package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
- package/src/tigerbeetle/src/test/state_checker.zig +5 -7
- package/src/tigerbeetle/src/test/storage.zig +453 -110
- package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
- package/src/tigerbeetle/src/tigerbeetle.zig +1 -0
- package/src/tigerbeetle/src/unit_tests.zig +6 -1
- package/src/tigerbeetle/src/util.zig +97 -11
- package/src/tigerbeetle/src/vopr.zig +2 -1
- package/src/tigerbeetle/src/vsr/client.zig +8 -3
- package/src/tigerbeetle/src/vsr/journal.zig +280 -202
- package/src/tigerbeetle/src/vsr/replica.zig +169 -31
- package/src/tigerbeetle/src/vsr/superblock.zig +356 -629
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -6
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +414 -151
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +44 -9
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
- package/src/tigerbeetle/src/vsr.zig +19 -5
- package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
- package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
- package/src/tigerbeetle/src/vopr_hub/README.md +0 -58
- package/src/tigerbeetle/src/vopr_hub/SETUP.md +0 -199
- package/src/tigerbeetle/src/vopr_hub/go.mod +0 -3
- package/src/tigerbeetle/src/vopr_hub/main.go +0 -1022
- package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +0 -3
- 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.
|
|
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": "
|
|
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
|
|
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
|
|
@@ -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 =
|
|
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 =
|
|
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
|
-
.
|
|
113
|
+
.pending_id = 0,
|
|
114
|
+
.timeout = 0,
|
|
114
115
|
.ledger = 2,
|
|
116
|
+
.code = 1,
|
|
115
117
|
.flags = .{},
|
|
116
118
|
.amount = 1,
|
|
117
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
18
|
-
comptime MessageBus: type,
|
|
44
|
+
comptime Client: type,
|
|
19
45
|
) type {
|
|
20
46
|
return struct {
|
|
21
47
|
const Context = @This();
|
|
22
|
-
const Thread = ThreadType(
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
)
|
|
41
|
-
|
|
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
|
-
|
|
53
|
-
context.thread.init(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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.
|
|
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:
|
|
7
|
+
user_data: ?*anyopaque,
|
|
8
8
|
operation: u8,
|
|
9
9
|
status: Status,
|
|
10
10
|
data_size: u32,
|
|
11
|
-
data:
|
|
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
|
|
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
|
-
|
|
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) {
|