lib/machine/src/explore/fault.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const explore = @import("root.zig");
2 const fault = @import("../fault/root.zig");
3 const profile = @import("../profile/root.zig");
4 const std = @import("std");
5
6 pub fn Generator(comptime capacity_value: explore.FaultCapacity) type {
7 return struct {
8 seed: explore.Seed,
9 temporal: explore.TemporalPlan,
10 kinds: [capacity.kinds]fault.Kind,
11 kind_count: u8,
12 sequence: u16 = 0,
13 virtual_time_tick: u64,
14 active: ?fault.Kind = null,
15
16 const Self = @This();
17
18 pub const capacity: explore.FaultCapacity = capacity_value;
19 pub const Error: type = explore.GeneratorError;
20 pub const InitResult: type = explore.Generation(Self);
21 pub const Site: type = explore.ChoiceSite(
22 explore.FaultAlternative,
23 capacity.alternatives,
24 );
25 pub const NextResult: type = explore.Generation(Site);
26
27 pub fn init(
28 selected: profile.Profile,
29 seed: explore.Seed,
30 plan: explore.FaultPlan,
31 ) Error!InitResult {
32 try profile.validate(selected);
33 if (plan.temporal.end_tick < plan.temporal.start_tick or
34 plan.kinds.len == 0)
35 {
36 return error.InvalidPlan;
37 }
38 if (plan.temporal.steps == 0 or
39 plan.temporal.start_tick == plan.temporal.end_tick)
40 {
41 return .exhausted;
42 }
43 if (plan.temporal.steps > capacity.steps) {
44 return .{ .incomplete = .step_capacity };
45 }
46 if (plan.kinds.len > capacity.kinds) {
47 return .{ .incomplete = .kind_capacity };
48 }
49 if (plan.kinds.len + 1 > capacity.alternatives) {
50 return .{ .incomplete = .alternative_capacity };
51 }
52 try validateKinds(plan.kinds);
53 var kinds: [capacity.kinds]fault.Kind = undefined;
54 @memcpy(kinds[0..plan.kinds.len], plan.kinds);
55 return .{ .item = .{
56 .seed = seed,
57 .temporal = plan.temporal,
58 .kinds = kinds,
59 .kind_count = @intCast(plan.kinds.len),
60 .virtual_time_tick = plan.temporal.start_tick,
61 } };
62 }
63
64 pub fn next(self: *const Self) NextResult {
65 self.assertValid();
66 if (self.sequence == self.temporal.steps or
67 self.virtual_time_tick == self.temporal.end_tick)
68 {
69 return .exhausted;
70 }
71 var site = Site{
72 .id = self.siteId(),
73 .alternatives = undefined,
74 .count = 0,
75 .suggested = 0,
76 };
77 if (self.active) |kind| {
78 self.activeAlternatives(&site, kind);
79 } else {
80 self.healthyAlternatives(&site);
81 }
82 std.debug.assert(site.count > 0);
83 site.suggested = self.seed.suggest(.fault, self.sequence, site.count);
84 return .{ .item = site };
85 }
86
87 pub fn choose(
88 self: *Self,
89 site: Site,
90 index: u8,
91 ) Error!explore.GeneratedFault {
92 const current = switch (self.next()) {
93 .item => |value| value,
94 .exhausted, .incomplete => return error.StaleChoiceSite,
95 };
96 if (!sameSite(current, site)) return error.StaleChoiceSite;
97 if (index >= site.count) return error.ChoiceOutOfRange;
98 const selected = site.alternatives[index];
99 switch (selected.action) {
100 .healthy => self.active = null,
101 .inject => |kind| self.active = kind,
102 .persist => |kind| std.debug.assert(self.active.? == kind),
103 .recover => |kind| {
104 std.debug.assert(self.active.? == kind);
105 self.active = null;
106 },
107 }
108 self.virtual_time_tick = selected.virtual_time_tick;
109 self.sequence += 1;
110 self.assertValid();
111 return selected;
112 }
113
114 pub fn activeFault(self: *const Self) ?fault.Kind {
115 self.assertValid();
116 return self.active;
117 }
118
119 fn healthyAlternatives(self: *const Self, site: *Site) void {
120 self.addAt(site, self.longDelay(), null, .{ .healthy = {} });
121 for (self.kinds[0..self.kind_count]) |kind| {
122 self.addAt(site, 1, explore.faultOrigin(kind), .{ .inject = kind });
123 }
124 }
125
126 fn activeAlternatives(self: *const Self, site: *Site, kind: fault.Kind) void {
127 const persistent_delay = 1 + self.seed.word(.fault, self.sequence, 1) % 4;
128 self.addAt(
129 site,
130 persistent_delay,
131 explore.faultOrigin(kind),
132 .{ .persist = kind },
133 );
134 self.addAt(site, 1, explore.faultOrigin(kind), .{ .recover = kind });
135 }
136
137 fn addAt(
138 self: *const Self,
139 site: *Site,
140 delta: u64,
141 effect_origin: ?explore.Origin,
142 action: explore.FaultAction,
143 ) void {
144 const tick = self.futureTick(delta) orelse return;
145 std.debug.assert(site.count < site.alternatives.len);
146 site.alternatives[site.count] = .{
147 .choice_origin = explore.origin(.fault_choice),
148 .effect_origin = effect_origin,
149 .virtual_time_tick = tick,
150 .action = action,
151 };
152 site.count += 1;
153 }
154
155 fn futureTick(self: *const Self, delta: u64) ?u64 {
156 std.debug.assert(self.virtual_time_tick <= self.temporal.end_tick);
157 const remaining = self.temporal.end_tick - self.virtual_time_tick;
158 if (delta == 0 or delta > remaining) return null;
159 return self.virtual_time_tick + delta;
160 }
161
162 fn longDelay(self: *const Self) u64 {
163 return 4 + self.seed.word(.fault, self.sequence, 2) % 13;
164 }
165
166 fn siteId(self: *const Self) explore.SiteId {
167 return .{
168 .stream = .fault,
169 .sequence = self.sequence,
170 .virtual_time_tick = self.virtual_time_tick,
171 };
172 }
173
174 fn assertValid(self: *const Self) void {
175 std.debug.assert(self.kind_count > 0);
176 std.debug.assert(self.kind_count <= capacity.kinds);
177 std.debug.assert(self.sequence <= self.temporal.steps);
178 std.debug.assert(self.sequence <= capacity.steps);
179 std.debug.assert(self.virtual_time_tick >= self.temporal.start_tick);
180 std.debug.assert(self.virtual_time_tick <= self.temporal.end_tick);
181 }
182
183 fn sameSite(expected: Site, actual: Site) bool {
184 if (!std.meta.eql(expected.id, actual.id)) return false;
185 if (expected.count != actual.count) return false;
186 if (expected.suggested != actual.suggested) return false;
187 for (expected.values(), actual.values()) |left, right| {
188 if (!std.meta.eql(left, right)) return false;
189 }
190 return true;
191 }
192 };
193 }
194
195 fn validateKinds(kinds: []const fault.Kind) error{InvalidPlan}!void {
196 for (kinds, 0..) |kind, index| {
197 for (kinds[0..index]) |prior| {
198 if (kind == prior) return error.InvalidPlan;
199 }
200 }
201 }