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