golang.org/x/tools/gopls@v0.15.3/internal/filecache/filecache.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // The filecache package provides a file-based shared durable blob cache. 6 // 7 // The cache is a machine-global mapping from (kind string, key 8 // [32]byte) to []byte, where kind is an identifier describing the 9 // namespace or purpose (e.g. "analysis"), and key is a SHA-256 digest 10 // of the recipe of the value. (It need not be the digest of the value 11 // itself, so you can query the cache without knowing what value the 12 // recipe would produce.) 13 // 14 // The space budget of the cache can be controlled by [SetBudget]. 15 // Cache entries may be evicted at any time or in any order. 16 // Note that "du -sh $GOPLSCACHE" may report a disk usage 17 // figure that is rather larger (e.g. 50%) than the budget because 18 // it rounds up partial disk blocks. 19 // 20 // The Get and Set operations are concurrency-safe. 21 package filecache 22 23 import ( 24 "bytes" 25 "crypto/sha256" 26 "encoding/hex" 27 "encoding/json" 28 "errors" 29 "fmt" 30 "io" 31 "io/fs" 32 "log" 33 "os" 34 "path/filepath" 35 "sort" 36 "strings" 37 "sync" 38 "sync/atomic" 39 "time" 40 41 "golang.org/x/tools/gopls/internal/util/bug" 42 "golang.org/x/tools/gopls/internal/util/lru" 43 ) 44 45 // Start causes the filecache to initialize and start garbage gollection. 46 // 47 // Start is automatically called by the first call to Get, but may be called 48 // explicitly to pre-initialize the cache. 49 func Start() { 50 go getCacheDir() 51 } 52 53 // As an optimization, use a 100MB in-memory LRU cache in front of filecache 54 // operations. This reduces I/O for operations such as diagnostics or 55 // implementations that repeatedly access the same cache entries. 56 var memCache = lru.New(100 * 1e6) 57 58 type memKey struct { 59 kind string 60 key [32]byte 61 } 62 63 // Get retrieves from the cache and returns the value most recently 64 // supplied to Set(kind, key), possibly by another process. 65 // Get returns ErrNotFound if the value was not found. 66 // 67 // Callers should not modify the returned array. 68 func Get(kind string, key [32]byte) ([]byte, error) { 69 // First consult the read-through memory cache. 70 // Note that memory cache hits do not update the times 71 // used for LRU eviction of the file-based cache. 72 if value := memCache.Get(memKey{kind, key}); value != nil { 73 return value.([]byte), nil 74 } 75 76 iolimit <- struct{}{} // acquire a token 77 defer func() { <-iolimit }() // release a token 78 79 // Read the index file, which provides the name of the CAS file. 80 indexName, err := filename(kind, key) 81 if err != nil { 82 return nil, err 83 } 84 indexData, err := os.ReadFile(indexName) 85 if err != nil { 86 if errors.Is(err, os.ErrNotExist) { 87 return nil, ErrNotFound 88 } 89 return nil, err 90 } 91 var valueHash [32]byte 92 if copy(valueHash[:], indexData) != len(valueHash) { 93 return nil, ErrNotFound // index entry has wrong length 94 } 95 96 // Read the CAS file and check its contents match. 97 // 98 // This ensures integrity in all cases (corrupt or truncated 99 // file, short read, I/O error, wrong length, etc) except an 100 // engineered hash collision, which is infeasible. 101 casName, err := filename(casKind, valueHash) 102 if err != nil { 103 return nil, err 104 } 105 value, _ := os.ReadFile(casName) // ignore error 106 if sha256.Sum256(value) != valueHash { 107 return nil, ErrNotFound // CAS file is missing or has wrong contents 108 } 109 110 // Update file times used by LRU eviction. 111 // 112 // Because this turns a read into a write operation, 113 // we follow the approach used in the go command's 114 // cache and update the access time only if the 115 // existing timestamp is older than one hour. 116 // 117 // (Traditionally the access time would be updated 118 // automatically, but for efficiency most POSIX systems have 119 // for many years set the noatime mount option to avoid every 120 // open or read operation entailing a metadata write.) 121 now := time.Now() 122 touch := func(filename string) { 123 st, err := os.Stat(filename) 124 if err == nil && now.Sub(st.ModTime()) > time.Hour { 125 os.Chtimes(filename, now, now) // ignore error 126 } 127 } 128 touch(indexName) 129 touch(casName) 130 131 memCache.Set(memKey{kind, key}, value, len(value)) 132 133 return value, nil 134 } 135 136 // ErrNotFound is the distinguished error 137 // returned by Get when the key is not found. 138 var ErrNotFound = fmt.Errorf("not found") 139 140 // Set updates the value in the cache. 141 func Set(kind string, key [32]byte, value []byte) error { 142 memCache.Set(memKey{kind, key}, value, len(value)) 143 144 // Set the active event to wake up the GC. 145 select { 146 case active <- struct{}{}: 147 default: 148 } 149 150 iolimit <- struct{}{} // acquire a token 151 defer func() { <-iolimit }() // release a token 152 153 // First, add the value to the content- 154 // addressable store (CAS), if not present. 155 hash := sha256.Sum256(value) 156 casName, err := filename(casKind, hash) 157 if err != nil { 158 return err 159 } 160 // Does CAS file exist and have correct (complete) content? 161 // TODO(adonovan): opt: use mmap for this check. 162 if prev, _ := os.ReadFile(casName); !bytes.Equal(prev, value) { 163 if err := os.MkdirAll(filepath.Dir(casName), 0700); err != nil { 164 return err 165 } 166 // Avoiding O_TRUNC here is merely an optimization to avoid 167 // cache misses when two threads race to write the same file. 168 if err := writeFileNoTrunc(casName, value, 0600); err != nil { 169 os.Remove(casName) // ignore error 170 return err // e.g. disk full 171 } 172 } 173 174 // Now write an index entry that refers to the CAS file. 175 indexName, err := filename(kind, key) 176 if err != nil { 177 return err 178 } 179 if err := os.MkdirAll(filepath.Dir(indexName), 0700); err != nil { 180 return err 181 } 182 if err := writeFileNoTrunc(indexName, hash[:], 0600); err != nil { 183 os.Remove(indexName) // ignore error 184 return err // e.g. disk full 185 } 186 187 return nil 188 } 189 190 // The active 1-channel is a selectable resettable event 191 // indicating recent cache activity. 192 var active = make(chan struct{}, 1) 193 194 // writeFileNoTrunc is like os.WriteFile but doesn't truncate until 195 // after the write, so that racing writes of the same data are idempotent. 196 func writeFileNoTrunc(filename string, data []byte, perm os.FileMode) error { 197 f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, perm) 198 if err != nil { 199 return err 200 } 201 _, err = f.Write(data) 202 if err == nil { 203 err = f.Truncate(int64(len(data))) 204 } 205 if closeErr := f.Close(); err == nil { 206 err = closeErr 207 } 208 return err 209 } 210 211 // reserved kind strings 212 const ( 213 casKind = "cas" // content-addressable store files 214 bugKind = "bug" // gopls bug reports 215 ) 216 217 var iolimit = make(chan struct{}, 128) // counting semaphore to limit I/O concurrency in Set. 218 219 var budget int64 = 1e9 // 1GB 220 221 // SetBudget sets a soft limit on disk usage of regular files in the 222 // cache (in bytes) and returns the previous value. Supplying a 223 // negative value queries the current value without changing it. 224 // 225 // If two gopls processes have different budgets, the one with the 226 // lower budget will collect garbage more actively, but both will 227 // observe the effect. 228 // 229 // Even in the steady state, the storage usage reported by the 'du' 230 // command may exceed the budget by as much as a factor of 3 due to 231 // the overheads of directories and the effects of block quantization, 232 // which are especially pronounced for the small index files. 233 func SetBudget(new int64) (old int64) { 234 if new < 0 { 235 return atomic.LoadInt64(&budget) 236 } 237 return atomic.SwapInt64(&budget, new) 238 } 239 240 // --- implementation ---- 241 242 // filename returns the name of the cache file of the specified kind and key. 243 // 244 // A typical cache file has a name such as: 245 // 246 // $HOME/Library/Caches / gopls / VVVVVVVV / KK / KKKK...KKKK - kind 247 // 248 // The portions separated by spaces are as follows: 249 // - The user's preferred cache directory; the default value varies by OS. 250 // - The constant "gopls". 251 // - The "version", 32 bits of the digest of the gopls executable. 252 // - The first 8 bits of the key, to avoid huge directories. 253 // - The full 256 bits of the key. 254 // - The kind or purpose of this cache file (e.g. "analysis"). 255 // 256 // The kind establishes a namespace for the keys. It is represented as 257 // a suffix, not a segment, as this significantly reduces the number 258 // of directories created, and thus the storage overhead. 259 // 260 // Previous iterations of the design aimed for the invariant that once 261 // a file is written, its contents are never modified, though it may 262 // be atomically replaced or removed. However, not all platforms have 263 // an atomic rename operation (our first approach), and file locking 264 // (our second) is a notoriously fickle mechanism. 265 // 266 // The current design instead exploits a trick from the cache 267 // implementation used by the go command: writes of small files are in 268 // practice atomic (all or nothing) on all platforms. 269 // (See GOROOT/src/cmd/go/internal/cache/cache.go.) 270 // 271 // Russ Cox notes: "all file systems use an rwlock around every file 272 // system block, including data blocks, so any writes or reads within 273 // the same block are going to be handled atomically by the FS 274 // implementation without any need to request file locking explicitly. 275 // And since the files are so small, there's only one block. (A block 276 // is at minimum 512 bytes, usually much more.)" And: "all modern file 277 // systems protect against [partial writes due to power loss] with 278 // journals." 279 // 280 // We use a two-level scheme consisting of an index and a 281 // content-addressable store (CAS). A single cache entry consists of 282 // two files. The value of a cache entry is written into the file at 283 // filename("cas", sha256(value)). Since the value may be arbitrarily 284 // large, this write is not atomic. That means we must check the 285 // integrity of the contents read back from the CAS to make sure they 286 // hash to the expected key. If the CAS file is incomplete or 287 // inconsistent, we proceed as if it were missing. 288 // 289 // Once the CAS file has been written, we write a small fixed-size 290 // index file at filename(kind, key), using the values supplied by the 291 // caller. The index file contains the hash that identifies the value 292 // file in the CAS. (We could add extra metadata to this file, up to 293 // 512B, the minimum size of a disk block, if later desired, so long 294 // as the total size remains fixed.) Because the index file is small, 295 // concurrent writes to it are atomic in practice, even though this is 296 // not guaranteed by any OS. The fixed size ensures that readers can't 297 // see a palimpsest when a short new file overwrites a longer old one. 298 // 299 // New versions of gopls are free to reorganize the contents of the 300 // version directory as needs evolve. But all versions of gopls must 301 // in perpetuity treat the "gopls" directory in a common fashion. 302 // 303 // In particular, each gopls process attempts to garbage collect 304 // the entire gopls directory so that newer binaries can clean up 305 // after older ones: in the development cycle especially, new 306 // versions may be created frequently. 307 func filename(kind string, key [32]byte) (string, error) { 308 base := fmt.Sprintf("%x-%s", key, kind) 309 dir, err := getCacheDir() 310 if err != nil { 311 return "", err 312 } 313 // Keep the BugReports function consistent with this one. 314 return filepath.Join(dir, base[:2], base), nil 315 } 316 317 // getCacheDir returns the persistent cache directory of all processes 318 // running this version of the gopls executable. 319 // 320 // It must incorporate the hash of the executable so that we needn't 321 // worry about incompatible changes to the file format or changes to 322 // the algorithm that produced the index. 323 func getCacheDir() (string, error) { 324 cacheDirOnce.Do(func() { 325 // Use user's preferred cache directory. 326 userDir := os.Getenv("GOPLSCACHE") 327 if userDir == "" { 328 var err error 329 userDir, err = os.UserCacheDir() 330 if err != nil { 331 userDir = os.TempDir() 332 } 333 } 334 goplsDir := filepath.Join(userDir, "gopls") 335 336 // UserCacheDir may return a nonexistent directory 337 // (in which case we must create it, which may fail), 338 // or it may return a non-writable directory, in 339 // which case we should ideally respect the user's express 340 // wishes (e.g. XDG_CACHE_HOME) and not write somewhere else. 341 // Sadly UserCacheDir doesn't currently let us distinguish 342 // such intent from accidental misconfiguraton such as HOME=/ 343 // in a CI builder. So, we check whether the gopls subdirectory 344 // can be created (or already exists) and not fall back to /tmp. 345 // See also https://github.com/golang/go/issues/57638. 346 if os.MkdirAll(goplsDir, 0700) != nil { 347 goplsDir = filepath.Join(os.TempDir(), "gopls") 348 } 349 350 // Start the garbage collector. 351 go gc(goplsDir) 352 353 // Compute the hash of this executable (~20ms) and create a subdirectory. 354 hash, err := hashExecutable() 355 if err != nil { 356 cacheDirErr = fmt.Errorf("can't hash gopls executable: %v", err) 357 } 358 // Use only 32 bits of the digest to avoid unwieldy filenames. 359 // It's not an adversarial situation. 360 cacheDir = filepath.Join(goplsDir, fmt.Sprintf("%x", hash[:4])) 361 if err := os.MkdirAll(cacheDir, 0700); err != nil { 362 cacheDirErr = fmt.Errorf("can't create cache: %v", err) 363 } 364 }) 365 return cacheDir, cacheDirErr 366 } 367 368 var ( 369 cacheDirOnce sync.Once 370 cacheDir string 371 cacheDirErr error 372 ) 373 374 func hashExecutable() (hash [32]byte, err error) { 375 exe, err := os.Executable() 376 if err != nil { 377 return hash, err 378 } 379 f, err := os.Open(exe) 380 if err != nil { 381 return hash, err 382 } 383 defer f.Close() 384 h := sha256.New() 385 if _, err := io.Copy(h, f); err != nil { 386 return hash, fmt.Errorf("can't read executable: %w", err) 387 } 388 h.Sum(hash[:0]) 389 return hash, nil 390 } 391 392 // gc runs forever, periodically deleting files from the gopls 393 // directory until the space budget is no longer exceeded, and also 394 // deleting files older than the maximum age, regardless of budget. 395 // 396 // One gopls process may delete garbage created by a different gopls 397 // process, possibly running a different version of gopls, possibly 398 // running concurrently. 399 func gc(goplsDir string) { 400 // period between collections 401 // 402 // Originally the period was always 1 minute, but this 403 // consumed 15% of a CPU core when idle (#61049). 404 // 405 // The reason for running collections even when idle is so 406 // that long lived gopls sessions eventually clean up the 407 // caches created by defunct executables. 408 const ( 409 minPeriod = 5 * time.Minute // when active 410 maxPeriod = 6 * time.Hour // when idle 411 ) 412 413 // Sleep statDelay*batchSize between stats to smooth out I/O. 414 // 415 // The constants below were chosen using the following heuristics: 416 // - 1GB of filecache is on the order of ~100-200k files, in which case 417 // 100μs delay per file introduces 10-20s of additional walk time, 418 // less than the minPeriod. 419 // - Processing batches of stats at once is much more efficient than 420 // sleeping after every stat (due to OS optimizations). 421 const statDelay = 100 * time.Microsecond // average delay between stats, to smooth out I/O 422 const batchSize = 1000 // # of stats to process before sleeping 423 const maxAge = 5 * 24 * time.Hour // max time since last access before file is deleted 424 425 // The macOS filesystem is strikingly slow, at least on some machines. 426 // /usr/bin/find achieves only about 25,000 stats per second 427 // at full speed (no pause between items), meaning a large 428 // cache may take several minutes to scan. 429 // We must ensure that short-lived processes (crucially, 430 // tests) are able to make progress sweeping garbage. 431 // 432 // (gopls' caches should never actually get this big in 433 // practice: the example mentioned above resulted from a bug 434 // that caused filecache to fail to delete any files.) 435 436 const debug = false 437 438 // Names of all directories found in first pass; nil thereafter. 439 dirs := make(map[string]bool) 440 441 for { 442 // Enumerate all files in the cache. 443 type item struct { 444 path string 445 mtime time.Time 446 size int64 447 } 448 var files []item 449 start := time.Now() 450 var total int64 // bytes 451 _ = filepath.Walk(goplsDir, func(path string, stat os.FileInfo, err error) error { 452 if err != nil { 453 return nil // ignore errors 454 } 455 if stat.IsDir() { 456 // Collect (potentially empty) directories. 457 if dirs != nil { 458 dirs[path] = true 459 } 460 } else { 461 // Unconditionally delete files we haven't used in ages. 462 // (We do this here, not in the second loop, so that we 463 // perform age-based collection even in short-lived processes.) 464 age := time.Since(stat.ModTime()) 465 if age > maxAge { 466 if debug { 467 log.Printf("age: deleting stale file %s (%dB, age %v)", 468 path, stat.Size(), age) 469 } 470 os.Remove(path) // ignore error 471 } else { 472 files = append(files, item{path, stat.ModTime(), stat.Size()}) 473 total += stat.Size() 474 if debug && len(files)%1000 == 0 { 475 log.Printf("filecache: checked %d files in %v", len(files), time.Since(start)) 476 } 477 if len(files)%batchSize == 0 { 478 time.Sleep(batchSize * statDelay) 479 } 480 } 481 } 482 return nil 483 }) 484 485 // Sort oldest files first. 486 sort.Slice(files, func(i, j int) bool { 487 return files[i].mtime.Before(files[j].mtime) 488 }) 489 490 // Delete oldest files until we're under budget. 491 budget := atomic.LoadInt64(&budget) 492 for _, file := range files { 493 if total < budget { 494 break 495 } 496 if debug { 497 age := time.Since(file.mtime) 498 log.Printf("budget: deleting stale file %s (%dB, age %v)", 499 file.path, file.size, age) 500 } 501 os.Remove(file.path) // ignore error 502 total -= file.size 503 } 504 files = nil // release memory before sleep 505 506 // Wait unconditionally for the minimum period. 507 time.Sleep(minPeriod) 508 509 // Once only, delete all directories. 510 // This will succeed only for the empty ones, 511 // and ensures that stale directories (whose 512 // files have been deleted) are removed eventually. 513 // They don't take up much space but they do slow 514 // down the traversal. 515 // 516 // We do this after the sleep to minimize the 517 // race against Set, which may create a directory 518 // that is momentarily empty. 519 // 520 // (Test processes don't live that long, so 521 // this may not be reached on the CI builders.) 522 if dirs != nil { 523 dirnames := make([]string, 0, len(dirs)) 524 for dir := range dirs { 525 dirnames = append(dirnames, dir) 526 } 527 dirs = nil 528 529 // Descending length order => children before parents. 530 sort.Slice(dirnames, func(i, j int) bool { 531 return len(dirnames[i]) > len(dirnames[j]) 532 }) 533 var deleted int 534 for _, dir := range dirnames { 535 if os.Remove(dir) == nil { // ignore error 536 deleted++ 537 } 538 } 539 if debug { 540 log.Printf("deleted %d empty directories", deleted) 541 } 542 } 543 544 // Wait up to the max period, 545 // or for Set activity in this process. 546 select { 547 case <-active: 548 case <-time.After(maxPeriod): 549 } 550 } 551 } 552 553 func init() { 554 // Register a handler to durably record this process's first 555 // assertion failure in the cache so that we can ask users to 556 // share this information via the stats command. 557 bug.Handle(func(bug bug.Bug) { 558 // Wait for cache init (bugs in tests happen early). 559 _, _ = getCacheDir() 560 561 data, err := json.Marshal(bug) 562 if err != nil { 563 panic(fmt.Sprintf("error marshalling bug %+v: %v", bug, err)) 564 } 565 566 key := sha256.Sum256(data) 567 _ = Set(bugKind, key, data) 568 }) 569 } 570 571 // BugReports returns a new unordered array of the contents 572 // of all cached bug reports produced by this executable. 573 // It also returns the location of the cache directory 574 // used by this process (or "" on initialization error). 575 func BugReports() (string, []bug.Bug) { 576 // To test this logic, run: 577 // $ TEST_GOPLS_BUG=oops gopls bug # trigger a bug 578 // $ gopls stats # list the bugs 579 580 dir, err := getCacheDir() 581 if err != nil { 582 return "", nil // ignore initialization errors 583 } 584 var result []bug.Bug 585 _ = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { 586 if err != nil { 587 return nil // ignore readdir/stat errors 588 } 589 // Parse the key from each "XXXX-bug" cache file name. 590 if !info.IsDir() && strings.HasSuffix(path, bugKind) { 591 var key [32]byte 592 n, err := hex.Decode(key[:], []byte(filepath.Base(path)[:len(key)*2])) 593 if err != nil || n != len(key) { 594 return nil // ignore malformed file names 595 } 596 content, err := Get(bugKind, key) 597 if err == nil { // ignore read errors 598 var b bug.Bug 599 if err := json.Unmarshal(content, &b); err != nil { 600 log.Printf("error marshalling bug %q: %v", string(content), err) 601 } 602 result = append(result, b) 603 } 604 } 605 return nil 606 }) 607 return dir, result 608 }