tigerbeetle-node 0.11.8 → 0.11.10
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/dist/.client.node.sha256 +1 -1
- package/package.json +4 -3
- package/scripts/build_lib.sh +41 -0
- package/src/node.zig +1 -1
- package/src/tigerbeetle/scripts/validate_docs.sh +7 -1
- package/src/tigerbeetle/src/benchmark.zig +3 -3
- package/src/tigerbeetle/src/config.zig +31 -16
- package/src/tigerbeetle/src/constants.zig +48 -9
- package/src/tigerbeetle/src/ewah.zig +5 -5
- package/src/tigerbeetle/src/ewah_fuzz.zig +1 -1
- package/src/tigerbeetle/src/lsm/binary_search.zig +1 -1
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +1 -1
- package/src/tigerbeetle/src/lsm/compaction.zig +34 -21
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +84 -104
- package/src/tigerbeetle/src/lsm/grid.zig +19 -13
- package/src/tigerbeetle/src/lsm/manifest_log.zig +8 -10
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +18 -13
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +1 -1
- package/src/tigerbeetle/src/lsm/segmented_array.zig +17 -17
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +1 -1
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +1 -1
- package/src/tigerbeetle/src/lsm/table.zig +8 -20
- package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
- package/src/tigerbeetle/src/lsm/table_iterator.zig +3 -3
- package/src/tigerbeetle/src/lsm/table_mutable.zig +14 -2
- package/src/tigerbeetle/src/lsm/test.zig +5 -4
- package/src/tigerbeetle/src/lsm/tree.zig +1 -2
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +85 -115
- package/src/tigerbeetle/src/message_bus.zig +4 -4
- package/src/tigerbeetle/src/message_pool.zig +7 -10
- package/src/tigerbeetle/src/ring_buffer.zig +22 -12
- package/src/tigerbeetle/src/simulator.zig +366 -239
- package/src/tigerbeetle/src/state_machine/auditor.zig +5 -5
- package/src/tigerbeetle/src/state_machine/workload.zig +3 -3
- package/src/tigerbeetle/src/state_machine.zig +190 -178
- package/src/tigerbeetle/src/{util.zig → stdx.zig} +2 -0
- package/src/tigerbeetle/src/storage.zig +13 -6
- package/src/tigerbeetle/src/{test → testing/cluster}/message_bus.zig +3 -3
- package/src/tigerbeetle/src/{test → testing/cluster}/network.zig +46 -22
- package/src/tigerbeetle/src/testing/cluster/state_checker.zig +169 -0
- package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +202 -0
- package/src/tigerbeetle/src/testing/cluster.zig +443 -0
- package/src/tigerbeetle/src/{test → testing}/fuzz.zig +0 -0
- package/src/tigerbeetle/src/testing/hash_log.zig +66 -0
- package/src/tigerbeetle/src/{test → testing}/id.zig +0 -0
- package/src/tigerbeetle/src/testing/packet_simulator.zig +365 -0
- package/src/tigerbeetle/src/{test → testing}/priority_queue.zig +1 -1
- package/src/tigerbeetle/src/testing/reply_sequence.zig +139 -0
- package/src/tigerbeetle/src/{test → testing}/state_machine.zig +3 -1
- package/src/tigerbeetle/src/testing/storage.zig +757 -0
- package/src/tigerbeetle/src/{test → testing}/table.zig +21 -0
- package/src/tigerbeetle/src/{test → testing}/time.zig +0 -0
- package/src/tigerbeetle/src/tigerbeetle.zig +2 -0
- package/src/tigerbeetle/src/tracer.zig +3 -3
- package/src/tigerbeetle/src/unit_tests.zig +4 -4
- package/src/tigerbeetle/src/vopr.zig +2 -2
- package/src/tigerbeetle/src/vsr/client.zig +5 -2
- package/src/tigerbeetle/src/vsr/clock.zig +93 -53
- package/src/tigerbeetle/src/vsr/journal.zig +109 -98
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +2 -2
- package/src/tigerbeetle/src/vsr/replica.zig +1983 -1430
- package/src/tigerbeetle/src/vsr/replica_format.zig +13 -13
- package/src/tigerbeetle/src/vsr/superblock.zig +240 -142
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -7
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +1 -1
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +1 -1
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +49 -14
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +38 -19
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +48 -48
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +51 -51
- package/src/tigerbeetle/src/vsr.zig +99 -33
- package/src/tigerbeetle/src/demo.zig +0 -132
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +0 -35
- package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +0 -7
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +0 -37
- package/src/tigerbeetle/src/demo_04_create_pending_transfers.zig +0 -61
- package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +0 -37
- package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +0 -24
- package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +0 -7
- package/src/tigerbeetle/src/test/cluster.zig +0 -352
- package/src/tigerbeetle/src/test/conductor.zig +0 -366
- package/src/tigerbeetle/src/test/packet_simulator.zig +0 -398
- package/src/tigerbeetle/src/test/state_checker.zig +0 -169
- package/src/tigerbeetle/src/test/storage.zig +0 -864
- package/src/tigerbeetle/src/test/storage_checker.zig +0 -204
|
@@ -4,7 +4,7 @@ const mem = std.mem;
|
|
|
4
4
|
|
|
5
5
|
const constants = @import("../constants.zig");
|
|
6
6
|
const vsr = @import("../vsr.zig");
|
|
7
|
-
const
|
|
7
|
+
const stdx = @import("../stdx.zig");
|
|
8
8
|
|
|
9
9
|
const MessagePool = @import("../message_pool.zig").MessagePool;
|
|
10
10
|
|
|
@@ -128,7 +128,7 @@ pub const ClientTable = struct {
|
|
|
128
128
|
size = new_size;
|
|
129
129
|
|
|
130
130
|
for (entries) |entry| {
|
|
131
|
-
|
|
131
|
+
stdx.copy_disjoint(.inexact, u8, target[size..], mem.asBytes(entry.reply.header));
|
|
132
132
|
size += @sizeOf(vsr.Header);
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -138,7 +138,7 @@ pub const ClientTable = struct {
|
|
|
138
138
|
size = new_size;
|
|
139
139
|
|
|
140
140
|
for (entries) |entry| {
|
|
141
|
-
|
|
141
|
+
stdx.copy_disjoint(.inexact, u8, target[size..], mem.asBytes(&entry.session));
|
|
142
142
|
size += @sizeOf(u64);
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -150,7 +150,7 @@ pub const ClientTable = struct {
|
|
|
150
150
|
for (entries) |entry| {
|
|
151
151
|
const body = entry.reply.body();
|
|
152
152
|
assert(body.len == (entry.reply.header.size - @sizeOf(vsr.Header)));
|
|
153
|
-
|
|
153
|
+
stdx.copy_disjoint(.inexact, u8, target[size..], body);
|
|
154
154
|
size += body.len;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -159,7 +159,7 @@ pub const ClientTable = struct {
|
|
|
159
159
|
std.mem.set(u8, target[size..new_size], 0);
|
|
160
160
|
size = new_size;
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
stdx.copy_disjoint(.inexact, u8, target[size..], mem.asBytes(&entries_count));
|
|
163
163
|
size += @sizeOf(u32);
|
|
164
164
|
|
|
165
165
|
assert(size <= encode_size_max);
|
|
@@ -169,7 +169,7 @@ pub const ClientTable = struct {
|
|
|
169
169
|
pub fn decode(client_table: *ClientTable, source: []align(@alignOf(vsr.Header)) const u8) void {
|
|
170
170
|
// Read the entry count at the end of the buffer to determine how many there are.
|
|
171
171
|
var entries_count: u32 = undefined;
|
|
172
|
-
|
|
172
|
+
stdx.copy_disjoint(.exact, u8, mem.asBytes(&entries_count), source[source.len - @sizeOf(u32) ..]);
|
|
173
173
|
assert(entries_count <= client_table.sorted.len);
|
|
174
174
|
|
|
175
175
|
assert(client_table.count() == 0);
|
|
@@ -214,7 +214,7 @@ pub const ClientTable = struct {
|
|
|
214
214
|
|
|
215
215
|
// Read the message body for the entry.
|
|
216
216
|
assert(bodies.len >= body_size);
|
|
217
|
-
|
|
217
|
+
stdx.copy_disjoint(.exact, u8, body, bodies[0..body_size]);
|
|
218
218
|
bodies = bodies[body_size..];
|
|
219
219
|
assert(entry.reply.header.valid_checksum_body(body));
|
|
220
220
|
|
|
@@ -8,7 +8,7 @@ const MaskInt = DynamicBitSetUnmanaged.MaskInt;
|
|
|
8
8
|
const constants = @import("../constants.zig");
|
|
9
9
|
|
|
10
10
|
const ewah = @import("../ewah.zig").ewah(usize);
|
|
11
|
-
const div_ceil = @import("../
|
|
11
|
+
const div_ceil = @import("../stdx.zig").div_ceil;
|
|
12
12
|
|
|
13
13
|
/// This is logically a range of addresses within the FreeSet, but its actual fields are block
|
|
14
14
|
/// indexes for ease of calculation.
|
|
@@ -7,7 +7,7 @@ const log = std.log.scoped(.fuzz_vsr_superblock_free_set);
|
|
|
7
7
|
|
|
8
8
|
const FreeSet = @import("./superblock_free_set.zig").FreeSet;
|
|
9
9
|
const Reservation = @import("./superblock_free_set.zig").Reservation;
|
|
10
|
-
const fuzz = @import("../
|
|
10
|
+
const fuzz = @import("../testing/fuzz.zig");
|
|
11
11
|
|
|
12
12
|
pub const tigerbeetle_config = @import("../config.zig").configs.test_min;
|
|
13
13
|
|
|
@@ -15,16 +15,17 @@ const assert = std.debug.assert;
|
|
|
15
15
|
const log = std.log.scoped(.fuzz_vsr_superblock);
|
|
16
16
|
|
|
17
17
|
const constants = @import("../constants.zig");
|
|
18
|
-
const
|
|
18
|
+
const stdx = @import("../stdx.zig");
|
|
19
19
|
const vsr = @import("../vsr.zig");
|
|
20
|
-
const Storage = @import("../
|
|
20
|
+
const Storage = @import("../testing/storage.zig").Storage;
|
|
21
|
+
const StorageFaultAtlas = @import("../testing/storage.zig").ClusterFaultAtlas;
|
|
21
22
|
const MessagePool = @import("../message_pool.zig").MessagePool;
|
|
22
23
|
const superblock_zone_size = @import("superblock.zig").superblock_zone_size;
|
|
23
24
|
const data_file_size_min = @import("superblock.zig").data_file_size_min;
|
|
24
|
-
const VSRState = @import("superblock.zig").
|
|
25
|
+
const VSRState = @import("superblock.zig").SuperBlockHeader.VSRState;
|
|
25
26
|
const SuperBlockType = @import("superblock.zig").SuperBlockType;
|
|
26
27
|
const SuperBlock = SuperBlockType(Storage);
|
|
27
|
-
const fuzz = @import("../
|
|
28
|
+
const fuzz = @import("../testing/fuzz.zig");
|
|
28
29
|
|
|
29
30
|
pub const tigerbeetle_config = @import("../config.zig").configs.test_min;
|
|
30
31
|
|
|
@@ -44,7 +45,14 @@ fn run_fuzz(allocator: std.mem.Allocator, seed: u64, transitions_count_total: us
|
|
|
44
45
|
var prng = std.rand.DefaultPrng.init(seed);
|
|
45
46
|
const random = prng.random();
|
|
46
47
|
|
|
48
|
+
const storage_fault_atlas = StorageFaultAtlas.init(1, random, .{
|
|
49
|
+
.faulty_superblock = true,
|
|
50
|
+
.faulty_wal_headers = false,
|
|
51
|
+
.faulty_wal_prepares = false,
|
|
52
|
+
});
|
|
53
|
+
|
|
47
54
|
const storage_options = .{
|
|
55
|
+
.replica_index = 0,
|
|
48
56
|
.seed = random.int(u64),
|
|
49
57
|
// SuperBlock's IO is all serial, so latencies never reorder reads/writes.
|
|
50
58
|
.read_latency_min = 1,
|
|
@@ -56,7 +64,7 @@ fn run_fuzz(allocator: std.mem.Allocator, seed: u64, transitions_count_total: us
|
|
|
56
64
|
.read_fault_probability = 25 + random.uintLessThan(u8, 76),
|
|
57
65
|
.write_fault_probability = 25 + random.uintLessThan(u8, 76),
|
|
58
66
|
.crash_fault_probability = 50 + random.uintLessThan(u8, 51),
|
|
59
|
-
.
|
|
67
|
+
.fault_atlas = &storage_fault_atlas,
|
|
60
68
|
};
|
|
61
69
|
|
|
62
70
|
var storage = try Storage.init(allocator, superblock_zone_size, storage_options);
|
|
@@ -143,9 +151,10 @@ const Environment = struct {
|
|
|
143
151
|
/// Indexed by sequence.
|
|
144
152
|
const SequenceStates = std.ArrayList(struct {
|
|
145
153
|
vsr_state: VSRState,
|
|
154
|
+
vsr_headers: vsr.ViewChangeHeaders.BoundedArray,
|
|
146
155
|
/// Track the expected `checksum(free_set)`.
|
|
147
156
|
/// Note that this is a checksum of the decoded free set; it is not the same as
|
|
148
|
-
/// `
|
|
157
|
+
/// `SuperBlockHeader.free_set_checksum`.
|
|
149
158
|
free_set: u128,
|
|
150
159
|
});
|
|
151
160
|
|
|
@@ -158,7 +167,7 @@ const Environment = struct {
|
|
|
158
167
|
latest_sequence: u64 = 0,
|
|
159
168
|
latest_checksum: u128 = 0,
|
|
160
169
|
latest_parent: u128 = 0,
|
|
161
|
-
latest_vsr_state: VSRState = std.mem.
|
|
170
|
+
latest_vsr_state: VSRState = std.mem.zeroes(VSRState),
|
|
162
171
|
|
|
163
172
|
context_format: SuperBlock.Context = undefined,
|
|
164
173
|
context_open: SuperBlock.Context = undefined,
|
|
@@ -224,7 +233,7 @@ const Environment = struct {
|
|
|
224
233
|
if (env.latest_checksum != 0) {
|
|
225
234
|
if (env.latest_sequence + 1 == env.superblock_verify.working.sequence) {
|
|
226
235
|
// After a checkpoint() or view_change(), the parent points to the previous
|
|
227
|
-
// working
|
|
236
|
+
// working header.
|
|
228
237
|
assert(env.superblock_verify.working.parent == env.latest_checksum);
|
|
229
238
|
}
|
|
230
239
|
}
|
|
@@ -260,10 +269,14 @@ const Environment = struct {
|
|
|
260
269
|
.replica = 0,
|
|
261
270
|
});
|
|
262
271
|
|
|
272
|
+
var vsr_headers = vsr.ViewChangeHeaders.BoundedArray{ .buffer = undefined };
|
|
273
|
+
vsr_headers.appendAssumeCapacity(vsr.Header.root_prepare(cluster));
|
|
274
|
+
|
|
263
275
|
assert(env.sequence_states.items.len == 0);
|
|
264
276
|
try env.sequence_states.append(undefined); // skip sequence=0
|
|
265
277
|
try env.sequence_states.append(.{
|
|
266
278
|
.vsr_state = VSRState.root(cluster),
|
|
279
|
+
.vsr_headers = vsr_headers,
|
|
267
280
|
.free_set = checksum_free_set(env.superblock),
|
|
268
281
|
});
|
|
269
282
|
}
|
|
@@ -298,18 +311,33 @@ const Environment = struct {
|
|
|
298
311
|
.commit_min_checksum = env.superblock.staging.vsr_state.commit_min_checksum,
|
|
299
312
|
.commit_min = env.superblock.staging.vsr_state.commit_min,
|
|
300
313
|
.commit_max = env.superblock.staging.vsr_state.commit_max + 3,
|
|
301
|
-
.
|
|
314
|
+
.log_view = env.superblock.staging.vsr_state.log_view + 4,
|
|
302
315
|
.view = env.superblock.staging.vsr_state.view + 5,
|
|
303
316
|
};
|
|
304
317
|
|
|
318
|
+
var vsr_headers = vsr.ViewChangeHeaders.BoundedArray{ .buffer = undefined };
|
|
319
|
+
var vsr_head = std.mem.zeroInit(vsr.Header, .{
|
|
320
|
+
.command = .prepare,
|
|
321
|
+
.op = env.superblock.staging.vsr_state.commit_min,
|
|
322
|
+
});
|
|
323
|
+
vsr_head.set_checksum_body(&.{});
|
|
324
|
+
vsr_head.set_checksum();
|
|
325
|
+
vsr_headers.appendAssumeCapacity(vsr_head);
|
|
326
|
+
|
|
305
327
|
assert(env.sequence_states.items.len == env.superblock.staging.sequence + 1);
|
|
306
328
|
try env.sequence_states.append(.{
|
|
307
329
|
.vsr_state = vsr_state,
|
|
330
|
+
.vsr_headers = vsr_headers,
|
|
308
331
|
.free_set = env.sequence_states.items[env.sequence_states.items.len - 1].free_set,
|
|
309
332
|
});
|
|
310
333
|
|
|
311
334
|
env.pending.insert(.view_change);
|
|
312
|
-
env.superblock.view_change(view_change_callback, &env.context_view_change,
|
|
335
|
+
env.superblock.view_change(view_change_callback, &env.context_view_change, .{
|
|
336
|
+
.commit_max = vsr_state.commit_max,
|
|
337
|
+
.log_view = vsr_state.log_view,
|
|
338
|
+
.view = vsr_state.view,
|
|
339
|
+
.headers = vsr_headers,
|
|
340
|
+
});
|
|
313
341
|
}
|
|
314
342
|
|
|
315
343
|
fn view_change_callback(context: *SuperBlock.Context) void {
|
|
@@ -326,18 +354,25 @@ const Environment = struct {
|
|
|
326
354
|
.commit_min_checksum = env.superblock.staging.vsr_state.commit_min_checksum + 1,
|
|
327
355
|
.commit_min = env.superblock.staging.vsr_state.commit_min + 1,
|
|
328
356
|
.commit_max = env.superblock.staging.vsr_state.commit_max + 1,
|
|
329
|
-
.
|
|
330
|
-
.view = env.superblock.staging.vsr_state.view
|
|
357
|
+
.log_view = env.superblock.staging.vsr_state.log_view,
|
|
358
|
+
.view = env.superblock.staging.vsr_state.view,
|
|
331
359
|
};
|
|
332
360
|
|
|
333
361
|
assert(env.sequence_states.items.len == env.superblock.staging.sequence + 1);
|
|
334
362
|
try env.sequence_states.append(.{
|
|
335
363
|
.vsr_state = vsr_state,
|
|
364
|
+
.vsr_headers = vsr.ViewChangeHeaders.BoundedArray.fromSlice(
|
|
365
|
+
env.superblock.staging.vsr_headers().slice,
|
|
366
|
+
) catch unreachable,
|
|
336
367
|
.free_set = checksum_free_set(env.superblock),
|
|
337
368
|
});
|
|
338
369
|
|
|
339
370
|
env.pending.insert(.checkpoint);
|
|
340
|
-
env.superblock.checkpoint(checkpoint_callback, &env.context_checkpoint,
|
|
371
|
+
env.superblock.checkpoint(checkpoint_callback, &env.context_checkpoint, .{
|
|
372
|
+
.commit_min_checksum = vsr_state.commit_min_checksum,
|
|
373
|
+
.commit_min = vsr_state.commit_min,
|
|
374
|
+
.commit_max = vsr_state.commit_max,
|
|
375
|
+
});
|
|
341
376
|
}
|
|
342
377
|
|
|
343
378
|
fn checkpoint_callback(context: *SuperBlock.Context) void {
|
|
@@ -350,6 +385,6 @@ const Environment = struct {
|
|
|
350
385
|
fn checksum_free_set(superblock: *const SuperBlock) u128 {
|
|
351
386
|
const mask_bits = @bitSizeOf(std.DynamicBitSetUnmanaged.MaskInt);
|
|
352
387
|
const count_bits = superblock.free_set.blocks.bit_length;
|
|
353
|
-
const count_words =
|
|
388
|
+
const count_words = stdx.div_ceil(count_bits, mask_bits);
|
|
354
389
|
return vsr.checksum(std.mem.sliceAsBytes(superblock.free_set.blocks.masks[0..count_words]));
|
|
355
390
|
}
|
|
@@ -6,7 +6,7 @@ const log = std.log.scoped(.superblock_manifest);
|
|
|
6
6
|
const mem = std.mem;
|
|
7
7
|
|
|
8
8
|
const constants = @import("../constants.zig");
|
|
9
|
-
const
|
|
9
|
+
const stdx = @import("../stdx.zig");
|
|
10
10
|
|
|
11
11
|
// TODO Compute & use the upper bound of manifest blocks (per tree) to size the trailer zone.
|
|
12
12
|
|
|
@@ -29,7 +29,7 @@ pub const Manifest = struct {
|
|
|
29
29
|
/// A map from table address to the manifest block and entry that is the latest extent version.
|
|
30
30
|
/// Used to determine whether a table should be dropped in a compaction.
|
|
31
31
|
/// Shared by all trees and sized to accommodate all possible tables.
|
|
32
|
-
tables:
|
|
32
|
+
tables: Tables,
|
|
33
33
|
|
|
34
34
|
/// A set of block addresses that have free entries.
|
|
35
35
|
/// Used to determine whether a block should be compacted.
|
|
@@ -37,6 +37,13 @@ pub const Manifest = struct {
|
|
|
37
37
|
/// This enables us to track fragmentation even in unflushed blocks.
|
|
38
38
|
compaction_set: std.AutoHashMapUnmanaged(u64, void),
|
|
39
39
|
|
|
40
|
+
pub const Tables = std.AutoHashMapUnmanaged(TableExtentKey, TableExtent);
|
|
41
|
+
|
|
42
|
+
pub const TableExtentKey = struct {
|
|
43
|
+
tree_hash: u128,
|
|
44
|
+
table: u64,
|
|
45
|
+
};
|
|
46
|
+
|
|
40
47
|
pub const TableExtent = struct {
|
|
41
48
|
block: u64, // ManifestLog block address.
|
|
42
49
|
entry: u32, // Index within the ManifestLog Label/TableInfo arrays.
|
|
@@ -60,7 +67,7 @@ pub const Manifest = struct {
|
|
|
60
67
|
const addresses = try allocator.alloc(u64, manifest_block_count_max);
|
|
61
68
|
errdefer allocator.free(addresses);
|
|
62
69
|
|
|
63
|
-
var tables =
|
|
70
|
+
var tables = Tables{};
|
|
64
71
|
try tables.ensureTotalCapacity(allocator, forest_table_count_max);
|
|
65
72
|
errdefer tables.deinit(allocator);
|
|
66
73
|
|
|
@@ -100,7 +107,7 @@ pub const Manifest = struct {
|
|
|
100
107
|
var size: u64 = 0;
|
|
101
108
|
|
|
102
109
|
const trees = target[size..][0 .. manifest.count * @sizeOf(u128)];
|
|
103
|
-
|
|
110
|
+
stdx.copy_disjoint(
|
|
104
111
|
.exact,
|
|
105
112
|
u128,
|
|
106
113
|
mem.bytesAsSlice(u128, trees),
|
|
@@ -109,7 +116,7 @@ pub const Manifest = struct {
|
|
|
109
116
|
size += trees.len;
|
|
110
117
|
|
|
111
118
|
const checksums = target[size..][0 .. manifest.count * @sizeOf(u128)];
|
|
112
|
-
|
|
119
|
+
stdx.copy_disjoint(
|
|
113
120
|
.exact,
|
|
114
121
|
u128,
|
|
115
122
|
mem.bytesAsSlice(u128, checksums),
|
|
@@ -118,7 +125,7 @@ pub const Manifest = struct {
|
|
|
118
125
|
size += checksums.len;
|
|
119
126
|
|
|
120
127
|
const addresses = target[size..][0 .. manifest.count * @sizeOf(u64)];
|
|
121
|
-
|
|
128
|
+
stdx.copy_disjoint(
|
|
122
129
|
.exact,
|
|
123
130
|
u64,
|
|
124
131
|
mem.bytesAsSlice(u64, addresses),
|
|
@@ -144,7 +151,7 @@ pub const Manifest = struct {
|
|
|
144
151
|
var size: u64 = 0;
|
|
145
152
|
|
|
146
153
|
const trees = source[size..][0 .. manifest.count * @sizeOf(u128)];
|
|
147
|
-
|
|
154
|
+
stdx.copy_disjoint(
|
|
148
155
|
.exact,
|
|
149
156
|
u128,
|
|
150
157
|
manifest.trees[0..manifest.count],
|
|
@@ -153,7 +160,7 @@ pub const Manifest = struct {
|
|
|
153
160
|
size += trees.len;
|
|
154
161
|
|
|
155
162
|
const checksums = source[size..][0 .. manifest.count * @sizeOf(u128)];
|
|
156
|
-
|
|
163
|
+
stdx.copy_disjoint(
|
|
157
164
|
.exact,
|
|
158
165
|
u128,
|
|
159
166
|
manifest.checksums[0..manifest.count],
|
|
@@ -162,7 +169,7 @@ pub const Manifest = struct {
|
|
|
162
169
|
size += checksums.len;
|
|
163
170
|
|
|
164
171
|
const addresses = source[size..][0 .. manifest.count * @sizeOf(u64)];
|
|
165
|
-
|
|
172
|
+
stdx.copy_disjoint(
|
|
166
173
|
.exact,
|
|
167
174
|
u64,
|
|
168
175
|
manifest.addresses[0..manifest.count],
|
|
@@ -224,9 +231,9 @@ pub const Manifest = struct {
|
|
|
224
231
|
manifest.verify_index_tree_checksum_address(index, tree, checksum, address);
|
|
225
232
|
|
|
226
233
|
const tail = manifest.count - (index + 1);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
stdx.copy_left(.inexact, u128, manifest.trees[index..], manifest.trees[index + 1 ..][0..tail]);
|
|
235
|
+
stdx.copy_left(.inexact, u128, manifest.checksums[index..], manifest.checksums[index + 1 ..][0..tail]);
|
|
236
|
+
stdx.copy_left(.inexact, u64, manifest.addresses[index..], manifest.addresses[index + 1 ..][0..tail]);
|
|
230
237
|
manifest.count -= 1;
|
|
231
238
|
|
|
232
239
|
manifest.trees[manifest.count] = 0;
|
|
@@ -293,11 +300,14 @@ pub const Manifest = struct {
|
|
|
293
300
|
|
|
294
301
|
/// Inserts the table extent if it does not yet exist, and returns true.
|
|
295
302
|
/// Otherwise, returns false.
|
|
296
|
-
pub fn insert_table_extent(manifest: *Manifest, table: u64, block: u64, entry: u32) bool {
|
|
303
|
+
pub fn insert_table_extent(manifest: *Manifest, tree_hash: u128, table: u64, block: u64, entry: u32) bool {
|
|
297
304
|
assert(table > 0);
|
|
298
305
|
assert(block > 0);
|
|
299
306
|
|
|
300
|
-
var extent = manifest.tables.getOrPutAssumeCapacity(
|
|
307
|
+
var extent = manifest.tables.getOrPutAssumeCapacity(.{
|
|
308
|
+
.tree_hash = tree_hash,
|
|
309
|
+
.table = table,
|
|
310
|
+
});
|
|
301
311
|
if (extent.found_existing) return false;
|
|
302
312
|
|
|
303
313
|
extent.value_ptr.* = .{
|
|
@@ -311,11 +321,14 @@ pub const Manifest = struct {
|
|
|
311
321
|
/// Inserts or updates the table extent, and returns the previous block address if any.
|
|
312
322
|
/// The table extent must be updated immediately when appending, without delay.
|
|
313
323
|
/// Otherwise, ManifestLog.compact() may append a stale version over the latest.
|
|
314
|
-
pub fn update_table_extent(manifest: *Manifest, table: u64, block: u64, entry: u32) ?u64 {
|
|
324
|
+
pub fn update_table_extent(manifest: *Manifest, tree_hash: u128, table: u64, block: u64, entry: u32) ?u64 {
|
|
315
325
|
assert(table > 0);
|
|
316
326
|
assert(block > 0);
|
|
317
327
|
|
|
318
|
-
var extent = manifest.tables.getOrPutAssumeCapacity(
|
|
328
|
+
var extent = manifest.tables.getOrPutAssumeCapacity(.{
|
|
329
|
+
.tree_hash = tree_hash,
|
|
330
|
+
.table = table,
|
|
331
|
+
});
|
|
319
332
|
const previous_block = if (extent.found_existing) extent.value_ptr.block else null;
|
|
320
333
|
|
|
321
334
|
extent.value_ptr.* = .{
|
|
@@ -328,13 +341,19 @@ pub const Manifest = struct {
|
|
|
328
341
|
|
|
329
342
|
/// Removes the table extent if { block, entry } is the latest version, and returns true.
|
|
330
343
|
/// Otherwise, returns false.
|
|
331
|
-
pub fn remove_table_extent(manifest: *Manifest, table: u64, block: u64, entry: u32) bool {
|
|
344
|
+
pub fn remove_table_extent(manifest: *Manifest, tree_hash: u128, table: u64, block: u64, entry: u32) bool {
|
|
332
345
|
assert(table > 0);
|
|
333
346
|
assert(block > 0);
|
|
334
347
|
|
|
335
|
-
const extent = manifest.tables.getPtr(
|
|
348
|
+
const extent = manifest.tables.getPtr(.{
|
|
349
|
+
.tree_hash = tree_hash,
|
|
350
|
+
.table = table,
|
|
351
|
+
}).?;
|
|
336
352
|
if (extent.block == block and extent.entry == entry) {
|
|
337
|
-
assert(manifest.tables.remove(
|
|
353
|
+
assert(manifest.tables.remove(.{
|
|
354
|
+
.tree_hash = tree_hash,
|
|
355
|
+
.table = table,
|
|
356
|
+
}));
|
|
338
357
|
|
|
339
358
|
return true;
|
|
340
359
|
} else {
|
|
@@ -3,7 +3,7 @@ const assert = std.debug.assert;
|
|
|
3
3
|
const log = std.log.scoped(.superblock_quorums);
|
|
4
4
|
|
|
5
5
|
const superblock = @import("./superblock.zig");
|
|
6
|
-
const
|
|
6
|
+
const SuperBlockHeader = superblock.SuperBlockHeader;
|
|
7
7
|
const SuperBlockVersion = superblock.SuperBlockVersion;
|
|
8
8
|
const fuzz = @import("./superblock_quorums_fuzz.zig");
|
|
9
9
|
|
|
@@ -16,10 +16,10 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
16
16
|
const Quorums = @This();
|
|
17
17
|
|
|
18
18
|
const Quorum = struct {
|
|
19
|
-
|
|
19
|
+
header: *const SuperBlockHeader,
|
|
20
20
|
valid: bool = false,
|
|
21
21
|
/// Track which copies are a member of the quorum.
|
|
22
|
-
/// Used to ignore duplicate copies of a
|
|
22
|
+
/// Used to ignore duplicate copies of a header when determining a quorum.
|
|
23
23
|
copies: QuorumCount = QuorumCount.initEmpty(),
|
|
24
24
|
/// An integer value indicates the copy index found in the corresponding slot.
|
|
25
25
|
/// A `null` value indicates that the copy is invalid or not a member of the working
|
|
@@ -86,7 +86,7 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
86
86
|
/// trailers may be damaged.
|
|
87
87
|
pub fn working(
|
|
88
88
|
quorums: *Quorums,
|
|
89
|
-
copies: []
|
|
89
|
+
copies: []SuperBlockHeader,
|
|
90
90
|
threshold: Threshold,
|
|
91
91
|
) Error!Quorum {
|
|
92
92
|
assert(copies.len == options.superblock_copies);
|
|
@@ -102,17 +102,17 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
102
102
|
for (quorums.slice()) |quorum| {
|
|
103
103
|
if (quorum.copies.count() == options.superblock_copies) {
|
|
104
104
|
log.debug("quorum: checksum={x} parent={x} sequence={} count={} valid={}", .{
|
|
105
|
-
quorum.
|
|
106
|
-
quorum.
|
|
107
|
-
quorum.
|
|
105
|
+
quorum.header.checksum,
|
|
106
|
+
quorum.header.parent,
|
|
107
|
+
quorum.header.sequence,
|
|
108
108
|
quorum.copies.count(),
|
|
109
109
|
quorum.valid,
|
|
110
110
|
});
|
|
111
111
|
} else {
|
|
112
112
|
log.warn("quorum: checksum={x} parent={x} sequence={} count={} valid={}", .{
|
|
113
|
-
quorum.
|
|
114
|
-
quorum.
|
|
115
|
-
quorum.
|
|
113
|
+
quorum.header.checksum,
|
|
114
|
+
quorum.header.parent,
|
|
115
|
+
quorum.header.sequence,
|
|
116
116
|
quorum.copies.count(),
|
|
117
117
|
quorum.valid,
|
|
118
118
|
});
|
|
@@ -128,7 +128,7 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
128
128
|
// Verify that the remaining quorums are correctly sorted:
|
|
129
129
|
for (quorums.slice()[1..]) |a| {
|
|
130
130
|
assert(sort_priority_descending({}, b, a));
|
|
131
|
-
assert(a.
|
|
131
|
+
assert(a.header.valid_checksum());
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// Even the best copy with the most quorum still has inadequate quorum.
|
|
@@ -140,61 +140,61 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
140
140
|
// - a lost or misdirected write
|
|
141
141
|
// - a latent sector error that prevented a write
|
|
142
142
|
for (quorums.slice()[1..]) |a| {
|
|
143
|
-
if (a.
|
|
143
|
+
if (a.header.cluster != b.header.cluster) {
|
|
144
144
|
log.warn("superblock copy={} has cluster={} instead of {}", .{
|
|
145
|
-
a.
|
|
146
|
-
a.
|
|
147
|
-
b.
|
|
145
|
+
a.header.copy,
|
|
146
|
+
a.header.cluster,
|
|
147
|
+
b.header.cluster,
|
|
148
148
|
});
|
|
149
149
|
continue;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
if (a.
|
|
152
|
+
if (a.header.replica != b.header.replica) {
|
|
153
153
|
log.warn("superblock copy={} has replica={} instead of {}", .{
|
|
154
|
-
a.
|
|
155
|
-
a.
|
|
156
|
-
b.
|
|
154
|
+
a.header.copy,
|
|
155
|
+
a.header.replica,
|
|
156
|
+
b.header.replica,
|
|
157
157
|
});
|
|
158
158
|
continue;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
if (a.
|
|
161
|
+
if (a.header.sequence == b.header.sequence) {
|
|
162
162
|
// Two quorums, same cluster+replica+sequence, but different checksums.
|
|
163
163
|
// This shouldn't ever happen — but if it does, we can't safely repair.
|
|
164
|
-
assert(a.
|
|
164
|
+
assert(a.header.checksum != b.header.checksum);
|
|
165
165
|
return error.Fork;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
if (a.
|
|
168
|
+
if (a.header.sequence > b.header.sequence + 1) {
|
|
169
169
|
// We read sequences such as (2,2,2,4) — 2 isn't safe to use, but there isn't a
|
|
170
170
|
// valid quorum for 4 either.
|
|
171
171
|
return error.ParentSkipped;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
if (a.
|
|
175
|
-
assert(a.
|
|
176
|
-
assert(a.
|
|
177
|
-
assert(a.
|
|
174
|
+
if (a.header.sequence + 1 == b.header.sequence) {
|
|
175
|
+
assert(a.header.checksum != b.header.checksum);
|
|
176
|
+
assert(a.header.cluster == b.header.cluster);
|
|
177
|
+
assert(a.header.replica == b.header.replica);
|
|
178
178
|
|
|
179
|
-
if (a.
|
|
179
|
+
if (a.header.checksum != b.header.parent) {
|
|
180
180
|
return error.ParentNotConnected;
|
|
181
|
-
} else if (!a.
|
|
181
|
+
} else if (!a.header.vsr_state.monotonic(b.header.vsr_state)) {
|
|
182
182
|
return error.VSRStateNotMonotonic;
|
|
183
183
|
} else {
|
|
184
|
-
assert(b.
|
|
184
|
+
assert(b.header.valid_checksum());
|
|
185
185
|
|
|
186
186
|
return b;
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
assert(b.
|
|
191
|
+
assert(b.header.valid_checksum());
|
|
192
192
|
return b;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
fn count_copy(
|
|
196
196
|
quorums: *Quorums,
|
|
197
|
-
copy: *const
|
|
197
|
+
copy: *const SuperBlockHeader,
|
|
198
198
|
slot: usize,
|
|
199
199
|
threshold: Threshold,
|
|
200
200
|
) void {
|
|
@@ -240,12 +240,12 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
var quorum = quorums.find_or_insert_quorum_for_copy(copy);
|
|
243
|
-
assert(quorum.
|
|
244
|
-
assert(quorum.
|
|
243
|
+
assert(quorum.header.checksum == copy.checksum);
|
|
244
|
+
assert(quorum.header.equal(copy));
|
|
245
245
|
|
|
246
246
|
if (copy.copy >= options.superblock_copies) {
|
|
247
|
-
// This
|
|
248
|
-
// The "
|
|
247
|
+
// This header is a valid member of the quorum, but with an unexpected copy number.
|
|
248
|
+
// The "SuperBlockHeader.copy" field is not protected by the checksum, so if that byte
|
|
249
249
|
// (and only that byte) is corrupted, the superblock is still valid — but we don't know
|
|
250
250
|
// for certain which copy this was supposed to be.
|
|
251
251
|
// We make the assumption that this was not a double-fault (corrupt + misdirect) —
|
|
@@ -262,13 +262,13 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
262
262
|
quorum.valid = quorum.copies.count() >= threshold.count();
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
fn find_or_insert_quorum_for_copy(quorums: *Quorums, copy: *const
|
|
265
|
+
fn find_or_insert_quorum_for_copy(quorums: *Quorums, copy: *const SuperBlockHeader) *Quorum {
|
|
266
266
|
assert(copy.valid_checksum());
|
|
267
267
|
|
|
268
268
|
for (quorums.array[0..quorums.count]) |*quorum| {
|
|
269
|
-
if (copy.checksum == quorum.
|
|
269
|
+
if (copy.checksum == quorum.header.checksum) return quorum;
|
|
270
270
|
} else {
|
|
271
|
-
quorums.array[quorums.count] = Quorum{ .
|
|
271
|
+
quorums.array[quorums.count] = Quorum{ .header = copy };
|
|
272
272
|
quorums.count += 1;
|
|
273
273
|
|
|
274
274
|
return &quorums.array[quorums.count - 1];
|
|
@@ -280,26 +280,26 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
fn sort_priority_descending(_: void, a: Quorum, b: Quorum) bool {
|
|
283
|
-
assert(a.
|
|
283
|
+
assert(a.header.checksum != b.header.checksum);
|
|
284
284
|
|
|
285
285
|
if (a.valid and !b.valid) return true;
|
|
286
286
|
if (b.valid and !a.valid) return false;
|
|
287
287
|
|
|
288
|
-
if (a.
|
|
289
|
-
if (b.
|
|
288
|
+
if (a.header.sequence > b.header.sequence) return true;
|
|
289
|
+
if (b.header.sequence > a.header.sequence) return false;
|
|
290
290
|
|
|
291
291
|
if (a.copies.count() > b.copies.count()) return true;
|
|
292
292
|
if (b.copies.count() > a.copies.count()) return false;
|
|
293
293
|
|
|
294
294
|
// The sort order must be stable and deterministic:
|
|
295
|
-
return a.
|
|
295
|
+
return a.header.checksum > b.header.checksum;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/// Repair a quorum's copies in the safest known order.
|
|
299
299
|
/// Repair is complete when every copy is on-disk (not necessarily in its home slot).
|
|
300
300
|
///
|
|
301
|
-
/// We must be careful when repairing superblock
|
|
302
|
-
/// an additional fault occurs. We primarily guard against torn
|
|
301
|
+
/// We must be careful when repairing superblock headers to avoid endangering our quorum if
|
|
302
|
+
/// an additional fault occurs. We primarily guard against torn header writes — preventing a
|
|
303
303
|
/// misdirected write from derailing repair is far more expensive and complex — but they are
|
|
304
304
|
/// likewise far less likely to occur.
|
|
305
305
|
///
|
|
@@ -342,9 +342,9 @@ pub fn QuorumsType(comptime options: Options) type {
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
// In descending order, our priorities for repair are:
|
|
345
|
-
// 1. The slot holds no
|
|
346
|
-
// 2. The slot holds no
|
|
347
|
-
// 3. The slot holds a misdirected
|
|
345
|
+
// 1. The slot holds no header, and the copy was not found anywhere.
|
|
346
|
+
// 2. The slot holds no header, but its copy was found elsewhere.
|
|
347
|
+
// 3. The slot holds a misdirected header, but that copy is in another slot as well.
|
|
348
348
|
var a: ?u8 = null;
|
|
349
349
|
var b: ?u8 = null;
|
|
350
350
|
var c: ?u8 = null;
|
|
@@ -388,7 +388,7 @@ test "Quorum.repairs" {
|
|
|
388
388
|
defer std.testing.log_level = level;
|
|
389
389
|
|
|
390
390
|
try fuzz.fuzz_quorum_repairs(prng.random(), .{ .superblock_copies = 4 });
|
|
391
|
-
// TODO: Enable these once
|
|
391
|
+
// TODO: Enable these once SuperBlockHeader is generic over its Constants.
|
|
392
392
|
// try fuzz.fuzz_quorum_repairs(prng.random(), .{ .superblock_copies = 6 });
|
|
393
393
|
// try fuzz.fuzz_quorum_repairs(prng.random(), .{ .superblock_copies = 8 });
|
|
394
394
|
}
|