ADFA-5231: Pin the live KtFile for the duration of an analysis - #1745
Open
itsaky-adfa wants to merge 4 commits into
Open
ADFA-5231: Pin the live KtFile for the duration of an analysis#1745itsaky-adfa wants to merge 4 commits into
itsaky-adfa wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
jatezzz
reviewed
Aug 26, 2026
jatezzz
requested changes
Aug 26, 2026
itsaky-adfa
force-pushed
the
ADFA-5231-migrate-call-sites
branch
from
August 26, 2026 15:33
96b520e to
656e748
Compare
jatezzz
approved these changes
Aug 26, 2026
Lands red on purpose: it is the ADFA-4165 regression, reduced to the smallest sequence that triggers it (acquire an instance, let a second request install a newer one for identical text, re-analyze the first).
An analysis that started against an older instance saw every declaration twice, once as its own PSI and once through DeclarationProvider, so FIR reported the file as conflicting with itself. Acquisition now pins the path for the whole scope and the raw accessors are private, so an unpinned analysis no longer compiles.
itsaky-adfa
force-pushed
the
ADFA-5231-migrate-call-sites
branch
from
August 27, 2026 09:06
656e748 to
f96a860
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack 3 of 5 for ADFA-5231. This is the layer that fixes the ticket.
Migrates every acquisition site onto the pinned scope from the previous PR, then makes the raw accessors
privateso an unpinned analysis stops compiling.Review by commit
Two commits, and the order matters:
add failing test for stale KtFile instance redeclarations- lands red on purpose, as the bisectable record of the regression. It fails with the ticket's own errors:INVISIBLE_REFERENCE: Cannot access 'fun extracted(...)': it is private in file.andCONFLICTING_OVERLOADS: Conflicting overloads: fun render(a: Int, b: Int): Int.pin the live KtFile for the duration of an analysis- turns it green.The PR tip is green; only the intermediate commit is red.
The migration
Every site had the same shape -
getCurrentKtFile(path).get()/.await(), thenproject.read { ... analyzeMaybeDangling(ktFile, ...) { ... } }- and becomes awithLiveKtFile(path) { live -> ... }scope usinglive.read/live.analyzing. It is one transformation repeated 13 times, so it reads faster than the line count suggests. Worth actual attention:KotlinCompletionsis the intricate one: its dangling placeholder copy now goes throughanalyzingVariant, which wiresoriginalFile/originalKtFileto the pinned instance so callers cannot forget to.GoToDefinition,FindUsages,OrganizeImportsAction,ImplementMembersActiontake the scope insideretryingOnPreemption, so each attempt pins afresh - the existing "awaited per attempt, not once" reasoning still holds and that comment is kept.FindUsages.ktFileForis deleted; the scope handles the open and on-disk cases itself.KotlinDiagnosticProvidernow drops a superseded result vialive.isStaleand reschedules instead of publishing diagnostics for text the user has already replaced.AdvancedKotlinEditHandleris the single production caller that legitimately needs an unpinned peek - UI thread, PSI only, no analysis session - so it sits behind@OptIn(UnpinnedKtFileAccess::class)on the function.getCurrentKtFile,getCurrentVersionedKtFileandgetCurrentKtFileIfPresentbecomeprivate. Comments that only restated the rule the API now enforces ("fetch the live KtFile BEFORE entering read", "safe to await a blocking refresh here") are deleted; ones that say something the types do not are kept.StaleKtFileInstanceDiagnosticsTestandCurrentKtFileCacheTestare rewritten to go through the scope - they called the now-private accessors and could not compile otherwise.Testing
462 tests, 0 failures in
:lsp:kotlin; 61 tests, 0 failures in:common. Removing the pin short-circuits fails the repro with the ticket's exact diagnostics.Review follow-ups
Two findings from review, both fixed here.
Import candidates resolve before the file is pinned.
AddImportAction.computeImportCandidatesused tohold the pin across
findSymbolBySimpleName(referenceName, limit = 0)- an unbounded SQLite scan thatnever reads the file - freezing live-PSI refresh for the path for the whole scan while concurrent
acquirers joined the frozen instance. The query now runs above
withLiveKtFileand is materializedthere, so the index's lazy source-active filter cannot trail into the scope either; the pin covers only
insertImport. An empty candidate list returns before the pin is taken, so the common no-candidates pathno longer resolves the live document at all.
AddImportActionPinScopeTestguards the ordering with aquery-time hook on the fixture's source index.
The superseded-diagnostics reschedule is a self-send, on one of two paths. Reached through
fileAnalyzer's own action (CompilationEnvironment.kt:194),env.fileAnalyzer.schedule(file)reads tothe worker as a newer key and cancels the run it came from. Reached through
KotlinLanguageServer.analyze()instead, it is an ordinary reschedule - so the comment scopes the claimrather than stating it flat.
That is intended - the key is still re-sent, and the cancelled tail was only going to publish
NO_UPDATE- but it does not read like an ordinary reschedule, so it now carries a comment saying so.KeyedDebouncingActionSelfScheduleTestin:commonpins down both halves of the mechanism; nothingcovered it before.
One behavioural consequence worth stating plainly: when the document moves on with no new keystroke
arriving, that analysis is computed and thrown away. Sustained typing does not take this path - each
keystroke schedules a key that cancels the analysis mid-flight through the same
select- so thecomputed-then-discarded case is narrower than the debounce interval suggests.