From 513734185df3b11252862a52a0955690a7fbc4b5 Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Mon, 19 Apr 2021 08:59:58 +0300 Subject: [PATCH] Feature - add method to update ttl and idleTime of entry by key. #3423 --- .../java/org/redisson/RedissonMapCache.java | 76 +++++++++++++++++++ .../main/java/org/redisson/api/RMapCache.java | 25 +++++- .../java/org/redisson/api/RMapCacheAsync.java | 23 ++++++ .../org/redisson/RedissonMapCacheTest.java | 16 +++- 4 files changed, 136 insertions(+), 4 deletions(-) diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index e6c5d2fb0..7765f70cf 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -1030,6 +1030,82 @@ public class RedissonMapCache extends RedissonMap implements RMapCac return future; } + @Override + public boolean updateEntryExpiration(K key, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { + return get(updateEntryExpirationAsync(key, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); + } + + @Override + public RFuture updateEntryExpirationAsync(K key, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { + checkKey(key); + + long currentTime = System.currentTimeMillis(); + long ttlTimeout = 0; + if (ttl > 0) { + ttlTimeout = currentTime + ttlUnit.toMillis(ttl); + } + + long maxIdleTimeout = 0; + if (maxIdleTime > 0) { + long maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime); + maxIdleTimeout = currentTime + maxIdleDelta; + } + + String name = getRawName(key); + RFuture future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN, + "local value = redis.call('hget', KEYS[1], ARGV[4]); " + + "local t, val;" + + "if value == false then " + + "return 0; " + + "else " + + "t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[4]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[4]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0; " + + "end; " + + "end; " + + + "if tonumber(ARGV[2]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[4]); " + + "end; " + + "if tonumber(ARGV[3]) > 0 then " + + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[4]); " + + "else " + + "redis.call('zrem', KEYS[3], ARGV[4]); " + + "end; " + + + // last access time + "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + + "local mode = redis.call('hget', KEYS[5], 'mode'); " + + "if maxSize ~= nil and maxSize ~= 0 then " + + "local currentTime = tonumber(ARGV[1]); " + + + "if mode == false or mode == 'LRU' then " + + "redis.call('zadd', KEYS[4], currentTime, ARGV[4]); " + + "end; " + + "if mode == 'LFU' then " + + "redis.call('zincrby', KEYS[4], 1, ARGV[4]); " + + "end; " + + "end; " + + "return 1;", + Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), + getLastAccessTimeSetName(name), getOptionsName(name)), + System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, encodeMapKey(key)); + return future; + } + @Override public RFuture putAsync(K key, V value, long ttl, TimeUnit ttlUnit) { return putAsync(key, value, ttl, ttlUnit, 0, null); diff --git a/redisson/src/main/java/org/redisson/api/RMapCache.java b/redisson/src/main/java/org/redisson/api/RMapCache.java index b48697570..b6f55d1f6 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCache.java +++ b/redisson/src/main/java/org/redisson/api/RMapCache.java @@ -265,7 +265,30 @@ public interface RMapCache extends RMap, RMapCacheAsync, RDest * @param ttlUnit - time unit */ void putAll(java.util.Map map, long ttl, TimeUnit ttlUnit); - + + /** + * Updates time to live and max idle time of specified entry by key. + * Entry expires when specified time to live or max idle time was reached. + *

+ * Returns false if entry already expired or doesn't exist, + * otherwise returns true. + * + * @param key - map key + * @param ttl - time to live for key\value entry. + * If 0 then time to live doesn't affect entry expiration. + * @param ttlUnit - time unit + * @param maxIdleTime - max idle time for key\value entry. + * If 0 then max idle time doesn't affect entry expiration. + * @param maxIdleUnit - time unit + *

+ * if maxIdleTime and ttl params are equal to 0 + * then entry stores infinitely. + * + * @return returns false if entry already expired or doesn't exist, + * otherwise returns true. + */ + boolean updateEntryExpiration(K key, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); + /** * Returns the number of entries in cache. * This number can reflects expired entries too diff --git a/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java b/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java index aceba4fba..efdfc3082 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java +++ b/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java @@ -249,6 +249,29 @@ public interface RMapCacheAsync extends RMapAsync { */ RFuture fastPutIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); + /** + * Updates time to live and max idle time of specified entry by key. + * Entry expires when specified time to live or max idle time was reached. + *

+ * Returns false if entry already expired or doesn't exist, + * otherwise returns true. + * + * @param key - map key + * @param ttl - time to live for key\value entry. + * If 0 then time to live doesn't affect entry expiration. + * @param ttlUnit - time unit + * @param maxIdleTime - max idle time for key\value entry. + * If 0 then max idle time doesn't affect entry expiration. + * @param maxIdleUnit - time unit + *

+ * if maxIdleTime and ttl params are equal to 0 + * then entry stores infinitely. + * + * @return returns false if entry already expired or doesn't exist, + * otherwise returns true. + */ + RFuture updateEntryExpirationAsync(K key, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); + /** * Returns the number of entries in cache. * This number can reflects expired entries too diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java index 0008ec041..f0016cc51 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -40,11 +40,21 @@ import org.redisson.eviction.EvictionScheduler; public class RedissonMapCacheTest extends BaseMapTest { + @Test + public void testUpdateEntryExpiration() throws InterruptedException { + RMapCache cache = redisson.getMapCache("testUpdateEntryExpiration"); + cache.put(1, 2, 3, TimeUnit.SECONDS); + Thread.sleep(2000); + long ttl = cache.remainTimeToLive(1); + assertThat(ttl).isBetween(900L, 1000L); + cache.updateEntryExpiration(1, 2, TimeUnit.SECONDS, -1, TimeUnit.SECONDS); + long ttl2 = cache.remainTimeToLive(1); + assertThat(ttl2).isBetween(1900L, 2000L); + } + @Test public void testRemoveListener() { - RMapCache rMapCache = redisson.getMapCache("test", - LocalCachedMapOptions.defaults().evictionPolicy(LocalCachedMapOptions.EvictionPolicy.LRU) - .timeToLive(-1)); + RMapCache rMapCache = redisson.getMapCache("test"); rMapCache.trySetMaxSize(5); AtomicBoolean removed = new AtomicBoolean(); rMapCache.addListener(new EntryRemovedListener() {