// Start causes the filecache to initialize and start garbage gollection. // // Start is automatically called by the first call to Get, but may be called // explicitly to pre-initialize the cache. func Start() { … } var memCache … type memKey … // Get retrieves from the cache and returns the value most recently // supplied to Set(kind, key), possibly by another process. // Get returns ErrNotFound if the value was not found. // // Callers should not modify the returned array. func Get(kind string, key [32]byte) ([]byte, error) { … } var ErrNotFound … // Set updates the value in the cache. func Set(kind string, key [32]byte, value []byte) error { … } var active … // writeFileNoTrunc is like os.WriteFile but doesn't truncate until // after the write, so that racing writes of the same data are idempotent. func writeFileNoTrunc(filename string, data []byte, perm os.FileMode) error { … } const casKind … const bugKind … var iolimit … var budget … // SetBudget sets a soft limit on disk usage of regular files in the // cache (in bytes) and returns the previous value. Supplying a // negative value queries the current value without changing it. // // If two gopls processes have different budgets, the one with the // lower budget will collect garbage more actively, but both will // observe the effect. // // Even in the steady state, the storage usage reported by the 'du' // command may exceed the budget by as much as a factor of 3 due to // the overheads of directories and the effects of block quantization, // which are especially pronounced for the small index files. func SetBudget(new int64) (old int64) { … } // filename returns the name of the cache file of the specified kind and key. // // A typical cache file has a name such as: // // $HOME/Library/Caches / gopls / VVVVVVVV / KK / KKKK...KKKK - kind // // The portions separated by spaces are as follows: // - The user's preferred cache directory; the default value varies by OS. // - The constant "gopls". // - The "version", 32 bits of the digest of the gopls executable. // - The first 8 bits of the key, to avoid huge directories. // - The full 256 bits of the key. // - The kind or purpose of this cache file (e.g. "analysis"). // // The kind establishes a namespace for the keys. It is represented as // a suffix, not a segment, as this significantly reduces the number // of directories created, and thus the storage overhead. // // Previous iterations of the design aimed for the invariant that once // a file is written, its contents are never modified, though it may // be atomically replaced or removed. However, not all platforms have // an atomic rename operation (our first approach), and file locking // (our second) is a notoriously fickle mechanism. // // The current design instead exploits a trick from the cache // implementation used by the go command: writes of small files are in // practice atomic (all or nothing) on all platforms. // (See GOROOT/src/cmd/go/internal/cache/cache.go.) // // Russ Cox notes: "all file systems use an rwlock around every file // system block, including data blocks, so any writes or reads within // the same block are going to be handled atomically by the FS // implementation without any need to request file locking explicitly. // And since the files are so small, there's only one block. (A block // is at minimum 512 bytes, usually much more.)" And: "all modern file // systems protect against [partial writes due to power loss] with // journals." // // We use a two-level scheme consisting of an index and a // content-addressable store (CAS). A single cache entry consists of // two files. The value of a cache entry is written into the file at // filename("cas", sha256(value)). Since the value may be arbitrarily // large, this write is not atomic. That means we must check the // integrity of the contents read back from the CAS to make sure they // hash to the expected key. If the CAS file is incomplete or // inconsistent, we proceed as if it were missing. // // Once the CAS file has been written, we write a small fixed-size // index file at filename(kind, key), using the values supplied by the // caller. The index file contains the hash that identifies the value // file in the CAS. (We could add extra metadata to this file, up to // 512B, the minimum size of a disk block, if later desired, so long // as the total size remains fixed.) Because the index file is small, // concurrent writes to it are atomic in practice, even though this is // not guaranteed by any OS. The fixed size ensures that readers can't // see a palimpsest when a short new file overwrites a longer old one. // // New versions of gopls are free to reorganize the contents of the // version directory as needs evolve. But all versions of gopls must // in perpetuity treat the "gopls" directory in a common fashion. // // In particular, each gopls process attempts to garbage collect // the entire gopls directory so that newer binaries can clean up // after older ones: in the development cycle especially, new // versions may be created frequently. func filename(kind string, key [32]byte) (string, error) { … } // getCacheDir returns the persistent cache directory of all processes // running this version of the gopls executable. // // It must incorporate the hash of the executable so that we needn't // worry about incompatible changes to the file format or changes to // the algorithm that produced the index. func getCacheDir() (string, error) { … } var cacheDirOnce … var cacheDir … var cacheDirErr … func hashExecutable() (hash [32]byte, err error) { … } // gc runs forever, periodically deleting files from the gopls // directory until the space budget is no longer exceeded, and also // deleting files older than the maximum age, regardless of budget. // // One gopls process may delete garbage created by a different gopls // process, possibly running a different version of gopls, possibly // running concurrently. func gc(goplsDir string) { … } func init() { … } // BugReports returns a new unordered array of the contents // of all cached bug reports produced by this executable. // It also returns the location of the cache directory // used by this process (or "" on initialization error). func BugReports() (string, []bug.Bug) { … }