gopls/internal/lsp/filecache: actually delete files

This change fixes an embarrassing blunder: the filename
we gave to os.Rename was absolutized twice (goplsDir+goplsDir+path),
so of course it was not found. The error was rightly ignored,
but this meant the bug was undetected. CI builder machines
filled their disks.

Also, this change causes filecache's GC to delete files older than
maxAge as soon as it encounters them, instead of in the second pass
over the sorted list of all files in the cache. This should
allow short-lived processes (e.g. tests) to make progress on
garbage collection. Though this now seems like a distinctly
third-order effect compared to... not deleting files at all.

Also:
- don't delay between stats after deleting files based on age.
- reduce the statDelay to 100us (was 1ms). Scanning a file tree
  on macOS is already very slow, at least on my Google-issued
  machine.
- reduce maxAge to 5 days (was 7), which should still tide most
  users over a long weekend.

Fixes golang/go#57900

Change-Id: I053f2891d6c52c94f4d5dd18903280dff2282eab
Reviewed-on: https://go-review.googlesource.com/c/tools/+/462597
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
This commit is contained in:
Alan Donovan 2023-01-18 14:43:08 -05:00
Родитель 9682b0d473
Коммит 561a9be679
1 изменённых файлов: 40 добавлений и 15 удалений

Просмотреть файл

@ -243,9 +243,22 @@ func hashExecutable() (hash [32]byte, err error) {
// process, possibly running a different version of gopls, possibly // process, possibly running a different version of gopls, possibly
// running concurrently. // running concurrently.
func gc(goplsDir string) { func gc(goplsDir string) {
const period = 1 * time.Minute // period between collections const period = 1 * time.Minute // period between collections
const statDelay = 1 * time.Millisecond // delay between stats to smooth out I/O const statDelay = 100 * time.Microsecond // delay between stats to smooth out I/O
const maxAge = 7 * 24 * time.Hour // max time since last access before file is deleted const maxAge = 5 * 24 * time.Hour // max time since last access before file is deleted
// The macOS filesystem is strikingly slow, at least on some machines.
// /usr/bin/find achieves only about 25,000 stats per second
// at full speed (no pause between items), meaning a large
// cache may take several minutes to scan.
// We must ensure that short-lived processes (crucially,
// tests) are able to make progress sweeping garbage.
//
// (gopls' caches should never actually get this big in
// practise: the example mentioned above resulted from a bug
// that caused filecache to fail to delete any files.)
const debug = false
for { for {
// Enumerate all files in the cache. // Enumerate all files in the cache.
@ -259,9 +272,21 @@ func gc(goplsDir string) {
// TODO(adonovan): opt: also collect empty directories, // TODO(adonovan): opt: also collect empty directories,
// as they typically occupy around 1KB. // as they typically occupy around 1KB.
if err == nil && !stat.IsDir() { if err == nil && !stat.IsDir() {
files = append(files, item{path, stat}) // Unconditionally delete files we haven't used in ages.
total += stat.Size() // (We do this here, not in the second loop, so that we
time.Sleep(statDelay) // perform age-based collection even in short-lived processes.)
age := time.Since(stat.ModTime())
if age > maxAge {
if debug {
log.Printf("age: deleting stale file %s (%dB, age %v)",
path, stat.Size(), age)
}
os.Remove(path) // ignore error
} else {
files = append(files, item{path, stat})
total += stat.Size()
time.Sleep(statDelay)
}
} }
return nil return nil
}) })
@ -272,18 +297,18 @@ func gc(goplsDir string) {
}) })
// Delete oldest files until we're under budget. // Delete oldest files until we're under budget.
// Unconditionally delete files we haven't used in ages.
budget := atomic.LoadInt64(&budget) budget := atomic.LoadInt64(&budget)
for _, file := range files { for _, file := range files {
age := time.Since(file.stat.ModTime()) if total < budget {
if total > budget || age > maxAge { break
if false { // debugging
log.Printf("deleting stale file %s (%dB, age %v)",
file.path, file.stat.Size(), age)
}
os.Remove(filepath.Join(goplsDir, file.path)) // ignore error
total -= file.stat.Size()
} }
if debug {
age := time.Since(file.stat.ModTime())
log.Printf("budget: deleting stale file %s (%dB, age %v)",
file.path, file.stat.Size(), age)
}
os.Remove(file.path) // ignore error
total -= file.stat.Size()
} }
time.Sleep(period) time.Sleep(period)