Keep a clean look below a share for an hour, so the backup check does not walk the library every 30 s

This commit is contained in:
jochen
2026-10-08 20:20:05 +02:00
parent 8738c02d00
commit e50978ca45
3 changed files with 62 additions and 0 deletions
@@ -10,6 +10,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
)
@@ -101,6 +102,9 @@ type Machine struct {
// WalkLimit bounds the look for a backup repository below a share.
WalkLimit int
owners map[string]Owner
// clean is when a look below each real path last found no backup repository (LookKept).
clean map[string]time.Time
cleanMu sync.Mutex
}
// Here is the machine this process runs on.
@@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
"testing"
"time"
)
// The review of 2026-10-08 (mesh-catalog #136): each finding, reproduced.
@@ -311,3 +312,30 @@ func TestWantsFromTheMountsStateKeepOnlyWhatIsAskedOfThisMachine(t *testing.T) {
t.Errorf("the tools may write the wants file: %v", err)
}
}
// The look below a share for a backup repository walks up to WalkLimit folders; on a library of tens of
// thousands of folders that must not run on every 30 s pass. A clean result is kept for LookKept, and a
// repository put there later is found once that has passed.
func TestTheLookBelowIsKeptForAWhileAndThenTakenAgain(t *testing.T) {
f := newFake(t)
f.mountinfo("/")
f.dir("/srv/clean/films/one")
f.file(ConfigFile, grantsConfig("clean=/srv/clean", "clean=ws:rw"))
f.roster("ws 10.0.0.3")
f.wants(map[string][]Want{"ws": {{Share: "clean", Access: "rw"}}})
m := f.machine()
now := time.Date(2026, 10, 8, 19, 0, 0, 0, time.UTC)
m.Now = func() time.Time { return now }
if s := m.Pass(context.Background(), nil); refusedWhy(s, "clean") != "" {
t.Fatalf("a clean folder is refused: %+v", s.Refused)
}
f.restic("/srv/clean/films/one/backup")
now = now.Add(time.Minute)
if s := m.Pass(context.Background(), nil); refusedWhy(s, "clean") != "" {
t.Errorf("the look was taken again within a minute: %+v", s.Refused)
}
now = now.Add(LookKept)
if s := m.Pass(context.Background(), nil); !strings.Contains(refusedWhy(s, "clean"), "/srv/clean/films/one/backup") {
t.Errorf("a repository put there later was not found once the kept look expired: %+v", s.Refused)
}
}
@@ -6,6 +6,7 @@ import (
"path"
"sort"
"strings"
"time"
)
// The shares, from the assignment, and the export lines made from them (novox/hq ADR 0263).
@@ -242,9 +243,38 @@ const (
WalkLimit = 200000
)
// LookKept is how long a look below a share that found no backup repository is believed. The look walks up
// to WalkLimit folders, which on a large library is too much for every 30 s pass; a repository put under a
// share is found within this long. A look that found one, or was cut short, is never kept: the share stays
// refused and is looked at again on the next pass.
const LookKept = time.Hour
// repositoryBelow finds a backup repository within WalkDepth levels below dir. complete is false when
// the look stopped at its bound before it saw everything.
func (m *Machine) repositoryBelow(dir string) (found string, complete bool) {
now := time.Now()
if m.Now != nil {
now = m.Now()
}
m.cleanMu.Lock()
defer m.cleanMu.Unlock()
if at, kept := m.clean[dir]; kept && now.Sub(at) < LookKept {
return "", true
}
found, complete = m.walkBelow(dir)
if found == "" && complete {
if m.clean == nil {
m.clean = map[string]time.Time{}
}
m.clean[dir] = now
} else {
delete(m.clean, dir)
}
return found, complete
}
// walkBelow is the look itself, breadth first, bounded by WalkDepth and WalkLimit.
func (m *Machine) walkBelow(dir string) (found string, complete bool) {
limit := m.WalkLimit
if limit <= 0 {
limit = WalkLimit