@@ -63,7 +63,7 @@ type Session struct {
63
63
64
64
viewMu sync.Mutex
65
65
views []* View
66
- viewMap map [protocol.DocumentURI ]* View // file->best view; nil after shutdown
66
+ viewMap map [protocol.DocumentURI ]* View // file->best view or nil ; nil after shutdown
67
67
68
68
// snapshots is a counting semaphore that records the number
69
69
// of unreleased snapshots associated with this session.
@@ -117,6 +117,10 @@ func (s *Session) NewView(ctx context.Context, folder *Folder) (*View, *Snapshot
117
117
s .viewMu .Lock ()
118
118
defer s .viewMu .Unlock ()
119
119
120
+ if s .viewMap == nil {
121
+ return nil , nil , nil , fmt .Errorf ("session is shut down" )
122
+ }
123
+
120
124
// Querying the file system to check whether
121
125
// two folders denote the same existing directory.
122
126
if inode1 , err := os .Stat (filepath .FromSlash (folder .Dir .Path ())); err == nil {
@@ -303,22 +307,30 @@ var (
303
307
304
308
// RemoveView removes from the session the view rooted at the specified directory.
305
309
// It reports whether a view of that directory was removed.
306
- func (s * Session ) RemoveView (dir protocol.DocumentURI ) bool {
310
+ func (s * Session ) RemoveView (ctx context. Context , dir protocol.DocumentURI ) bool {
307
311
s .viewMu .Lock ()
308
312
defer s .viewMu .Unlock ()
313
+
314
+ if s .viewMap == nil {
315
+ return false // Session is shutdown.
316
+ }
317
+ s .viewMap = make (map [protocol.DocumentURI ]* View ) // reset view associations
318
+
319
+ var newViews []* View
309
320
for _ , view := range s .views {
310
321
if view .folder .Dir == dir {
311
- i := s .dropView (view )
312
- if i == - 1 {
313
- return false // can't happen
314
- }
315
- // delete this view... we don't care about order but we do want to make
316
- // sure we can garbage collect the view
317
- s .views = removeElement (s .views , i )
318
- return true
322
+ view .shutdown ()
323
+ } else {
324
+ newViews = append (newViews , view )
319
325
}
320
326
}
321
- return false
327
+ removed := len (s .views ) - len (newViews )
328
+ if removed != 1 {
329
+ // This isn't a bug report, because it could be a client-side bug.
330
+ event .Error (ctx , "removing view" , fmt .Errorf ("removed %d views, want exactly 1" , removed ))
331
+ }
332
+ s .views = newViews
333
+ return removed > 0
322
334
}
323
335
324
336
// View returns the view with a matching id, if present.
@@ -434,30 +446,30 @@ var errNoViews = errors.New("no views")
434
446
//
435
447
// May return (nil, nil) if no best view can be determined.
436
448
func (s * Session ) viewOfLocked (ctx context.Context , uri protocol.DocumentURI ) (* View , error ) {
449
+ if s .viewMap == nil {
450
+ return nil , errors .New ("session is shut down" )
451
+ }
437
452
v , hit := s .viewMap [uri ]
438
453
if ! hit {
439
454
// Cache miss: compute (and memoize) the best view.
440
455
fh , err := s .ReadFile (ctx , uri )
441
456
if err != nil {
442
457
return nil , err
443
458
}
444
- bestViews , err := BestViews (ctx , s , fh .URI (), s .views )
459
+ relevantViews , err := RelevantViews (ctx , s , fh .URI (), s .views )
445
460
if err != nil {
446
461
return nil , err
447
462
}
448
- v = matchingView (fh , bestViews )
449
- if v == nil && len (bestViews ) > 0 {
450
- // If we have candidate views, but none of them matched the file's build
463
+ v = matchingView (fh , relevantViews )
464
+ if v == nil && len (relevantViews ) > 0 {
465
+ // If we have relevant views, but none of them matched the file's build
451
466
// constraints, then we are still better off using one of them here.
452
467
// Otherwise, logic may fall back to an inferior view, which lacks
453
468
// relevant module information, leading to misleading diagnostics.
454
469
// (as in golang/go#60776).
455
- v = bestViews [0 ]
456
- }
457
- if s .viewMap == nil {
458
- return nil , errors .New ("session is shut down" )
470
+ v = relevantViews [0 ]
459
471
}
460
- s .viewMap [uri ] = v
472
+ s .viewMap [uri ] = v // may be nil
461
473
}
462
474
return v , nil
463
475
}
@@ -527,13 +539,13 @@ checkFiles:
527
539
if err != nil {
528
540
return nil , err
529
541
}
530
- bestViews , err := BestViews (ctx , fs , fh .URI (), defs )
542
+ relevantViews , err := RelevantViews (ctx , fs , fh .URI (), defs )
531
543
if err != nil {
532
544
// We should never call selectViewDefs with a cancellable context, so
533
545
// this should never fail.
534
546
return nil , bug .Errorf ("failed to find best view for open file: %v" , err )
535
547
}
536
- def := matchingView (fh , bestViews )
548
+ def := matchingView (fh , relevantViews )
537
549
if def != nil {
538
550
continue // file covered by an existing view
539
551
}
@@ -564,10 +576,11 @@ checkFiles:
564
576
// Views and viewDefinitions.
565
577
type viewDefiner interface { definition () * viewDefinition }
566
578
567
- // BestViews returns the most relevant subset of views for a given uri.
568
- //
569
- // This may be used to filter diagnostics to the most relevant builds.
570
- func BestViews [V viewDefiner ](ctx context.Context , fs file.Source , uri protocol.DocumentURI , views []V ) ([]V , error ) {
579
+ // RelevantViews returns the views that may contain the given URI, or nil if
580
+ // none exist. A view is "relevant" if, ignoring build constraints, it may have
581
+ // a workspace package containing uri. Therefore, the definition of relevance
582
+ // depends on the view type.
583
+ func RelevantViews [V viewDefiner ](ctx context.Context , fs file.Source , uri protocol.DocumentURI , views []V ) ([]V , error ) {
571
584
if len (views ) == 0 {
572
585
return nil , nil // avoid the call to findRootPattern
573
586
}
@@ -640,35 +653,35 @@ func BestViews[V viewDefiner](ctx context.Context, fs file.Source, uri protocol.
640
653
//
641
654
// We only consider one type of view, since the matching view created by
642
655
// defineView should be of the best type.
643
- var bestViews []V
656
+ var relevantViews []V
644
657
switch {
645
658
case len (workViews ) > 0 :
646
- bestViews = workViews
659
+ relevantViews = workViews
647
660
case len (modViews ) > 0 :
648
- bestViews = modViews
661
+ relevantViews = modViews
649
662
case len (gopathViews ) > 0 :
650
- bestViews = gopathViews
663
+ relevantViews = gopathViews
651
664
case len (goPackagesViews ) > 0 :
652
- bestViews = goPackagesViews
665
+ relevantViews = goPackagesViews
653
666
case len (adHocViews ) > 0 :
654
- bestViews = adHocViews
667
+ relevantViews = adHocViews
655
668
}
656
669
657
- return bestViews , nil
670
+ return relevantViews , nil
658
671
}
659
672
660
- // matchingView returns the View or viewDefinition out of bestViews that
673
+ // matchingView returns the View or viewDefinition out of relevantViews that
661
674
// matches the given file's build constraints, or nil if no match is found.
662
675
//
663
676
// Making this function generic is convenient so that we can avoid mapping view
664
677
// definitions back to views inside Session.DidModifyFiles, where performance
665
678
// matters. It is, however, not the cleanest application of generics.
666
679
//
667
680
// Note: keep this function in sync with defineView.
668
- func matchingView [V viewDefiner ](fh file.Handle , bestViews []V ) V {
681
+ func matchingView [V viewDefiner ](fh file.Handle , relevantViews []V ) V {
669
682
var zero V
670
683
671
- if len (bestViews ) == 0 {
684
+ if len (relevantViews ) == 0 {
672
685
return zero
673
686
}
674
687
@@ -678,14 +691,14 @@ func matchingView[V viewDefiner](fh file.Handle, bestViews []V) V {
678
691
// Note that the behavior here on non-existent files shouldn't matter much,
679
692
// since there will be a subsequent failure.
680
693
if fileKind (fh ) != file .Go || err != nil {
681
- return bestViews [0 ]
694
+ return relevantViews [0 ]
682
695
}
683
696
684
697
// Find the first view that matches constraints.
685
698
// Content trimming is nontrivial, so do this outside of the loop below.
686
699
path := fh .URI ().Path ()
687
700
content = trimContentForPortMatch (content )
688
- for _ , v := range bestViews {
701
+ for _ , v := range relevantViews {
689
702
def := v .definition ()
690
703
viewPort := port {def .GOOS (), def .GOARCH ()}
691
704
if viewPort .matches (path , content ) {
@@ -696,63 +709,35 @@ func matchingView[V viewDefiner](fh file.Handle, bestViews []V) V {
696
709
return zero // no view found
697
710
}
698
711
699
- // updateViewLocked recreates the view with the given options.
700
- //
701
- // If the resulting error is non-nil, the view may or may not have already been
702
- // dropped from the session.
703
- func (s * Session ) updateViewLocked (ctx context.Context , view * View , def * viewDefinition ) (* View , error ) {
704
- i := s .dropView (view )
705
- if i == - 1 {
706
- return nil , fmt .Errorf ("view %q not found" , view .id )
707
- }
708
-
709
- view , _ , release := s .createView (ctx , def )
710
- defer release ()
712
+ // ResetView resets the best view for the given URI.
713
+ func (s * Session ) ResetView (ctx context.Context , uri protocol.DocumentURI ) (* View , error ) {
714
+ s .viewMu .Lock ()
715
+ defer s .viewMu .Unlock ()
711
716
712
- // substitute the new view into the array where the old view was
713
- s .views [i ] = view
714
- s .viewMap = make (map [protocol.DocumentURI ]* View )
715
- return view , nil
716
- }
717
+ if s .viewMap == nil {
718
+ return nil , fmt .Errorf ("session is shut down" )
719
+ }
717
720
718
- // removeElement removes the ith element from the slice replacing it with the last element.
719
- // TODO(adonovan): generics, someday.
720
- func removeElement (slice []* View , index int ) []* View {
721
- last := len (slice ) - 1
722
- slice [index ] = slice [last ]
723
- slice [last ] = nil // aid GC
724
- return slice [:last ]
725
- }
721
+ view , err := s .viewOfLocked (ctx , uri )
722
+ if err != nil {
723
+ return nil , err
724
+ }
725
+ if view == nil {
726
+ return nil , fmt .Errorf ("no view for %s" , uri )
727
+ }
726
728
727
- // dropView removes v from the set of views for the receiver s and calls
728
- // v.shutdown, returning the index of v in s.views (if found), or -1 if v was
729
- // not found. s.viewMu must be held while calling this function.
730
- func (s * Session ) dropView (v * View ) int {
731
- // we always need to drop the view map
732
729
s .viewMap = make (map [protocol.DocumentURI ]* View )
733
- for i := range s .views {
734
- if v == s . views [ i ] {
735
- // we found the view, drop it and return the index it was found at
736
- s . views [ i ] = nil
730
+ for i , v := range s .views {
731
+ if v == view {
732
+ v2 , _ , release := s . createView ( ctx , view . viewDefinition )
733
+ release () // don't need the snapshot
737
734
v .shutdown ()
738
- return i
735
+ s .views [i ] = v2
736
+ return v2 , nil
739
737
}
740
738
}
741
- // TODO(rfindley): it looks wrong that we don't shutdown v in this codepath.
742
- // We should never get here.
743
- bug .Reportf ("tried to drop nonexistent view %q" , v .id )
744
- return - 1
745
- }
746
739
747
- // ResetView resets the best view for the given URI.
748
- func (s * Session ) ResetView (ctx context.Context , uri protocol.DocumentURI ) (* View , error ) {
749
- s .viewMu .Lock ()
750
- defer s .viewMu .Unlock ()
751
- v , err := s .viewOfLocked (ctx , uri )
752
- if err != nil {
753
- return nil , err
754
- }
755
- return s .updateViewLocked (ctx , v , v .viewDefinition )
740
+ return nil , bug .Errorf ("missing view" ) // can't happen...
756
741
}
757
742
758
743
// DidModifyFiles reports a file modification to the session. It returns
@@ -768,6 +753,11 @@ func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modif
768
753
s .viewMu .Lock ()
769
754
defer s .viewMu .Unlock ()
770
755
756
+ // Short circuit the logic below if s is shut down.
757
+ if s .viewMap == nil {
758
+ return nil , fmt .Errorf ("session is shut down" )
759
+ }
760
+
771
761
// Update overlays.
772
762
//
773
763
// This is done while holding viewMu because the set of open files affects
@@ -899,9 +889,10 @@ func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modif
899
889
for _ , mod := range modifications {
900
890
v , err := s .viewOfLocked (ctx , mod .URI )
901
891
if err != nil {
902
- // bestViewForURI only returns an error in the event of context
903
- // cancellation. Since state changes should occur on an uncancellable
904
- // context, an error here is a bug.
892
+ // viewOfLocked only returns an error in the event of context
893
+ // cancellation, or if the session is shut down. Since state changes
894
+ // should occur on an uncancellable context, and s.viewMap was checked at
895
+ // the top of this function, an error here is a bug.
905
896
bug .Reportf ("finding best view for change: %v" , err )
906
897
continue
907
898
}
0 commit comments