diff --git a/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCache.java b/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCache.java
index da2f84bf0c4..182120de9c1 100644
--- a/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCache.java
+++ b/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCache.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,14 @@
/**
* Generic cache with eviction and max size.
*
+ * Cache timeouts:
+ *
+ * - {@link io.helidon.security.providers.common.EvictableCache.Builder#overallTimeout(long, java.util.concurrent.TimeUnit)}
+ * defines the timeout of record since its creation
+ * - {@link io.helidon.security.providers.common.EvictableCache.Builder#timeout(long, java.util.concurrent.TimeUnit)}
+ * defines the timeout of record since last use (a sliding timeout)
+ *
+ *
* @param type of keys in this cache
* @param type of values in this cache
*/
@@ -165,8 +173,10 @@ default void close() {
class Builder implements io.helidon.common.Builder> {
private boolean cacheEnabled = true;
private long cacheTimeout = CACHE_TIMEOUT_MINUTES;
- private long cacheMaxSize = CACHE_MAX_SIZE;
private TimeUnit cacheTimeoutUnit = TimeUnit.MINUTES;
+ private long overallTimeout = CACHE_TIMEOUT_MINUTES;
+ private TimeUnit overallTimeoutUnit = TimeUnit.MINUTES;
+ private long cacheMaxSize = CACHE_MAX_SIZE;
private long cacheEvictDelay = CACHE_EVICT_DELAY_MINUTES;
private long cacheEvictPeriod = CACHE_EVICT_PERIOD_MINUTES;
private TimeUnit cacheEvictTimeUnit = TimeUnit.MINUTES;
@@ -188,7 +198,7 @@ public EvictableCache build() {
}
/**
- * Configure record timeout since last modification.
+ * Configure record timeout since last access.
*
* @param timeout timeout value
* @param timeoutUnit timeout unit
@@ -200,6 +210,19 @@ public Builder timeout(long timeout, TimeUnit timeoutUnit) {
return this;
}
+ /**
+ * Configure record timeout since its creation.
+ *
+ * @param timeout timeout value
+ * @param timeoutUnit timeout unit
+ * @return updated builder instance
+ */
+ public Builder overallTimeout(long timeout, TimeUnit timeoutUnit) {
+ this.overallTimeout = timeout;
+ this.overallTimeoutUnit = timeoutUnit;
+ return this;
+ }
+
/**
* Configure maximal cache size.
*
@@ -290,6 +313,8 @@ public Builder config(Config config) {
config.get("cache-enabled").asBoolean().ifPresent(this::cacheEnabled);
if (cacheEnabled) {
config.get("cache-timeout-millis").asLong().ifPresent(timeout -> timeout(timeout, TimeUnit.MILLISECONDS));
+ config.get("cache-overall-timeout-millis").asLong()
+ .ifPresent(timeout -> overallTimeout(timeout, TimeUnit.MILLISECONDS));
long evictDelay = config.get("cache-evict-delay-millis").asLong()
.orElse(cacheEvictTimeUnit.toMillis(cacheEvictDelay));
long evictPeriod = config.get("cache-evict-period-millis").asLong()
@@ -319,14 +344,22 @@ long cacheTimeout() {
return cacheTimeout;
}
- long cacheMaxSize() {
- return cacheMaxSize;
- }
-
TimeUnit cacheTimeoutUnit() {
return cacheTimeoutUnit;
}
+ long overallTimeout() {
+ return overallTimeout;
+ }
+
+ TimeUnit overallTimeoutUnit() {
+ return overallTimeoutUnit;
+ }
+
+ long cacheMaxSize() {
+ return cacheMaxSize;
+ }
+
long cacheEvictDelay() {
return cacheEvictDelay;
}
diff --git a/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCacheImpl.java b/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCacheImpl.java
index ead8b34c9ff..435d0735028 100644
--- a/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCacheImpl.java
+++ b/security/providers/common/src/main/java/io/helidon/security/providers/common/EvictableCacheImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,7 +54,8 @@ public Thread newThread(Runnable r) {
}
private final ConcurrentHashMap> cacheMap = new ConcurrentHashMap<>();
- private final long cacheTimoutNanos;
+ private final long cacheTimeoutNanos;
+ private final long overallTimeoutNanos;
private final long cacheMaxSize;
private final long evictParallelismThreshold;
private final ScheduledFuture> evictionFuture;
@@ -62,7 +63,8 @@ public Thread newThread(Runnable r) {
EvictableCacheImpl(Builder builder) {
cacheMaxSize = builder.cacheMaxSize();
- cacheTimoutNanos = TimeUnit.NANOSECONDS.convert(builder.cacheTimeout(), builder.cacheTimeoutUnit());
+ cacheTimeoutNanos = TimeUnit.NANOSECONDS.convert(builder.cacheTimeout(), builder.cacheTimeoutUnit());
+ overallTimeoutNanos = TimeUnit.NANOSECONDS.convert(builder.overallTimeout(), builder.overallTimeoutUnit());
evictParallelismThreshold = builder.parallelismThreshold();
evictor = builder.evictor();
@@ -114,7 +116,7 @@ void evict() {
if ((null == cacheRecord) || evictor.apply(cacheRecord.getKey(), cacheRecord.getValue())) {
return null;
} else {
- if (cacheRecord.isValid(cacheTimoutNanos)) {
+ if (cacheRecord.isValid(cacheTimeoutNanos, overallTimeoutNanos)) {
return cacheRecord;
} else {
return null;
@@ -124,7 +126,7 @@ void evict() {
}
private Optional> validate(CacheRecord record) {
- if (record.isValid(cacheTimoutNanos) && !evictor.apply(record.getKey(), record.getValue())) {
+ if (record.isValid(cacheTimeoutNanos, overallTimeoutNanos) && !evictor.apply(record.getKey(), record.getValue())) {
return Optional.of(record);
}
cacheMap.remove(record.key);
@@ -133,7 +135,7 @@ private Optional> validate(CacheRecord record) {
private Optional doComputeValue(K key, Supplier> valueSupplier) {
CacheRecord record = cacheMap.compute(key, (s, cacheRecord) -> {
- if ((null != cacheRecord) && cacheRecord.isValid(cacheTimoutNanos)) {
+ if ((null != cacheRecord) && cacheRecord.isValid(cacheTimeoutNanos, overallTimeoutNanos)) {
cacheRecord.accessed();
return cacheRecord;
}
@@ -161,6 +163,7 @@ private Optional> getRecord(K key) {
private static final class CacheRecord {
private final K key;
private final V value;
+ private final long created = System.nanoTime();
private volatile long lastAccess = System.nanoTime();
private CacheRecord(K key, V value) {
@@ -172,8 +175,10 @@ private void accessed() {
lastAccess = System.nanoTime();
}
- private boolean isValid(long timeoutNanos) {
- return (System.nanoTime() - lastAccess) < timeoutNanos;
+ private boolean isValid(long timeoutNanos, long overallTimeout) {
+ long nano = System.nanoTime();
+
+ return ((nano - created) < overallTimeout) && ((nano - lastAccess) < timeoutNanos);
}
private K getKey() {
diff --git a/security/providers/common/src/test/java/io/helidon/security/providers/common/EvictableCacheTest.java b/security/providers/common/src/test/java/io/helidon/security/providers/common/EvictableCacheTest.java
index 6774d191125..2c4f823a5fb 100644
--- a/security/providers/common/src/test/java/io/helidon/security/providers/common/EvictableCacheTest.java
+++ b/security/providers/common/src/test/java/io/helidon/security/providers/common/EvictableCacheTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,6 +63,21 @@ void testEviction() throws InterruptedException {
cache.close();
}
+ @Test
+ void testOverallTimeout() throws InterruptedException {
+ EvictableCache cache = EvictableCache.builder()
+ .timeout(10, TimeUnit.MINUTES)
+ .overallTimeout(50, TimeUnit.MILLISECONDS)
+ .build();
+
+ assertThat(cache.computeValue("one", () -> Optional.of("1")), is(Optional.of("1")));
+ assertThat(cache.get("one"), is(Optional.of("1")));
+ TimeUnit.MILLISECONDS.sleep(200);
+ assertThat(cache.get("one"), is(EMPTY));
+
+ cache.close();
+ }
+
@Test
void testEvictor() {
EvictableCache cache = EvictableCache.builder()