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