@@ -44,10 +44,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.codetest.invalidSo
4444@Service
4545class CodeWhispererZipUploadManager (private val project : Project ) {
4646
47- private val maxRetryAttempts = 3
48- private val initialDelay = 200L // 200ms
49- private val maxBackoff = 1000L // 1 second
50-
5147 fun createUploadUrlAndUpload (
5248 zipFile : File ,
5349 artifactType : String ,
@@ -90,11 +86,8 @@ class CodeWhispererZipUploadManager(private val project: Project) {
9086 requestHeaders : Map <String , String >? ,
9187 featureUseCase : CodeWhispererConstants .FeatureName ,
9288 ) {
93- var attempts = 0
94- var currentDelay = initialDelay
95-
96- while (attempts < maxRetryAttempts) {
97- try {
89+ RetryableOperation <Unit >().execute(
90+ operation = {
9891 val uploadIdJson = """ {"uploadId":"$uploadId "}"""
9992 HttpRequests .put(url, " application/zip" ).userAgent(AwsClientManager .getUserAgent()).tuner {
10093 if (requestHeaders.isNullOrEmpty()) {
@@ -115,26 +108,17 @@ class CodeWhispererZipUploadManager(private val project: Project) {
115108 connection.setFixedLengthStreamingMode(fileToUpload.length())
116109 IoUtils .copy(fileToUpload.inputStream(), connection.outputStream)
117110 }
118- return // Success, exit the function
119- } catch (e: Exception ) {
120- LOG .debug { " $featureUseCase : Artifact failed to upload in the S3 bucket (attempt ${attempts + 1 } ): ${e.message} " }
121-
122- // Determine if the exception is retryable
123- val isRetryable = when (e) {
124- is IOException , // Network issues
111+ },
112+ isRetryable = { e ->
113+ when (e) {
114+ is IOException ,
125115 is SocketException ,
126116 is SocketTimeoutException ,
127117 -> true
128118 else -> false
129119 }
130- attempts++
131- if (attempts < maxRetryAttempts && isRetryable) {
132- Thread .sleep(currentDelay)
133- currentDelay = (currentDelay * 2 ).coerceAtMost(maxBackoff)
134- continue
135- }
136-
137- // If not retryable or max attempts reached, handle the error
120+ },
121+ errorHandler = { e, attempts ->
138122 val errorMessage = getTelemetryErrorMessage(e, featureUseCase)
139123 when (featureUseCase) {
140124 CodeWhispererConstants .FeatureName .CODE_REVIEW ->
@@ -145,85 +129,56 @@ class CodeWhispererZipUploadManager(private val project: Project) {
145129 " UploadTestArtifactToS3Error" ,
146130 message(" testgen.error.generic_technical_error_message" )
147131 )
148- else -> throw RuntimeException (" $errorMessage (after $attempts attempts)" ) // Adding else for safety check
132+ else -> throw RuntimeException (" $errorMessage (after $attempts attempts)" )
149133 }
150134 }
151- }
135+ )
152136 }
137+
153138 fun createUploadUrl (
154139 md5Content : String ,
155140 artifactType : String ,
156141 uploadTaskType : CodeWhispererConstants .UploadTaskType ,
157142 taskName : String ,
158143 featureUseCase : CodeWhispererConstants .FeatureName ,
159- ): CreateUploadUrlResponse {
160- var attempts = 0
161- var currentDelay = initialDelay
162- var lastException: Exception ? = null
163-
164- while (attempts < maxRetryAttempts) {
165- try {
166- return CodeWhispererClientAdaptor .getInstance(project).createUploadUrl(
167- CreateUploadUrlRequest .builder()
168- .contentMd5(md5Content)
169- .artifactType(artifactType)
170- .uploadIntent(getUploadIntent(uploadTaskType))
171- .uploadContext(
172- // For UTG we don't need uploadContext but sending else case as UploadContext
173- if (uploadTaskType == CodeWhispererConstants .UploadTaskType .CODE_FIX ) {
174- UploadContext .fromCodeFixUploadContext(CodeFixUploadContext .builder().codeFixName(taskName).build())
175- } else {
176- UploadContext .fromCodeAnalysisUploadContext(CodeAnalysisUploadContext .builder().codeScanName(taskName).build())
177- }
178- )
179- .build()
180- )
181- } catch (e: Exception ) {
182- lastException = e
183- LOG .debug { " $featureUseCase : Create Upload URL failed (attempt ${attempts + 1 } ): ${e.message} " }
144+ ): CreateUploadUrlResponse = RetryableOperation <CreateUploadUrlResponse >().execute(
145+ operation = {
146+ CodeWhispererClientAdaptor .getInstance(project).createUploadUrl(
147+ CreateUploadUrlRequest .builder()
148+ .contentMd5(md5Content)
149+ .artifactType(artifactType)
150+ .uploadIntent(getUploadIntent(uploadTaskType))
151+ .uploadContext(
152+ // For UTG we don't need uploadContext but sending else case as UploadContext
153+ if (uploadTaskType == CodeWhispererConstants .UploadTaskType .CODE_FIX ) {
154+ UploadContext .fromCodeFixUploadContext(CodeFixUploadContext .builder().codeFixName(taskName).build())
155+ } else {
156+ UploadContext .fromCodeAnalysisUploadContext(CodeAnalysisUploadContext .builder().codeScanName(taskName).build())
157+ }
158+ )
159+ .build()
160+ )
161+ },
162+ isRetryable = { e ->
163+ e is ThrottlingException || e is InternalServerException
164+ },
165+ errorHandler = { e, attempts ->
166+ val errorMessage = getTelemetryErrorMessage(e, featureUseCase)
167+ when (featureUseCase) {
168+ CodeWhispererConstants .FeatureName .CODE_REVIEW ->
169+ codeScanServerException(" CreateUploadUrlException after $attempts attempts: $errorMessage " )
184170
185- // Don't retry these exceptions
186- if (e !is ThrottlingException && e !is InternalServerException ) {
187- val errorMessage = getTelemetryErrorMessage(e, featureUseCase)
188- when (featureUseCase) {
189- CodeWhispererConstants .FeatureName .CODE_REVIEW ->
190- codeScanServerException(" CreateUploadUrlException: $errorMessage " )
191- CodeWhispererConstants .FeatureName .TEST_GENERATION ->
192- throw CodeTestException (
193- " CreateUploadUrlError: $errorMessage " ,
194- " CreateUploadUrlError" ,
195- message(" testgen.error.generic_technical_error_message" )
196- )
197- else -> throw RuntimeException (errorMessage)
198- }
199- }
171+ CodeWhispererConstants .FeatureName .TEST_GENERATION ->
172+ throw CodeTestException (
173+ " CreateUploadUrlError after $attempts attempts: $errorMessage " ,
174+ " CreateUploadUrlError" ,
175+ message(" testgen.error.generic_technical_error_message" )
176+ )
200177
201- attempts++
202- if (attempts < maxRetryAttempts) {
203- Thread .sleep(currentDelay)
204- currentDelay = (currentDelay * 2 ).coerceAtMost(maxBackoff)
205- continue
206- }
178+ else -> throw RuntimeException (" $errorMessage (after $attempts attempts)" )
207179 }
208180 }
209-
210- // If we've exhausted all retries, handle the last exception
211- val errorMessage = getTelemetryErrorMessage(
212- lastException ? : Exception (" Unknown error" ),
213- featureUseCase
214- )
215- when (featureUseCase) {
216- CodeWhispererConstants .FeatureName .CODE_REVIEW ->
217- codeScanServerException(" CreateUploadUrlException after $maxRetryAttempts attempts: $errorMessage " )
218- CodeWhispererConstants .FeatureName .TEST_GENERATION ->
219- throw CodeTestException (
220- " CreateUploadUrlError after $maxRetryAttempts attempts: $errorMessage " ,
221- " CreateUploadUrlError" ,
222- message(" testgen.error.generic_technical_error_message" )
223- )
224- else -> throw RuntimeException (" $errorMessage (after $maxRetryAttempts attempts)" ) // Adding else for safety check
225- }
226- }
181+ )
227182
228183 private fun getUploadIntent (uploadTaskType : CodeWhispererConstants .UploadTaskType ): UploadIntent = when (uploadTaskType) {
229184 CodeWhispererConstants .UploadTaskType .SCAN_FILE -> UploadIntent .AUTOMATIC_FILE_SECURITY_SCAN
@@ -258,3 +213,41 @@ fun getTelemetryErrorMessage(e: Exception, featureUseCase: CodeWhispererConstant
258213 else -> message(" testgen.message.failed" )
259214 }
260215}
216+
217+ class RetryableOperation <T > {
218+ private var attempts = 0
219+ private var currentDelay = initialDelay
220+ private var lastException: Exception ? = null
221+
222+ fun execute (
223+ operation : () -> T ,
224+ isRetryable : (Exception ) -> Boolean ,
225+ errorHandler : (Exception , Int ) -> Nothing ,
226+ ): T {
227+ while (attempts < maxRetryAttempts) {
228+ try {
229+ return operation()
230+ } catch (e: Exception ) {
231+ lastException = e
232+
233+ attempts++
234+ if (attempts < maxRetryAttempts && isRetryable(e)) {
235+ Thread .sleep(currentDelay)
236+ currentDelay = (currentDelay * 2 ).coerceAtMost(maxBackoff)
237+ continue
238+ }
239+
240+ errorHandler(e, attempts)
241+ }
242+ }
243+
244+ // This line should never be reached due to errorHandler throwing exception
245+ throw RuntimeException (" Unexpected state after $attempts attempts" )
246+ }
247+
248+ companion object {
249+ private const val initialDelay = 100L // milliseconds
250+ private const val maxBackoff = 10000L // milliseconds
251+ private const val maxRetryAttempts = 3
252+ }
253+ }
0 commit comments