golang.org/x/tools/gopls@v0.15.3/internal/cache/session.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 "errors" 10 "fmt" 11 "os" 12 "path/filepath" 13 "sort" 14 "strconv" 15 "strings" 16 "sync" 17 "sync/atomic" 18 "time" 19 20 "golang.org/x/tools/gopls/internal/cache/metadata" 21 "golang.org/x/tools/gopls/internal/cache/typerefs" 22 "golang.org/x/tools/gopls/internal/file" 23 "golang.org/x/tools/gopls/internal/protocol" 24 "golang.org/x/tools/gopls/internal/util/bug" 25 "golang.org/x/tools/gopls/internal/util/persistent" 26 "golang.org/x/tools/gopls/internal/util/slices" 27 "golang.org/x/tools/gopls/internal/vulncheck" 28 "golang.org/x/tools/internal/event" 29 "golang.org/x/tools/internal/gocommand" 30 "golang.org/x/tools/internal/imports" 31 "golang.org/x/tools/internal/memoize" 32 "golang.org/x/tools/internal/xcontext" 33 ) 34 35 // NewSession creates a new gopls session with the given cache. 36 func NewSession(ctx context.Context, c *Cache) *Session { 37 index := atomic.AddInt64(&sessionIndex, 1) 38 s := &Session{ 39 id: strconv.FormatInt(index, 10), 40 cache: c, 41 gocmdRunner: &gocommand.Runner{}, 42 overlayFS: newOverlayFS(c), 43 parseCache: newParseCache(1 * time.Minute), // keep recently parsed files for a minute, to optimize typing CPU 44 viewMap: make(map[protocol.DocumentURI]*View), 45 } 46 event.Log(ctx, "New session", KeyCreateSession.Of(s)) 47 return s 48 } 49 50 // A Session holds the state (views, file contents, parse cache, 51 // memoized computations) of a gopls server process. 52 // 53 // It implements the file.Source interface. 54 type Session struct { 55 // Unique identifier for this session. 56 id string 57 58 // Immutable attributes shared across views. 59 cache *Cache // shared cache 60 gocmdRunner *gocommand.Runner // limits go command concurrency 61 62 viewMu sync.Mutex 63 views []*View 64 viewMap map[protocol.DocumentURI]*View // file->best view; nil after shutdown 65 66 // snapshots is a counting semaphore that records the number 67 // of unreleased snapshots associated with this session. 68 // Shutdown waits for it to fall to zero. 69 snapshotWG sync.WaitGroup 70 71 parseCache *parseCache 72 73 *overlayFS 74 } 75 76 // ID returns the unique identifier for this session on this server. 77 func (s *Session) ID() string { return s.id } 78 func (s *Session) String() string { return s.id } 79 80 // GoCommandRunner returns the gocommand Runner for this session. 81 func (s *Session) GoCommandRunner() *gocommand.Runner { 82 return s.gocmdRunner 83 } 84 85 // Shutdown the session and all views it has created. 86 func (s *Session) Shutdown(ctx context.Context) { 87 var views []*View 88 s.viewMu.Lock() 89 views = append(views, s.views...) 90 s.views = nil 91 s.viewMap = nil 92 s.viewMu.Unlock() 93 for _, view := range views { 94 view.shutdown() 95 } 96 s.parseCache.stop() 97 s.snapshotWG.Wait() // wait for all work on associated snapshots to finish 98 event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s)) 99 } 100 101 // Cache returns the cache that created this session, for debugging only. 102 func (s *Session) Cache() *Cache { 103 return s.cache 104 } 105 106 // TODO(rfindley): is the logic surrounding this error actually necessary? 107 var ErrViewExists = errors.New("view already exists for session") 108 109 // NewView creates a new View, returning it and its first snapshot. If a 110 // non-empty tempWorkspace directory is provided, the View will record a copy 111 // of its gopls workspace module in that directory, so that client tooling 112 // can execute in the same main module. On success it also returns a release 113 // function that must be called when the Snapshot is no longer needed. 114 func (s *Session) NewView(ctx context.Context, folder *Folder) (*View, *Snapshot, func(), error) { 115 s.viewMu.Lock() 116 defer s.viewMu.Unlock() 117 118 // Querying the file system to check whether 119 // two folders denote the same existing directory. 120 if inode1, err := os.Stat(filepath.FromSlash(folder.Dir.Path())); err == nil { 121 for _, view := range s.views { 122 inode2, err := os.Stat(filepath.FromSlash(view.folder.Dir.Path())) 123 if err == nil && os.SameFile(inode1, inode2) { 124 return nil, nil, nil, ErrViewExists 125 } 126 } 127 } 128 129 def, err := defineView(ctx, s, folder, nil) 130 if err != nil { 131 return nil, nil, nil, err 132 } 133 view, snapshot, release := s.createView(ctx, def) 134 s.views = append(s.views, view) 135 // we always need to drop the view map 136 s.viewMap = make(map[protocol.DocumentURI]*View) 137 return view, snapshot, release, nil 138 } 139 140 // createView creates a new view, with an initial snapshot that retains the 141 // supplied context, detached from events and cancelation. 142 // 143 // The caller is responsible for calling the release function once. 144 func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *Snapshot, func()) { 145 index := atomic.AddInt64(&viewIndex, 1) 146 147 // We want a true background context and not a detached context here 148 // the spans need to be unrelated and no tag values should pollute it. 149 baseCtx := event.Detach(xcontext.Detach(ctx)) 150 backgroundCtx, cancel := context.WithCancel(baseCtx) 151 152 // Compute a skip function to use for module cache scanning. 153 // 154 // Note that unlike other filtering operations, we definitely don't want to 155 // exclude the gomodcache here, even if it is contained in the workspace 156 // folder. 157 // 158 // TODO(rfindley): consolidate with relPathExcludedByFilter(Func), Filterer, 159 // View.filterFunc. 160 var skipPath func(string) bool 161 { 162 // Compute a prefix match, respecting segment boundaries, by ensuring 163 // the pattern (dir) has a trailing slash. 164 dirPrefix := strings.TrimSuffix(string(def.folder.Dir), "/") + "/" 165 filterer := NewFilterer(def.folder.Options.DirectoryFilters) 166 skipPath = func(dir string) bool { 167 uri := strings.TrimSuffix(string(protocol.URIFromPath(dir)), "/") 168 // Note that the logic below doesn't handle the case where uri == 169 // v.folder.Dir, because there is no point in excluding the entire 170 // workspace folder! 171 if rel := strings.TrimPrefix(uri, dirPrefix); rel != uri { 172 return filterer.Disallow(rel) 173 } 174 return false 175 } 176 } 177 178 var ignoreFilter *ignoreFilter 179 { 180 var dirs []string 181 if len(def.workspaceModFiles) == 0 { 182 for _, entry := range filepath.SplitList(def.folder.Env.GOPATH) { 183 dirs = append(dirs, filepath.Join(entry, "src")) 184 } 185 } else { 186 dirs = append(dirs, def.folder.Env.GOMODCACHE) 187 for m := range def.workspaceModFiles { 188 dirs = append(dirs, filepath.Dir(m.Path())) 189 } 190 } 191 ignoreFilter = newIgnoreFilter(dirs) 192 } 193 194 var pe *imports.ProcessEnv 195 { 196 env := make(map[string]string) 197 envSlice := slices.Concat(os.Environ(), def.folder.Options.EnvSlice(), []string{"GO111MODULE=" + def.adjustedGO111MODULE()}) 198 for _, kv := range envSlice { 199 if k, v, ok := strings.Cut(kv, "="); ok { 200 env[k] = v 201 } 202 } 203 pe = &imports.ProcessEnv{ 204 GocmdRunner: s.gocmdRunner, 205 BuildFlags: slices.Clone(def.folder.Options.BuildFlags), 206 // TODO(rfindley): an old comment said "processEnv operations should not mutate the modfile" 207 // But shouldn't we honor the default behavior of mod vendoring? 208 ModFlag: "readonly", 209 SkipPathInScan: skipPath, 210 Env: env, 211 WorkingDir: def.root.Path(), 212 ModCache: s.cache.modCache.dirCache(def.folder.Env.GOMODCACHE), 213 } 214 if def.folder.Options.VerboseOutput { 215 pe.Logf = func(format string, args ...interface{}) { 216 event.Log(ctx, fmt.Sprintf(format, args...)) 217 } 218 } 219 } 220 221 v := &View{ 222 id: strconv.FormatInt(index, 10), 223 gocmdRunner: s.gocmdRunner, 224 initialWorkspaceLoad: make(chan struct{}), 225 initializationSema: make(chan struct{}, 1), 226 baseCtx: baseCtx, 227 parseCache: s.parseCache, 228 ignoreFilter: ignoreFilter, 229 fs: s.overlayFS, 230 viewDefinition: def, 231 importsState: newImportsState(backgroundCtx, s.cache.modCache, pe), 232 } 233 234 s.snapshotWG.Add(1) 235 v.snapshot = &Snapshot{ 236 view: v, 237 backgroundCtx: backgroundCtx, 238 cancel: cancel, 239 store: s.cache.store, 240 refcount: 1, // Snapshots are born referenced. 241 done: s.snapshotWG.Done, 242 packages: new(persistent.Map[PackageID, *packageHandle]), 243 meta: new(metadata.Graph), 244 files: newFileMap(), 245 activePackages: new(persistent.Map[PackageID, *Package]), 246 symbolizeHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), 247 shouldLoad: new(persistent.Map[PackageID, []PackagePath]), 248 unloadableFiles: new(persistent.Set[protocol.DocumentURI]), 249 parseModHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), 250 parseWorkHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), 251 modTidyHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), 252 modVulnHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), 253 modWhyHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), 254 pkgIndex: typerefs.NewPackageIndex(), 255 moduleUpgrades: new(persistent.Map[protocol.DocumentURI, map[string]string]), 256 vulns: new(persistent.Map[protocol.DocumentURI, *vulncheck.Result]), 257 } 258 259 // Snapshots must observe all open files, as there are some caching 260 // heuristics that change behavior depending on open files. 261 for _, o := range s.overlayFS.Overlays() { 262 _, _ = v.snapshot.ReadFile(ctx, o.URI()) 263 } 264 265 // Record the environment of the newly created view in the log. 266 event.Log(ctx, viewEnv(v)) 267 268 // Initialize the view without blocking. 269 initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx)) 270 v.cancelInitialWorkspaceLoad = initCancel 271 snapshot := v.snapshot 272 273 // Pass a second reference to the background goroutine. 274 bgRelease := snapshot.Acquire() 275 go func() { 276 defer bgRelease() 277 snapshot.initialize(initCtx, true) 278 }() 279 280 // Return a third reference to the caller. 281 return v, snapshot, snapshot.Acquire() 282 } 283 284 // RemoveView removes from the session the view rooted at the specified directory. 285 // It reports whether a view of that directory was removed. 286 func (s *Session) RemoveView(dir protocol.DocumentURI) bool { 287 s.viewMu.Lock() 288 defer s.viewMu.Unlock() 289 for _, view := range s.views { 290 if view.folder.Dir == dir { 291 i := s.dropView(view) 292 if i == -1 { 293 return false // can't happen 294 } 295 // delete this view... we don't care about order but we do want to make 296 // sure we can garbage collect the view 297 s.views = removeElement(s.views, i) 298 return true 299 } 300 } 301 return false 302 } 303 304 // View returns the view with a matching id, if present. 305 func (s *Session) View(id string) (*View, error) { 306 s.viewMu.Lock() 307 defer s.viewMu.Unlock() 308 for _, view := range s.views { 309 if view.ID() == id { 310 return view, nil 311 } 312 } 313 return nil, fmt.Errorf("no view with ID %q", id) 314 } 315 316 // SnapshotOf returns a Snapshot corresponding to the given URI. 317 // 318 // In the case where the file can be can be associated with a View by 319 // bestViewForURI (based on directory information alone, without package 320 // metadata), SnapshotOf returns the current Snapshot for that View. Otherwise, 321 // it awaits loading package metadata and returns a Snapshot for the first View 322 // containing a real (=not command-line-arguments) package for the file. 323 // 324 // If that also fails to find a View, SnapshotOf returns a Snapshot for the 325 // first view in s.views that is not shut down (i.e. s.views[0] unless we lose 326 // a race), for determinism in tests and so that we tend to aggregate the 327 // resulting command-line-arguments packages into a single view. 328 // 329 // SnapshotOf returns an error if a failure occurs along the way (most likely due 330 // to context cancellation), or if there are no Views in the Session. 331 // 332 // On success, the caller must call the returned function to release the snapshot. 333 func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Snapshot, func(), error) { 334 // Fast path: if the uri has a static association with a view, return it. 335 s.viewMu.Lock() 336 v, err := s.viewOfLocked(ctx, uri) 337 s.viewMu.Unlock() 338 339 if err != nil { 340 return nil, nil, err 341 } 342 343 if v != nil { 344 snapshot, release, err := v.Snapshot() 345 if err == nil { 346 return snapshot, release, nil 347 } 348 // View is shut down. Forget this association. 349 s.viewMu.Lock() 350 if s.viewMap[uri] == v { 351 delete(s.viewMap, uri) 352 } 353 s.viewMu.Unlock() 354 } 355 356 // Fall-back: none of the views could be associated with uri based on 357 // directory information alone. 358 // 359 // Don't memoize the view association in viewMap, as it is not static: Views 360 // may change as metadata changes. 361 // 362 // TODO(rfindley): we could perhaps optimize this case by peeking at existing 363 // metadata before awaiting the load (after all, a load only adds metadata). 364 // But that seems potentially tricky, when in the common case no loading 365 // should be required. 366 views := s.Views() 367 for _, v := range views { 368 snapshot, release, err := v.Snapshot() 369 if err != nil { 370 continue // view was shut down 371 } 372 _ = snapshot.awaitLoaded(ctx) // ignore error 373 g := snapshot.MetadataGraph() 374 // We don't check the error from awaitLoaded, because a load failure (that 375 // doesn't result from context cancelation) should not prevent us from 376 // continuing to search for the best view. 377 if ctx.Err() != nil { 378 release() 379 return nil, nil, ctx.Err() 380 } 381 // Special handling for the builtin file, since it doesn't have packages. 382 if snapshot.IsBuiltin(uri) { 383 return snapshot, release, nil 384 } 385 // Only match this view if it loaded a real package for the file. 386 // 387 // Any view can load a command-line-arguments package; aggregate those into 388 // views[0] below. 389 for _, id := range g.IDs[uri] { 390 if !metadata.IsCommandLineArguments(id) || g.Packages[id].Standalone { 391 return snapshot, release, nil 392 } 393 } 394 release() 395 } 396 397 for _, v := range views { 398 snapshot, release, err := v.Snapshot() 399 if err == nil { 400 return snapshot, release, nil // first valid snapshot 401 } 402 } 403 return nil, nil, errNoViews 404 } 405 406 // errNoViews is sought by orphaned file diagnostics, to detect the case where 407 // we have no view containing a file. 408 var errNoViews = errors.New("no views") 409 410 // viewOfLocked wraps bestViewForURI, memoizing its result. 411 // 412 // Precondition: caller holds s.viewMu lock. 413 // 414 // May return (nil, nil). 415 func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*View, error) { 416 v, hit := s.viewMap[uri] 417 if !hit { 418 // Cache miss: compute (and memoize) the best view. 419 fh, err := s.ReadFile(ctx, uri) 420 if err != nil { 421 return nil, err 422 } 423 v, err = bestView(ctx, s, fh, s.views) 424 if err != nil { 425 return nil, err 426 } 427 if s.viewMap == nil { 428 return nil, errors.New("session is shut down") 429 } 430 s.viewMap[uri] = v 431 } 432 return v, nil 433 } 434 435 func (s *Session) Views() []*View { 436 s.viewMu.Lock() 437 defer s.viewMu.Unlock() 438 result := make([]*View, len(s.views)) 439 copy(result, s.views) 440 return result 441 } 442 443 // selectViewDefs constructs the best set of views covering the provided workspace 444 // folders and open files. 445 // 446 // This implements the zero-config algorithm of golang/go#57979. 447 func selectViewDefs(ctx context.Context, fs file.Source, folders []*Folder, openFiles []protocol.DocumentURI) ([]*viewDefinition, error) { 448 var defs []*viewDefinition 449 450 // First, compute a default view for each workspace folder. 451 // TODO(golang/go#57979): technically, this is path dependent, since 452 // DidChangeWorkspaceFolders could introduce a path-dependent ordering on 453 // folders. We should keep folders sorted, or sort them here. 454 for _, folder := range folders { 455 def, err := defineView(ctx, fs, folder, nil) 456 if err != nil { 457 return nil, err 458 } 459 defs = append(defs, def) 460 } 461 462 // Next, ensure that the set of views covers all open files contained in a 463 // workspace folder. 464 // 465 // We only do this for files contained in a workspace folder, because other 466 // open files are most likely the result of jumping to a definition from a 467 // workspace file; we don't want to create additional views in those cases: 468 // they should be resolved after initialization. 469 470 folderForFile := func(uri protocol.DocumentURI) *Folder { 471 var longest *Folder 472 for _, folder := range folders { 473 // Check that this is a better match than longest, but not through a 474 // vendor directory. Count occurrences of "/vendor/" as a quick check 475 // that the vendor directory is between the folder and the file. Note the 476 // addition of a trailing "/" to handle the odd case where the folder is named 477 // vendor (which I hope is exceedingly rare in any case). 478 // 479 // Vendored packages are, by definition, part of an existing view. 480 if (longest == nil || len(folder.Dir) > len(longest.Dir)) && 481 folder.Dir.Encloses(uri) && 482 strings.Count(string(uri), "/vendor/") == strings.Count(string(folder.Dir)+"/", "/vendor/") { 483 484 longest = folder 485 } 486 } 487 return longest 488 } 489 490 checkFiles: 491 for _, uri := range openFiles { 492 folder := folderForFile(uri) 493 if folder == nil || !folder.Options.ZeroConfig { 494 continue // only guess views for open files 495 } 496 fh, err := fs.ReadFile(ctx, uri) 497 if err != nil { 498 return nil, err 499 } 500 def, err := bestView(ctx, fs, fh, defs) 501 if err != nil { 502 // We should never call selectViewDefs with a cancellable context, so 503 // this should never fail. 504 return nil, bug.Errorf("failed to find best view for open file: %v", err) 505 } 506 if def != nil { 507 continue // file covered by an existing view 508 } 509 def, err = defineView(ctx, fs, folder, fh) 510 if err != nil { 511 // We should never call selectViewDefs with a cancellable context, so 512 // this should never fail. 513 return nil, bug.Errorf("failed to define view for open file: %v", err) 514 } 515 // It need not strictly be the case that the best view for a file is 516 // distinct from other views, as the logic of getViewDefinition and 517 // bestViewForURI does not align perfectly. This is not necessarily a bug: 518 // there may be files for which we can't construct a valid view. 519 // 520 // Nevertheless, we should not create redundant views. 521 for _, alt := range defs { 522 if viewDefinitionsEqual(alt, def) { 523 continue checkFiles 524 } 525 } 526 defs = append(defs, def) 527 } 528 529 return defs, nil 530 } 531 532 // The viewDefiner interface allows the bestView algorithm to operate on both 533 // Views and viewDefinitions. 534 type viewDefiner interface{ definition() *viewDefinition } 535 536 // BestViews returns the most relevant subset of views for a given uri. 537 // 538 // This may be used to filter diagnostics to the most relevant builds. 539 func BestViews[V viewDefiner](ctx context.Context, fs file.Source, uri protocol.DocumentURI, views []V) ([]V, error) { 540 if len(views) == 0 { 541 return nil, nil // avoid the call to findRootPattern 542 } 543 dir := uri.Dir() 544 modURI, err := findRootPattern(ctx, dir, "go.mod", fs) 545 if err != nil { 546 return nil, err 547 } 548 549 // Prefer GoWork > GoMod > GOPATH > GoPackages > AdHoc. 550 var ( 551 goPackagesViews []V // prefer longest 552 workViews []V // prefer longest 553 modViews []V // exact match 554 gopathViews []V // prefer longest 555 adHocViews []V // exact match 556 ) 557 558 // pushView updates the views slice with the matching view v, using the 559 // heuristic that views with a longer root are preferable. Accordingly, 560 // pushView may be a no op if v's root is shorter than the roots in the views 561 // slice. 562 // 563 // Invariant: the length of all roots in views is the same. 564 pushView := func(views *[]V, v V) { 565 if len(*views) == 0 { 566 *views = []V{v} 567 return 568 } 569 better := func(l, r V) bool { 570 return len(l.definition().root) > len(r.definition().root) 571 } 572 existing := (*views)[0] 573 switch { 574 case better(existing, v): 575 case better(v, existing): 576 *views = []V{v} 577 default: 578 *views = append(*views, v) 579 } 580 } 581 582 for _, view := range views { 583 switch def := view.definition(); def.Type() { 584 case GoPackagesDriverView: 585 if def.root.Encloses(dir) { 586 pushView(&goPackagesViews, view) 587 } 588 case GoWorkView: 589 if _, ok := def.workspaceModFiles[modURI]; ok || uri == def.gowork { 590 pushView(&workViews, view) 591 } 592 case GoModView: 593 if _, ok := def.workspaceModFiles[modURI]; ok { 594 modViews = append(modViews, view) 595 } 596 case GOPATHView: 597 if def.root.Encloses(dir) { 598 pushView(&gopathViews, view) 599 } 600 case AdHocView: 601 if def.root == dir { 602 adHocViews = append(adHocViews, view) 603 } 604 } 605 } 606 607 // Now that we've collected matching views, choose the best match, 608 // considering ports. 609 // 610 // We only consider one type of view, since the matching view created by 611 // defineView should be of the best type. 612 var bestViews []V 613 switch { 614 case len(workViews) > 0: 615 bestViews = workViews 616 case len(modViews) > 0: 617 bestViews = modViews 618 case len(gopathViews) > 0: 619 bestViews = gopathViews 620 case len(goPackagesViews) > 0: 621 bestViews = goPackagesViews 622 case len(adHocViews) > 0: 623 bestViews = adHocViews 624 } 625 626 return bestViews, nil 627 } 628 629 // bestView returns the best View or viewDefinition that contains the 630 // given file, or (nil, nil) if no matching view is found. 631 // 632 // bestView only returns an error in the event of context cancellation. 633 // 634 // Making this function generic is convenient so that we can avoid mapping view 635 // definitions back to views inside Session.DidModifyFiles, where performance 636 // matters. It is, however, not the cleanest application of generics. 637 // 638 // Note: keep this function in sync with defineView. 639 func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle, views []V) (V, error) { 640 var zero V 641 bestViews, err := BestViews(ctx, fs, fh.URI(), views) 642 if err != nil || len(bestViews) == 0 { 643 return zero, err 644 } 645 646 content, err := fh.Content() 647 // Port matching doesn't apply to non-go files, or files that no longer exist. 648 // Note that the behavior here on non-existent files shouldn't matter much, 649 // since there will be a subsequent failure. But it is simpler to preserve 650 // the invariant that bestView only fails on context cancellation. 651 if fileKind(fh) != file.Go || err != nil { 652 return bestViews[0], nil 653 } 654 655 // Find the first view that matches constraints. 656 // Content trimming is nontrivial, so do this outside of the loop below. 657 path := fh.URI().Path() 658 content = trimContentForPortMatch(content) 659 for _, v := range bestViews { 660 def := v.definition() 661 viewPort := port{def.GOOS(), def.GOARCH()} 662 if viewPort.matches(path, content) { 663 return v, nil 664 } 665 } 666 667 return zero, nil // no view found 668 } 669 670 // updateViewLocked recreates the view with the given options. 671 // 672 // If the resulting error is non-nil, the view may or may not have already been 673 // dropped from the session. 674 func (s *Session) updateViewLocked(ctx context.Context, view *View, def *viewDefinition) (*View, error) { 675 i := s.dropView(view) 676 if i == -1 { 677 return nil, fmt.Errorf("view %q not found", view.id) 678 } 679 680 view, _, release := s.createView(ctx, def) 681 defer release() 682 683 // substitute the new view into the array where the old view was 684 s.views[i] = view 685 s.viewMap = make(map[protocol.DocumentURI]*View) 686 return view, nil 687 } 688 689 // removeElement removes the ith element from the slice replacing it with the last element. 690 // TODO(adonovan): generics, someday. 691 func removeElement(slice []*View, index int) []*View { 692 last := len(slice) - 1 693 slice[index] = slice[last] 694 slice[last] = nil // aid GC 695 return slice[:last] 696 } 697 698 // dropView removes v from the set of views for the receiver s and calls 699 // v.shutdown, returning the index of v in s.views (if found), or -1 if v was 700 // not found. s.viewMu must be held while calling this function. 701 func (s *Session) dropView(v *View) int { 702 // we always need to drop the view map 703 s.viewMap = make(map[protocol.DocumentURI]*View) 704 for i := range s.views { 705 if v == s.views[i] { 706 // we found the view, drop it and return the index it was found at 707 s.views[i] = nil 708 v.shutdown() 709 return i 710 } 711 } 712 // TODO(rfindley): it looks wrong that we don't shutdown v in this codepath. 713 // We should never get here. 714 bug.Reportf("tried to drop nonexistent view %q", v.id) 715 return -1 716 } 717 718 // ResetView resets the best view for the given URI. 719 func (s *Session) ResetView(ctx context.Context, uri protocol.DocumentURI) (*View, error) { 720 s.viewMu.Lock() 721 defer s.viewMu.Unlock() 722 v, err := s.viewOfLocked(ctx, uri) 723 if err != nil { 724 return nil, err 725 } 726 return s.updateViewLocked(ctx, v, v.viewDefinition) 727 } 728 729 // DidModifyFiles reports a file modification to the session. It returns 730 // the new snapshots after the modifications have been applied, paired with 731 // the affected file URIs for those snapshots. 732 // On success, it returns a release function that 733 // must be called when the snapshots are no longer needed. 734 // 735 // TODO(rfindley): what happens if this function fails? It must leave us in a 736 // broken state, which we should surface to the user, probably as a request to 737 // restart gopls. 738 func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modification) (map[*View][]protocol.DocumentURI, error) { 739 s.viewMu.Lock() 740 defer s.viewMu.Unlock() 741 742 // Update overlays. 743 // 744 // This is done while holding viewMu because the set of open files affects 745 // the set of views, and to prevent views from seeing updated file content 746 // before they have processed invalidations. 747 replaced, err := s.updateOverlays(ctx, modifications) 748 if err != nil { 749 return nil, err 750 } 751 752 // checkViews controls whether the set of views needs to be recomputed, for 753 // example because a go.mod file was created or deleted, or a go.work file 754 // changed on disk. 755 checkViews := false 756 757 changed := make(map[protocol.DocumentURI]file.Handle) 758 for _, c := range modifications { 759 fh := mustReadFile(ctx, s, c.URI) 760 changed[c.URI] = fh 761 762 // Any change to the set of open files causes views to be recomputed. 763 if c.Action == file.Open || c.Action == file.Close { 764 checkViews = true 765 } 766 767 // Any on-disk change to a go.work or go.mod file causes recomputing views. 768 // 769 // TODO(rfindley): go.work files need not be named "go.work" -- we need to 770 // check each view's source to handle the case of an explicit GOWORK value. 771 // Write a test that fails, and fix this. 772 if (isGoWork(c.URI) || isGoMod(c.URI)) && (c.Action == file.Save || c.OnDisk) { 773 checkViews = true 774 } 775 776 // Any change to the set of supported ports in a file may affect view 777 // selection. This is perhaps more subtle than it first seems: since the 778 // algorithm for selecting views considers open files in a deterministic 779 // order, a change in supported ports may cause a different port to be 780 // chosen, even if all open files still match an existing View! 781 // 782 // We endeavor to avoid that sort of path dependence, so must re-run the 783 // view selection algorithm whenever any input changes. 784 // 785 // However, extracting the build comment is nontrivial, so we don't want to 786 // pay this cost when e.g. processing a bunch of on-disk changes due to a 787 // branch change. Be careful to only do this if both files are open Go 788 // files. 789 if old, ok := replaced[c.URI]; ok && !checkViews && fileKind(fh) == file.Go { 790 if new, ok := fh.(*overlay); ok { 791 if buildComment(old.content) != buildComment(new.content) { 792 checkViews = true 793 } 794 } 795 } 796 } 797 798 if checkViews { 799 // Hack: collect folders from existing views. 800 // TODO(golang/go#57979): we really should track folders independent of 801 // Views, but since we always have a default View for each folder, this 802 // works for now. 803 var folders []*Folder // preserve folder order 804 seen := make(map[*Folder]unit) 805 for _, v := range s.views { 806 if _, ok := seen[v.folder]; ok { 807 continue 808 } 809 seen[v.folder] = unit{} 810 folders = append(folders, v.folder) 811 } 812 813 var openFiles []protocol.DocumentURI 814 for _, o := range s.Overlays() { 815 openFiles = append(openFiles, o.URI()) 816 } 817 // Sort for determinism. 818 sort.Slice(openFiles, func(i, j int) bool { 819 return openFiles[i] < openFiles[j] 820 }) 821 822 // TODO(rfindley): can we avoid running the go command (go env) 823 // synchronously to change processing? Can we assume that the env did not 824 // change, and derive go.work using a combination of the configured 825 // GOWORK value and filesystem? 826 defs, err := selectViewDefs(ctx, s, folders, openFiles) 827 if err != nil { 828 // Catastrophic failure, equivalent to a failure of session 829 // initialization and therefore should almost never happen. One 830 // scenario where this failure mode could occur is if some file 831 // permissions have changed preventing us from reading go.mod 832 // files. 833 // 834 // TODO(rfindley): consider surfacing this error more loudly. We 835 // could report a bug, but it's not really a bug. 836 event.Error(ctx, "selecting new views", err) 837 } else { 838 kept := make(map[*View]unit) 839 var newViews []*View 840 for _, def := range defs { 841 var newView *View 842 // Reuse existing view? 843 for _, v := range s.views { 844 if viewDefinitionsEqual(def, v.viewDefinition) { 845 newView = v 846 kept[v] = unit{} 847 break 848 } 849 } 850 if newView == nil { 851 v, _, release := s.createView(ctx, def) 852 release() 853 newView = v 854 } 855 newViews = append(newViews, newView) 856 } 857 for _, v := range s.views { 858 if _, ok := kept[v]; !ok { 859 v.shutdown() 860 } 861 } 862 s.views = newViews 863 s.viewMap = make(map[protocol.DocumentURI]*View) 864 } 865 } 866 867 // We only want to run fast-path diagnostics (i.e. diagnoseChangedFiles) once 868 // for each changed file, in its best view. 869 viewsToDiagnose := map[*View][]protocol.DocumentURI{} 870 for _, mod := range modifications { 871 v, err := s.viewOfLocked(ctx, mod.URI) 872 if err != nil { 873 // bestViewForURI only returns an error in the event of context 874 // cancellation. Since state changes should occur on an uncancellable 875 // context, an error here is a bug. 876 bug.Reportf("finding best view for change: %v", err) 877 continue 878 } 879 if v != nil { 880 viewsToDiagnose[v] = append(viewsToDiagnose[v], mod.URI) 881 } 882 } 883 884 // ...but changes may be relevant to other views, for example if they are 885 // changes to a shared package. 886 for _, v := range s.views { 887 _, release, needsDiagnosis := s.invalidateViewLocked(ctx, v, StateChange{Modifications: modifications, Files: changed}) 888 release() 889 890 if needsDiagnosis || checkViews { 891 if _, ok := viewsToDiagnose[v]; !ok { 892 viewsToDiagnose[v] = nil 893 } 894 } 895 } 896 897 return viewsToDiagnose, nil 898 } 899 900 // ExpandModificationsToDirectories returns the set of changes with the 901 // directory changes removed and expanded to include all of the files in 902 // the directory. 903 func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []file.Modification) []file.Modification { 904 var snapshots []*Snapshot 905 s.viewMu.Lock() 906 for _, v := range s.views { 907 snapshot, release, err := v.Snapshot() 908 if err != nil { 909 continue // view is shut down; continue with others 910 } 911 defer release() 912 snapshots = append(snapshots, snapshot) 913 } 914 s.viewMu.Unlock() 915 916 // Expand the modification to any file we could care about, which we define 917 // to be any file observed by any of the snapshots. 918 // 919 // There may be other files in the directory, but if we haven't read them yet 920 // we don't need to invalidate them. 921 var result []file.Modification 922 for _, c := range changes { 923 expanded := make(map[protocol.DocumentURI]bool) 924 for _, snapshot := range snapshots { 925 for _, uri := range snapshot.filesInDir(c.URI) { 926 expanded[uri] = true 927 } 928 } 929 if len(expanded) == 0 { 930 result = append(result, c) 931 } else { 932 for uri := range expanded { 933 result = append(result, file.Modification{ 934 URI: uri, 935 Action: c.Action, 936 LanguageID: "", 937 OnDisk: c.OnDisk, 938 // changes to directories cannot include text or versions 939 }) 940 } 941 } 942 } 943 return result 944 } 945 946 // updateOverlays updates the set of overlays and returns a map of any existing 947 // overlay values that were replaced. 948 // 949 // Precondition: caller holds s.viewMu lock. 950 // TODO(rfindley): move this to fs_overlay.go. 951 func (fs *overlayFS) updateOverlays(ctx context.Context, changes []file.Modification) (map[protocol.DocumentURI]*overlay, error) { 952 fs.mu.Lock() 953 defer fs.mu.Unlock() 954 955 replaced := make(map[protocol.DocumentURI]*overlay) 956 for _, c := range changes { 957 o, ok := fs.overlays[c.URI] 958 if ok { 959 replaced[c.URI] = o 960 } 961 962 // If the file is not opened in an overlay and the change is on disk, 963 // there's no need to update an overlay. If there is an overlay, we 964 // may need to update the overlay's saved value. 965 if !ok && c.OnDisk { 966 continue 967 } 968 969 // Determine the file kind on open, otherwise, assume it has been cached. 970 var kind file.Kind 971 switch c.Action { 972 case file.Open: 973 kind = file.KindForLang(c.LanguageID) 974 default: 975 if !ok { 976 return nil, fmt.Errorf("updateOverlays: modifying unopened overlay %v", c.URI) 977 } 978 kind = o.kind 979 } 980 981 // Closing a file just deletes its overlay. 982 if c.Action == file.Close { 983 delete(fs.overlays, c.URI) 984 continue 985 } 986 987 // If the file is on disk, check if its content is the same as in the 988 // overlay. Saves and on-disk file changes don't come with the file's 989 // content. 990 text := c.Text 991 if text == nil && (c.Action == file.Save || c.OnDisk) { 992 if !ok { 993 return nil, fmt.Errorf("no known content for overlay for %s", c.Action) 994 } 995 text = o.content 996 } 997 // On-disk changes don't come with versions. 998 version := c.Version 999 if c.OnDisk || c.Action == file.Save { 1000 version = o.version 1001 } 1002 hash := file.HashOf(text) 1003 var sameContentOnDisk bool 1004 switch c.Action { 1005 case file.Delete: 1006 // Do nothing. sameContentOnDisk should be false. 1007 case file.Save: 1008 // Make sure the version and content (if present) is the same. 1009 if false && o.version != version { // Client no longer sends the version 1010 return nil, fmt.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version) 1011 } 1012 if c.Text != nil && o.hash != hash { 1013 return nil, fmt.Errorf("updateOverlays: overlay %s changed on save", c.URI) 1014 } 1015 sameContentOnDisk = true 1016 default: 1017 fh := mustReadFile(ctx, fs.delegate, c.URI) 1018 _, readErr := fh.Content() 1019 sameContentOnDisk = (readErr == nil && fh.Identity().Hash == hash) 1020 } 1021 o = &overlay{ 1022 uri: c.URI, 1023 version: version, 1024 content: text, 1025 kind: kind, 1026 hash: hash, 1027 saved: sameContentOnDisk, 1028 } 1029 1030 // NOTE: previous versions of this code checked here that the overlay had a 1031 // view and file kind (but we don't know why). 1032 1033 fs.overlays[c.URI] = o 1034 } 1035 1036 return replaced, nil 1037 } 1038 1039 func mustReadFile(ctx context.Context, fs file.Source, uri protocol.DocumentURI) file.Handle { 1040 ctx = xcontext.Detach(ctx) 1041 fh, err := fs.ReadFile(ctx, uri) 1042 if err != nil { 1043 // ReadFile cannot fail with an uncancellable context. 1044 bug.Reportf("reading file failed unexpectedly: %v", err) 1045 return brokenFile{uri, err} 1046 } 1047 return fh 1048 } 1049 1050 // A brokenFile represents an unexpected failure to read a file. 1051 type brokenFile struct { 1052 uri protocol.DocumentURI 1053 err error 1054 } 1055 1056 func (b brokenFile) URI() protocol.DocumentURI { return b.uri } 1057 func (b brokenFile) Identity() file.Identity { return file.Identity{URI: b.uri} } 1058 func (b brokenFile) SameContentsOnDisk() bool { return false } 1059 func (b brokenFile) Version() int32 { return 0 } 1060 func (b brokenFile) Content() ([]byte, error) { return nil, b.err } 1061 1062 // FileWatchingGlobPatterns returns a set of glob patterns that the client is 1063 // required to watch for changes, and notify the server of them, in order to 1064 // keep the server's state up to date. 1065 // 1066 // This set includes 1067 // 1. all go.mod and go.work files in the workspace; and 1068 // 2. for each Snapshot, its modules (or directory for ad-hoc views). In 1069 // module mode, this is the set of active modules (and for VS Code, all 1070 // workspace directories within them, due to golang/go#42348). 1071 // 1072 // The watch for workspace go.work and go.mod files in (1) is sufficient to 1073 // capture changes to the repo structure that may affect the set of views. 1074 // Whenever this set changes, we reload the workspace and invalidate memoized 1075 // files. 1076 // 1077 // The watch for workspace directories in (2) should keep each View up to date, 1078 // as it should capture any newly added/modified/deleted Go files. 1079 // 1080 // Patterns are returned as a set of protocol.RelativePatterns, since they can 1081 // always be later translated to glob patterns (i.e. strings) if the client 1082 // lacks relative pattern support. By convention, any pattern returned with 1083 // empty baseURI should be served as a glob pattern. 1084 // 1085 // In general, we prefer to serve relative patterns, as they work better on 1086 // most clients that support both, and do not have issues with Windows driver 1087 // letter casing: 1088 // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#relativePattern 1089 // 1090 // TODO(golang/go#57979): we need to reset the memoizedFS when a view changes. 1091 // Consider the case where we incidentally read a file, then it moved outside 1092 // of an active module, and subsequently changed: we would still observe the 1093 // original file state. 1094 func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[protocol.RelativePattern]unit { 1095 s.viewMu.Lock() 1096 defer s.viewMu.Unlock() 1097 1098 // Always watch files that may change the set of views. 1099 patterns := map[protocol.RelativePattern]unit{ 1100 {Pattern: "**/*.{mod,work}"}: {}, 1101 } 1102 1103 for _, view := range s.views { 1104 snapshot, release, err := view.Snapshot() 1105 if err != nil { 1106 continue // view is shut down; continue with others 1107 } 1108 for k, v := range snapshot.fileWatchingGlobPatterns() { 1109 patterns[k] = v 1110 } 1111 release() 1112 } 1113 return patterns 1114 } 1115 1116 // OrphanedFileDiagnostics reports diagnostics describing why open files have 1117 // no packages or have only command-line-arguments packages. 1118 // 1119 // If the resulting diagnostic is nil, the file is either not orphaned or we 1120 // can't produce a good diagnostic. 1121 // 1122 // The caller must not mutate the result. 1123 func (s *Session) OrphanedFileDiagnostics(ctx context.Context) (map[protocol.DocumentURI][]*Diagnostic, error) { 1124 // Note: diagnostics holds a slice for consistency with other diagnostic 1125 // funcs. 1126 diagnostics := make(map[protocol.DocumentURI][]*Diagnostic) 1127 1128 byView := make(map[*View][]*overlay) 1129 for _, o := range s.Overlays() { 1130 uri := o.URI() 1131 snapshot, release, err := s.SnapshotOf(ctx, uri) 1132 if err != nil { 1133 // TODO(golang/go#57979): we have to use the .go suffix as an approximation for 1134 // file kind here, because we don't have access to Options if no View was 1135 // matched. 1136 // 1137 // But Options are really a property of Folder, not View, and we could 1138 // match a folder here. 1139 // 1140 // Refactor so that Folders are tracked independently of Views, and use 1141 // the correct options here to get the most accurate file kind. 1142 // 1143 // TODO(golang/go#57979): once we switch entirely to the zeroconfig 1144 // logic, we should use this diagnostic for the fallback case of 1145 // s.views[0] in the ViewOf logic. 1146 if errors.Is(err, errNoViews) { 1147 if strings.HasSuffix(string(uri), ".go") { 1148 if _, rng, ok := orphanedFileDiagnosticRange(ctx, s.parseCache, o); ok { 1149 diagnostics[uri] = []*Diagnostic{{ 1150 URI: uri, 1151 Range: rng, 1152 Severity: protocol.SeverityWarning, 1153 Source: ListError, 1154 Message: fmt.Sprintf("No active builds contain %s: consider opening a new workspace folder containing it", uri.Path()), 1155 }} 1156 } 1157 } 1158 continue 1159 } 1160 return nil, err 1161 } 1162 v := snapshot.View() 1163 release() 1164 byView[v] = append(byView[v], o) 1165 } 1166 1167 for view, overlays := range byView { 1168 snapshot, release, err := view.Snapshot() 1169 if err != nil { 1170 continue // view is shutting down 1171 } 1172 defer release() 1173 diags, err := snapshot.orphanedFileDiagnostics(ctx, overlays) 1174 if err != nil { 1175 return nil, err 1176 } 1177 for _, d := range diags { 1178 diagnostics[d.URI] = append(diagnostics[d.URI], d) 1179 } 1180 } 1181 return diagnostics, nil 1182 }