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.
Files changed (143) hide show
  1. package/README.md +212 -196
  2. package/dist/bin/aarch64-linux-gnu/client.node +0 -0
  3. package/dist/bin/aarch64-linux-musl/client.node +0 -0
  4. package/dist/bin/aarch64-macos/client.node +0 -0
  5. package/dist/bin/x86_64-linux-gnu/client.node +0 -0
  6. package/dist/bin/x86_64-linux-musl/client.node +0 -0
  7. package/dist/bin/x86_64-macos/client.node +0 -0
  8. package/dist/index.js +33 -1
  9. package/dist/index.js.map +1 -1
  10. package/package-lock.json +66 -0
  11. package/package.json +8 -17
  12. package/src/index.ts +56 -1
  13. package/src/node.zig +10 -9
  14. package/dist/.client.node.sha256 +0 -1
  15. package/scripts/build_lib.sh +0 -61
  16. package/scripts/download_node_headers.sh +0 -32
  17. package/src/tigerbeetle/scripts/benchmark.bat +0 -48
  18. package/src/tigerbeetle/scripts/benchmark.sh +0 -66
  19. package/src/tigerbeetle/scripts/confirm_image.sh +0 -44
  20. package/src/tigerbeetle/scripts/fuzz_loop.sh +0 -15
  21. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +0 -7
  22. package/src/tigerbeetle/scripts/install.bat +0 -7
  23. package/src/tigerbeetle/scripts/install.sh +0 -21
  24. package/src/tigerbeetle/scripts/install_zig.bat +0 -113
  25. package/src/tigerbeetle/scripts/install_zig.sh +0 -90
  26. package/src/tigerbeetle/scripts/lint.zig +0 -199
  27. package/src/tigerbeetle/scripts/pre-commit.sh +0 -9
  28. package/src/tigerbeetle/scripts/scripts/benchmark.bat +0 -48
  29. package/src/tigerbeetle/scripts/scripts/benchmark.sh +0 -66
  30. package/src/tigerbeetle/scripts/scripts/confirm_image.sh +0 -44
  31. package/src/tigerbeetle/scripts/scripts/fuzz_loop.sh +0 -15
  32. package/src/tigerbeetle/scripts/scripts/fuzz_unique_errors.sh +0 -7
  33. package/src/tigerbeetle/scripts/scripts/install.bat +0 -7
  34. package/src/tigerbeetle/scripts/scripts/install.sh +0 -21
  35. package/src/tigerbeetle/scripts/scripts/install_zig.bat +0 -113
  36. package/src/tigerbeetle/scripts/scripts/install_zig.sh +0 -90
  37. package/src/tigerbeetle/scripts/scripts/lint.zig +0 -199
  38. package/src/tigerbeetle/scripts/scripts/pre-commit.sh +0 -9
  39. package/src/tigerbeetle/scripts/scripts/shellcheck.sh +0 -5
  40. package/src/tigerbeetle/scripts/scripts/tests_on_alpine.sh +0 -10
  41. package/src/tigerbeetle/scripts/scripts/tests_on_ubuntu.sh +0 -14
  42. package/src/tigerbeetle/scripts/scripts/upgrade_ubuntu_kernel.sh +0 -48
  43. package/src/tigerbeetle/scripts/scripts/validate_docs.sh +0 -23
  44. package/src/tigerbeetle/scripts/scripts/vr_state_enumerate +0 -46
  45. package/src/tigerbeetle/scripts/shellcheck.sh +0 -5
  46. package/src/tigerbeetle/scripts/tests_on_alpine.sh +0 -10
  47. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +0 -14
  48. package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -48
  49. package/src/tigerbeetle/scripts/validate_docs.sh +0 -23
  50. package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
  51. package/src/tigerbeetle/src/benchmark.zig +0 -314
  52. package/src/tigerbeetle/src/config.zig +0 -234
  53. package/src/tigerbeetle/src/constants.zig +0 -436
  54. package/src/tigerbeetle/src/ewah.zig +0 -286
  55. package/src/tigerbeetle/src/ewah_benchmark.zig +0 -120
  56. package/src/tigerbeetle/src/ewah_fuzz.zig +0 -130
  57. package/src/tigerbeetle/src/fifo.zig +0 -120
  58. package/src/tigerbeetle/src/io/benchmark.zig +0 -213
  59. package/src/tigerbeetle/src/io/darwin.zig +0 -814
  60. package/src/tigerbeetle/src/io/linux.zig +0 -1062
  61. package/src/tigerbeetle/src/io/test.zig +0 -643
  62. package/src/tigerbeetle/src/io/windows.zig +0 -1183
  63. package/src/tigerbeetle/src/io.zig +0 -34
  64. package/src/tigerbeetle/src/iops.zig +0 -107
  65. package/src/tigerbeetle/src/lsm/README.md +0 -308
  66. package/src/tigerbeetle/src/lsm/binary_search.zig +0 -341
  67. package/src/tigerbeetle/src/lsm/bloom_filter.zig +0 -125
  68. package/src/tigerbeetle/src/lsm/compaction.zig +0 -603
  69. package/src/tigerbeetle/src/lsm/composite_key.zig +0 -77
  70. package/src/tigerbeetle/src/lsm/direction.zig +0 -11
  71. package/src/tigerbeetle/src/lsm/eytzinger.zig +0 -587
  72. package/src/tigerbeetle/src/lsm/eytzinger_benchmark.zig +0 -330
  73. package/src/tigerbeetle/src/lsm/forest.zig +0 -204
  74. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +0 -401
  75. package/src/tigerbeetle/src/lsm/grid.zig +0 -573
  76. package/src/tigerbeetle/src/lsm/groove.zig +0 -972
  77. package/src/tigerbeetle/src/lsm/k_way_merge.zig +0 -474
  78. package/src/tigerbeetle/src/lsm/level_iterator.zig +0 -332
  79. package/src/tigerbeetle/src/lsm/manifest.zig +0 -617
  80. package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -877
  81. package/src/tigerbeetle/src/lsm/manifest_log.zig +0 -789
  82. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +0 -691
  83. package/src/tigerbeetle/src/lsm/merge_iterator.zig +0 -106
  84. package/src/tigerbeetle/src/lsm/node_pool.zig +0 -235
  85. package/src/tigerbeetle/src/lsm/posted_groove.zig +0 -378
  86. package/src/tigerbeetle/src/lsm/segmented_array.zig +0 -1328
  87. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +0 -148
  88. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +0 -9
  89. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +0 -850
  90. package/src/tigerbeetle/src/lsm/table.zig +0 -1031
  91. package/src/tigerbeetle/src/lsm/table_immutable.zig +0 -203
  92. package/src/tigerbeetle/src/lsm/table_iterator.zig +0 -340
  93. package/src/tigerbeetle/src/lsm/table_mutable.zig +0 -220
  94. package/src/tigerbeetle/src/lsm/test.zig +0 -438
  95. package/src/tigerbeetle/src/lsm/tree.zig +0 -1193
  96. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +0 -474
  97. package/src/tigerbeetle/src/message_bus.zig +0 -1012
  98. package/src/tigerbeetle/src/message_pool.zig +0 -156
  99. package/src/tigerbeetle/src/ring_buffer.zig +0 -399
  100. package/src/tigerbeetle/src/simulator.zig +0 -569
  101. package/src/tigerbeetle/src/state_machine/auditor.zig +0 -577
  102. package/src/tigerbeetle/src/state_machine/workload.zig +0 -883
  103. package/src/tigerbeetle/src/state_machine.zig +0 -1881
  104. package/src/tigerbeetle/src/static_allocator.zig +0 -65
  105. package/src/tigerbeetle/src/stdx.zig +0 -162
  106. package/src/tigerbeetle/src/storage.zig +0 -393
  107. package/src/tigerbeetle/src/testing/cluster/message_bus.zig +0 -82
  108. package/src/tigerbeetle/src/testing/cluster/network.zig +0 -237
  109. package/src/tigerbeetle/src/testing/cluster/state_checker.zig +0 -169
  110. package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +0 -202
  111. package/src/tigerbeetle/src/testing/cluster.zig +0 -443
  112. package/src/tigerbeetle/src/testing/fuzz.zig +0 -140
  113. package/src/tigerbeetle/src/testing/hash_log.zig +0 -66
  114. package/src/tigerbeetle/src/testing/id.zig +0 -99
  115. package/src/tigerbeetle/src/testing/packet_simulator.zig +0 -364
  116. package/src/tigerbeetle/src/testing/priority_queue.zig +0 -645
  117. package/src/tigerbeetle/src/testing/reply_sequence.zig +0 -139
  118. package/src/tigerbeetle/src/testing/state_machine.zig +0 -249
  119. package/src/tigerbeetle/src/testing/storage.zig +0 -757
  120. package/src/tigerbeetle/src/testing/table.zig +0 -247
  121. package/src/tigerbeetle/src/testing/time.zig +0 -84
  122. package/src/tigerbeetle/src/tigerbeetle.zig +0 -227
  123. package/src/tigerbeetle/src/time.zig +0 -112
  124. package/src/tigerbeetle/src/tracer.zig +0 -529
  125. package/src/tigerbeetle/src/unit_tests.zig +0 -42
  126. package/src/tigerbeetle/src/vopr.zig +0 -495
  127. package/src/tigerbeetle/src/vsr/README.md +0 -209
  128. package/src/tigerbeetle/src/vsr/client.zig +0 -544
  129. package/src/tigerbeetle/src/vsr/clock.zig +0 -853
  130. package/src/tigerbeetle/src/vsr/journal.zig +0 -2413
  131. package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +0 -111
  132. package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
  133. package/src/tigerbeetle/src/vsr/replica.zig +0 -6381
  134. package/src/tigerbeetle/src/vsr/replica_format.zig +0 -219
  135. package/src/tigerbeetle/src/vsr/superblock.zig +0 -1631
  136. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +0 -256
  137. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +0 -929
  138. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +0 -334
  139. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +0 -390
  140. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +0 -615
  141. package/src/tigerbeetle/src/vsr/superblock_quorums.zig +0 -394
  142. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +0 -314
  143. 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
- };