diff --git a/pushiko-fcm/src/main/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManager.kt b/pushiko-fcm/src/main/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManager.kt index de65291..c18ae40 100644 --- a/pushiko-fcm/src/main/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManager.kt +++ b/pushiko-fcm/src/main/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManager.kt @@ -86,7 +86,18 @@ internal class CredentialsRefreshManager( logger.info("Scheduling task to refresh access token, delayed {}s", it) } delay(interval) - if (iterator.next() != null) { + val token = try { + iterator.next() + } catch (e: IOException) { + if (e is Retryable && !e.isRetryable) { + logger.error( + "Non-retryable OAuth failure during keep-alive, token refresh has permanently stopped: {}", + e.message + ) + } + throw e + } + if (token != null) { backOff.reset() } keepAlive() diff --git a/pushiko-fcm/src/test/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManagerTest.kt b/pushiko-fcm/src/test/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManagerTest.kt index 2add6fb..73e2b33 100644 --- a/pushiko-fcm/src/test/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManagerTest.kt +++ b/pushiko-fcm/src/test/kotlin/com/bloomberg/pushiko/fcm/oauth/CredentialsRefreshManagerTest.kt @@ -32,6 +32,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertSame +import org.mockito.kotlin.doReturn import org.mockito.kotlin.doThrow internal class CredentialsRefreshManagerTest { @@ -107,6 +108,27 @@ internal class CredentialsRefreshManagerTest { } } + @Test + fun keepAliveStopsWhenRefreshFailureIsNonRetryable() { + val credentials = mock() + val exception = NonRetryableIOException("invalid_grant") + val backOff = mock() + var callCount = 0 + whenever(credentials.refresh()).thenAnswer { + if (callCount++ == 0) { + whenever(credentials.accessToken).thenReturn( + AccessToken(FAKE_TOKEN, Date(System.currentTimeMillis() + DEFAULT_EXPIRY_MILLIS)) + ) + } else { + throw exception + } + } + whenever(backOff.nextBackOffMillis()) doReturn 0L + assertFailsWith { + manager = CredentialsRefreshManager(credentials, Dispatchers.Unconfined, backOff) + } + } + private companion object { private const val DEFAULT_EXPIRY_MILLIS = 10_000L * 1_000L private const val FAKE_TOKEN = "abc123"