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