Skip to content

Commit 663f626

Browse files
authored
fix(amazonq): prevent race condition in inline completion provider (aws#8163)
## Problem When pressing Enter to trigger inline suggestions, ghost text fails to appear in the editor despite the language server returning valid suggestions. Root Cause: VS Code triggers two provideInlineCompletionItems calls simultaneously when Enter is pressed: 1. Automatic trigger (t=0ms) - sends LSP request, receives valid suggestions after ~380ms 2. Invoke trigger (t=2ms) - sends duplicate LSP request, receives empty response after ~6ms (language server skips concurrent requests) 3. VS Code uses the most recent provider response, which is the empty result from the second call, causing the first call's valid suggestions to be ignored. Evidence from logs: ``` [info] Sending inline completion request (474) [info] Sending inline completion request (476) [info] Received response (476): { sessionId: '', items: [] } ← Used by VS Code [info] Received response (474): { items: [{ itemId: '...', insertText: '...' }] } ← Ignored ``` ## Solution I am not able to have a working fix to prevent either automatic or invoke trigger, which is controlled in toolkit. So I implement request deduplication on Q side by tracking pending requests in the inline completion provider: * Shared Request Pattern: When a request is in progress, subsequent concurrent calls reuse the same pending promise instead of creating new LSP requests * Independent Cancellation: Each call checks its own CancellationToken after the shared request completes, allowing independent cancellation semantics Key Changes: * Added pendingRequest field to track in-flight requests * Wrapped provideInlineCompletionItems to check for and reuse pending requests * Added per-call cancellation check when reusing requests * Ensured pendingRequest is cleared after completion for fresh subsequent requests Tests: Tested on the same scenarios, both concurrent calls now return the same valid suggestions, ghost text appears reliably when pressing Enter Logs after fix: ``` [info] _provideInlineCompletionItems called (Automatic) [info] _provideInlineCompletionItems called (Invoke) [info] Reusing pending inline completion request to avoid race condition [info] Received response: { sessionId: '...', itemCount: 2, items: [...] } ``` --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent c3197fa commit 663f626

File tree

1 file changed

+44
-0
lines changed

1 file changed

+44
-0
lines changed

packages/amazonq/src/app/inline/completion.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ export class InlineCompletionManager implements Disposable {
213213

214214
export class AmazonQInlineCompletionItemProvider implements InlineCompletionItemProvider {
215215
private logger = getLogger()
216+
private pendingRequest: Promise<InlineCompletionItem[]> | undefined
217+
216218
constructor(
217219
private readonly languageClient: LanguageClient,
218220
private readonly recommendationService: RecommendationService,
@@ -300,6 +302,48 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
300302
options: JSON.stringify(getAllRecommendationsOptions),
301303
})
302304

305+
// If there's already a pending request, wait for it to complete instead of starting a new one
306+
// This prevents race conditions where multiple concurrent calls cause the later (empty) response
307+
// to override the earlier (valid) response
308+
if (this.pendingRequest) {
309+
getLogger().info('Reusing pending inline completion request to avoid race condition')
310+
try {
311+
const result = await this.pendingRequest
312+
// Check if THIS call's token was cancelled (not the original call's token)
313+
if (token.isCancellationRequested) {
314+
getLogger().info('Reused request completed but this call was cancelled')
315+
return []
316+
}
317+
return result
318+
} catch (e) {
319+
// If the pending request failed, continue with a new request
320+
getLogger().info('Pending request failed, starting new request: %O', e)
321+
}
322+
}
323+
324+
// Start a new request and track it
325+
this.pendingRequest = this._provideInlineCompletionItemsImpl(
326+
document,
327+
position,
328+
context,
329+
token,
330+
getAllRecommendationsOptions
331+
)
332+
333+
try {
334+
return await this.pendingRequest
335+
} finally {
336+
this.pendingRequest = undefined
337+
}
338+
}
339+
340+
private async _provideInlineCompletionItemsImpl(
341+
document: TextDocument,
342+
position: Position,
343+
context: InlineCompletionContext,
344+
token: CancellationToken,
345+
getAllRecommendationsOptions?: GetAllRecommendationsOptions
346+
): Promise<InlineCompletionItem[]> {
303347
if (vsCodeState.isCodeWhispererEditing) {
304348
getLogger().info('Q is editing, returning empty')
305349
return []

0 commit comments

Comments
 (0)