fstest/testserver: add CleanupAll for end-of-run server sweep

Cleanup today is entirely per-goroutine via the stop closure that Start
returns. If the driver process is killed or panics, those deferred
stops never run and the underlying container keeps running.

Track every remote Start has brought up in a process-local map, and
expose CleanupAll which force-stops each tracked remote via the new
run.bash "force-stop" verb. The returned stop closure is now
sync.Once-wrapped so it and CleanupAll can both fire harmlessly. No
callers yet; wired up in fstest/test_all in a follow-up commit.
This commit is contained in:
Nick Craig-Wood
2026-04-16 17:30:10 +01:00
parent 58a51c2ce9
commit 48a6584baf
+47 -1
View File
@@ -21,6 +21,13 @@ import (
var (
findConfigOnce sync.Once
configDir string // where the config is stored
// trackedServers counts how many live Start/stop pairs this
// process holds for each remote. Used by CleanupAll to force-stop
// everything this process started even if individual stop
// functions never ran (e.g. on signal, panic, fs.Fatalf).
trackedMu sync.Mutex
trackedServers = map[string]int{}
)
// Assume we are run somewhere within the rclone root
@@ -190,9 +197,48 @@ func Start(remote string) (fn func(), err error) {
return nil, err
}
// And return a function to stop it
trackedMu.Lock()
trackedServers[name]++
trackedMu.Unlock()
// And return a function to stop it. The returned closure is
// idempotent: calling it twice (e.g. by defer and by CleanupAll)
// only decrements once.
var once sync.Once
return func() {
once.Do(func() {
trackedMu.Lock()
if trackedServers[name] > 0 {
trackedServers[name]--
}
trackedMu.Unlock()
stop(name)
})
}, nil
}
// CleanupAll force-stops every server this process ever started via
// Start, regardless of refcount. Safe to call multiple times and from
// any goroutine. Intended for end-of-run or signal-handler cleanup in
// long-running test drivers like fstest/test_all.
func CleanupAll() {
trackedMu.Lock()
names := make([]string, 0, len(trackedServers))
for name, count := range trackedServers {
if count > 0 {
names = append(names, name)
}
}
for _, name := range names {
trackedServers[name] = 0
}
trackedMu.Unlock()
for _, name := range names {
fs.Logf(name, "Force-stopping test server")
if _, err := run(name, "force-stop"); err != nil {
fs.Errorf(name, "Failed to force-stop test server: %v", err)
}
}
}