tigerbeetle-node 0.11.12 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -196
- package/dist/bin/aarch64-linux-gnu/client.node +0 -0
- package/dist/bin/aarch64-linux-musl/client.node +0 -0
- package/dist/bin/aarch64-macos/client.node +0 -0
- package/dist/bin/x86_64-linux-gnu/client.node +0 -0
- package/dist/bin/x86_64-linux-musl/client.node +0 -0
- package/dist/bin/x86_64-macos/client.node +0 -0
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/package-lock.json +66 -0
- package/package.json +8 -17
- package/src/index.ts +56 -1
- package/src/node.zig +10 -9
- package/dist/.client.node.sha256 +0 -1
- package/scripts/build_lib.sh +0 -61
- package/scripts/download_node_headers.sh +0 -32
- package/src/tigerbeetle/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/src/benchmark.zig +0 -314
- package/src/tigerbeetle/src/config.zig +0 -234
- package/src/tigerbeetle/src/constants.zig +0 -436
- package/src/tigerbeetle/src/ewah.zig +0 -286
- package/src/tigerbeetle/src/ewah_benchmark.zig +0 -120
- package/src/tigerbeetle/src/ewah_fuzz.zig +0 -130
- package/src/tigerbeetle/src/fifo.zig +0 -120
- package/src/tigerbeetle/src/io/benchmark.zig +0 -213
- package/src/tigerbeetle/src/io/darwin.zig +0 -814
- package/src/tigerbeetle/src/io/linux.zig +0 -1062
- package/src/tigerbeetle/src/io/test.zig +0 -643
- package/src/tigerbeetle/src/io/windows.zig +0 -1183
- package/src/tigerbeetle/src/io.zig +0 -34
- package/src/tigerbeetle/src/iops.zig +0 -107
- package/src/tigerbeetle/src/lsm/README.md +0 -308
- package/src/tigerbeetle/src/lsm/binary_search.zig +0 -341
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +0 -125
- package/src/tigerbeetle/src/lsm/compaction.zig +0 -603
- package/src/tigerbeetle/src/lsm/composite_key.zig +0 -77
- package/src/tigerbeetle/src/lsm/direction.zig +0 -11
- package/src/tigerbeetle/src/lsm/eytzinger.zig +0 -587
- package/src/tigerbeetle/src/lsm/eytzinger_benchmark.zig +0 -330
- package/src/tigerbeetle/src/lsm/forest.zig +0 -204
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +0 -401
- package/src/tigerbeetle/src/lsm/grid.zig +0 -573
- package/src/tigerbeetle/src/lsm/groove.zig +0 -972
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +0 -474
- package/src/tigerbeetle/src/lsm/level_iterator.zig +0 -332
- package/src/tigerbeetle/src/lsm/manifest.zig +0 -617
- package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -877
- package/src/tigerbeetle/src/lsm/manifest_log.zig +0 -789
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +0 -691
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +0 -106
- package/src/tigerbeetle/src/lsm/node_pool.zig +0 -235
- package/src/tigerbeetle/src/lsm/posted_groove.zig +0 -378
- package/src/tigerbeetle/src/lsm/segmented_array.zig +0 -1328
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +0 -148
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +0 -9
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +0 -850
- package/src/tigerbeetle/src/lsm/table.zig +0 -1031
- package/src/tigerbeetle/src/lsm/table_immutable.zig +0 -203
- package/src/tigerbeetle/src/lsm/table_iterator.zig +0 -340
- package/src/tigerbeetle/src/lsm/table_mutable.zig +0 -220
- package/src/tigerbeetle/src/lsm/test.zig +0 -438
- package/src/tigerbeetle/src/lsm/tree.zig +0 -1193
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +0 -474
- package/src/tigerbeetle/src/message_bus.zig +0 -1012
- package/src/tigerbeetle/src/message_pool.zig +0 -156
- package/src/tigerbeetle/src/ring_buffer.zig +0 -399
- package/src/tigerbeetle/src/simulator.zig +0 -569
- package/src/tigerbeetle/src/state_machine/auditor.zig +0 -577
- package/src/tigerbeetle/src/state_machine/workload.zig +0 -883
- package/src/tigerbeetle/src/state_machine.zig +0 -1881
- package/src/tigerbeetle/src/static_allocator.zig +0 -65
- package/src/tigerbeetle/src/stdx.zig +0 -162
- package/src/tigerbeetle/src/storage.zig +0 -393
- package/src/tigerbeetle/src/testing/cluster/message_bus.zig +0 -82
- package/src/tigerbeetle/src/testing/cluster/network.zig +0 -237
- package/src/tigerbeetle/src/testing/cluster/state_checker.zig +0 -169
- package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +0 -202
- package/src/tigerbeetle/src/testing/cluster.zig +0 -443
- package/src/tigerbeetle/src/testing/fuzz.zig +0 -140
- package/src/tigerbeetle/src/testing/hash_log.zig +0 -66
- package/src/tigerbeetle/src/testing/id.zig +0 -99
- package/src/tigerbeetle/src/testing/packet_simulator.zig +0 -364
- package/src/tigerbeetle/src/testing/priority_queue.zig +0 -645
- package/src/tigerbeetle/src/testing/reply_sequence.zig +0 -139
- package/src/tigerbeetle/src/testing/state_machine.zig +0 -249
- package/src/tigerbeetle/src/testing/storage.zig +0 -757
- package/src/tigerbeetle/src/testing/table.zig +0 -247
- package/src/tigerbeetle/src/testing/time.zig +0 -84
- package/src/tigerbeetle/src/tigerbeetle.zig +0 -227
- package/src/tigerbeetle/src/time.zig +0 -112
- package/src/tigerbeetle/src/tracer.zig +0 -529
- package/src/tigerbeetle/src/unit_tests.zig +0 -42
- package/src/tigerbeetle/src/vopr.zig +0 -495
- package/src/tigerbeetle/src/vsr/README.md +0 -209
- package/src/tigerbeetle/src/vsr/client.zig +0 -544
- package/src/tigerbeetle/src/vsr/clock.zig +0 -853
- package/src/tigerbeetle/src/vsr/journal.zig +0 -2413
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +0 -111
- package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
- package/src/tigerbeetle/src/vsr/replica.zig +0 -6381
- package/src/tigerbeetle/src/vsr/replica_format.zig +0 -219
- package/src/tigerbeetle/src/vsr/superblock.zig +0 -1631
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +0 -256
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +0 -929
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +0 -334
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +0 -390
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +0 -615
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +0 -394
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +0 -314
- package/src/tigerbeetle/src/vsr.zig +0 -1352
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
//! An allocator wrapper which can be disabled at runtime.
|
|
2
|
-
//! We use this for allocating at startup and then
|
|
3
|
-
//! disable it to prevent accidental dynamic allocation at runtime.
|
|
4
|
-
|
|
5
|
-
const std = @import("std");
|
|
6
|
-
const assert = std.debug.assert;
|
|
7
|
-
const mem = std.mem;
|
|
8
|
-
const log = std.log.scoped(.static_allocator);
|
|
9
|
-
|
|
10
|
-
const Self = @This();
|
|
11
|
-
parent_allocator: mem.Allocator,
|
|
12
|
-
state: State,
|
|
13
|
-
|
|
14
|
-
const State = enum {
|
|
15
|
-
/// Allow `alloc` and `resize`.
|
|
16
|
-
/// (To make errdefer cleanup easier to write we also allow calling `free`,
|
|
17
|
-
/// in which case we switch state to `.deinit` and no longer allow `alloc` or `resize`.)
|
|
18
|
-
init,
|
|
19
|
-
/// Don't allow any calls.
|
|
20
|
-
static,
|
|
21
|
-
/// Allow `free` but not `alloc` and `resize`.
|
|
22
|
-
deinit,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
pub fn init(parent_allocator: mem.Allocator) Self {
|
|
26
|
-
return .{
|
|
27
|
-
.parent_allocator = parent_allocator,
|
|
28
|
-
.state = .init,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
pub fn deinit(self: *Self) void {
|
|
33
|
-
self.* = undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
pub fn transition_from_init_to_static(self: *Self) void {
|
|
37
|
-
assert(self.state == .init);
|
|
38
|
-
self.state = .static;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
pub fn transition_from_static_to_deinit(self: *Self) void {
|
|
42
|
-
assert(self.state == .static);
|
|
43
|
-
self.state = .deinit;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
pub fn allocator(self: *Self) mem.Allocator {
|
|
47
|
-
return mem.Allocator.init(self, alloc, resize, free);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
fn alloc(self: *Self, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) error{OutOfMemory}![]u8 {
|
|
51
|
-
assert(self.state == .init);
|
|
52
|
-
return self.parent_allocator.rawAlloc(len, ptr_align, len_align, ret_addr);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
fn resize(self: *Self, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) ?usize {
|
|
56
|
-
assert(self.state == .init);
|
|
57
|
-
return self.parent_allocator.rawResize(buf, buf_align, new_len, len_align, ret_addr);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fn free(self: *Self, buf: []u8, buf_align: u29, ret_addr: usize) void {
|
|
61
|
-
assert(self.state == .init or self.state == .deinit);
|
|
62
|
-
// Once you start freeing, you don't stop.
|
|
63
|
-
self.state = .deinit;
|
|
64
|
-
return self.parent_allocator.rawFree(buf, buf_align, ret_addr);
|
|
65
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
//! Extensions to the standard library -- things which could have been in std, but aren't.
|
|
2
|
-
|
|
3
|
-
const std = @import("std");
|
|
4
|
-
const assert = std.debug.assert;
|
|
5
|
-
|
|
6
|
-
pub inline fn div_ceil(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) {
|
|
7
|
-
comptime {
|
|
8
|
-
switch (@typeInfo(@TypeOf(numerator))) {
|
|
9
|
-
.Int => |int| assert(int.signedness == .unsigned),
|
|
10
|
-
.ComptimeInt => assert(numerator >= 0),
|
|
11
|
-
else => @compileError("div_ceil: invalid numerator type"),
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
switch (@typeInfo(@TypeOf(denominator))) {
|
|
15
|
-
.Int => |int| assert(int.signedness == .unsigned),
|
|
16
|
-
.ComptimeInt => assert(denominator > 0),
|
|
17
|
-
else => @compileError("div_ceil: invalid denominator type"),
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
assert(denominator > 0);
|
|
22
|
-
|
|
23
|
-
if (numerator == 0) return 0;
|
|
24
|
-
return @divFloor(numerator - 1, denominator) + 1;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
test "div_ceil" {
|
|
28
|
-
// Comptime ints.
|
|
29
|
-
try std.testing.expectEqual(div_ceil(0, 8), 0);
|
|
30
|
-
try std.testing.expectEqual(div_ceil(1, 8), 1);
|
|
31
|
-
try std.testing.expectEqual(div_ceil(7, 8), 1);
|
|
32
|
-
try std.testing.expectEqual(div_ceil(8, 8), 1);
|
|
33
|
-
try std.testing.expectEqual(div_ceil(9, 8), 2);
|
|
34
|
-
|
|
35
|
-
// Unsized ints
|
|
36
|
-
const max = std.math.maxInt(u64);
|
|
37
|
-
try std.testing.expectEqual(div_ceil(@as(u64, 0), 8), 0);
|
|
38
|
-
try std.testing.expectEqual(div_ceil(@as(u64, 1), 8), 1);
|
|
39
|
-
try std.testing.expectEqual(div_ceil(@as(u64, max), 2), max / 2 + 1);
|
|
40
|
-
try std.testing.expectEqual(div_ceil(@as(u64, max) - 1, 2), max / 2);
|
|
41
|
-
try std.testing.expectEqual(div_ceil(@as(u64, max) - 2, 2), max / 2);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
pub const CopyPrecision = enum { exact, inexact };
|
|
45
|
-
|
|
46
|
-
pub inline fn copy_left(
|
|
47
|
-
comptime precision: CopyPrecision,
|
|
48
|
-
comptime T: type,
|
|
49
|
-
target: []T,
|
|
50
|
-
source: []const T,
|
|
51
|
-
) void {
|
|
52
|
-
switch (precision) {
|
|
53
|
-
.exact => assert(target.len == source.len),
|
|
54
|
-
.inexact => assert(target.len >= source.len),
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!disjoint_slices(T, T, target, source)) {
|
|
58
|
-
assert(@ptrToInt(target.ptr) < @ptrToInt(source.ptr));
|
|
59
|
-
}
|
|
60
|
-
std.mem.copy(T, target, source);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
test "copy_left" {
|
|
64
|
-
const a = try std.testing.allocator.alloc(usize, 8);
|
|
65
|
-
defer std.testing.allocator.free(a);
|
|
66
|
-
|
|
67
|
-
for (a) |*v, i| v.* = i;
|
|
68
|
-
copy_left(.exact, usize, a[0..6], a[2..]);
|
|
69
|
-
try std.testing.expect(std.mem.eql(usize, a, &.{ 2, 3, 4, 5, 6, 7, 6, 7 }));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
pub inline fn copy_right(
|
|
73
|
-
comptime precision: CopyPrecision,
|
|
74
|
-
comptime T: type,
|
|
75
|
-
target: []T,
|
|
76
|
-
source: []const T,
|
|
77
|
-
) void {
|
|
78
|
-
switch (precision) {
|
|
79
|
-
.exact => assert(target.len == source.len),
|
|
80
|
-
.inexact => assert(target.len >= source.len),
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!disjoint_slices(T, T, target, source)) {
|
|
84
|
-
assert(@ptrToInt(target.ptr) > @ptrToInt(source.ptr));
|
|
85
|
-
}
|
|
86
|
-
std.mem.copyBackwards(T, target, source);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
test "copy_right" {
|
|
90
|
-
const a = try std.testing.allocator.alloc(usize, 8);
|
|
91
|
-
defer std.testing.allocator.free(a);
|
|
92
|
-
|
|
93
|
-
for (a) |*v, i| v.* = i;
|
|
94
|
-
copy_right(.exact, usize, a[2..], a[0..6]);
|
|
95
|
-
try std.testing.expect(std.mem.eql(usize, a, &.{ 0, 1, 0, 1, 2, 3, 4, 5 }));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
pub inline fn copy_disjoint(
|
|
99
|
-
comptime precision: CopyPrecision,
|
|
100
|
-
comptime T: type,
|
|
101
|
-
target: []T,
|
|
102
|
-
source: []const T,
|
|
103
|
-
) void {
|
|
104
|
-
switch (precision) {
|
|
105
|
-
.exact => assert(target.len == source.len),
|
|
106
|
-
.inexact => assert(target.len >= source.len),
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
assert(disjoint_slices(T, T, target, source));
|
|
110
|
-
std.mem.copy(T, target, source);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
pub inline fn disjoint_slices(comptime A: type, comptime B: type, a: []const A, b: []const B) bool {
|
|
114
|
-
return @ptrToInt(a.ptr) + a.len * @sizeOf(A) <= @ptrToInt(b.ptr) or
|
|
115
|
-
@ptrToInt(b.ptr) + b.len * @sizeOf(B) <= @ptrToInt(a.ptr);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
test "disjoint_slices" {
|
|
119
|
-
const a = try std.testing.allocator.alignedAlloc(u8, @sizeOf(u32), 8 * @sizeOf(u32));
|
|
120
|
-
defer std.testing.allocator.free(a);
|
|
121
|
-
|
|
122
|
-
const b = try std.testing.allocator.alloc(u32, 8);
|
|
123
|
-
defer std.testing.allocator.free(b);
|
|
124
|
-
|
|
125
|
-
try std.testing.expectEqual(true, disjoint_slices(u8, u32, a, b));
|
|
126
|
-
try std.testing.expectEqual(true, disjoint_slices(u32, u8, b, a));
|
|
127
|
-
|
|
128
|
-
try std.testing.expectEqual(true, disjoint_slices(u8, u8, a, a[0..0]));
|
|
129
|
-
try std.testing.expectEqual(true, disjoint_slices(u32, u32, b, b[0..0]));
|
|
130
|
-
|
|
131
|
-
try std.testing.expectEqual(false, disjoint_slices(u8, u8, a, a[0..1]));
|
|
132
|
-
try std.testing.expectEqual(false, disjoint_slices(u8, u8, a, a[a.len - 1 .. a.len]));
|
|
133
|
-
|
|
134
|
-
try std.testing.expectEqual(false, disjoint_slices(u32, u32, b, b[0..1]));
|
|
135
|
-
try std.testing.expectEqual(false, disjoint_slices(u32, u32, b, b[b.len - 1 .. b.len]));
|
|
136
|
-
|
|
137
|
-
try std.testing.expectEqual(false, disjoint_slices(u8, u32, a, std.mem.bytesAsSlice(u32, a)));
|
|
138
|
-
try std.testing.expectEqual(false, disjoint_slices(u32, u8, b, std.mem.sliceAsBytes(b)));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/// Utility function for ad-hoc profiling.
|
|
142
|
-
///
|
|
143
|
-
/// A thin wrapper around `std.time.Timer` which handles the boilerplate of
|
|
144
|
-
/// printing to stderr and formatting times in some (unspecified) readable way.
|
|
145
|
-
pub fn timeit() TimeIt {
|
|
146
|
-
return TimeIt{ .inner = std.time.Timer.start() catch unreachable };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const TimeIt = struct {
|
|
150
|
-
inner: std.time.Timer,
|
|
151
|
-
|
|
152
|
-
/// Prints elapesed time to stderr and resets the internal timer.
|
|
153
|
-
pub fn lap(self: *TimeIt, comptime label: []const u8) void {
|
|
154
|
-
const label_alignment = comptime " " ** (1 + (12 -| label.len));
|
|
155
|
-
|
|
156
|
-
const nanos = self.inner.lap();
|
|
157
|
-
std.debug.print(
|
|
158
|
-
label ++ ":" ++ label_alignment ++ "{}\n",
|
|
159
|
-
.{std.fmt.fmtDuration(nanos)},
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const builtin = @import("builtin");
|
|
3
|
-
const os = std.os;
|
|
4
|
-
const assert = std.debug.assert;
|
|
5
|
-
const log = std.log.scoped(.storage);
|
|
6
|
-
|
|
7
|
-
const IO = @import("io.zig").IO;
|
|
8
|
-
const FIFO = @import("fifo.zig").FIFO;
|
|
9
|
-
const constants = @import("constants.zig");
|
|
10
|
-
const vsr = @import("vsr.zig");
|
|
11
|
-
|
|
12
|
-
pub const Storage = struct {
|
|
13
|
-
/// See usage in Journal.write_sectors() for details.
|
|
14
|
-
pub const synchronicity: enum {
|
|
15
|
-
always_synchronous,
|
|
16
|
-
always_asynchronous,
|
|
17
|
-
} = .always_asynchronous;
|
|
18
|
-
|
|
19
|
-
pub const Read = struct {
|
|
20
|
-
completion: IO.Completion,
|
|
21
|
-
callback: fn (read: *Storage.Read) void,
|
|
22
|
-
|
|
23
|
-
/// The buffer to read into, re-sliced and re-assigned as we go, e.g. after partial reads.
|
|
24
|
-
buffer: []u8,
|
|
25
|
-
|
|
26
|
-
/// The position into the file descriptor from where we should read, also adjusted as we go.
|
|
27
|
-
offset: u64,
|
|
28
|
-
|
|
29
|
-
/// The maximum amount of bytes to read per syscall. We use this to subdivide troublesome
|
|
30
|
-
/// reads into smaller reads to work around latent sector errors (LSEs).
|
|
31
|
-
target_max: u64,
|
|
32
|
-
|
|
33
|
-
/// Returns a target slice into `buffer` to read into, capped by `target_max`.
|
|
34
|
-
/// If the previous read was a partial read of physical sectors (e.g. 512 bytes) less than
|
|
35
|
-
/// our logical sector size (e.g. 4 KiB), so that the remainder of the buffer is no longer
|
|
36
|
-
/// aligned to a logical sector, then we further cap the slice to get back onto a logical
|
|
37
|
-
/// sector boundary.
|
|
38
|
-
fn target(read: *Read) []u8 {
|
|
39
|
-
// A worked example of a partial read that leaves the rest of the buffer unaligned:
|
|
40
|
-
// This could happen for non-Advanced Format disks with a physical sector of 512 bytes.
|
|
41
|
-
// We want to read 8 KiB:
|
|
42
|
-
// buffer.ptr = 0
|
|
43
|
-
// buffer.len = 8192
|
|
44
|
-
// ... and then experience a partial read of only 512 bytes:
|
|
45
|
-
// buffer.ptr = 512
|
|
46
|
-
// buffer.len = 7680
|
|
47
|
-
// We can now see that `buffer.len` is no longer a sector multiple of 4 KiB and further
|
|
48
|
-
// that we have 3584 bytes left of the partial sector read. If we subtract this amount
|
|
49
|
-
// from our logical sector size of 4 KiB we get 512 bytes, which is the alignment error
|
|
50
|
-
// that we need to subtract from `target_max` to get back onto the boundary.
|
|
51
|
-
var max = read.target_max;
|
|
52
|
-
|
|
53
|
-
const partial_sector_read_remainder = read.buffer.len % constants.sector_size;
|
|
54
|
-
if (partial_sector_read_remainder != 0) {
|
|
55
|
-
// TODO log.debug() because this is interesting, and to ensure fuzz test coverage.
|
|
56
|
-
const partial_sector_read = constants.sector_size - partial_sector_read_remainder;
|
|
57
|
-
max -= partial_sector_read;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return read.buffer[0..std.math.min(read.buffer.len, max)];
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
pub const Write = struct {
|
|
65
|
-
completion: IO.Completion,
|
|
66
|
-
callback: fn (write: *Storage.Write) void,
|
|
67
|
-
buffer: []const u8,
|
|
68
|
-
offset: u64,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
pub const NextTick = struct {
|
|
72
|
-
next: ?*NextTick = null,
|
|
73
|
-
callback: fn (next_tick: *NextTick) void,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
io: *IO,
|
|
77
|
-
fd: os.fd_t,
|
|
78
|
-
|
|
79
|
-
next_tick_queue: FIFO(NextTick) = .{},
|
|
80
|
-
next_tick_completion_scheduled: bool = false,
|
|
81
|
-
next_tick_completion: IO.Completion = undefined,
|
|
82
|
-
|
|
83
|
-
pub fn init(io: *IO, fd: os.fd_t) !Storage {
|
|
84
|
-
return Storage{
|
|
85
|
-
.io = io,
|
|
86
|
-
.fd = fd,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
pub fn deinit(storage: *Storage) void {
|
|
91
|
-
assert(storage.next_tick_queue.empty());
|
|
92
|
-
assert(storage.fd != IO.INVALID_FILE);
|
|
93
|
-
storage.fd = IO.INVALID_FILE;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
pub fn tick(storage: *Storage) void {
|
|
97
|
-
storage.io.tick() catch |err| {
|
|
98
|
-
log.warn("tick: {}", .{err});
|
|
99
|
-
std.debug.panic("io.tick(): {}", .{err});
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
pub fn on_next_tick(
|
|
104
|
-
storage: *Storage,
|
|
105
|
-
callback: fn (next_tick: *Storage.NextTick) void,
|
|
106
|
-
next_tick: *Storage.NextTick,
|
|
107
|
-
) void {
|
|
108
|
-
next_tick.* = .{ .callback = callback };
|
|
109
|
-
storage.next_tick_queue.push(next_tick);
|
|
110
|
-
|
|
111
|
-
if (!storage.next_tick_completion_scheduled) {
|
|
112
|
-
storage.next_tick_completion_scheduled = true;
|
|
113
|
-
storage.io.timeout(
|
|
114
|
-
*Storage,
|
|
115
|
-
storage,
|
|
116
|
-
timeout_callback,
|
|
117
|
-
&storage.next_tick_completion,
|
|
118
|
-
0, // 0ns timeout means to resolve as soon as possible - like a yield
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
fn timeout_callback(
|
|
124
|
-
storage: *Storage,
|
|
125
|
-
completion: *IO.Completion,
|
|
126
|
-
result: IO.TimeoutError!void,
|
|
127
|
-
) void {
|
|
128
|
-
assert(completion == &storage.next_tick_completion);
|
|
129
|
-
_ = result catch |e| switch (e) {
|
|
130
|
-
error.Canceled => unreachable,
|
|
131
|
-
error.Unexpected => unreachable,
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Reset the scheduled flag after processing all tick entries
|
|
135
|
-
assert(storage.next_tick_completion_scheduled);
|
|
136
|
-
defer {
|
|
137
|
-
assert(storage.next_tick_completion_scheduled);
|
|
138
|
-
storage.next_tick_completion_scheduled = false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
while (storage.next_tick_queue.pop()) |next_tick| {
|
|
142
|
-
next_tick.callback(next_tick);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
pub fn read_sectors(
|
|
147
|
-
self: *Storage,
|
|
148
|
-
callback: fn (read: *Storage.Read) void,
|
|
149
|
-
read: *Storage.Read,
|
|
150
|
-
buffer: []u8,
|
|
151
|
-
zone: vsr.Zone,
|
|
152
|
-
offset_in_zone: u64,
|
|
153
|
-
) void {
|
|
154
|
-
if (zone.size()) |zone_size| {
|
|
155
|
-
assert(offset_in_zone + buffer.len <= zone_size);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const offset_in_storage = zone.offset(offset_in_zone);
|
|
159
|
-
assert_alignment(buffer, offset_in_storage);
|
|
160
|
-
|
|
161
|
-
read.* = .{
|
|
162
|
-
.completion = undefined,
|
|
163
|
-
.callback = callback,
|
|
164
|
-
.buffer = buffer,
|
|
165
|
-
.offset = offset_in_storage,
|
|
166
|
-
.target_max = buffer.len,
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
self.start_read(read, null);
|
|
170
|
-
assert(read.target().len > 0);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
fn start_read(self: *Storage, read: *Storage.Read, bytes_read: ?usize) void {
|
|
174
|
-
const bytes = bytes_read orelse 0;
|
|
175
|
-
assert(bytes <= read.target().len);
|
|
176
|
-
|
|
177
|
-
read.offset += bytes;
|
|
178
|
-
read.buffer = read.buffer[bytes..];
|
|
179
|
-
|
|
180
|
-
const target = read.target();
|
|
181
|
-
if (target.len == 0) {
|
|
182
|
-
// Resolving the read inline means start_read() must not have been called from
|
|
183
|
-
// read_sectors(). If it was, this is a synchronous callback resolution and should
|
|
184
|
-
// be reported.
|
|
185
|
-
assert(bytes_read != null);
|
|
186
|
-
|
|
187
|
-
read.callback(read);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
self.assert_bounds(target, read.offset);
|
|
192
|
-
self.io.read(
|
|
193
|
-
*Storage,
|
|
194
|
-
self,
|
|
195
|
-
on_read,
|
|
196
|
-
&read.completion,
|
|
197
|
-
self.fd,
|
|
198
|
-
target,
|
|
199
|
-
read.offset,
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
fn on_read(self: *Storage, completion: *IO.Completion, result: IO.ReadError!usize) void {
|
|
204
|
-
const read = @fieldParentPtr(Storage.Read, "completion", completion);
|
|
205
|
-
|
|
206
|
-
const bytes_read = result catch |err| switch (err) {
|
|
207
|
-
error.InputOutput => {
|
|
208
|
-
// The disk was unable to read some sectors (an internal CRC or hardware failure):
|
|
209
|
-
// We may also have already experienced a partial unaligned read, reading less
|
|
210
|
-
// physical sectors than the logical sector size, so we cannot expect `target.len`
|
|
211
|
-
// to be an exact logical sector multiple.
|
|
212
|
-
const target = read.target();
|
|
213
|
-
if (target.len > constants.sector_size) {
|
|
214
|
-
// We tried to read more than a logical sector and failed.
|
|
215
|
-
log.err("latent sector error: offset={}, subdividing read...", .{read.offset});
|
|
216
|
-
|
|
217
|
-
// Divide the buffer in half and try to read each half separately:
|
|
218
|
-
// This creates a recursive binary search for the sector(s) causing the error.
|
|
219
|
-
// This is considerably slower than doing a single bulk read and by now we might
|
|
220
|
-
// also have experienced the disk's read retry timeout (in seconds).
|
|
221
|
-
// TODO Our docs must instruct on why and how to reduce disk firmware timeouts.
|
|
222
|
-
|
|
223
|
-
// These lines both implement ceiling division e.g. `((3 - 1) / 2) + 1 == 2` and
|
|
224
|
-
// require that the numerator is always greater than zero:
|
|
225
|
-
assert(target.len > 0);
|
|
226
|
-
const target_sectors = @divFloor(target.len - 1, constants.sector_size) + 1;
|
|
227
|
-
assert(target_sectors > 0);
|
|
228
|
-
read.target_max = (@divFloor(target_sectors - 1, 2) + 1) * constants.sector_size;
|
|
229
|
-
assert(read.target_max >= constants.sector_size);
|
|
230
|
-
|
|
231
|
-
// Pass 0 for `bytes_read`, we want to retry the read with smaller `target_max`:
|
|
232
|
-
self.start_read(read, 0);
|
|
233
|
-
return;
|
|
234
|
-
} else {
|
|
235
|
-
// We tried to read at (or less than) logical sector granularity and failed.
|
|
236
|
-
log.err("latent sector error: offset={}, zeroing sector...", .{read.offset});
|
|
237
|
-
|
|
238
|
-
// Zero this logical sector which can't be read:
|
|
239
|
-
// We will treat these EIO errors the same as a checksum failure.
|
|
240
|
-
// TODO This could be an interesting avenue to explore further, whether
|
|
241
|
-
// temporary or permanent EIO errors should be conflated with checksum failures.
|
|
242
|
-
assert(target.len > 0);
|
|
243
|
-
std.mem.set(u8, target, 0);
|
|
244
|
-
|
|
245
|
-
// We could set `read.target_max` to `vsr.sector_ceil(read.buffer.len)` here
|
|
246
|
-
// in order to restart our pseudo-binary search on the rest of the sectors to be
|
|
247
|
-
// read, optimistically assuming that this is the last failing sector.
|
|
248
|
-
// However, data corruption that causes EIO errors often has spacial locality.
|
|
249
|
-
// Therefore, restarting our pseudo-binary search here might give us abysmal
|
|
250
|
-
// performance in the (not uncommon) case of many successive failing sectors.
|
|
251
|
-
self.start_read(read, target.len);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
error.WouldBlock,
|
|
257
|
-
error.NotOpenForReading,
|
|
258
|
-
error.ConnectionResetByPeer,
|
|
259
|
-
error.Alignment,
|
|
260
|
-
error.IsDir,
|
|
261
|
-
error.SystemResources,
|
|
262
|
-
error.Unseekable,
|
|
263
|
-
error.ConnectionTimedOut,
|
|
264
|
-
error.Unexpected,
|
|
265
|
-
=> {
|
|
266
|
-
log.err(
|
|
267
|
-
"impossible read: offset={} buffer.len={} error={s}",
|
|
268
|
-
.{ read.offset, read.buffer.len, @errorName(err) },
|
|
269
|
-
);
|
|
270
|
-
@panic("impossible read");
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
if (bytes_read == 0) {
|
|
275
|
-
// We tried to read more than there really is available to read.
|
|
276
|
-
// In other words, we thought we could read beyond the end of the file descriptor.
|
|
277
|
-
// This can happen if the data file inode `size` was truncated or corrupted.
|
|
278
|
-
log.err(
|
|
279
|
-
"short read: buffer.len={} offset={} bytes_read={}",
|
|
280
|
-
.{ read.offset, read.buffer.len, bytes_read },
|
|
281
|
-
);
|
|
282
|
-
@panic("data file inode size was truncated or corrupted");
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// If our target was limited to a single sector, perhaps because of a latent sector error,
|
|
286
|
-
// then increase `target_max` according to AIMD now that we have read successfully and
|
|
287
|
-
// hopefully cleared the faulty zone.
|
|
288
|
-
// We assume that `target_max` may exceed `read.buffer.len` at any time.
|
|
289
|
-
if (read.target_max == constants.sector_size) {
|
|
290
|
-
// TODO Add log.debug because this is interesting.
|
|
291
|
-
read.target_max += constants.sector_size;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
self.start_read(read, bytes_read);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
pub fn write_sectors(
|
|
298
|
-
self: *Storage,
|
|
299
|
-
callback: fn (write: *Storage.Write) void,
|
|
300
|
-
write: *Storage.Write,
|
|
301
|
-
buffer: []const u8,
|
|
302
|
-
zone: vsr.Zone,
|
|
303
|
-
offset_in_zone: u64,
|
|
304
|
-
) void {
|
|
305
|
-
if (zone.size()) |zone_size| {
|
|
306
|
-
assert(offset_in_zone + buffer.len <= zone_size);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const offset_in_storage = zone.offset(offset_in_zone);
|
|
310
|
-
assert_alignment(buffer, offset_in_storage);
|
|
311
|
-
|
|
312
|
-
write.* = .{
|
|
313
|
-
.completion = undefined,
|
|
314
|
-
.callback = callback,
|
|
315
|
-
.buffer = buffer,
|
|
316
|
-
.offset = offset_in_storage,
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
self.start_write(write);
|
|
320
|
-
// Assert that the callback is called asynchronously.
|
|
321
|
-
assert(write.buffer.len > 0);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
fn start_write(self: *Storage, write: *Storage.Write) void {
|
|
325
|
-
self.assert_bounds(write.buffer, write.offset);
|
|
326
|
-
self.io.write(
|
|
327
|
-
*Storage,
|
|
328
|
-
self,
|
|
329
|
-
on_write,
|
|
330
|
-
&write.completion,
|
|
331
|
-
self.fd,
|
|
332
|
-
write.buffer,
|
|
333
|
-
write.offset,
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
fn on_write(self: *Storage, completion: *IO.Completion, result: IO.WriteError!usize) void {
|
|
338
|
-
const write = @fieldParentPtr(Storage.Write, "completion", completion);
|
|
339
|
-
|
|
340
|
-
const bytes_written = result catch |err| switch (err) {
|
|
341
|
-
// We assume that the disk will attempt to reallocate a spare sector for any LSE.
|
|
342
|
-
// TODO What if we receive a temporary EIO error because of a faulty cable?
|
|
343
|
-
error.InputOutput => @panic("latent sector error: no spare sectors to reallocate"),
|
|
344
|
-
// TODO: It seems like it might be possible for some filesystems to return ETIMEDOUT
|
|
345
|
-
// here. Consider handling this without panicking.
|
|
346
|
-
else => {
|
|
347
|
-
log.err(
|
|
348
|
-
"impossible write: offset={} buffer.len={} error={s}",
|
|
349
|
-
.{ write.offset, write.buffer.len, @errorName(err) },
|
|
350
|
-
);
|
|
351
|
-
@panic("impossible write");
|
|
352
|
-
},
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
if (bytes_written == 0) {
|
|
356
|
-
// This should never happen if the kernel and filesystem are well behaved.
|
|
357
|
-
// However, block devices are known to exhibit this behavior in the wild.
|
|
358
|
-
// TODO: Consider retrying with a timeout if this panic proves problematic, and be
|
|
359
|
-
// careful to avoid logging in a busy loop. Perhaps a better approach might be to
|
|
360
|
-
// return wrote = null here and let the protocol retry at a higher layer where there is
|
|
361
|
-
// more context available to decide on how important this is or whether to cancel.
|
|
362
|
-
@panic("write operation returned 0 bytes written");
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
write.offset += bytes_written;
|
|
366
|
-
write.buffer = write.buffer[bytes_written..];
|
|
367
|
-
|
|
368
|
-
if (write.buffer.len == 0) {
|
|
369
|
-
write.callback(write);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
self.start_write(write);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/// Ensures that the read or write is aligned correctly for Direct I/O.
|
|
377
|
-
/// If this is not the case, then the underlying syscall will return EINVAL.
|
|
378
|
-
/// We check this only at the start of a read or write because the physical sector size may be
|
|
379
|
-
/// less than our logical sector size so that partial IOs then leave us no longer aligned.
|
|
380
|
-
fn assert_alignment(buffer: []const u8, offset: u64) void {
|
|
381
|
-
assert(@ptrToInt(buffer.ptr) % constants.sector_size == 0);
|
|
382
|
-
assert(buffer.len % constants.sector_size == 0);
|
|
383
|
-
assert(offset % constants.sector_size == 0);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/// Ensures that the read or write is within bounds and intends to read or write some bytes.
|
|
387
|
-
fn assert_bounds(self: *Storage, buffer: []const u8, offset: u64) void {
|
|
388
|
-
_ = self;
|
|
389
|
-
_ = offset;
|
|
390
|
-
|
|
391
|
-
assert(buffer.len > 0);
|
|
392
|
-
}
|
|
393
|
-
};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
|
|
4
|
-
const MessagePool = @import("../../message_pool.zig").MessagePool;
|
|
5
|
-
const Message = MessagePool.Message;
|
|
6
|
-
const Header = @import("../../vsr.zig").Header;
|
|
7
|
-
const ProcessType = @import("../../vsr.zig").ProcessType;
|
|
8
|
-
|
|
9
|
-
const Network = @import("network.zig").Network;
|
|
10
|
-
|
|
11
|
-
const log = std.log.scoped(.message_bus);
|
|
12
|
-
|
|
13
|
-
pub const Process = union(ProcessType) {
|
|
14
|
-
replica: u8,
|
|
15
|
-
client: u128,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
pub const MessageBus = struct {
|
|
19
|
-
network: *Network,
|
|
20
|
-
pool: *MessagePool,
|
|
21
|
-
|
|
22
|
-
cluster: u32,
|
|
23
|
-
process: Process,
|
|
24
|
-
|
|
25
|
-
/// The callback to be called when a message is received.
|
|
26
|
-
on_message_callback: fn (message_bus: *MessageBus, message: *Message) void,
|
|
27
|
-
|
|
28
|
-
pub const Options = struct {
|
|
29
|
-
network: *Network,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
pub fn init(
|
|
33
|
-
_: std.mem.Allocator,
|
|
34
|
-
cluster: u32,
|
|
35
|
-
process: Process,
|
|
36
|
-
message_pool: *MessagePool,
|
|
37
|
-
on_message_callback: fn (message_bus: *MessageBus, message: *Message) void,
|
|
38
|
-
options: Options,
|
|
39
|
-
) !MessageBus {
|
|
40
|
-
return MessageBus{
|
|
41
|
-
.network = options.network,
|
|
42
|
-
.pool = message_pool,
|
|
43
|
-
.cluster = cluster,
|
|
44
|
-
.process = process,
|
|
45
|
-
.on_message_callback = on_message_callback,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/// TODO
|
|
50
|
-
pub fn deinit(_: *MessageBus, _: std.mem.Allocator) void {}
|
|
51
|
-
|
|
52
|
-
pub fn tick(_: *MessageBus) void {}
|
|
53
|
-
|
|
54
|
-
pub fn get_message(bus: *MessageBus) *Message {
|
|
55
|
-
return bus.pool.get_message();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
pub fn unref(bus: *MessageBus, message: *Message) void {
|
|
59
|
-
bus.pool.unref(message);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
pub fn send_message_to_replica(bus: *MessageBus, replica: u8, message: *Message) void {
|
|
63
|
-
// Messages sent by a process to itself should never be passed to the message bus
|
|
64
|
-
if (bus.process == .replica) assert(replica != bus.process.replica);
|
|
65
|
-
|
|
66
|
-
bus.network.send_message(message, .{
|
|
67
|
-
.source = bus.process,
|
|
68
|
-
.target = .{ .replica = replica },
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/// Try to send the message to the client with the given id.
|
|
73
|
-
/// If the client is not currently connected, the message is silently dropped.
|
|
74
|
-
pub fn send_message_to_client(bus: *MessageBus, client_id: u128, message: *Message) void {
|
|
75
|
-
assert(bus.process == .replica);
|
|
76
|
-
|
|
77
|
-
bus.network.send_message(message, .{
|
|
78
|
-
.source = bus.process,
|
|
79
|
-
.target = .{ .client = client_id },
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
};
|