mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
7d2106bfb5
* fix(test): reclaim fixture scratch dirs stranded in /tmp The shared predastore fixture and the integration suite's JetStream store both outlive any individual test, so neither can use t.TempDir() - the first test to finish would delete files the daemon still has open. Both used os.MkdirTemp with nothing removing the directory afterwards, so every run stranded one, and a run killed by a timeout panic or the OOM killer stranded it regardless. /tmp is a tmpfs on the dev boxes, so the leak is charged to RAM and swap, and swapped tmpfs pages have no backing store to be dropped to. Add a shared sweep that reclaims a directory only when it matches both the prefix and an age no live run can reach. The NATS store also has a clean shutdown point, so TestMain now removes it outright and the sweep is left to cover the killed-run case. The predastore daemon runs until the process exits and has no such point, so the sweep is its only mechanism. * chore: repin viperblock to the dev tip carrying the scratch-dir fix Test-only upstream change, so the build output is unchanged - this keeps go.mod aligned with the submodule pointer rather than delivering a fix.
58 lines
2.0 KiB
Go
58 lines
2.0 KiB
Go
// Package scratch reclaims test scratch directories stranded in the system
|
|
// temp directory by earlier runs.
|
|
//
|
|
// Fixtures whose daemon outlives any individual test cannot use t.TempDir():
|
|
// the first test to finish would delete files the shared daemon still has
|
|
// open. They use os.MkdirTemp instead, which nothing then removes when the
|
|
// process dies without running its cleanups — a test timeout panic or an OOM
|
|
// kill. /tmp is a tmpfs on the dev boxes, so a stranded directory is charged
|
|
// to RAM and swap rather than disk, and swapped tmpfs pages have no backing
|
|
// store to be dropped to: they are never reclaimed until the files go.
|
|
package scratch
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// DefaultMaxAge is how old a stranded directory must be before it is
|
|
// reclaimed. A full run takes minutes, so a directory this old cannot belong
|
|
// to a live test binary.
|
|
const DefaultMaxAge = 6 * time.Hour
|
|
|
|
// SweepAbandoned removes directories under tmpDir that match prefix and are
|
|
// older than maxAge.
|
|
//
|
|
// Deleting shared scratch deserves caution, so a candidate must match on both
|
|
// the prefix and the age: a concurrently running test binary's directory is
|
|
// minutes old at most and can never qualify. Failures are reported and
|
|
// otherwise ignored — not reclaiming disk must never fail the run.
|
|
func SweepAbandoned(tmpDir, prefix string, maxAge time.Duration) {
|
|
entries, err := os.ReadDir(tmpDir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "scratch sweep: read %s: %v\n", tmpDir, err)
|
|
return
|
|
}
|
|
|
|
cutoff := time.Now().Add(-maxAge)
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() || !strings.HasPrefix(entry.Name(), prefix) {
|
|
continue
|
|
}
|
|
info, err := entry.Info()
|
|
if err != nil || info.ModTime().After(cutoff) {
|
|
continue
|
|
}
|
|
|
|
path := filepath.Join(tmpDir, entry.Name())
|
|
if err := os.RemoveAll(path); err != nil {
|
|
fmt.Fprintf(os.Stderr, "scratch sweep: remove %s: %v\n", path, err)
|
|
continue
|
|
}
|
|
fmt.Fprintf(os.Stderr, "scratch sweep: reclaimed abandoned scratch dir %s\n", path)
|
|
}
|
|
}
|