@@ -8,6 +8,8 @@ import com.intellij.openapi.components.service
88import com.intellij.openapi.extensions.ExtensionPointName
99import com.intellij.openapi.progress.ProcessCanceledException
1010import com.intellij.openapi.project.Project
11+ import kotlinx.coroutines.delay
12+ import kotlinx.coroutines.runBlocking
1113import migration.software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
1214import software.amazon.awssdk.services.ssooidc.model.SsoOidcException
1315import software.aws.toolkits.core.ClientConnectionSettings
@@ -25,12 +27,16 @@ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAu
2527import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider
2628import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
2729import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.InteractiveBearerTokenProvider
30+ import software.aws.toolkits.jetbrains.utils.notifyInfo
2831import software.aws.toolkits.jetbrains.utils.runUnderProgressIfNeeded
2932import software.aws.toolkits.resources.AwsCoreBundle
33+ import software.aws.toolkits.resources.AwsCoreBundle.message
3034import software.aws.toolkits.telemetry.CredentialSourceId
3135import software.aws.toolkits.telemetry.CredentialType
3236import software.aws.toolkits.telemetry.Result
37+ import java.net.UnknownHostException
3338import java.time.Instant
39+ import kotlin.math.min
3440
3541sealed 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+
395446data class ConnectionMetadata (
396447 val sourceId : String? = null ,
397448)
0 commit comments