Fix suggest model triggering on disposed inline completions model#320077
Merged
ulugbekna merged 1 commit intoJun 5, 2026
Conversation
The wait inside \`_waitForInlineCompletionsAndTrigger\` snapshotted \`inlineController.model.get()\` and then subscribed long-term to the snapshot's \`state\`/\`status\` derived observables. Those derived are owned only for debug — they are not registered to the model's \`_store\`, so after the controller swaps or disposes the model (text model replaced, readOnly toggled, controller disposed) they keep observing live external dependencies and continue to fire. That could cause the 750ms timeout to call \`stop()\` on a disposed model and the autorun to trigger quick suggest spuriously. Read \`inlineController.model\` as the first dependency of a single autorun; if it differs from the snapshot, bail out before touching \`state\`/\`status\`. A single autorun avoids the nested-autorun ordering hazard (no defined run order between independent autoruns that share a dependency).
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a race in SuggestModel._waitForInlineCompletionsAndTrigger where the wait could keep observing InlineCompletionsModel-owned derived observables after the inline model had been disposed/recreated (e.g. due to readOnly toggling), potentially leading to stop()/auto-trigger being applied to a dead inline model.
Changes:
- Bind the inline-completions wait to the current inline model by reading
inlineController.modelfirst within a singleautorun, and bailing out when the model instance changes. - Add a regression test that disposes the inline model mid-wait by toggling
readOnly, then verifies quick suggest is not triggered after the 750ms timeout window.
Show a summary per file
| File | Description |
|---|---|
| src/vs/editor/contrib/suggest/browser/suggestModel.ts | Ensures the inline-completions wait observes the controller’s current model instance and cleans up when it changes, avoiding actions on disposed models. |
| src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts | Adds coverage for the mid-wait inline model disposal scenario (via readOnly toggle) to prevent regressions. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 0
lszomoru
approved these changes
Jun 5, 2026
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.
Problem
The wait inside
SuggestModel._waitForInlineCompletionsAndTriggersnapshottedinlineController.model.get()and then subscribed long-term to that snapshot'sstate/statusderived observables.Those derived observables are owned only for debug — they are not registered to the
InlineCompletionsModel._store. The model itself is produced by aderivedDisposableinInlineCompletionsControllerand is disposed and recreated whenever its dependencies change:editor.setModel(...), etc.)readOnlyis toggledAfter the model is disposed, its
state/statusderived keep observing live external dependencies (_editorObs.versionId, config observables, …) and continue to fire.SuggestModeldoes not callcancel()ononDidChangeConfiguration, so areadOnlytoggle (concrete reproducer) leaves the wait running against a dead model:inlineModel.stop('automatic')on a disposed instance (mutates_isActiveon a dead model);triggerAndCleanUp(true)and can spuriously callthis.trigger({ auto: true }).Fix
Read
inlineController.modelas the first dependency of a single autorun. If the value differs from the snapshot we got at the start of the wait, the controller has swapped or disposed the model — bail out before any read onstate/status.A single autorun (rather than a nested outer/inner pair) is deliberate: independent autoruns share no run-ordering guarantee, so an inner state watcher could otherwise run on a disposed model before the outer model watcher cleaned it up.
Test
Added a regression test that:
readOnly: truemid-wait (disposes the model),Verified the test fails on the original code and passes on the fix.