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 "strings" 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 options source.Options 29 30 viewMu sync.Mutex 31 views []*View 32 viewMap map[span.URI]*View 33 34 overlayMu sync.Mutex 35 overlays map[span.URI]*overlay 36 37 // gocmdRunner guards go command calls from concurrency errors. 38 gocmdRunner *gocommand.Runner 39} 40 41type overlay struct { 42 session *Session 43 uri span.URI 44 text []byte 45 hash string 46 version float64 47 kind source.FileKind 48 49 // saved is true if a file matches the state on disk, 50 // and therefore does not need to be part of the overlay sent to go/packages. 51 saved bool 52} 53 54func (o *overlay) Read() ([]byte, error) { 55 return o.text, nil 56} 57 58func (o *overlay) FileIdentity() source.FileIdentity { 59 return source.FileIdentity{ 60 URI: o.uri, 61 Hash: o.hash, 62 Kind: o.kind, 63 } 64} 65 66func (o *overlay) VersionedFileIdentity() source.VersionedFileIdentity { 67 return source.VersionedFileIdentity{ 68 URI: o.uri, 69 SessionID: o.session.id, 70 Version: o.version, 71 } 72} 73 74func (o *overlay) Kind() source.FileKind { 75 return o.kind 76} 77 78func (o *overlay) URI() span.URI { 79 return o.uri 80} 81 82func (o *overlay) Version() float64 { 83 return o.version 84} 85 86func (o *overlay) Session() string { 87 return o.session.id 88} 89 90func (o *overlay) Saved() bool { 91 return o.saved 92} 93 94// closedFile implements LSPFile for a file that the editor hasn't told us about. 95type closedFile struct { 96 source.FileHandle 97} 98 99func (c *closedFile) VersionedFileIdentity() source.VersionedFileIdentity { 100 return source.VersionedFileIdentity{ 101 URI: c.FileHandle.URI(), 102 SessionID: "", 103 Version: 0, 104 } 105} 106 107func (c *closedFile) Session() string { 108 return "" 109} 110 111func (c *closedFile) Version() float64 { 112 return 0 113} 114 115func (s *Session) ID() string { return s.id } 116func (s *Session) String() string { return s.id } 117 118func (s *Session) Options() source.Options { 119 return s.options 120} 121 122func (s *Session) SetOptions(options source.Options) { 123 s.options = options 124} 125 126func (s *Session) Shutdown(ctx context.Context) { 127 s.viewMu.Lock() 128 defer s.viewMu.Unlock() 129 for _, view := range s.views { 130 view.shutdown(ctx) 131 } 132 s.views = nil 133 s.viewMap = nil 134 event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s)) 135} 136 137func (s *Session) Cache() interface{} { 138 return s.cache 139} 140 141func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, source.Snapshot, func(), error) { 142 s.viewMu.Lock() 143 defer s.viewMu.Unlock() 144 view, snapshot, release, err := s.createView(ctx, name, folder, options, 0) 145 if err != nil { 146 return nil, nil, func() {}, err 147 } 148 s.views = append(s.views, view) 149 // we always need to drop the view map 150 s.viewMap = make(map[span.URI]*View) 151 return view, snapshot, release, nil 152} 153 154func (s *Session) createView(ctx context.Context, name string, folder span.URI, options source.Options, snapshotID uint64) (*View, *snapshot, func(), error) { 155 index := atomic.AddInt64(&viewIndex, 1) 156 // We want a true background context and not a detached context here 157 // the spans need to be unrelated and no tag values should pollute it. 158 baseCtx := event.Detach(xcontext.Detach(ctx)) 159 backgroundCtx, cancel := context.WithCancel(baseCtx) 160 161 v := &View{ 162 session: s, 163 initialized: make(chan struct{}), 164 initializationSema: make(chan struct{}, 1), 165 initializeOnce: &sync.Once{}, 166 id: strconv.FormatInt(index, 10), 167 options: options, 168 baseCtx: baseCtx, 169 backgroundCtx: backgroundCtx, 170 cancel: cancel, 171 name: name, 172 folder: folder, 173 filesByURI: make(map[span.URI]*fileBase), 174 filesByBase: make(map[string][]*fileBase), 175 snapshot: &snapshot{ 176 id: snapshotID, 177 packages: make(map[packageKey]*packageHandle), 178 ids: make(map[span.URI][]packageID), 179 metadata: make(map[packageID]*metadata), 180 files: make(map[span.URI]source.VersionedFileHandle), 181 goFiles: make(map[parseKey]*parseGoHandle), 182 importedBy: make(map[packageID][]packageID), 183 actions: make(map[actionKey]*actionHandle), 184 workspacePackages: make(map[packageID]packagePath), 185 unloadableFiles: make(map[span.URI]struct{}), 186 parseModHandles: make(map[span.URI]*parseModHandle), 187 }, 188 } 189 v.snapshot.view = v 190 v.snapshot.active.Add(1) 191 192 if v.session.cache.options != nil { 193 v.session.cache.options(&v.options) 194 } 195 // Set the module-specific information. 196 if err := v.setBuildInformation(ctx, folder, options.Env, v.options.TempModfile); err != nil { 197 return nil, nil, func() {}, err 198 } 199 200 // We have v.goEnv now. 201 v.processEnv = &imports.ProcessEnv{ 202 GocmdRunner: s.gocmdRunner, 203 WorkingDir: folder.Filename(), 204 Env: v.goEnv, 205 } 206 207 // Set the first snapshot's workspace directories. The view's modURI was 208 // set by setBuildInformation. 209 var fh source.FileHandle 210 if v.modURI != "" { 211 fh, _ = s.GetFile(ctx, v.modURI) 212 } 213 v.snapshot.workspaceDirectories = v.snapshot.findWorkspaceDirectories(ctx, fh) 214 215 // Initialize the view without blocking. 216 initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx)) 217 v.initCancelFirstAttempt = initCancel 218 go v.initialize(initCtx, v.snapshot, true) 219 220 v.snapshot.active.Add(1) 221 return v, v.snapshot, v.snapshot.active.Done, nil 222} 223 224// View returns the view by name. 225func (s *Session) View(name string) source.View { 226 s.viewMu.Lock() 227 defer s.viewMu.Unlock() 228 for _, view := range s.views { 229 if view.Name() == name { 230 return view 231 } 232 } 233 return nil 234} 235 236// ViewOf returns a view corresponding to the given URI. 237// If the file is not already associated with a view, pick one using some heuristics. 238func (s *Session) ViewOf(uri span.URI) (source.View, error) { 239 return s.viewOf(uri) 240} 241 242func (s *Session) viewOf(uri span.URI) (*View, error) { 243 s.viewMu.Lock() 244 defer s.viewMu.Unlock() 245 246 // Check if we already know this file. 247 if v, found := s.viewMap[uri]; found { 248 return v, nil 249 } 250 // Pick the best view for this file and memoize the result. 251 v, err := s.bestView(uri) 252 if err != nil { 253 return nil, err 254 } 255 s.viewMap[uri] = v 256 return v, nil 257} 258 259func (s *Session) viewsOf(uri span.URI) []*View { 260 s.viewMu.Lock() 261 defer s.viewMu.Unlock() 262 263 var views []*View 264 for _, view := range s.views { 265 if strings.HasPrefix(string(uri), string(view.Folder())) { 266 views = append(views, view) 267 } 268 } 269 return views 270} 271 272func (s *Session) Views() []source.View { 273 s.viewMu.Lock() 274 defer s.viewMu.Unlock() 275 result := make([]source.View, len(s.views)) 276 for i, v := range s.views { 277 result[i] = v 278 } 279 return result 280} 281 282// bestView finds the best view to associate a given URI with. 283// viewMu must be held when calling this method. 284func (s *Session) bestView(uri span.URI) (*View, error) { 285 if len(s.views) == 0 { 286 return nil, errors.Errorf("no views in the session") 287 } 288 // we need to find the best view for this file 289 var longest *View 290 for _, view := range s.views { 291 if longest != nil && len(longest.Folder()) > len(view.Folder()) { 292 continue 293 } 294 if view.contains(uri) { 295 longest = view 296 } 297 } 298 if longest != nil { 299 return longest, nil 300 } 301 // Try our best to return a view that knows the file. 302 for _, view := range s.views { 303 if view.knownFile(uri) { 304 return view, nil 305 } 306 } 307 // TODO: are there any more heuristics we can use? 308 return s.views[0], nil 309} 310 311func (s *Session) removeView(ctx context.Context, view *View) error { 312 s.viewMu.Lock() 313 defer s.viewMu.Unlock() 314 i, err := s.dropView(ctx, view) 315 if err != nil { 316 return err 317 } 318 // delete this view... we don't care about order but we do want to make 319 // sure we can garbage collect the view 320 s.views[i] = s.views[len(s.views)-1] 321 s.views[len(s.views)-1] = nil 322 s.views = s.views[:len(s.views)-1] 323 return nil 324} 325 326func (s *Session) updateView(ctx context.Context, view *View, options source.Options) (*View, error) { 327 s.viewMu.Lock() 328 defer s.viewMu.Unlock() 329 i, err := s.dropView(ctx, view) 330 if err != nil { 331 return nil, err 332 } 333 // Preserve the snapshot ID if we are recreating the view. 334 view.snapshotMu.Lock() 335 snapshotID := view.snapshot.id 336 view.snapshotMu.Unlock() 337 v, _, release, err := s.createView(ctx, view.name, view.folder, options, snapshotID) 338 release() 339 if err != nil { 340 // we have dropped the old view, but could not create the new one 341 // this should not happen and is very bad, but we still need to clean 342 // up the view array if it happens 343 s.views[i] = s.views[len(s.views)-1] 344 s.views[len(s.views)-1] = nil 345 s.views = s.views[:len(s.views)-1] 346 } 347 // substitute the new view into the array where the old view was 348 s.views[i] = v 349 return v, nil 350} 351 352func (s *Session) dropView(ctx context.Context, v *View) (int, error) { 353 // we always need to drop the view map 354 s.viewMap = make(map[span.URI]*View) 355 for i := range s.views { 356 if v == s.views[i] { 357 // we found the view, drop it and return the index it was found at 358 s.views[i] = nil 359 v.shutdown(ctx) 360 return i, nil 361 } 362 } 363 return -1, errors.Errorf("view %s for %v not found", v.Name(), v.Folder()) 364} 365 366func (s *Session) ModifyFiles(ctx context.Context, changes []source.FileModification) error { 367 _, releases, _, err := s.DidModifyFiles(ctx, changes) 368 for _, release := range releases { 369 release() 370 } 371 return err 372} 373 374func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) ([]source.Snapshot, []func(), []span.URI, error) { 375 views := make(map[*View]map[span.URI]source.VersionedFileHandle) 376 377 // Keep track of deleted files so that we can clear their diagnostics. 378 // A file might be re-created after deletion, so only mark files that 379 // have truly been deleted. 380 deletions := map[span.URI]struct{}{} 381 382 overlays, err := s.updateOverlays(ctx, changes) 383 if err != nil { 384 return nil, nil, nil, err 385 } 386 var forceReloadMetadata bool 387 for _, c := range changes { 388 if c.Action == source.InvalidateMetadata { 389 forceReloadMetadata = true 390 } 391 392 // Look through all of the session's views, invalidating the file for 393 // all of the views to which it is known. 394 for _, view := range s.views { 395 // Don't propagate changes that are outside of the view's scope 396 // or knowledge. 397 if !view.relevantChange(c) { 398 continue 399 } 400 // Make sure that the file is added to the view. 401 if _, err := view.getFile(c.URI); err != nil { 402 return nil, nil, nil, err 403 } 404 if _, ok := views[view]; !ok { 405 views[view] = make(map[span.URI]source.VersionedFileHandle) 406 } 407 var ( 408 fh source.VersionedFileHandle 409 ok bool 410 ) 411 if fh, ok = overlays[c.URI]; ok { 412 views[view][c.URI] = fh 413 delete(deletions, c.URI) 414 } else { 415 fsFile, err := s.cache.getFile(ctx, c.URI) 416 if err != nil { 417 return nil, nil, nil, err 418 } 419 fh = &closedFile{fsFile} 420 views[view][c.URI] = fh 421 if _, err := fh.Read(); err != nil { 422 deletions[c.URI] = struct{}{} 423 } 424 } 425 // If the file change is to a go.mod file, and initialization for 426 // the view has previously failed, we should attempt to retry. 427 // TODO(rstambler): We can use unsaved contents with -modfile, so 428 // maybe we should do that and retry on any change? 429 if fh.Kind() == source.Mod && (c.OnDisk || c.Action == source.Save) { 430 view.maybeReinitialize() 431 } 432 } 433 } 434 var snapshots []source.Snapshot 435 var releases []func() 436 for view, uris := range views { 437 snapshot, release := view.invalidateContent(ctx, uris, forceReloadMetadata) 438 snapshots = append(snapshots, snapshot) 439 releases = append(releases, release) 440 } 441 var deletionsSlice []span.URI 442 for uri := range deletions { 443 deletionsSlice = append(deletionsSlice, uri) 444 } 445 return snapshots, releases, deletionsSlice, nil 446} 447 448func (s *Session) isOpen(uri span.URI) bool { 449 s.overlayMu.Lock() 450 defer s.overlayMu.Unlock() 451 452 _, open := s.overlays[uri] 453 return open 454} 455 456func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModification) (map[span.URI]*overlay, error) { 457 s.overlayMu.Lock() 458 defer s.overlayMu.Unlock() 459 460 for _, c := range changes { 461 // Don't update overlays for metadata invalidations. 462 if c.Action == source.InvalidateMetadata { 463 continue 464 } 465 466 o, ok := s.overlays[c.URI] 467 468 // If the file is not opened in an overlay and the change is on disk, 469 // there's no need to update an overlay. If there is an overlay, we 470 // may need to update the overlay's saved value. 471 if !ok && c.OnDisk { 472 continue 473 } 474 475 // Determine the file kind on open, otherwise, assume it has been cached. 476 var kind source.FileKind 477 switch c.Action { 478 case source.Open: 479 kind = source.DetectLanguage(c.LanguageID, c.URI.Filename()) 480 default: 481 if !ok { 482 return nil, errors.Errorf("updateOverlays: modifying unopened overlay %v", c.URI) 483 } 484 kind = o.kind 485 } 486 if kind == source.UnknownKind { 487 return nil, errors.Errorf("updateOverlays: unknown file kind for %s", c.URI) 488 } 489 490 // Closing a file just deletes its overlay. 491 if c.Action == source.Close { 492 delete(s.overlays, c.URI) 493 continue 494 } 495 496 // If the file is on disk, check if its content is the same as in the 497 // overlay. Saves and on-disk file changes don't come with the file's 498 // content. 499 text := c.Text 500 if text == nil && (c.Action == source.Save || c.OnDisk) { 501 if !ok { 502 return nil, fmt.Errorf("no known content for overlay for %s", c.Action) 503 } 504 text = o.text 505 } 506 // On-disk changes don't come with versions. 507 version := c.Version 508 if c.OnDisk { 509 version = o.version 510 } 511 hash := hashContents(text) 512 var sameContentOnDisk bool 513 switch c.Action { 514 case source.Delete: 515 // Do nothing. sameContentOnDisk should be false. 516 case source.Save: 517 // Make sure the version and content (if present) is the same. 518 if o.version != version { 519 return nil, errors.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version) 520 } 521 if c.Text != nil && o.hash != hash { 522 return nil, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI) 523 } 524 sameContentOnDisk = true 525 default: 526 fh, err := s.cache.getFile(ctx, c.URI) 527 if err != nil { 528 return nil, err 529 } 530 _, readErr := fh.Read() 531 sameContentOnDisk = (readErr == nil && fh.FileIdentity().Hash == hash) 532 } 533 o = &overlay{ 534 session: s, 535 uri: c.URI, 536 version: version, 537 text: text, 538 kind: kind, 539 hash: hash, 540 saved: sameContentOnDisk, 541 } 542 s.overlays[c.URI] = o 543 } 544 545 // Get the overlays for each change while the session's overlay map is 546 // locked. 547 overlays := make(map[span.URI]*overlay) 548 for _, c := range changes { 549 if o, ok := s.overlays[c.URI]; ok { 550 overlays[c.URI] = o 551 } 552 } 553 return overlays, nil 554} 555 556func (s *Session) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 557 if overlay := s.readOverlay(uri); overlay != nil { 558 return overlay, nil 559 } 560 // Fall back to the cache-level file system. 561 return s.cache.getFile(ctx, uri) 562} 563 564func (s *Session) readOverlay(uri span.URI) *overlay { 565 s.overlayMu.Lock() 566 defer s.overlayMu.Unlock() 567 568 if overlay, ok := s.overlays[uri]; ok { 569 return overlay 570 } 571 return nil 572} 573 574func (s *Session) Overlays() []source.Overlay { 575 s.overlayMu.Lock() 576 defer s.overlayMu.Unlock() 577 578 overlays := make([]source.Overlay, 0, len(s.overlays)) 579 for _, overlay := range s.overlays { 580 overlays = append(overlays, overlay) 581 } 582 return overlays 583} 584