tigerbeetle 0.0.38 → 0.0.39
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/ext/tb_client/tigerbeetle/build.zig +14 -45
- data/ext/tb_client/tigerbeetle/src/build/fetch.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2 -5
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +4 -1
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +56 -29
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +2 -24
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +7 -8
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +8 -3
- data/ext/tb_client/tigerbeetle/src/stdx/huge_page_allocator.zig +115 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +4 -4
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +61 -62
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +39 -20
- data/ext/tb_client/tigerbeetle/src/tidy.zig +2 -1
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +1 -1
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +1 -1
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +10 -9
- data/lib/tigerbeetle/version.rb +2 -2
- metadata +3 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const builtin = @import("builtin");
|
|
3
|
+
const stdx = @import("stdx.zig");
|
|
4
|
+
const Allocator = std.mem.Allocator;
|
|
5
|
+
|
|
6
|
+
const log = std.log.scoped(.allocator);
|
|
7
|
+
|
|
8
|
+
const page_allocator_vtable = std.heap.page_allocator.vtable;
|
|
9
|
+
|
|
10
|
+
/// Like `std.heap.page_allocator`, but on Linux applies `MADV_HUGEPAGE` to
|
|
11
|
+
/// allocated regions so that the kernel may back them with 2 MiB transparent
|
|
12
|
+
/// huge pages, reducing TLB pressure for large allocations.
|
|
13
|
+
///
|
|
14
|
+
/// Only `alloc` is intercepted. `resize` and `remap` inherit the VMA flags
|
|
15
|
+
/// (including `VM_HUGEPAGE`) set on the original mapping, so they need no
|
|
16
|
+
/// additional `madvise` call.
|
|
17
|
+
///
|
|
18
|
+
/// On non-Linux targets this is identical to `std.heap.page_allocator`.
|
|
19
|
+
pub const huge_page_allocator: Allocator = .{
|
|
20
|
+
.ptr = std.heap.page_allocator.ptr,
|
|
21
|
+
.vtable = if (builtin.target.os.tag == .linux) &vtable else page_allocator_vtable,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const vtable: Allocator.VTable = .{
|
|
25
|
+
.alloc = alloc,
|
|
26
|
+
.resize = page_allocator_vtable.resize,
|
|
27
|
+
.remap = page_allocator_vtable.remap,
|
|
28
|
+
.free = page_allocator_vtable.free,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
fn alloc(context: *anyopaque, n: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 {
|
|
32
|
+
const ptr = page_allocator_vtable.alloc(context, n, alignment, ra) orelse return null;
|
|
33
|
+
// This is just a hint, so if it fails we can safely ignore it.
|
|
34
|
+
std.posix.madvise(@alignCast(ptr), n, std.posix.MADV.HUGEPAGE) catch {
|
|
35
|
+
log.warn("Transparent Huge Pages (THP) are disabled.", .{});
|
|
36
|
+
};
|
|
37
|
+
return ptr;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const testing = std.testing;
|
|
41
|
+
const assert = std.debug.assert;
|
|
42
|
+
|
|
43
|
+
/// Checks /proc/self/smaps for the "hg" VmFlag on the mapping containing `ptr`.
|
|
44
|
+
fn verify_address_is_huge_page(ptr: [*]const u8) !bool {
|
|
45
|
+
assert(builtin.target.os.tag == .linux);
|
|
46
|
+
const addr = @intFromPtr(ptr);
|
|
47
|
+
|
|
48
|
+
var file = try std.fs.openFileAbsolute("/proc/self/smaps", .{});
|
|
49
|
+
defer file.close();
|
|
50
|
+
|
|
51
|
+
const content = try file.readToEndAlloc(testing.allocator, 10 * 1024 * 1024);
|
|
52
|
+
defer testing.allocator.free(content);
|
|
53
|
+
|
|
54
|
+
var lines = std.mem.splitScalar(u8, content, '\n');
|
|
55
|
+
|
|
56
|
+
// Find the mapping header that contains our address.
|
|
57
|
+
while (lines.next()) |line| {
|
|
58
|
+
if (parse_mapping_range(line)) |range| {
|
|
59
|
+
if (addr >= range.min and addr < range.max) {
|
|
60
|
+
// Scan subsequent lines for VmFlags within this mapping.
|
|
61
|
+
while (lines.next()) |detail| {
|
|
62
|
+
if (stdx.cut_prefix(detail, "VmFlags:")) |rest| {
|
|
63
|
+
return stdx.cut(rest, "hg") != null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn parse_mapping_range(line: []const u8) ?struct { min: u64, max: u64 } {
|
|
73
|
+
const addr_range, _ = stdx.cut(line, " ") orelse return null;
|
|
74
|
+
const addr_hex_min, const addr_hex_max = stdx.cut(addr_range, "-") orelse return null;
|
|
75
|
+
const addr_min = std.fmt.parseUnsigned(u64, addr_hex_min, 16) catch return null;
|
|
76
|
+
const addr_max = std.fmt.parseUnsigned(u64, addr_hex_max, 16) catch return null;
|
|
77
|
+
return .{ .min = addr_min, .max = addr_max };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
test "huge_page_allocator: basic alloc and free" {
|
|
81
|
+
const slice = try huge_page_allocator.alloc(u8, 4096);
|
|
82
|
+
defer huge_page_allocator.free(slice);
|
|
83
|
+
|
|
84
|
+
@memset(slice, 0xab);
|
|
85
|
+
try testing.expectEqual(@as(u8, 0xab), slice[0]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test "huge_page_allocator: large THP-eligible allocation" {
|
|
89
|
+
// 4 MiB — large enough for THP promotion on Linux.
|
|
90
|
+
const size = 4 * 1024 * 1024;
|
|
91
|
+
const slice = try huge_page_allocator.alloc(u8, size);
|
|
92
|
+
defer huge_page_allocator.free(slice);
|
|
93
|
+
|
|
94
|
+
@memset(slice, 0xcd);
|
|
95
|
+
try testing.expectEqual(@as(u8, 0xcd), slice[size - 1]);
|
|
96
|
+
|
|
97
|
+
if (builtin.target.os.tag == .linux) {
|
|
98
|
+
// Verify that MADV_HUGEPAGE was applied by checking VmFlags in /proc/self/smaps.
|
|
99
|
+
// The "hg" flag means the process requested hugepages via madvise — this is
|
|
100
|
+
// deterministic regardless of whether the kernel actually promoted the pages.
|
|
101
|
+
try testing.expect(try verify_address_is_huge_page(slice.ptr));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
test "huge_page_allocator: as ArenaAllocator backing" {
|
|
106
|
+
var arena = std.heap.ArenaAllocator.init(huge_page_allocator);
|
|
107
|
+
defer arena.deinit();
|
|
108
|
+
|
|
109
|
+
const alloc1 = try arena.allocator().alloc(u8, 1024);
|
|
110
|
+
const alloc2 = try arena.allocator().alloc(u8, 2048);
|
|
111
|
+
@memset(alloc1, 1);
|
|
112
|
+
@memset(alloc2, 2);
|
|
113
|
+
try testing.expectEqual(@as(u8, 1), alloc1[0]);
|
|
114
|
+
try testing.expectEqual(@as(u8, 2), alloc2[0]);
|
|
115
|
+
}
|
|
@@ -16,6 +16,8 @@ pub const Snap = @import("testing/snaptest.zig").Snap;
|
|
|
16
16
|
pub const ZipfianGenerator = @import("zipfian.zig").ZipfianGenerator;
|
|
17
17
|
pub const ZipfianShuffled = @import("zipfian.zig").ZipfianShuffled;
|
|
18
18
|
|
|
19
|
+
pub const huge_page_allocator = @import("huge_page_allocator.zig").huge_page_allocator;
|
|
20
|
+
|
|
19
21
|
pub const aegis = @import("vendored/aegis.zig");
|
|
20
22
|
pub const dbg = @import("debug.zig").dbg;
|
|
21
23
|
pub const flags = @import("flags.zig").parse;
|
|
@@ -1144,6 +1146,7 @@ pub fn term_from_status(status: u32) std.process.Child.Term {
|
|
|
1144
1146
|
}
|
|
1145
1147
|
|
|
1146
1148
|
comptime {
|
|
1149
|
+
_ = @import("huge_page_allocator.zig");
|
|
1147
1150
|
_ = @import("vendored/aegis.zig");
|
|
1148
1151
|
_ = @import("bit_set.zig");
|
|
1149
1152
|
_ = @import("bounded_array.zig");
|
|
@@ -5,10 +5,10 @@ pub const vsr = @import("../../constants.zig");
|
|
|
5
5
|
|
|
6
6
|
pub const vortex = struct {
|
|
7
7
|
pub const cluster_id = 0;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
);
|
|
8
|
+
// Maximum number of connections *per replica*.
|
|
9
|
+
// -1 since replicas don't connect to themselves.
|
|
10
|
+
// +1 for the single driver/client.
|
|
11
|
+
pub const connections_count_max = (vsr.replicas_max - 1) + 1;
|
|
12
12
|
|
|
13
13
|
// We allow the cluster to not make progress processing requests for this amount of time.
|
|
14
14
|
// After that it's considered a test failure.
|
|
@@ -34,19 +34,17 @@ const Faults = struct {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
delay: ?Delay = null,
|
|
37
|
-
lose: ?Ratio = null,
|
|
38
37
|
corrupt: ?Ratio = null,
|
|
39
38
|
|
|
40
39
|
// Others not implemented: duplication, reordering, rate
|
|
41
40
|
|
|
42
41
|
pub fn heal(faults: *Faults) void {
|
|
43
42
|
faults.delay = null;
|
|
44
|
-
faults.lose = null;
|
|
45
43
|
faults.corrupt = null;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
pub fn is_healed(faults: *const Faults) bool {
|
|
49
|
-
return faults.delay == null and faults.
|
|
47
|
+
return faults.delay == null and faults.corrupt == null;
|
|
50
48
|
}
|
|
51
49
|
};
|
|
52
50
|
|
|
@@ -70,11 +68,13 @@ const Pipe = struct {
|
|
|
70
68
|
) void {
|
|
71
69
|
assert(pipe.connection.state == .proxying);
|
|
72
70
|
assert(pipe.status == .idle);
|
|
71
|
+
assert(pipe.input == null);
|
|
72
|
+
assert(pipe.output == null);
|
|
73
|
+
assert(pipe.recv_size == 0);
|
|
74
|
+
assert(pipe.send_size == 0);
|
|
73
75
|
|
|
74
76
|
pipe.input = input;
|
|
75
77
|
pipe.output = output;
|
|
76
|
-
pipe.recv_size = 0;
|
|
77
|
-
pipe.send_size = 0;
|
|
78
78
|
|
|
79
79
|
// Kick off the recv/send loop.
|
|
80
80
|
pipe.recv();
|
|
@@ -94,16 +94,19 @@ const Pipe = struct {
|
|
|
94
94
|
pipe.connection.io.recv(
|
|
95
95
|
*Pipe,
|
|
96
96
|
pipe,
|
|
97
|
-
|
|
97
|
+
recv_callback,
|
|
98
98
|
&pipe.recv_completion,
|
|
99
99
|
pipe.input.?,
|
|
100
100
|
pipe.buffer[0..],
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
fn
|
|
104
|
+
fn recv_callback(pipe: *Pipe, _: *IO.Completion, result: IO.RecvError!usize) void {
|
|
105
105
|
assert(pipe.recv_size == 0);
|
|
106
106
|
assert(pipe.send_size == 0);
|
|
107
|
+
assert(pipe.connection.state != .free);
|
|
108
|
+
assert(pipe.connection.state != .accepting);
|
|
109
|
+
assert(pipe.connection.state != .connecting);
|
|
107
110
|
|
|
108
111
|
assert(pipe.status == .recv);
|
|
109
112
|
pipe.status = .idle;
|
|
@@ -125,18 +128,6 @@ const Pipe = struct {
|
|
|
125
128
|
return pipe.connection.try_close();
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
if (pipe.connection.network.faults.lose) |lose| {
|
|
129
|
-
if (pipe.connection.network.prng.chance(lose)) {
|
|
130
|
-
log.debug("losing {d} bytes ({d},{d})", .{
|
|
131
|
-
pipe.recv_size,
|
|
132
|
-
pipe.connection.replica_index,
|
|
133
|
-
pipe.connection.connection_index,
|
|
134
|
-
});
|
|
135
|
-
pipe.recv();
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
131
|
if (pipe.connection.network.faults.corrupt) |corrupt| {
|
|
141
132
|
if (pipe.connection.network.prng.chance(corrupt)) {
|
|
142
133
|
switch (pipe.connection.network.prng.enum_uniform(enum { shuffle, zero })) {
|
|
@@ -180,14 +171,23 @@ const Pipe = struct {
|
|
|
180
171
|
|
|
181
172
|
assert(pipe.status == .idle);
|
|
182
173
|
pipe.status = .send_timeout;
|
|
183
|
-
pipe.io.timeout(
|
|
174
|
+
pipe.io.timeout(
|
|
175
|
+
*Pipe,
|
|
176
|
+
pipe,
|
|
177
|
+
timeout_callback,
|
|
178
|
+
&pipe.send_completion,
|
|
179
|
+
timeout_duration_ns,
|
|
180
|
+
);
|
|
184
181
|
} else {
|
|
185
182
|
pipe.send();
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
185
|
|
|
189
|
-
fn
|
|
186
|
+
fn timeout_callback(pipe: *Pipe, _: *IO.Completion, result: IO.TimeoutError!void) void {
|
|
190
187
|
assert(pipe.status == .send_timeout);
|
|
188
|
+
assert(pipe.connection.state != .free);
|
|
189
|
+
assert(pipe.connection.state != .accepting);
|
|
190
|
+
assert(pipe.connection.state != .connecting);
|
|
191
191
|
pipe.status = .idle;
|
|
192
192
|
|
|
193
193
|
if (pipe.connection.state != .proxying) return;
|
|
@@ -206,15 +206,18 @@ const Pipe = struct {
|
|
|
206
206
|
pipe.io.send(
|
|
207
207
|
*Pipe,
|
|
208
208
|
pipe,
|
|
209
|
-
|
|
209
|
+
send_callback,
|
|
210
210
|
&pipe.send_completion,
|
|
211
211
|
pipe.output.?,
|
|
212
212
|
pipe.buffer[pipe.send_size..pipe.recv_size],
|
|
213
213
|
);
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
fn
|
|
216
|
+
fn send_callback(pipe: *Pipe, _: *IO.Completion, result: IO.SendError!usize) void {
|
|
217
217
|
assert(pipe.send_size < pipe.recv_size);
|
|
218
|
+
assert(pipe.connection.state != .free);
|
|
219
|
+
assert(pipe.connection.state != .accepting);
|
|
220
|
+
assert(pipe.connection.state != .connecting);
|
|
218
221
|
|
|
219
222
|
assert(pipe.status == .send);
|
|
220
223
|
pipe.status = .idle;
|
|
@@ -268,20 +271,14 @@ const Connection = struct {
|
|
|
268
271
|
connect_completion: IO.Completion = undefined,
|
|
269
272
|
close_completion: IO.Completion = undefined,
|
|
270
273
|
|
|
271
|
-
fn
|
|
274
|
+
fn accept_callback(
|
|
272
275
|
connection: *Connection,
|
|
273
276
|
_: *IO.Completion,
|
|
274
277
|
result: IO.AcceptError!std.posix.socket_t,
|
|
275
278
|
) void {
|
|
276
279
|
assert(connection.state == .accepting);
|
|
277
|
-
defer assert(connection.state == .connecting);
|
|
278
|
-
|
|
279
280
|
assert(connection.origin_fd == null);
|
|
280
|
-
defer assert(connection.origin_fd != null);
|
|
281
|
-
|
|
282
281
|
assert(connection.remote_fd == null);
|
|
283
|
-
defer assert(connection.remote_fd != null);
|
|
284
|
-
|
|
285
282
|
assert(connection.remote_address != null);
|
|
286
283
|
|
|
287
284
|
const fd = result catch |err| {
|
|
@@ -305,21 +302,21 @@ const Connection = struct {
|
|
|
305
302
|
});
|
|
306
303
|
return connection.try_close();
|
|
307
304
|
};
|
|
308
|
-
connection.remote_fd = remote_fd;
|
|
309
305
|
|
|
306
|
+
connection.remote_fd = remote_fd;
|
|
310
307
|
connection.state = .connecting;
|
|
311
308
|
|
|
312
309
|
connection.io.connect(
|
|
313
310
|
*Connection,
|
|
314
311
|
connection,
|
|
315
|
-
Connection.
|
|
312
|
+
Connection.connect_callback,
|
|
316
313
|
&connection.connect_completion,
|
|
317
314
|
connection.remote_fd.?,
|
|
318
315
|
connection.remote_address.?,
|
|
319
316
|
);
|
|
320
317
|
}
|
|
321
318
|
|
|
322
|
-
fn
|
|
319
|
+
fn connect_callback(
|
|
323
320
|
connection: *Connection,
|
|
324
321
|
_: *IO.Completion,
|
|
325
322
|
result: IO.ConnectError!void,
|
|
@@ -357,41 +354,37 @@ const Connection = struct {
|
|
|
357
354
|
connection.connection_index,
|
|
358
355
|
});
|
|
359
356
|
connection.state = .closing;
|
|
360
|
-
std.posix.shutdown(connection.origin_fd.?, .both) catch |err| {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}),
|
|
366
|
-
}
|
|
357
|
+
std.posix.shutdown(connection.origin_fd.?, .both) catch |err| switch (err) {
|
|
358
|
+
error.SocketNotConnected => {},
|
|
359
|
+
else => log.warn("shutdown origin_fd ({d},{d}) failed: {}", .{
|
|
360
|
+
connection.replica_index, connection.connection_index, err,
|
|
361
|
+
}),
|
|
367
362
|
};
|
|
368
|
-
std.posix.shutdown(connection.remote_fd.?, .both) catch |err| {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}),
|
|
374
|
-
}
|
|
363
|
+
std.posix.shutdown(connection.remote_fd.?, .both) catch |err| switch (err) {
|
|
364
|
+
error.SocketNotConnected => {},
|
|
365
|
+
else => log.warn("shutdown remote_fd ({d},{d}) failed: {}", .{
|
|
366
|
+
connection.replica_index, connection.connection_index, err,
|
|
367
|
+
}),
|
|
375
368
|
};
|
|
376
369
|
}
|
|
377
370
|
|
|
378
|
-
if (
|
|
371
|
+
if (has_inflight_operations) {
|
|
372
|
+
// Network.tick() will keep calling try_close().
|
|
373
|
+
assert(connection.state == .closing);
|
|
374
|
+
} else {
|
|
379
375
|
// Kick off the close sequence.
|
|
380
376
|
connection.state = .closing_origin;
|
|
381
377
|
connection.io.close(
|
|
382
378
|
*Connection,
|
|
383
379
|
connection,
|
|
384
|
-
|
|
380
|
+
close_origin_callback,
|
|
385
381
|
&connection.close_completion,
|
|
386
382
|
connection.origin_fd.?,
|
|
387
383
|
);
|
|
388
|
-
return;
|
|
389
384
|
}
|
|
390
|
-
|
|
391
|
-
assert(connection.state == .closing);
|
|
392
385
|
}
|
|
393
386
|
|
|
394
|
-
fn
|
|
387
|
+
fn close_origin_callback(
|
|
395
388
|
connection: *Connection,
|
|
396
389
|
_: *IO.Completion,
|
|
397
390
|
result: IO.CloseError!void,
|
|
@@ -403,7 +396,7 @@ const Connection = struct {
|
|
|
403
396
|
defer assert(connection.origin_fd == null);
|
|
404
397
|
|
|
405
398
|
result catch |err| {
|
|
406
|
-
log.warn("
|
|
399
|
+
log.warn("close_origin_callback ({d},{d}) error: {any}", .{
|
|
407
400
|
connection.replica_index,
|
|
408
401
|
connection.connection_index,
|
|
409
402
|
err,
|
|
@@ -415,13 +408,13 @@ const Connection = struct {
|
|
|
415
408
|
connection.io.close(
|
|
416
409
|
*Connection,
|
|
417
410
|
connection,
|
|
418
|
-
|
|
411
|
+
close_remote_callback,
|
|
419
412
|
&connection.close_completion,
|
|
420
413
|
connection.remote_fd.?,
|
|
421
414
|
);
|
|
422
415
|
}
|
|
423
416
|
|
|
424
|
-
fn
|
|
417
|
+
fn close_remote_callback(
|
|
425
418
|
connection: *Connection,
|
|
426
419
|
_: *IO.Completion,
|
|
427
420
|
result: IO.CloseError!void,
|
|
@@ -433,19 +426,22 @@ const Connection = struct {
|
|
|
433
426
|
defer assert(connection.remote_fd == null);
|
|
434
427
|
|
|
435
428
|
result catch |err| {
|
|
436
|
-
log.warn("
|
|
429
|
+
log.warn("close_remote_callback ({d},{d}) error: {any}", .{
|
|
437
430
|
connection.replica_index,
|
|
438
431
|
connection.connection_index,
|
|
439
432
|
err,
|
|
440
433
|
});
|
|
441
434
|
};
|
|
442
435
|
|
|
443
|
-
log.debug("
|
|
436
|
+
log.debug("close_remote_callback ({d},{d}): marking connection as free", .{
|
|
444
437
|
connection.replica_index,
|
|
445
438
|
connection.connection_index,
|
|
446
439
|
});
|
|
447
440
|
connection.state = .free;
|
|
448
441
|
connection.remote_fd = null;
|
|
442
|
+
connection.remote_address = null;
|
|
443
|
+
connection.origin_to_remote_pipe = .{ .io = connection.io, .connection = connection };
|
|
444
|
+
connection.remote_to_origin_pipe = .{ .io = connection.io, .connection = connection };
|
|
449
445
|
}
|
|
450
446
|
};
|
|
451
447
|
|
|
@@ -543,8 +539,10 @@ pub const Network = struct {
|
|
|
543
539
|
}
|
|
544
540
|
|
|
545
541
|
pub fn tick(network: *Network) void {
|
|
546
|
-
for (network.proxies) |*proxy| {
|
|
542
|
+
for (network.proxies, 0..) |*proxy, replica_index| {
|
|
547
543
|
for (&proxy.connections) |*connection| {
|
|
544
|
+
assert(connection.replica_index == replica_index);
|
|
545
|
+
|
|
548
546
|
if (connection.state == .closing) {
|
|
549
547
|
connection.try_close();
|
|
550
548
|
continue;
|
|
@@ -555,6 +553,9 @@ pub const Network = struct {
|
|
|
555
553
|
if (connection.state == .free) {
|
|
556
554
|
assert(connection.origin_to_remote_pipe.status == .idle);
|
|
557
555
|
assert(connection.remote_to_origin_pipe.status == .idle);
|
|
556
|
+
assert(connection.origin_fd == null);
|
|
557
|
+
assert(connection.remote_fd == null);
|
|
558
|
+
assert(connection.remote_address == null);
|
|
558
559
|
|
|
559
560
|
log.debug("accepting ({d},{d})", .{
|
|
560
561
|
connection.replica_index,
|
|
@@ -563,13 +564,11 @@ pub const Network = struct {
|
|
|
563
564
|
|
|
564
565
|
connection.state = .accepting;
|
|
565
566
|
connection.remote_address = proxy.remote_address;
|
|
566
|
-
connection.origin_fd = null;
|
|
567
|
-
connection.remote_fd = null;
|
|
568
567
|
|
|
569
568
|
network.io.accept(
|
|
570
569
|
*Connection,
|
|
571
570
|
connection,
|
|
572
|
-
Connection.
|
|
571
|
+
Connection.accept_callback,
|
|
573
572
|
&connection.accept_completion,
|
|
574
573
|
proxy.accept_fd,
|
|
575
574
|
);
|
|
@@ -296,15 +296,13 @@ const Supervisor = struct {
|
|
|
296
296
|
const too_slow_request = supervisor.workload.find_slow_request_since(start_ns);
|
|
297
297
|
|
|
298
298
|
if (no_finished_requests) {
|
|
299
|
-
|
|
299
|
+
fatal(.liveness, "liveness check: no finished requests after {d} seconds", .{
|
|
300
300
|
constants.vortex.liveness_requirement_seconds,
|
|
301
301
|
});
|
|
302
|
-
return error.TestFailed;
|
|
303
302
|
}
|
|
304
303
|
|
|
305
304
|
if (too_slow_request) |_| {
|
|
306
|
-
|
|
307
|
-
return error.TestFailed;
|
|
305
|
+
fatal(.request_slow, "liveness check: too slow request", .{});
|
|
308
306
|
}
|
|
309
307
|
}
|
|
310
308
|
|
|
@@ -335,7 +333,6 @@ const Supervisor = struct {
|
|
|
335
333
|
replica_pause,
|
|
336
334
|
replica_resume,
|
|
337
335
|
network_delay,
|
|
338
|
-
network_lose,
|
|
339
336
|
network_corrupt,
|
|
340
337
|
network_heal,
|
|
341
338
|
quiesce,
|
|
@@ -348,7 +345,6 @@ const Supervisor = struct {
|
|
|
348
345
|
.replica_pause = if (running_replicas.len > 0) 3 else 0,
|
|
349
346
|
.replica_resume = if (paused_replicas.len > 0) 10 else 0,
|
|
350
347
|
.network_delay = if (supervisor.network.faults.delay == null) 3 else 0,
|
|
351
|
-
.network_lose = if (supervisor.network.faults.lose == null) 3 else 0,
|
|
352
348
|
.network_corrupt = if (supervisor.network.faults.corrupt == null) 3 else 0,
|
|
353
349
|
.network_heal = if (!supervisor.network.faults.is_healed()) 10 else 0,
|
|
354
350
|
.quiesce = if (faulty_replica_count > 0 or
|
|
@@ -388,11 +384,6 @@ const Supervisor = struct {
|
|
|
388
384
|
};
|
|
389
385
|
log.info("injecting network delays: {any}", .{supervisor.network.faults});
|
|
390
386
|
},
|
|
391
|
-
.network_lose => {
|
|
392
|
-
supervisor.network.faults.lose =
|
|
393
|
-
ratio(supervisor.prng.range_inclusive(u8, 1, 10), 100);
|
|
394
|
-
log.info("injecting network loss: {any}", .{supervisor.network.faults});
|
|
395
|
-
},
|
|
396
387
|
.network_corrupt => {
|
|
397
388
|
supervisor.network.faults.corrupt =
|
|
398
389
|
ratio(supervisor.prng.range_inclusive(u8, 1, 10), 100);
|
|
@@ -440,15 +431,18 @@ const Supervisor = struct {
|
|
|
440
431
|
// success or failure.
|
|
441
432
|
std.posix.exit(@intCast(128 + term.Signal));
|
|
442
433
|
} else {
|
|
443
|
-
|
|
434
|
+
fatal(.replica_exit_result, "replica exited with: {}", .{term});
|
|
444
435
|
}
|
|
445
436
|
}
|
|
446
437
|
}
|
|
447
438
|
}
|
|
448
439
|
|
|
449
440
|
if (supervisor.workload.process.wait_nonblocking()) |code| {
|
|
450
|
-
|
|
451
|
-
|
|
441
|
+
fatal(
|
|
442
|
+
.workload_exit_early,
|
|
443
|
+
"workload terminated by itself: code={}",
|
|
444
|
+
.{code},
|
|
445
|
+
);
|
|
452
446
|
}
|
|
453
447
|
} else blk: {
|
|
454
448
|
log.info("terminating workload due to max duration", .{});
|
|
@@ -460,14 +454,20 @@ const Supervisor = struct {
|
|
|
460
454
|
switch (signal) {
|
|
461
455
|
std.posix.SIG.KILL => log.info("workload terminated as requested", .{}),
|
|
462
456
|
else => {
|
|
463
|
-
|
|
464
|
-
|
|
457
|
+
fatal(
|
|
458
|
+
.workload_exit_result,
|
|
459
|
+
"workload exited unexpectedly with signal {d}",
|
|
460
|
+
.{signal},
|
|
461
|
+
);
|
|
465
462
|
},
|
|
466
463
|
}
|
|
467
464
|
},
|
|
468
465
|
else => {
|
|
469
|
-
|
|
470
|
-
|
|
466
|
+
fatal(
|
|
467
|
+
.workload_exit_result,
|
|
468
|
+
"unexpected workload result: {any}",
|
|
469
|
+
.{workload_result},
|
|
470
|
+
);
|
|
471
471
|
},
|
|
472
472
|
}
|
|
473
473
|
}
|
|
@@ -724,8 +724,7 @@ const Workload = struct {
|
|
|
724
724
|
if (workload.process.state != .running) return;
|
|
725
725
|
|
|
726
726
|
const count = result catch |err| {
|
|
727
|
-
|
|
728
|
-
return;
|
|
727
|
+
fatal(.workload_read_error, "couldn't read from workload stdout: {}", .{err});
|
|
729
728
|
};
|
|
730
729
|
|
|
731
730
|
workload.read_progress += count;
|
|
@@ -764,3 +763,23 @@ const Workload = struct {
|
|
|
764
763
|
return null;
|
|
765
764
|
}
|
|
766
765
|
};
|
|
766
|
+
|
|
767
|
+
const FatalReason = enum(u8) {
|
|
768
|
+
workload_exit_early = 10,
|
|
769
|
+
workload_exit_result = 11,
|
|
770
|
+
workload_read_error = 12,
|
|
771
|
+
replica_exit_result = 13,
|
|
772
|
+
liveness = 14,
|
|
773
|
+
request_slow = 15,
|
|
774
|
+
|
|
775
|
+
pub fn exit_status(reason: FatalReason) u8 {
|
|
776
|
+
return @intFromEnum(reason);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
fn fatal(reason: FatalReason, comptime fmt: []const u8, args: anytype) noreturn {
|
|
781
|
+
log.err(fmt, args);
|
|
782
|
+
const status = reason.exit_status();
|
|
783
|
+
assert(status != 0);
|
|
784
|
+
std.process.exit(status);
|
|
785
|
+
}
|
|
@@ -1220,6 +1220,7 @@ const DeadFilesDetector = struct {
|
|
|
1220
1220
|
"build_multiversion.zig",
|
|
1221
1221
|
"build.zig",
|
|
1222
1222
|
"dotnet_bindings.zig",
|
|
1223
|
+
"fetch.zig",
|
|
1223
1224
|
"file_checker.zig",
|
|
1224
1225
|
"fuzz_tests.zig",
|
|
1225
1226
|
"go_bindings.zig",
|
|
@@ -1232,10 +1233,10 @@ const DeadFilesDetector = struct {
|
|
|
1232
1233
|
"node.zig",
|
|
1233
1234
|
"page_writer.zig",
|
|
1234
1235
|
"python_bindings.zig",
|
|
1236
|
+
"rust_bindings.zig",
|
|
1235
1237
|
"scripts.zig",
|
|
1236
1238
|
"search_index_writer.zig",
|
|
1237
1239
|
"service_worker_writer.zig",
|
|
1238
|
-
"rust_bindings.zig",
|
|
1239
1240
|
"single_page_writer.zig",
|
|
1240
1241
|
"tb_client_header.zig",
|
|
1241
1242
|
"unit_tests.zig",
|
|
@@ -63,7 +63,7 @@ pub const std_options: std.Options = .{
|
|
|
63
63
|
pub fn main() !void {
|
|
64
64
|
if (builtin.os.tag == .windows) try vsr.multiversion.wait_for_parent_to_exit();
|
|
65
65
|
|
|
66
|
-
var arena_instance = std.heap.ArenaAllocator.init(
|
|
66
|
+
var arena_instance = std.heap.ArenaAllocator.init(stdx.huge_page_allocator);
|
|
67
67
|
defer arena_instance.deinit();
|
|
68
68
|
|
|
69
69
|
// Arena is an implementation detail, all memory must be freed.
|
|
@@ -338,7 +338,7 @@ pub fn JournalType(comptime Replica: type, comptime Storage: type) type {
|
|
|
338
338
|
))[0..constants.journal_iops_write_max];
|
|
339
339
|
errdefer allocator.free(write_headers_sectors);
|
|
340
340
|
|
|
341
|
-
log.
|
|
341
|
+
log.info("{}: slot_count={} size={} headers_size={} prepares_size={}", .{
|
|
342
342
|
replica,
|
|
343
343
|
slot_count,
|
|
344
344
|
std.fmt.fmtIntSizeBin(write_ahead_log_zone_size),
|