Skip to content

Commit d128e3d

Browse files
authored
Cancel auto-import cache warming if changes come in while building (#3454)
1 parent ae8ae79 commit d128e3d

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

internal/project/session.go

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ type Session struct {
135135
diagnosticsRefreshCancel context.CancelFunc
136136
diagnosticsRefreshMu sync.Mutex
137137

138+
// warmAutoImportCancel is the cancelation function for a running
139+
// auto-import cache warming task. It is cancelled on file opens,
140+
// closes, changes, watched-file changes, new auto-import warming
141+
// requests, and when the session closes.
142+
warmAutoImportCancel context.CancelFunc
143+
warmAutoImportMu sync.Mutex
144+
138145
// idleCacheCleanTimer is a resettable timer for scheduling idle disk
139146
// cache cleans. The timer resets on any file event (open, close,
140147
// change, save, watch) and fires after 30 seconds of inactivity.
@@ -270,6 +277,7 @@ func (s *Session) InitializeWithUserConfig(config lsutil.UserPreferences) {
270277

271278
func (s *Session) DidOpenFile(ctx context.Context, uri lsproto.DocumentUri, version int32, content string, languageKind lsproto.LanguageKind) {
272279
s.cancelDiagnosticsRefresh()
280+
s.cancelWarmAutoImportCache()
273281
s.scheduleIdleCacheClean()
274282
s.snapshotUpdateMu.Lock()
275283
defer s.snapshotUpdateMu.Unlock()
@@ -294,6 +302,7 @@ func (s *Session) DidOpenFile(ctx context.Context, uri lsproto.DocumentUri, vers
294302

295303
func (s *Session) DidCloseFile(ctx context.Context, uri lsproto.DocumentUri) {
296304
s.cancelDiagnosticsRefresh()
305+
s.cancelWarmAutoImportCache()
297306
s.scheduleIdleCacheClean()
298307
s.pendingFileChangesMu.Lock()
299308
defer s.pendingFileChangesMu.Unlock()
@@ -305,6 +314,7 @@ func (s *Session) DidCloseFile(ctx context.Context, uri lsproto.DocumentUri) {
305314

306315
func (s *Session) DidChangeFile(ctx context.Context, uri lsproto.DocumentUri, version int32, changes []lsproto.TextDocumentContentChangePartialOrWholeDocument) {
307316
s.cancelDiagnosticsRefresh()
317+
s.cancelWarmAutoImportCache()
308318
s.scheduleIdleCacheClean()
309319
s.pendingFileChangesMu.Lock()
310320
defer s.pendingFileChangesMu.Unlock()
@@ -353,6 +363,7 @@ func (s *Session) DidChangeWatchedFiles(ctx context.Context, changes []*lsproto.
353363

354364
// Schedule a debounced diagnostics refresh
355365
s.ScheduleDiagnosticsRefresh()
366+
s.cancelWarmAutoImportCache()
356367
s.scheduleIdleCacheClean()
357368
}
358369

@@ -416,6 +427,15 @@ func (s *Session) cancelDiagnosticsRefresh() {
416427
}
417428
}
418429

430+
func (s *Session) cancelWarmAutoImportCache() {
431+
s.warmAutoImportMu.Lock()
432+
defer s.warmAutoImportMu.Unlock()
433+
if s.warmAutoImportCancel != nil {
434+
s.warmAutoImportCancel()
435+
s.warmAutoImportCancel = nil
436+
}
437+
}
438+
419439
const idleCacheCleanDelay = 30 * time.Second
420440

421441
func (s *Session) scheduleIdleCacheClean() {
@@ -1245,6 +1265,8 @@ func (s *Session) updateWatches(oldSnapshot *Snapshot, newSnapshot *Snapshot) er
12451265
func (s *Session) Close() {
12461266
// Cancel any pending diagnostics refresh
12471267
s.cancelDiagnosticsRefresh()
1268+
// Cancel any pending auto-import cache warming
1269+
s.cancelWarmAutoImportCache()
12481270
// Cancel any pending idle cache clean
12491271
s.cancelIdleCacheClean()
12501272
// Cancel periodic performance telemetry
@@ -1590,6 +1612,56 @@ func (s *Session) warmAutoImportCache(ctx context.Context, change SnapshotChange
15901612
) {
15911613
return
15921614
}
1593-
_, _ = s.GetCurrentLanguageServiceWithAutoImports(ctx, changedFile)
1615+
1616+
// Cancel any previous auto-import warming and create a new cancellable context.
1617+
// Only publish the new cancel func if the derived context is still active,
1618+
// and make the stored cancel func a no-op once that warming task is done.
1619+
s.warmAutoImportMu.Lock()
1620+
if s.warmAutoImportCancel != nil {
1621+
s.warmAutoImportCancel()
1622+
}
1623+
warmCtx, cancel := context.WithCancel(ctx)
1624+
if warmCtx.Err() == nil {
1625+
s.warmAutoImportCancel = func() {
1626+
if warmCtx.Err() != nil {
1627+
return
1628+
}
1629+
s.logger.Logf("Cancelling auto-import warming for file %s", changedFile.FileName())
1630+
cancel()
1631+
}
1632+
}
1633+
s.warmAutoImportMu.Unlock()
1634+
1635+
if warmCtx.Err() != nil {
1636+
cancel()
1637+
return
1638+
}
1639+
defer cancel()
1640+
1641+
// Clone the snapshot with auto-imports using warmCtx so the expensive
1642+
// extraction work is cancelled if a file change arrives.
1643+
if !newSnapshot.tryRef() {
1644+
return
1645+
}
1646+
defer newSnapshot.Deref(s)
1647+
1648+
warmChange := SnapshotChange{
1649+
reason: UpdateReasonRequestedLanguageServiceWithAutoImports,
1650+
ResourceRequest: ResourceRequest{
1651+
Documents: []lsproto.DocumentUri{changedFile},
1652+
AutoImports: changedFile,
1653+
},
1654+
}
1655+
clonedSnapshot := newSnapshot.Clone(warmCtx, warmChange, newSnapshot.fs.overlays, s)
1656+
1657+
// If cancelled during clone, discard the incomplete result.
1658+
if warmCtx.Err() != nil {
1659+
clonedSnapshot.Deref(s)
1660+
return
1661+
}
1662+
1663+
// Conditionally adopt: if the session hasn't moved past newSnapshot,
1664+
// promote the clone so future requests benefit from the warmed cache.
1665+
s.adoptSnapshotChange(newSnapshot, clonedSnapshot)
15941666
}
15951667
}

internal/project/snapshot.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,21 @@ func (s *Snapshot) ref() {
503503
}
504504
}
505505

506+
// tryRef attempts to increment the snapshot's reference count. If the
507+
// snapshot is already disposed (refCount == 0), it returns false without
508+
// modifying the count. On success the caller must eventually call Deref.
509+
func (s *Snapshot) tryRef() bool {
510+
for {
511+
rc := s.refCount.Load()
512+
if rc <= 0 {
513+
return false
514+
}
515+
if s.refCount.CompareAndSwap(rc, rc+1) {
516+
return true
517+
}
518+
}
519+
}
520+
506521
// Deref decrements the snapshot's reference count. When the count reaches
507522
// zero, the snapshot is disposed and its resources are released.
508523
func (s *Snapshot) Deref(session *Session) {

0 commit comments

Comments
 (0)