tigerbeetle-node 0.9.0 → 0.9.143
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 +580 -179
- package/dist/benchmark.js +44 -36
- package/dist/benchmark.js.map +1 -1
- 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/bin/x86_64-windows/client.node +0 -0
- package/dist/bindings.d.ts +141 -0
- package/dist/bindings.js +112 -0
- package/dist/bindings.js.map +1 -0
- package/dist/index.d.ts +2 -125
- package/dist/index.js +51 -101
- package/dist/index.js.map +1 -1
- package/dist/test.js +68 -54
- package/dist/test.js.map +1 -1
- package/package-lock.json +26 -0
- package/package.json +13 -22
- package/src/benchmark.ts +58 -49
- package/src/bindings.ts +631 -0
- package/src/index.ts +71 -163
- package/src/node.zig +169 -148
- package/src/test.ts +71 -57
- package/src/translate.zig +19 -36
- package/scripts/download_node_headers.sh +0 -25
- package/src/tigerbeetle/scripts/benchmark.bat +0 -46
- package/src/tigerbeetle/scripts/benchmark.sh +0 -55
- package/src/tigerbeetle/scripts/install.sh +0 -6
- package/src/tigerbeetle/scripts/install_zig.bat +0 -109
- package/src/tigerbeetle/scripts/install_zig.sh +0 -84
- package/src/tigerbeetle/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -39
- package/src/tigerbeetle/scripts/vopr.bat +0 -48
- package/src/tigerbeetle/scripts/vopr.sh +0 -33
- package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/src/benchmark.zig +0 -290
- package/src/tigerbeetle/src/cli.zig +0 -244
- package/src/tigerbeetle/src/config.zig +0 -239
- package/src/tigerbeetle/src/demo.zig +0 -125
- 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 -24
- 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/fifo.zig +0 -104
- package/src/tigerbeetle/src/io/benchmark.zig +0 -213
- package/src/tigerbeetle/src/io/darwin.zig +0 -793
- package/src/tigerbeetle/src/io/linux.zig +0 -1038
- package/src/tigerbeetle/src/io/test.zig +0 -643
- package/src/tigerbeetle/src/io/windows.zig +0 -1161
- package/src/tigerbeetle/src/io.zig +0 -34
- package/src/tigerbeetle/src/main.zig +0 -144
- package/src/tigerbeetle/src/message_bus.zig +0 -1000
- package/src/tigerbeetle/src/message_pool.zig +0 -142
- package/src/tigerbeetle/src/ring_buffer.zig +0 -289
- package/src/tigerbeetle/src/simulator.zig +0 -417
- package/src/tigerbeetle/src/state_machine.zig +0 -2470
- package/src/tigerbeetle/src/storage.zig +0 -308
- package/src/tigerbeetle/src/test/cluster.zig +0 -351
- package/src/tigerbeetle/src/test/message_bus.zig +0 -93
- package/src/tigerbeetle/src/test/network.zig +0 -179
- package/src/tigerbeetle/src/test/packet_simulator.zig +0 -387
- package/src/tigerbeetle/src/test/state_checker.zig +0 -145
- package/src/tigerbeetle/src/test/state_machine.zig +0 -76
- package/src/tigerbeetle/src/test/storage.zig +0 -438
- package/src/tigerbeetle/src/test/time.zig +0 -84
- package/src/tigerbeetle/src/tigerbeetle.zig +0 -222
- package/src/tigerbeetle/src/time.zig +0 -113
- package/src/tigerbeetle/src/unit_tests.zig +0 -14
- package/src/tigerbeetle/src/vsr/client.zig +0 -505
- package/src/tigerbeetle/src/vsr/clock.zig +0 -812
- package/src/tigerbeetle/src/vsr/journal.zig +0 -2293
- package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
- package/src/tigerbeetle/src/vsr/replica.zig +0 -5015
- package/src/tigerbeetle/src/vsr.zig +0 -1017
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
|
|
4
|
-
const config = @import("../config.zig");
|
|
5
|
-
|
|
6
|
-
const MessagePool = @import("../message_pool.zig").MessagePool;
|
|
7
|
-
const Message = MessagePool.Message;
|
|
8
|
-
const Header = @import("../vsr.zig").Header;
|
|
9
|
-
const ProcessType = @import("../vsr.zig").ProcessType;
|
|
10
|
-
|
|
11
|
-
const Network = @import("network.zig").Network;
|
|
12
|
-
|
|
13
|
-
const log = std.log.scoped(.message_bus);
|
|
14
|
-
|
|
15
|
-
pub const Process = union(ProcessType) {
|
|
16
|
-
replica: u8,
|
|
17
|
-
client: u128,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
pub const MessageBus = struct {
|
|
21
|
-
network: *Network,
|
|
22
|
-
pool: MessagePool,
|
|
23
|
-
|
|
24
|
-
cluster: u32,
|
|
25
|
-
process: Process,
|
|
26
|
-
|
|
27
|
-
/// The callback to be called when a message is received. Use set_on_message() to set
|
|
28
|
-
/// with type safety for the context pointer.
|
|
29
|
-
on_message_callback: ?fn (context: ?*anyopaque, message: *Message) void = null,
|
|
30
|
-
on_message_context: ?*anyopaque = null,
|
|
31
|
-
|
|
32
|
-
pub fn init(
|
|
33
|
-
allocator: std.mem.Allocator,
|
|
34
|
-
cluster: u32,
|
|
35
|
-
process: Process,
|
|
36
|
-
network: *Network,
|
|
37
|
-
) !MessageBus {
|
|
38
|
-
return MessageBus{
|
|
39
|
-
.pool = try MessagePool.init(allocator, @as(ProcessType, process)),
|
|
40
|
-
.network = network,
|
|
41
|
-
.cluster = cluster,
|
|
42
|
-
.process = process,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/// TODO
|
|
47
|
-
pub fn deinit(_: *MessageBus) void {}
|
|
48
|
-
|
|
49
|
-
pub fn set_on_message(
|
|
50
|
-
bus: *MessageBus,
|
|
51
|
-
comptime Context: type,
|
|
52
|
-
context: Context,
|
|
53
|
-
comptime on_message: fn (context: Context, message: *Message) void,
|
|
54
|
-
) void {
|
|
55
|
-
bus.on_message_callback = struct {
|
|
56
|
-
fn wrapper(_context: ?*anyopaque, message: *Message) void {
|
|
57
|
-
on_message(@intToPtr(Context, @ptrToInt(_context)), message);
|
|
58
|
-
}
|
|
59
|
-
}.wrapper;
|
|
60
|
-
bus.on_message_context = context;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
pub fn tick(_: *MessageBus) void {}
|
|
64
|
-
|
|
65
|
-
pub fn get_message(bus: *MessageBus) *Message {
|
|
66
|
-
return bus.pool.get_message();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
pub fn unref(bus: *MessageBus, message: *Message) void {
|
|
70
|
-
bus.pool.unref(message);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
pub fn send_message_to_replica(bus: *MessageBus, replica: u8, message: *Message) void {
|
|
74
|
-
// Messages sent by a process to itself should never be passed to the message bus
|
|
75
|
-
if (bus.process == .replica) assert(replica != bus.process.replica);
|
|
76
|
-
|
|
77
|
-
bus.network.send_message(message, .{
|
|
78
|
-
.source = bus.process,
|
|
79
|
-
.target = .{ .replica = replica },
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/// Try to send the message to the client with the given id.
|
|
84
|
-
/// If the client is not currently connected, the message is silently dropped.
|
|
85
|
-
pub fn send_message_to_client(bus: *MessageBus, client_id: u128, message: *Message) void {
|
|
86
|
-
assert(bus.process == .replica);
|
|
87
|
-
|
|
88
|
-
bus.network.send_message(message, .{
|
|
89
|
-
.source = bus.process,
|
|
90
|
-
.target = .{ .client = client_id },
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
};
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const math = std.math;
|
|
3
|
-
const mem = std.mem;
|
|
4
|
-
const assert = std.debug.assert;
|
|
5
|
-
|
|
6
|
-
const config = @import("../config.zig");
|
|
7
|
-
const vsr = @import("../vsr.zig");
|
|
8
|
-
|
|
9
|
-
const MessagePool = @import("../message_pool.zig").MessagePool;
|
|
10
|
-
const Message = MessagePool.Message;
|
|
11
|
-
|
|
12
|
-
const MessageBus = @import("message_bus.zig").MessageBus;
|
|
13
|
-
const Process = @import("message_bus.zig").Process;
|
|
14
|
-
|
|
15
|
-
const PacketSimulator = @import("packet_simulator.zig").PacketSimulator;
|
|
16
|
-
const PacketSimulatorOptions = @import("packet_simulator.zig").PacketSimulatorOptions;
|
|
17
|
-
const PacketSimulatorPath = @import("packet_simulator.zig").Path;
|
|
18
|
-
|
|
19
|
-
const log = std.log.scoped(.network);
|
|
20
|
-
|
|
21
|
-
pub const NetworkOptions = struct {
|
|
22
|
-
packet_simulator_options: PacketSimulatorOptions,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
pub const Network = struct {
|
|
26
|
-
pub const Packet = struct {
|
|
27
|
-
network: *Network,
|
|
28
|
-
message: *Message,
|
|
29
|
-
|
|
30
|
-
pub fn deinit(packet: *const Packet, path: PacketSimulatorPath) void {
|
|
31
|
-
const source_bus = &packet.network.buses.items[path.source];
|
|
32
|
-
source_bus.unref(packet.message);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
pub const Path = struct {
|
|
37
|
-
source: Process,
|
|
38
|
-
target: Process,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
allocator: std.mem.Allocator,
|
|
42
|
-
|
|
43
|
-
options: NetworkOptions,
|
|
44
|
-
packet_simulator: PacketSimulator(Packet),
|
|
45
|
-
|
|
46
|
-
buses: std.ArrayListUnmanaged(MessageBus),
|
|
47
|
-
processes: std.ArrayListUnmanaged(u128),
|
|
48
|
-
|
|
49
|
-
pub fn init(
|
|
50
|
-
allocator: std.mem.Allocator,
|
|
51
|
-
replica_count: u8,
|
|
52
|
-
client_count: u8,
|
|
53
|
-
options: NetworkOptions,
|
|
54
|
-
) !Network {
|
|
55
|
-
const process_count = client_count + replica_count;
|
|
56
|
-
assert(process_count <= std.math.maxInt(u8));
|
|
57
|
-
|
|
58
|
-
var buses = try std.ArrayListUnmanaged(MessageBus).initCapacity(allocator, process_count);
|
|
59
|
-
errdefer buses.deinit(allocator);
|
|
60
|
-
|
|
61
|
-
var processes = try std.ArrayListUnmanaged(u128).initCapacity(allocator, process_count);
|
|
62
|
-
errdefer processes.deinit(allocator);
|
|
63
|
-
|
|
64
|
-
const packet_simulator = try PacketSimulator(Packet).init(
|
|
65
|
-
allocator,
|
|
66
|
-
options.packet_simulator_options,
|
|
67
|
-
);
|
|
68
|
-
errdefer packet_simulator.deinit(allocator);
|
|
69
|
-
|
|
70
|
-
return Network{
|
|
71
|
-
.allocator = allocator,
|
|
72
|
-
.options = options,
|
|
73
|
-
.packet_simulator = packet_simulator,
|
|
74
|
-
.buses = buses,
|
|
75
|
-
.processes = processes,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
pub fn deinit(network: *Network) void {
|
|
80
|
-
// TODO: deinit the buses themselves when they gain a deinit()
|
|
81
|
-
network.buses.deinit(network.allocator);
|
|
82
|
-
network.processes.deinit(network.allocator);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/// Returns the address (index into Network.buses)
|
|
86
|
-
pub fn init_message_bus(network: *Network, cluster: u32, process: Process) !*MessageBus {
|
|
87
|
-
const raw_process = switch (process) {
|
|
88
|
-
.replica => |replica| replica,
|
|
89
|
-
.client => |client| blk: {
|
|
90
|
-
assert(client >= config.replicas_max);
|
|
91
|
-
break :blk client;
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
for (network.processes.items) |p| assert(p != raw_process);
|
|
96
|
-
|
|
97
|
-
const bus = try MessageBus.init(network.allocator, cluster, process, network);
|
|
98
|
-
|
|
99
|
-
network.processes.appendAssumeCapacity(raw_process);
|
|
100
|
-
network.buses.appendAssumeCapacity(bus);
|
|
101
|
-
|
|
102
|
-
return &network.buses.items[network.buses.items.len - 1];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
pub fn send_message(network: *Network, message: *Message, path: Path) void {
|
|
106
|
-
// TODO: we want to unref this message at some point between send()
|
|
107
|
-
// and recv() for better realism.
|
|
108
|
-
log.debug("send_message: {} > {}: {}", .{
|
|
109
|
-
path.source,
|
|
110
|
-
path.target,
|
|
111
|
-
message.header.command,
|
|
112
|
-
});
|
|
113
|
-
network.packet_simulator.submit_packet(
|
|
114
|
-
.{
|
|
115
|
-
.message = message.ref(),
|
|
116
|
-
.network = network,
|
|
117
|
-
},
|
|
118
|
-
deliver_message,
|
|
119
|
-
.{
|
|
120
|
-
.source = network.process_to_address(path.source),
|
|
121
|
-
.target = network.process_to_address(path.target),
|
|
122
|
-
},
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
fn process_to_address(network: *Network, process: Process) u8 {
|
|
127
|
-
for (network.processes.items) |p, i| {
|
|
128
|
-
if (std.meta.eql(raw_process_to_process(p), process)) return @intCast(u8, i);
|
|
129
|
-
}
|
|
130
|
-
unreachable;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
pub fn get_message_bus(network: *Network, process: Process) *MessageBus {
|
|
134
|
-
return &network.buses.items[network.process_to_address(process)];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
fn deliver_message(packet: Packet, path: PacketSimulatorPath) void {
|
|
138
|
-
const network = packet.network;
|
|
139
|
-
|
|
140
|
-
const target_bus = &network.buses.items[path.target];
|
|
141
|
-
|
|
142
|
-
const message = target_bus.get_message();
|
|
143
|
-
defer target_bus.unref(message);
|
|
144
|
-
|
|
145
|
-
std.mem.copy(u8, message.buffer, packet.message.buffer);
|
|
146
|
-
|
|
147
|
-
const process_path = .{
|
|
148
|
-
.source = raw_process_to_process(network.processes.items[path.source]),
|
|
149
|
-
.target = raw_process_to_process(network.processes.items[path.target]),
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
log.debug("deliver_message: {} > {}: {}", .{
|
|
153
|
-
process_path.source,
|
|
154
|
-
process_path.target,
|
|
155
|
-
packet.message.header.command,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
if (message.header.command == .request or message.header.command == .prepare) {
|
|
159
|
-
const sector_ceil = vsr.sector_ceil(message.header.size);
|
|
160
|
-
if (message.header.size != sector_ceil) {
|
|
161
|
-
assert(message.header.size < sector_ceil);
|
|
162
|
-
assert(message.buffer.len == config.message_size_max + config.sector_size);
|
|
163
|
-
mem.set(u8, message.buffer[message.header.size..sector_ceil], 0);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
target_bus.on_message_callback.?(target_bus.on_message_context, message);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
fn raw_process_to_process(raw: u128) Process {
|
|
171
|
-
switch (raw) {
|
|
172
|
-
0...(config.replicas_max - 1) => return .{ .replica = @intCast(u8, raw) },
|
|
173
|
-
else => {
|
|
174
|
-
assert(raw >= config.replicas_max);
|
|
175
|
-
return .{ .client = raw };
|
|
176
|
-
},
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
};
|
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
const math = std.math;
|
|
4
|
-
|
|
5
|
-
const log = std.log.scoped(.packet_simulator);
|
|
6
|
-
const ReplicaHealth = @import("./cluster.zig").ReplicaHealth;
|
|
7
|
-
|
|
8
|
-
pub const PacketSimulatorOptions = struct {
|
|
9
|
-
/// Mean for the exponential distribution used to calculate forward delay.
|
|
10
|
-
one_way_delay_mean: u64,
|
|
11
|
-
one_way_delay_min: u64,
|
|
12
|
-
|
|
13
|
-
packet_loss_probability: u8,
|
|
14
|
-
packet_replay_probability: u8,
|
|
15
|
-
seed: u64,
|
|
16
|
-
|
|
17
|
-
replica_count: u8,
|
|
18
|
-
client_count: u8,
|
|
19
|
-
node_count: u8,
|
|
20
|
-
|
|
21
|
-
/// How the partitions should be generated
|
|
22
|
-
partition_mode: PartitionMode,
|
|
23
|
-
|
|
24
|
-
/// Probability per tick that a partition will occur
|
|
25
|
-
partition_probability: u8,
|
|
26
|
-
|
|
27
|
-
/// Probability per tick that a partition will resolve
|
|
28
|
-
unpartition_probability: u8,
|
|
29
|
-
|
|
30
|
-
/// Minimum time a partition lasts
|
|
31
|
-
partition_stability: u32,
|
|
32
|
-
|
|
33
|
-
/// Minimum time the cluster is fully connected until it is partitioned again
|
|
34
|
-
unpartition_stability: u32,
|
|
35
|
-
|
|
36
|
-
/// The maximum number of in-flight packets a path can have before packets are randomly dropped.
|
|
37
|
-
path_maximum_capacity: u8,
|
|
38
|
-
|
|
39
|
-
/// Mean for the exponential distribution used to calculate how long a path is clogged for.
|
|
40
|
-
path_clog_duration_mean: u64,
|
|
41
|
-
path_clog_probability: u8,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
pub const Path = struct {
|
|
45
|
-
source: u8,
|
|
46
|
-
target: u8,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/// Determines how the partitions are created. Partitions
|
|
50
|
-
/// are two-way, i.e. if i cannot communicate with j, then
|
|
51
|
-
/// j cannot communicate with i.
|
|
52
|
-
///
|
|
53
|
-
/// Only replicas are partitioned. There will always be exactly two partitions.
|
|
54
|
-
pub const PartitionMode = enum {
|
|
55
|
-
/// Draws the size of the partition uniformly at random from (1, n-1).
|
|
56
|
-
/// Replicas are randomly assigned a partition.
|
|
57
|
-
uniform_size,
|
|
58
|
-
|
|
59
|
-
/// Assigns each node to a partition uniformly at random. This biases towards
|
|
60
|
-
/// equal-size partitions.
|
|
61
|
-
uniform_partition,
|
|
62
|
-
|
|
63
|
-
/// Isolates exactly one replica.
|
|
64
|
-
isolate_single,
|
|
65
|
-
|
|
66
|
-
/// User-defined partitioning algorithm.
|
|
67
|
-
custom,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/// A fully connected network of nodes used for testing. Simulates the fault model:
|
|
71
|
-
/// Packets may be dropped.
|
|
72
|
-
/// Packets may be delayed.
|
|
73
|
-
/// Packets may be replayed.
|
|
74
|
-
pub const PacketStatistics = enum(u8) {
|
|
75
|
-
dropped_due_to_partition,
|
|
76
|
-
dropped_due_to_congestion,
|
|
77
|
-
dropped_due_to_crash,
|
|
78
|
-
dropped,
|
|
79
|
-
replay,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
pub fn PacketSimulator(comptime Packet: type) type {
|
|
83
|
-
return struct {
|
|
84
|
-
const Self = @This();
|
|
85
|
-
|
|
86
|
-
const Data = struct {
|
|
87
|
-
expiry: u64,
|
|
88
|
-
callback: fn (packet: Packet, path: Path) void,
|
|
89
|
-
packet: Packet,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/// A send and receive path between each node in the network. We use the `path` function to
|
|
93
|
-
/// index it.
|
|
94
|
-
paths: []std.PriorityQueue(Data, void, Self.order_packets),
|
|
95
|
-
|
|
96
|
-
/// We can arbitrary clog a path until a tick.
|
|
97
|
-
path_clogged_till: []u64,
|
|
98
|
-
ticks: u64 = 0,
|
|
99
|
-
options: PacketSimulatorOptions,
|
|
100
|
-
prng: std.rand.DefaultPrng,
|
|
101
|
-
stats: [@typeInfo(PacketStatistics).Enum.fields.len]u32 = [_]u32{0} **
|
|
102
|
-
@typeInfo(PacketStatistics).Enum.fields.len,
|
|
103
|
-
|
|
104
|
-
is_partitioned: bool,
|
|
105
|
-
partition: []bool,
|
|
106
|
-
replicas: []u8,
|
|
107
|
-
stability: u32,
|
|
108
|
-
|
|
109
|
-
pub fn init(allocator: std.mem.Allocator, options: PacketSimulatorOptions) !Self {
|
|
110
|
-
assert(options.one_way_delay_mean >= options.one_way_delay_min);
|
|
111
|
-
var self = Self{
|
|
112
|
-
.paths = try allocator.alloc(
|
|
113
|
-
std.PriorityQueue(Data, void, Self.order_packets),
|
|
114
|
-
@as(usize, options.node_count) * options.node_count,
|
|
115
|
-
),
|
|
116
|
-
.path_clogged_till = try allocator.alloc(
|
|
117
|
-
u64,
|
|
118
|
-
@as(usize, options.node_count) * options.node_count,
|
|
119
|
-
),
|
|
120
|
-
.options = options,
|
|
121
|
-
.prng = std.rand.DefaultPrng.init(options.seed),
|
|
122
|
-
|
|
123
|
-
.is_partitioned = false,
|
|
124
|
-
.stability = options.unpartition_stability,
|
|
125
|
-
.partition = try allocator.alloc(bool, @as(usize, options.replica_count)),
|
|
126
|
-
.replicas = try allocator.alloc(u8, @as(usize, options.replica_count)),
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
for (self.replicas) |_, i| {
|
|
130
|
-
self.replicas[i] = @intCast(u8, i);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
for (self.paths) |*queue| {
|
|
134
|
-
queue.* = std.PriorityQueue(Data, void, Self.order_packets).init(allocator, {});
|
|
135
|
-
try queue.ensureTotalCapacity(options.path_maximum_capacity);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
for (self.path_clogged_till) |*clogged_till| {
|
|
139
|
-
clogged_till.* = 0;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return self;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
|
|
146
|
-
for (self.paths) |*queue| {
|
|
147
|
-
while (queue.popOrNull()) |*data| data.packet.deinit();
|
|
148
|
-
queue.deinit();
|
|
149
|
-
}
|
|
150
|
-
allocator.free(self.paths);
|
|
151
|
-
allocator.free(self.path_clogged_till);
|
|
152
|
-
allocator.free(self.partition);
|
|
153
|
-
allocator.free(self.replicas);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
fn order_packets(context: void, a: Data, b: Data) math.Order {
|
|
157
|
-
_ = context;
|
|
158
|
-
|
|
159
|
-
return math.order(a.expiry, b.expiry);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
fn should_drop(self: *Self) bool {
|
|
163
|
-
return self.prng.random().uintAtMost(u8, 100) < self.options.packet_loss_probability;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
fn path_index(self: *Self, path: Path) usize {
|
|
167
|
-
assert(path.source < self.options.node_count and path.target < self.options.node_count);
|
|
168
|
-
|
|
169
|
-
return @as(usize, path.source) * self.options.node_count + path.target;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
pub fn path_queue(self: *Self, path: Path) *std.PriorityQueue(Data, void, Self.order_packets) {
|
|
173
|
-
return &self.paths[self.path_index(path)];
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
fn is_clogged(self: *Self, path: Path) bool {
|
|
177
|
-
return self.path_clogged_till[self.path_index(path)] > self.ticks;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
fn should_clog(self: *Self, path: Path) bool {
|
|
181
|
-
_ = path;
|
|
182
|
-
|
|
183
|
-
return self.prng.random().uintAtMost(u8, 100) < self.options.path_clog_probability;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
fn clog_for(self: *Self, path: Path, ticks: u64) void {
|
|
187
|
-
const clog_expiry = &self.path_clogged_till[self.path_index(path)];
|
|
188
|
-
clog_expiry.* = self.ticks + ticks;
|
|
189
|
-
log.debug("Path path.source={} path.target={} clogged for ticks={}", .{
|
|
190
|
-
path.source,
|
|
191
|
-
path.target,
|
|
192
|
-
ticks,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
fn should_replay(self: *Self) bool {
|
|
197
|
-
return self.prng.random().uintAtMost(u8, 100) < self.options.packet_replay_probability;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
fn should_partition(self: *Self) bool {
|
|
201
|
-
return self.prng.random().uintAtMost(u8, 100) < self.options.partition_probability;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
fn should_unpartition(self: *Self) bool {
|
|
205
|
-
return self.prng.random().uintAtMost(u8, 100) < self.options.unpartition_probability;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/// Return a value produced using an exponential distribution with
|
|
209
|
-
/// the minimum and mean specified in self.options
|
|
210
|
-
fn one_way_delay(self: *Self) u64 {
|
|
211
|
-
const min = self.options.one_way_delay_min;
|
|
212
|
-
const mean = self.options.one_way_delay_mean;
|
|
213
|
-
return min + @floatToInt(u64, @intToFloat(f64, mean - min) * self.prng.random().floatExp(f64));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/// Partitions the network. Guaranteed to isolate at least one replica.
|
|
217
|
-
fn partition_network(
|
|
218
|
-
self: *Self,
|
|
219
|
-
) void {
|
|
220
|
-
assert(self.options.replica_count > 1);
|
|
221
|
-
|
|
222
|
-
self.is_partitioned = true;
|
|
223
|
-
self.stability = self.options.partition_stability;
|
|
224
|
-
|
|
225
|
-
switch (self.options.partition_mode) {
|
|
226
|
-
.uniform_size => {
|
|
227
|
-
// Exclude cases sz == 0 and sz == replica_count
|
|
228
|
-
const sz =
|
|
229
|
-
1 + self.prng.random().uintAtMost(u8, self.options.replica_count - 2);
|
|
230
|
-
self.prng.random().shuffle(u8, self.replicas);
|
|
231
|
-
for (self.replicas) |r, i| {
|
|
232
|
-
self.partition[r] = i < sz;
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
.uniform_partition => {
|
|
236
|
-
var only_same = true;
|
|
237
|
-
self.partition[0] =
|
|
238
|
-
self.prng.random().uintLessThan(u8, 2) == 1;
|
|
239
|
-
|
|
240
|
-
var i: usize = 1;
|
|
241
|
-
while (i < self.options.replica_count) : (i += 1) {
|
|
242
|
-
self.partition[i] =
|
|
243
|
-
self.prng.random().uintLessThan(u8, 2) == 1;
|
|
244
|
-
only_same =
|
|
245
|
-
only_same and (self.partition[i - 1] == self.partition[i]);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (only_same) {
|
|
249
|
-
const n = self.prng.random().uintLessThan(u8, self.options.replica_count);
|
|
250
|
-
self.partition[n] = true;
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
.isolate_single => {
|
|
254
|
-
for (self.replicas) |_, i| {
|
|
255
|
-
self.partition[i] = false;
|
|
256
|
-
}
|
|
257
|
-
const n = self.prng.random().uintLessThan(u8, self.options.replica_count);
|
|
258
|
-
self.partition[n] = true;
|
|
259
|
-
},
|
|
260
|
-
// Put your own partitioning logic here.
|
|
261
|
-
.custom => unreachable,
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
fn unpartition_network(
|
|
266
|
-
self: *Self,
|
|
267
|
-
) void {
|
|
268
|
-
self.is_partitioned = false;
|
|
269
|
-
self.stability = self.options.unpartition_stability;
|
|
270
|
-
|
|
271
|
-
for (self.replicas) |_, i| {
|
|
272
|
-
self.partition[i] = false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
fn replicas_are_in_different_partitions(self: *Self, from: u8, to: u8) bool {
|
|
277
|
-
return from < self.options.replica_count and
|
|
278
|
-
to < self.options.replica_count and
|
|
279
|
-
self.partition[from] != self.partition[to];
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
pub fn tick(self: *Self, cluster_health: []const ReplicaHealth) void {
|
|
283
|
-
self.ticks += 1;
|
|
284
|
-
|
|
285
|
-
if (self.stability > 0) {
|
|
286
|
-
self.stability -= 1;
|
|
287
|
-
} else {
|
|
288
|
-
if (self.is_partitioned) {
|
|
289
|
-
if (self.should_unpartition()) {
|
|
290
|
-
self.unpartition_network();
|
|
291
|
-
log.err("unpartitioned network: partition={d}", .{self.partition});
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
if (self.options.replica_count > 1 and self.should_partition()) {
|
|
295
|
-
self.partition_network();
|
|
296
|
-
log.err("partitioned network: partition={d}", .{self.partition});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
var from: u8 = 0;
|
|
302
|
-
while (from < self.options.node_count) : (from += 1) {
|
|
303
|
-
var to: u8 = 0;
|
|
304
|
-
while (to < self.options.node_count) : (to += 1) {
|
|
305
|
-
const path = .{ .source = from, .target = to };
|
|
306
|
-
if (self.is_clogged(path)) continue;
|
|
307
|
-
|
|
308
|
-
const queue = self.path_queue(path);
|
|
309
|
-
while (queue.peek()) |*data| {
|
|
310
|
-
if (data.expiry > self.ticks) break;
|
|
311
|
-
_ = queue.remove();
|
|
312
|
-
|
|
313
|
-
if (self.is_partitioned and
|
|
314
|
-
self.replicas_are_in_different_partitions(from, to))
|
|
315
|
-
{
|
|
316
|
-
self.stats[@enumToInt(PacketStatistics.dropped_due_to_partition)] += 1;
|
|
317
|
-
log.err("dropped packet (different partitions): from={} to={}", .{ from, to });
|
|
318
|
-
data.packet.deinit(path);
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (self.should_drop()) {
|
|
323
|
-
self.stats[@enumToInt(PacketStatistics.dropped)] += 1;
|
|
324
|
-
log.err("dropped packet from={} to={}.", .{ from, to });
|
|
325
|
-
data.packet.deinit(path);
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (to < self.options.replica_count and cluster_health[to] == .down) {
|
|
330
|
-
self.stats[@enumToInt(PacketStatistics.dropped_due_to_crash)] += 1;
|
|
331
|
-
log.err("dropped packet (destination is crashed): from={} to={}", .{ from, to });
|
|
332
|
-
data.packet.deinit(path);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (self.should_replay()) {
|
|
337
|
-
self.submit_packet(data.packet, data.callback, path);
|
|
338
|
-
|
|
339
|
-
log.debug("replayed packet from={} to={}", .{ from, to });
|
|
340
|
-
self.stats[@enumToInt(PacketStatistics.replay)] += 1;
|
|
341
|
-
|
|
342
|
-
data.callback(data.packet, path);
|
|
343
|
-
} else {
|
|
344
|
-
log.debug("delivering packet from={} to={}", .{ from, to });
|
|
345
|
-
data.callback(data.packet, path);
|
|
346
|
-
data.packet.deinit(path);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const reverse_path: Path = .{ .source = to, .target = from };
|
|
351
|
-
|
|
352
|
-
if (self.should_clog(reverse_path)) {
|
|
353
|
-
log.debug("reverse path clogged", .{});
|
|
354
|
-
const mean = @intToFloat(f64, self.options.path_clog_duration_mean);
|
|
355
|
-
const ticks = @floatToInt(u64, mean * self.prng.random().floatExp(f64));
|
|
356
|
-
self.clog_for(reverse_path, ticks);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
pub fn submit_packet(
|
|
363
|
-
self: *Self,
|
|
364
|
-
packet: Packet,
|
|
365
|
-
callback: fn (packet: Packet, path: Path) void,
|
|
366
|
-
path: Path,
|
|
367
|
-
) void {
|
|
368
|
-
const queue = self.path_queue(path);
|
|
369
|
-
var queue_length = queue.count();
|
|
370
|
-
if (queue_length + 1 > queue.capacity()) {
|
|
371
|
-
const index = self.prng.random().uintLessThanBiased(u64, queue_length);
|
|
372
|
-
const data = queue.removeIndex(index);
|
|
373
|
-
data.packet.deinit(path);
|
|
374
|
-
log.err("submit_packet: {} reached capacity, dropped packet={}", .{
|
|
375
|
-
path,
|
|
376
|
-
index,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
queue.add(.{
|
|
381
|
-
.expiry = self.ticks + self.one_way_delay(),
|
|
382
|
-
.packet = packet,
|
|
383
|
-
.callback = callback,
|
|
384
|
-
}) catch unreachable;
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
}
|