lib/machine/src/world/live.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const fabric = @import("../fabric/root.zig");
  2 const instance = @import("../instance/root.zig");
  3 const moment = @import("moment.zig");
  4 const os = @import("os");
  5 const restore = @import("restore.zig");
  6 const std = @import("std");
  7 
  8 const LiveOwnerError = error{
  9     ActivationExitMismatch,
 10     MachineNodeUnavailable,
 11     MachinePhaseMismatch,
 12     QuiescenceExitMismatch,
 13 };
 14 
 15 pub const Error = fabric.Error ||
 16     instance.AcknowledgeError ||
 17     instance.DeliveryError ||
 18     instance.EventBatchError ||
 19     instance.EventError ||
 20     instance.QuiescenceReceiptError ||
 21     instance.ReactivateError ||
 22     instance.RunError ||
 23     moment.Error ||
 24     os.abi.wire.FenceError ||
 25     restore.StableError ||
 26     LiveOwnerError;
 27 
 28 /// What an activation yields: the node now awake and every event it emitted on the
 29 /// way to its ready point. A caller reads this to see which node woke and what it
 30 /// said on the way up.
 31 pub const Activated = struct {
 32     node: fabric.NodeId,
 33     events: instance.EventBatch,
 34 };
 35 
 36 /// What a settled turn yields: two ledger transitions record the admission and the
 37 /// settlement, a moment marks where the world stands once both are in, the guest
 38 /// events travel with them, and a quiescence receipt shows that the guest came to
 39 /// its cooperative stop. A caller reads this to see everything one settled turn
 40 /// produced.
 41 pub const Advanced = struct {
 42     admission: fabric.Transition,
 43     settlement: fabric.Transition,
 44     moment: moment.Moment,
 45     events: instance.EventBatch,
 46     receipt: instance.QuiescenceReceipt,
 47 };
 48 
 49 /// A named failure, carrying the node involved whenever the code can name one.
 50 pub const Rejection = struct {
 51     node: ?fabric.NodeId,
 52     failure: Error,
 53 };
 54 
 55 /// The activation outcome, so a caller switches on this to decide whether the world
 56 /// can keep going. A world that rejected the call still holds the state it had before
 57 /// and stays usable. A world marked invalid failed once mutation was already under
 58 /// way, so its live instances were destroyed and it cannot continue.
 59 pub const ActivateResult = union(enum) {
 60     activated: Activated,
 61     rejected: Rejection,
 62     invalidated: Rejection,
 63 };
 64 
 65 /// The outcome of a turn obeys the contract `ActivateResult` obeys, so a caller
 66 /// switches on this the same way it switches on an activation outcome. Rejected
 67 /// worlds continue, and invalidated worlds are gone.
 68 pub const TurnResult = union(enum) {
 69     advanced: Advanced,
 70     rejected: Rejection,
 71     invalidated: Rejection,
 72 };
 73 
 74 /// Brings one restored node up and carries it as far as its ready point, so a caller
 75 /// activates a node once after restore, before it can take turns. The fence carries
 76 /// the authority for this activation, so a machine waiting to be reactivated takes
 77 /// the fence as its own, and the batch of events it emits has to verify under that
 78 /// same fence. The call demands a stable world, and the node's machine has to be
 79 /// either booting or waiting to be reactivated. An exit other than a ready doorbell
 80 /// invalidates the world.
 81 pub fn activate(
 82     restored: *restore.Restored,
 83     node: fabric.NodeId,
 84     fence: os.abi.ActivationFence,
 85 ) ActivateResult {
 86     _ = restore.validateStable(restored) catch |failure|
 87         return activationReject(node, failure);
 88     os.abi.wire.validateFence(fence) catch |failure|
 89         return activationReject(node, failure);
 90     const machine = findMachine(restored, node) orelse
 91         return activationReject(node, error.MachineNodeUnavailable);
 92     switch (machine.phase()) {
 93         .booting => {},
 94         .awaiting_reactivation => machine.reactivate(fence) catch |failure|
 95             return activationInvalidate(restored, node, failure),
 96         else => return activationReject(node, error.MachinePhaseMismatch),
 97     }
 98     const exit_value = machine.run() catch |failure|
 99         return activationInvalidate(restored, node, failure);
100     if (!std.meta.eql(
101         exit_value,
102         instance.Exit{ .doorbell = .{ .code = .ready } },
103     )) {
104         return activationInvalidate(
105             restored,
106             node,
107             error.ActivationExitMismatch,
108         );
109     }
110     var events: instance.EventBatch = undefined;
111     machine.takeEvents(&events) catch |failure|
112         return activationInvalidate(restored, node, failure);
113     instance.verifyEventBatch(&events, fence) catch |failure|
114         return activationInvalidate(restored, node, failure);
115     return .{ .activated = .{ .node = node, .events = events } };
116 }
117 
118 /// Carries one connected terminal turn from end to end, so a caller feeds one node
119 /// input and lets the guest settle it: the call admits the bytes into a candidate
120 /// ledger, hands them to the node, runs the guest until it quiesces, checks the
121 /// events it produced against the quiescence receipt and the fence, settles the
122 /// ledger, and moves the moment forward. The call commits the candidate ledger once
123 /// every check has passed. The call requires a stable world, a valid fence, and
124 /// a node awaiting input. A failure before delivery rejects, and a failure from
125 /// delivery onward invalidates.
126 pub fn terminal(
127     restored: *restore.Restored,
128     node: fabric.NodeId,
129     fence: os.abi.ActivationFence,
130     bytes: []const u8,
131 ) TurnResult {
132     _ = restore.validateStable(restored) catch |failure|
133         return turnReject(node, failure);
134     os.abi.wire.validateFence(fence) catch |failure|
135         return turnReject(node, failure);
136     const machine = findMachine(restored, node) orelse
137         return turnReject(node, error.MachineNodeUnavailable);
138     if (machine.phase() != .awaiting_input) {
139         return turnReject(node, error.MachinePhaseMismatch);
140     }
141     var candidate = restored.fabric.*;
142     const admitted = candidate.terminal(
143         node,
144         machine,
145         fence,
146         bytes,
147     ) catch |failure| return turnReject(node, failure);
148     machine.deliverAdmitted(&admitted.delivery) catch |failure|
149         return turnInvalidate(restored, node, failure);
150     const exit_value = machine.run() catch |failure|
151         return turnInvalidate(restored, node, failure);
152     if (!std.meta.eql(
153         exit_value,
154         instance.Exit{ .doorbell = .{ .code = .quiescent } },
155     )) {
156         return turnInvalidate(
157             restored,
158             node,
159             error.QuiescenceExitMismatch,
160         );
161     }
162     var events: instance.EventBatch = undefined;
163     machine.takeEvents(&events) catch |failure|
164         return turnInvalidate(restored, node, failure);
165     machine.acknowledge(admitted.delivery.receipt) catch |failure|
166         return turnInvalidate(restored, node, failure);
167     const receipt = machine.quiescenceReceipt() catch |failure|
168         return turnInvalidate(restored, node, failure);
169     instance.verifyEventBatchReceipt(
170         &events,
171         receipt,
172         fence,
173     ) catch |failure| return turnInvalidate(restored, node, failure);
174     const settled = candidate.settle(node, receipt) catch |failure|
175         return turnInvalidate(restored, node, failure);
176     const next_moment = moment.prepare(
177         restored.root,
178         candidate.root(),
179     ) catch |failure| return turnInvalidate(restored, node, failure);
180     restored.fabric.* = candidate;
181     restored.moment = next_moment;
182     return .{ .advanced = .{
183         .admission = .{ .entry = admitted.entry, .root = admitted.root },
184         .settlement = .{ .entry = settled.entry, .root = settled.root },
185         .moment = next_moment,
186         .events = events,
187         .receipt = receipt,
188     } };
189 }
190 
191 fn findMachine(
192     restored: *restore.Restored,
193     id: fabric.NodeId,
194 ) ?*instance.Instance {
195     std.debug.assert(restored.node_count <= restored.nodes.len);
196     for (restored.nodes[0..restored.node_count]) |*node| {
197         if (!std.meta.eql(node.id, id)) continue;
198         return if (node.machine) |*machine| machine else null;
199     }
200     return null;
201 }
202 
203 fn activationReject(node: fabric.NodeId, failure: Error) ActivateResult {
204     return .{ .rejected = .{ .node = node, .failure = failure } };
205 }
206 
207 fn activationInvalidate(
208     restored: *restore.Restored,
209     node: fabric.NodeId,
210     failure: Error,
211 ) ActivateResult {
212     restored.deinit();
213     return .{ .invalidated = .{ .node = node, .failure = failure } };
214 }
215 
216 fn turnReject(node: fabric.NodeId, failure: Error) TurnResult {
217     return .{ .rejected = .{ .node = node, .failure = failure } };
218 }
219 
220 fn turnInvalidate(
221     restored: *restore.Restored,
222     node: fabric.NodeId,
223     failure: Error,
224 ) TurnResult {
225     restored.deinit();
226     return .{ .invalidated = .{ .node = node, .failure = failure } };
227 }