Skip to content

Commit 3a03e95

Browse files
authored
perf(amazonq): Adding backoff and retry for /test APIs (#6461)
### Problem - Unit test generation API does not have retry mechanisms. ## Solution - Adding retry mechanisms for UTG APIs for reducing error rate. - No functionality is effected with this PR change, so no new test cases were added. --- - 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 d6acae9 commit 3a03e95

File tree

2 files changed

+96
-36
lines changed

2 files changed

+96
-36
lines changed

packages/core/src/codewhisperer/service/securityScanHandler.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CodeScanStoppedError,
1515
onDemandFileScanState,
1616
} from '../models/model'
17-
import { sleep } from '../../shared/utilities/timeoutUtils'
17+
import { sleep, waitUntil } from '../../shared/utilities/timeoutUtils'
1818
import * as codewhispererClient from '../client/codewhisperer'
1919
import * as CodeWhispererConstants from '../models/constants'
2020
import { existsSync, statSync, readFileSync } from 'fs' // eslint-disable-line no-restricted-imports
@@ -363,29 +363,47 @@ export async function uploadArtifactToS3(
363363
headersObj['x-amz-server-side-encryption-aws-kms-key-id'] = resp.kmsKeyArn
364364
}
365365

366-
try {
367-
const response = await request.fetch('PUT', resp.uploadUrl, {
368-
body: readFileSync(fileName),
369-
headers: resp?.requestHeaders ?? headersObj,
370-
}).response
371-
logger.debug(`StatusCode: ${response.status}, Text: ${response.statusText}`)
372-
} catch (error) {
373-
let errorMessage = ''
374-
const isCodeScan = featureUseCase === FeatureUseCase.CODE_SCAN
375-
const featureType = isCodeScan ? 'security scans' : 'unit test generation'
376-
const defaultMessage = isCodeScan ? 'Security scan failed.' : 'Test generation failed.'
377-
getLogger().error(
378-
`Amazon Q is unable to upload workspace artifacts to Amazon S3 for ${featureType}. ` +
379-
'For more information, see the Amazon Q documentation or contact your network or organization administrator.'
380-
)
381-
const errorDesc = getTelemetryReasonDesc(error)
382-
if (errorDesc?.includes('"PUT" request failed with code "403"')) {
383-
errorMessage = '"PUT" request failed with code "403"'
384-
} else if (errorDesc?.includes('"PUT" request failed with code "503"')) {
385-
errorMessage = '"PUT" request failed with code "503"'
386-
} else {
387-
errorMessage = errorDesc ?? defaultMessage
366+
let errorMessage = ''
367+
const isCodeScan = featureUseCase === FeatureUseCase.CODE_SCAN
368+
const result = await waitUntil(
369+
async () => {
370+
try {
371+
const response = await request.fetch('PUT', resp.uploadUrl, {
372+
body: readFileSync(fileName),
373+
headers: resp?.requestHeaders ?? headersObj,
374+
}).response
375+
376+
logger.debug(`StatusCode: ${response.status}, Text: ${response.statusText}`)
377+
return response.status === 200 ? response : false
378+
} catch (error) {
379+
const featureType = isCodeScan ? 'security scans' : 'unit test generation'
380+
const defaultMessage = isCodeScan ? 'Security scan failed.' : 'Test generation failed.'
381+
382+
getLogger().error(
383+
`Amazon Q is unable to upload workspace artifacts to Amazon S3 for ${featureType}. ` +
384+
'For more information, see the Amazon Q documentation or contact your network or organization administrator.'
385+
)
386+
const errorDesc = getTelemetryReasonDesc(error)
387+
const errorCodes = new Map([
388+
['403', '"PUT" request failed with code "403"'],
389+
['503', '"PUT" request failed with code "503"'],
390+
])
391+
392+
errorMessage = errorDesc?.includes('"PUT" request failed with code "')
393+
? (errorCodes.get(errorDesc.match(/\d+/)?.[0] ?? '') ?? errorDesc)
394+
: (errorDesc ?? defaultMessage)
395+
396+
return false // Return false to continue retrying
397+
}
398+
},
399+
{
400+
interval: 200, // 200ms between attempts
401+
timeout: 1000, // 1 second timeout
402+
truthy: true,
388403
}
404+
)
405+
406+
if (!result) {
389407
throw isCodeScan ? new UploadArtifactToS3Error(errorMessage) : new UploadTestArtifactToS3Error(errorMessage)
390408
}
391409
}

packages/core/src/codewhisperer/service/testGenHandler.ts

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
TestGenTimedOutError,
2424
} from '../../amazonqTest/error'
2525
import { getMd5, uploadArtifactToS3 } from './securityScanHandler'
26-
import { fs, randomUUID, sleep, tempDirPath } from '../../shared'
26+
import { fs, randomUUID, sleep, tempDirPath, waitUntil } from '../../shared'
2727
import { ShortAnswer, testGenState } from '../models/model'
2828
import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession'
2929
import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient'
@@ -54,17 +54,39 @@ export async function getPresignedUrlAndUploadTestGen(zipMetadata: ZipMetadata)
5454
uploadIntent: CodeWhispererConstants.testGenUploadIntent,
5555
}
5656
logger.verbose(`Prepare for uploading src context...`)
57-
const srcResp = await codeWhisperer.codeWhispererClient.createUploadUrl(srcReq).catch((err) => {
58-
getLogger().error(`Failed getting presigned url for uploading src context. Request id: ${err.requestId}`)
59-
throw new CreateUploadUrlError(err.message)
60-
})
61-
logger.verbose(`CreateUploadUrlRequest requestId: ${srcResp.$response.requestId}`)
57+
let errorMessage = ''
58+
const result = await waitUntil(
59+
async () => {
60+
try {
61+
const srcResp = await codeWhisperer.codeWhispererClient.createUploadUrl(srcReq)
62+
// Only return the response if it's successful, otherwise return false to continue retrying
63+
return srcResp.$response?.httpResponse.statusCode === 200 ? srcResp : false
64+
} catch (err: any) {
65+
getLogger().error(
66+
`Failed getting presigned url for uploading src context. Request id: ${err.requestId}`
67+
)
68+
errorMessage = err.message
69+
return false // Return false to continue retrying
70+
}
71+
},
72+
{
73+
interval: 200, // 200ms between attempts
74+
timeout: 1000, // 1 second timeout
75+
truthy: true,
76+
}
77+
)
78+
79+
if (!result) {
80+
throw new CreateUploadUrlError(errorMessage)
81+
}
82+
83+
logger.verbose(`CreateUploadUrlRequest requestId: ${result.$response.requestId}`)
6284
logger.verbose(`Complete Getting presigned Url for uploading src context.`)
6385
logger.verbose(`Uploading src context...`)
64-
await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp, CodeWhispererConstants.FeatureUseCase.TEST_GENERATION)
86+
await uploadArtifactToS3(zipMetadata.zipFilePath, result, CodeWhispererConstants.FeatureUseCase.TEST_GENERATION)
6587
logger.verbose(`Complete uploading src context.`)
6688
const artifactMap: ArtifactMap = {
67-
SourceCode: srcResp.uploadId,
89+
SourceCode: result.uploadId,
6890
}
6991
return artifactMap
7092
}
@@ -102,11 +124,31 @@ export async function createTestJob(
102124
logger.debug('target line range start: %O', firstTargetLineRangeList?.start)
103125
logger.debug('target line range end: %O', firstTargetLineRangeList?.end)
104126

105-
const resp = await codewhispererClient.codeWhispererClient.startTestGeneration(req).catch((err) => {
106-
ChatSessionManager.Instance.getSession().startTestGenerationRequestId = err.requestId
107-
logger.error(`Failed creating test job. Request id: ${err.requestId}`)
108-
throw new CreateTestJobError(err.message)
109-
})
127+
let errorMessage = ''
128+
const resp = await waitUntil(
129+
async () => {
130+
try {
131+
const response = await codewhispererClient.codeWhispererClient.startTestGeneration(req)
132+
// Only return the response if it's successful, otherwise return false to continue retrying
133+
return response.$response?.httpResponse.statusCode === 200 ? response : false
134+
} catch (err: any) {
135+
ChatSessionManager.Instance.getSession().startTestGenerationRequestId = err.requestId
136+
logger.error(`Failed creating test job. Request id: ${err.requestId}`)
137+
errorMessage = err.message
138+
return false // Return false to continue retrying
139+
}
140+
},
141+
{
142+
interval: 200, // 200ms between attempts
143+
timeout: 1000, // 1 second timeout
144+
truthy: true,
145+
}
146+
)
147+
148+
if (!resp) {
149+
throw new CreateTestJobError(errorMessage)
150+
}
151+
110152
logger.info('Unit test generation request id: %s', resp.$response.requestId)
111153
logger.debug('Unit test generation data: %O', resp.$response.data)
112154
ChatSessionManager.Instance.getSession().startTestGenerationRequestId = resp.$response.requestId

0 commit comments

Comments
 (0)