Skip to content

Commit 34a8786

Browse files
committed
fix(amazonq): Reduce plugin start-up latency
1 parent b39ab4e commit 34a8786

File tree

5 files changed

+56
-13
lines changed

5 files changed

+56
-13
lines changed

packages/amazonq/src/extension.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import * as vscode from 'vscode'
4444
import { registerCommands } from './commands'
4545
import { focusAmazonQPanel } from 'aws-core-vscode/codewhispererChat'
4646
import { activate as activateAmazonqLsp } from './lsp/activation'
47-
import { activate as activateInlineCompletion } from './app/inline/activation'
4847
import { hasGlibcPatch } from './lsp/client'
4948

5049
export const amazonQContextPrefix = 'amazonq'
@@ -124,20 +123,18 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is
124123
// Configure proxy settings early
125124
await ProxyUtil.configureProxyForLanguageServer()
126125

126+
// Parallelize independent activation tasks
127127
// This contains every lsp agnostic things (auth, security scan, code scan)
128-
await activateCodeWhisperer(extContext as ExtContext)
129-
if (
130-
(Experiments.instance.get('amazonqLSP', true) || Auth.instance.isInternalAmazonUser()) &&
131-
(!isAmazonLinux2() || hasGlibcPatch())
132-
) {
133-
// start the Amazon Q LSP for internal users first
134-
// for AL2, start LSP if glibc patch is found
135-
await activateAmazonqLsp(context)
136-
}
137-
if (!Experiments.instance.get('amazonqLSPInline', true)) {
138-
await activateInlineCompletion()
128+
const activationTasks = [activateCodeWhisperer(extContext as ExtContext)]
129+
130+
if (!isAmazonLinux2() || hasGlibcPatch()) {
131+
// Activate Amazon Q LSP for everyone unless they're using AL2 without the glibc patch
132+
activationTasks.push(activateAmazonqLsp(context))
139133
}
140134

135+
// Wait for all activation tasks to complete
136+
await Promise.all(activationTasks)
137+
141138
// Generic extension commands
142139
registerGenericCommands(context, amazonQContextPrefix)
143140

packages/core/src/codewhisperer/commands/basicCommands.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,12 @@ const registerToolkitApiCallbackOnce = once(() => {
634634
export const registerToolkitApiCallback = Commands.declare(
635635
{ id: 'aws.amazonq.refreshConnectionCallback' },
636636
() => async (toolkitApi?: any) => {
637+
// Early return if already registered to avoid duplicate work
638+
if (_toolkitApi) {
639+
getLogger().debug('Toolkit API callback already registered, skipping')
640+
return
641+
}
642+
637643
// While the Q/CW exposes an API for the Toolkit to register callbacks on auth changes,
638644
// we need to do it manually here because the Toolkit would have been unable to call
639645
// this API if the Q/CW extension started afterwards (and this code block is running).

packages/core/src/codewhisperer/region/regionProfileManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class RegionProfileManager {
7777
result: undefined,
7878
},
7979
},
80-
{ timeout: 15000, interval: 1500, truthy: true }
80+
{ timeout: 15000, interval: 500, truthy: true }
8181
)
8282
}
8383

packages/core/src/shared/featureConfig.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export const featureDefinitions = new Map<FeatureName, FeatureContext>([
5555

5656
export class FeatureConfigProvider {
5757
private featureConfigs = new Map<string, FeatureContext>()
58+
private fetchPromise: Promise<void> | undefined = undefined
59+
private lastFetchTime = 0
60+
private readonly minFetchInterval = 5000 // 5 seconds minimum between fetches
5861

5962
static #instance: FeatureConfigProvider
6063

@@ -123,6 +126,28 @@ export class FeatureConfigProvider {
123126
return
124127
}
125128

129+
// Debounce multiple concurrent calls
130+
const now = performance.now()
131+
if (this.fetchPromise && now - this.lastFetchTime < this.minFetchInterval) {
132+
getLogger().debug('amazonq: Debouncing feature config fetch')
133+
return this.fetchPromise
134+
}
135+
136+
if (this.fetchPromise) {
137+
return this.fetchPromise
138+
}
139+
140+
this.lastFetchTime = now
141+
this.fetchPromise = this._fetchFeatureConfigsInternal()
142+
143+
try {
144+
await this.fetchPromise
145+
} finally {
146+
this.fetchPromise = undefined
147+
}
148+
}
149+
150+
private async _fetchFeatureConfigsInternal(): Promise<void> {
126151
getLogger().debug('amazonq: Fetching feature configs')
127152
try {
128153
const response = await this.listFeatureEvaluations()

packages/core/src/shared/utilities/resourceCache.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ export abstract class CachedResource<V> {
6060
abstract resourceProvider(): Promise<V>
6161

6262
async getResource(): Promise<V> {
63+
// Check cache without locking first
64+
const quickCheck = this.readCacheOrDefault()
65+
if (quickCheck.resource.result && !quickCheck.resource.locked) {
66+
const duration = now() - quickCheck.resource.timestamp
67+
if (duration < this.expirationInMilli) {
68+
logger.debug(
69+
`cache hit (fast path), duration(%sms) is less than expiration(%sms), returning cached value: %s`,
70+
duration,
71+
this.expirationInMilli,
72+
this.key
73+
)
74+
return quickCheck.resource.result
75+
}
76+
}
77+
6378
const cachedValue = await this.tryLoadResourceAndLock()
6479
const resource = cachedValue?.resource
6580

0 commit comments

Comments
 (0)