Skip to content

Commit 5919673

Browse files
committed
internal/lsp/filecache: eliminate 'kind' directories
This CL causes the kind component of each cache file to be represented as a suffix, and no longer a complete path segment. This avoids the creation of 7 directory trees (6 application kinds + cas) each containing 256 subdirectories. The proliferation of kinds was causing the storage requirements to increase well beyond (2.2x) the nominal budget, because the accounting for the latter ignores directories. This also reduces the number of directory lookups required for each file operation. Also, report the GOPLSCACHE environment variable and the computed executable-specific cache directory in the output of 'gopls stats'. Change-Id: Ibbebbf2bc10afd08b84444b8f71d0d110d5ae655 Reviewed-on: https://go-review.googlesource.com/c/tools/+/496437 Reviewed-by: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Alan Donovan <[email protected]>
1 parent a5ef6c3 commit 5919673

File tree

2 files changed

+46
-31
lines changed

2 files changed

+46
-31
lines changed

gopls/internal/lsp/cmd/stats.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (s *stats) Run(ctx context.Context, args ...string) error {
6868
stats := GoplsStats{
6969
GOOS: runtime.GOOS,
7070
GOARCH: runtime.GOARCH,
71+
GOPLSCACHE: os.Getenv("GOPLSCACHE"),
7172
GoVersion: runtime.Version(),
7273
GoplsVersion: debug.Version,
7374
}
@@ -140,7 +141,9 @@ func (s *stats) Run(ctx context.Context, args ...string) error {
140141
// this executable and persisted in the cache.
141142
stats.BugReports = []string{} // non-nil for JSON
142143
do("Gathering bug reports", func() error {
143-
for _, report := range filecache.BugReports() {
144+
cacheDir, reports := filecache.BugReports()
145+
stats.CacheDir = cacheDir
146+
for _, report := range reports {
144147
stats.BugReports = append(stats.BugReports, string(report))
145148
}
146149
return nil
@@ -193,10 +196,11 @@ func (s *stats) Run(ctx context.Context, args ...string) error {
193196
}
194197

195198
type GoplsStats struct {
196-
GOOS, GOARCH string
199+
GOOS, GOARCH, GOPLSCACHE string
197200
GoVersion string
198201
GoplsVersion string
199202
InitialWorkspaceLoadDuration string // in time.Duration string form
203+
CacheDir string
200204
BugReports []string
201205
MemStats command.MemStatsResult
202206
WorkspaceStats command.WorkspaceStatsResult

gopls/internal/lsp/filecache/filecache.go

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"os"
3333
"path/filepath"
3434
"sort"
35+
"strings"
3536
"sync"
3637
"sync/atomic"
3738
"time"
@@ -203,7 +204,11 @@ func writeFileNoTrunc(filename string, data []byte, perm os.FileMode) error {
203204
return err
204205
}
205206

206-
const casKind = "cas" // kind for CAS (content-addressable store) files
207+
// reserved kind strings
208+
const (
209+
casKind = "cas" // content-addressable store files
210+
bugKind = "bug" // gopls bug reports
211+
)
207212

208213
var iolimit = make(chan struct{}, 128) // counting semaphore to limit I/O concurrency in Set.
209214

@@ -233,15 +238,19 @@ func SetBudget(new int64) (old int64) {
233238
//
234239
// A typical cache file has a name such as:
235240
//
236-
// $HOME/Library/Caches / gopls / VVVVVVVV / kind / KK / KKKK...KKKK
241+
// $HOME/Library/Caches / gopls / VVVVVVVV / KK / KKKK...KKKK - kind
237242
//
238243
// The portions separated by spaces are as follows:
239244
// - The user's preferred cache directory; the default value varies by OS.
240245
// - The constant "gopls".
241246
// - The "version", 32 bits of the digest of the gopls executable.
242-
// - The kind or purpose of this cache subtree (e.g. "analysis").
243247
// - The first 8 bits of the key, to avoid huge directories.
244248
// - The full 256 bits of the key.
249+
// - The kind or purpose of this cache file (e.g. "analysis").
250+
//
251+
// The kind establishes a namespace for the keys. It is represented as
252+
// a suffix, not a segment, as this significantly reduces the number
253+
// of directories created, and thus the storage overhead.
245254
//
246255
// Previous iterations of the design aimed for the invariant that once
247256
// a file is written, its contents are never modified, though it may
@@ -290,16 +299,14 @@ func SetBudget(new int64) (old int64) {
290299
// the entire gopls directory so that newer binaries can clean up
291300
// after older ones: in the development cycle especially, new
292301
// new versions may be created frequently.
293-
//
294-
// TODO(adonovan): opt: use "VVVVVVVV / KK / KKKK...KKKK-kind" to
295-
// avoid creating 256 directories per distinct kind (+ cas).
296302
func filename(kind string, key [32]byte) (string, error) {
297-
hex := fmt.Sprintf("%x", key)
303+
base := fmt.Sprintf("%x-%s", key, kind)
298304
dir, err := getCacheDir()
299305
if err != nil {
300306
return "", err
301307
}
302-
return filepath.Join(dir, kind, hex[:2], hex), nil
308+
// Keep the BugReports function consistent with this one.
309+
return filepath.Join(dir, base[:2], base), nil
303310
}
304311

305312
// getCacheDir returns the persistent cache directory of all processes
@@ -526,8 +533,6 @@ func gc(goplsDir string) {
526533
}
527534
}
528535

529-
const bugKind = "bug" // reserved kind for gopls bug reports
530-
531536
func init() {
532537
// Register a handler to durably record this process's first
533538
// assertion failure in the cache so that we can ask users to
@@ -544,29 +549,35 @@ func init() {
544549

545550
// BugReports returns a new unordered array of the contents
546551
// of all cached bug reports produced by this executable.
547-
func BugReports() [][]byte {
552+
// It also returns the location of the cache directory
553+
// used by this process (or "" on initialization error).
554+
func BugReports() (string, [][]byte) {
555+
// To test this logic, run:
556+
// $ TEST_GOPLS_BUG=oops gopls stats # trigger a bug
557+
// $ gopls stats # list the bugs
558+
548559
dir, err := getCacheDir()
549560
if err != nil {
550-
return nil // ignore initialization errors
561+
return "", nil // ignore initialization errors
551562
}
552563
var result [][]byte
553-
_ = filepath.Walk(filepath.Join(dir, bugKind),
554-
func(path string, info fs.FileInfo, err error) error {
555-
if err != nil {
556-
return nil // ignore readdir/stat errors
564+
_ = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
565+
if err != nil {
566+
return nil // ignore readdir/stat errors
567+
}
568+
// Parse the key from each "XXXX-bug" cache file name.
569+
if !info.IsDir() && strings.HasSuffix(path, bugKind) {
570+
var key [32]byte
571+
n, err := hex.Decode(key[:], []byte(filepath.Base(path)[:len(key)*2]))
572+
if err != nil || n != len(key) {
573+
return nil // ignore malformed file names
557574
}
558-
if !info.IsDir() {
559-
var key [32]byte
560-
n, err := hex.Decode(key[:], []byte(filepath.Base(path)))
561-
if err != nil || n != len(key) {
562-
return nil // ignore malformed file names
563-
}
564-
content, err := Get(bugKind, key)
565-
if err == nil { // ignore read errors
566-
result = append(result, content)
567-
}
575+
content, err := Get(bugKind, key)
576+
if err == nil { // ignore read errors
577+
result = append(result, content)
568578
}
569-
return nil
570-
})
571-
return result
579+
}
580+
return nil
581+
})
582+
return dir, result
572583
}

0 commit comments

Comments
 (0)