Skip to content

Commit 17ae6e5

Browse files
committed
add retry with backoff logic to auth manager
1 parent 13e5d8d commit 17ae6e5

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ToolkitAuthManager.kt

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import com.intellij.openapi.components.service
88
import com.intellij.openapi.extensions.ExtensionPointName
99
import com.intellij.openapi.progress.ProcessCanceledException
1010
import com.intellij.openapi.project.Project
11+
import kotlinx.coroutines.delay
12+
import kotlinx.coroutines.runBlocking
1113
import migration.software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
1214
import software.amazon.awssdk.services.ssooidc.model.SsoOidcException
1315
import software.aws.toolkits.core.ClientConnectionSettings
@@ -25,12 +27,16 @@ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAu
2527
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider
2628
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
2729
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider
30+
import software.aws.toolkits.jetbrains.utils.notifyInfo
2831
import software.aws.toolkits.jetbrains.utils.runUnderProgressIfNeeded
2932
import software.aws.toolkits.resources.AwsCoreBundle
33+
import software.aws.toolkits.resources.AwsCoreBundle.message
3034
import software.aws.toolkits.telemetry.CredentialSourceId
3135
import software.aws.toolkits.telemetry.CredentialType
3236
import software.aws.toolkits.telemetry.Result
37+
import java.net.UnknownHostException
3338
import java.time.Instant
39+
import kotlin.math.min
3440

3541
sealed interface ToolkitConnection {
3642
val id: String
@@ -305,7 +311,7 @@ fun maybeReauthProviderIfNeeded(
305311
onReauthRequired: (SsoOidcException?) -> Any,
306312
): Boolean {
307313
val state = tokenProvider.state()
308-
when (state) {
314+
return when (state) {
309315
BearerTokenAuthState.NOT_AUTHENTICATED -> {
310316
getLogger<ToolkitAuthManager>().info { "Token provider NOT_AUTHENTICATED, requesting login" }
311317
onReauthRequired(null)
@@ -314,15 +320,36 @@ fun maybeReauthProviderIfNeeded(
314320

315321
BearerTokenAuthState.NEEDS_REFRESH -> {
316322
try {
317-
return runUnderProgressIfNeeded(project, AwsCoreBundle.message("credentials.refreshing"), true) {
318-
tokenProvider.resolveToken()
319-
BearerTokenProviderListener.notifyCredUpdate(tokenProvider.id)
320-
return@runUnderProgressIfNeeded false
323+
retryWithBackoff {
324+
return@retryWithBackoff runUnderProgressIfNeeded(project, AwsCoreBundle.message("credentials.refreshing"), true) {
325+
tokenProvider.refresh()
326+
hasNotifiedNetworkErrorOnce = false
327+
BearerTokenProviderListener.notifyCredUpdate(tokenProvider.id)
328+
return@runUnderProgressIfNeeded false
329+
}
330+
}
331+
} catch (e: Exception) {
332+
when {
333+
e is SsoOidcException -> {
334+
getLogger<ToolkitAuthManager>().warn(e) { "Redriving bearer token login flow since token could not be refreshed" }
335+
onReauthRequired(e)
336+
return true
337+
}
338+
339+
e is UnknownHostException || e.message?.contains("Unable to execute HTTP request") == true -> {
340+
getLogger<ToolkitAuthManager>().error("Failed to refresh token", e)
341+
if (!hasNotifiedNetworkErrorOnce) {
342+
hasNotifiedNetworkErrorOnce = true
343+
notifyInfo(
344+
message("general.auth.network.error"),
345+
message("general.auth.network.error.message"),
346+
project
347+
)
348+
}
349+
return false
350+
}
351+
else -> {return false}
321352
}
322-
} catch (e: SsoOidcException) {
323-
getLogger<ToolkitAuthManager>().warn(e) { "Redriving bearer token login flow since token could not be refreshed" }
324-
onReauthRequired(e)
325-
return true
326353
}
327354
}
328355

@@ -392,6 +419,30 @@ private fun recordAddConnection(
392419
}
393420
}
394421

422+
private fun <T> retryWithBackoff(
423+
maxAttempts: Int = 3,
424+
initialDelayMs: Long = 1000,
425+
maxDelayMs: Long = 10000,
426+
factor: Double = 2.0,
427+
block: () -> T
428+
): T {
429+
var currentDelay = initialDelayMs
430+
repeat(maxAttempts) { attempt ->
431+
try {
432+
return block()
433+
} catch (e: Exception) {
434+
if (attempt == maxAttempts - 1 || e is SsoOidcException) throw e
435+
println("Attempt ${attempt + 1} failed. Retrying in $currentDelay ms")
436+
runBlocking { delay(currentDelay) }
437+
438+
currentDelay = min(maxDelayMs, (currentDelay * factor).toLong())
439+
}
440+
}
441+
throw IllegalStateException("This line should never be reached")
442+
}
443+
444+
private var hasNotifiedNetworkErrorOnce = false
445+
395446
data class ConnectionMetadata(
396447
val sourceId: String? = null,
397448
)

plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,8 @@ gateway.connection.workflow.step_skipped=Step skipped
12361236
gateway.connection.workflow.step_successful=\nStep completed successfully\n
12371237
general.add.another=Add another
12381238
general.auth.reauthenticate=Reauthenticate
1239+
general.auth.network.error=Network Error
1240+
general.auth.network.error.message=Failed to update connection due to networking issues
12391241
general.cancel=Cancel
12401242
general.close_button=Close
12411243
general.configure_button=Configure

0 commit comments

Comments
 (0)