@@ -29,6 +29,7 @@ import (
2929 "golang.org/x/tools/gopls/internal/settings"
3030 "golang.org/x/tools/gopls/internal/util/maps"
3131 "golang.org/x/tools/gopls/internal/util/pathutil"
32+ "golang.org/x/tools/gopls/internal/util/slices"
3233 "golang.org/x/tools/gopls/internal/vulncheck"
3334 "golang.org/x/tools/internal/event"
3435 "golang.org/x/tools/internal/gocommand"
@@ -100,21 +101,12 @@ type View struct {
100101 // ignoreFilter is used for fast checking of ignored files.
101102 ignoreFilter * ignoreFilter
102103
103- // initCancelFirstAttempt can be used to terminate the view's first
104+ // cancelInitialWorkspaceLoad can be used to terminate the view's first
104105 // attempt at initialization.
105- initCancelFirstAttempt context.CancelFunc
106+ cancelInitialWorkspaceLoad context.CancelFunc
106107
107- // Track the latest snapshot via the snapshot field, guarded by snapshotMu.
108- //
109- // Invariant: whenever the snapshot field is overwritten, destroy(snapshot)
110- // is called on the previous (overwritten) snapshot while snapshotMu is held,
111- // incrementing snapshotWG. During shutdown the final snapshot is
112- // overwritten with nil and destroyed, guaranteeing that all observed
113- // snapshots have been destroyed via the destroy method, and snapshotWG may
114- // be waited upon to let these destroy operations complete.
115108 snapshotMu sync.Mutex
116- snapshot * Snapshot // latest snapshot; nil after shutdown has been called
117- snapshotWG sync.WaitGroup // refcount for pending destroy operations
109+ snapshot * Snapshot // latest snapshot; nil after shutdown has been called
118110
119111 // initialWorkspaceLoad is closed when the first workspace initialization has
120112 // completed. If we failed to load, we only retry if the go.mod file changes,
@@ -513,11 +505,10 @@ func (v *View) filterFunc() func(protocol.DocumentURI) bool {
513505 }
514506}
515507
516- // shutdown releases resources associated with the view, and waits for ongoing
517- // work to complete.
508+ // shutdown releases resources associated with the view.
518509func (v * View ) shutdown () {
519510 // Cancel the initial workspace load if it is still running.
520- v .initCancelFirstAttempt ()
511+ v .cancelInitialWorkspaceLoad ()
521512
522513 v .snapshotMu .Lock ()
523514 if v .snapshot != nil {
@@ -526,8 +517,6 @@ func (v *View) shutdown() {
526517 v .snapshot = nil
527518 }
528519 v .snapshotMu .Unlock ()
529-
530- v .snapshotWG .Wait ()
531520}
532521
533522// IgnoredFile reports if a file would be ignored by a `go list` of the whole
@@ -767,16 +756,33 @@ type StateChange struct {
767756 GCDetails map [metadata.PackageID ]bool // package -> whether or not we want details
768757}
769758
770- // Invalidate processes the provided state change, invalidating any derived
759+ // InvalidateView processes the provided state change, invalidating any derived
771760// results that depend on the changed state.
772761//
773762// The resulting snapshot is non-nil, representing the outcome of the state
774763// change. The second result is a function that must be called to release the
775764// snapshot when the snapshot is no longer needed.
776765//
777- // The resulting bool reports whether the new View needs to be re-diagnosed.
778- // See Snapshot.clone for more details.
779- func (v * View ) Invalidate (ctx context.Context , changed StateChange ) (* Snapshot , func (), bool ) {
766+ // An error is returned if the given view is no longer active in the session.
767+ func (s * Session ) InvalidateView (ctx context.Context , view * View , changed StateChange ) (* Snapshot , func (), error ) {
768+ s .viewMu .Lock ()
769+ defer s .viewMu .Unlock ()
770+
771+ if ! slices .Contains (s .views , view ) {
772+ return nil , nil , fmt .Errorf ("view is no longer active" )
773+ }
774+ snapshot , release , _ := s .invalidateViewLocked (ctx , view , changed )
775+ return snapshot , release , nil
776+ }
777+
778+ // invalidateViewLocked invalidates the content of the given view.
779+ // (See [Session.InvalidateView]).
780+ //
781+ // The resulting bool reports whether the View needs to be re-diagnosed.
782+ // (See [Snapshot.clone]).
783+ //
784+ // s.viewMu must be held while calling this method.
785+ func (s * Session ) invalidateViewLocked (ctx context.Context , v * View , changed StateChange ) (* Snapshot , func (), bool ) {
780786 // Detach the context so that content invalidation cannot be canceled.
781787 ctx = xcontext .Detach (ctx )
782788
@@ -799,9 +805,9 @@ func (v *View) Invalidate(ctx context.Context, changed StateChange) (*Snapshot,
799805 // TODO(rfindley): shouldn't we do this before canceling?
800806 prevSnapshot .AwaitInitialized (ctx )
801807
802- // Save one lease of the cloned snapshot in the view.
803808 var needsDiagnosis bool
804- v .snapshot , needsDiagnosis = prevSnapshot .clone (ctx , v .baseCtx , changed )
809+ s .snapshotWG .Add (1 )
810+ v .snapshot , needsDiagnosis = prevSnapshot .clone (ctx , v .baseCtx , changed , s .snapshotWG .Done )
805811
806812 // Remove the initial reference created when prevSnapshot was created.
807813 prevSnapshot .decref ()
0 commit comments