github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/cache/cache.go (about)

     1  // Copyright 2019 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  package cache
     6  
     7  import (
     8  	"context"
     9  	"crypto/sha256"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/token"
    13  	"go/types"
    14  	"html/template"
    15  	"io/ioutil"
    16  	"os"
    17  	"reflect"
    18  	"sort"
    19  	"strconv"
    20  	"sync"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	"github.com/jhump/golang-x-tools/internal/event"
    25  	"github.com/jhump/golang-x-tools/internal/gocommand"
    26  	"github.com/jhump/golang-x-tools/internal/lsp/debug/tag"
    27  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    28  	"github.com/jhump/golang-x-tools/internal/memoize"
    29  	"github.com/jhump/golang-x-tools/internal/span"
    30  )
    31  
    32  func New(options func(*source.Options)) *Cache {
    33  	index := atomic.AddInt64(&cacheIndex, 1)
    34  	c := &Cache{
    35  		id:          strconv.FormatInt(index, 10),
    36  		fset:        token.NewFileSet(),
    37  		options:     options,
    38  		fileContent: map[span.URI]*fileHandle{},
    39  	}
    40  	return c
    41  }
    42  
    43  type Cache struct {
    44  	id      string
    45  	fset    *token.FileSet
    46  	options func(*source.Options)
    47  
    48  	store memoize.Store
    49  
    50  	fileMu      sync.Mutex
    51  	fileContent map[span.URI]*fileHandle
    52  }
    53  
    54  type fileHandle struct {
    55  	modTime time.Time
    56  	uri     span.URI
    57  	bytes   []byte
    58  	hash    string
    59  	err     error
    60  
    61  	// size is the file length as reported by Stat, for the purpose of
    62  	// invalidation. Probably we could just use len(bytes), but this is done
    63  	// defensively in case the definition of file size in the file system
    64  	// differs.
    65  	size int64
    66  }
    67  
    68  func (h *fileHandle) Saved() bool {
    69  	return true
    70  }
    71  
    72  func (c *Cache) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
    73  	return c.getFile(ctx, uri)
    74  }
    75  
    76  func (c *Cache) getFile(ctx context.Context, uri span.URI) (*fileHandle, error) {
    77  	fi, statErr := os.Stat(uri.Filename())
    78  	if statErr != nil {
    79  		return &fileHandle{
    80  			err: statErr,
    81  			uri: uri,
    82  		}, nil
    83  	}
    84  
    85  	c.fileMu.Lock()
    86  	fh, ok := c.fileContent[uri]
    87  	c.fileMu.Unlock()
    88  
    89  	// Check mtime and file size to infer whether the file has changed. This is
    90  	// an imperfect heuristic. Notably on some real systems (such as WSL) the
    91  	// filesystem clock resolution can be large -- 1/64s was observed. Therefore
    92  	// it's quite possible for multiple file modifications to occur within a
    93  	// single logical 'tick'. This can leave the cache in an incorrect state, but
    94  	// unfortunately we can't afford to pay the price of reading the actual file
    95  	// content here. Or to be more precise, reading would be a risky change and
    96  	// we don't know if we can afford it.
    97  	//
    98  	// We check file size in an attempt to reduce the probability of false cache
    99  	// hits.
   100  	if ok && fh.modTime.Equal(fi.ModTime()) && fh.size == fi.Size() {
   101  		return fh, nil
   102  	}
   103  
   104  	fh, err := readFile(ctx, uri, fi)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	c.fileMu.Lock()
   109  	c.fileContent[uri] = fh
   110  	c.fileMu.Unlock()
   111  	return fh, nil
   112  }
   113  
   114  // ioLimit limits the number of parallel file reads per process.
   115  var ioLimit = make(chan struct{}, 128)
   116  
   117  func readFile(ctx context.Context, uri span.URI, fi os.FileInfo) (*fileHandle, error) {
   118  	select {
   119  	case ioLimit <- struct{}{}:
   120  	case <-ctx.Done():
   121  		return nil, ctx.Err()
   122  	}
   123  	defer func() { <-ioLimit }()
   124  
   125  	ctx, done := event.Start(ctx, "cache.readFile", tag.File.Of(uri.Filename()))
   126  	_ = ctx
   127  	defer done()
   128  
   129  	data, err := ioutil.ReadFile(uri.Filename())
   130  	if err != nil {
   131  		return &fileHandle{
   132  			modTime: fi.ModTime(),
   133  			size:    fi.Size(),
   134  			err:     err,
   135  		}, nil
   136  	}
   137  	return &fileHandle{
   138  		modTime: fi.ModTime(),
   139  		size:    fi.Size(),
   140  		uri:     uri,
   141  		bytes:   data,
   142  		hash:    hashContents(data),
   143  	}, nil
   144  }
   145  
   146  func (c *Cache) NewSession(ctx context.Context) *Session {
   147  	index := atomic.AddInt64(&sessionIndex, 1)
   148  	options := source.DefaultOptions().Clone()
   149  	if c.options != nil {
   150  		c.options(options)
   151  	}
   152  	s := &Session{
   153  		cache:       c,
   154  		id:          strconv.FormatInt(index, 10),
   155  		options:     options,
   156  		overlays:    make(map[span.URI]*overlay),
   157  		gocmdRunner: &gocommand.Runner{},
   158  	}
   159  	event.Log(ctx, "New session", KeyCreateSession.Of(s))
   160  	return s
   161  }
   162  
   163  func (c *Cache) FileSet() *token.FileSet {
   164  	return c.fset
   165  }
   166  
   167  func (h *fileHandle) URI() span.URI {
   168  	return h.uri
   169  }
   170  
   171  func (h *fileHandle) Hash() string {
   172  	return h.hash
   173  }
   174  
   175  func (h *fileHandle) FileIdentity() source.FileIdentity {
   176  	return source.FileIdentity{
   177  		URI:  h.uri,
   178  		Hash: h.hash,
   179  	}
   180  }
   181  
   182  func (h *fileHandle) Read() ([]byte, error) {
   183  	return h.bytes, h.err
   184  }
   185  
   186  func hashContents(contents []byte) string {
   187  	return fmt.Sprintf("%x", sha256.Sum256(contents))
   188  }
   189  
   190  var cacheIndex, sessionIndex, viewIndex int64
   191  
   192  func (c *Cache) ID() string                     { return c.id }
   193  func (c *Cache) MemStats() map[reflect.Type]int { return c.store.Stats() }
   194  
   195  type packageStat struct {
   196  	id        PackageID
   197  	mode      source.ParseMode
   198  	file      int64
   199  	ast       int64
   200  	types     int64
   201  	typesInfo int64
   202  	total     int64
   203  }
   204  
   205  func (c *Cache) PackageStats(withNames bool) template.HTML {
   206  	var packageStats []packageStat
   207  	c.store.DebugOnlyIterate(func(k, v interface{}) {
   208  		switch k.(type) {
   209  		case packageHandleKey:
   210  			v := v.(*packageData)
   211  			if v.pkg == nil {
   212  				break
   213  			}
   214  			var typsCost, typInfoCost int64
   215  			if v.pkg.types != nil {
   216  				typsCost = typesCost(v.pkg.types.Scope())
   217  			}
   218  			if v.pkg.typesInfo != nil {
   219  				typInfoCost = typesInfoCost(v.pkg.typesInfo)
   220  			}
   221  			stat := packageStat{
   222  				id:        v.pkg.m.ID,
   223  				mode:      v.pkg.mode,
   224  				types:     typsCost,
   225  				typesInfo: typInfoCost,
   226  			}
   227  			for _, f := range v.pkg.compiledGoFiles {
   228  				stat.file += int64(len(f.Src))
   229  				stat.ast += astCost(f.File)
   230  			}
   231  			stat.total = stat.file + stat.ast + stat.types + stat.typesInfo
   232  			packageStats = append(packageStats, stat)
   233  		}
   234  	})
   235  	var totalCost int64
   236  	for _, stat := range packageStats {
   237  		totalCost += stat.total
   238  	}
   239  	sort.Slice(packageStats, func(i, j int) bool {
   240  		return packageStats[i].total > packageStats[j].total
   241  	})
   242  	html := "<table><thead><td>Name</td><td>total = file + ast + types + types info</td></thead>\n"
   243  	human := func(n int64) string {
   244  		return fmt.Sprintf("%.2f", float64(n)/(1024*1024))
   245  	}
   246  	var printedCost int64
   247  	for _, stat := range packageStats {
   248  		name := stat.id
   249  		if !withNames {
   250  			name = "-"
   251  		}
   252  		html += fmt.Sprintf("<tr><td>%v (%v)</td><td>%v = %v + %v + %v + %v</td></tr>\n", name, stat.mode,
   253  			human(stat.total), human(stat.file), human(stat.ast), human(stat.types), human(stat.typesInfo))
   254  		printedCost += stat.total
   255  		if float64(printedCost) > float64(totalCost)*.9 {
   256  			break
   257  		}
   258  	}
   259  	html += "</table>\n"
   260  	return template.HTML(html)
   261  }
   262  
   263  func astCost(f *ast.File) int64 {
   264  	if f == nil {
   265  		return 0
   266  	}
   267  	var count int64
   268  	ast.Inspect(f, func(_ ast.Node) bool {
   269  		count += 32 // nodes are pretty small.
   270  		return true
   271  	})
   272  	return count
   273  }
   274  
   275  func typesCost(scope *types.Scope) int64 {
   276  	cost := 64 + int64(scope.Len())*128 // types.object looks pretty big
   277  	for i := 0; i < scope.NumChildren(); i++ {
   278  		cost += typesCost(scope.Child(i))
   279  	}
   280  	return cost
   281  }
   282  
   283  func typesInfoCost(info *types.Info) int64 {
   284  	// Most of these refer to existing objects, with the exception of InitOrder, Selections, and Types.
   285  	cost := 24*len(info.Defs) +
   286  		32*len(info.Implicits) +
   287  		256*len(info.InitOrder) + // these are big, but there aren't many of them.
   288  		32*len(info.Scopes) +
   289  		128*len(info.Selections) + // wild guess
   290  		128*len(info.Types) + // wild guess
   291  		32*len(info.Uses)
   292  	return int64(cost)
   293  }