tigerbeetle-node 0.11.12 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -196
- package/dist/bin/aarch64-linux-gnu/client.node +0 -0
- package/dist/bin/aarch64-linux-musl/client.node +0 -0
- package/dist/bin/aarch64-macos/client.node +0 -0
- package/dist/bin/x86_64-linux-gnu/client.node +0 -0
- package/dist/bin/x86_64-linux-musl/client.node +0 -0
- package/dist/bin/x86_64-macos/client.node +0 -0
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/package-lock.json +66 -0
- package/package.json +8 -17
- package/src/index.ts +56 -1
- package/src/node.zig +10 -9
- package/dist/.client.node.sha256 +0 -1
- package/scripts/build_lib.sh +0 -61
- package/scripts/download_node_headers.sh +0 -32
- package/src/tigerbeetle/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/src/benchmark.zig +0 -314
- package/src/tigerbeetle/src/config.zig +0 -234
- package/src/tigerbeetle/src/constants.zig +0 -436
- package/src/tigerbeetle/src/ewah.zig +0 -286
- package/src/tigerbeetle/src/ewah_benchmark.zig +0 -120
- package/src/tigerbeetle/src/ewah_fuzz.zig +0 -130
- package/src/tigerbeetle/src/fifo.zig +0 -120
- package/src/tigerbeetle/src/io/benchmark.zig +0 -213
- package/src/tigerbeetle/src/io/darwin.zig +0 -814
- package/src/tigerbeetle/src/io/linux.zig +0 -1062
- package/src/tigerbeetle/src/io/test.zig +0 -643
- package/src/tigerbeetle/src/io/windows.zig +0 -1183
- package/src/tigerbeetle/src/io.zig +0 -34
- package/src/tigerbeetle/src/iops.zig +0 -107
- package/src/tigerbeetle/src/lsm/README.md +0 -308
- package/src/tigerbeetle/src/lsm/binary_search.zig +0 -341
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +0 -125
- package/src/tigerbeetle/src/lsm/compaction.zig +0 -603
- package/src/tigerbeetle/src/lsm/composite_key.zig +0 -77
- package/src/tigerbeetle/src/lsm/direction.zig +0 -11
- package/src/tigerbeetle/src/lsm/eytzinger.zig +0 -587
- package/src/tigerbeetle/src/lsm/eytzinger_benchmark.zig +0 -330
- package/src/tigerbeetle/src/lsm/forest.zig +0 -204
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +0 -401
- package/src/tigerbeetle/src/lsm/grid.zig +0 -573
- package/src/tigerbeetle/src/lsm/groove.zig +0 -972
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +0 -474
- package/src/tigerbeetle/src/lsm/level_iterator.zig +0 -332
- package/src/tigerbeetle/src/lsm/manifest.zig +0 -617
- package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -877
- package/src/tigerbeetle/src/lsm/manifest_log.zig +0 -789
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +0 -691
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +0 -106
- package/src/tigerbeetle/src/lsm/node_pool.zig +0 -235
- package/src/tigerbeetle/src/lsm/posted_groove.zig +0 -378
- package/src/tigerbeetle/src/lsm/segmented_array.zig +0 -1328
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +0 -148
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +0 -9
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +0 -850
- package/src/tigerbeetle/src/lsm/table.zig +0 -1031
- package/src/tigerbeetle/src/lsm/table_immutable.zig +0 -203
- package/src/tigerbeetle/src/lsm/table_iterator.zig +0 -340
- package/src/tigerbeetle/src/lsm/table_mutable.zig +0 -220
- package/src/tigerbeetle/src/lsm/test.zig +0 -438
- package/src/tigerbeetle/src/lsm/tree.zig +0 -1193
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +0 -474
- package/src/tigerbeetle/src/message_bus.zig +0 -1012
- package/src/tigerbeetle/src/message_pool.zig +0 -156
- package/src/tigerbeetle/src/ring_buffer.zig +0 -399
- package/src/tigerbeetle/src/simulator.zig +0 -569
- package/src/tigerbeetle/src/state_machine/auditor.zig +0 -577
- package/src/tigerbeetle/src/state_machine/workload.zig +0 -883
- package/src/tigerbeetle/src/state_machine.zig +0 -1881
- package/src/tigerbeetle/src/static_allocator.zig +0 -65
- package/src/tigerbeetle/src/stdx.zig +0 -162
- package/src/tigerbeetle/src/storage.zig +0 -393
- package/src/tigerbeetle/src/testing/cluster/message_bus.zig +0 -82
- package/src/tigerbeetle/src/testing/cluster/network.zig +0 -237
- package/src/tigerbeetle/src/testing/cluster/state_checker.zig +0 -169
- package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +0 -202
- package/src/tigerbeetle/src/testing/cluster.zig +0 -443
- package/src/tigerbeetle/src/testing/fuzz.zig +0 -140
- package/src/tigerbeetle/src/testing/hash_log.zig +0 -66
- package/src/tigerbeetle/src/testing/id.zig +0 -99
- package/src/tigerbeetle/src/testing/packet_simulator.zig +0 -364
- package/src/tigerbeetle/src/testing/priority_queue.zig +0 -645
- package/src/tigerbeetle/src/testing/reply_sequence.zig +0 -139
- package/src/tigerbeetle/src/testing/state_machine.zig +0 -249
- package/src/tigerbeetle/src/testing/storage.zig +0 -757
- package/src/tigerbeetle/src/testing/table.zig +0 -247
- package/src/tigerbeetle/src/testing/time.zig +0 -84
- package/src/tigerbeetle/src/tigerbeetle.zig +0 -227
- package/src/tigerbeetle/src/time.zig +0 -112
- package/src/tigerbeetle/src/tracer.zig +0 -529
- package/src/tigerbeetle/src/unit_tests.zig +0 -42
- package/src/tigerbeetle/src/vopr.zig +0 -495
- package/src/tigerbeetle/src/vsr/README.md +0 -209
- package/src/tigerbeetle/src/vsr/client.zig +0 -544
- package/src/tigerbeetle/src/vsr/clock.zig +0 -853
- package/src/tigerbeetle/src/vsr/journal.zig +0 -2413
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +0 -111
- package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
- package/src/tigerbeetle/src/vsr/replica.zig +0 -6381
- package/src/tigerbeetle/src/vsr/replica_format.zig +0 -219
- package/src/tigerbeetle/src/vsr/superblock.zig +0 -1631
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +0 -256
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +0 -929
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +0 -334
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +0 -390
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +0 -615
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +0 -394
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +0 -314
- package/src/tigerbeetle/src/vsr.zig +0 -1352
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
const mem = std.mem;
|
|
4
|
-
|
|
5
|
-
const constants = @import("../constants.zig");
|
|
6
|
-
const message_pool = @import("../message_pool.zig");
|
|
7
|
-
const MessagePool = message_pool.MessagePool;
|
|
8
|
-
const Message = MessagePool.Message;
|
|
9
|
-
|
|
10
|
-
const Storage = @import("storage.zig").Storage;
|
|
11
|
-
const StorageFaultAtlas = @import("storage.zig").ClusterFaultAtlas;
|
|
12
|
-
const Time = @import("time.zig").Time;
|
|
13
|
-
const IdPermutation = @import("id.zig").IdPermutation;
|
|
14
|
-
|
|
15
|
-
const MessageBus = @import("cluster/message_bus.zig").MessageBus;
|
|
16
|
-
const Network = @import("cluster/network.zig").Network;
|
|
17
|
-
const NetworkOptions = @import("cluster/network.zig").NetworkOptions;
|
|
18
|
-
const StateCheckerType = @import("cluster/state_checker.zig").StateCheckerType;
|
|
19
|
-
const StorageCheckerType = @import("cluster/storage_checker.zig").StorageCheckerType;
|
|
20
|
-
|
|
21
|
-
const vsr = @import("../vsr.zig");
|
|
22
|
-
pub const ReplicaFormat = vsr.ReplicaFormatType(Storage);
|
|
23
|
-
const SuperBlock = vsr.SuperBlockType(Storage);
|
|
24
|
-
const superblock_zone_size = @import("../vsr/superblock.zig").superblock_zone_size;
|
|
25
|
-
|
|
26
|
-
pub const ReplicaHealth = enum { up, down };
|
|
27
|
-
|
|
28
|
-
/// Integer values represent exit codes.
|
|
29
|
-
// TODO This doesn't really belong in Cluster, but it is needed here so that StateChecker failures
|
|
30
|
-
// use the particular exit code.
|
|
31
|
-
pub const Failure = enum(u8) {
|
|
32
|
-
/// Any assertion crash will be given an exit code of 127 by default.
|
|
33
|
-
crash = 127,
|
|
34
|
-
liveness = 128,
|
|
35
|
-
correctness = 129,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/// Shift the id-generating index because the simulator network expects client ids to never collide
|
|
39
|
-
/// with a replica index.
|
|
40
|
-
const client_id_permutation_shift = constants.replicas_max;
|
|
41
|
-
|
|
42
|
-
pub fn ClusterType(comptime StateMachineType: fn (comptime Storage: type, comptime constants: anytype) type) type {
|
|
43
|
-
return struct {
|
|
44
|
-
const Self = @This();
|
|
45
|
-
|
|
46
|
-
pub const StateMachine = StateMachineType(Storage, .{
|
|
47
|
-
.message_body_size_max = constants.message_body_size_max,
|
|
48
|
-
});
|
|
49
|
-
pub const Replica = vsr.ReplicaType(StateMachine, MessageBus, Storage, Time);
|
|
50
|
-
pub const Client = vsr.Client(StateMachine, MessageBus);
|
|
51
|
-
pub const StateChecker = StateCheckerType(Client, Replica);
|
|
52
|
-
pub const StorageChecker = StorageCheckerType(Replica);
|
|
53
|
-
|
|
54
|
-
pub const Options = struct {
|
|
55
|
-
cluster_id: u32,
|
|
56
|
-
replica_count: u8,
|
|
57
|
-
client_count: u8,
|
|
58
|
-
storage_size_limit: u64,
|
|
59
|
-
storage_fault_atlas: StorageFaultAtlas.Options,
|
|
60
|
-
seed: u64,
|
|
61
|
-
|
|
62
|
-
network: NetworkOptions,
|
|
63
|
-
storage: Storage.Options,
|
|
64
|
-
state_machine: StateMachine.Options,
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
allocator: mem.Allocator,
|
|
68
|
-
options: Options,
|
|
69
|
-
on_client_reply: fn (
|
|
70
|
-
cluster: *Self,
|
|
71
|
-
client: usize,
|
|
72
|
-
request: *Message,
|
|
73
|
-
reply: *Message,
|
|
74
|
-
) void,
|
|
75
|
-
|
|
76
|
-
network: *Network,
|
|
77
|
-
storages: []Storage,
|
|
78
|
-
storage_fault_atlas: *StorageFaultAtlas,
|
|
79
|
-
replicas: []Replica,
|
|
80
|
-
replica_pools: []MessagePool,
|
|
81
|
-
replica_health: []ReplicaHealth,
|
|
82
|
-
|
|
83
|
-
clients: []Client,
|
|
84
|
-
client_pools: []MessagePool,
|
|
85
|
-
client_id_permutation: IdPermutation,
|
|
86
|
-
|
|
87
|
-
state_checker: StateChecker,
|
|
88
|
-
storage_checker: StorageChecker,
|
|
89
|
-
|
|
90
|
-
context: ?*anyopaque = null,
|
|
91
|
-
|
|
92
|
-
pub fn init(
|
|
93
|
-
allocator: mem.Allocator,
|
|
94
|
-
/// Includes command=register messages.
|
|
95
|
-
on_client_reply: fn (
|
|
96
|
-
cluster: *Self,
|
|
97
|
-
client: usize,
|
|
98
|
-
request: *Message,
|
|
99
|
-
reply: *Message,
|
|
100
|
-
) void,
|
|
101
|
-
options: Options,
|
|
102
|
-
) !*Self {
|
|
103
|
-
assert(options.replica_count >= 1);
|
|
104
|
-
assert(options.replica_count <= 6);
|
|
105
|
-
assert(options.client_count > 0);
|
|
106
|
-
assert(options.storage_size_limit % constants.sector_size == 0);
|
|
107
|
-
assert(options.storage_size_limit <= constants.storage_size_max);
|
|
108
|
-
assert(options.storage.replica_index == null);
|
|
109
|
-
assert(options.storage.fault_atlas == null);
|
|
110
|
-
|
|
111
|
-
var prng = std.rand.DefaultPrng.init(options.seed);
|
|
112
|
-
const random = prng.random();
|
|
113
|
-
|
|
114
|
-
// TODO(Zig) Client.init()'s MessagePool.Options require a reference to the network — use
|
|
115
|
-
// @returnAddress() instead.
|
|
116
|
-
var network = try allocator.create(Network);
|
|
117
|
-
errdefer allocator.destroy(network);
|
|
118
|
-
|
|
119
|
-
network.* = try Network.init(
|
|
120
|
-
allocator,
|
|
121
|
-
options.replica_count,
|
|
122
|
-
options.client_count,
|
|
123
|
-
options.network,
|
|
124
|
-
);
|
|
125
|
-
errdefer network.deinit();
|
|
126
|
-
|
|
127
|
-
// TODO(Zig) @returnAddress()
|
|
128
|
-
var storage_fault_atlas = try allocator.create(StorageFaultAtlas);
|
|
129
|
-
errdefer allocator.destroy(storage_fault_atlas);
|
|
130
|
-
|
|
131
|
-
storage_fault_atlas.* = StorageFaultAtlas.init(
|
|
132
|
-
options.replica_count,
|
|
133
|
-
random,
|
|
134
|
-
options.storage_fault_atlas,
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const storages = try allocator.alloc(Storage, options.replica_count);
|
|
138
|
-
errdefer allocator.free(storages);
|
|
139
|
-
|
|
140
|
-
for (storages) |*storage, replica_index| {
|
|
141
|
-
var storage_options = options.storage;
|
|
142
|
-
storage_options.replica_index = @intCast(u8, replica_index);
|
|
143
|
-
storage_options.fault_atlas = storage_fault_atlas;
|
|
144
|
-
storage.* = try Storage.init(allocator, options.storage_size_limit, storage_options);
|
|
145
|
-
// Disable most faults at startup, so that the replicas don't get stuck recovering_head.
|
|
146
|
-
storage.faulty = replica_index >= vsr.quorums(options.replica_count).view_change;
|
|
147
|
-
}
|
|
148
|
-
errdefer for (storages) |*storage| storage.deinit(allocator);
|
|
149
|
-
|
|
150
|
-
var replica_pools = try allocator.alloc(MessagePool, options.replica_count);
|
|
151
|
-
errdefer allocator.free(replica_pools);
|
|
152
|
-
|
|
153
|
-
for (replica_pools) |*pool, i| {
|
|
154
|
-
errdefer for (replica_pools[0..i]) |*p| p.deinit(allocator);
|
|
155
|
-
pool.* = try MessagePool.init(allocator, .replica);
|
|
156
|
-
}
|
|
157
|
-
errdefer for (replica_pools) |*pool| pool.deinit(allocator);
|
|
158
|
-
|
|
159
|
-
const replicas = try allocator.alloc(Replica, options.replica_count);
|
|
160
|
-
errdefer allocator.free(replicas);
|
|
161
|
-
|
|
162
|
-
const replica_health = try allocator.alloc(ReplicaHealth, options.replica_count);
|
|
163
|
-
errdefer allocator.free(replica_health);
|
|
164
|
-
mem.set(ReplicaHealth, replica_health, .up);
|
|
165
|
-
|
|
166
|
-
var client_pools = try allocator.alloc(MessagePool, options.client_count);
|
|
167
|
-
errdefer allocator.free(client_pools);
|
|
168
|
-
|
|
169
|
-
for (client_pools) |*pool, i| {
|
|
170
|
-
errdefer for (client_pools[0..i]) |*p| p.deinit(allocator);
|
|
171
|
-
pool.* = try MessagePool.init(allocator, .client);
|
|
172
|
-
}
|
|
173
|
-
errdefer for (replica_pools) |*pool| pool.deinit(allocator);
|
|
174
|
-
|
|
175
|
-
const client_id_permutation = IdPermutation.generate(random);
|
|
176
|
-
var clients = try allocator.alloc(Client, options.client_count);
|
|
177
|
-
errdefer allocator.free(clients);
|
|
178
|
-
|
|
179
|
-
for (clients) |*client, i| {
|
|
180
|
-
errdefer for (clients[0..i]) |*c| c.deinit(allocator);
|
|
181
|
-
client.* = try Client.init(
|
|
182
|
-
allocator,
|
|
183
|
-
client_id_permutation.encode(i + client_id_permutation_shift),
|
|
184
|
-
options.cluster_id,
|
|
185
|
-
options.replica_count,
|
|
186
|
-
&client_pools[i],
|
|
187
|
-
.{ .network = network },
|
|
188
|
-
);
|
|
189
|
-
network.link(client.message_bus.process, &client.message_bus);
|
|
190
|
-
}
|
|
191
|
-
errdefer for (clients) |*c| c.deinit(allocator);
|
|
192
|
-
|
|
193
|
-
var state_checker =
|
|
194
|
-
try StateChecker.init(allocator, options.cluster_id, replicas, clients);
|
|
195
|
-
errdefer state_checker.deinit();
|
|
196
|
-
|
|
197
|
-
var storage_checker = StorageChecker.init(allocator);
|
|
198
|
-
errdefer storage_checker.deinit();
|
|
199
|
-
|
|
200
|
-
// Format each replica's storage (equivalent to "tigerbeetle format ...").
|
|
201
|
-
for (storages) |*storage, replica_index| {
|
|
202
|
-
var superblock = try SuperBlock.init(allocator, .{
|
|
203
|
-
.storage = storage,
|
|
204
|
-
.message_pool = &replica_pools[replica_index],
|
|
205
|
-
.storage_size_limit = options.storage_size_limit,
|
|
206
|
-
});
|
|
207
|
-
defer superblock.deinit(allocator);
|
|
208
|
-
|
|
209
|
-
try vsr.format(
|
|
210
|
-
Storage,
|
|
211
|
-
allocator,
|
|
212
|
-
options.cluster_id,
|
|
213
|
-
@intCast(u8, replica_index),
|
|
214
|
-
storage,
|
|
215
|
-
&superblock,
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// We must heap-allocate the cluster since its pointer will be attached to the replica.
|
|
220
|
-
// TODO(Zig) @returnAddress().
|
|
221
|
-
var cluster = try allocator.create(Self);
|
|
222
|
-
errdefer allocator.destroy(cluster);
|
|
223
|
-
|
|
224
|
-
cluster.* = Self{
|
|
225
|
-
.allocator = allocator,
|
|
226
|
-
.options = options,
|
|
227
|
-
.on_client_reply = on_client_reply,
|
|
228
|
-
.network = network,
|
|
229
|
-
.storages = storages,
|
|
230
|
-
.storage_fault_atlas = storage_fault_atlas,
|
|
231
|
-
.replicas = replicas,
|
|
232
|
-
.replica_pools = replica_pools,
|
|
233
|
-
.replica_health = replica_health,
|
|
234
|
-
.clients = clients,
|
|
235
|
-
.client_pools = client_pools,
|
|
236
|
-
.client_id_permutation = client_id_permutation,
|
|
237
|
-
.state_checker = state_checker,
|
|
238
|
-
.storage_checker = storage_checker,
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
for (cluster.replicas) |_, replica_index| {
|
|
242
|
-
try cluster.open_replica(@intCast(u8, replica_index), .{
|
|
243
|
-
.resolution = constants.tick_ms * std.time.ns_per_ms,
|
|
244
|
-
.offset_type = .linear,
|
|
245
|
-
.offset_coefficient_A = 0,
|
|
246
|
-
.offset_coefficient_B = 0,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
errdefer for (cluster.replicas) |*replica| replica.deinit(allocator);
|
|
250
|
-
|
|
251
|
-
for (clients) |*client| {
|
|
252
|
-
client.on_reply_context = cluster;
|
|
253
|
-
client.on_reply_callback = client_on_reply;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return cluster;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
pub fn deinit(cluster: *Self) void {
|
|
260
|
-
cluster.storage_checker.deinit();
|
|
261
|
-
cluster.state_checker.deinit();
|
|
262
|
-
cluster.network.deinit();
|
|
263
|
-
for (cluster.clients) |*client| client.deinit(cluster.allocator);
|
|
264
|
-
for (cluster.client_pools) |*pool| pool.deinit(cluster.allocator);
|
|
265
|
-
for (cluster.replicas) |*replica| replica.deinit(cluster.allocator);
|
|
266
|
-
for (cluster.replica_pools) |*pool| pool.deinit(cluster.allocator);
|
|
267
|
-
for (cluster.storages) |*storage| storage.deinit(cluster.allocator);
|
|
268
|
-
|
|
269
|
-
cluster.allocator.free(cluster.clients);
|
|
270
|
-
cluster.allocator.free(cluster.client_pools);
|
|
271
|
-
cluster.allocator.free(cluster.replicas);
|
|
272
|
-
cluster.allocator.free(cluster.replica_health);
|
|
273
|
-
cluster.allocator.free(cluster.replica_pools);
|
|
274
|
-
cluster.allocator.free(cluster.storages);
|
|
275
|
-
cluster.allocator.destroy(cluster.storage_fault_atlas);
|
|
276
|
-
cluster.allocator.destroy(cluster.network);
|
|
277
|
-
cluster.allocator.destroy(cluster);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
pub fn tick(cluster: *Self) void {
|
|
281
|
-
cluster.network.tick();
|
|
282
|
-
|
|
283
|
-
for (cluster.clients) |*client| client.tick();
|
|
284
|
-
for (cluster.storages) |*storage| storage.tick();
|
|
285
|
-
for (cluster.replicas) |*replica, i| {
|
|
286
|
-
switch (cluster.replica_health[i]) {
|
|
287
|
-
.up => replica.tick(),
|
|
288
|
-
// Keep ticking the time so that it won't have diverged too far to synchronize
|
|
289
|
-
// when the replica restarts.
|
|
290
|
-
.down => replica.clock.time.tick(),
|
|
291
|
-
}
|
|
292
|
-
on_replica_change_state(replica);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
pub fn restart_replica(cluster: *Self, replica_index: u8) void {
|
|
297
|
-
assert(cluster.replica_health[replica_index] == .down);
|
|
298
|
-
|
|
299
|
-
cluster.network.process_enable(.{ .replica = replica_index });
|
|
300
|
-
cluster.replica_health[replica_index] = .up;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/// Reset a replica to its initial state, simulating a random crash/panic.
|
|
304
|
-
/// Leave the persistent storage untouched, and leave any currently
|
|
305
|
-
/// inflight messages to/from the replica in the network.
|
|
306
|
-
///
|
|
307
|
-
/// Returns whether the replica was crashed.
|
|
308
|
-
/// Returns an error when the replica was unable to recover (open).
|
|
309
|
-
pub fn crash_replica(cluster: *Self, replica_index: u8) !void {
|
|
310
|
-
assert(cluster.replica_health[replica_index] == .up);
|
|
311
|
-
|
|
312
|
-
// Reset the storage before the replica so that pending writes can (partially) finish.
|
|
313
|
-
cluster.storages[replica_index].reset();
|
|
314
|
-
|
|
315
|
-
const replica = &cluster.replicas[replica_index];
|
|
316
|
-
const replica_time = replica.time;
|
|
317
|
-
replica.deinit(cluster.allocator);
|
|
318
|
-
cluster.network.process_disable(.{ .replica = replica_index });
|
|
319
|
-
cluster.replica_health[replica_index] = .down;
|
|
320
|
-
|
|
321
|
-
// Ensure that none of the replica's messages leaked when it was deinitialized.
|
|
322
|
-
var messages_in_pool: usize = 0;
|
|
323
|
-
const message_bus = cluster.network.get_message_bus(.{ .replica = replica_index });
|
|
324
|
-
{
|
|
325
|
-
var it = message_bus.pool.free_list;
|
|
326
|
-
while (it) |message| : (it = message.next) messages_in_pool += 1;
|
|
327
|
-
}
|
|
328
|
-
assert(messages_in_pool == message_pool.messages_max_replica);
|
|
329
|
-
|
|
330
|
-
// Logically it would make more sense to run this during restart, not immediately following
|
|
331
|
-
// the crash. But having it here allows the replica's MessageBus to initialize and begin
|
|
332
|
-
// queueing packets.
|
|
333
|
-
//
|
|
334
|
-
// Pass the old replica's Time through to the new replica. It will continue to tick while
|
|
335
|
-
// the replica is crashed, to ensure the clocks don't desyncronize too far to recover.
|
|
336
|
-
try cluster.open_replica(replica_index, replica_time);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
fn open_replica(cluster: *Self, replica_index: u8, time: Time) !void {
|
|
340
|
-
var replica = &cluster.replicas[replica_index];
|
|
341
|
-
try replica.open(
|
|
342
|
-
cluster.allocator,
|
|
343
|
-
.{
|
|
344
|
-
.replica_count = @intCast(u8, cluster.replicas.len),
|
|
345
|
-
.storage = &cluster.storages[replica_index],
|
|
346
|
-
// TODO Test restarting with a higher storage limit.
|
|
347
|
-
.storage_size_limit = cluster.options.storage_size_limit,
|
|
348
|
-
.message_pool = &cluster.replica_pools[replica_index],
|
|
349
|
-
.time = time,
|
|
350
|
-
.state_machine_options = cluster.options.state_machine,
|
|
351
|
-
.message_bus_options = .{ .network = cluster.network },
|
|
352
|
-
},
|
|
353
|
-
);
|
|
354
|
-
assert(replica.cluster == cluster.options.cluster_id);
|
|
355
|
-
assert(replica.replica == replica_index);
|
|
356
|
-
assert(replica.replica_count == cluster.replicas.len);
|
|
357
|
-
|
|
358
|
-
replica.context = cluster;
|
|
359
|
-
replica.on_change_state = on_replica_change_state;
|
|
360
|
-
replica.on_compact = on_replica_compact;
|
|
361
|
-
replica.on_checkpoint = on_replica_checkpoint;
|
|
362
|
-
cluster.network.link(replica.message_bus.process, &replica.message_bus);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
pub fn request(
|
|
366
|
-
cluster: *Self,
|
|
367
|
-
client_index: usize,
|
|
368
|
-
request_operation: StateMachine.Operation,
|
|
369
|
-
request_message: *Message,
|
|
370
|
-
request_body_size: usize,
|
|
371
|
-
) void {
|
|
372
|
-
cluster.clients[client_index].request(
|
|
373
|
-
undefined,
|
|
374
|
-
request_callback,
|
|
375
|
-
request_operation,
|
|
376
|
-
request_message,
|
|
377
|
-
request_body_size,
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/// The `request_callback` is not used — Cluster uses `Client.on_reply_{context,callback}`
|
|
382
|
-
/// instead because:
|
|
383
|
-
/// - Cluster needs access to the request
|
|
384
|
-
/// - Cluster needs access to the reply message (not just the body)
|
|
385
|
-
/// - Cluster needs to know about command=register messages
|
|
386
|
-
///
|
|
387
|
-
/// See `on_reply`.
|
|
388
|
-
fn request_callback(
|
|
389
|
-
user_data: u128,
|
|
390
|
-
operation: StateMachine.Operation,
|
|
391
|
-
result: Client.Error![]const u8,
|
|
392
|
-
) void {
|
|
393
|
-
_ = user_data;
|
|
394
|
-
_ = operation;
|
|
395
|
-
_ = result catch |err| switch (err) {
|
|
396
|
-
error.TooManyOutstandingRequests => unreachable,
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
fn client_on_reply(client: *Client, request_message: *Message, reply_message: *Message) void {
|
|
401
|
-
const cluster = @ptrCast(*Self, @alignCast(@alignOf(Self), client.on_reply_context.?));
|
|
402
|
-
assert(reply_message.header.cluster == cluster.options.cluster_id);
|
|
403
|
-
assert(reply_message.header.invalid() == null);
|
|
404
|
-
assert(reply_message.header.client == client.id);
|
|
405
|
-
assert(reply_message.header.request == request_message.header.request);
|
|
406
|
-
assert(reply_message.header.command == .reply);
|
|
407
|
-
assert(reply_message.header.operation == request_message.header.operation);
|
|
408
|
-
|
|
409
|
-
const client_index = for (cluster.clients) |*c, i| {
|
|
410
|
-
if (client == c) break i;
|
|
411
|
-
} else unreachable;
|
|
412
|
-
|
|
413
|
-
cluster.on_client_reply(cluster, client_index, request_message, reply_message);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
fn on_replica_change_state(replica: *const Replica) void {
|
|
417
|
-
const cluster = @ptrCast(*Self, @alignCast(@alignOf(Self), replica.context.?));
|
|
418
|
-
cluster.state_checker.check_state(replica.replica) catch |err| {
|
|
419
|
-
fatal(.correctness, "state checker error: {}", .{err});
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
fn on_replica_compact(replica: *const Replica) void {
|
|
424
|
-
const cluster = @ptrCast(*Self, @alignCast(@alignOf(Self), replica.context.?));
|
|
425
|
-
cluster.storage_checker.replica_compact(replica) catch |err| {
|
|
426
|
-
fatal(.correctness, "storage checker error: {}", .{err});
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
fn on_replica_checkpoint(replica: *const Replica) void {
|
|
431
|
-
const cluster = @ptrCast(*Self, @alignCast(@alignOf(Self), replica.context.?));
|
|
432
|
-
cluster.storage_checker.replica_checkpoint(replica) catch |err| {
|
|
433
|
-
fatal(.correctness, "storage checker error: {}", .{err});
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/// Print an error message and then exit with an exit code.
|
|
438
|
-
fn fatal(failure: Failure, comptime fmt_string: []const u8, args: anytype) noreturn {
|
|
439
|
-
std.log.scoped(.state_checker).err(fmt_string, args);
|
|
440
|
-
std.os.exit(@enumToInt(failure));
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
//! Utils functions for writing fuzzers.
|
|
2
|
-
|
|
3
|
-
const std = @import("std");
|
|
4
|
-
const assert = std.debug.assert;
|
|
5
|
-
const mem = std.mem;
|
|
6
|
-
|
|
7
|
-
const log = std.log.scoped(.fuzz);
|
|
8
|
-
|
|
9
|
-
/// Returns an integer of type `T` with an exponential distribution of rate `avg`.
|
|
10
|
-
/// Note: If you specify a very high rate then `std.math.maxInt(T)` may be over-represented.
|
|
11
|
-
pub fn random_int_exponential(random: std.rand.Random, comptime T: type, avg: T) T {
|
|
12
|
-
comptime {
|
|
13
|
-
const info = @typeInfo(T);
|
|
14
|
-
assert(info == .Int);
|
|
15
|
-
assert(info.Int.signedness == .unsigned);
|
|
16
|
-
}
|
|
17
|
-
const exp = random.floatExp(f64) * @intToFloat(f64, avg);
|
|
18
|
-
return std.math.lossyCast(T, exp);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
pub fn Distribution(comptime Enum: type) type {
|
|
22
|
-
return std.enums.EnumFieldStruct(Enum, f64, null);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/// Return a distribution for use with `random_enum`.
|
|
26
|
-
pub fn random_enum_distribution(
|
|
27
|
-
random: std.rand.Random,
|
|
28
|
-
comptime Enum: type,
|
|
29
|
-
) Distribution(Enum) {
|
|
30
|
-
const fields = @typeInfo(Distribution(Enum)).Struct.fields;
|
|
31
|
-
var distribution: Distribution(Enum) = undefined;
|
|
32
|
-
var total: f64 = 0;
|
|
33
|
-
inline for (fields) |field| {
|
|
34
|
-
const p = @intToFloat(f64, random.uintLessThan(u8, 10));
|
|
35
|
-
@field(distribution, field.name) = p;
|
|
36
|
-
total += p;
|
|
37
|
-
}
|
|
38
|
-
// Ensure that at least one field has non-zero probability.
|
|
39
|
-
if (total == 0) {
|
|
40
|
-
@field(distribution, fields[0].name) = 1;
|
|
41
|
-
}
|
|
42
|
-
return distribution;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/// Generate a random `Enum`, given a distribution over the fields of the enum.
|
|
46
|
-
pub fn random_enum(
|
|
47
|
-
random: std.rand.Random,
|
|
48
|
-
comptime Enum: type,
|
|
49
|
-
distribution: Distribution(Enum),
|
|
50
|
-
) Enum {
|
|
51
|
-
const fields = @typeInfo(Enum).Enum.fields;
|
|
52
|
-
var total: f64 = 0;
|
|
53
|
-
inline for (fields) |field| {
|
|
54
|
-
total += @field(distribution, field.name);
|
|
55
|
-
}
|
|
56
|
-
assert(total > 0);
|
|
57
|
-
var choice = random.float(f64) * total;
|
|
58
|
-
inline for (fields) |field| {
|
|
59
|
-
choice -= @field(distribution, field.name);
|
|
60
|
-
if (choice < 0) return @intToEnum(Enum, field.value);
|
|
61
|
-
}
|
|
62
|
-
unreachable;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
pub const FuzzArgs = struct {
|
|
66
|
-
seed: u64,
|
|
67
|
-
events_max: ?usize,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/// Parse common command-line arguments to fuzzers:
|
|
71
|
-
///
|
|
72
|
-
/// [--seed u64]
|
|
73
|
-
/// Sets the seed used for the random number generator.
|
|
74
|
-
/// [--events-max usize]
|
|
75
|
-
/// Override the fuzzer's default maximum number of generated events.
|
|
76
|
-
pub fn parse_fuzz_args(allocator: mem.Allocator) !FuzzArgs {
|
|
77
|
-
var seed: ?u64 = null;
|
|
78
|
-
var events_max: ?usize = null;
|
|
79
|
-
|
|
80
|
-
var args = std.process.args();
|
|
81
|
-
|
|
82
|
-
// Discard executable name.
|
|
83
|
-
allocator.free(try args.next(allocator).?);
|
|
84
|
-
|
|
85
|
-
while (args.next(allocator)) |arg_or_err| {
|
|
86
|
-
const arg = try arg_or_err;
|
|
87
|
-
defer allocator.free(arg);
|
|
88
|
-
|
|
89
|
-
if (std.mem.eql(u8, arg, "--seed")) {
|
|
90
|
-
const seed_string_or_err = args.next(allocator) orelse
|
|
91
|
-
std.debug.panic("Expected an argument to --seed", .{});
|
|
92
|
-
const seed_string = try seed_string_or_err;
|
|
93
|
-
defer allocator.free(seed_string);
|
|
94
|
-
|
|
95
|
-
if (seed != null) {
|
|
96
|
-
std.debug.panic("Received more than one \"--seed\"", .{});
|
|
97
|
-
}
|
|
98
|
-
seed = std.fmt.parseInt(u64, seed_string, 10) catch |err|
|
|
99
|
-
std.debug.panic(
|
|
100
|
-
"Could not parse \"{}\" as an integer seed: {}",
|
|
101
|
-
.{ std.zig.fmtEscapes(seed_string), err },
|
|
102
|
-
);
|
|
103
|
-
} else if (std.mem.eql(u8, arg, "--events-max")) {
|
|
104
|
-
const events_string_or_err = args.next(allocator) orelse
|
|
105
|
-
std.debug.panic("Expected an argument to --events-max", .{});
|
|
106
|
-
const events_string = try events_string_or_err;
|
|
107
|
-
defer allocator.free(events_string);
|
|
108
|
-
|
|
109
|
-
if (events_max != null) {
|
|
110
|
-
std.debug.panic("Received more than one \"--events-max\"", .{});
|
|
111
|
-
}
|
|
112
|
-
events_max = std.fmt.parseInt(usize, events_string, 10) catch |err|
|
|
113
|
-
std.debug.panic(
|
|
114
|
-
"Could not parse \"{}\" as an integer events-max: {}",
|
|
115
|
-
.{ std.zig.fmtEscapes(events_string), err },
|
|
116
|
-
);
|
|
117
|
-
} else {
|
|
118
|
-
// When run with `--test-cmd`,
|
|
119
|
-
// `zig run` also passes the location of the zig binary as an extra arg.
|
|
120
|
-
// I don't know how to turn this off, so we just skip such args.
|
|
121
|
-
if (!std.mem.endsWith(u8, arg, "zig")) {
|
|
122
|
-
std.debug.panic("Unrecognized argument: \"{}\"", .{std.zig.fmtEscapes(arg)});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (seed == null) {
|
|
128
|
-
// If no seed was given, use a random seed instead.
|
|
129
|
-
var buffer: [@sizeOf(u64)]u8 = undefined;
|
|
130
|
-
try std.os.getrandom(&buffer);
|
|
131
|
-
seed = @bitCast(u64, buffer);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
log.info("Fuzz seed = {}", .{seed.?});
|
|
135
|
-
|
|
136
|
-
return FuzzArgs{
|
|
137
|
-
.seed = seed.?,
|
|
138
|
-
.events_max = events_max,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
//! A tool for narrowing down the point of divergence between two executions that should be identical.
|
|
2
|
-
//! Sprinkle calls to `emit(some_hash)` throughout the code.
|
|
3
|
-
//! With `-Dhash-log-mode=create`, all emitted hashes are written to ./hash_log.
|
|
4
|
-
//! With `-Dhash-log-mode=check`, all emitted hashes are checked against the hashes in ./hash_log.
|
|
5
|
-
//! Otherwise, calls to `emit` are noops.
|
|
6
|
-
|
|
7
|
-
const std = @import("std");
|
|
8
|
-
const assert = std.debug.assert;
|
|
9
|
-
const panic = std.debug.panic;
|
|
10
|
-
|
|
11
|
-
const constants = @import("../constants.zig");
|
|
12
|
-
|
|
13
|
-
var file: ?std.fs.File = null;
|
|
14
|
-
var hash_count: usize = 0;
|
|
15
|
-
|
|
16
|
-
fn ensure_init() void {
|
|
17
|
-
if (file != null) return;
|
|
18
|
-
switch (constants.hash_log_mode) {
|
|
19
|
-
.none => unreachable,
|
|
20
|
-
.create => {
|
|
21
|
-
file = std.fs.cwd().createFile("./hash_log", .{ .truncate = true }) catch unreachable;
|
|
22
|
-
},
|
|
23
|
-
.check => {
|
|
24
|
-
file = std.fs.cwd().openFile("./hash_log", .{ .read = true }) catch unreachable;
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
pub fn emit(hash: u128) void {
|
|
30
|
-
@call(.{ .modifier = .never_inline }, emit_never_inline, .{hash});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Don't inline because we want to be able to break on this function.
|
|
34
|
-
fn emit_never_inline(hash: u128) void {
|
|
35
|
-
switch (constants.hash_log_mode) {
|
|
36
|
-
.none => {},
|
|
37
|
-
.create => {
|
|
38
|
-
ensure_init();
|
|
39
|
-
std.fmt.format(file.?.writer(), "{x:0>32}\n", .{hash}) catch unreachable;
|
|
40
|
-
hash_count += 1;
|
|
41
|
-
},
|
|
42
|
-
.check => {
|
|
43
|
-
ensure_init();
|
|
44
|
-
var buffer: [33]u8 = undefined;
|
|
45
|
-
const bytes_read = file.?.readAll(&buffer) catch unreachable;
|
|
46
|
-
if (bytes_read != 33) {
|
|
47
|
-
panic("Unexpected end of hash_log at hash_count={}. Expected EOF, found {x:0>32}.", .{ hash_count, hash });
|
|
48
|
-
}
|
|
49
|
-
const expected_hash = std.fmt.parseInt(u128, buffer[0..32], 16) catch unreachable;
|
|
50
|
-
if (hash != expected_hash) {
|
|
51
|
-
panic(
|
|
52
|
-
"Hash mismatch at hash_count={}. Expected {x:0>32}, found {x:0>32}.",
|
|
53
|
-
.{ hash_count, expected_hash, hash },
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
hash_count += 1;
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
pub fn emit_autohash(hashable: anytype, comptime strategy: std.hash.Strategy) void {
|
|
62
|
-
if (constants.hash_log_mode == .none) return;
|
|
63
|
-
var hasher = std.hash.Wyhash.init(0);
|
|
64
|
-
std.hash.autoHashStrat(&hasher, hashable, strategy);
|
|
65
|
-
emit(hasher.final());
|
|
66
|
-
}
|