github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/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 "fmt" 10 "strconv" 11 "sync" 12 "sync/atomic" 13 14 "github.com/jhump/golang-x-tools/internal/event" 15 "github.com/jhump/golang-x-tools/internal/gocommand" 16 "github.com/jhump/golang-x-tools/internal/imports" 17 "github.com/jhump/golang-x-tools/internal/lsp/progress" 18 "github.com/jhump/golang-x-tools/internal/lsp/source" 19 "github.com/jhump/golang-x-tools/internal/span" 20 "github.com/jhump/golang-x-tools/internal/xcontext" 21 errors "golang.org/x/xerrors" 22 ) 23 24 type Session struct { 25 cache *Cache 26 id string 27 28 optionsMu sync.Mutex 29 options *source.Options 30 31 viewMu sync.RWMutex 32 views []*View 33 viewMap map[span.URI]*View // map of URI->best view 34 35 overlayMu sync.Mutex 36 overlays map[span.URI]*overlay 37 38 // gocmdRunner guards go command calls from concurrency errors. 39 gocmdRunner *gocommand.Runner 40 41 progress *progress.Tracker 42 } 43 44 type overlay struct { 45 session *Session 46 uri span.URI 47 text []byte 48 hash string 49 version int32 50 kind source.FileKind 51 52 // saved is true if a file matches the state on disk, 53 // and therefore does not need to be part of the overlay sent to go/packages. 54 saved bool 55 } 56 57 func (o *overlay) Read() ([]byte, error) { 58 return o.text, nil 59 } 60 61 func (o *overlay) FileIdentity() source.FileIdentity { 62 return source.FileIdentity{ 63 URI: o.uri, 64 Hash: o.hash, 65 } 66 } 67 68 func (o *overlay) VersionedFileIdentity() source.VersionedFileIdentity { 69 return source.VersionedFileIdentity{ 70 URI: o.uri, 71 SessionID: o.session.id, 72 Version: o.version, 73 } 74 } 75 76 func (o *overlay) Kind() source.FileKind { 77 return o.kind 78 } 79 80 func (o *overlay) URI() span.URI { 81 return o.uri 82 } 83 84 func (o *overlay) Version() int32 { 85 return o.version 86 } 87 88 func (o *overlay) Session() string { 89 return o.session.id 90 } 91 92 func (o *overlay) Saved() bool { 93 return o.saved 94 } 95 96 // closedFile implements LSPFile for a file that the editor hasn't told us about. 97 type closedFile struct { 98 source.FileHandle 99 } 100 101 func (c *closedFile) VersionedFileIdentity() source.VersionedFileIdentity { 102 return source.VersionedFileIdentity{ 103 URI: c.FileHandle.URI(), 104 SessionID: "", 105 Version: 0, 106 } 107 } 108 109 func (c *closedFile) Saved() bool { 110 return true 111 } 112 113 func (c *closedFile) Session() string { 114 return "" 115 } 116 117 func (c *closedFile) Version() int32 { 118 return 0 119 } 120 121 func (s *Session) ID() string { return s.id } 122 func (s *Session) String() string { return s.id } 123 124 func (s *Session) Options() *source.Options { 125 s.optionsMu.Lock() 126 defer s.optionsMu.Unlock() 127 return s.options 128 } 129 130 func (s *Session) SetOptions(options *source.Options) { 131 s.optionsMu.Lock() 132 defer s.optionsMu.Unlock() 133 s.options = options 134 } 135 136 func (s *Session) SetProgressTracker(tracker *progress.Tracker) { 137 // The progress tracker should be set before any view is initialized. 138 s.progress = tracker 139 } 140 141 func (s *Session) Shutdown(ctx context.Context) { 142 var views []*View 143 s.viewMu.Lock() 144 views = append(views, s.views...) 145 s.views = nil 146 s.viewMap = nil 147 s.viewMu.Unlock() 148 for _, view := range views { 149 view.shutdown(ctx) 150 } 151 event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s)) 152 } 153 154 func (s *Session) Cache() interface{} { 155 return s.cache 156 } 157 158 func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) { 159 s.viewMu.Lock() 160 defer s.viewMu.Unlock() 161 for _, view := range s.views { 162 if span.CompareURI(view.folder, folder) == 0 { 163 return nil, nil, nil, source.ErrViewExists 164 } 165 } 166 view, snapshot, release, err := s.createView(ctx, name, folder, options, 0) 167 if err != nil { 168 return nil, nil, func() {}, err 169 } 170 s.views = append(s.views, view) 171 // we always need to drop the view map 172 s.viewMap = make(map[span.URI]*View) 173 return view, snapshot, release, nil 174 } 175 176 func (s *Session) createView(ctx context.Context, name string, folder span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) { 177 index := atomic.AddInt64(&viewIndex, 1) 178 179 if s.cache.options != nil { 180 s.cache.options(options) 181 } 182 183 // Set the module-specific information. 184 ws, err := s.getWorkspaceInformation(ctx, folder, options) 185 if err != nil { 186 return nil, nil, func() {}, err 187 } 188 root := folder 189 if options.ExpandWorkspaceToModule { 190 root, err = findWorkspaceRoot(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), options.ExperimentalWorkspaceModule) 191 if err != nil { 192 return nil, nil, func() {}, err 193 } 194 } 195 196 // Build the gopls workspace, collecting active modules in the view. 197 workspace, err := newWorkspace(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), ws.userGo111Module == off, options.ExperimentalWorkspaceModule) 198 if err != nil { 199 return nil, nil, func() {}, err 200 } 201 202 // We want a true background context and not a detached context here 203 // the spans need to be unrelated and no tag values should pollute it. 204 baseCtx := event.Detach(xcontext.Detach(ctx)) 205 backgroundCtx, cancel := context.WithCancel(baseCtx) 206 207 v := &View{ 208 session: s, 209 initialWorkspaceLoad: make(chan struct{}), 210 initializationSema: make(chan struct{}, 1), 211 id: strconv.FormatInt(index, 10), 212 options: options, 213 baseCtx: baseCtx, 214 name: name, 215 folder: folder, 216 moduleUpgrades: map[string]string{}, 217 filesByURI: map[span.URI]*fileBase{}, 218 filesByBase: map[string][]*fileBase{}, 219 rootURI: root, 220 workspaceInformation: *ws, 221 } 222 v.importsState = &importsState{ 223 ctx: backgroundCtx, 224 processEnv: &imports.ProcessEnv{ 225 GocmdRunner: s.gocmdRunner, 226 }, 227 } 228 v.snapshot = &snapshot{ 229 id: snapshotID, 230 view: v, 231 backgroundCtx: backgroundCtx, 232 cancel: cancel, 233 initializeOnce: &sync.Once{}, 234 generation: s.cache.store.Generation(generationName(v, 0)), 235 packages: make(map[packageKey]*packageHandle), 236 ids: make(map[span.URI][]PackageID), 237 metadata: make(map[PackageID]*KnownMetadata), 238 files: make(map[span.URI]source.VersionedFileHandle), 239 goFiles: make(map[parseKey]*parseGoHandle), 240 symbols: make(map[span.URI]*symbolHandle), 241 importedBy: make(map[PackageID][]PackageID), 242 actions: make(map[actionKey]*actionHandle), 243 workspacePackages: make(map[PackageID]PackagePath), 244 unloadableFiles: make(map[span.URI]struct{}), 245 parseModHandles: make(map[span.URI]*parseModHandle), 246 parseWorkHandles: make(map[span.URI]*parseWorkHandle), 247 modTidyHandles: make(map[span.URI]*modTidyHandle), 248 modWhyHandles: make(map[span.URI]*modWhyHandle), 249 workspace: workspace, 250 } 251 252 // Initialize the view without blocking. 253 initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx)) 254 v.initCancelFirstAttempt = initCancel 255 snapshot := v.snapshot 256 release := snapshot.generation.Acquire() 257 go func() { 258 defer release() 259 snapshot.initialize(initCtx, true) 260 }() 261 return v, snapshot, snapshot.generation.Acquire(), nil 262 } 263 264 // View returns the view by name. 265 func (s *Session) View(name string) source.View { 266 s.viewMu.RLock() 267 defer s.viewMu.RUnlock() 268 for _, view := range s.views { 269 if view.Name() == name { 270 return view 271 } 272 } 273 return nil 274 } 275 276 // ViewOf returns a view corresponding to the given URI. 277 // If the file is not already associated with a view, pick one using some heuristics. 278 func (s *Session) ViewOf(uri span.URI) (source.View, error) { 279 return s.viewOf(uri) 280 } 281 282 func (s *Session) viewOf(uri span.URI) (*View, error) { 283 s.viewMu.RLock() 284 defer s.viewMu.RUnlock() 285 // Check if we already know this file. 286 if v, found := s.viewMap[uri]; found { 287 return v, nil 288 } 289 // Pick the best view for this file and memoize the result. 290 if len(s.views) == 0 { 291 return nil, fmt.Errorf("no views in session") 292 } 293 s.viewMap[uri] = bestViewForURI(uri, s.views) 294 return s.viewMap[uri], nil 295 } 296 297 func (s *Session) viewsOf(uri span.URI) []*View { 298 s.viewMu.RLock() 299 defer s.viewMu.RUnlock() 300 301 var views []*View 302 for _, view := range s.views { 303 if source.InDir(view.folder.Filename(), uri.Filename()) { 304 views = append(views, view) 305 } 306 } 307 return views 308 } 309 310 func (s *Session) Views() []source.View { 311 s.viewMu.RLock() 312 defer s.viewMu.RUnlock() 313 result := make([]source.View, len(s.views)) 314 for i, v := range s.views { 315 result[i] = v 316 } 317 return result 318 } 319 320 // bestViewForURI returns the most closely matching view for the given URI 321 // out of the given set of views. 322 func bestViewForURI(uri span.URI, views []*View) *View { 323 // we need to find the best view for this file 324 var longest *View 325 for _, view := range views { 326 if longest != nil && len(longest.Folder()) > len(view.Folder()) { 327 continue 328 } 329 if view.contains(uri) { 330 longest = view 331 } 332 } 333 if longest != nil { 334 return longest 335 } 336 // Try our best to return a view that knows the file. 337 for _, view := range views { 338 if view.knownFile(uri) { 339 return view 340 } 341 } 342 // TODO: are there any more heuristics we can use? 343 return views[0] 344 } 345 346 func (s *Session) removeView(ctx context.Context, view *View) error { 347 s.viewMu.Lock() 348 defer s.viewMu.Unlock() 349 i, err := s.dropView(ctx, view) 350 if err != nil { 351 return err 352 } 353 // delete this view... we don't care about order but we do want to make 354 // sure we can garbage collect the view 355 s.views[i] = s.views[len(s.views)-1] 356 s.views[len(s.views)-1] = nil 357 s.views = s.views[:len(s.views)-1] 358 return nil 359 } 360 361 func (s *Session) updateView(ctx context.Context, view *View, options *source.Options) (*View, error) { 362 s.viewMu.Lock() 363 defer s.viewMu.Unlock() 364 365 // Preserve the snapshot ID if we are recreating the view. 366 view.snapshotMu.Lock() 367 if view.snapshot == nil { 368 view.snapshotMu.Unlock() 369 panic("updateView called after View was already shut down") 370 } 371 snapshotID := view.snapshot.id 372 view.snapshotMu.Unlock() 373 374 i, err := s.dropView(ctx, view) 375 if err != nil { 376 return nil, err 377 } 378 379 v, _, release, err := s.createView(ctx, view.name, view.folder, options, snapshotID) 380 release() 381 382 if err != nil { 383 // we have dropped the old view, but could not create the new one 384 // this should not happen and is very bad, but we still need to clean 385 // up the view array if it happens 386 s.views[i] = s.views[len(s.views)-1] 387 s.views[len(s.views)-1] = nil 388 s.views = s.views[:len(s.views)-1] 389 return nil, err 390 } 391 // substitute the new view into the array where the old view was 392 s.views[i] = v 393 return v, nil 394 } 395 396 func (s *Session) dropView(ctx context.Context, v *View) (int, error) { 397 // we always need to drop the view map 398 s.viewMap = make(map[span.URI]*View) 399 for i := range s.views { 400 if v == s.views[i] { 401 // we found the view, drop it and return the index it was found at 402 s.views[i] = nil 403 v.shutdown(ctx) 404 return i, nil 405 } 406 } 407 return -1, errors.Errorf("view %s for %v not found", v.Name(), v.Folder()) 408 } 409 410 func (s *Session) ModifyFiles(ctx context.Context, changes []source.FileModification) error { 411 _, releases, err := s.DidModifyFiles(ctx, changes) 412 for _, release := range releases { 413 release() 414 } 415 return err 416 } 417 418 type fileChange struct { 419 content []byte 420 exists bool 421 fileHandle source.VersionedFileHandle 422 423 // isUnchanged indicates whether the file action is one that does not 424 // change the actual contents of the file. Opens and closes should not 425 // be treated like other changes, since the file content doesn't change. 426 isUnchanged bool 427 } 428 429 func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) (map[source.Snapshot][]span.URI, []func(), error) { 430 s.viewMu.RLock() 431 defer s.viewMu.RUnlock() 432 views := make(map[*View]map[span.URI]*fileChange) 433 affectedViews := map[span.URI][]*View{} 434 435 overlays, err := s.updateOverlays(ctx, changes) 436 if err != nil { 437 return nil, nil, err 438 } 439 var forceReloadMetadata bool 440 for _, c := range changes { 441 if c.Action == source.InvalidateMetadata { 442 forceReloadMetadata = true 443 } 444 445 // Build the list of affected views. 446 var changedViews []*View 447 for _, view := range s.views { 448 // Don't propagate changes that are outside of the view's scope 449 // or knowledge. 450 if !view.relevantChange(c) { 451 continue 452 } 453 changedViews = append(changedViews, view) 454 } 455 // If the change is not relevant to any view, but the change is 456 // happening in the editor, assign it the most closely matching view. 457 if len(changedViews) == 0 { 458 if c.OnDisk { 459 continue 460 } 461 bestView, err := s.viewOf(c.URI) 462 if err != nil { 463 return nil, nil, err 464 } 465 changedViews = append(changedViews, bestView) 466 } 467 affectedViews[c.URI] = changedViews 468 469 isUnchanged := c.Action == source.Open || c.Action == source.Close 470 471 // Apply the changes to all affected views. 472 for _, view := range changedViews { 473 // Make sure that the file is added to the view. 474 _ = view.getFile(c.URI) 475 if _, ok := views[view]; !ok { 476 views[view] = make(map[span.URI]*fileChange) 477 } 478 if fh, ok := overlays[c.URI]; ok { 479 views[view][c.URI] = &fileChange{ 480 content: fh.text, 481 exists: true, 482 fileHandle: fh, 483 isUnchanged: isUnchanged, 484 } 485 } else { 486 fsFile, err := s.cache.getFile(ctx, c.URI) 487 if err != nil { 488 return nil, nil, err 489 } 490 content, err := fsFile.Read() 491 fh := &closedFile{fsFile} 492 views[view][c.URI] = &fileChange{ 493 content: content, 494 exists: err == nil, 495 fileHandle: fh, 496 isUnchanged: isUnchanged, 497 } 498 } 499 } 500 } 501 502 var releases []func() 503 viewToSnapshot := map[*View]*snapshot{} 504 for view, changed := range views { 505 snapshot, release := view.invalidateContent(ctx, changed, forceReloadMetadata) 506 releases = append(releases, release) 507 viewToSnapshot[view] = snapshot 508 } 509 510 // We only want to diagnose each changed file once, in the view to which 511 // it "most" belongs. We do this by picking the best view for each URI, 512 // and then aggregating the set of snapshots and their URIs (to avoid 513 // diagnosing the same snapshot multiple times). 514 snapshotURIs := map[source.Snapshot][]span.URI{} 515 for _, mod := range changes { 516 viewSlice, ok := affectedViews[mod.URI] 517 if !ok || len(viewSlice) == 0 { 518 continue 519 } 520 view := bestViewForURI(mod.URI, viewSlice) 521 snapshot, ok := viewToSnapshot[view] 522 if !ok { 523 panic(fmt.Sprintf("no snapshot for view %s", view.Folder())) 524 } 525 snapshotURIs[snapshot] = append(snapshotURIs[snapshot], mod.URI) 526 } 527 return snapshotURIs, releases, nil 528 } 529 530 func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []source.FileModification) []source.FileModification { 531 s.viewMu.RLock() 532 defer s.viewMu.RUnlock() 533 var snapshots []*snapshot 534 for _, v := range s.views { 535 snapshot, release := v.getSnapshot() 536 defer release() 537 snapshots = append(snapshots, snapshot) 538 } 539 knownDirs := knownDirectories(ctx, snapshots) 540 var result []source.FileModification 541 for _, c := range changes { 542 if _, ok := knownDirs[c.URI]; !ok { 543 result = append(result, c) 544 continue 545 } 546 affectedFiles := knownFilesInDir(ctx, snapshots, c.URI) 547 var fileChanges []source.FileModification 548 for uri := range affectedFiles { 549 fileChanges = append(fileChanges, source.FileModification{ 550 URI: uri, 551 Action: c.Action, 552 LanguageID: "", 553 OnDisk: c.OnDisk, 554 // changes to directories cannot include text or versions 555 }) 556 } 557 result = append(result, fileChanges...) 558 } 559 return result 560 } 561 562 // knownDirectories returns all of the directories known to the given 563 // snapshots, including workspace directories and their subdirectories. 564 func knownDirectories(ctx context.Context, snapshots []*snapshot) map[span.URI]struct{} { 565 result := map[span.URI]struct{}{} 566 for _, snapshot := range snapshots { 567 dirs := snapshot.workspace.dirs(ctx, snapshot) 568 for _, dir := range dirs { 569 result[dir] = struct{}{} 570 } 571 for _, dir := range snapshot.getKnownSubdirs(dirs) { 572 result[dir] = struct{}{} 573 } 574 } 575 return result 576 } 577 578 // knownFilesInDir returns the files known to the snapshots in the session. 579 // It does not respect symlinks. 580 func knownFilesInDir(ctx context.Context, snapshots []*snapshot, dir span.URI) map[span.URI]struct{} { 581 files := map[span.URI]struct{}{} 582 583 for _, snapshot := range snapshots { 584 for _, uri := range snapshot.knownFilesInDir(ctx, dir) { 585 files[uri] = struct{}{} 586 } 587 } 588 return files 589 } 590 591 func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModification) (map[span.URI]*overlay, error) { 592 s.overlayMu.Lock() 593 defer s.overlayMu.Unlock() 594 595 for _, c := range changes { 596 // Don't update overlays for metadata invalidations. 597 if c.Action == source.InvalidateMetadata { 598 continue 599 } 600 601 o, ok := s.overlays[c.URI] 602 603 // If the file is not opened in an overlay and the change is on disk, 604 // there's no need to update an overlay. If there is an overlay, we 605 // may need to update the overlay's saved value. 606 if !ok && c.OnDisk { 607 continue 608 } 609 610 // Determine the file kind on open, otherwise, assume it has been cached. 611 var kind source.FileKind 612 switch c.Action { 613 case source.Open: 614 kind = source.FileKindForLang(c.LanguageID) 615 default: 616 if !ok { 617 return nil, errors.Errorf("updateOverlays: modifying unopened overlay %v", c.URI) 618 } 619 kind = o.kind 620 } 621 622 // Closing a file just deletes its overlay. 623 if c.Action == source.Close { 624 delete(s.overlays, c.URI) 625 continue 626 } 627 628 // If the file is on disk, check if its content is the same as in the 629 // overlay. Saves and on-disk file changes don't come with the file's 630 // content. 631 text := c.Text 632 if text == nil && (c.Action == source.Save || c.OnDisk) { 633 if !ok { 634 return nil, fmt.Errorf("no known content for overlay for %s", c.Action) 635 } 636 text = o.text 637 } 638 // On-disk changes don't come with versions. 639 version := c.Version 640 if c.OnDisk || c.Action == source.Save { 641 version = o.version 642 } 643 hash := hashContents(text) 644 var sameContentOnDisk bool 645 switch c.Action { 646 case source.Delete: 647 // Do nothing. sameContentOnDisk should be false. 648 case source.Save: 649 // Make sure the version and content (if present) is the same. 650 if false && o.version != version { // Client no longer sends the version 651 return nil, errors.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version) 652 } 653 if c.Text != nil && o.hash != hash { 654 return nil, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI) 655 } 656 sameContentOnDisk = true 657 default: 658 fh, err := s.cache.getFile(ctx, c.URI) 659 if err != nil { 660 return nil, err 661 } 662 _, readErr := fh.Read() 663 sameContentOnDisk = (readErr == nil && fh.FileIdentity().Hash == hash) 664 } 665 o = &overlay{ 666 session: s, 667 uri: c.URI, 668 version: version, 669 text: text, 670 kind: kind, 671 hash: hash, 672 saved: sameContentOnDisk, 673 } 674 675 // When opening files, ensure that we actually have a well-defined view and file kind. 676 if c.Action == source.Open { 677 view, err := s.ViewOf(o.uri) 678 if err != nil { 679 return nil, errors.Errorf("updateOverlays: finding view for %s: %v", o.uri, err) 680 } 681 if kind := view.FileKind(o); kind == source.UnknownKind { 682 return nil, errors.Errorf("updateOverlays: unknown file kind for %s", o.uri) 683 } 684 } 685 686 s.overlays[c.URI] = o 687 } 688 689 // Get the overlays for each change while the session's overlay map is 690 // locked. 691 overlays := make(map[span.URI]*overlay) 692 for _, c := range changes { 693 if o, ok := s.overlays[c.URI]; ok { 694 overlays[c.URI] = o 695 } 696 } 697 return overlays, nil 698 } 699 700 func (s *Session) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 701 if overlay := s.readOverlay(uri); overlay != nil { 702 return overlay, nil 703 } 704 // Fall back to the cache-level file system. 705 return s.cache.getFile(ctx, uri) 706 } 707 708 func (s *Session) readOverlay(uri span.URI) *overlay { 709 s.overlayMu.Lock() 710 defer s.overlayMu.Unlock() 711 712 if overlay, ok := s.overlays[uri]; ok { 713 return overlay 714 } 715 return nil 716 } 717 718 func (s *Session) Overlays() []source.Overlay { 719 s.overlayMu.Lock() 720 defer s.overlayMu.Unlock() 721 722 overlays := make([]source.Overlay, 0, len(s.overlays)) 723 for _, overlay := range s.overlays { 724 overlays = append(overlays, overlay) 725 } 726 return overlays 727 } 728 729 func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[string]struct{} { 730 s.viewMu.RLock() 731 defer s.viewMu.RUnlock() 732 patterns := map[string]struct{}{} 733 for _, view := range s.views { 734 snapshot, release := view.getSnapshot() 735 for k, v := range snapshot.fileWatchingGlobPatterns(ctx) { 736 patterns[k] = v 737 } 738 release() 739 } 740 return patterns 741 }