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