tigerbeetle-node 0.11.7 → 0.11.9
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 +29 -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 +29 -16
- package/src/tigerbeetle/src/constants.zig +30 -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 +85 -103
- 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 +12 -8
- 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/tree.zig +31 -5
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +86 -114
- 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 +360 -214
- 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 +537 -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 +754 -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 +16 -9
- package/src/tigerbeetle/src/vsr/clock.zig +93 -53
- package/src/tigerbeetle/src/vsr/journal.zig +29 -14
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +2 -2
- package/src/tigerbeetle/src/vsr/replica.zig +1383 -774
- package/src/tigerbeetle/src/vsr/replica_format.zig +2 -2
- package/src/tigerbeetle/src/vsr/superblock.zig +59 -43
- 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 +15 -7
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +38 -19
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +1 -1
- package/src/tigerbeetle/src/vsr.zig +6 -4
- 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
|
@@ -8,17 +8,21 @@ const constants = @import("constants.zig");
|
|
|
8
8
|
const vsr = @import("vsr.zig");
|
|
9
9
|
const Header = vsr.Header;
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
11
|
+
const state_machine = @import("vsr_simulator_options").state_machine;
|
|
12
|
+
const StateMachineType = switch (state_machine) {
|
|
13
|
+
.accounting => @import("state_machine.zig").StateMachineType,
|
|
14
|
+
.testing => @import("testing/state_machine.zig").StateMachineType,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const Client = @import("testing/cluster.zig").Client;
|
|
18
|
+
const Cluster = @import("testing/cluster.zig").ClusterType(StateMachineType);
|
|
19
|
+
const Replica = @import("testing/cluster.zig").Replica;
|
|
20
|
+
const StateMachine = Cluster.StateMachine;
|
|
21
|
+
const Failure = @import("testing/cluster.zig").Failure;
|
|
22
|
+
const PartitionMode = @import("testing/packet_simulator.zig").PartitionMode;
|
|
23
|
+
const ReplySequence = @import("testing/reply_sequence.zig").ReplySequence;
|
|
24
|
+
const IdPermutation = @import("testing/id.zig").IdPermutation;
|
|
25
|
+
const Message = @import("message_pool.zig").MessagePool.Message;
|
|
22
26
|
|
|
23
27
|
/// The `log` namespace in this root file is required to implement our custom `log` function.
|
|
24
28
|
const output = std.log.scoped(.state_checker);
|
|
@@ -36,10 +40,6 @@ pub const log_level: std.log.Level = if (log_state_transitions_only) .info else
|
|
|
36
40
|
|
|
37
41
|
const cluster_id = 0;
|
|
38
42
|
|
|
39
|
-
var cluster: *Cluster = undefined;
|
|
40
|
-
var state_checker: *StateChecker = undefined;
|
|
41
|
-
var storage_checker: *StorageChecker = undefined;
|
|
42
|
-
|
|
43
43
|
pub fn main() !void {
|
|
44
44
|
// This must be initialized at runtime as stderr is not comptime known on e.g. Windows.
|
|
45
45
|
log_buffer.unbuffered_writer = std.io.getStdErr().writer();
|
|
@@ -79,51 +79,35 @@ pub fn main() !void {
|
|
|
79
79
|
|
|
80
80
|
const replica_count = 1 + random.uintLessThan(u8, constants.replicas_max);
|
|
81
81
|
const client_count = 1 + random.uintLessThan(u8, constants.clients_max);
|
|
82
|
-
const node_count = replica_count + client_count;
|
|
83
|
-
|
|
84
|
-
const ticks_max = 50_000_000;
|
|
85
|
-
const request_probability = 1 + random.uintLessThan(u8, 99);
|
|
86
|
-
const idle_on_probability = random.uintLessThan(u8, 20);
|
|
87
|
-
const idle_off_probability = 10 + random.uintLessThan(u8, 10);
|
|
88
|
-
|
|
89
|
-
// The maximum number of transitions from calling `client.request()`, not including
|
|
90
|
-
// `register` messages.
|
|
91
|
-
const requests_committed_max: usize = constants.journal_slot_count * 3;
|
|
92
82
|
|
|
93
|
-
const cluster_options
|
|
94
|
-
.
|
|
83
|
+
const cluster_options = Cluster.Options{
|
|
84
|
+
.cluster_id = cluster_id,
|
|
95
85
|
.replica_count = replica_count,
|
|
96
86
|
.client_count = client_count,
|
|
97
87
|
.storage_size_limit = vsr.sector_floor(
|
|
98
88
|
constants.storage_size_max - random.uintLessThan(u64, constants.storage_size_max / 10),
|
|
99
89
|
),
|
|
100
90
|
.seed = random.int(u64),
|
|
101
|
-
.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.partition_mode = random_partition_mode(random),
|
|
120
|
-
.partition_probability = random.uintLessThan(u8, 3),
|
|
121
|
-
.unpartition_probability = 1 + random.uintLessThan(u8, 10),
|
|
122
|
-
.partition_stability = 100 + random.uintLessThan(u32, 100),
|
|
123
|
-
.unpartition_stability = random.uintLessThan(u32, 20),
|
|
124
|
-
},
|
|
91
|
+
.network = .{
|
|
92
|
+
.replica_count = replica_count,
|
|
93
|
+
.client_count = client_count,
|
|
94
|
+
|
|
95
|
+
.seed = random.int(u64),
|
|
96
|
+
.one_way_delay_mean = 3 + random.uintLessThan(u16, 10),
|
|
97
|
+
.one_way_delay_min = random.uintLessThan(u16, 3),
|
|
98
|
+
.packet_loss_probability = random.uintLessThan(u8, 30),
|
|
99
|
+
.path_maximum_capacity = 2 + random.uintLessThan(u8, 19),
|
|
100
|
+
.path_clog_duration_mean = random.uintLessThan(u16, 500),
|
|
101
|
+
.path_clog_probability = random.uintLessThan(u8, 2),
|
|
102
|
+
.packet_replay_probability = random.uintLessThan(u8, 50),
|
|
103
|
+
|
|
104
|
+
.partition_mode = random_partition_mode(random),
|
|
105
|
+
.partition_probability = random.uintLessThan(u8, 3),
|
|
106
|
+
.unpartition_probability = 1 + random.uintLessThan(u8, 10),
|
|
107
|
+
.partition_stability = 100 + random.uintLessThan(u32, 100),
|
|
108
|
+
.unpartition_stability = random.uintLessThan(u32, 20),
|
|
125
109
|
},
|
|
126
|
-
.
|
|
110
|
+
.storage = .{
|
|
127
111
|
.seed = random.int(u64),
|
|
128
112
|
.read_latency_min = random.uintLessThan(u16, 3),
|
|
129
113
|
.read_latency_mean = 3 + random.uintLessThan(u16, 10),
|
|
@@ -132,16 +116,13 @@ pub fn main() !void {
|
|
|
132
116
|
.read_fault_probability = random.uintLessThan(u8, 10),
|
|
133
117
|
.write_fault_probability = random.uintLessThan(u8, 10),
|
|
134
118
|
.crash_fault_probability = 80 + random.uintLessThan(u8, 21),
|
|
135
|
-
.faulty_superblock = true,
|
|
136
119
|
},
|
|
137
|
-
.
|
|
138
|
-
.
|
|
139
|
-
.
|
|
140
|
-
.
|
|
141
|
-
.restart_stability = random.uintLessThan(u32, 1_000),
|
|
120
|
+
.storage_fault_atlas = .{
|
|
121
|
+
.faulty_superblock = true,
|
|
122
|
+
.faulty_wal_headers = replica_count > 1,
|
|
123
|
+
.faulty_wal_prepares = replica_count > 1,
|
|
142
124
|
},
|
|
143
|
-
|
|
144
|
-
.state_machine_options = switch (constants.state_machine) {
|
|
125
|
+
.state_machine = switch (state_machine) {
|
|
145
126
|
.testing => .{},
|
|
146
127
|
.accounting => .{
|
|
147
128
|
.lsm_forest_node_count = 4096,
|
|
@@ -152,6 +133,26 @@ pub fn main() !void {
|
|
|
152
133
|
},
|
|
153
134
|
};
|
|
154
135
|
|
|
136
|
+
const workload_options = StateMachine.Workload.Options.generate(random, .{
|
|
137
|
+
.client_count = client_count,
|
|
138
|
+
// TODO(DJ) Once Workload no longer needs in_flight_max, make stalled_queue_capacity private.
|
|
139
|
+
// Also maybe make it dynamic (computed from the client_count instead of clients_max).
|
|
140
|
+
.in_flight_max = ReplySequence.stalled_queue_capacity,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const simulator_options = Simulator.Options{
|
|
144
|
+
.cluster = cluster_options,
|
|
145
|
+
.workload = workload_options,
|
|
146
|
+
.replica_crash_probability = 0.000001,
|
|
147
|
+
.replica_crash_stability = random.uintLessThan(u32, 1_000),
|
|
148
|
+
.replica_restart_probability = 0.0001,
|
|
149
|
+
.replica_restart_stability = random.uintLessThan(u32, 1_000),
|
|
150
|
+
.requests_max = constants.journal_slot_count * 3,
|
|
151
|
+
.request_probability = 1 + random.uintLessThan(u8, 99),
|
|
152
|
+
.request_idle_on_probability = random.uintLessThan(u8, 20),
|
|
153
|
+
.request_idle_off_probability = 10 + random.uintLessThan(u8, 10),
|
|
154
|
+
};
|
|
155
|
+
|
|
155
156
|
output.info(
|
|
156
157
|
\\
|
|
157
158
|
\\ SEED={}
|
|
@@ -185,98 +186,278 @@ pub fn main() !void {
|
|
|
185
186
|
\\ restart_stability={} ticks
|
|
186
187
|
, .{
|
|
187
188
|
seed,
|
|
188
|
-
replica_count,
|
|
189
|
-
client_count,
|
|
190
|
-
request_probability,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
cluster_options.
|
|
194
|
-
cluster_options.
|
|
195
|
-
cluster_options.
|
|
196
|
-
cluster_options.
|
|
197
|
-
cluster_options.
|
|
198
|
-
cluster_options.
|
|
199
|
-
cluster_options.
|
|
200
|
-
cluster_options.
|
|
201
|
-
cluster_options.
|
|
202
|
-
cluster_options.
|
|
203
|
-
cluster_options.
|
|
204
|
-
cluster_options.
|
|
205
|
-
cluster_options.
|
|
206
|
-
cluster_options.
|
|
207
|
-
cluster_options.
|
|
208
|
-
cluster_options.
|
|
209
|
-
cluster_options.
|
|
210
|
-
cluster_options.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
189
|
+
cluster_options.replica_count,
|
|
190
|
+
cluster_options.client_count,
|
|
191
|
+
simulator_options.request_probability,
|
|
192
|
+
simulator_options.request_idle_on_probability,
|
|
193
|
+
simulator_options.request_idle_off_probability,
|
|
194
|
+
cluster_options.network.one_way_delay_mean,
|
|
195
|
+
cluster_options.network.one_way_delay_min,
|
|
196
|
+
cluster_options.network.packet_loss_probability,
|
|
197
|
+
cluster_options.network.path_maximum_capacity,
|
|
198
|
+
cluster_options.network.path_clog_duration_mean,
|
|
199
|
+
cluster_options.network.path_clog_probability,
|
|
200
|
+
cluster_options.network.packet_replay_probability,
|
|
201
|
+
cluster_options.network.partition_mode,
|
|
202
|
+
cluster_options.network.partition_probability,
|
|
203
|
+
cluster_options.network.unpartition_probability,
|
|
204
|
+
cluster_options.network.partition_stability,
|
|
205
|
+
cluster_options.network.unpartition_stability,
|
|
206
|
+
cluster_options.storage.read_latency_min,
|
|
207
|
+
cluster_options.storage.read_latency_mean,
|
|
208
|
+
cluster_options.storage.write_latency_min,
|
|
209
|
+
cluster_options.storage.write_latency_mean,
|
|
210
|
+
cluster_options.storage.read_fault_probability,
|
|
211
|
+
cluster_options.storage.write_fault_probability,
|
|
212
|
+
simulator_options.replica_crash_probability * 100,
|
|
213
|
+
simulator_options.replica_crash_stability,
|
|
214
|
+
simulator_options.replica_restart_probability * 100,
|
|
215
|
+
simulator_options.replica_restart_stability,
|
|
215
216
|
});
|
|
216
217
|
|
|
217
|
-
|
|
218
|
-
defer
|
|
218
|
+
var simulator = try Simulator.init(allocator, random, simulator_options);
|
|
219
|
+
defer simulator.deinit(allocator);
|
|
219
220
|
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
const ticks_max = 50_000_000;
|
|
222
|
+
var tick: u64 = 0;
|
|
223
|
+
while (tick < ticks_max) : (tick += 1) {
|
|
224
|
+
simulator.tick();
|
|
225
|
+
if (simulator.done()) break;
|
|
226
|
+
} else {
|
|
227
|
+
output.err("you can reproduce this failure with seed={}", .{seed});
|
|
228
|
+
fatal(.liveness, "unable to complete requests_committed_max before ticks_max", .{});
|
|
229
|
+
}
|
|
230
|
+
assert(simulator.done());
|
|
224
231
|
|
|
225
|
-
|
|
226
|
-
|
|
232
|
+
output.info("\n PASSED ({} ticks)", .{tick});
|
|
233
|
+
}
|
|
227
234
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
.
|
|
231
|
-
.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
pub const Simulator = struct {
|
|
236
|
+
pub const Options = struct {
|
|
237
|
+
cluster: Cluster.Options,
|
|
238
|
+
workload: StateMachine.Workload.Options,
|
|
239
|
+
|
|
240
|
+
/// Probability per tick that a crash will occur.
|
|
241
|
+
replica_crash_probability: f64,
|
|
242
|
+
/// Minimum duration of a crash.
|
|
243
|
+
replica_crash_stability: u32,
|
|
244
|
+
/// Probability per tick that a crashed replica will recovery.
|
|
245
|
+
replica_restart_probability: f64,
|
|
246
|
+
/// Minimum time a replica is up until it is crashed again.
|
|
247
|
+
replica_restart_stability: u32,
|
|
248
|
+
|
|
249
|
+
/// The total number of requests to send. Does not count `register` messages.
|
|
250
|
+
requests_max: usize,
|
|
251
|
+
request_probability: u8, // percent
|
|
252
|
+
request_idle_on_probability: u8, // percent
|
|
253
|
+
request_idle_off_probability: u8, // percent
|
|
254
|
+
};
|
|
239
255
|
|
|
240
|
-
|
|
241
|
-
|
|
256
|
+
random: std.rand.Random,
|
|
257
|
+
options: Options,
|
|
258
|
+
cluster: *Cluster,
|
|
259
|
+
workload: StateMachine.Workload,
|
|
260
|
+
|
|
261
|
+
/// Protect a replica from fast successive crash/restarts.
|
|
262
|
+
replica_stability: []usize,
|
|
263
|
+
reply_sequence: ReplySequence,
|
|
264
|
+
|
|
265
|
+
/// Total number of requests sent, including those that have not been delivered.
|
|
266
|
+
/// Does not include `register` messages.
|
|
267
|
+
requests_sent: usize = 0,
|
|
268
|
+
requests_idle: bool = false,
|
|
269
|
+
|
|
270
|
+
pub fn init(allocator: std.mem.Allocator, random: std.rand.Random, options: Options) !Simulator {
|
|
271
|
+
assert(options.replica_crash_probability < 1.0);
|
|
272
|
+
assert(options.replica_crash_probability >= 0.0);
|
|
273
|
+
assert(options.replica_restart_probability < 1.0);
|
|
274
|
+
assert(options.replica_restart_probability >= 0.0);
|
|
275
|
+
assert(options.requests_max > 0);
|
|
276
|
+
assert(options.request_probability > 0);
|
|
277
|
+
assert(options.request_probability <= 100);
|
|
278
|
+
assert(options.request_idle_on_probability <= 100);
|
|
279
|
+
assert(options.request_idle_off_probability > 0);
|
|
280
|
+
assert(options.request_idle_off_probability <= 100);
|
|
281
|
+
|
|
282
|
+
var cluster = try Cluster.init(allocator, on_cluster_reply, options.cluster);
|
|
283
|
+
errdefer cluster.deinit();
|
|
284
|
+
|
|
285
|
+
var workload = try StateMachine.Workload.init(allocator, random, options.workload);
|
|
286
|
+
errdefer workload.deinit(allocator);
|
|
287
|
+
|
|
288
|
+
var replica_stability = try allocator.alloc(usize, options.cluster.replica_count);
|
|
289
|
+
errdefer allocator.free(replica_stability);
|
|
290
|
+
std.mem.set(usize, replica_stability, 0);
|
|
291
|
+
|
|
292
|
+
var reply_sequence = try ReplySequence.init(allocator);
|
|
293
|
+
errdefer reply_sequence.deinit(allocator);
|
|
294
|
+
|
|
295
|
+
return Simulator{
|
|
296
|
+
.random = random,
|
|
297
|
+
.options = options,
|
|
298
|
+
.cluster = cluster,
|
|
299
|
+
.workload = workload,
|
|
300
|
+
.replica_stability = replica_stability,
|
|
301
|
+
.reply_sequence = reply_sequence,
|
|
302
|
+
};
|
|
242
303
|
}
|
|
243
304
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
305
|
+
pub fn deinit(simulator: *Simulator, allocator: std.mem.Allocator) void {
|
|
306
|
+
allocator.free(simulator.replica_stability);
|
|
307
|
+
simulator.reply_sequence.deinit(allocator);
|
|
308
|
+
simulator.workload.deinit(allocator);
|
|
309
|
+
simulator.cluster.deinit();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
pub fn done(simulator: *Simulator) bool {
|
|
313
|
+
assert(simulator.requests_sent <= simulator.options.requests_max);
|
|
314
|
+
|
|
315
|
+
if (!simulator.cluster.state_checker.convergence()) return false;
|
|
316
|
+
if (!simulator.reply_sequence.empty()) return false;
|
|
317
|
+
if (simulator.requests_sent < simulator.options.requests_max) return false;
|
|
318
|
+
|
|
319
|
+
for (simulator.cluster.replica_health) |health| {
|
|
320
|
+
if (health == .down) return false;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
for (simulator.cluster.clients) |*client| {
|
|
324
|
+
if (client.request_queue.count > 0) return false;
|
|
325
|
+
}
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
pub fn tick(simulator: *Simulator) void {
|
|
330
|
+
// TODO(Zig): Remove (see on_cluster_reply()).
|
|
331
|
+
simulator.cluster.context = simulator;
|
|
332
|
+
|
|
333
|
+
simulator.cluster.tick();
|
|
334
|
+
simulator.tick_requests();
|
|
335
|
+
simulator.tick_crash();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
fn on_cluster_reply(
|
|
339
|
+
cluster: *Cluster,
|
|
340
|
+
reply_client: usize,
|
|
341
|
+
request: *Message,
|
|
342
|
+
reply: *Message,
|
|
343
|
+
) void {
|
|
344
|
+
// TODO(Zig) Use @returnAddress to initialzie the cluster, then this can just use @fieldParentPtr().
|
|
345
|
+
const simulator = @ptrCast(*Simulator, @alignCast(@alignOf(Simulator), cluster.context.?));
|
|
346
|
+
simulator.reply_sequence.insert(reply_client, request, reply);
|
|
347
|
+
|
|
348
|
+
while (simulator.reply_sequence.peek()) |commit| {
|
|
349
|
+
defer simulator.reply_sequence.next();
|
|
350
|
+
|
|
351
|
+
const commit_client = simulator.cluster.clients[commit.client_index];
|
|
352
|
+
assert(commit.reply.references == 1);
|
|
353
|
+
assert(commit.reply.header.command == .reply);
|
|
354
|
+
assert(commit.reply.header.client == commit_client.id);
|
|
355
|
+
assert(commit.reply.header.request == commit.request.header.request);
|
|
356
|
+
assert(commit.reply.header.operation == commit.request.header.operation);
|
|
357
|
+
|
|
358
|
+
assert(commit.request.references == 1);
|
|
359
|
+
assert(commit.request.header.command == .request);
|
|
360
|
+
assert(commit.request.header.client == commit_client.id);
|
|
361
|
+
|
|
362
|
+
log_simulator.debug("consume_stalled_replies: op={} operation={} client={} request={}", .{
|
|
363
|
+
commit.reply.header.op,
|
|
364
|
+
commit.reply.header.operation,
|
|
365
|
+
commit.request.header.client,
|
|
366
|
+
commit.request.header.request,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (commit.request.header.operation != .register) {
|
|
370
|
+
simulator.workload.on_reply(
|
|
371
|
+
commit.client_index,
|
|
372
|
+
commit.reply.header.operation,
|
|
373
|
+
commit.reply.header.timestamp,
|
|
374
|
+
commit.request.body(),
|
|
375
|
+
commit.reply.body(),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// Maybe send a request from one of the cluster's clients.
|
|
382
|
+
fn tick_requests(simulator: *Simulator) void {
|
|
383
|
+
if (simulator.requests_idle) {
|
|
384
|
+
if (chance(simulator.random, simulator.options.request_idle_off_probability)) {
|
|
385
|
+
simulator.requests_idle = false;
|
|
386
|
+
}
|
|
267
387
|
} else {
|
|
268
|
-
|
|
388
|
+
if (chance(simulator.random, simulator.options.request_idle_on_probability)) {
|
|
389
|
+
simulator.requests_idle = true;
|
|
390
|
+
}
|
|
269
391
|
}
|
|
270
|
-
};
|
|
271
392
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
393
|
+
if (simulator.requests_idle) return;
|
|
394
|
+
if (simulator.requests_sent == simulator.options.requests_max) return;
|
|
395
|
+
if (!chance(simulator.random, simulator.options.request_probability)) return;
|
|
396
|
+
|
|
397
|
+
const client_index =
|
|
398
|
+
simulator.random.uintLessThan(usize, simulator.options.cluster.client_count);
|
|
399
|
+
var client = &simulator.cluster.clients[client_index];
|
|
400
|
+
|
|
401
|
+
// Make sure that there is capacity in the client's request queue so that we never trigger
|
|
402
|
+
// error.TooManyOutstandingRequests.
|
|
403
|
+
if (client.request_queue.count + 1 > constants.client_request_queue_max) return;
|
|
404
|
+
|
|
405
|
+
// Messages aren't added to the ReplySequence until a reply arrives.
|
|
406
|
+
// Before sending a new message, make sure there will definitely be room for it.
|
|
407
|
+
var reserved: usize = 0;
|
|
408
|
+
for (simulator.cluster.clients) |*c| {
|
|
409
|
+
// Count the number of clients that are still waiting for a `register` to complete,
|
|
410
|
+
// since they may start one at any time.
|
|
411
|
+
reserved += @boolToInt(c.session == 0);
|
|
412
|
+
// Count the number of requests queued.
|
|
413
|
+
reserved += c.request_queue.count;
|
|
414
|
+
}
|
|
415
|
+
// +1 for the potential request — is there room in the sequencer's queue?
|
|
416
|
+
if (reserved + 1 > simulator.reply_sequence.free()) return;
|
|
417
|
+
|
|
418
|
+
var request_message = client.get_message();
|
|
419
|
+
defer client.unref(request_message);
|
|
420
|
+
|
|
421
|
+
const request_metadata = simulator.workload.build_request(
|
|
422
|
+
client_index,
|
|
423
|
+
@alignCast(
|
|
424
|
+
@alignOf(vsr.Header),
|
|
425
|
+
request_message.buffer[@sizeOf(vsr.Header)..constants.message_size_max],
|
|
426
|
+
),
|
|
427
|
+
);
|
|
428
|
+
assert(request_metadata.size <= constants.message_size_max - @sizeOf(vsr.Header));
|
|
429
|
+
|
|
430
|
+
simulator.cluster.request(
|
|
431
|
+
client_index,
|
|
432
|
+
request_metadata.operation,
|
|
433
|
+
request_message,
|
|
434
|
+
request_metadata.size,
|
|
435
|
+
);
|
|
436
|
+
// Since we already checked the client's request queue for free space, `client.request()`
|
|
437
|
+
// should always queue the request.
|
|
438
|
+
assert(request_message == client.request_queue.tail_ptr().?.message);
|
|
439
|
+
assert(request_message.header.size == @sizeOf(vsr.Header) + request_metadata.size);
|
|
440
|
+
assert(request_message.header.operation.cast(StateMachine) == request_metadata.operation);
|
|
441
|
+
|
|
442
|
+
simulator.requests_sent += 1;
|
|
443
|
+
assert(simulator.requests_sent <= simulator.options.requests_max);
|
|
444
|
+
}
|
|
277
445
|
|
|
278
|
-
|
|
279
|
-
|
|
446
|
+
fn tick_crash(simulator: *Simulator) void {
|
|
447
|
+
// The maximum number of replicas that can crash, with the cluster still able to recover.
|
|
448
|
+
var crashes = blk: {
|
|
449
|
+
// The minimum number of healthy replicas required for a crashed replica to be able to
|
|
450
|
+
// recover. A cluster of 1 can crash safely (as long as there is no disk corruption)
|
|
451
|
+
// since it does not run the recovery protocol.
|
|
452
|
+
var replica_normal_min = if (simulator.options.cluster.replica_count == 1)
|
|
453
|
+
0
|
|
454
|
+
else
|
|
455
|
+
vsr.quorums(simulator.options.cluster.replica_count).view_change;
|
|
456
|
+
break :blk simulator.cluster.replica_normal_count() -| replica_normal_min;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
for (simulator.cluster.storages) |*storage, replica| {
|
|
460
|
+
if (simulator.cluster.replicas[replica].journal.status == .recovered) {
|
|
280
461
|
// TODO Remove this workaround when VSR recovery protocol is disabled.
|
|
281
462
|
// When only the minimum number of replicas are healthy (no more crashes allowed),
|
|
282
463
|
// disable storage faults on all healthy replicas.
|
|
@@ -286,7 +467,7 @@ pub fn main() !void {
|
|
|
286
467
|
// not have the prepare. The two healthy replicas can never complete a view change,
|
|
287
468
|
// because two replicas are not enough to nack, and the unhealthy replica cannot
|
|
288
469
|
// complete the VSR recovery protocol either.
|
|
289
|
-
if (cluster.
|
|
470
|
+
if (simulator.cluster.replica_health[replica] == .up and crashes == 0) {
|
|
290
471
|
if (storage.faulty) {
|
|
291
472
|
log_simulator.debug("{}: disable storage faults", .{replica});
|
|
292
473
|
storage.faulty = false;
|
|
@@ -302,73 +483,57 @@ pub fn main() !void {
|
|
|
302
483
|
}
|
|
303
484
|
}
|
|
304
485
|
|
|
305
|
-
for (cluster.replicas) |*replica
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
replica.tick();
|
|
310
|
-
cluster.storages[index].tick();
|
|
311
|
-
|
|
312
|
-
state_checker.check_state(replica.replica) catch |err| {
|
|
313
|
-
fatal(.correctness, "state checker error: {}", .{err});
|
|
314
|
-
};
|
|
486
|
+
for (simulator.cluster.replicas) |*replica| {
|
|
487
|
+
simulator.replica_stability[replica.replica] -|= 1;
|
|
488
|
+
const stability = simulator.replica_stability[replica.replica];
|
|
489
|
+
if (stability > 0) continue;
|
|
315
490
|
|
|
316
|
-
|
|
491
|
+
switch (simulator.cluster.replica_health[replica.replica]) {
|
|
492
|
+
.up => {
|
|
317
493
|
if (crashes == 0) continue;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
494
|
+
const replica_writes = simulator.cluster.storages[replica.replica].writes.count();
|
|
495
|
+
const crash_probability = simulator.options.replica_crash_probability *
|
|
496
|
+
@as(f64, if (replica_writes == 0) 1.0 else 10.0);
|
|
497
|
+
if (!chance_f64(simulator.random, crash_probability)) continue;
|
|
498
|
+
|
|
499
|
+
const replica_crashed = simulator.cluster.crash_replica(replica.replica) catch |err| {
|
|
500
|
+
log_simulator.err("{}: crash replica: unable to open after crash (err={})", .{
|
|
501
|
+
replica.replica,
|
|
502
|
+
err,
|
|
503
|
+
});
|
|
504
|
+
unreachable;
|
|
505
|
+
};
|
|
506
|
+
if (replica_crashed) {
|
|
507
|
+
log_simulator.debug("{}: crash replica", .{replica.replica});
|
|
508
|
+
crashes -= 1;
|
|
509
|
+
simulator.replica_stability[replica.replica] =
|
|
510
|
+
simulator.options.replica_crash_stability;
|
|
322
511
|
}
|
|
323
|
-
|
|
324
|
-
if (!try cluster.crash_replica(replica.replica)) continue;
|
|
325
|
-
log_simulator.debug("{}: crash replica", .{replica.replica});
|
|
326
|
-
crashes -= 1;
|
|
327
512
|
},
|
|
328
|
-
.down =>
|
|
329
|
-
ticks.* -|= 1;
|
|
330
|
-
// Keep ticking the time so that it won't have diverged too far to synchronize
|
|
331
|
-
// when the replica restarts.
|
|
332
|
-
replica.clock.time.tick();
|
|
513
|
+
.down => {
|
|
333
514
|
assert(replica.status == .recovering);
|
|
334
|
-
if (
|
|
335
|
-
cluster.
|
|
515
|
+
if (chance_f64(simulator.random, simulator.options.replica_restart_probability)) {
|
|
516
|
+
simulator.cluster.restart_replica(replica.replica);
|
|
336
517
|
log_simulator.debug("{}: restart replica", .{replica.replica});
|
|
518
|
+
simulator.replica_stability[replica.replica] =
|
|
519
|
+
simulator.options.replica_restart_stability;
|
|
337
520
|
}
|
|
338
521
|
},
|
|
339
522
|
}
|
|
340
523
|
}
|
|
341
|
-
|
|
342
|
-
cluster.network.packet_simulator.tick(cluster.health);
|
|
343
|
-
conductor.tick();
|
|
344
|
-
|
|
345
|
-
if (state_checker.convergence() and conductor.done() and
|
|
346
|
-
cluster.replica_up_count() == replica_count)
|
|
347
|
-
{
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
351
|
-
output.err("you can reproduce this failure with seed={}", .{seed});
|
|
352
|
-
fatal(.liveness, "unable to complete requests_committed_max before ticks_max", .{});
|
|
353
524
|
}
|
|
354
|
-
|
|
355
|
-
assert(state_checker.convergence());
|
|
356
|
-
assert(conductor.done());
|
|
357
|
-
|
|
358
|
-
output.info("\n PASSED ({} ticks)", .{tick});
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
pub const ExitCode = enum(u8) {
|
|
362
|
-
ok = 0,
|
|
363
|
-
crash = 127, // Any assertion crash will be given an exit code of 127 by default.
|
|
364
|
-
liveness = 128,
|
|
365
|
-
correctness = 129,
|
|
366
525
|
};
|
|
367
526
|
|
|
368
527
|
/// Print an error message and then exit with an exit code.
|
|
369
|
-
fn fatal(
|
|
528
|
+
fn fatal(failure: Failure, comptime fmt_string: []const u8, args: anytype) noreturn {
|
|
370
529
|
output.err(fmt_string, args);
|
|
371
|
-
std.os.exit(@enumToInt(
|
|
530
|
+
std.os.exit(@enumToInt(failure));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/// Returns true, `p` percent of the time, else false.
|
|
534
|
+
fn chance(random: std.rand.Random, p: u8) bool {
|
|
535
|
+
assert(p <= 100);
|
|
536
|
+
return random.uintLessThanBiased(u8, 100) < p;
|
|
372
537
|
}
|
|
373
538
|
|
|
374
539
|
/// Returns true, `p` percent of the time, else false.
|
|
@@ -383,29 +548,10 @@ fn args_next(args: *std.process.ArgIterator, allocator: std.mem.Allocator) ?[:0]
|
|
|
383
548
|
return err_or_bytes catch @panic("Unable to extract next value from args");
|
|
384
549
|
}
|
|
385
550
|
|
|
386
|
-
|
|
387
|
-
state_checker.check_state(replica.replica) catch |err| {
|
|
388
|
-
fatal(.correctness, "state checker error: {}", .{err});
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
fn on_replica_compact(replica: *const Replica) void {
|
|
393
|
-
storage_checker.replica_compact(replica) catch |err| {
|
|
394
|
-
fatal(.correctness, "storage checker error: {}", .{err});
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
fn on_replica_checkpoint(replica: *const Replica) void {
|
|
399
|
-
storage_checker.replica_checkpoint(replica) catch |err| {
|
|
400
|
-
fatal(.correctness, "storage checker error: {}", .{err});
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/// Returns a random partitioning mode, excluding .custom
|
|
551
|
+
/// Returns a random partitioning mode.
|
|
405
552
|
fn random_partition_mode(random: std.rand.Random) PartitionMode {
|
|
406
553
|
const typeInfo = @typeInfo(PartitionMode).Enum;
|
|
407
|
-
var enumAsInt = random.uintAtMost(typeInfo.tag_type, typeInfo.fields.len -
|
|
408
|
-
if (enumAsInt >= @enumToInt(PartitionMode.custom)) enumAsInt += 1;
|
|
554
|
+
var enumAsInt = random.uintAtMost(typeInfo.tag_type, typeInfo.fields.len - 1);
|
|
409
555
|
return @intToEnum(PartitionMode, enumAsInt);
|
|
410
556
|
}
|
|
411
557
|
|