R-1256: four restore edges (review round 2 HUNT-1 H4/H5/H6/H8) — each test seen failing first
gates / gates (push) Successful in 31s

H4: a failed definition write in the off-site restore writes the live definition back before the start;
    a failed write-back holds the app (restore_mixed).
H5: a put-back whose stop fails pours nothing, keeps the copy and holds the app (off-site, local, power-cut);
    a failed definition write-back keeps the copy (D20); a failed 'volume rm -f' fails that volume (no merge).
H6: TryBeginRestoreOp/RestoreBusy: check every operation and record the restore in one step under m.mu;
    every web handler and the kept loads begin through it; install-time kept load asks about the mirror too.
H8: SweepUnusedRestoreCopies every 6 h: removes a copy only with no hold, no marker naming it, no restore
    running, and older than 14 days.

Commit only; rides the next controller release.
This commit is contained in:
admin committed 2026-10-28 22:24:16 +01:00
1 parent ed3927f03a
commit 342a394bff
20 files changed
+860 -50

No files matched your search

+26 -5
View File
@@ -301,8 +301,12 @@ func (m *Manager) downloadKeptOffsiteUnit(ctx context.Context, app, drive string
// LoadKeptOffsite is LoadKeptApp from the off-site copy c: download the unit alone, judge it, then
// prepare (nil = nothing; a dated folder's files move back here), then the one unit restore, then
// after(ok). A download failure or a refusal never reaches prepare, so the kept files stay as they were.
func (m *Manager) LoadKeptOffsite(app, drive string, c KeptCopy, prepare func() error, okMsg, failMsg func(err error) string, after func(ok bool)) {
m.BeginRestoreOp("restore", app)
// ErrRestoreBusy (nothing started, after not called) when another operation runs (R-1256 H6).
func (m *Manager) LoadKeptOffsite(app, drive string, c KeptCopy, prepare func() error, okMsg, failMsg func(err error) string, after func(ok bool)) error {
if busy, _ := m.TryBeginRestoreOp("restore", app); busy != "" {
m.logger.Printf("[WARN] [backup] kept load %s REFUSED: a %s operation runs (R-1256)", app, busy)
return ErrRestoreBusy
}
go func() {
start := time.Now()
fail := func(err error) {
@@ -345,6 +349,7 @@ func (m *Manager) LoadKeptOffsite(app, drive string, c KeptCopy, prepare func()
after(true)
}
}()
return nil
}
// RemovedUnitOnDrive is the start-fresh hook (stacks.SetKeptUnitFinder): the removed app's OWN unit when
@@ -370,11 +375,26 @@ func (m *Manager) PrimaryUnitHome(app, drive string) string {
// LoadKeptApp runs the load as a restore operation the backup pages already follow (the poll banner):
// Begin → RestoreFromRecoveryUnitAt(app, unitDir) → End, then after(ok) in the same goroutine (the
// after_load command, the kept folder's tidy-up). The caller has checked RestoreStatus/IsRunning.
func (m *Manager) LoadKeptApp(app, unitDir string, okMsg, failMsg func(err error) string, after func(ok bool)) {
m.BeginRestoreOp("restore", app)
// after_load command, the kept folder's tidy-up). prepare (nil = nothing) runs first, inside the operation — a dated
// folder's files move back there. R-1256 H6: the op is begun in the same step as the check for every other operation;
// a refusal returns ErrRestoreBusy with nothing started and nothing moved.
func (m *Manager) LoadKeptApp(app, unitDir string, prepare func() error, okMsg, failMsg func(err error) string, after func(ok bool)) error {
if busy, _ := m.TryBeginRestoreOp("restore", app); busy != "" {
m.logger.Printf("[WARN] [backup] kept load %s REFUSED: a %s operation runs (R-1256)", app, busy)
return ErrRestoreBusy
}
go func() {
start := time.Now()
if prepare != nil {
if perr := prepare(); perr != nil {
m.logger.Printf("[ERROR] [backup] kept load %s: preparing the kept files FAILED: %v — nothing was loaded", app, perr)
m.EndRestoreOp(false, failMsg(perr))
if after != nil {
after(false)
}
return
}
}
res, err := m.keptUnitRestore()(app, unitDir)
if errors.Is(err, ErrRestoreNotStarted) {
m.keptLoadNotStarted(app, err, after)
@@ -395,6 +415,7 @@ func (m *Manager) LoadKeptApp(app, unitDir string, okMsg, failMsg func(err error
after(true)
}
}()
return nil
}
// keptLoadNotStarted (R-1270): the kept data is loaded but the app did not start in time. Never „done": the record says