@@ -12,6 +12,8 @@ import software.amazon.awssdk.services.codewhispererruntime.model.CodeAnalysisUp
1212import software.amazon.awssdk.services.codewhispererruntime.model.CodeFixUploadContext
1313import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlRequest
1414import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlResponse
15+ import software.amazon.awssdk.services.codewhispererruntime.model.InternalServerException
16+ import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingException
1517import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext
1618import software.amazon.awssdk.services.codewhispererruntime.model.UploadIntent
1719import software.amazon.awssdk.utils.IoUtils
@@ -82,40 +84,50 @@ class CodeWhispererZipUploadManager(private val project: Project) {
8284 requestHeaders : Map <String , String >? ,
8385 featureUseCase : CodeWhispererConstants .FeatureName ,
8486 ) {
85- try {
86- val uploadIdJson = """ {"uploadId":"$uploadId "}"""
87- HttpRequests .put(url, " application/zip" ).userAgent(AwsClientManager .getUserAgent()).tuner {
88- if (requestHeaders.isNullOrEmpty()) {
89- it.setRequestProperty(CONTENT_MD5 , md5)
90- it.setRequestProperty(CONTENT_TYPE , APPLICATION_ZIP )
91- it.setRequestProperty(SERVER_SIDE_ENCRYPTION , AWS_KMS )
92- if (kmsArn?.isNotEmpty() == true ) {
93- it.setRequestProperty(SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID , kmsArn)
94- }
95- it.setRequestProperty(SERVER_SIDE_ENCRYPTION_CONTEXT , Base64 .getEncoder().encodeToString(uploadIdJson.toByteArray()))
96- } else {
97- requestHeaders.forEach { entry ->
98- it.setRequestProperty(entry.key, entry.value)
87+ RetryableOperation <Unit >().execute(
88+ operation = {
89+ val uploadIdJson = """ {"uploadId":"$uploadId "}"""
90+ HttpRequests .put(url, " application/zip" ).userAgent(AwsClientManager .getUserAgent()).tuner {
91+ if (requestHeaders.isNullOrEmpty()) {
92+ it.setRequestProperty(CONTENT_MD5 , md5)
93+ it.setRequestProperty(CONTENT_TYPE , APPLICATION_ZIP )
94+ it.setRequestProperty(SERVER_SIDE_ENCRYPTION , AWS_KMS )
95+ if (kmsArn?.isNotEmpty() == true ) {
96+ it.setRequestProperty(SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID , kmsArn)
97+ }
98+ it.setRequestProperty(SERVER_SIDE_ENCRYPTION_CONTEXT , Base64 .getEncoder().encodeToString(uploadIdJson.toByteArray()))
99+ } else {
100+ requestHeaders.forEach { entry ->
101+ it.setRequestProperty(entry.key, entry.value)
102+ }
99103 }
104+ }.connect {
105+ val connection = it.connection as HttpURLConnection
106+ connection.setFixedLengthStreamingMode(fileToUpload.length())
107+ IoUtils .copy(fileToUpload.inputStream(), connection.outputStream)
108+ }
109+ },
110+ isRetryable = { e ->
111+ when (e) {
112+ is IOException -> true
113+ else -> false
114+ }
115+ },
116+ errorHandler = { e, attempts ->
117+ val errorMessage = getTelemetryErrorMessage(e, featureUseCase)
118+ when (featureUseCase) {
119+ CodeWhispererConstants .FeatureName .CODE_REVIEW ->
120+ codeScanServerException(" CreateUploadUrlException: $errorMessage " )
121+ CodeWhispererConstants .FeatureName .TEST_GENERATION ->
122+ throw CodeTestException (
123+ " UploadTestArtifactToS3Error: $errorMessage " ,
124+ " UploadTestArtifactToS3Error" ,
125+ message(" testgen.error.generic_technical_error_message" )
126+ )
127+ else -> throw RuntimeException (" $errorMessage (after $attempts attempts)" )
100128 }
101- }.connect {
102- val connection = it.connection as HttpURLConnection
103- connection.setFixedLengthStreamingMode(fileToUpload.length())
104- IoUtils .copy(fileToUpload.inputStream(), connection.outputStream)
105- }
106- } catch (e: Exception ) {
107- LOG .debug { " $featureUseCase : Artifact failed to upload in the S3 bucket: ${e.message} " }
108- val errorMessage = getTelemetryErrorMessage(e, featureUseCase)
109- when (featureUseCase) {
110- CodeWhispererConstants .FeatureName .CODE_REVIEW -> codeScanServerException(" CreateUploadUrlException: $errorMessage " )
111- CodeWhispererConstants .FeatureName .TEST_GENERATION -> throw CodeTestException (
112- " UploadTestArtifactToS3Error: $errorMessage " ,
113- " UploadTestArtifactToS3Error" ,
114- message(" testgen.error.generic_technical_error_message" )
115- )
116- else -> throw RuntimeException (errorMessage) // Adding else for safety check
117129 }
118- }
130+ )
119131 }
120132
121133 fun createUploadUrl (
@@ -124,35 +136,44 @@ class CodeWhispererZipUploadManager(private val project: Project) {
124136 uploadTaskType : CodeWhispererConstants .UploadTaskType ,
125137 taskName : String ,
126138 featureUseCase : CodeWhispererConstants .FeatureName ,
127- ): CreateUploadUrlResponse = try {
128- CodeWhispererClientAdaptor .getInstance(project).createUploadUrl(
129- CreateUploadUrlRequest .builder()
130- .contentMd5(md5Content)
131- .artifactType(artifactType)
132- .uploadIntent(getUploadIntent(uploadTaskType))
133- .uploadContext(
134- // For UTG we don't need uploadContext but sending else case as UploadContext
135- if (uploadTaskType == CodeWhispererConstants .UploadTaskType .CODE_FIX ) {
136- UploadContext .fromCodeFixUploadContext(CodeFixUploadContext .builder().codeFixName(taskName).build())
137- } else {
138- UploadContext .fromCodeAnalysisUploadContext(CodeAnalysisUploadContext .builder().codeScanName(taskName).build())
139- }
140- )
141- .build()
142- )
143- } catch (e: Exception ) {
144- LOG .debug { " $featureUseCase : Create Upload URL failed: ${e.message} " }
145- val errorMessage = getTelemetryErrorMessage(e, featureUseCase)
146- when (featureUseCase) {
147- CodeWhispererConstants .FeatureName .CODE_REVIEW -> codeScanServerException(" CreateUploadUrlException: $errorMessage " )
148- CodeWhispererConstants .FeatureName .TEST_GENERATION -> throw CodeTestException (
149- " CreateUploadUrlError: $errorMessage " ,
150- " CreateUploadUrlError" ,
151- message(" testgen.error.generic_technical_error_message" )
139+ ): CreateUploadUrlResponse = RetryableOperation <CreateUploadUrlResponse >().execute(
140+ operation = {
141+ CodeWhispererClientAdaptor .getInstance(project).createUploadUrl(
142+ CreateUploadUrlRequest .builder()
143+ .contentMd5(md5Content)
144+ .artifactType(artifactType)
145+ .uploadIntent(getUploadIntent(uploadTaskType))
146+ .uploadContext(
147+ // For UTG we don't need uploadContext but sending else case as UploadContext
148+ if (uploadTaskType == CodeWhispererConstants .UploadTaskType .CODE_FIX ) {
149+ UploadContext .fromCodeFixUploadContext(CodeFixUploadContext .builder().codeFixName(taskName).build())
150+ } else {
151+ UploadContext .fromCodeAnalysisUploadContext(CodeAnalysisUploadContext .builder().codeScanName(taskName).build())
152+ }
153+ )
154+ .build()
152155 )
153- else -> throw RuntimeException (errorMessage) // Adding else for safety check
156+ },
157+ isRetryable = { e ->
158+ e is ThrottlingException || e is InternalServerException
159+ },
160+ errorHandler = { e, attempts ->
161+ val errorMessage = getTelemetryErrorMessage(e, featureUseCase)
162+ when (featureUseCase) {
163+ CodeWhispererConstants .FeatureName .CODE_REVIEW ->
164+ codeScanServerException(" CreateUploadUrlException after $attempts attempts: $errorMessage " )
165+
166+ CodeWhispererConstants .FeatureName .TEST_GENERATION ->
167+ throw CodeTestException (
168+ " CreateUploadUrlError after $attempts attempts: $errorMessage " ,
169+ " CreateUploadUrlError" ,
170+ message(" testgen.error.generic_technical_error_message" )
171+ )
172+
173+ else -> throw RuntimeException (" $errorMessage (after $attempts attempts)" )
174+ }
154175 }
155- }
176+ )
156177
157178 private fun getUploadIntent (uploadTaskType : CodeWhispererConstants .UploadTaskType ): UploadIntent = when (uploadTaskType) {
158179 CodeWhispererConstants .UploadTaskType .SCAN_FILE -> UploadIntent .AUTOMATIC_FILE_SECURITY_SCAN
@@ -187,3 +208,41 @@ fun getTelemetryErrorMessage(e: Exception, featureUseCase: CodeWhispererConstant
187208 else -> message(" testgen.message.failed" )
188209 }
189210}
211+
212+ class RetryableOperation <T > {
213+ private var attempts = 0
214+ private var currentDelay = INITIAL_DELAY
215+ private var lastException: Exception ? = null
216+
217+ fun execute (
218+ operation : () -> T ,
219+ isRetryable : (Exception ) -> Boolean ,
220+ errorHandler : (Exception , Int ) -> Nothing ,
221+ ): T {
222+ while (attempts < MAX_RETRY_ATTEMPTS ) {
223+ try {
224+ return operation()
225+ } catch (e: Exception ) {
226+ lastException = e
227+
228+ attempts++
229+ if (attempts < MAX_RETRY_ATTEMPTS && isRetryable(e)) {
230+ Thread .sleep(currentDelay)
231+ currentDelay = (currentDelay * 2 ).coerceAtMost(MAX_BACKOFF )
232+ continue
233+ }
234+
235+ errorHandler(e, attempts)
236+ }
237+ }
238+
239+ // This line should never be reached due to errorHandler throwing exception
240+ throw RuntimeException (" Unexpected state after $attempts attempts" )
241+ }
242+
243+ companion object {
244+ private const val INITIAL_DELAY = 100L // milliseconds
245+ private const val MAX_BACKOFF = 10000L // milliseconds
246+ private const val MAX_RETRY_ATTEMPTS = 3
247+ }
248+ }
0 commit comments