tigerbeetle-node 0.5.0 → 0.6.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 +3 -4
- package/package.json +2 -2
- package/scripts/postinstall.sh +2 -2
- package/src/node.zig +19 -27
- package/src/tigerbeetle/scripts/benchmark.bat +46 -0
- package/src/tigerbeetle/scripts/install.sh +1 -1
- package/src/tigerbeetle/scripts/install_zig.bat +4 -4
- package/src/tigerbeetle/scripts/install_zig.sh +4 -2
- package/src/tigerbeetle/scripts/lint.zig +8 -2
- package/src/tigerbeetle/scripts/vopr.sh +2 -2
- package/src/tigerbeetle/src/benchmark.zig +10 -12
- package/src/tigerbeetle/src/cli.zig +43 -20
- package/src/tigerbeetle/src/config.zig +26 -11
- package/src/tigerbeetle/src/demo.zig +119 -97
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +5 -3
- package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +2 -3
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
- package/src/tigerbeetle/src/demo_04_create_transfers_two_phase_commit.zig +5 -3
- package/src/tigerbeetle/src/demo_05_accept_transfers.zig +5 -3
- package/src/tigerbeetle/src/demo_06_reject_transfers.zig +5 -3
- package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +2 -3
- package/src/tigerbeetle/src/io/benchmark.zig +213 -0
- package/src/tigerbeetle/src/{io_darwin.zig → io/darwin.zig} +259 -167
- package/src/tigerbeetle/src/io/linux.zig +1038 -0
- package/src/tigerbeetle/src/io/test.zig +643 -0
- package/src/tigerbeetle/src/io/windows.zig +1161 -0
- package/src/tigerbeetle/src/io.zig +9 -1328
- package/src/tigerbeetle/src/main.zig +28 -15
- package/src/tigerbeetle/src/message_bus.zig +78 -107
- package/src/tigerbeetle/src/message_pool.zig +65 -58
- package/src/tigerbeetle/src/ring_buffer.zig +7 -0
- package/src/tigerbeetle/src/simulator.zig +44 -40
- package/src/tigerbeetle/src/state_machine.zig +58 -27
- package/src/tigerbeetle/src/storage.zig +7 -234
- package/src/tigerbeetle/src/test/cluster.zig +5 -8
- package/src/tigerbeetle/src/test/message_bus.zig +10 -9
- package/src/tigerbeetle/src/test/network.zig +16 -19
- package/src/tigerbeetle/src/test/packet_simulator.zig +32 -29
- package/src/tigerbeetle/src/test/state_checker.zig +4 -3
- package/src/tigerbeetle/src/test/state_machine.zig +4 -0
- package/src/tigerbeetle/src/test/storage.zig +23 -19
- package/src/tigerbeetle/src/test/time.zig +2 -2
- package/src/tigerbeetle/src/tigerbeetle.zig +8 -128
- package/src/tigerbeetle/src/time.zig +61 -13
- package/src/tigerbeetle/src/vsr/client.zig +23 -37
- package/src/tigerbeetle/src/vsr/clock.zig +27 -44
- package/src/tigerbeetle/src/vsr/journal.zig +9 -12
- package/src/tigerbeetle/src/vsr/marzullo.zig +6 -3
- package/src/tigerbeetle/src/vsr/replica.zig +184 -204
- package/src/tigerbeetle/src/vsr.zig +287 -25
- package/src/translate.zig +55 -55
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const builtin = @import("builtin");
|
|
2
3
|
const assert = std.debug.assert;
|
|
3
4
|
const mem = std.mem;
|
|
4
5
|
|
|
@@ -16,15 +17,55 @@ comptime {
|
|
|
16
17
|
/// message to be shifted to make space for 0 padding to vsr.sector_ceil.
|
|
17
18
|
const message_size_max_padded = config.message_size_max + config.sector_size;
|
|
18
19
|
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
|
|
20
|
+
/// The number of full-sized messages allocated at initialization by the replica message pool.
|
|
21
|
+
/// There must be enough messages to ensure that the replica can always progress, to avoid deadlock.
|
|
22
|
+
pub const messages_max_replica = messages_max: {
|
|
23
|
+
var sum: usize = 0;
|
|
24
|
+
|
|
25
|
+
sum += config.io_depth_read + config.io_depth_write; // Journal I/O
|
|
26
|
+
sum += config.clients_max; // Replica.client_table
|
|
27
|
+
sum += 1; // Replica.loopback_queue
|
|
28
|
+
sum += config.pipelining_max; // Replica.pipeline
|
|
29
|
+
sum += config.replicas_max; // Replica.do_view_change_from_all_replicas quorum (all others are bitsets)
|
|
30
|
+
sum += config.connections_max; // Connection.recv_message
|
|
31
|
+
sum += config.connections_max * config.connection_send_queue_max_replica; // Connection.send_queue
|
|
32
|
+
sum += 1; // Handle bursts (e.g. Connection.parse_message)
|
|
33
|
+
// Handle Replica.commit_op's reply:
|
|
34
|
+
// (This is separate from the burst +1 because they may occur concurrently).
|
|
35
|
+
sum += 1;
|
|
36
|
+
sum += 20; // TODO Our network simulator allows up to 20 messages for path_capacity_max.
|
|
37
|
+
|
|
38
|
+
break :messages_max sum;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/// The number of full-sized messages allocated at initialization by the client message pool.
|
|
42
|
+
pub const messages_max_client = messages_max: {
|
|
43
|
+
var sum: usize = 0;
|
|
44
|
+
|
|
45
|
+
sum += config.replicas_max; // Connection.recv_message
|
|
46
|
+
sum += config.replicas_max * config.connection_send_queue_max_client; // Connection.send_queue
|
|
47
|
+
sum += config.client_request_queue_max; // Client.request_queue
|
|
48
|
+
// Handle bursts (e.g. Connection.parse_message, or sending a ping when the send queue is full).
|
|
49
|
+
sum += 1;
|
|
50
|
+
sum += 20; // TODO Our network simulator allows up to 20 messages for path_capacity_max.
|
|
51
|
+
|
|
52
|
+
break :messages_max sum;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
comptime {
|
|
56
|
+
// These conditions are necessary (but not sufficient) to prevent deadlocks.
|
|
57
|
+
assert(messages_max_replica > config.replicas_max);
|
|
58
|
+
assert(messages_max_client > config.client_request_queue_max);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// A pool of reference-counted Messages, memory for which is allocated only once during
|
|
62
|
+
/// initialization and reused thereafter. The messages_max values determine the size of this pool.
|
|
22
63
|
pub const MessagePool = struct {
|
|
23
64
|
pub const Message = struct {
|
|
24
65
|
// TODO: replace this with a header() function to save memory
|
|
25
66
|
header: *Header,
|
|
26
|
-
///
|
|
27
|
-
///
|
|
67
|
+
/// This buffer is aligned to config.sector_size and casting to that alignment in order
|
|
68
|
+
/// to perform Direct I/O is safe.
|
|
28
69
|
buffer: []u8,
|
|
29
70
|
references: u32 = 0,
|
|
30
71
|
next: ?*Message,
|
|
@@ -38,27 +79,23 @@ pub const MessagePool = struct {
|
|
|
38
79
|
pub fn body(message: *Message) []u8 {
|
|
39
80
|
return message.buffer[@sizeOf(Header)..message.header.size];
|
|
40
81
|
}
|
|
41
|
-
|
|
42
|
-
fn header_only(message: Message) bool {
|
|
43
|
-
const ret = message.buffer.len == @sizeOf(Header);
|
|
44
|
-
assert(ret or message.buffer.len == message_size_max_padded);
|
|
45
|
-
return ret;
|
|
46
|
-
}
|
|
47
82
|
};
|
|
48
83
|
|
|
49
84
|
/// List of currently unused messages of message_size_max_padded
|
|
50
85
|
free_list: ?*Message,
|
|
51
|
-
/// List of currently usused header-sized messages
|
|
52
|
-
header_only_free_list: ?*Message,
|
|
53
86
|
|
|
54
|
-
pub fn init(allocator:
|
|
87
|
+
pub fn init(allocator: mem.Allocator, process_type: vsr.ProcessType) error{OutOfMemory}!MessagePool {
|
|
88
|
+
const messages_max: usize = switch (process_type) {
|
|
89
|
+
.replica => messages_max_replica,
|
|
90
|
+
.client => messages_max_client,
|
|
91
|
+
};
|
|
92
|
+
|
|
55
93
|
var ret: MessagePool = .{
|
|
56
94
|
.free_list = null,
|
|
57
|
-
.header_only_free_list = null,
|
|
58
95
|
};
|
|
59
96
|
{
|
|
60
97
|
var i: usize = 0;
|
|
61
|
-
while (i <
|
|
98
|
+
while (i < messages_max) : (i += 1) {
|
|
62
99
|
const buffer = try allocator.allocAdvanced(
|
|
63
100
|
u8,
|
|
64
101
|
config.sector_size,
|
|
@@ -74,59 +111,29 @@ pub const MessagePool = struct {
|
|
|
74
111
|
ret.free_list = message;
|
|
75
112
|
}
|
|
76
113
|
}
|
|
77
|
-
{
|
|
78
|
-
var i: usize = 0;
|
|
79
|
-
while (i < config.message_bus_headers_max) : (i += 1) {
|
|
80
|
-
const header = try allocator.create(Header);
|
|
81
|
-
const message = try allocator.create(Message);
|
|
82
|
-
message.* = .{
|
|
83
|
-
.header = header,
|
|
84
|
-
.buffer = mem.asBytes(header),
|
|
85
|
-
.next = ret.header_only_free_list,
|
|
86
|
-
};
|
|
87
|
-
ret.header_only_free_list = message;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
114
|
|
|
91
115
|
return ret;
|
|
92
116
|
}
|
|
93
117
|
|
|
94
|
-
/// Get an unused message with a buffer of config.message_size_max.
|
|
95
|
-
///
|
|
96
|
-
pub fn get_message(pool: *MessagePool)
|
|
97
|
-
const
|
|
98
|
-
pool.free_list =
|
|
99
|
-
|
|
100
|
-
assert(
|
|
101
|
-
assert(ret.references == 0);
|
|
102
|
-
ret.references = 1;
|
|
103
|
-
return ret;
|
|
104
|
-
}
|
|
118
|
+
/// Get an unused message with a buffer of config.message_size_max.
|
|
119
|
+
/// The returned message has exactly one reference.
|
|
120
|
+
pub fn get_message(pool: *MessagePool) *Message {
|
|
121
|
+
const message = pool.free_list.?;
|
|
122
|
+
pool.free_list = message.next;
|
|
123
|
+
message.next = null;
|
|
124
|
+
assert(message.references == 0);
|
|
105
125
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
pub fn get_header_only_message(pool: *MessagePool) ?*Message {
|
|
109
|
-
const ret = pool.header_only_free_list orelse return null;
|
|
110
|
-
pool.header_only_free_list = ret.next;
|
|
111
|
-
ret.next = null;
|
|
112
|
-
assert(ret.header_only());
|
|
113
|
-
assert(ret.references == 0);
|
|
114
|
-
ret.references = 1;
|
|
115
|
-
return ret;
|
|
126
|
+
message.references = 1;
|
|
127
|
+
return message;
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
/// Decrement the reference count of the message, possibly freeing it.
|
|
119
131
|
pub fn unref(pool: *MessagePool, message: *Message) void {
|
|
120
132
|
message.references -= 1;
|
|
121
133
|
if (message.references == 0) {
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
pool.header_only_free_list = message;
|
|
126
|
-
} else {
|
|
127
|
-
message.next = pool.free_list;
|
|
128
|
-
pool.free_list = message;
|
|
129
|
-
}
|
|
134
|
+
if (builtin.mode == .Debug) mem.set(u8, message.buffer, undefined);
|
|
135
|
+
message.next = pool.free_list;
|
|
136
|
+
pool.free_list = message;
|
|
130
137
|
}
|
|
131
138
|
}
|
|
132
139
|
};
|
|
@@ -76,6 +76,13 @@ pub fn RingBuffer(comptime T: type, comptime size: usize) type {
|
|
|
76
76
|
self.advance_tail();
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/// Add an element to a RingBuffer, and assert that the capacity is sufficient.
|
|
80
|
+
pub fn push_assume_capacity(self: *Self, item: T) void {
|
|
81
|
+
self.push(item) catch |err| switch (err) {
|
|
82
|
+
error.NoSpaceLeft => unreachable,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
79
86
|
/// Remove and return the next item, if any.
|
|
80
87
|
pub fn pop(self: *Self) ?T {
|
|
81
88
|
const result = self.head() orelse return null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const builtin = @import("builtin");
|
|
2
3
|
const assert = std.debug.assert;
|
|
3
4
|
const mem = std.mem;
|
|
4
5
|
|
|
@@ -17,7 +18,7 @@ const output = std.log.scoped(.state_checker);
|
|
|
17
18
|
|
|
18
19
|
/// Set this to `false` if you want to see how literally everything works.
|
|
19
20
|
/// This will run much slower but will trace all logic across the cluster.
|
|
20
|
-
const log_state_transitions_only =
|
|
21
|
+
const log_state_transitions_only = builtin.mode != .Debug;
|
|
21
22
|
|
|
22
23
|
/// You can fine tune your log levels even further (debug/info/notice/warn/err/crit/alert/emerg):
|
|
23
24
|
pub const log_level: std.log.Level = if (log_state_transitions_only) .info else .debug;
|
|
@@ -40,13 +41,13 @@ pub fn main() !void {
|
|
|
40
41
|
break :seed_from_arg parse_seed(arg_two);
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
if (
|
|
44
|
+
if (builtin.mode == .ReleaseFast or builtin.mode == .ReleaseSmall) {
|
|
44
45
|
// We do not support ReleaseFast or ReleaseSmall because they disable assertions.
|
|
45
46
|
@panic("the simulator must be run with -OReleaseSafe");
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
if (seed == seed_random) {
|
|
49
|
-
if (
|
|
50
|
+
if (builtin.mode != .ReleaseSafe) {
|
|
50
51
|
// If no seed is provided, than Debug is too slow and ReleaseSafe is much faster.
|
|
51
52
|
@panic("no seed provided: the simulator must be run with -OReleaseSafe");
|
|
52
53
|
}
|
|
@@ -56,53 +57,53 @@ pub fn main() !void {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
var prng = std.rand.DefaultPrng.init(seed);
|
|
59
|
-
const random =
|
|
60
|
+
const random = prng.random();
|
|
60
61
|
|
|
61
|
-
const replica_count = 1 +
|
|
62
|
-
const client_count = 1 +
|
|
62
|
+
const replica_count = 1 + random.uintLessThan(u8, config.replicas_max);
|
|
63
|
+
const client_count = 1 + random.uintLessThan(u8, config.clients_max);
|
|
63
64
|
const node_count = replica_count + client_count;
|
|
64
65
|
|
|
65
66
|
const ticks_max = 100_000_000;
|
|
66
67
|
const transitions_max = config.journal_size_max / config.message_size_max;
|
|
67
|
-
const request_probability = 1 +
|
|
68
|
-
const idle_on_probability =
|
|
69
|
-
const idle_off_probability = 10 +
|
|
68
|
+
const request_probability = 1 + random.uintLessThan(u8, 99);
|
|
69
|
+
const idle_on_probability = random.uintLessThan(u8, 20);
|
|
70
|
+
const idle_off_probability = 10 + random.uintLessThan(u8, 10);
|
|
70
71
|
|
|
71
|
-
cluster = try Cluster.create(allocator,
|
|
72
|
+
cluster = try Cluster.create(allocator, random, .{
|
|
72
73
|
.cluster = 0,
|
|
73
74
|
.replica_count = replica_count,
|
|
74
75
|
.client_count = client_count,
|
|
75
|
-
.seed =
|
|
76
|
+
.seed = random.int(u64),
|
|
76
77
|
.network_options = .{
|
|
77
78
|
.packet_simulator_options = .{
|
|
78
79
|
.replica_count = replica_count,
|
|
79
80
|
.client_count = client_count,
|
|
80
81
|
.node_count = node_count,
|
|
81
82
|
|
|
82
|
-
.seed =
|
|
83
|
-
.one_way_delay_mean = 3 +
|
|
84
|
-
.one_way_delay_min =
|
|
85
|
-
.packet_loss_probability =
|
|
86
|
-
.path_maximum_capacity =
|
|
87
|
-
.path_clog_duration_mean =
|
|
88
|
-
.path_clog_probability =
|
|
89
|
-
.packet_replay_probability =
|
|
83
|
+
.seed = random.int(u64),
|
|
84
|
+
.one_way_delay_mean = 3 + random.uintLessThan(u16, 10),
|
|
85
|
+
.one_way_delay_min = random.uintLessThan(u16, 3),
|
|
86
|
+
.packet_loss_probability = random.uintLessThan(u8, 30),
|
|
87
|
+
.path_maximum_capacity = 2 + random.uintLessThan(u8, 19),
|
|
88
|
+
.path_clog_duration_mean = random.uintLessThan(u16, 500),
|
|
89
|
+
.path_clog_probability = random.uintLessThan(u8, 2),
|
|
90
|
+
.packet_replay_probability = random.uintLessThan(u8, 50),
|
|
90
91
|
|
|
91
92
|
.partition_mode = random_partition_mode(random),
|
|
92
|
-
.partition_probability =
|
|
93
|
-
.unpartition_probability = 1 +
|
|
94
|
-
.partition_stability = 100 +
|
|
95
|
-
.unpartition_stability =
|
|
93
|
+
.partition_probability = random.uintLessThan(u8, 3),
|
|
94
|
+
.unpartition_probability = 1 + random.uintLessThan(u8, 10),
|
|
95
|
+
.partition_stability = 100 + random.uintLessThan(u32, 100),
|
|
96
|
+
.unpartition_stability = random.uintLessThan(u32, 20),
|
|
96
97
|
},
|
|
97
98
|
},
|
|
98
99
|
.storage_options = .{
|
|
99
|
-
.seed =
|
|
100
|
-
.read_latency_min =
|
|
101
|
-
.read_latency_mean = 3 +
|
|
102
|
-
.write_latency_min =
|
|
103
|
-
.write_latency_mean = 3 +
|
|
104
|
-
.read_fault_probability =
|
|
105
|
-
.write_fault_probability =
|
|
100
|
+
.seed = random.int(u64),
|
|
101
|
+
.read_latency_min = random.uintLessThan(u16, 3),
|
|
102
|
+
.read_latency_mean = 3 + random.uintLessThan(u16, 10),
|
|
103
|
+
.write_latency_min = random.uintLessThan(u16, 3),
|
|
104
|
+
.write_latency_mean = 3 + random.uintLessThan(u16, 10),
|
|
105
|
+
.read_fault_probability = random.uintLessThan(u8, 10),
|
|
106
|
+
.write_fault_probability = random.uintLessThan(u8, 10),
|
|
106
107
|
},
|
|
107
108
|
});
|
|
108
109
|
defer cluster.destroy();
|
|
@@ -206,23 +207,23 @@ pub fn main() !void {
|
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
if (cluster.state_checker.transitions < transitions_max) {
|
|
209
|
-
output.
|
|
210
|
+
output.err("you can reproduce this failure with seed={}", .{seed});
|
|
210
211
|
@panic("unable to complete transitions_max before ticks_max");
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
assert(cluster.state_checker.convergence());
|
|
214
215
|
|
|
215
|
-
output.info("\n PASSED", .{});
|
|
216
|
+
output.info("\n PASSED ({} ticks)", .{tick});
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
/// Returns true, `p` percent of the time, else false.
|
|
219
|
-
fn chance(random:
|
|
220
|
+
fn chance(random: std.rand.Random, p: u8) bool {
|
|
220
221
|
assert(p <= 100);
|
|
221
222
|
return random.uintLessThan(u8, 100) < p;
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
/// Returns the next argument for the simulator or null (if none available)
|
|
225
|
-
fn args_next(args: *std.process.ArgIterator, allocator:
|
|
226
|
+
fn args_next(args: *std.process.ArgIterator, allocator: std.mem.Allocator) ?[:0]const u8 {
|
|
226
227
|
const err_or_bytes = args.next(allocator) orelse return null;
|
|
227
228
|
return err_or_bytes catch @panic("Unable to extract next value from args");
|
|
228
229
|
}
|
|
@@ -232,7 +233,7 @@ fn on_change_replica(replica: *Replica) void {
|
|
|
232
233
|
cluster.state_checker.check_state(replica.replica);
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
fn send_request(random:
|
|
236
|
+
fn send_request(random: std.rand.Random) bool {
|
|
236
237
|
const client_index = random.uintLessThan(u8, cluster.options.client_count);
|
|
237
238
|
|
|
238
239
|
const client = &cluster.clients[client_index];
|
|
@@ -243,7 +244,7 @@ fn send_request(random: *std.rand.Random) bool {
|
|
|
243
244
|
if (client.request_queue.full()) return false;
|
|
244
245
|
if (checker_request_queue.full()) return false;
|
|
245
246
|
|
|
246
|
-
const message = client.get_message()
|
|
247
|
+
const message = client.get_message();
|
|
247
248
|
defer client.unref(message);
|
|
248
249
|
|
|
249
250
|
const body_size_max = config.message_size_max - @sizeOf(Header);
|
|
@@ -264,7 +265,7 @@ fn send_request(random: *std.rand.Random) bool {
|
|
|
264
265
|
// While hashing the client ID with the request body prevents input collisions across clients,
|
|
265
266
|
// it's still possible for the same client to generate the same body, and therefore input hash.
|
|
266
267
|
const client_input = StateMachine.hash(client.id, body);
|
|
267
|
-
checker_request_queue.
|
|
268
|
+
checker_request_queue.push_assume_capacity(client_input);
|
|
268
269
|
std.log.scoped(.test_client).debug("client {} sending input={x}", .{
|
|
269
270
|
client_index,
|
|
270
271
|
client_input,
|
|
@@ -280,11 +281,14 @@ fn client_callback(
|
|
|
280
281
|
operation: StateMachine.Operation,
|
|
281
282
|
results: Client.Error![]const u8,
|
|
282
283
|
) void {
|
|
284
|
+
_ = operation;
|
|
285
|
+
_ = results catch unreachable;
|
|
286
|
+
|
|
283
287
|
assert(user_data == 0);
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
/// Returns a random partitioning mode, excluding .custom
|
|
287
|
-
fn random_partition_mode(random:
|
|
291
|
+
fn random_partition_mode(random: std.rand.Random) PartitionMode {
|
|
288
292
|
const typeInfo = @typeInfo(PartitionMode).Enum;
|
|
289
293
|
var enumAsInt = random.uintAtMost(typeInfo.tag_type, typeInfo.fields.len - 2);
|
|
290
294
|
if (enumAsInt >= @enumToInt(PartitionMode.custom)) enumAsInt += 1;
|
|
@@ -310,8 +314,8 @@ pub fn log(
|
|
|
310
314
|
const prefix = if (log_state_transitions_only) "" else prefix_default;
|
|
311
315
|
|
|
312
316
|
// Print the message to stdout, silently ignoring any errors
|
|
313
|
-
const held = std.debug.getStderrMutex().acquire();
|
|
314
|
-
defer held.release();
|
|
315
317
|
const stderr = std.io.getStdErr().writer();
|
|
318
|
+
std.debug.getStderrMutex().lock();
|
|
319
|
+
defer std.debug.getStderrMutex().unlock();
|
|
316
320
|
nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
|
|
317
321
|
}
|
|
@@ -2,7 +2,25 @@ const std = @import("std");
|
|
|
2
2
|
const assert = std.debug.assert;
|
|
3
3
|
const log = std.log.scoped(.state_machine);
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const tb = @import("tigerbeetle.zig");
|
|
6
|
+
|
|
7
|
+
const Account = tb.Account;
|
|
8
|
+
const AccountFlags = tb.AccountFlags;
|
|
9
|
+
|
|
10
|
+
const Transfer = tb.Transfer;
|
|
11
|
+
const TransferFlags = tb.TransferFlags;
|
|
12
|
+
|
|
13
|
+
const Commit = tb.Commit;
|
|
14
|
+
const CommitFlags = tb.CommitFlags;
|
|
15
|
+
|
|
16
|
+
const CreateAccountsResult = tb.CreateAccountsResult;
|
|
17
|
+
const CreateTransfersResult = tb.CreateTransfersResult;
|
|
18
|
+
const CommitTransfersResult = tb.CommitTransfersResult;
|
|
19
|
+
|
|
20
|
+
const CreateAccountResult = tb.CreateAccountResult;
|
|
21
|
+
const CreateTransferResult = tb.CreateTransferResult;
|
|
22
|
+
const CommitTransferResult = tb.CommitTransferResult;
|
|
23
|
+
const LookupAccountResult = tb.LookupAccountResult;
|
|
6
24
|
|
|
7
25
|
const HashMapAccounts = std.AutoHashMap(u128, Account);
|
|
8
26
|
const HashMapTransfers = std.AutoHashMap(u128, Transfer);
|
|
@@ -21,13 +39,9 @@ pub const StateMachine = struct {
|
|
|
21
39
|
commit_transfers,
|
|
22
40
|
lookup_accounts,
|
|
23
41
|
lookup_transfers,
|
|
24
|
-
|
|
25
|
-
pub fn jsonStringify(self: Command, options: StringifyOptions, writer: anytype) !void {
|
|
26
|
-
try std.fmt.format(writer, "\"{}\"", .{@tagName(self)});
|
|
27
|
-
}
|
|
28
42
|
};
|
|
29
43
|
|
|
30
|
-
allocator:
|
|
44
|
+
allocator: std.mem.Allocator,
|
|
31
45
|
prepare_timestamp: u64,
|
|
32
46
|
commit_timestamp: u64,
|
|
33
47
|
accounts: HashMapAccounts,
|
|
@@ -35,22 +49,22 @@ pub const StateMachine = struct {
|
|
|
35
49
|
commits: HashMapCommits,
|
|
36
50
|
|
|
37
51
|
pub fn init(
|
|
38
|
-
allocator:
|
|
52
|
+
allocator: std.mem.Allocator,
|
|
39
53
|
accounts_max: usize,
|
|
40
54
|
transfers_max: usize,
|
|
41
55
|
commits_max: usize,
|
|
42
56
|
) !StateMachine {
|
|
43
57
|
var accounts = HashMapAccounts.init(allocator);
|
|
44
58
|
errdefer accounts.deinit();
|
|
45
|
-
try accounts.
|
|
59
|
+
try accounts.ensureTotalCapacity(@intCast(u32, accounts_max));
|
|
46
60
|
|
|
47
61
|
var transfers = HashMapTransfers.init(allocator);
|
|
48
62
|
errdefer transfers.deinit();
|
|
49
|
-
try transfers.
|
|
63
|
+
try transfers.ensureTotalCapacity(@intCast(u32, transfers_max));
|
|
50
64
|
|
|
51
65
|
var commits = HashMapCommits.init(allocator);
|
|
52
66
|
errdefer commits.deinit();
|
|
53
|
-
try commits.
|
|
67
|
+
try commits.ensureTotalCapacity(@intCast(u32, commits_max));
|
|
54
68
|
|
|
55
69
|
// TODO After recovery, set prepare_timestamp max(wall clock, op timestamp).
|
|
56
70
|
// TODO After recovery, set commit_timestamp max(wall clock, commit timestamp).
|
|
@@ -140,6 +154,8 @@ pub const StateMachine = struct {
|
|
|
140
154
|
input: []const u8,
|
|
141
155
|
output: []u8,
|
|
142
156
|
) usize {
|
|
157
|
+
_ = client;
|
|
158
|
+
|
|
143
159
|
return switch (operation) {
|
|
144
160
|
.init => unreachable,
|
|
145
161
|
.register => 0,
|
|
@@ -263,7 +279,7 @@ pub const StateMachine = struct {
|
|
|
263
279
|
const output_len = @divFloor(output.len, @sizeOf(Account)) * @sizeOf(Account);
|
|
264
280
|
const results = std.mem.bytesAsSlice(Account, output[0..output_len]);
|
|
265
281
|
var results_count: usize = 0;
|
|
266
|
-
for (batch) |id
|
|
282
|
+
for (batch) |id| {
|
|
267
283
|
if (self.get_account(id)) |result| {
|
|
268
284
|
results[results_count] = result.*;
|
|
269
285
|
results_count += 1;
|
|
@@ -277,7 +293,7 @@ pub const StateMachine = struct {
|
|
|
277
293
|
const output_len = @divFloor(output.len, @sizeOf(Transfer)) * @sizeOf(Transfer);
|
|
278
294
|
const results = std.mem.bytesAsSlice(Transfer, output[0..output_len]);
|
|
279
295
|
var results_count: usize = 0;
|
|
280
|
-
for (batch) |id
|
|
296
|
+
for (batch) |id| {
|
|
281
297
|
if (self.get_transfer(id)) |result| {
|
|
282
298
|
results[results_count] = result.*;
|
|
283
299
|
results_count += 1;
|
|
@@ -538,7 +554,8 @@ const testing = std.testing;
|
|
|
538
554
|
test "create/lookup accounts" {
|
|
539
555
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
540
556
|
defer arena.deinit();
|
|
541
|
-
|
|
557
|
+
|
|
558
|
+
const allocator = arena.allocator();
|
|
542
559
|
|
|
543
560
|
const Vector = struct { result: CreateAccountResult, object: Account };
|
|
544
561
|
|
|
@@ -693,7 +710,8 @@ test "create/lookup accounts" {
|
|
|
693
710
|
test "linked accounts" {
|
|
694
711
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
695
712
|
defer arena.deinit();
|
|
696
|
-
|
|
713
|
+
|
|
714
|
+
const allocator = arena.allocator();
|
|
697
715
|
|
|
698
716
|
const accounts_max = 5;
|
|
699
717
|
const transfers_max = 0;
|
|
@@ -733,7 +751,7 @@ test "linked accounts" {
|
|
|
733
751
|
std.mem.zeroInit(Account, .{ .id = 3 }),
|
|
734
752
|
};
|
|
735
753
|
|
|
736
|
-
var state_machine = try StateMachine.init(allocator,
|
|
754
|
+
var state_machine = try StateMachine.init(allocator, accounts_max, transfers_max, commits_max);
|
|
737
755
|
defer state_machine.deinit();
|
|
738
756
|
|
|
739
757
|
const input = std.mem.asBytes(&accounts);
|
|
@@ -774,7 +792,8 @@ test "linked accounts" {
|
|
|
774
792
|
test "create/lookup/rollback transfers" {
|
|
775
793
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
776
794
|
defer arena.deinit();
|
|
777
|
-
|
|
795
|
+
|
|
796
|
+
const allocator = arena.allocator();
|
|
778
797
|
|
|
779
798
|
var accounts = [_]Account{
|
|
780
799
|
std.mem.zeroInit(Account, .{ .id = 1 }),
|
|
@@ -795,10 +814,12 @@ test "create/lookup/rollback transfers" {
|
|
|
795
814
|
|
|
796
815
|
state_machine.prepare(0, .create_accounts, input);
|
|
797
816
|
const size = state_machine.commit(0, .create_accounts, input, output);
|
|
798
|
-
const results = std.mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
|
|
799
817
|
|
|
800
|
-
|
|
801
|
-
|
|
818
|
+
const errors = std.mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
|
|
819
|
+
try testing.expectEqual(@as(usize, 0), errors.len);
|
|
820
|
+
|
|
821
|
+
for (accounts) |account| {
|
|
822
|
+
try testing.expectEqual(account, state_machine.get_account(account.id).?.*);
|
|
802
823
|
}
|
|
803
824
|
|
|
804
825
|
const Vector = struct { result: CreateTransferResult, object: Transfer };
|
|
@@ -1088,7 +1109,8 @@ test "create/lookup/rollback transfers" {
|
|
|
1088
1109
|
test "create/lookup/rollback commits" {
|
|
1089
1110
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
1090
1111
|
defer arena.deinit();
|
|
1091
|
-
|
|
1112
|
+
|
|
1113
|
+
const allocator = arena.allocator();
|
|
1092
1114
|
|
|
1093
1115
|
const Vector = struct { result: CommitTransferResult, object: Commit };
|
|
1094
1116
|
|
|
@@ -1171,10 +1193,13 @@ test "create/lookup/rollback commits" {
|
|
|
1171
1193
|
// Accounts:
|
|
1172
1194
|
state_machine.prepare(0, .create_accounts, input);
|
|
1173
1195
|
const size = state_machine.commit(0, .create_accounts, input, output);
|
|
1174
|
-
|
|
1196
|
+
{
|
|
1197
|
+
const errors = std.mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
|
|
1198
|
+
try testing.expectEqual(@as(usize, 0), errors.len);
|
|
1199
|
+
}
|
|
1175
1200
|
|
|
1176
|
-
for (accounts) |account
|
|
1177
|
-
try testing.expectEqual(
|
|
1201
|
+
for (accounts) |account| {
|
|
1202
|
+
try testing.expectEqual(account, state_machine.get_account(account.id).?.*);
|
|
1178
1203
|
}
|
|
1179
1204
|
|
|
1180
1205
|
// Transfers:
|
|
@@ -1182,11 +1207,17 @@ test "create/lookup/rollback commits" {
|
|
|
1182
1207
|
const output_transfers = try allocator.alloc(u8, 4096);
|
|
1183
1208
|
|
|
1184
1209
|
state_machine.prepare(0, .create_transfers, object_transfers);
|
|
1185
|
-
const size_transfers = state_machine.commit(
|
|
1186
|
-
|
|
1210
|
+
const size_transfers = state_machine.commit(
|
|
1211
|
+
0,
|
|
1212
|
+
.create_transfers,
|
|
1213
|
+
object_transfers,
|
|
1214
|
+
output_transfers,
|
|
1215
|
+
);
|
|
1216
|
+
const errors = std.mem.bytesAsSlice(CreateTransfersResult, output_transfers[0..size_transfers]);
|
|
1217
|
+
try testing.expectEqual(@as(usize, 0), errors.len);
|
|
1187
1218
|
|
|
1188
|
-
for (transfers) |transfer
|
|
1189
|
-
try testing.expectEqual(
|
|
1219
|
+
for (transfers) |transfer| {
|
|
1220
|
+
try testing.expectEqual(transfer, state_machine.get_transfer(transfer.id).?.*);
|
|
1190
1221
|
}
|
|
1191
1222
|
|
|
1192
1223
|
// Commits:
|