Skip to content

Commit 007db49

Browse files
committed
Replace cacheInvalidError marker interface with predicate.
1 parent 1927e30 commit 007db49

4 files changed

Lines changed: 46 additions & 155 deletions

File tree

core/sdk-core/src/main/java/software/amazon/awssdk/core/exception/CacheInvalidatingException.java

Lines changed: 0 additions & 110 deletions
This file was deleted.

utils/src/main/java/software/amazon/awssdk/utils/cache/CacheInvalidatingError.java

Lines changed: 0 additions & 31 deletions
This file was deleted.

utils/src/main/java/software/amazon/awssdk/utils/cache/CachedSupplier.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.concurrent.atomic.AtomicBoolean;
2626
import java.util.concurrent.locks.Lock;
2727
import java.util.concurrent.locks.ReentrantLock;
28+
import java.util.function.Predicate;
2829
import java.util.function.Supplier;
2930
import software.amazon.awssdk.annotations.SdkProtectedApi;
3031
import software.amazon.awssdk.annotations.SdkTestInternalApi;
@@ -54,14 +55,14 @@ public class CachedSupplier<T> implements Supplier<T>, SdkAutoCloseable {
5455
private static final Duration BLOCKING_REFRESH_MAX_WAIT = Duration.ofSeconds(5);
5556

5657
/**
57-
* Minimum backoff duration in seconds when a refresh fails (inclusive).
58+
* Minimum backoff duration when a refresh fails (inclusive).
5859
*/
59-
private static final int STATIC_STABILITY_BACKOFF_MIN_SECONDS = 300;
60+
private static final Duration STATIC_STABILITY_BACKOFF_MIN = Duration.ofMinutes(5);
6061

6162
/**
62-
* Maximum backoff duration in seconds when a refresh fails (inclusive).
63+
* Maximum backoff duration when a refresh fails (inclusive).
6364
*/
64-
private static final int STATIC_STABILITY_BACKOFF_MAX_SECONDS = 600;
65+
private static final Duration STATIC_STABILITY_BACKOFF_MAX = Duration.ofMinutes(10);
6566

6667

6768
/**
@@ -112,6 +113,12 @@ public class CachedSupplier<T> implements Supplier<T>, SdkAutoCloseable {
112113
*/
113114
private final Random jitterRandom = new Random();
114115

116+
/**
117+
* Predicate that determines whether an exception represents a non-recoverable refresh failure
118+
* that should bypass static stability (i.e., be re-thrown immediately without extending expiration).
119+
*/
120+
private final Predicate<RuntimeException> cacheInvalidatingPredicate;
121+
115122
private CachedSupplier(Builder<T> builder) {
116123
Validate.notNull(builder.supplier, "builder.supplier");
117124
Validate.notNull(builder.jitterEnabled, "builder.jitterEnabled");
@@ -121,6 +128,7 @@ private CachedSupplier(Builder<T> builder) {
121128
this.staleValueBehavior = Validate.notNull(builder.staleValueBehavior, "builder.staleValueBehavior");
122129
this.clock = Validate.notNull(builder.clock, "builder.clock");
123130
this.cachedValueName = Validate.notNull(builder.cachedValueName, "builder.cachedValueName");
131+
this.cacheInvalidatingPredicate = builder.cacheInvalidatingPredicate;
124132
}
125133

126134
/**
@@ -276,14 +284,15 @@ private RefreshResult<T> handleFetchFailure(RuntimeException e) {
276284
throw e;
277285
case ALLOW:
278286
// Cache-invalidating errors bypass static stability
279-
if (e instanceof CacheInvalidatingError) {
287+
if (cacheInvalidatingPredicate != null && cacheInvalidatingPredicate.test(e)) {
280288
throw e;
281289
}
282290

283291
// Uniform random backoff: 5-10 minutes
284-
long backoffSeconds = STATIC_STABILITY_BACKOFF_MIN_SECONDS
292+
long backoffSeconds = STATIC_STABILITY_BACKOFF_MIN.getSeconds()
285293
+ jitterRandom.nextInt(
286-
STATIC_STABILITY_BACKOFF_MAX_SECONDS - STATIC_STABILITY_BACKOFF_MIN_SECONDS + 1);
294+
(int) (STATIC_STABILITY_BACKOFF_MAX.getSeconds()
295+
- STATIC_STABILITY_BACKOFF_MIN.getSeconds() + 1));
287296
Instant extendedStaleTime = now.plusSeconds(backoffSeconds);
288297

289298
log.warn(() -> "(" + cachedValueName + ") Credential refresh failed: " + e.getMessage()
@@ -301,13 +310,14 @@ private RefreshResult<T> handleFetchFailure(RuntimeException e) {
301310

302311
// Not yet stale — we're in the prefetch window. Handle failure based on mode.
303312
if (staleValueBehavior == StaleValueBehavior.ALLOW) {
304-
if (e instanceof CacheInvalidatingError) {
313+
if (cacheInvalidatingPredicate != null && cacheInvalidatingPredicate.test(e)) {
305314
throw e;
306315
}
307316
// During prefetch window failure: extend prefetchTime to suppress further attempts
308-
long backoffSeconds = STATIC_STABILITY_BACKOFF_MIN_SECONDS
317+
long backoffSeconds = STATIC_STABILITY_BACKOFF_MIN.getSeconds()
309318
+ jitterRandom.nextInt(
310-
STATIC_STABILITY_BACKOFF_MAX_SECONDS - STATIC_STABILITY_BACKOFF_MIN_SECONDS + 1);
319+
(int) (STATIC_STABILITY_BACKOFF_MAX.getSeconds()
320+
- STATIC_STABILITY_BACKOFF_MIN.getSeconds() + 1));
311321
Instant extendedPrefetchTime = now.plusSeconds(backoffSeconds);
312322

313323
log.warn(() -> "(" + cachedValueName + ") Credential refresh failed: " + e.getMessage()
@@ -406,6 +416,7 @@ public static final class Builder<T> {
406416
private StaleValueBehavior staleValueBehavior = StaleValueBehavior.STRICT;
407417
private Clock clock = Clock.systemUTC();
408418
private String cachedValueName = "unknown";
419+
private Predicate<RuntimeException> cacheInvalidatingPredicate;
409420

410421
private Builder(Supplier<RefreshResult<T>> supplier) {
411422
this.supplier = supplier;
@@ -445,6 +456,23 @@ public Builder<T> cachedValueName(String cachedValueName) {
445456
return this;
446457
}
447458

459+
/**
460+
* Configure a predicate that determines whether an exception represents a non-recoverable refresh failure
461+
* that should bypass static stability. When the predicate returns {@code true} for a given exception,
462+
* the exception will be re-thrown immediately without extending the cached value's expiration.
463+
*
464+
* <p>This is used for errors where the credential source has definitively indicated that the current
465+
* authentication state is invalid and requires user intervention (e.g., expired SSO tokens,
466+
* changed user credentials).</p>
467+
*
468+
* <p>By default, no exceptions are considered cache-invalidating (all failures trigger static stability
469+
* backoff when {@link StaleValueBehavior#ALLOW} is configured).</p>
470+
*/
471+
public Builder<T> cacheInvalidatingPredicate(Predicate<RuntimeException> cacheInvalidatingPredicate) {
472+
this.cacheInvalidatingPredicate = cacheInvalidatingPredicate;
473+
return this;
474+
}
475+
448476
/**
449477
* Configure the clock used for this cached supplier. Configurable for testing.
450478
*/
@@ -523,8 +551,8 @@ public enum StaleValueBehavior {
523551
* Allow stale values to be returned from the cache with static stability semantics. On refresh failure,
524552
* extends the stale time by a uniformly random backoff between 5 and 10 minutes (300-600 seconds).
525553
*
526-
* <p>If the failure is a {@link CacheInvalidatingError}, the exception is re-thrown immediately
527-
* without extending the stale time.</p>
554+
* <p>If a {@link Builder#cacheInvalidatingPredicate(Predicate)} is configured and returns {@code true}
555+
* for the exception, it is re-thrown immediately without extending the stale time.</p>
528556
*
529557
* <p>Value retrieval will never fail as long as the cache has succeeded at least once,
530558
* unless the error is cache-invalidating.</p>

utils/src/test/java/software/amazon/awssdk/utils/cache/CachedSupplierTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,8 @@ public void allowMode_cacheInvalidatingError_isRethrown() throws InterruptedExce
397397
MutableSupplier supplier = new MutableSupplier();
398398
try (CachedSupplier<String> cachedSupplier = CachedSupplier.builder(supplier)
399399
.staleValueBehavior(ALLOW)
400+
.cacheInvalidatingPredicate(
401+
e -> e instanceof CacheInvalidatingRuntimeException)
400402
.clock(clock)
401403
.jitterEnabled(false)
402404
.build()) {
@@ -513,6 +515,8 @@ public void allowMode_prefetchWindowFailure_cacheInvalidatingError_isRethrown()
513515
MutableSupplier supplier = new MutableSupplier();
514516
try (CachedSupplier<String> cachedSupplier = CachedSupplier.builder(supplier)
515517
.staleValueBehavior(ALLOW)
518+
.cacheInvalidatingPredicate(
519+
e -> e instanceof CacheInvalidatingRuntimeException)
516520
.clock(clock)
517521
.jitterEnabled(false)
518522
.build()) {
@@ -538,9 +542,9 @@ public void allowMode_prefetchWindowFailure_cacheInvalidatingError_isRethrown()
538542
}
539543

540544
/**
541-
* A RuntimeException that implements CacheInvalidatingError for testing.
545+
* A RuntimeException that represents a cache-invalidating error for testing.
542546
*/
543-
private static class CacheInvalidatingRuntimeException extends RuntimeException implements CacheInvalidatingError {
547+
private static class CacheInvalidatingRuntimeException extends RuntimeException {
544548
CacheInvalidatingRuntimeException(String message) {
545549
super(message);
546550
}

0 commit comments

Comments
 (0)