R-1342: a restore copy under the stable-start watch is not deletable (D62 gap); an app removed during the watch raises no alarm — tests red first; commit only
gates / gates (push) Successful in 31s

This commit is contained in:
admin committed 2026-10-29 11:16:13 +01:00
1 parent 6c72e3ff34
commit 652a690e5d
10 files changed
+231 -1

No files matched your search

@@ -71,9 +71,14 @@ func orNone(s string) string {
// so and the kept copy is removed (the normal rule after a successful restore). A new restore of the app ends the watch
// untouched.
func (m *Manager) watchLateStart(stack, copyDir string, since time.Time, watch time.Duration) {
m.setWatching(copyDir, true) // R-1342: the copy is the way back while the app may still start
defer m.setWatching(copyDir, false)
switch m.waitStableStart(stack, since, since.Add(watch)) {
case stableAborted:
return // a new restore of this app owns it now
case stableRemoved:
m.logger.Printf("[INFO] [backup] %s: the app was removed while the box waited for it to start — the watch ends (R-1342)", stack)
return
case stableNot:
m.logger.Printf("[WARN] [backup] %s: still not running stably %s after its restore — the restore copy stays for support (%s) (R-1270)", stack, lateStartWatch, orNone(copyDir))
return
@@ -133,12 +138,26 @@ type containerSampler interface {
ContainerSamples(name string) ([]ContainerSample, error)
}
// StackRemovalReporter (R-1342) is the provider's optional answer to „was this app removed?" — true only when the app is
// known and no longer installed. A watch that sees it ends quietly: an app the household removed did not „fail to
// start". The production stackAdapter implements it (a compile-time assertion in cmd/controller/main.go).
type StackRemovalReporter interface {
StackRemoved(name string) bool
}
// stackRemoved asks the provider; a provider without the answer never reads „removed".
func (m *Manager) stackRemoved(stack string) bool {
r, ok := m.stackProvider.(StackRemovalReporter)
return ok && r.StackRemoved(stack)
}
type stableResult int
const (
stableYes stableResult = iota
stableNot
stableAborted
stableRemoved // R-1342: the app was removed during the watch
)
// stableSample is one look: ok, and the fingerprint (container id → restart count) a stable window must keep.
@@ -201,6 +220,9 @@ func (m *Manager) waitStableFor(stack string, watchFrom, deadline time.Time, win
if m.restoreBegunAfter(stack, watchFrom) {
return stableAborted
}
if m.stackRemoved(stack) {
return stableRemoved
}
ok, fp := m.stableSample(stack)
switch {
case !ok:
@@ -226,13 +248,17 @@ func (m *Manager) releaseAfterStableStart(stack, kind string, undo *liveVolumeCo
dir := undo.dir
from := time.Now() // taken HERE, before the goroutine: a restore begun after this moment is a new one
until := from.Add(stableReleaseWatch)
m.setWatching(dir, true) // R-1342: „Törlés most" refuses the copy while the watch holds it
go func() {
defer m.setWatching(dir, false)
switch m.waitStableStart(stack, from, until) {
case stableYes:
removeCopyDir(m, stack, dir)
m.logger.Printf("[INFO] [backup] %s: ran stably for %s after its %s — the restore copy is released (R-1270)", stack, stableStartWindow, kind)
case stableAborted:
m.logger.Printf("[INFO] [backup] %s: a new restore began before the %s's start was stable — its restore copy stays for support (%s)", stack, kind, dir)
case stableRemoved:
m.logger.Printf("[INFO] [backup] %s: the app was removed during the stable-start check after its %s — no alarm; the restore copy, if the remove left it, is an unused one (%s) (R-1342)", stack, kind, dir)
default:
m.logger.Printf("[ERROR] [backup] %s: did not run stably within %s after its %s — the restore copy stays for support (%s) (R-1270)", stack, stableReleaseWatch, kind, dir)
if m.restoreNotStartedNotify != nil {
@@ -262,6 +288,8 @@ func (m *Manager) restoredAppStarted(stack string) error {
return nil
case stableAborted:
return fmt.Errorf("a new restore of %s began", stack)
case stableRemoved:
return fmt.Errorf("%s was removed during the restore's start check", stack)
}
return fmt.Errorf("stack %s did not run stably for %s (it restarted or turned unhealthy) after restore", stack, verdictStableWindow)
}