tigerbeetle-node 0.4.2 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/dist/benchmark.js.map +1 -1
- package/dist/index.d.ts +18 -16
- package/dist/index.js +35 -13
- package/dist/index.js.map +1 -1
- package/dist/test.js +12 -0
- package/dist/test.js.map +1 -1
- package/package.json +2 -2
- package/scripts/postinstall.sh +2 -2
- package/src/benchmark.ts +2 -2
- package/src/index.ts +29 -4
- package/src/node.zig +120 -17
- package/src/test.ts +14 -0
- package/src/tigerbeetle/scripts/install.sh +1 -1
- package/src/tigerbeetle/scripts/install_zig.bat +109 -0
- package/src/tigerbeetle/scripts/install_zig.sh +4 -2
- package/src/tigerbeetle/scripts/lint.zig +8 -2
- package/src/tigerbeetle/scripts/vopr.bat +48 -0
- package/src/tigerbeetle/src/benchmark.zig +10 -8
- package/src/tigerbeetle/src/cli.zig +6 -4
- package/src/tigerbeetle/src/config.zig +2 -2
- package/src/tigerbeetle/src/demo.zig +119 -89
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +5 -3
- package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +2 -3
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
- package/src/tigerbeetle/src/demo_04_create_transfers_two_phase_commit.zig +5 -3
- package/src/tigerbeetle/src/demo_05_accept_transfers.zig +5 -3
- package/src/tigerbeetle/src/demo_06_reject_transfers.zig +5 -3
- package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +7 -0
- package/src/tigerbeetle/src/io/benchmark.zig +238 -0
- package/src/tigerbeetle/src/{io_darwin.zig → io/darwin.zig} +89 -124
- package/src/tigerbeetle/src/io/linux.zig +933 -0
- package/src/tigerbeetle/src/io/test.zig +621 -0
- package/src/tigerbeetle/src/io.zig +7 -1328
- package/src/tigerbeetle/src/main.zig +18 -10
- package/src/tigerbeetle/src/message_bus.zig +43 -60
- package/src/tigerbeetle/src/message_pool.zig +3 -2
- package/src/tigerbeetle/src/ring_buffer.zig +135 -68
- package/src/tigerbeetle/src/simulator.zig +41 -37
- package/src/tigerbeetle/src/state_machine.zig +851 -26
- package/src/tigerbeetle/src/storage.zig +49 -46
- package/src/tigerbeetle/src/test/cluster.zig +2 -2
- package/src/tigerbeetle/src/test/message_bus.zig +6 -6
- package/src/tigerbeetle/src/test/network.zig +3 -3
- package/src/tigerbeetle/src/test/packet_simulator.zig +32 -29
- package/src/tigerbeetle/src/test/state_checker.zig +2 -2
- package/src/tigerbeetle/src/test/state_machine.zig +4 -0
- package/src/tigerbeetle/src/test/storage.zig +39 -19
- package/src/tigerbeetle/src/test/time.zig +2 -2
- package/src/tigerbeetle/src/tigerbeetle.zig +6 -129
- package/src/tigerbeetle/src/time.zig +6 -5
- package/src/tigerbeetle/src/vsr/client.zig +11 -11
- package/src/tigerbeetle/src/vsr/clock.zig +26 -43
- package/src/tigerbeetle/src/vsr/journal.zig +7 -6
- package/src/tigerbeetle/src/vsr/marzullo.zig +6 -3
- package/src/tigerbeetle/src/vsr/replica.zig +51 -48
- package/src/tigerbeetle/src/vsr.zig +24 -20
- package/src/translate.zig +55 -55
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const builtin = @import("builtin");
|
|
2
3
|
const os = std.os;
|
|
3
4
|
const Allocator = std.mem.Allocator;
|
|
4
5
|
const assert = std.debug.assert;
|
|
5
6
|
const log = std.log.scoped(.storage);
|
|
6
7
|
|
|
7
8
|
const IO = @import("io.zig").IO;
|
|
8
|
-
const is_darwin =
|
|
9
|
+
const is_darwin = builtin.target.isDarwin();
|
|
9
10
|
|
|
10
11
|
const config = @import("config.zig");
|
|
11
12
|
const vsr = @import("vsr.zig");
|
|
@@ -90,7 +91,7 @@ pub const Storage = struct {
|
|
|
90
91
|
buffer: []u8,
|
|
91
92
|
offset: u64,
|
|
92
93
|
) void {
|
|
93
|
-
|
|
94
|
+
assert_alignment(buffer, offset);
|
|
94
95
|
|
|
95
96
|
read.* = .{
|
|
96
97
|
.completion = undefined,
|
|
@@ -189,7 +190,7 @@ pub const Storage = struct {
|
|
|
189
190
|
error.Unseekable,
|
|
190
191
|
error.Unexpected,
|
|
191
192
|
=> {
|
|
192
|
-
log.
|
|
193
|
+
log.err(
|
|
193
194
|
"impossible read: offset={} buffer.len={} error={s}",
|
|
194
195
|
.{ read.offset, read.buffer.len, @errorName(err) },
|
|
195
196
|
);
|
|
@@ -201,7 +202,7 @@ pub const Storage = struct {
|
|
|
201
202
|
// We tried to read more than there really is available to read.
|
|
202
203
|
// In other words, we thought we could read beyond the end of the file descriptor.
|
|
203
204
|
// This can happen if the data file inode `size` was truncated or corrupted.
|
|
204
|
-
log.
|
|
205
|
+
log.err(
|
|
205
206
|
"short read: buffer.len={} offset={} bytes_read={}",
|
|
206
207
|
.{ read.offset, read.buffer.len, bytes_read },
|
|
207
208
|
);
|
|
@@ -227,7 +228,7 @@ pub const Storage = struct {
|
|
|
227
228
|
buffer: []const u8,
|
|
228
229
|
offset: u64,
|
|
229
230
|
) void {
|
|
230
|
-
|
|
231
|
+
assert_alignment(buffer, offset);
|
|
231
232
|
|
|
232
233
|
write.* = .{
|
|
233
234
|
.completion = undefined,
|
|
@@ -262,7 +263,7 @@ pub const Storage = struct {
|
|
|
262
263
|
// TODO: It seems like it might be possible for some filesystems to return ETIMEDOUT
|
|
263
264
|
// here. Consider handling this without panicking.
|
|
264
265
|
else => {
|
|
265
|
-
log.
|
|
266
|
+
log.err(
|
|
266
267
|
"impossible write: offset={} buffer.len={} error={s}",
|
|
267
268
|
.{ write.offset, write.buffer.len, @errorName(err) },
|
|
268
269
|
);
|
|
@@ -295,7 +296,7 @@ pub const Storage = struct {
|
|
|
295
296
|
/// If this is not the case, then the underlying syscall will return EINVAL.
|
|
296
297
|
/// We check this only at the start of a read or write because the physical sector size may be
|
|
297
298
|
/// less than our logical sector size so that partial IOs then leave us no longer aligned.
|
|
298
|
-
fn assert_alignment(
|
|
299
|
+
fn assert_alignment(buffer: []const u8, offset: u64) void {
|
|
299
300
|
assert(@ptrToInt(buffer.ptr) % config.sector_size == 0);
|
|
300
301
|
assert(buffer.len % config.sector_size == 0);
|
|
301
302
|
assert(offset % config.sector_size == 0);
|
|
@@ -330,17 +331,17 @@ pub const Storage = struct {
|
|
|
330
331
|
// TODO Use O_EXCL when opening as a block device to obtain a mandatory exclusive lock.
|
|
331
332
|
// This is much stronger than an advisory exclusive lock, and is required on some platforms.
|
|
332
333
|
|
|
333
|
-
var flags: u32 = os.
|
|
334
|
+
var flags: u32 = os.O.CLOEXEC | os.O.RDWR | os.O.DSYNC;
|
|
334
335
|
var mode: os.mode_t = 0;
|
|
335
336
|
|
|
336
337
|
// TODO Document this and investigate whether this is in fact correct to set here.
|
|
337
|
-
if (@hasDecl(os, "O_LARGEFILE")) flags |= os.
|
|
338
|
+
if (@hasDecl(os, "O_LARGEFILE")) flags |= os.O.LARGEFILE;
|
|
338
339
|
|
|
339
340
|
var direct_io_supported = false;
|
|
340
341
|
if (config.direct_io) {
|
|
341
342
|
direct_io_supported = try Storage.fs_supports_direct_io(dir_fd);
|
|
342
343
|
if (direct_io_supported) {
|
|
343
|
-
if (!is_darwin) flags |= os.
|
|
344
|
+
if (!is_darwin) flags |= os.O.DIRECT;
|
|
344
345
|
} else if (config.deployment_environment == .development) {
|
|
345
346
|
log.warn("file system does not support Direct I/O", .{});
|
|
346
347
|
} else {
|
|
@@ -352,15 +353,15 @@ pub const Storage = struct {
|
|
|
352
353
|
|
|
353
354
|
if (must_create) {
|
|
354
355
|
log.info("creating \"{s}\"...", .{relative_path});
|
|
355
|
-
flags |= os.
|
|
356
|
-
flags |= os.
|
|
356
|
+
flags |= os.O.CREAT;
|
|
357
|
+
flags |= os.O.EXCL;
|
|
357
358
|
mode = 0o666;
|
|
358
359
|
} else {
|
|
359
360
|
log.info("opening \"{s}\"...", .{relative_path});
|
|
360
361
|
}
|
|
361
362
|
|
|
362
363
|
// This is critical as we rely on O_DSYNC for fsync() whenever we write to the file:
|
|
363
|
-
assert((flags & os.
|
|
364
|
+
assert((flags & os.O.DSYNC) > 0);
|
|
364
365
|
|
|
365
366
|
// Be careful with openat(2): "If pathname is absolute, then dirfd is ignored." (man page)
|
|
366
367
|
assert(!std.fs.path.isAbsolute(relative_path));
|
|
@@ -372,12 +373,12 @@ pub const Storage = struct {
|
|
|
372
373
|
|
|
373
374
|
// On darwin, use F_NOCACHE on direct_io to disable the page cache as O_DIRECT doesn't exit.
|
|
374
375
|
if (is_darwin and config.direct_io and direct_io_supported) {
|
|
375
|
-
_ = try os.fcntl(fd, os.
|
|
376
|
+
_ = try os.fcntl(fd, os.F.NOCACHE, 1);
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
// Obtain an advisory exclusive lock that works only if all processes actually use flock().
|
|
379
380
|
// LOCK_NB means that we want to fail the lock without waiting if another process has it.
|
|
380
|
-
os.flock(fd, os.
|
|
381
|
+
os.flock(fd, os.LOCK.EX | os.LOCK.NB) catch |err| switch (err) {
|
|
381
382
|
error.WouldBlock => @panic("another process holds the data file lock"),
|
|
382
383
|
else => return err,
|
|
383
384
|
};
|
|
@@ -411,7 +412,7 @@ pub const Storage = struct {
|
|
|
411
412
|
Storage.fallocate(fd, 0, 0, @intCast(i64, size)) catch |err| switch (err) {
|
|
412
413
|
error.OperationNotSupported => {
|
|
413
414
|
log.warn("file system does not support fallocate(), an ENOSPC will panic", .{});
|
|
414
|
-
log.
|
|
415
|
+
log.info("allocating by writing to the last sector of the file instead...", .{});
|
|
415
416
|
|
|
416
417
|
const sector_size = config.sector_size;
|
|
417
418
|
const sector: [sector_size]u8 align(sector_size) = [_]u8{0} ** sector_size;
|
|
@@ -436,6 +437,8 @@ pub const Storage = struct {
|
|
|
436
437
|
const F_ALLOCATEALL = 0x4; // allocate all or nothing
|
|
437
438
|
const F_PEOFPOSMODE = 3; // use relative offset from the seek pos mode
|
|
438
439
|
const F_VOLPOSMODE = 4; // use the specified volume offset
|
|
440
|
+
_ = F_VOLPOSMODE;
|
|
441
|
+
|
|
439
442
|
const fstore_t = extern struct {
|
|
440
443
|
fst_flags: c_uint,
|
|
441
444
|
fst_posmode: c_int,
|
|
@@ -453,24 +456,24 @@ pub const Storage = struct {
|
|
|
453
456
|
};
|
|
454
457
|
|
|
455
458
|
// try to pre-allocate contiguous space and fall back to default non-continugous
|
|
456
|
-
var res = os.system.fcntl(fd, os.
|
|
457
|
-
if (os.errno(res) !=
|
|
459
|
+
var res = os.system.fcntl(fd, os.F.PREALLOCATE, @ptrToInt(&store));
|
|
460
|
+
if (os.errno(res) != .SUCCESS) {
|
|
458
461
|
store.fst_flags = F_ALLOCATEALL;
|
|
459
|
-
res = os.system.fcntl(fd, os.
|
|
462
|
+
res = os.system.fcntl(fd, os.F.PREALLOCATE, @ptrToInt(&store));
|
|
460
463
|
}
|
|
461
464
|
|
|
462
465
|
switch (os.errno(res)) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
466
|
+
.SUCCESS => {},
|
|
467
|
+
.ACCES => unreachable, // F_SETLK or F_SETSIZE of F_WRITEBOOTSTRAP
|
|
468
|
+
.BADF => return error.FileDescriptorInvalid,
|
|
469
|
+
.DEADLK => unreachable, // F_SETLKW
|
|
470
|
+
.INTR => unreachable, // F_SETLKW
|
|
471
|
+
.INVAL => return error.ArgumentsInvalid, // for F_PREALLOCATE (offset invalid)
|
|
472
|
+
.MFILE => unreachable, // F_DUPFD or F_DUPED
|
|
473
|
+
.NOLCK => unreachable, // F_SETLK or F_SETLKW
|
|
474
|
+
.OVERFLOW => return error.FileTooBig,
|
|
475
|
+
.SRCH => unreachable, // F_SETOWN
|
|
476
|
+
.OPNOTSUPP => return error.OperationNotSupported, // not reported but need same error union
|
|
474
477
|
else => |errno| return os.unexpectedErrno(errno),
|
|
475
478
|
}
|
|
476
479
|
|
|
@@ -484,19 +487,19 @@ pub const Storage = struct {
|
|
|
484
487
|
while (true) {
|
|
485
488
|
const rc = os.linux.fallocate(fd, mode, offset, length);
|
|
486
489
|
switch (os.linux.getErrno(rc)) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
490
|
+
.SUCCESS => return,
|
|
491
|
+
.BADF => return error.FileDescriptorInvalid,
|
|
492
|
+
.FBIG => return error.FileTooBig,
|
|
493
|
+
.INTR => continue,
|
|
494
|
+
.INVAL => return error.ArgumentsInvalid,
|
|
495
|
+
.IO => return error.InputOutput,
|
|
496
|
+
.NODEV => return error.NoDevice,
|
|
497
|
+
.NOSPC => return error.NoSpaceLeft,
|
|
498
|
+
.NOSYS => return error.SystemOutdated,
|
|
499
|
+
.OPNOTSUPP => return error.OperationNotSupported,
|
|
500
|
+
.PERM => return error.PermissionDenied,
|
|
501
|
+
.SPIPE => return error.Unseekable,
|
|
502
|
+
.TXTBSY => return error.FileBusy,
|
|
500
503
|
else => |errno| return os.unexpectedErrno(errno),
|
|
501
504
|
}
|
|
502
505
|
}
|
|
@@ -509,18 +512,18 @@ pub const Storage = struct {
|
|
|
509
512
|
|
|
510
513
|
const path = "fs_supports_direct_io";
|
|
511
514
|
const dir = std.fs.Dir{ .fd = dir_fd };
|
|
512
|
-
const fd = try os.openatZ(dir_fd, path, os.
|
|
515
|
+
const fd = try os.openatZ(dir_fd, path, os.O.CLOEXEC | os.O.CREAT | os.O.TRUNC, 0o666);
|
|
513
516
|
defer os.close(fd);
|
|
514
517
|
defer dir.deleteFile(path) catch {};
|
|
515
518
|
|
|
516
519
|
// F_NOCACHE on darwin is the most similar option to O_DIRECT on linux.
|
|
517
520
|
if (is_darwin) {
|
|
518
|
-
_ = os.fcntl(fd, os.
|
|
521
|
+
_ = os.fcntl(fd, os.F.NOCACHE, 1) catch return false;
|
|
519
522
|
return true;
|
|
520
523
|
}
|
|
521
524
|
|
|
522
525
|
while (true) {
|
|
523
|
-
const res = os.system.openat(dir_fd, path, os.
|
|
526
|
+
const res = os.system.openat(dir_fd, path, os.O.CLOEXEC | os.O.RDONLY | os.O.DIRECT, 0);
|
|
524
527
|
switch (os.linux.getErrno(res)) {
|
|
525
528
|
0 => {
|
|
526
529
|
os.close(@intCast(os.fd_t, res));
|
|
@@ -34,7 +34,7 @@ pub const ClusterOptions = struct {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
pub const Cluster = struct {
|
|
37
|
-
allocator:
|
|
37
|
+
allocator: mem.Allocator,
|
|
38
38
|
options: ClusterOptions,
|
|
39
39
|
|
|
40
40
|
state_machines: []StateMachine,
|
|
@@ -49,7 +49,7 @@ pub const Cluster = struct {
|
|
|
49
49
|
state_checker: StateChecker = undefined,
|
|
50
50
|
on_change_state: fn (replica: *Replica) void = undefined,
|
|
51
51
|
|
|
52
|
-
pub fn create(allocator:
|
|
52
|
+
pub fn create(allocator: mem.Allocator, prng: std.rand.Random, options: ClusterOptions) !*Cluster {
|
|
53
53
|
const cluster = try allocator.create(Cluster);
|
|
54
54
|
errdefer allocator.destroy(cluster);
|
|
55
55
|
|
|
@@ -25,11 +25,11 @@ pub const MessageBus = struct {
|
|
|
25
25
|
|
|
26
26
|
/// The callback to be called when a message is received. Use set_on_message() to set
|
|
27
27
|
/// with type safety for the context pointer.
|
|
28
|
-
on_message_callback: ?fn (context: ?*
|
|
29
|
-
on_message_context: ?*
|
|
28
|
+
on_message_callback: ?fn (context: ?*anyopaque, message: *Message) void = null,
|
|
29
|
+
on_message_context: ?*anyopaque = null,
|
|
30
30
|
|
|
31
31
|
pub fn init(
|
|
32
|
-
allocator:
|
|
32
|
+
allocator: std.mem.Allocator,
|
|
33
33
|
cluster: u32,
|
|
34
34
|
process: Process,
|
|
35
35
|
network: *Network,
|
|
@@ -43,7 +43,7 @@ pub const MessageBus = struct {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/// TODO
|
|
46
|
-
pub fn deinit(
|
|
46
|
+
pub fn deinit(_: *MessageBus) void {}
|
|
47
47
|
|
|
48
48
|
pub fn set_on_message(
|
|
49
49
|
bus: *MessageBus,
|
|
@@ -52,14 +52,14 @@ pub const MessageBus = struct {
|
|
|
52
52
|
comptime on_message: fn (context: Context, message: *Message) void,
|
|
53
53
|
) void {
|
|
54
54
|
bus.on_message_callback = struct {
|
|
55
|
-
fn wrapper(_context: ?*
|
|
55
|
+
fn wrapper(_context: ?*anyopaque, message: *Message) void {
|
|
56
56
|
on_message(@intToPtr(Context, @ptrToInt(_context)), message);
|
|
57
57
|
}
|
|
58
58
|
}.wrapper;
|
|
59
59
|
bus.on_message_context = context;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
pub fn tick(
|
|
62
|
+
pub fn tick(_: *MessageBus) void {}
|
|
63
63
|
|
|
64
64
|
pub fn get_message(bus: *MessageBus) ?*Message {
|
|
65
65
|
return bus.pool.get_message();
|
|
@@ -38,7 +38,7 @@ pub const Network = struct {
|
|
|
38
38
|
target: Process,
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
allocator:
|
|
41
|
+
allocator: std.mem.Allocator,
|
|
42
42
|
|
|
43
43
|
options: NetworkOptions,
|
|
44
44
|
packet_simulator: PacketSimulator(Packet),
|
|
@@ -47,7 +47,7 @@ pub const Network = struct {
|
|
|
47
47
|
processes: std.ArrayListUnmanaged(u128),
|
|
48
48
|
|
|
49
49
|
pub fn init(
|
|
50
|
-
allocator:
|
|
50
|
+
allocator: std.mem.Allocator,
|
|
51
51
|
replica_count: u8,
|
|
52
52
|
client_count: u8,
|
|
53
53
|
options: NetworkOptions,
|
|
@@ -136,7 +136,7 @@ pub const Network = struct {
|
|
|
136
136
|
|
|
137
137
|
fn deliver_message(packet: Packet, path: PacketSimulatorPath) void {
|
|
138
138
|
const network = packet.network;
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
const target_bus = &network.busses.items[path.target];
|
|
141
141
|
|
|
142
142
|
const message = target_bus.get_message() orelse {
|
|
@@ -88,7 +88,7 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
88
88
|
|
|
89
89
|
/// A send and receive path between each node in the network. We use the `path` function to
|
|
90
90
|
/// index it.
|
|
91
|
-
paths: []std.PriorityQueue(Data),
|
|
91
|
+
paths: []std.PriorityQueue(Data, void, Self.order_packets),
|
|
92
92
|
|
|
93
93
|
/// We can arbitrary clog a path until a tick.
|
|
94
94
|
path_clogged_till: []u64,
|
|
@@ -103,11 +103,11 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
103
103
|
replicas: []u8,
|
|
104
104
|
stability: u32,
|
|
105
105
|
|
|
106
|
-
pub fn init(allocator:
|
|
106
|
+
pub fn init(allocator: std.mem.Allocator, options: PacketSimulatorOptions) !Self {
|
|
107
107
|
assert(options.one_way_delay_mean >= options.one_way_delay_min);
|
|
108
108
|
var self = Self{
|
|
109
109
|
.paths = try allocator.alloc(
|
|
110
|
-
std.PriorityQueue(Data),
|
|
110
|
+
std.PriorityQueue(Data, void, Self.order_packets),
|
|
111
111
|
@as(usize, options.node_count) * options.node_count,
|
|
112
112
|
),
|
|
113
113
|
.path_clogged_till = try allocator.alloc(
|
|
@@ -128,8 +128,8 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
for (self.paths) |*queue| {
|
|
131
|
-
queue.* = std.PriorityQueue(Data).init(allocator,
|
|
132
|
-
try queue.
|
|
131
|
+
queue.* = std.PriorityQueue(Data, void, Self.order_packets).init(allocator, {});
|
|
132
|
+
try queue.ensureTotalCapacity(options.path_maximum_capacity);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
for (self.path_clogged_till) |*clogged_till| {
|
|
@@ -139,7 +139,7 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
139
139
|
return self;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
pub fn deinit(self: *Self, allocator:
|
|
142
|
+
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
|
|
143
143
|
for (self.paths) |*queue| {
|
|
144
144
|
while (queue.popOrNull()) |*data| data.packet.deinit();
|
|
145
145
|
queue.deinit();
|
|
@@ -147,12 +147,14 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
147
147
|
allocator.free(self.paths);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
fn order_packets(a: Data, b: Data) math.Order {
|
|
150
|
+
fn order_packets(context: void, a: Data, b: Data) math.Order {
|
|
151
|
+
_ = context;
|
|
152
|
+
|
|
151
153
|
return math.order(a.expiry, b.expiry);
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
fn should_drop(self: *Self) bool {
|
|
155
|
-
return self.prng.random.uintAtMost(u8, 100) < self.options.packet_loss_probability;
|
|
157
|
+
return self.prng.random().uintAtMost(u8, 100) < self.options.packet_loss_probability;
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
fn path_index(self: *Self, path: Path) usize {
|
|
@@ -161,9 +163,8 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
161
163
|
return @as(usize, path.source) * self.options.node_count + path.target;
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
pub fn path_queue(self: *Self, path: Path) *std.PriorityQueue(Data) {
|
|
165
|
-
|
|
166
|
-
return &self.paths[@as(usize, path.source) * self.options.node_count + path.target];
|
|
166
|
+
pub fn path_queue(self: *Self, path: Path) *std.PriorityQueue(Data, void, Self.order_packets) {
|
|
167
|
+
return &self.paths[self.path_index(path)];
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
fn is_clogged(self: *Self, path: Path) bool {
|
|
@@ -171,7 +172,9 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
fn should_clog(self: *Self, path: Path) bool {
|
|
174
|
-
|
|
175
|
+
_ = path;
|
|
176
|
+
|
|
177
|
+
return self.prng.random().uintAtMost(u8, 100) < self.options.path_clog_probability;
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
fn clog_for(self: *Self, path: Path, ticks: u64) void {
|
|
@@ -185,15 +188,15 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
fn should_replay(self: *Self) bool {
|
|
188
|
-
return self.prng.random.uintAtMost(u8, 100) < self.options.packet_replay_probability;
|
|
191
|
+
return self.prng.random().uintAtMost(u8, 100) < self.options.packet_replay_probability;
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
fn should_partition(self: *Self) bool {
|
|
192
|
-
return self.prng.random.uintAtMost(u8, 100) < self.options.partition_probability;
|
|
195
|
+
return self.prng.random().uintAtMost(u8, 100) < self.options.partition_probability;
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
fn should_unpartition(self: *Self) bool {
|
|
196
|
-
return self.prng.random.uintAtMost(u8, 100) < self.options.unpartition_probability;
|
|
199
|
+
return self.prng.random().uintAtMost(u8, 100) < self.options.unpartition_probability;
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
/// Return a value produced using an exponential distribution with
|
|
@@ -201,7 +204,7 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
201
204
|
fn one_way_delay(self: *Self) u64 {
|
|
202
205
|
const min = self.options.one_way_delay_min;
|
|
203
206
|
const mean = self.options.one_way_delay_mean;
|
|
204
|
-
return min + @floatToInt(u64, @intToFloat(f64, mean - min) * self.prng.random.floatExp(f64));
|
|
207
|
+
return min + @floatToInt(u64, @intToFloat(f64, mean - min) * self.prng.random().floatExp(f64));
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
/// Partitions the network. Guaranteed to isolate at least one replica.
|
|
@@ -217,8 +220,8 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
217
220
|
.uniform_size => {
|
|
218
221
|
// Exclude cases sz == 0 and sz == replica_count
|
|
219
222
|
const sz =
|
|
220
|
-
1 + self.prng.random.uintAtMost(u8, self.options.replica_count - 2);
|
|
221
|
-
self.prng.random.shuffle(u8, self.replicas);
|
|
223
|
+
1 + self.prng.random().uintAtMost(u8, self.options.replica_count - 2);
|
|
224
|
+
self.prng.random().shuffle(u8, self.replicas);
|
|
222
225
|
for (self.replicas) |r, i| {
|
|
223
226
|
self.partition[r] = i < sz;
|
|
224
227
|
}
|
|
@@ -226,18 +229,18 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
226
229
|
.uniform_partition => {
|
|
227
230
|
var only_same = true;
|
|
228
231
|
self.partition[0] =
|
|
229
|
-
self.prng.random.uintLessThan(u8, 2) == 1;
|
|
232
|
+
self.prng.random().uintLessThan(u8, 2) == 1;
|
|
230
233
|
|
|
231
234
|
var i: usize = 1;
|
|
232
235
|
while (i < self.options.replica_count) : (i += 1) {
|
|
233
236
|
self.partition[i] =
|
|
234
|
-
self.prng.random.uintLessThan(u8, 2) == 1;
|
|
237
|
+
self.prng.random().uintLessThan(u8, 2) == 1;
|
|
235
238
|
only_same =
|
|
236
239
|
only_same and (self.partition[i - 1] == self.partition[i]);
|
|
237
240
|
}
|
|
238
241
|
|
|
239
242
|
if (only_same) {
|
|
240
|
-
const n = self.prng.random.uintLessThan(u8, self.options.replica_count);
|
|
243
|
+
const n = self.prng.random().uintLessThan(u8, self.options.replica_count);
|
|
241
244
|
self.partition[n] = true;
|
|
242
245
|
}
|
|
243
246
|
},
|
|
@@ -245,7 +248,7 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
245
248
|
for (self.replicas) |_, i| {
|
|
246
249
|
self.partition[i] = false;
|
|
247
250
|
}
|
|
248
|
-
const n = self.prng.random.uintLessThan(u8, self.options.replica_count);
|
|
251
|
+
const n = self.prng.random().uintLessThan(u8, self.options.replica_count);
|
|
249
252
|
self.partition[n] = true;
|
|
250
253
|
},
|
|
251
254
|
// Put your own partitioning logic here.
|
|
@@ -279,12 +282,12 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
279
282
|
if (self.is_partitioned) {
|
|
280
283
|
if (self.should_unpartition()) {
|
|
281
284
|
self.unpartition_network();
|
|
282
|
-
log.
|
|
285
|
+
log.err("unpartitioned network: partition={d}", .{self.partition});
|
|
283
286
|
}
|
|
284
287
|
} else {
|
|
285
288
|
if (self.options.replica_count > 1 and self.should_partition()) {
|
|
286
289
|
self.partition_network();
|
|
287
|
-
log.
|
|
290
|
+
log.err("partitioned network: partition={d}", .{self.partition});
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
293
|
}
|
|
@@ -305,14 +308,14 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
305
308
|
self.replicas_are_in_different_partitions(from, to))
|
|
306
309
|
{
|
|
307
310
|
self.stats[@enumToInt(PacketStatistics.dropped_due_to_partition)] += 1;
|
|
308
|
-
log.
|
|
311
|
+
log.err("dropped packet (different partitions): from={} to={}", .{ from, to });
|
|
309
312
|
data.packet.deinit(path);
|
|
310
313
|
continue;
|
|
311
314
|
}
|
|
312
315
|
|
|
313
316
|
if (self.should_drop()) {
|
|
314
317
|
self.stats[@enumToInt(PacketStatistics.dropped)] += 1;
|
|
315
|
-
log.
|
|
318
|
+
log.err("dropped packet from={} to={}.", .{ from, to });
|
|
316
319
|
data.packet.deinit(path);
|
|
317
320
|
continue;
|
|
318
321
|
}
|
|
@@ -336,7 +339,7 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
336
339
|
if (self.should_clog(reverse_path)) {
|
|
337
340
|
log.debug("reverse path clogged", .{});
|
|
338
341
|
const mean = @intToFloat(f64, self.options.path_clog_duration_mean);
|
|
339
|
-
const ticks = @floatToInt(u64, mean * self.prng.random.floatExp(f64));
|
|
342
|
+
const ticks = @floatToInt(u64, mean * self.prng.random().floatExp(f64));
|
|
340
343
|
self.clog_for(reverse_path, ticks);
|
|
341
344
|
}
|
|
342
345
|
}
|
|
@@ -352,10 +355,10 @@ pub fn PacketSimulator(comptime Packet: type) type {
|
|
|
352
355
|
const queue = self.path_queue(path);
|
|
353
356
|
var queue_length = queue.count();
|
|
354
357
|
if (queue_length + 1 > queue.capacity()) {
|
|
355
|
-
const index = self.prng.random.uintLessThanBiased(u64, queue_length);
|
|
358
|
+
const index = self.prng.random().uintLessThanBiased(u64, queue_length);
|
|
356
359
|
const data = queue.removeIndex(index);
|
|
357
360
|
data.packet.deinit(path);
|
|
358
|
-
log.
|
|
361
|
+
log.err("submit_packet: {} reached capacity, dropped packet={}", .{
|
|
359
362
|
path,
|
|
360
363
|
index,
|
|
361
364
|
});
|
|
@@ -34,7 +34,7 @@ pub const StateChecker = struct {
|
|
|
34
34
|
/// The number of times the cannonical state has been advanced.
|
|
35
35
|
transitions: u64 = 0,
|
|
36
36
|
|
|
37
|
-
pub fn init(allocator:
|
|
37
|
+
pub fn init(allocator: mem.Allocator, cluster: *Cluster) !StateChecker {
|
|
38
38
|
const state = cluster.state_machines[0].state;
|
|
39
39
|
|
|
40
40
|
var state_machine_states: [config.replicas_max]u128 = undefined;
|
|
@@ -86,7 +86,7 @@ pub const StateChecker = struct {
|
|
|
86
86
|
// The replica has transitioned to state `b` that is not yet in the history.
|
|
87
87
|
// Check if this is a valid new state based on all currently inflight client requests.
|
|
88
88
|
for (state_checker.client_requests) |*queue| {
|
|
89
|
-
if (queue.
|
|
89
|
+
if (queue.head_ptr()) |input| {
|
|
90
90
|
if (b == StateMachine.hash(state_checker.state, std.mem.asBytes(input))) {
|
|
91
91
|
const transitions_executed = state_checker.history.get(a).?;
|
|
92
92
|
if (transitions_executed < state_checker.transitions) {
|