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 }