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: + * + * * @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()