1use crate::journal::{Journal, JournalError};
10use crate::ledger::{LedgerError, LiveTxn, OpRecord, Reconciliation, Record};
11use crate::machine::tag;
12use crate::store::Store;
13use planarian_schema::{
14 Actor, Attribution, GenerationBackend, GenerationId, Hash, InventoryBackend, JobId, Lease, Millis, OpId, Outcome,
15 Override, OverrideDesired, OwnerId, RecoveryStrategy, RejectReason, Timestamp, Transaction, Transition, TxnId,
16 TxnState, UnitId,
17};
18
19pub const JOB_DRAIN: Millis = Millis::secs(60);
21pub const LEASE_GRACE: Millis = Millis::secs(30);
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum RecoveryAction {
29 Reject,
31 Reobserve,
33 CandidateActive,
35 CheckGeneration,
37 RecheckPublished,
39 WriteCommitted,
41 RevertGeneration,
43 Publishing,
45 DriverBound,
47 CheckDevice,
49 RevertDevice,
51 AwaitBoot,
54 Fail,
56}
57
58pub fn should_take_over(
63 state: &TxnState,
64 boot_only: bool,
65 mine: bool,
66 owner_alive: bool,
67 now: Timestamp,
68 deadline: Timestamp,
69 renewed: Timestamp,
70) -> bool {
71 let in_window = match state {
74 TxnState::Activating
75 | TxnState::Checking
76 | TxnState::Publishing
77 | TxnState::Published
78 | TxnState::Reverting => true,
79 TxnState::Promoted => !boot_only,
80 _ => false,
81 };
82 if !in_window || mine {
83 return false;
84 }
85 let unrenewed = now > deadline && now > renewed.plus(LEASE_GRACE);
86 !owner_alive || unrenewed
87}
88
89pub fn next_epoch(lease_epoch: u64, fence: Option<u64>) -> Option<u64> {
93 lease_epoch.max(fence.unwrap_or(0)).checked_add(1)
94}
95
96pub fn fence_admits(fence: Option<u64>, epoch: u64) -> bool {
99 fence == Some(epoch)
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub enum SwapStep {
105 Done,
107 Swap,
109 ThirdParty,
111}
112
113pub fn swap_step(current: &Hash, from: &Hash, to: &Hash) -> SwapStep {
114 if current == to {
115 SwapStep::Done
116 } else if current == from {
117 SwapStep::Swap
118 } else {
119 SwapStep::ThirdParty
120 }
121}
122
123pub fn recovery_action(state: &TxnState, strategy: RecoveryStrategy) -> RecoveryAction {
125 use RecoveryAction as A;
126 use RecoveryStrategy as S;
127 match (state, strategy) {
128 (s, _) if s.is_pre_mutation() => A::Reject,
129 (TxnState::Activating | TxnState::Checking, S::ReobserveOnly) => A::Reobserve,
130 (TxnState::Activating, S::RevertGeneration) => A::CandidateActive,
131 (TxnState::Checking, S::RevertGeneration) => A::CheckGeneration,
132 (TxnState::Published, S::RevertGeneration) => A::RecheckPublished,
133 (TxnState::Promoted, S::RevertGeneration) => A::WriteCommitted,
134 (TxnState::Reverting, S::RevertGeneration) => A::RevertGeneration,
135 (TxnState::AwaitingBoot, S::RevertGeneration) => A::AwaitBoot,
136 (TxnState::Publishing, S::RevertGeneration) => A::Publishing,
137 (TxnState::Activating, S::UnbindDevice) => A::DriverBound,
138 (TxnState::Checking, S::UnbindDevice) => A::CheckDevice,
139 (TxnState::Reverting, S::UnbindDevice) => A::RevertDevice,
140 _ => A::Fail,
141 }
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub enum CheckPhase {
147 Candidate,
149 Revert,
151}
152
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub enum Verdict {
155 Pass,
156 Fail(String),
157}
158
159pub trait Host {
161 type Gen: GenerationBackend;
162 type Inv: InventoryBackend;
163
164 fn generations(&self) -> &Self::Gen;
165 fn inventory(&self) -> &Self::Inv;
166 fn execute_runtime(&self, t: &Transition, op: &OpId) -> Outcome;
169 fn reobserve(&self, txn: &Transaction) -> Outcome;
172 fn check(&self, txn: &Transaction, phase: CheckPhase) -> Verdict;
175 fn release_gc_root(&self, path: &str) -> bool;
177 fn now(&self) -> Timestamp;
178 fn boot_now(&self) -> Timestamp;
180 fn sleep(&self, d: Millis);
181 fn kill_worker(&self, pid: u32) -> bool;
183 fn pid_alive(&self, pid: u32) -> bool;
184 fn floor_fixes(&self, _txn: &Transaction, _floor: Option<GenerationId>) -> Result<Vec<String>, Outcome> {
189 Ok(vec![])
190 }
191}
192
193#[derive(Debug)]
194pub enum KernelError {
195 Journal(JournalError),
196 Crashed,
198 LostLease(TxnId),
200 NotLive(TxnId),
201}
202
203impl From<JournalError> for KernelError {
204 fn from(e: JournalError) -> Self {
205 match e {
206 JournalError::Ledger(LedgerError::StaleEpoch { txn, .. }) => KernelError::LostLease(txn),
207 e => KernelError::Journal(e),
208 }
209 }
210}
211
212type R<T> = Result<T, KernelError>;
213
214#[derive(Clone, Debug, PartialEq, Eq)]
217pub enum CrashPoint {
218 State(&'static str),
220 OpBegun(&'static str),
222 OpCalled(&'static str),
224}
225
226pub struct Kernel<S: Store, H: Host> {
227 pub journal: Journal<S>,
228 pub host: H,
229 pub me: OwnerId,
230 pub crash_at: Option<CrashPoint>,
231 pub worker_pid: Option<u32>,
233 held: std::collections::BTreeMap<TxnId, u64>,
237}
238
239impl<S: Store, H: Host> Kernel<S, H> {
240 pub fn new(journal: Journal<S>, host: H, me: OwnerId) -> Self {
241 Kernel { journal, host, me, crash_at: None, worker_pid: None, held: Default::default() }
242 }
243
244 fn live(&mut self, id: TxnId) -> R<LiveTxn> {
245 self.journal.sync_ledger()?.live.get(&id).cloned().ok_or(KernelError::NotLive(id))
246 }
247
248 fn epoch(&self, txn: TxnId) -> R<u64> {
249 self.held.get(&txn).copied().ok_or(KernelError::LostLease(txn))
250 }
251
252 fn record(&mut self, r: Record) -> R<()> {
253 let now = self.host.now();
254 let txn = match &r {
255 Record::Advance { txn, .. } | Record::Takeover { txn, .. } | Record::Renew { txn, .. } => Some(*txn),
256 Record::OpBegin { op, .. } | Record::OpAck { op, .. } => Some(op.txn),
257 _ => None,
258 };
259 match self.journal.append(now, r) {
260 Err(JournalError::Full) => {
261 let mutating =
264 txn.and_then(|t| self.journal.ledger().live.get(&t)).filter(|l| !l.txn.state.is_pre_mutation());
265 if let Some(l) = mutating {
266 let id = l.txn.id;
267 self.journal.write_sentinel(id, "journal full on the recovery path")?;
268 }
269 Err(KernelError::Journal(JournalError::Full))
270 }
271 r => r.map(|_| ()).map_err(KernelError::from),
272 }
273 }
274
275 fn advance(&mut self, txn: &Transaction, to: TxnState, note: &str) -> R<()> {
276 self.advance_for(txn, to, note, None)
277 }
278
279 fn advance_for(&mut self, txn: &Transaction, to: TxnState, note: &str, withdrawn_for: Option<TxnId>) -> R<()> {
280 let epoch = self.epoch(txn.id)?;
281 let rec = Record::Advance { txn: txn.id, epoch, to: to.clone(), note: note.into(), withdrawn_for };
282 self.record(rec)?;
283 if to.is_terminal()
284 && to != TxnState::Committed
285 && let Some(root) = &txn.gc_root
286 {
287 self.host.release_gc_root(root);
288 }
289 if self.crash_at == Some(CrashPoint::State(tag(&to))) {
290 return Err(KernelError::Crashed);
291 }
292 Ok(())
293 }
294
295 fn call(&mut self, txn: TxnId, name: &'static str, f: impl FnOnce(&H, &OpId) -> Outcome) -> R<Outcome> {
298 self.call_op(txn, name, f).map(|(_, o)| o)
299 }
300
301 fn call_op(&mut self, txn: TxnId, name: &'static str, f: impl FnOnce(&H, &OpId) -> Outcome) -> R<(OpId, Outcome)> {
302 let epoch = self.epoch(txn)?;
303 let l = self.journal.sync_ledger()?.live.get(&txn).cloned().ok_or(KernelError::NotLive(txn))?;
304 let op = OpId { txn, epoch, seq: l.ops.len() as u64 + 1 };
305 self.record(Record::OpBegin { op, call: name.into() })?;
306 if self.crash_at == Some(CrashPoint::OpBegun(name)) {
307 return Err(KernelError::Crashed);
308 }
309 let outcome = f(&self.host, &op);
310 if outcome.is_fenced() {
311 return Err(KernelError::LostLease(txn));
312 }
313 if self.crash_at == Some(CrashPoint::OpCalled(name)) {
314 return Err(KernelError::Crashed);
315 }
316 self.record(Record::OpAck { op, outcome: outcome.clone() })?;
317 Ok((op, outcome))
318 }
319
320 fn drain_op(&mut self, txn: TxnId, op: &OpId) -> R<Result<(), String>> {
324 let jobs = match self.host.generations().jobs_tagged(op) {
325 Ok(Some(jobs)) => jobs,
326 Ok(None) => return Ok(Err(format!("backend cannot say what op {op} started"))),
327 Err(o) => return Ok(Err(format!("jobs_tagged({op}): {o:?}"))),
328 };
329 if jobs.is_empty() || self.wait_gone(&jobs)? {
330 return Ok(Ok(()));
331 }
332 for job in &jobs {
333 self.call(txn, "cancel", |h, op| h.generations().cancel(job, op))?;
334 }
335 if self.wait_gone(&jobs)? {
336 Ok(Ok(()))
337 } else {
338 Ok(Err(format!("jobs of op {op} still pending after cancel and {}s drain", JOB_DRAIN.0 / 1000)))
339 }
340 }
341
342 fn wait_gone(&mut self, jobs: &[JobId]) -> R<bool> {
344 let deadline = self.host.boot_now().plus(JOB_DRAIN);
345 loop {
346 match self.host.generations().pending_jobs() {
347 Ok(pending) if !jobs.iter().any(|j| pending.contains(j)) => return Ok(true),
348 Ok(_) => {}
349 Err(_) => return Ok(false),
350 }
351 if self.host.boot_now() >= deadline {
352 return Ok(false);
353 }
354 self.host.sleep(Millis::secs(1));
355 }
356 }
357
358 pub fn prepare(&mut self, mut txn: Transaction) -> R<TxnId> {
363 if self.journal.needs_compaction() {
365 self.journal.compact()?;
366 }
367 let now = self.host.now();
368 let me = self.me.clone();
369 let worker = self.worker_pid;
370 self.journal
371 .locked(|j| {
372 let id = TxnId(j.ledger().next_txn.max(1));
373 txn.id = id;
374 txn.state = TxnState::Prepared;
375 txn.recovery = txn.transition.recovery();
376 txn.lease = Lease {
377 txn: id,
378 owner: me,
379 epoch: 1,
380 worker_pid: worker,
381 renewed: self.host.boot_now(),
382 jobs: vec![],
383 };
384 j.store().create_fence(id, 1)?;
386 let r = j.append(now, Record::Prepared(Box::new(txn))).map(|_| id);
387 if r.is_err() {
388 let _ = j.store().forget_fences(|t| t != id);
390 }
391 r
392 })?
393 .map_err(KernelError::from)
394 .inspect(|id| {
395 self.held.insert(*id, 1);
396 })
397 }
398
399 pub fn reject(&mut self, id: TxnId, reason: RejectReason) -> R<TxnState> {
401 let l = self.live(id)?;
402 let to = TxnState::Rejected(reason);
403 self.advance(&l.txn, to.clone(), "rejected before mutation")?;
404 Ok(to)
405 }
406
407 pub fn withdraw(&mut self, id: TxnId, rollback: TxnId) -> R<TxnState> {
410 let l = self.live(id)?;
411 let to = TxnState::Rejected(RejectReason::Interrupted);
412 self.advance_for(&l.txn, to.clone(), "withdrawn for a rollback", Some(rollback))?;
413 Ok(to)
414 }
415
416 pub fn cancel(&mut self, id: TxnId, by: Actor) -> R<TxnState> {
417 let l = self.live(id)?;
418 let to = TxnState::Cancelled(by);
419 self.advance(&l.txn, to.clone(), "withdrawn before mutation")?;
420 Ok(to)
421 }
422
423 pub fn wait_built(&mut self, id: TxnId, why: &str) -> R<TxnState> {
429 let l = self.live(id)?;
430 if l.txn.state != TxnState::Built {
431 self.advance(&l.txn, TxnState::Built, why)?;
432 }
433 Ok(TxnState::Built)
434 }
435
436 pub fn run_boot_only(&mut self, id: TxnId) -> R<TxnState> {
441 let l = self.live(id)?;
442 if self.held.get(&id) != Some(&l.txn.lease.epoch) {
443 return Err(KernelError::LostLease(id));
444 }
445 let txn = l.txn;
446 let Some((cand, path)) = txn.candidate.clone() else {
447 return self.reject(id, RejectReason::Other("no candidate".into()));
448 };
449 let expected = txn.candidate_nar;
450 if self.host.generations().verify_candidate(&path, expected.as_ref()) != Ok(true) {
451 return self.reject(id, RejectReason::Other("candidate failed verification".into()));
452 }
453 self.wait_built(id, "takes effect at boot")?;
454 self.advance(&txn, TxnState::Publishing, "publishing to boot once")?;
457 let base = txn.base;
460 let o = self.call(id, "publish_boot_once", |h, op| h.generations().publish_boot_once(cand, base, op))?;
461 if !o.is_success() {
462 let to = TxnState::Rejected(RejectReason::Other(format!("publish_boot_once: {o:?}")));
464 self.advance(&txn, to.clone(), "nothing was published")?;
465 return Ok(to);
466 }
467 self.advance(&txn, TxnState::AwaitingBoot, "published to boot once; restart to finish")?;
468 Ok(TxnState::AwaitingBoot)
469 }
470
471 fn recover_publishing(&mut self, txn: &Transaction, ops: &[OpRecord]) -> R<TxnState> {
476 let Some(cand) = Self::candidate(txn) else {
477 return self.recovery_failed(txn, "Publishing without a candidate");
478 };
479 let publish = ops.iter().rev().find(|o| o.call == "publish_boot_once");
480 let armed = match publish.and_then(|o| o.ack.as_ref()) {
484 Some(Outcome::Succeeded) => true,
485 Some(Outcome::Failed(_)) => false,
486 _ => match self.host.generations().one_shot_pending() {
487 Ok(pending) => pending == Some(cand),
488 Err(o) => {
489 return self
490 .recovery_failed(txn, &format!("cannot tell whether the one-shot entry is armed: {o:?}"));
491 }
492 },
493 };
494 if armed {
495 self.advance(txn, TxnState::AwaitingBoot, "recovery: the one-shot entry is armed")?;
496 Ok(TxnState::AwaitingBoot)
497 } else {
498 let to = TxnState::Rejected(RejectReason::Interrupted);
499 self.advance(txn, to.clone(), "recovery: nothing was published")?;
500 Ok(to)
501 }
502 }
503
504 pub fn run(&mut self, id: TxnId) -> R<TxnState> {
508 let l = self.live(id)?;
509 if self.held.get(&id) != Some(&l.txn.lease.epoch) {
510 return Err(KernelError::LostLease(id));
511 }
512 if !l.txn.state.is_pre_mutation() {
513 return self.recover_steps(id);
514 }
515 match l.txn.recovery {
516 RecoveryStrategy::ReobserveOnly => self.run_reobserve(&l.txn),
517 RecoveryStrategy::RevertGeneration => self.run_generation(&l.txn),
518 RecoveryStrategy::UnbindDevice => self.run_device(&l.txn),
519 RecoveryStrategy::RestoreSnapshotThenRevert => {
520 self.reject(id, RejectReason::Other("RestoreSnapshot is Phase C".into()))
522 }
523 }
524 }
525
526 fn run_reobserve(&mut self, txn: &Transaction) -> R<TxnState> {
527 let caller = txn.facts.caller.clone();
528 match &txn.transition {
534 Transition::StopAndSuppress { unit, until, reason } => {
535 let s = Override::attested(
536 unit.clone(),
537 OverrideDesired::Stopped,
538 until.clone(),
539 reason.clone(),
540 caller.clone(),
541 );
542 self.record(Record::OverrideSet(s))?;
543 }
544 Transition::SetOverride(s) => match s.desired {
545 OverrideDesired::LeaveAlone | OverrideDesired::Stopped => {
546 let s = Override { by: caller.clone(), attribution: Attribution::Attested, ..s.clone() };
547 self.record(Record::OverrideSet(s))?;
548 }
549 OverrideDesired::Absent | OverrideDesired::Detected => {
550 return self
551 .reject(txn.id, RejectReason::Other("Phase A overrides are LeaveAlone or Stopped".into()));
552 }
553 },
554 _ => {}
555 }
556 self.advance(txn, TxnState::Activating, "")?;
557 let outcome = match &txn.transition {
558 Transition::StopAndSuppress { unit, .. } => {
559 let stop = Transition::StopUnit(unit.clone());
560 self.call(txn.id, "stop", |h, op| h.execute_runtime(&stop, op))?
561 }
562 Transition::SetOverride(s) if s.desired == OverrideDesired::Stopped => {
564 let stop = Transition::StopUnit(s.unit.clone());
565 self.call(txn.id, "stop", |h, op| h.execute_runtime(&stop, op))?
566 }
567 Transition::SetOverride(_) => Outcome::Succeeded,
568 Transition::LiftOverride(u) => {
569 self.record(Record::OverrideLifted(u.clone()))?;
570 Outcome::Succeeded
571 }
572 Transition::Resolve { txn: target, resolution } => {
573 self.record(Record::Resolved { txn: *target, resolution: *resolution, by: caller })?;
574 Outcome::Succeeded
575 }
576 t => {
577 let t = t.clone();
578 self.call(txn.id, "runtime", |h, op| h.execute_runtime(&t, op))?
579 }
580 };
581 self.advance(txn, TxnState::Checking, &format!("{outcome:?}"))?;
582 self.finish_reobserve(txn)
583 }
584
585 fn finish_reobserve(&mut self, txn: &Transaction) -> R<TxnState> {
586 let observed = self.host.reobserve(txn);
587 self.advance(txn, TxnState::Committed, &format!("observed: {observed:?}"))?;
588 Ok(TxnState::Committed)
589 }
590
591 fn candidate(txn: &Transaction) -> Option<GenerationId> {
592 txn.candidate.as_ref().map(|(g, _)| *g)
593 }
594
595 fn run_generation(&mut self, txn: &Transaction) -> R<TxnState> {
596 let Some((cand, path)) = txn.candidate.clone() else {
597 return self.reject(txn.id, RejectReason::Other("no candidate".into()));
598 };
599 let expected = txn.candidate_nar;
600 match self.host.generations().verify_candidate(&path, expected.as_ref()) {
601 Ok(true) => {}
602 _ => return self.reject(txn.id, RejectReason::Other("candidate failed verification".into())),
603 }
604 self.advance(txn, TxnState::Activating, "")?;
605 let (op, outcome) = self.call_op(txn.id, "activate_live", |h, op| h.generations().activate_live(cand, op))?;
606 if !outcome.is_success()
607 && let Err(why) = self.drain_op(txn.id, &op)?
608 {
609 return self.recovery_failed(txn, &why);
610 }
611 if outcome.is_success() || self.host.generations().current().ok() == Some(cand) {
612 self.advance(txn, TxnState::Checking, &format!("{outcome:?}"))?;
613 self.check_then_commit(txn)
614 } else {
615 self.revert_generation(txn, &format!("activation: {outcome:?}"))
616 }
617 }
618
619 fn restop_suppressed(&mut self, txn: &Transaction) -> R<()> {
623 let now = self.host.now();
624 let units: Vec<UnitId> = self
625 .journal
626 .sync_ledger()?
627 .overrides
628 .iter()
629 .filter(|s| s.desired == OverrideDesired::Stopped && s.holds_at(now))
630 .map(|s| s.unit.clone())
631 .collect();
632 for u in units {
633 let stop = Transition::StopUnit(u);
634 self.call(txn.id, "stop(suppressed)", |h, op| h.execute_runtime(&stop, op))?;
635 }
636 Ok(())
637 }
638
639 fn check_then_commit(&mut self, txn: &Transaction) -> R<TxnState> {
641 self.restop_suppressed(txn)?;
642 match self.host.check(txn, CheckPhase::Candidate) {
643 Verdict::Pass => self.commit_from_checking(txn),
644 Verdict::Fail(why) => self.revert_generation(txn, &why),
645 }
646 }
647
648 fn commit_from_checking(&mut self, txn: &Transaction) -> R<TxnState> {
651 let cand = Self::candidate(txn).expect("generation transactions have a candidate");
652 let published = self.call(txn.id, "publish_boot", |h, op| h.generations().publish_boot(cand, op))?;
653 if !published.is_success() {
654 return self.revert_generation(txn, &format!("publish_boot: {published:?}"));
656 }
657 self.advance(txn, TxnState::Published, "probes passed")?;
658 self.promote_and_commit(txn)
659 }
660
661 fn promote_and_commit(&mut self, txn: &Transaction) -> R<TxnState> {
662 match self.swap_override(txn, txn.base_override, txn.candidate_override)? {
663 Swap::Done => {}
664 Swap::Failed(why) => return self.revert_generation(txn, &format!("promotion: {why}")),
665 Swap::ThirdParty => {
666 self.advance(txn, TxnState::RecoveryFailed, "override changed by a third party during promotion")?;
667 return Ok(TxnState::RecoveryFailed);
668 }
669 }
670 self.advance(txn, TxnState::Promoted, "")?;
671 self.write_committed(txn)
672 }
673
674 pub fn boot_only(&self, id: TxnId) -> bool {
677 self.journal.ledger().live.get(&id).is_some_and(|l| l.boot_only || l.path.iter().any(|p| p == "AwaitingBoot"))
678 }
679
680 fn admit_boot_only(&mut self, txn: &Transaction, cand: GenerationId) -> R<TxnState> {
685 let o = self.call(txn.id, "publish_boot", |h, op| h.generations().publish_boot(cand, op))?;
686 if !o.is_success() {
687 return self.withdraw_boot_only(txn, false, &format!("publish_boot: {o:?}"));
688 }
689 match self.swap_override(txn, txn.base_override, txn.candidate_override)? {
690 Swap::Done => {}
691 Swap::Failed(why) => return self.withdraw_boot_only(txn, false, &format!("promotion: {why}")),
692 Swap::ThirdParty => {
693 return self.recovery_failed(txn, "override changed by a third party during promotion");
694 }
695 }
696 self.advance(txn, TxnState::Promoted, "admitted at boot; confirmed at the 10-minute checkpoint")?;
697 Ok(TxnState::Promoted)
698 }
699
700 pub fn confirm_boot_only(&mut self, id: TxnId, healthy: bool) -> R<TxnState> {
705 let l = self.live(id)?;
706 if self.held.get(&id) != Some(&l.txn.lease.epoch) {
707 return Err(KernelError::LostLease(id));
708 }
709 let txn = l.txn;
710 if txn.state != TxnState::Promoted || !self.boot_only(id) {
711 return Ok(txn.state);
712 }
713 let running = self.host.generations().last_booted().ok() == Self::candidate(&txn);
714 if healthy && running {
715 self.write_committed(&txn)
716 } else {
717 self.withdraw_boot_only(&txn, true, "not confirmed: canonical probes failed at the 10-minute checkpoint")
718 }
719 }
720
721 fn withdraw_boot_only(&mut self, txn: &Transaction, promoted: bool, why: &str) -> R<TxnState> {
726 if self.live(txn.id)?.txn.state != TxnState::Reverting {
727 self.advance(txn, TxnState::Reverting, why)?;
728 }
729 let back = txn.base;
730 let o = self.call(txn.id, "publish_boot(publishing)", |h, op| h.generations().publish_boot(back, op))?;
731 if !o.is_success() {
732 return self.recovery_failed(txn, &format!("withdraw publish_boot: {o:?}"));
733 }
734 if promoted {
735 match self.swap_override(txn, txn.candidate_override, txn.base_override)? {
736 Swap::Done => {}
737 Swap::Failed(why) => return self.recovery_failed(txn, &format!("override restore: {why}")),
738 Swap::ThirdParty => return self.recovery_failed(txn, "override changed by a third party"),
739 }
740 }
741 self.advance(txn, TxnState::Reverted, why)?;
742 Ok(TxnState::Reverted)
743 }
744
745 fn readmit_boot_only(&mut self, txn: &Transaction, same_boot: bool) -> R<TxnState> {
751 if same_boot {
752 return Ok(TxnState::Promoted);
753 }
754 let cand = Self::candidate(txn);
755 if self.host.generations().last_booted().ok() != cand {
756 return self.withdraw_boot_only(txn, true, "another generation booted before the change was confirmed");
757 }
758 match self.host.check(txn, CheckPhase::Candidate) {
759 Verdict::Pass => Ok(TxnState::Promoted),
760 Verdict::Fail(why) => self.withdraw_boot_only(txn, true, &why),
761 }
762 }
763
764 pub fn await_boot(&mut self, txn: &Transaction) -> R<TxnState> {
770 let Some(cand) = Self::candidate(txn) else {
771 return self.recovery_failed(txn, "AwaitingBoot without a candidate");
772 };
773 let gens = self.host.generations();
774 match gens.one_shot_pending() {
776 Ok(Some(g)) if g == cand => return Ok(TxnState::AwaitingBoot),
777 Err(_) => return Ok(TxnState::AwaitingBoot),
778 _ => {}
779 }
780 if gens.last_booted().ok() != Some(cand) {
781 self.advance(txn, TxnState::Reverted, "the boot-only candidate did not boot; the default entry did")?;
782 return Ok(TxnState::Reverted);
783 }
784 match self.host.check(txn, CheckPhase::Candidate) {
787 Verdict::Pass => self.admit_boot_only(txn, cand),
788 Verdict::Fail(why) => self.withdraw_boot_only(txn, false, &why),
789 }
790 }
791
792 fn write_committed(&mut self, txn: &Transaction) -> R<TxnState> {
798 if let Some(cand) = Self::candidate(txn) {
799 let floor = self.journal.ledger().security_floor;
800 if floor.is_none_or(|f| f.0 < cand.0) {
801 match self.host.floor_fixes(txn, floor) {
802 Ok(fixes) if !fixes.is_empty() => self.record(Record::SecurityFloor(Some(cand)))?,
803 Ok(_) => {}
804 Err(_) => return Ok(self.live(txn.id)?.txn.state),
805 }
806 }
807 if let Some(h) = &txn.candidate_override {
808 let _ = self.host.generations().record_override(cand, h);
809 }
810 }
811 let boot_only = self.boot_only(txn.id);
813 self.advance(txn, TxnState::Committed, "")?;
814 if let Some(cand) = Self::candidate(txn)
815 && !boot_only
816 {
817 self.record(Record::LiveTested(Some(cand)))?;
818 }
819 Ok(TxnState::Committed)
820 }
821
822 fn swap_override(&mut self, txn: &Transaction, from: Option<Hash>, to: Option<Hash>) -> R<Swap> {
826 let (Some(from), Some(to)) = (from, to) else {
827 return Ok(Swap::Done);
828 };
829 let current = match self.host.generations().override_hash() {
830 Ok(h) => h,
831 Err(o) => return Ok(Swap::Failed(format!("{o:?}"))),
832 };
833 match swap_step(¤t, &from, &to) {
834 SwapStep::Done => return Ok(Swap::Done),
835 SwapStep::ThirdParty => return Ok(Swap::ThirdParty),
836 SwapStep::Swap => {}
837 }
838 let o = self.call(txn.id, "swap_override", |h, op| h.generations().swap_override(&from, &to, op))?;
839 Ok(if o.is_success() { Swap::Done } else { Swap::Failed(format!("{o:?}")) })
840 }
841
842 fn revert_generation(&mut self, txn: &Transaction, why: &str) -> R<TxnState> {
843 let state = self.live(txn.id)?.txn.state;
844 if state != TxnState::Reverting {
845 self.advance(txn, TxnState::Reverting, why)?;
846 }
847 let to = txn.revert_to;
848 let (op, activated) =
849 self.call_op(txn.id, "activate_live(revert_to)", |h, op| h.generations().activate_live(to, op))?;
850 if !activated.is_success() {
851 if let Err(why) = self.drain_op(txn.id, &op)? {
853 return self.recovery_failed(txn, &format!("revert activation: {why}"));
854 }
855 }
856 if !activated.is_success() && self.host.generations().current().ok() != Some(to) {
857 return self.recovery_failed(txn, &format!("revert activation: {activated:?}"));
858 }
859 self.restop_suppressed(txn)?;
860 let published = self.call(txn.id, "publish_boot(revert_to)", |h, op| h.generations().publish_boot(to, op))?;
861 if !published.is_success() {
862 return self.recovery_failed(txn, &format!("revert publish_boot: {published:?}"));
863 }
864 match self.swap_override(txn, txn.candidate_override, txn.base_override)? {
865 Swap::Done => {}
866 Swap::Failed(why) => return self.recovery_failed(txn, &format!("override restore: {why}")),
867 Swap::ThirdParty => return self.recovery_failed(txn, "override changed by a third party"),
868 }
869 match self.host.check(txn, CheckPhase::Revert) {
870 Verdict::Pass => {
871 self.advance(txn, TxnState::Reverted, why)?;
872 Ok(TxnState::Reverted)
873 }
874 Verdict::Fail(f) => self.recovery_failed(txn, &format!("revert probes: {f}")),
875 }
876 }
877
878 fn recovery_failed(&mut self, txn: &Transaction, why: &str) -> R<TxnState> {
879 self.advance(txn, TxnState::RecoveryFailed, why)?;
880 Ok(TxnState::RecoveryFailed)
881 }
882
883 fn run_device(&mut self, txn: &Transaction) -> R<TxnState> {
884 let Transition::BindDriver { device, driver } = &txn.transition else {
885 return self.reject(txn.id, RejectReason::Other("device op without BindDriver".into()));
886 };
887 self.advance(txn, TxnState::Activating, &format!("previous driver: {:?}", txn.prior_driver))?;
888 let (device, driver) = (device.clone(), driver.clone());
889 let bound = self.call(txn.id, "bind", |h, op| h.inventory().bind(&device, &driver, op))?;
890 if !bound.is_success() {
891 return self.revert_device(txn, &format!("bind: {bound:?}"));
892 }
893 self.advance(txn, TxnState::Checking, "")?;
894 self.check_device(txn)
895 }
896
897 fn check_device(&mut self, txn: &Transaction) -> R<TxnState> {
898 match self.host.check(txn, CheckPhase::Candidate) {
899 Verdict::Pass => {
900 self.advance(txn, TxnState::Committed, "")?;
901 Ok(TxnState::Committed)
902 }
903 Verdict::Fail(why) => self.revert_device(txn, &why),
904 }
905 }
906
907 fn revert_device(&mut self, txn: &Transaction, why: &str) -> R<TxnState> {
909 let Transition::BindDriver { device, .. } = &txn.transition else { unreachable!("device op") };
910 let device = device.clone();
911 if self.live(txn.id)?.txn.state != TxnState::Reverting {
912 self.advance(txn, TxnState::Reverting, why)?;
913 }
914 let unbound = self.call(txn.id, "unbind", |h, op| h.inventory().unbind(&device, op))?;
915 if !unbound.is_success() && self.host.inventory().bound_driver(&device).ok() != Some(None) {
916 return self.recovery_failed(txn, &format!("unbind: {unbound:?}"));
917 }
918 if let Some(prev) = txn.prior_driver.clone() {
919 let rebound = self.call(txn.id, "bind(previous)", |h, op| h.inventory().bind(&device, &prev, op))?;
920 if !rebound.is_success() {
921 return self.recovery_failed(txn, &format!("rebind previous: {rebound:?}"));
922 }
923 }
924 match self.host.check(txn, CheckPhase::Revert) {
925 Verdict::Pass => {
926 self.advance(txn, TxnState::Reverted, why)?;
927 Ok(TxnState::Reverted)
928 }
929 Verdict::Fail(f) => self.recovery_failed(txn, &format!("revert probes: {f}")),
930 }
931 }
932
933 pub fn watchdog_should_take_over(&self, txn: &Transaction) -> bool {
937 should_take_over(
938 &txn.state,
939 self.boot_only(txn.id),
940 txn.lease.owner == self.me,
941 txn.lease.owner.boot_id() == self.me.boot_id() && self.host.pid_alive(txn.lease.owner.pid()),
943 self.host.boot_now(),
944 txn.deadline,
945 txn.lease.renewed,
946 )
947 }
948
949 pub fn take_over(&mut self, id: TxnId, seen_epoch: u64) -> R<Option<Lease>> {
955 let now = self.host.now();
956 let me = self.me.clone();
957 let worker = self.worker_pid;
958 let (old, new) = self.journal.locked(|j| -> Result<(Lease, Lease), KernelError> {
959 let l = j.ledger().live.get(&id).cloned().ok_or(KernelError::NotLive(id))?;
960 let old = l.txn.lease.clone();
961 if old.epoch != seen_epoch {
962 return Err(KernelError::LostLease(id));
963 }
964 let fence = j.store().fence(id).map_err(|e| KernelError::Journal(e.into()))?;
967 let epoch = next_epoch(old.epoch, fence).ok_or(KernelError::LostLease(id))?;
968 let new =
969 Lease { txn: id, owner: me, epoch, worker_pid: worker, renewed: self.host.boot_now(), jobs: vec![] };
970 j.store().set_fence(id, epoch).map_err(|e| KernelError::Journal(e.into()))?;
972 j.append(now, Record::Takeover { txn: id, from_epoch: old.epoch, lease: new.clone() })?;
973 Ok((old, new))
974 })??;
975 self.held.insert(id, new.epoch);
976
977 let l = self.live(id)?;
978 if l.txn.state.is_pre_mutation() {
979 return Ok(Some(new));
980 }
981 match self.quiesce(&l, &old)? {
982 Ok(()) => Ok(Some(new)),
983 Err(why) => {
984 self.advance(&l.txn, TxnState::RecoveryFailed, &format!("quiescence not confirmed: {why}"))?;
985 Ok(None)
986 }
987 }
988 }
989
990 fn quiesce(&mut self, l: &LiveTxn, old: &Lease) -> R<Result<(), String>> {
993 if let Some(pid) = old.worker_pid
994 && !self.host.kill_worker(pid)
995 {
996 return Ok(Err(format!("worker {pid} did not die")));
997 }
998 let mut jobs: Vec<JobId> = old.jobs.clone();
999 let unacked: Vec<OpId> = l.unacked_ops().map(|o| o.op).collect();
1000 for op in &unacked {
1001 match self.host.generations().jobs_tagged(op) {
1002 Ok(Some(found)) => jobs.extend(found),
1003 Ok(None) => return Ok(Err(format!("backend cannot say what op {op} started"))),
1004 Err(o) => return Ok(Err(format!("jobs_tagged({op}): {o:?}"))),
1005 }
1006 }
1007 jobs.sort();
1008 jobs.dedup();
1009 for job in &jobs {
1010 self.call(l.txn.id, "cancel", |h, op| h.generations().cancel(job, op))?;
1011 }
1012 if !self.wait_gone(&jobs)? {
1013 return Ok(Err(format!("jobs still pending after {}s drain", JOB_DRAIN.0 / 1000)));
1014 }
1015 self.supersede_unacked(&unacked)?;
1016 Ok(Ok(()))
1017 }
1018
1019 pub fn supersede_unacked(&mut self, ops: &[OpId]) -> R<()> {
1026 for op in ops {
1027 let now = self.host.now();
1028 let ack = Record::OpAck { op: *op, outcome: Outcome::Unknown("superseded by takeover".into()) };
1029 let appended = self.journal.locked(|j| {
1030 let still = j.ledger().live.get(&op.txn).is_some_and(|l| l.unacked_ops().any(|o| o.op == *op));
1031 if still { j.append(now, ack.clone()).map(|_| true) } else { Ok(false) }
1032 })?;
1033 match appended {
1034 Ok(_) => {}
1035 Err(JournalError::Full) => self.record(ack)?,
1037 Err(e) => return Err(e.into()),
1038 }
1039 }
1040 Ok(())
1041 }
1042
1043 pub fn recover(&mut self, id: TxnId, seen_epoch: u64) -> R<TxnState> {
1047 let before = self.live(id)?.txn;
1048 let same_boot = before.lease.owner.boot_id() == self.me.boot_id();
1049 if self.take_over(id, seen_epoch)?.is_none() {
1050 return Ok(TxnState::RecoveryFailed);
1051 }
1052 if before.state == TxnState::Promoted && self.boot_only(id) {
1053 let txn = self.live(id)?.txn;
1054 return self.readmit_boot_only(&txn, same_boot);
1055 }
1056 self.recover_steps(id)
1057 }
1058
1059 fn recover_steps(&mut self, id: TxnId) -> R<TxnState> {
1062 let l = self.live(id)?;
1063 let txn = l.txn;
1064 use RecoveryAction as A;
1065 match recovery_action(&txn.state, txn.recovery) {
1066 A::Reject => self.reject(id, RejectReason::Interrupted),
1067 A::Reobserve => self.finish_reobserve(&txn),
1070 A::CandidateActive => {
1071 let cand = Self::candidate(&txn);
1072 if cand.is_some() && self.host.generations().current().ok() == cand {
1073 self.advance(&txn, TxnState::Checking, "recovery: candidate is active")?;
1074 self.check_then_commit(&txn)
1075 } else {
1076 self.revert_generation(&txn, "recovery: candidate not active")
1077 }
1078 }
1079 A::CheckGeneration => self.check_then_commit(&txn),
1080 A::RecheckPublished => match self.host.check(&txn, CheckPhase::Candidate) {
1081 Verdict::Pass => {
1082 let cand = Self::candidate(&txn).expect("candidate");
1083 let o = self.call(id, "publish_boot", |h, op| h.generations().publish_boot(cand, op))?;
1084 if !o.is_success() {
1085 return self.revert_generation(&txn, &format!("publish_boot: {o:?}"));
1086 }
1087 self.promote_and_commit(&txn)
1088 }
1089 Verdict::Fail(why) => self.revert_generation(&txn, &why),
1090 },
1091 A::WriteCommitted => self.write_committed(&txn),
1092 A::RevertGeneration => self.revert_generation(&txn, "recovery: re-run revert"),
1093 A::DriverBound => {
1094 let Transition::BindDriver { device, driver } = &txn.transition else { unreachable!() };
1095 if self.host.inventory().bound_driver(device).ok().flatten().as_ref() == Some(driver) {
1096 self.advance(&txn, TxnState::Checking, "recovery: candidate driver bound")?;
1097 self.check_device(&txn)
1098 } else {
1099 self.revert_device(&txn, "recovery: candidate driver not bound")
1100 }
1101 }
1102 A::CheckDevice => self.check_device(&txn),
1103 A::RevertDevice => self.revert_device(&txn, "recovery: re-run revert"),
1104 A::AwaitBoot => self.await_boot(&txn),
1105 A::Publishing => self.recover_publishing(&txn, &l.ops),
1106 A::Fail => {
1107 let why = format!("no recovery path for {} under {:?}", tag(&txn.state), txn.recovery);
1108 self.recovery_failed(&txn, &why)
1109 }
1110 }
1111 }
1112
1113 pub fn reconcile(&mut self) -> R<Vec<TxnId>> {
1117 Ok(self.reconcile_deferring(false)?.0)
1118 }
1119
1120 pub fn reconcile_deferring(&mut self, defer: bool) -> R<(Vec<TxnId>, Vec<TxnId>)> {
1130 let live: Vec<LiveTxn> = self.journal.sync_ledger()?.live.values().cloned().collect();
1131 let mut found = vec![];
1132 let mut actions = vec![];
1133 let mut waiting = vec![];
1134 let mut deferred = vec![];
1135 for l in live {
1136 found.push((l.txn.id, l.txn.state.clone()));
1137 let holder = &l.txn.lease.owner;
1138 if holder.is_watchdog() && self.host.pid_alive(holder.pid()) {
1139 actions.push(format!("txn {}: held by the watchdog; waiting", l.txn.id.0));
1140 waiting.push(l.txn.id);
1141 continue;
1142 }
1143 let activates = matches!(
1144 l.txn.state,
1145 TxnState::Activating
1146 | TxnState::Checking
1147 | TxnState::Publishing
1148 | TxnState::Published
1149 | TxnState::Reverting
1150 );
1151 if defer && activates && l.txn.transition.changes_generation() {
1152 if self.take_over(l.txn.id, l.txn.lease.epoch)?.is_none() {
1153 actions.push(format!("txn {}: found {}, takeover failed", l.txn.id.0, tag(&l.txn.state)));
1154 continue;
1155 }
1156 actions.push(format!(
1157 "txn {}: found {}, taken over; recovered after ready",
1158 l.txn.id.0,
1159 tag(&l.txn.state)
1160 ));
1161 deferred.push(l.txn.id);
1162 continue;
1163 }
1164 let end = self.recover(l.txn.id, l.txn.lease.epoch)?;
1165 actions.push(format!("txn {}: found {}, now {}", l.txn.id.0, tag(&l.txn.state), tag(&end)));
1166 }
1167 let ledger = self.journal.ledger();
1169 for (id, t) in ledger.terminal.iter().rev().take(16) {
1170 if t.owner.is_watchdog() {
1171 found.push((*id, t.state.clone()));
1172 let why = if t.why.is_empty() { String::new() } else { format!("; because {}", t.why) };
1173 actions.push(format!(
1174 "txn {}: the watchdog took over and finished it: {} (path {}){why}",
1175 id.0,
1176 tag(&t.state),
1177 t.path.join(" → ")
1178 ));
1179 }
1180 }
1181 self.record(Record::Reconciliation(Reconciliation { by: self.me.clone(), found, actions }))?;
1182 Ok((waiting, deferred))
1183 }
1184
1185 pub fn finish_deferred(&mut self, ids: &[TxnId]) -> R<()> {
1188 if ids.is_empty() {
1189 return Ok(());
1190 }
1191 let mut found = vec![];
1192 let mut actions = vec![];
1193 for &id in ids {
1194 let Ok(l) = self.live(id) else {
1195 actions.push(format!("txn {}: no longer live", id.0));
1196 continue;
1197 };
1198 found.push((id, l.txn.state.clone()));
1199 if l.txn.lease.owner != self.me {
1200 actions.push(format!("txn {}: lease lost before recovery", id.0));
1201 continue;
1202 }
1203 let end = self.recover_steps(id)?;
1204 actions.push(format!("txn {}: found {}, now {} (after ready)", id.0, tag(&l.txn.state), tag(&end)));
1205 }
1206 self.record(Record::Reconciliation(Reconciliation { by: self.me.clone(), found, actions }))
1207 }
1208}
1209
1210enum Swap {
1211 Done,
1212 Failed(String),
1213 ThirdParty,
1214}