| index a569c53da..c7586cdf8 100644 |
| --- a/internal/moves/startcheck.go |
| +++ b/internal/moves/startcheck.go |
| @@ -48,27 +48,38 @@ func originOr(g string) string { |
| return g |
| } |
| |
| -// expectedGenerations is the set of generations a marker may name for this |
| -// holder: holder.generation, as the admission's CAS left it, and the lagging |
| -// ones. A marker is published once per admission, at Replicate, so after a |
| -// restarted attempt (a fresh generation restored from the prepared one) or a |
| -// run of admissions that published nothing, the marker still names an |
| -// earlier generation. Walking holder.admissions from the newest, each |
| -// admission whose generation is already accepted adds its predecessor. The |
| -// origin is hostclaim.RootGeneration here. |
| -func expectedGenerations(h *hostclaim.Holder) map[string]bool { |
| +// coldAdmission is the newest admission into generation g that is not a warm |
| +// self-link (F from F, the warm keep genselect's unfinished skips). Its |
| +// predecessor is where g came from. |
| +func coldAdmission(h *hostclaim.Holder, g string) (hostclaim.AdmissionRecord, bool) { |
| + for i := len(h.Admissions) - 1; i >= 0; i-- { |
| + a := h.Admissions[i] |
| + if originOr(a.Generation) != g || a.Mode == "warm" { |
| + continue |
| + } |
| + return a, true |
| + } |
| + return hostclaim.AdmissionRecord{}, false |
| +} |
| + |
| +// markerGenerations is the set of generations generation.json may name for |
| +// this holder: holder.generation, as the admission's CAS left it, and the |
| +// lagging ones. A marker is published once per admission, at Replicate, so |
| +// after a restarted attempt (a fresh generation restored from the prepared |
| +// one) or a run of admissions that published nothing, the marker still names |
| +// an earlier generation. Walking holder.admissions from the newest, skipping |
| +// warm self-links, each cold admission into an accepted generation adds its |
| +// predecessor. The origin is hostclaim.RootGeneration here. |
| +func markerGenerations(h *hostclaim.Holder) map[string]bool { |
| cur := originOr(h.Generation) |
| ok := map[string]bool{cur: true} |
| for { |
| - var next string |
| - found := false |
| - for i := len(h.Admissions) - 1; i >= 0; i-- { |
| - if originOr(h.Admissions[i].Generation) == cur { |
| - next, found = originOr(h.Admissions[i].Predecessor), true |
| - break |
| - } |
| + a, found := coldAdmission(h, cur) |
| + if !found { |
| + return ok |
| } |
| - if !found || ok[next] { |
| + next := originOr(a.Predecessor) |
| + if ok[next] { // damaged record: predecessors that loop |
| return ok |
| } |
| ok[next] = true |
| @@ -76,15 +87,31 @@ func expectedGenerations(h *hostclaim.Holder) map[string]bool { |
| } |
| } |
| |
| +// filesGenerationOK reports whether .files-gen may name g: exactly |
| +// holder.generation, or the cold predecessor of holder.generation (the |
| +// prepared generation a restarted attempt was restored from, whose files |
| +// are still the ones installed). Nothing further back counts: files from |
| +// before the preparation are stale. |
| +func filesGenerationOK(h *hostclaim.Holder, g string) bool { |
| + if g == "" || g == hostclaim.RootGeneration { |
| + return false |
| + } |
| + if g == h.Generation { |
| + return true |
| + } |
| + a, found := coldAdmission(h, originOr(h.Generation)) |
| + return found && a.Predecessor == g |
| +} |
| + |
| // StartCheck is the §5.1 start check against the claim, run after the |
| // admission CAS and before every spawn. |
| // |
| // 1. marker (generation.json; nil is the origin) names a generation the |
| // claim expects (holder.generation or, lagging, an earlier link of the |
| // admission chain) and the holder's incarnation. |
| -// 2. filesGen, when present, names an expected generation (the same chain: |
| -// a restarted attempt's files still name the prepared generation) and |
| -// the holder's incarnation. |
| +// 2. filesGen, when present, names holder.generation or its cold |
| +// predecessor (a restarted attempt's files still name the prepared |
| +// generation; nothing older) and the holder's incarnation. |
| // 3. dbGen, when not "", is exactly holder.generation: the restore rewrites |
| // it, so it is current after every restore. A warm database that fails |
| // here is from an earlier stay; the caller parks it and restores cold. |
| @@ -96,7 +123,7 @@ func StartCheck(c *hostclaim.Claim, marker *genselect.Marker, filesGen *FilesGen |
| return ErrStartMarker |
| } |
| h := c.Holder |
| - want := expectedGenerations(h) |
| + want := markerGenerations(h) |
| if marker == nil { |
| if !want[hostclaim.RootGeneration] { |
| return ErrStartMarker |
| @@ -106,8 +133,7 @@ func StartCheck(c *hostclaim.Claim, marker *genselect.Marker, filesGen *FilesGen |
| return ErrStartMarker |
| } |
| if filesGen != nil { |
| - if filesGen.Op == "" || filesGen.Op == hostclaim.RootGeneration || |
| - !want[filesGen.Op] || filesGen.Incarnation != h.Incarnation { |
| + if !filesGenerationOK(h, filesGen.Op) || filesGen.Incarnation != h.Incarnation { |
| return ErrStartFiles |
| } |
| } |