Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redisson.jcache.JCache Maven / Gradle / Ivy
Go to download
Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC
/**
* Copyright (c) 2013-2024 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.jcache;
import io.netty.buffer.ByteBuf;
import org.redisson.*;
import org.redisson.api.*;
import org.redisson.api.listener.MessageListener;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.MapValueDecoder;
import org.redisson.codec.BaseEventCodec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.connection.decoder.MapGetAllDecoder;
import org.redisson.iterator.RedissonBaseMapIterator;
import org.redisson.jcache.JMutableEntry.Action;
import org.redisson.jcache.configuration.JCacheConfiguration;
import org.redisson.misc.CompletableFutureWrapper;
import org.redisson.misc.Hash;
import org.redisson.reactive.ReactiveProxyBuilder;
import org.redisson.rx.RxProxyBuilder;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Configuration;
import javax.cache.configuration.Factory;
import javax.cache.event.*;
import javax.cache.integration.*;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.cache.processor.EntryProcessorResult;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* JCache implementation
*
* @author Nikita Koksharov
*
* @param key
* @param value
*/
public class JCache extends RedissonObject implements Cache, CacheAsync {
final boolean atomicExecution = System.getProperty("org.jsr107.tck.management.agentId") == null;
final JCacheManager cacheManager;
final JCacheConfiguration config;
private final ConcurrentMap, Map> listeners = new ConcurrentHashMap<>();
final Redisson redisson;
private CacheLoader cacheLoader;
private CacheWriter cacheWriter;
private AtomicBoolean closed = new AtomicBoolean();
private boolean hasOwnRedisson;
/*
* No locking required in atomic execution mode.
*/
private static final RLock DUMMY_LOCK = (RLock) Proxy.newProxyInstance(JCache.class.getClassLoader(), new Class[] {RLock.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
public JCache(JCacheManager cacheManager, Redisson redisson, String name, JCacheConfiguration config, boolean hasOwnRedisson) {
super(redisson.getConfig().getCodec(), redisson.getCommandExecutor(), name);
this.hasOwnRedisson = hasOwnRedisson;
this.redisson = redisson;
Factory> cacheLoaderFactory = config.getCacheLoaderFactory();
if (cacheLoaderFactory != null) {
cacheLoader = cacheLoaderFactory.create();
}
Factory> cacheWriterFactory = config.getCacheWriterFactory();
if (config.getCacheWriterFactory() != null) {
cacheWriter = (CacheWriter) cacheWriterFactory.create();
}
this.cacheManager = cacheManager;
this.config = config;
redisson.getEvictionScheduler().scheduleJCache(getRawName(), getTimeoutSetName(), getExpiredChannelName());
for (CacheEntryListenerConfiguration listenerConfig : config.getCacheEntryListenerConfigurations()) {
registerCacheEntryListener(listenerConfig, false);
}
}
void checkNotClosed() {
if (closed.get()) {
throw new IllegalStateException();
}
}
String getTimeoutSetName() {
return "jcache_timeout_set:{" + getRawName() + "}";
}
String getTimeoutSetName(String name) {
return prefixName("jcache_timeout_set", name);
}
String getSyncName(Object syncId) {
return "jcache_sync:" + syncId + ":{" + getRawName() + "}";
}
String getCreatedSyncChannelName() {
return "jcache_created_sync_channel:{" + getRawName() + "}";
}
String getCreatedSyncChannelName(String name) {
return prefixName("jcache_created_sync_channel", name);
}
String getUpdatedSyncChannelName() {
return "jcache_updated_sync_channel:{" + getRawName() + "}";
}
String getUpdatedSyncChannelName(String name) {
return prefixName("jcache_updated_sync_channel", name);
}
String getRemovedSyncChannelName() {
return "jcache_removed_sync_channel:{" + getRawName() + "}";
}
String getRemovedSyncChannelName(String name) {
return prefixName("jcache_removed_sync_channel", name);
}
String getCreatedChannelName() {
return "jcache_created_channel:{" + getRawName() + "}";
}
String getCreatedChannelName(String name) {
return prefixName("jcache_created_channel", name);
}
String getUpdatedChannelName() {
return "jcache_updated_channel:{" + getRawName() + "}";
}
String getUpdatedChannelName(String name) {
return prefixName("jcache_updated_channel", name);
}
String getExpiredChannelName() {
return "jcache_expired_channel:{" + getRawName() + "}";
}
String getRemovedChannelName(String name) {
return prefixName("jcache_removed_channel", name);
}
String getRemovedChannelName() {
return "jcache_removed_channel:{" + getRawName() + "}";
}
String getOldValueListenerCounter() {
return "jcache_old_value_listeners:{" + getRawName() + "}";
}
String getOldValueListenerCounter(String name) {
return prefixName("jcache_old_value_listeners", name);
}
long currentNanoTime() {
if (config.isStatisticsEnabled()) {
return System.nanoTime();
}
return 0;
}
void checkKey(Object key) {
if (key == null) {
throw new NullPointerException();
}
}
CompletionStage handleException(CompletionStage f) {
return f.handle((r, e) -> {
if (e != null) {
if (e instanceof CompletionException) {
e = e.getCause();
}
if (e instanceof CacheException) {
throw new CompletionException(e);
}
throw new CompletionException(new CacheException(e));
}
return r;
});
}
V sync(RFuture result) {
try {
return result.toCompletableFuture().join();
} catch (Exception e) {
if (e.getCause() != null) {
throw (RuntimeException) e.getCause();
}
throw e;
}
}
@Override
public V get(K key) {
RLock lock = getLockedLock(key);
try {
RFuture result = getAsync(key);
return sync(result);
} finally {
lock.unlock();
}
}
@Override
public RFuture getAsync(K key) {
checkNotClosed();
checkKey(key);
long startTime = currentNanoTime();
RFuture future;
if (atomicExecution) {
future = getValue(key);
} else {
V value = getValueLocked(key);
future = new CompletableFutureWrapper<>(value);
}
CompletableFuture result = new CompletableFuture<>();
future.whenComplete((value, e) -> {
if (e != null) {
result.completeExceptionally(new CacheException(e));
return;
}
if (value == null) {
cacheManager.getStatBean(this).addMisses(1);
if (config.isReadThrough()) {
redisson.getServiceManager().getExecutor().execute(() -> {
try {
V val = loadValue(key);
result.complete(val);
} catch (CacheException ex) {
result.completeExceptionally(ex);
} catch (Exception ex) {
result.completeExceptionally(new CacheException(ex));
}
});
return;
}
} else {
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addHits(1);
}
result.complete(value);
});
return new CompletableFutureWrapper<>(result);
}
V getValueLocked(K key) {
V value = evalWrite(getRawName(), codec, RedisCommands.EVAL_MAP_VALUE,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return nil; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return nil; "
+ "end; "
+ "return value; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName()),
0, System.currentTimeMillis(), encodeMapKey(key));
if (value != null) {
Long accessTimeout = getAccessTimeout();
if (accessTimeout == -1) {
return value;
}
List result = new ArrayList(3);
result.add(value);
double syncId = ThreadLocalRandom.current().nextDouble();
Long syncs = evalWrite(getRawName(), codec, RedisCommands.EVAL_LONG,
"if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value), ARGV[4]); "
+ "local syncs = redis.call('publish', KEYS[4], syncMsg); "
+ "return syncs;"
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "return 0;"
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(),
getRemovedSyncChannelName()),
accessTimeout, System.currentTimeMillis(), encodeMapKey(key), syncId);
result.add(syncs);
result.add(syncId);
waitSync(result);
return value;
}
return value;
}
RFuture getValue(K key) {
Long accessTimeout = getAccessTimeout();
if (accessTimeout == -1) {
String name = getRawName(key);
return commandExecutor.evalReadAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return nil; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return nil; "
+ "end; "
+ "return value; ",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name)),
accessTimeout, System.currentTimeMillis(), encodeMapKey(key));
}
String name = getRawName(key);
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return nil; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return nil; "
+ "end; "
+ "if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "end; "
+ "return value; ",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name)),
accessTimeout, System.currentTimeMillis(), encodeMapKey(key));
}
Long getAccessTimeout(long baseTime) {
if (config.getExpiryPolicy().getExpiryForAccess() == null) {
return -1L;
}
Long accessTimeout = config.getExpiryPolicy().getExpiryForAccess().getAdjustedTime(baseTime);
if (config.getExpiryPolicy().getExpiryForAccess().isZero()) {
accessTimeout = 0L;
} else if (accessTimeout.longValue() == Long.MAX_VALUE) {
accessTimeout = -1L;
}
return accessTimeout;
}
Long getAccessTimeout() {
return getAccessTimeout(System.currentTimeMillis());
}
Map loadValues(Iterable extends K> keys) {
Map loaded;
try {
loaded = cacheLoader.loadAll(keys);
} catch (Exception ex) {
throw new CacheLoaderException(ex);
}
if (loaded != null) {
loaded = loaded.entrySet()
.stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
long startTime = currentNanoTime();
putAllValues(loaded);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
}
return loaded;
}
V loadValue(K key) {
V value = null;
try {
value = cacheLoader.load(key);
} catch (Exception ex) {
throw new CacheLoaderException(ex);
}
if (value != null) {
long startTime = currentNanoTime();
if (atomicExecution) {
putValue(key, value);
} else {
putValueLocked(key, value);
}
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
}
return value;
}
private R write(String key, RedisCommand command, Object... params) {
RFuture future = commandExecutor.writeAsync(key, command, params);
try {
return get(future);
} catch (Exception e) {
throw new CacheException(e);
}
}
R evalWrite(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
RFuture future = commandExecutor.evalWriteAsync(key, codec, evalCommandType, script, keys, params);
try {
return get(future);
} catch (Exception e) {
throw new CacheException(e);
}
}
R evalRead(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
RFuture future = commandExecutor.evalReadAsync(key, codec, evalCommandType, script, keys, params);
try {
return get(future);
} catch (Exception e) {
throw new CacheException(e);
}
}
private boolean putValueLocked(K key, Object value) {
double syncId = ThreadLocalRandom.current().nextDouble();
if (containsKey(key)) {
Long updateTimeout = getUpdateTimeout();
List res = evalWrite(getRawName(), codec, RedisCommands.EVAL_LIST,
"if ARGV[2] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[4], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[7], syncMsg); "
+ "return {0, syncs};"
+ "elseif ARGV[2] ~= '-1' then "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, syncs};"
+ "else "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, syncs};"
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getCreatedChannelName(), getRemovedChannelName(), getUpdatedChannelName(),
getCreatedSyncChannelName(), getRemovedSyncChannelName(), getUpdatedSyncChannelName(), getOldValueListenerCounter()),
0, updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
res.add(syncId);
waitSync(res);
return (Long) res.get(0) == 1;
}
Long creationTimeout = getCreationTimeout();
if (creationTimeout == 0) {
return false;
}
List res = evalWrite(getRawName(), codec, RedisCommands.EVAL_LIST,
"if ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[6], syncMsg); "
+ "return {1, syncs};"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[6], syncMsg); "
+ "return {1, syncs};"
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getCreatedChannelName(), getRemovedChannelName(), getUpdatedChannelName(),
getCreatedSyncChannelName(), getRemovedSyncChannelName(), getUpdatedSyncChannelName()),
creationTimeout, 0, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
res.add(syncId);
waitSync(res);
return (Long) res.get(0) == 1;
}
RFuture putAllValues(Map extends K, ? extends V> map) {
double syncId = ThreadLocalRandom.current().nextDouble();
RFuture> res = putAllOperation(commandExecutor, syncId, null, getRawName(), map);
RFuture result = handlePutAllResult(syncId, res);
return result;
}
RFuture handlePutAllResult(double syncId, CompletionStage> res) {
if (atomicExecution) {
CompletionStage f = res.thenCompose(r -> {
Long added = (Long) r.get(0);
Long syncs = (Long) r.get(1);
if (syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
return semaphore.acquireAsync(syncs.intValue()).thenCompose(obj1 -> {
return semaphore.deleteAsync().thenApply(obj -> added);
});
}
return CompletableFuture.completedFuture(added);
});
f = handleException(f);
return new CompletableFutureWrapper<>(f);
} else {
List r = res.toCompletableFuture().join();
Long added = (Long) r.get(0);
Long syncs = (Long) r.get(1);
if (syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
try {
semaphore.acquire(syncs.intValue());
semaphore.delete();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return new CompletableFutureWrapper<>(added);
}
}
RFuture> putAllOperation(CommandAsyncExecutor commandExecutor, double syncId,
MasterSlaveEntry msEntry, String name, Map extends K, ? extends V> map) {
Long creationTimeout = getCreationTimeout();
Long updateTimeout = getUpdateTimeout();
List params = new ArrayList<>();
params.add(creationTimeout);
params.add(updateTimeout);
params.add(System.currentTimeMillis());
params.add(syncId);
for (Map.Entry extends K, ? extends V> entry : map.entrySet()) {
params.add(encodeMapKey(entry.getKey()));
params.add(encodeMapValue(entry.getValue()));
}
String script = "local added = 0; "
+ "local syncs = 0; "
+ "for i = 5, #ARGV, 2 do " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[i]);" +
"local exists = redis.call('hexists', KEYS[1], ARGV[i]) == 1;" +
"if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[3]) then " +
"exists = false;" +
"end;" +
"if exists then "
+ "if ARGV[2] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[i]); "
+ "redis.call('zrem', KEYS[2], ARGV[i]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[i]);"
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[i]), ARGV[i], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[4], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[i]), ARGV[i], string.len(tostring(value)), tostring(value), ARGV[4]); "
+ "syncs = syncs + redis.call('publish', KEYS[7], syncMsg); "
+ "elseif ARGV[2] ~= '-1' then "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[i], ARGV[i+1]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[i]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], -1, ARGV[4]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[i]);"
+ "redis.call('hset', KEYS[1], ARGV[i], ARGV[i+1]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[i]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], string.len(tostring(value)), tostring(value), ARGV[4]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "syncs = syncs + redis.call('publish', KEYS[8], syncMsg); "
+ "added = added + 1;"
+ "else "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[i], ARGV[i+1]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], -1, ARGV[4]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[i]);"
+ "redis.call('hset', KEYS[1], ARGV[i], ARGV[i+1]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], string.len(tostring(value)), tostring(value), ARGV[4]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "syncs = syncs + redis.call('publish', KEYS[8], syncMsg); "
+ "added = added + 1;"
+ "end; "
+ "else "
+ "if ARGV[1] == '0' then "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[i], ARGV[i+1]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[i]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], ARGV[4]); "
+ "syncs = syncs + redis.call('publish', KEYS[6], syncMsg); "
+ "added = added + 1;"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[i], ARGV[i+1]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[i]), ARGV[i], string.len(ARGV[i+1]), ARGV[i+1], ARGV[4]); "
+ "syncs = syncs + redis.call('publish', KEYS[6], syncMsg); "
+ "added = added + 1;"
+ "end; "
+ "end; "
+ "end; "
+ "return {added, syncs};";
if (msEntry == null) {
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_LIST, script,
Arrays.asList(name, getTimeoutSetName(name), getCreatedChannelName(name), getRemovedChannelName(name), getUpdatedChannelName(name),
getCreatedSyncChannelName(name), getRemovedSyncChannelName(name), getUpdatedSyncChannelName(name), getOldValueListenerCounter(name)),
params.toArray());
}
return commandExecutor.evalWriteAsync(msEntry, codec, RedisCommands.EVAL_LIST, script,
Arrays.asList(name, getTimeoutSetName(name), getCreatedChannelName(name), getRemovedChannelName(name), getUpdatedChannelName(name),
getCreatedSyncChannelName(name), getRemovedSyncChannelName(name), getUpdatedSyncChannelName(name), getOldValueListenerCounter(name)),
params.toArray());
}
RFuture putValue(K key, Object value) {
double syncId = ThreadLocalRandom.current().nextDouble();
Long creationTimeout = getCreationTimeout();
Long updateTimeout = getUpdateTimeout();
String name = getRawName(key);
RFuture> res = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_LIST,
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[4]);" +
"local exists = redis.call('hexists', KEYS[1], ARGV[4]) == 1;" +
"if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[3]) then " +
"exists = false;" +
"end;" +
"if exists then "
+ "if ARGV[2] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[4], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[7], syncMsg); "
+ "return {0, syncs};"
+ "elseif ARGV[2] ~= '-1' then "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, syncs};"
+ "else "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, syncs};"
+ "end; "
+ "else "
+ "if ARGV[1] == '0' then "
+ "return {0};"
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[6], syncMsg); "
+ "return {1, syncs};"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[6], syncMsg); "
+ "return {1, syncs};"
+ "end; "
+ "end; ",
Arrays.asList(name, getTimeoutSetName(name), getCreatedChannelName(name), getRemovedChannelName(name), getUpdatedChannelName(name),
getCreatedSyncChannelName(name), getRemovedSyncChannelName(name), getUpdatedSyncChannelName(name), getOldValueListenerCounter(name)),
creationTimeout, updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
RFuture result = waitSync(syncId, res);
return result;
}
RFuture waitSync(double syncId, RFuture> res) {
if (atomicExecution) {
CompletionStage f = res.thenCompose(r -> {
r.add(syncId);
if (r.size() < 2) {
return CompletableFuture.completedFuture((Long) r.get(0) >= 1);
}
Long syncs = (Long) r.get(r.size() - 2);
if (syncs != null && syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
return semaphore.acquireAsync(syncs.intValue()).thenCompose(obj1 -> {
return semaphore.deleteAsync().thenApply(obj -> (Long) r.get(0) >= 1);
});
} else {
return CompletableFuture.completedFuture((Long) r.get(0) >= 1);
}
});
f = handleException(f);
return new CompletableFutureWrapper<>(f);
} else {
List r = res.toCompletableFuture().join();
r.add(syncId);
waitSync(r);
return new CompletableFutureWrapper<>((Long) r.get(0) >= 1);
}
}
Long getUpdateTimeout(long baseTime) {
if (config.getExpiryPolicy().getExpiryForUpdate() == null) {
return -1L;
}
Long updateTimeout = config.getExpiryPolicy().getExpiryForUpdate().getAdjustedTime(baseTime);
if (config.getExpiryPolicy().getExpiryForUpdate().isZero()) {
updateTimeout = 0L;
} else if (updateTimeout.longValue() == Long.MAX_VALUE) {
updateTimeout = -1L;
}
return updateTimeout;
}
Long getUpdateTimeout() {
return getUpdateTimeout(System.currentTimeMillis());
}
Long getCreationTimeout(long baseTime) {
if (config.getExpiryPolicy().getExpiryForCreation() == null) {
return -1L;
}
Long creationTimeout = config.getExpiryPolicy().getExpiryForCreation().getAdjustedTime(baseTime);
if (config.getExpiryPolicy().getExpiryForCreation().isZero()) {
creationTimeout = 0L;
} else if (creationTimeout.longValue() == Long.MAX_VALUE) {
creationTimeout = -1L;
}
return creationTimeout;
}
Long getCreationTimeout() {
return getCreationTimeout(System.currentTimeMillis());
}
RFuture putIfAbsentValue(K key, Object value) {
Long creationTimeout = getCreationTimeout();
if (creationTimeout == 0) {
return new CompletableFutureWrapper<>(false);
}
String name = getRawName(key);
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]);" +
"local exists = redis.call('hexists', KEYS[1], ARGV[2]) == 1;" +
"if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[4]) then " +
"exists = false;" +
"end;" +
"if exists then "
+ "return 0; "
+ "else "
+ "if ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[2], ARGV[3]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "return 1;"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "return 1;"
+ "end; "
+ "end; ",
Arrays.asList(name, getTimeoutSetName(name), getCreatedChannelName(name)),
creationTimeout, encodeMapKey(key), encodeMapValue(value), System.currentTimeMillis());
}
private boolean putIfAbsentValueLocked(K key, Object value) {
if (containsKey(key)) {
return false;
}
Long creationTimeout = getCreationTimeout();
if (creationTimeout == 0) {
return false;
}
return evalWrite(getRawName(), codec, RedisCommands.EVAL_BOOLEAN,
"if ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[2], ARGV[3]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "return 1;"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "return 1;"
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getCreatedChannelName()),
creationTimeout, encodeMapKey(key), encodeMapValue(value));
}
private String getLockName(Object key) {
ByteBuf keyState = encodeMapKey(key);
try {
return "{" + getRawName() + "}:" + Hash.hash128toBase64(keyState) + ":key";
} finally {
keyState.release();
}
}
@Override
public Map getAll(Set extends K> keys) {
checkNotClosed();
if (keys == null) {
throw new NullPointerException();
}
for (K key : keys) {
checkKey(key);
}
if (!atomicExecution && !config.isReadThrough()) {
long startTime = currentNanoTime();
boolean exists = false;
for (K key : keys) {
if (containsKey(key)) {
exists = true;
break;
}
}
if (!exists) {
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
return Collections.emptyMap();
}
}
RFuture> result = getAllAsync(keys);
return sync(result);
}
@Override
public RFuture> getAllAsync(Set extends K> keys) {
checkNotClosed();
long startTime = currentNanoTime();
Long accessTimeout = getAccessTimeout();
List args = new ArrayList<>(keys.size() + 2);
args.add(accessTimeout);
args.add(System.currentTimeMillis());
encodeMapKeys(args, keys);
RFuture> res = getAllOperation(commandExecutor, getRawName(), null, new ArrayList<>(keys), accessTimeout, args);
return handleGetAllResult(startTime, res);
}
RFuture> getAllOperation(CommandAsyncExecutor commandExecutor, String name, MasterSlaveEntry entry, List keys, Long accessTimeout, List args) {
String script;
if (accessTimeout == -1) {
script = "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');"
+ "local accessTimeout = ARGV[1]; "
+ "local currentTime = tonumber(ARGV[2]); "
+ "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime; "
+ "local map = {};"
+ "for i=3, #ARGV, 5000 do "
+ "local m = redis.call('hmget', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); "
+ "for k,v in ipairs(m) do "
+ "table.insert(map, v) "
+ "end; "
+ "end; "
+ "local result = {};"
+ "for i, value in ipairs(map) do "
+ "if value ~= false then "
+ "local key = ARGV[i+2]; "
+ "if hasExpire then "
+ "local expireDateScore = redis.call('zscore', KEYS[2], key); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= currentTime then "
+ "value = false; "
+ "end; "
+ "end; "
+ "end; "
+ "table.insert(result, value); "
+ "end; "
+ "return result;";
} else {
script = "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');"
+ "local accessTimeout = ARGV[1]; "
+ "local currentTime = tonumber(ARGV[2]); "
+ "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime; "
+ "local map = {};"
+ "for i=3, #ARGV, 5000 do "
+ "local m = redis.call('hmget', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); "
+ "for k,v in ipairs(m) do "
+ "table.insert(map, v) "
+ "end; "
+ "end; "
+ "local result = {};"
+ "for i, value in ipairs(map) do "
+ "if value ~= false then "
+ "local key = ARGV[i+2]; "
+ "if hasExpire then "
+ "local expireDateScore = redis.call('zscore', KEYS[2], key); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= currentTime then "
+ "value = false; "
+ "end; "
+ "end; "
+ "if accessTimeout == '0' then "
+ "redis.call('hdel', KEYS[1], key); "
+ "redis.call('zrem', KEYS[2], key); "
+ "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(value), value); "
+ "redis.call('publish', KEYS[3], {key, value}); "
+ "elseif accessTimeout ~= '-1' then "
+ "redis.call('zadd', KEYS[2], accessTimeout, key); "
+ "end; "
+ "end; "
+ "table.insert(result, value); "
+ "end; "
+ "return result;";
}
if (entry == null) {
return commandExecutor.evalReadAsync(name, codec, new RedisCommand>("EVAL",
new MapValueDecoder(new MapGetAllDecoder(keys, 0, true))),
script, Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name)), args.toArray());
}
return commandExecutor.evalReadAsync(entry, codec, new RedisCommand>("EVAL",
new MapValueDecoder(new MapGetAllDecoder(keys, 0, true))),
script, Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name)), args.toArray());
}
RFuture> handleGetAllResult(long startTime, RFuture> res) {
CompletableFuture> result = new CompletableFuture<>();
res.whenComplete((r, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
Map notNullEntries = r.entrySet().stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue()));
cacheManager.getStatBean(this).addHits(notNullEntries.size());
int nullValues = r.size() - notNullEntries.size();
if (config.isReadThrough() && nullValues > 0) {
cacheManager.getStatBean(this).addMisses(nullValues);
commandExecutor.getServiceManager().getExecutor().execute(() -> {
try {
Set nullKeys = r.entrySet().stream()
.filter(e -> e.getValue() == null)
.map(e -> e.getKey())
.collect(Collectors.toSet());
Map loadedMap = loadValues(nullKeys);
notNullEntries.putAll(loadedMap);
} catch (Exception exc) {
result.completeExceptionally(exc);
return;
}
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
result.complete(notNullEntries);
});
} else {
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
result.complete(notNullEntries);
}
});
return new CompletableFutureWrapper<>(result);
}
@Override
public boolean containsKey(K key) {
RFuture future = containsKeyAsync(key);
return sync(future);
}
@Override
public RFuture containsKeyAsync(K key) {
checkNotClosed();
checkKey(key);
String name = getRawName(key);
CompletionStage future = commandExecutor.evalReadAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
"if redis.call('hexists', KEYS[1], ARGV[2]) == 0 then "
+ "return 0;"
+ "end;"
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then "
+ "return 0; "
+ "end; "
+ "return 1;",
Arrays.asList(name, getTimeoutSetName(name)),
System.currentTimeMillis(), encodeMapKey(key));
future = handleException(future);
return new CompletableFutureWrapper<>(future);
}
@Override
@SuppressWarnings("NestedTryDepth")
public void loadAll(final Set extends K> keys, final boolean replaceExistingValues, final CompletionListener completionListener) {
checkNotClosed();
if (keys == null) {
throw new NullPointerException();
}
for (K key : keys) {
checkKey(key);
}
if (cacheLoader == null) {
if (completionListener != null) {
completionListener.onCompletion();
}
return;
}
commandExecutor.getServiceManager().getExecutor().execute(new Runnable() {
@Override
public void run() {
for (K key : keys) {
try {
if (!containsKey(key) || replaceExistingValues) {
RLock lock = getLockedLock(key);
try {
if (!containsKey(key)|| replaceExistingValues) {
V value;
try {
value = cacheLoader.load(key);
} catch (Exception ex) {
throw new CacheLoaderException(ex);
}
if (value != null) {
if (atomicExecution) {
putValue(key, value);
} else {
putValueLocked(key, value);
}
}
}
} finally {
lock.unlock();
}
}
} catch (Exception e) {
if (completionListener != null) {
completionListener.onException(e);
}
throw e;
}
}
if (completionListener != null) {
completionListener.onCompletion();
}
}
});
}
RLock getLockedLock(K key) {
if (atomicExecution) {
return DUMMY_LOCK;
}
String lockName = getLockName(key);
RLock lock = redisson.getLock(lockName);
try {
lock.lock();
} catch (Exception e) {
throw new CacheException(e);
}
return lock;
}
@Override
public RFuture putAsync(K key, V value) {
checkNotClosed();
checkKey(key);
if (value == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
if (config.isWriteThrough()) {
RLock lock = getLockedLock(key);
try {
CompletionStage> future;
if (atomicExecution) {
future = getAndPutValue(key, value);
} else {
List res = getAndPutValueLocked(key, value);
future = new CompletableFutureWrapper<>(res);
}
CompletionStage f = future.thenCompose(res -> {
if (res.isEmpty()) {
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
return CompletableFuture.completedFuture(null);
}
Long added = (Long) res.get(0);
if (added == null) {
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
return CompletableFuture.completedFuture(null);
}
return writeCache(key, value, startTime, res, added);
});
f = handleException(f);
return new CompletableFutureWrapper<>(f);
} finally {
lock.unlock();
}
} else {
RLock lock = getLockedLock(key);
try {
RFuture future;
if (atomicExecution) {
future = putValue(key, value);
} else {
boolean res = putValueLocked(key, value);
future = new CompletableFutureWrapper<>(res);
}
CompletionStage f = future.handle((r, e) -> {
if (e != null) {
throw new CompletionException(new CacheException(e));
}
if (r) {
cacheManager.getStatBean(this).addPuts(1);
}
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
return null;
});
return new CompletableFutureWrapper<>(f);
} finally {
lock.unlock();
}
}
}
private CompletableFuture writeCache(K key, V value, long startTime, List res, Long added) {
Runnable r;
CompletableFuture result = new CompletableFuture<>();
if (added >= 1) {
r = () -> {
try {
cacheWriter.write(new JCacheEntry(key, value));
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(null);
} catch (Exception e) {
removeValues(key).whenComplete((obj, exc) -> {
if (exc != null) {
result.completeExceptionally(new CacheWriterException(exc));
return;
}
handleException(result, e);
});
}
};
} else {
r = () -> {
try {
cacheWriter.delete(key);
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(null);
} catch (Exception e) {
if (res.size() == 4 && res.get(1) != null) {
putValue(key, res.get(1)).whenComplete((obj, exc) -> {
if (exc != null) {
result.completeExceptionally(new CacheWriterException(exc));
return;
}
handleException(result, e);
});
return;
}
handleException(result, e);
}
};
}
if (atomicExecution) {
commandExecutor.getServiceManager().getExecutor().execute(r);
} else {
r.run();
}
return result;
}
@Override
public void put(K key, V value) {
RFuture future = putAsync(key, value);
sync(future);
}
RFuture removeValues(Object... keys) {
List params = new ArrayList<>(keys.length + 1);
params.add(System.currentTimeMillis());
encodeMapKeys(params, Arrays.asList(keys));
return removeValuesOperation(commandExecutor, getRawName(), null, params, null);
}
RFuture removeValuesOperation(CommandAsyncExecutor commandExecutor, String name, MasterSlaveEntry entry, List params, Object[] keys) {
String script = "local counter = 0;"
+ "for i=2, #ARGV do "
+ "local value = redis.call('hget', KEYS[1], ARGV[i]); "
+ "if value ~= false then "
+ "redis.call('hdel', KEYS[1], ARGV[i]); "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[i]); "
+ "if not (expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1])) then "
+ "counter = counter + 1;"
+ "end; "
+ "redis.call('zrem', KEYS[2], ARGV[i]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[i]), ARGV[i], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "end;"
+ "end; "
+ "return counter;";
if (entry == null) {
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_LONG, script,
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getRemovedSyncChannelName(name)),
params.toArray());
}
return commandExecutor.evalWriteAsync(entry, codec, RedisCommands.EVAL_LONG, script,
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getRemovedSyncChannelName(name)),
params.toArray());
}
private List getAndPutValueLocked(K key, V value) {
double syncId = ThreadLocalRandom.current().nextDouble();
if (containsKey(key)) {
Long updateTimeout = getUpdateTimeout();
List result = evalWrite(getRawName(), codec, RedisCommands.EVAL_LIST,
"local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "if ARGV[2] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[6], syncMsg); "
+ "return {0, value, syncs};"
+ "elseif ARGV[2] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, value, syncs};"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, value, syncs};"
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(), getCreatedChannelName(), getUpdatedChannelName(),
getRemovedSyncChannelName(), getCreatedSyncChannelName(), getUpdatedSyncChannelName(), getOldValueListenerCounter()),
0, updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
result.add(syncId);
waitSync(result);
return result;
}
Long creationTimeout = getCreationTimeout();
if (creationTimeout == 0) {
return Collections.emptyList();
}
List result = evalWrite(getRawName(), codec, RedisCommands.EVAL_LIST,
"if ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[4], syncMsg); "
+ "return {1, syncs};"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[4], syncMsg); "
+ "return {1, syncs};"
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getCreatedChannelName(), getCreatedSyncChannelName()),
creationTimeout, 0, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
result.add(syncId);
waitSync(result);
return result;
}
CompletionStage> getAndPutValue(K key, V value) {
Long creationTimeout = getCreationTimeout();
Long updateTimeout = getUpdateTimeout();
double syncId = ThreadLocalRandom.current().nextDouble();
String name = getRawName(key);
RFuture> future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_LIST,
"local value = redis.call('hget', KEYS[1], ARGV[4]);"
+ "if value ~= false then "
+ "if ARGV[2] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[6], syncMsg); "
+ "return {0, value, syncs};"
+ "elseif ARGV[2] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, value, syncs};"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[9])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], -1, ARGV[6]); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], string.len(tostring(value)), tostring(value), ARGV[6]); "
+ "end; "
+ "redis.call('publish', KEYS[5], msg); "
+ "local syncs = redis.call('publish', KEYS[8], syncMsg); "
+ "return {1, value, syncs};"
+ "end; "
+ "else "
+ "if ARGV[1] == '0' then "
+ "return {nil};"
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[4], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[7], syncMsg); "
+ "return {1, syncs};"
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[5]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5]); "
+ "redis.call('publish', KEYS[4], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[5]), ARGV[5], ARGV[6]); "
+ "local syncs = redis.call('publish', KEYS[7], syncMsg); "
+ "return {1, syncs};"
+ "end; "
+ "end; ",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getCreatedChannelName(name), getUpdatedChannelName(name),
getRemovedSyncChannelName(name), getCreatedSyncChannelName(name), getUpdatedSyncChannelName(name), getOldValueListenerCounter(name)),
creationTimeout, updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
return future.thenApply(r -> {
if (!r.isEmpty()) {
r.add(syncId);
}
return r;
});
}
@Override
public V getAndPut(K key, V value) {
RFuture future = getAndPutAsync(key, value);
return sync(future);
}
@Override
public RFuture getAndPutAsync(K key, V value) {
checkNotClosed();
checkKey(key);
if (value == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
if (config.isWriteThrough()) {
RLock lock = getLockedLock(key);
try {
CompletionStage> future;
if (atomicExecution) {
future = getAndPutValue(key, value);
} else {
List res = getAndPutValueLocked(key, value);
future = new CompletableFutureWrapper<>(res);
}
CompletionStage f = future.thenCompose(res -> {
if (res.isEmpty()) {
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addMisses(1);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
return CompletableFuture.completedFuture((V) null);
}
Long added = (Long) res.get(0);
if (added == null) {
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
return CompletableFuture.completedFuture((V) res.get(1));
}
CompletableFuture ff = writeCache(key, value, startTime, res, added);
return ff.thenApply(r -> {
V val = getAndPutResult(startTime, res);
return val;
});
});
f = handleException(f);
return new CompletableFutureWrapper<>(f);
} finally {
lock.unlock();
}
} else {
RLock lock = getLockedLock(key);
try {
CompletionStage> future;
if (atomicExecution) {
future = getAndPutValue(key, value);
} else {
List res = getAndPutValueLocked(key, value);
future = new CompletableFutureWrapper<>(res);
}
CompletionStage f = future.handle((r, e) -> {
if (e != null) {
throw new CompletionException(new CacheException(e));
}
V val = getAndPutResult(startTime, r);
return val;
});
return new CompletableFutureWrapper<>(f);
} finally {
lock.unlock();
}
}
}
private V getAndPutResult(long startTime, List result) {
if (result.size() != 4) {
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addMisses(1);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
return null;
}
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
return (V) result.get(1);
}
@Override
public void putAll(Map extends K, ? extends V> map) {
RFuture result = putAllAsync(map);
sync(result);
}
@Override
public RFuture putAllAsync(Map extends K, ? extends V> map) {
checkNotClosed();
for (Map.Entry extends K, ? extends V> entry : map.entrySet()) {
K key = entry.getKey();
checkKey(key);
V value = entry.getValue();
if (value == null) {
throw new NullPointerException();
}
}
long startTime = currentNanoTime();
RFuture future = putAllValues(map);
CompletableFuture result = new CompletableFuture<>();
Runnable r = () -> {
Map> addedEntries = new HashMap<>();
for (Map.Entry extends K, ? extends V> entry : map.entrySet()) {
addedEntries.put(entry.getKey(), new JCacheEntry(entry.getKey(), entry.getValue()));
}
try {
cacheWriter.writeAll(addedEntries.values());
} catch (Exception e) {
removeValues(addedEntries.keySet().toArray()).whenComplete((res, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
handleException(result, e);
});
return;
}
result.complete(null);
};
future.whenComplete((res, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
cacheManager.getStatBean(this).addPuts(res);
for (int i = 0; i < res; i++) {
cacheManager.getStatBean(this).addPutTime((currentNanoTime() - startTime) / res);
}
});
if (atomicExecution) {
future.whenComplete((res, ex) -> {
if (config.isWriteThrough()) {
commandExecutor.getServiceManager().getExecutor().execute(r);
} else {
result.complete(null);
}
});
} else {
if (config.isWriteThrough()) {
r.run();
} else {
result.complete(null);
}
}
return new CompletableFutureWrapper<>(result);
}
void waitSync(List result) {
if (result.size() < 2) {
return;
}
Long syncs = (Long) result.get(result.size() - 2);
Double syncId = (Double) result.get(result.size() - 1);
if (syncs != null && syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
try {
semaphore.acquire(syncs.intValue());
semaphore.delete();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@Override
public boolean putIfAbsent(K key, V value) {
RFuture result = putIfAbsentAsync(key, value);
return sync(result);
}
@Override
public RFuture putIfAbsentAsync(K key, V value) {
checkNotClosed();
checkKey(key);
if (value == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
RLock lock = getLockedLock(key);
CompletableFuture result = new CompletableFuture<>();
try {
RFuture future;
if (atomicExecution) {
future = putIfAbsentValue(key, value);
} else {
boolean r = putIfAbsentValueLocked(key, value);
future = new CompletableFutureWrapper<>(r);
}
future.whenComplete((r, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
if (r) {
cacheManager.getStatBean(this).addPuts(1);
if (config.isWriteThrough()) {
commandExecutor.getServiceManager().getExecutor().execute(() -> {
try {
cacheWriter.write(new JCacheEntry(key, value));
} catch (Exception e) {
removeValues(key);
handleException(result, e);
return;
}
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(r);
});
return;
}
}
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(r);
});
} finally {
lock.unlock();
}
return new CompletableFutureWrapper<>(result);
}
void handleException(CompletableFuture> result, Exception e) {
if (e instanceof CacheWriterException) {
result.completeExceptionally(e);
}
result.completeExceptionally(new CacheWriterException(e));
}
RFuture removeValue(K key) {
double syncId = ThreadLocalRandom.current().nextDouble();
String name = getRawName(key);
RFuture> future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_LIST,
"local value = redis.call('hexists', KEYS[1], ARGV[2]); "
+ "if value == 0 then "
+ "return {0}; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then "
+ "return {0}; "
+ "end; "
+ "value = redis.call('hget', KEYS[1], ARGV[2]); "
+ "redis.call('hdel', KEYS[1], ARGV[2]); "
+ "redis.call('zrem', KEYS[2], ARGV[2]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[2]), ARGV[2], string.len(tostring(value)), tostring(value), ARGV[3]); "
+ "local syncs = redis.call('publish', KEYS[4], syncMsg); "
+ "return {1, syncs};",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getRemovedSyncChannelName(name)),
System.currentTimeMillis(), encodeMapKey(key), syncId);
RFuture result = waitSync(syncId, future);
return result;
}
@Override
public boolean remove(K key) {
RFuture future = removeAsync(key);
return sync(future);
}
@Override
public RFuture removeAsync(K key) {
checkNotClosed();
checkKey(key);
long startTime = System.currentTimeMillis();
if (config.isWriteThrough()) {
RLock lock = getLockedLock(key);
try {
CompletableFuture result = new CompletableFuture<>();
CompletionStage future = getAndRemoveValue(key);
if (atomicExecution) {
future.whenComplete((oldValue, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
commandExecutor.getServiceManager().getExecutor().submit(() -> {
try {
cacheWriter.delete(key);
if (oldValue != null) {
cacheManager.getStatBean(this).addRemovals(1);
}
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
result.complete(oldValue != null);
} catch (Exception e) {
if (oldValue != null) {
putValue(key, oldValue);
}
handleException(result, e);
}
});
});
} else {
V oldValue = future.toCompletableFuture().join();
try {
cacheWriter.delete(key);
if (oldValue != null) {
cacheManager.getStatBean(this).addRemovals(1);
}
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
result.complete(oldValue != null);
} catch (Exception e) {
if (oldValue != null) {
putValue(key, oldValue);
}
handleException(result, e);
}
}
return new CompletableFutureWrapper<>(result);
} finally {
lock.unlock();
}
} else {
RFuture result = removeValue(key);
CompletionStage f = result.thenApply(res -> {
if (res) {
cacheManager.getStatBean(this).addRemovals(1);
}
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
return res;
});
return new CompletableFutureWrapper<>(f);
}
}
private boolean removeValueLocked(K key, V value) {
Boolean result = evalWrite(getRawName(), codec, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return 0; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return 0; "
+ "end; "
+ "if ARGV[4] == value then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "return 1; "
+ "end; "
+ "return nil;",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName()),
0, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
if (result == null) {
Long accessTimeout = getAccessTimeout();
if (accessTimeout == -1) {
return false;
}
return evalWrite(getRawName(), codec, RedisCommands.EVAL_BOOLEAN,
"if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName()),
accessTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
}
return result;
}
RFuture removeValue(K key, V value) {
Long accessTimeout = getAccessTimeout();
String name = getRawName(key);
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return 0; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return 0; "
+ "end; "
+ "if ARGV[4] == value then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "return 1; "
+ "end; "
+ "if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "end; "
+ "return 0; ",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name)),
accessTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
}
@Override
public boolean remove(K key, V value) {
RFuture future = removeAsync(key, value);
return sync(future);
}
@Override
public RFuture removeAsync(K key, V value) {
checkNotClosed();
checkKey(key);
if (value == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
if (config.isWriteThrough()) {
RLock lock = getLockedLock(key);
try {
if (atomicExecution) {
CompletableFuture result = new CompletableFuture<>();
RFuture future = removeValue(key, value);
future.whenComplete((r, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
if (r) {
commandExecutor.getServiceManager().getExecutor().submit(() -> {
try {
cacheWriter.delete(key);
} catch (Exception e) {
putValue(key, value);
handleException(result, e);
return;
}
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addRemovals(1);
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
result.complete(r);
});
} else {
cacheManager.getStatBean(this).addMisses(1);
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
result.complete(r);
}
});
return new CompletableFutureWrapper<>(result);
} else {
boolean res = removeValueLocked(key, value);
if (res) {
try {
cacheWriter.delete(key);
} catch (Exception e) {
putValue(key, value).toCompletableFuture().join();
if (e instanceof CacheWriterException) {
throw e;
}
throw new CacheWriterException(e);
}
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addRemovals(1);
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
} else {
cacheManager.getStatBean(this).addMisses(1);
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
}
return new CompletableFutureWrapper<>(res);
}
} finally {
lock.unlock();
}
} else {
RLock lock = getLockedLock(key);
try {
RFuture future;
if (atomicExecution) {
future = removeValue(key, value);
} else {
boolean res = removeValueLocked(key, value);
future = new CompletableFutureWrapper<>(res);
}
CompletionStage f = future.handle((r, ex) -> {
if (ex != null) {
throw new CompletionException(new CacheException(ex));
}
if (r) {
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addRemovals(1);
} else {
cacheManager.getStatBean(this).addMisses(1);
}
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
return r;
});
return new CompletableFutureWrapper<>(f);
} finally {
lock.unlock();
}
}
}
CompletionStage> getAndRemoveValues(Collection keys) {
double syncId = ThreadLocalRandom.current().nextDouble();
RFuture> future = getAndRemoveValuesOperation(commandExecutor, null, getRawName(), (Collection) keys, syncId);
if (atomicExecution) {
return future.thenCompose(r -> {
long nullsAmount = (long) r.get(1);
if (nullsAmount == keys.size()) {
return CompletableFuture.completedFuture(Collections.emptyMap());
}
long syncs = (long) r.get(0);
if (syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
return semaphore.acquireAsync((int) syncs).thenCompose(obj1 -> {
return semaphore.deleteAsync().thenCompose(obj -> {
return getAndRemoveValuesResult(keys, r, nullsAmount);
});
});
} else {
return getAndRemoveValuesResult(keys, r, nullsAmount);
}
});
} else {
List r = future.toCompletableFuture().join();
long nullsAmount = (long) r.get(1);
if (nullsAmount == keys.size()) {
return CompletableFuture.completedFuture(Collections.emptyMap());
}
long syncs = (long) r.get(0);
if (syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
try {
semaphore.acquire((int) syncs);
semaphore.delete();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return getAndRemoveValuesResult(keys, r, nullsAmount);
}
}
RFuture> getAndRemoveValuesOperation(CommandAsyncExecutor commandExecutor, MasterSlaveEntry entry, String name, Collection keys, double syncId) {
List params = new ArrayList<>();
params.add(System.currentTimeMillis());
params.add(syncId);
encodeMapKeys(params, keys);
String script = "local syncs = 0; "
+ "local values = {}; "
+ "local result = {}; "
+ "local nulls = {}; "
+ "for i = 3, #ARGV, 1 do "
+ "local value = redis.call('hget', KEYS[1], ARGV[i]); "
+ "if value == false then "
+ "table.insert(nulls, i-3); "
+ "else "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[i]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then "
+ "table.insert(nulls, i-3); "
+ "else "
+ "redis.call('hdel', KEYS[1], ARGV[i]); "
+ "redis.call('zrem', KEYS[2], ARGV[i]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[i]), ARGV[i], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[i]), ARGV[i], string.len(tostring(value)), tostring(value), ARGV[2]); "
+ "syncs = syncs + redis.call('publish', KEYS[4], syncMsg); "
+ "table.insert(values, value); "
+ "end; "
+ "end; "
+ "end; "
+ "table.insert(result, syncs); "
+ "table.insert(result, #nulls); "
+ "for i = 1, #nulls, 1 do "
+ "table.insert(result, nulls[i]); "
+ "end; "
+ "for i = 1, #values, 1 do "
+ "table.insert(result, values[i]); "
+ "end; "
+ "return result; ";
if (entry == null) {
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE_LIST, script,
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getRemovedSyncChannelName(name)),
params.toArray());
}
return commandExecutor.evalWriteAsync(entry, codec, RedisCommands.EVAL_MAP_VALUE_LIST, script,
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getRemovedSyncChannelName(name)),
params.toArray());
}
private CompletionStage> getAndRemoveValuesResult(Collection keys, List r, long nullsAmount) {
Map res = new HashMap<>();
fillMap(keys, r, res, nullsAmount, 0);
return CompletableFuture.completedFuture(res);
}
void fillMap(Collection keys, List r, Map res, long nullsAmount, int baseIndex) {
List list = (List) (Object) r.subList(baseIndex + 2, baseIndex + (int) nullsAmount + 2);
HashSet nullIndexes = new HashSet<>(list);
long i = 0;
for (K key : keys) {
if (nullIndexes.contains(i)) {
continue;
}
V value = (V) r.get((int) (baseIndex + i + nullsAmount + 2));
res.put(key, value);
i++;
}
}
CompletionStage getAndRemoveValue(K key) {
double syncId = ThreadLocalRandom.current().nextDouble();
String name = getRawName(key);
RFuture> future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE_LIST,
"local value = redis.call('hget', KEYS[1], ARGV[2]); "
+ "if value == false then "
+ "return {nil}; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then "
+ "return {nil}; "
+ "end; "
+ "redis.call('hdel', KEYS[1], ARGV[2]); "
+ "redis.call('zrem', KEYS[2], ARGV[2]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[2]), ARGV[2], string.len(tostring(value)), tostring(value), ARGV[3]); "
+ "local syncs = redis.call('publish', KEYS[4], syncMsg); "
+ "return {value, syncs}; ",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getRemovedSyncChannelName(name)),
System.currentTimeMillis(), encodeMapKey(key), syncId);
if (atomicExecution) {
return future.thenCompose(r -> {
if (r.size() < 2) {
return CompletableFuture.completedFuture((V) null);
}
Long syncs = (Long) r.get(1);
if (syncs != null && syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
return semaphore.acquireAsync(syncs.intValue()).thenCompose(obj1 -> {
return semaphore.deleteAsync().thenApply(obj -> (V) r.get(0));
});
} else {
return CompletableFuture.completedFuture((V) r.get(0));
}
});
} else {
List r = future.toCompletableFuture().join();
if (r.size() < 2) {
return CompletableFuture.completedFuture((V) null);
}
Long syncs = (Long) r.get(1);
if (syncs != null && syncs > 0) {
RSemaphore semaphore = redisson.getSemaphore(getSyncName(syncId));
try {
semaphore.acquire(syncs.intValue());
semaphore.delete();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return CompletableFuture.completedFuture((V) r.get(0));
}
}
@Override
public V getAndRemove(K key) {
RFuture future = getAndRemoveAsync(key);
return sync(future);
}
@Override
public RFuture getAndRemoveAsync(K key) {
checkNotClosed();
checkKey(key);
long startTime = currentNanoTime();
CompletableFuture result = new CompletableFuture<>();
RLock lock = getLockedLock(key);
try {
CompletionStage future = getAndRemoveValue(key);
future.whenComplete((value, e) -> {
if (e != null) {
result.completeExceptionally(new CacheException(e));
return;
}
if (value != null) {
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addRemovals(1);
} else {
cacheManager.getStatBean(this).addMisses(1);
}
if (config.isWriteThrough()) {
commandExecutor.getServiceManager().getExecutor().submit(() -> {
try {
cacheWriter.delete(key);
} catch (Exception ex) {
if (value != null) {
putValue(key, value);
}
handleException(result, ex);
return;
}
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
result.complete(value);
});
} else {
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
result.complete(value);
}
});
} finally {
lock.unlock();
}
return new CompletableFutureWrapper<>(result);
}
private long replaceValueLocked(K key, V oldValue, V newValue) {
Long res = evalWrite(getRawName(), codec, RedisCommands.EVAL_LONG,
"local value = redis.call('hget', KEYS[1], ARGV[4]); "
+ "if value == false then "
+ "return 0; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[4]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[3]) then "
+ "return 0; "
+ "end; "
+ "if ARGV[5] == value then "
+ "return 1;"
+ "end; "
+ "return -1;",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(), getUpdatedChannelName()),
0, 0, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue));
if (res == 1) {
Long updateTimeout = getUpdateTimeout();
double syncId = ThreadLocalRandom.current().nextDouble();
Long syncs = evalWrite(getRawName(), codec, RedisCommands.EVAL_LONG,
"if ARGV[2] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[7]); "
+ "return redis.call('publish', KEYS[5], syncMsg); "
+ "elseif ARGV[2] ~= '-1' then "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[7])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[6]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], -1, ARGV[7]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]); "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[6]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], string.len(tostring(value)), tostring(value), ARGV[7]); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "return redis.call('publish', KEYS[6], syncMsg); "
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[6]); "
+ "local msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], -1); "
+ "redis.call('publish', KEYS[4], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], -1, ARGV[7]); "
+ "return redis.call('publish', KEYS[6], syncMsg); "
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(), getUpdatedChannelName(),
getRemovedSyncChannelName(), getUpdatedSyncChannelName(), getOldValueListenerCounter()),
0, updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue), syncId);
List result = Arrays.asList(syncs, syncId);
waitSync(result);
return res;
} else if (res == 0) {
return res;
}
Long accessTimeout = getAccessTimeout();
if (accessTimeout == -1) {
return -1;
}
double syncId = ThreadLocalRandom.current().nextDouble();
List result = evalWrite(getRawName(), codec, RedisCommands.EVAL_LIST,
"if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(value), value); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[4]), ARGV[4], string.len(value), value, ARGV[7]); "
+ "local syncs = redis.call('publish', KEYS[4], syncMsg); "
+ "return {-1, syncs}; "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "return {0};"
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(), getRemovedSyncChannelName()),
accessTimeout, 0, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue), syncId);
result.add(syncId);
waitSync(result);
return (Long) result.get(0);
}
RFuture replaceValue(K key, V oldValue, V newValue) {
Long accessTimeout = getAccessTimeout();
Long updateTimeout = getUpdateTimeout();
String name = getRawName(key);
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_LONG,
"local value = redis.call('hget', KEYS[1], ARGV[4]); "
+ "if value == false then "
+ "return 0; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[4]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[3]) then "
+ "return 0; "
+ "end; "
+ "if ARGV[5] == value then "
+ "if ARGV[2] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "elseif ARGV[2] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[6]); "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[4]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[5])); "
+ "local msg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], -1); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], string.len(tostring(value)), tostring(value)); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[4], ARGV[6]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[5])); "
+ "local msg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], -1); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(ARGV[6]), ARGV[6], string.len(tostring(value)), tostring(value)); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "end; "
+ "return 1;"
+ "end; "
+ "if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[4]); "
+ "redis.call('zrem', KEYS[2], ARGV[4]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[4]), ARGV[4], string.len(value), value); "
+ "redis.call('publish', KEYS[3], msg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
+ "return 0;"
+ "end; "
+ "return -1; ",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getUpdatedChannelName(name), getOldValueListenerCounter(name)),
accessTimeout, updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue));
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
RFuture future = replaceAsync(key, oldValue, newValue);
return sync(future);
}
@Override
public RFuture replaceAsync(K key, V oldValue, V newValue) {
checkNotClosed();
checkKey(key);
if (oldValue == null) {
throw new NullPointerException();
}
if (newValue == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
RLock lock = getLockedLock(key);
CompletableFuture result = new CompletableFuture<>();
try {
RFuture future;
if (atomicExecution) {
future = replaceValue(key, oldValue, newValue);
} else {
long res = replaceValueLocked(key, oldValue, newValue);
future = new CompletableFutureWrapper<>(res);
}
future.whenComplete((res, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
if (res == 1) {
if (config.isWriteThrough()) {
commandExecutor.getServiceManager().getExecutor().submit(() -> {
try {
cacheWriter.write(new JCacheEntry(key, newValue));
} catch (Exception e) {
removeValues(key);
handleException(result, e);
return;
}
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(true);
});
} else {
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(true);
}
} else {
if (res == 0) {
cacheManager.getStatBean(this).addMisses(1);
} else {
cacheManager.getStatBean(this).addHits(1);
}
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(false);
}
});
} finally {
lock.unlock();
}
return new CompletableFutureWrapper<>(result);
}
private boolean replaceValueLocked(K key, V value) {
if (containsKey(key)) {
double syncId = ThreadLocalRandom.current().nextDouble();
Long updateTimeout = getUpdateTimeout();
Long syncs = evalWrite(getRawName(), codec, RedisCommands.EVAL_LONG,
"if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value), ARGV[5]); "
+ "return redis.call('publish', KEYS[5], syncMsg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[7])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1, ARGV[5]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[5]); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "return redis.call('publish', KEYS[6], syncMsg); "
+ "else "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[7])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1, ARGV[5]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[5]); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "return redis.call('publish', KEYS[6], syncMsg); "
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(), getUpdatedChannelName(),
getRemovedSyncChannelName(), getUpdatedSyncChannelName(), getOldValueListenerCounter()),
updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
List result = Arrays.asList(syncs, syncId);
waitSync(result);
return true;
}
return false;
}
RFuture replaceValue(K key, V value) {
Long updateTimeout = getUpdateTimeout();
String name = getRawName(key);
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return 0; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return 0; "
+ "end; "
+ "if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[5])); "
+ "local msg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[5])); "
+ "local msg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "end; "
+ "return 1;",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getUpdatedChannelName(name), getOldValueListenerCounter(name)),
updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
}
RFuture getAndReplaceValue(K key, V value) {
Long updateTimeout = getUpdateTimeout();
String name = getRawName(key);
return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return nil; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return nil; "
+ "end; "
+ "if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[5])); "
+ "local msg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "else "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[5])); "
+ "local msg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "else "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "end; "
+ "return value;",
Arrays.asList(name, getTimeoutSetName(name), getRemovedChannelName(name), getUpdatedChannelName(name), getOldValueListenerCounter(name)),
updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
}
private V getAndReplaceValueLocked(K key, V value) {
V oldValue = evalWrite(getRawName(), codec, RedisCommands.EVAL_MAP_VALUE,
"local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "if value == false then "
+ "return nil; "
+ "end; "
+ "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[2]) then "
+ "return nil; "
+ "end; "
+ "return value;", Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(), getUpdatedChannelName()),
0, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));
if (oldValue != null) {
Long updateTimeout = getUpdateTimeout();
double syncId = ThreadLocalRandom.current().nextDouble();
Long syncs = evalWrite(getRawName(), codec, RedisCommands.EVAL_LONG,
"if ARGV[1] == '0' then "
+ "redis.call('hdel', KEYS[1], ARGV[3]); "
+ "redis.call('zrem', KEYS[2], ARGV[3]); "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "local msg = struct.pack('Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value)); "
+ "redis.call('publish', KEYS[3], msg); "
+ "local syncMsg = struct.pack('Lc0Lc0d', string.len(ARGV[3]), ARGV[3], string.len(tostring(value)), tostring(value), ARGV[5]); "
+ "return redis.call('publish', KEYS[5], msg); "
+ "elseif ARGV[1] ~= '-1' then "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[7])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1, ARGV[5]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[5]); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "return redis.call('publish', KEYS[6], syncMsg); "
+ "else "
+ "local oldValueRequired = tonumber(redis.call('get', KEYS[7])); "
+ "local msg, syncMsg; "
+ "if oldValueRequired == nil or oldValueRequired < 1 then "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0h', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1); "
+ "syncMsg = struct.pack('Lc0Lc0hd', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], -1, ARGV[5]); "
+ "else "
+ "local value = redis.call('hget', KEYS[1], ARGV[3]); "
+ "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); "
+ "msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value)); "
+ "syncMsg = struct.pack('Lc0Lc0Lc0d', string.len(ARGV[3]), ARGV[3], string.len(ARGV[4]), ARGV[4], string.len(tostring(value)), tostring(value), ARGV[5]); "
+ "end; "
+ "redis.call('publish', KEYS[4], msg); "
+ "return redis.call('publish', KEYS[6], syncMsg); "
+ "end; ",
Arrays.asList(getRawName(), getTimeoutSetName(), getRemovedChannelName(), getUpdatedChannelName(),
getRemovedSyncChannelName(), getUpdatedSyncChannelName(), getOldValueListenerCounter()),
updateTimeout, System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), syncId);
List result = Arrays.asList(syncs, syncId);
waitSync(result);
}
return oldValue;
}
private void incrementOldValueListenerCounter(String counterName) {
evalWrite(getRawName(), codec, RedisCommands.EVAL_INTEGER,
"return redis.call('incr', KEYS[1]);",
Arrays.asList(counterName));
}
private void decrementOldValueListenerCounter(String counterName) {
evalWrite(getRawName(), codec, RedisCommands.EVAL_INTEGER,
"return redis.call('decr', KEYS[1]);",
Arrays.asList(counterName));
}
private Integer getOldValueListenerCount(String counterName) {
return evalWrite(getRawName(), codec, RedisCommands.EVAL_INTEGER,
"return tonumber(redis.call('get', KEYS[1]));",
Arrays.asList(counterName));
}
@Override
public boolean replace(K key, V value) {
RFuture future = replaceAsync(key, value);
return sync(future);
}
@Override
public RFuture replaceAsync(K key, V value) {
checkNotClosed();
checkKey(key);
if (value == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
CompletableFuture result = new CompletableFuture<>();
RLock lock = getLockedLock(key);
try {
RFuture future;
if (atomicExecution) {
future = replaceValue(key, value);
} else {
boolean res = replaceValueLocked(key, value);
future = new CompletableFutureWrapper<>(res);
}
future.whenComplete((r, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
if (r) {
if (config.isWriteThrough()) {
commandExecutor.getServiceManager().getExecutor().submit(() -> {
try {
cacheWriter.write(new JCacheEntry(key, value));
} catch (Exception e) {
removeValues(key);
handleException(result, e);
}
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addPuts(1);
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(r);
});
return;
}
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addPuts(1);
} else {
cacheManager.getStatBean(this).addMisses(1);
}
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
result.complete(r);
});
} finally {
lock.unlock();
}
return new CompletableFutureWrapper<>(result);
}
@Override
public V getAndReplace(K key, V value) {
RFuture future = getAndReplaceAsync(key, value);
return sync(future);
}
@Override
public RFuture getAndReplaceAsync(K key, V value) {
checkNotClosed();
checkKey(key);
if (value == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
CompletableFuture result = new CompletableFuture<>();
RLock lock = getLockedLock(key);
try {
RFuture future;
if (atomicExecution) {
future = getAndReplaceValue(key, value);
} else {
V res = getAndReplaceValueLocked(key, value);
future = new CompletableFutureWrapper<>(res);
}
future.whenComplete((r, ex) -> {
if (ex != null) {
result.completeExceptionally(new CacheException(ex));
return;
}
if (r != null) {
if (config.isWriteThrough()) {
commandExecutor.getServiceManager().getExecutor().submit(() -> {
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addPuts(1);
try {
cacheWriter.write(new JCacheEntry(key, value));
} catch (Exception e) {
removeValues(key);
handleException(result, e);
return;
}
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
result.complete(r);
});
return;
}
cacheManager.getStatBean(this).addHits(1);
cacheManager.getStatBean(this).addPuts(1);
} else {
cacheManager.getStatBean(this).addMisses(1);
}
cacheManager.getStatBean(this).addPutTime(currentNanoTime() - startTime);
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
result.complete(r);
});
} finally {
lock.unlock();
}
return new CompletableFutureWrapper<>(result);
}
@Override
public void removeAll(Set extends K> keys) {
RFuture future = removeAllAsync(keys);
sync(future);
}
@Override
public RFuture removeAllAsync(Set extends K> keys) {
checkNotClosed();
for (K key : keys) {
checkKey(key);
}
long startTime = currentNanoTime();
if (config.isWriteThrough()) {
CompletionStage> future = getAndRemoveValues((Set) keys);
CompletionStage f = future.handle((r, ex) -> {
try {
cacheWriter.deleteAll(r.keySet());
} catch (Exception e) {
putAllValues(r);
if (e instanceof CacheWriterException) {
throw new CompletionException(e);
}
throw new CompletionException(new CacheWriterException(e));
}
cacheManager.getStatBean(this).addRemovals(r.size());
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
return null;
});
return new CompletableFutureWrapper<>(f);
}
RFuture future = removeValues(keys.toArray());
CompletionStage f = future.thenAccept(res -> {
cacheManager.getStatBean(this).addRemovals(res);
cacheManager.getStatBean(this).addRemoveTime(currentNanoTime() - startTime);
});
f = handleException(f);
return new CompletableFutureWrapper<>(f);
}
MapScanResult scanIterator(String name, RedisClient client, String startPos) {
RFuture> f
= commandExecutor.readAsync(client, name, codec, RedisCommands.HSCAN, name, startPos, "COUNT", 50);
try {
return get(f);
} catch (Exception e) {
throw new CacheException(e);
}
}
Iterator keyIterator() {
return new RedissonBaseMapIterator() {
@Override
protected K getValue(Map.Entry entry) {
return (K) entry.getKey();
}
@Override
protected void remove(Map.Entry value) {
throw new UnsupportedOperationException();
}
@Override
protected Object put(Map.Entry entry, Object value) {
throw new UnsupportedOperationException();
}
@Override
protected ScanResult> iterator(RedisClient client,
String nextIterPos) {
return JCache.this.scanIterator(JCache.this.getRawName(), client, nextIterPos);
}
};
}
@Override
public void removeAll() {
checkNotClosed();
Set keys = new HashSet<>();
for (Iterator iterator = keyIterator(); iterator.hasNext();) {
K key = iterator.next();
keys.add(key);
if (keys.size() == 50) {
removeAll(keys);
keys.clear();
}
}
if (!keys.isEmpty()) {
removeAll(keys);
}
}
@Override
public void clear() {
RFuture future = clearAsync();
sync(future);
}
@Override
public RFuture clearAsync() {
return clearAsync(commandExecutor, null, getRawName());
}
RFuture clearAsync(CommandAsyncExecutor commandExecutor, MasterSlaveEntry entry, String name) {
checkNotClosed();
if (entry == null) {
return commandExecutor.writeAsync(name, StringCodec.INSTANCE, RedisCommands.DEL_VOID, name, getTimeoutSetName(name));
}
return commandExecutor.writeAsync(entry, StringCodec.INSTANCE, RedisCommands.DEL_VOID, name, getTimeoutSetName(name));
}
@Override
public > C getConfiguration(Class clazz) {
if (clazz.isInstance(config)) {
return clazz.cast(config);
}
throw new IllegalArgumentException("Configuration object is not an instance of " + clazz);
}
@Override
public T invoke(K key, EntryProcessor entryProcessor, Object... arguments)
throws EntryProcessorException {
checkNotClosed();
checkKey(key);
if (entryProcessor == null) {
throw new NullPointerException();
}
long startTime = currentNanoTime();
if (containsKey(key)) {
cacheManager.getStatBean(this).addHits(1);
} else {
cacheManager.getStatBean(this).addMisses(1);
}
cacheManager.getStatBean(this).addGetTime(currentNanoTime() - startTime);
JMutableEntry entry = new JMutableEntry(this, key, null, config.isReadThrough());
RLock lock = getLockedLock(key);
try {
T result = entryProcessor.process(entry, arguments);
if (entry.getAction() == Action.CREATED
|| entry.getAction() == Action.UPDATED) {
put(key, entry.value());
}
if (entry.getAction() == Action.DELETED) {
remove(key);
}
return result;
} catch (EntryProcessorException e) {
throw e;
} catch (Exception e) {
throw new EntryProcessorException(e);
} finally {
lock.unlock();
}
}
@Override
public Map> invokeAll(Set extends K> keys, EntryProcessor entryProcessor,
Object... arguments) {
checkNotClosed();
if (entryProcessor == null) {
throw new NullPointerException();
}
Map> results = new HashMap>();
for (K key : keys) {
try {
final T result = invoke(key, entryProcessor, arguments);
if (result != null) {
results.put(key, new EntryProcessorResult() {
@Override
public T get() throws EntryProcessorException {
return result;
}
});
}
} catch (final EntryProcessorException e) {
results.put(key, new EntryProcessorResult() {
@Override
public T get() throws EntryProcessorException {
throw e;
}
});
}
}
return results;
}
@Override
public CacheManager getCacheManager() {
checkNotClosed();
return cacheManager;
}
URI getURI() {
return cacheManager.getURI();
}
@Override
public void close() {
if (isClosed()) {
return;
}
if (closed.compareAndSet(false, true)) {
if (hasOwnRedisson) {
redisson.shutdown();
}
cacheManager.closeCache(this);
for (CacheEntryListenerConfiguration config : listeners.keySet()) {
deregisterCacheEntryListener(config);
}
redisson.getEvictionScheduler().remove(getRawName());
}
}
@Override
public boolean isClosed() {
return closed.get();
}
@Override
public T unwrap(Class clazz) {
if (clazz.isAssignableFrom(getClass())) {
return clazz.cast(this);
}
if (clazz == CacheAsync.class) {
return (T) this;
}
if (clazz == CacheReactive.class) {
return (T) ReactiveProxyBuilder.create(((RedissonReactive) redisson.reactive()).getCommandExecutor(), this, CacheReactive.class);
}
if (clazz == CacheRx.class) {
return (T) RxProxyBuilder.create(((RedissonRx) redisson.rxJava()).getCommandExecutor(), this, CacheRx.class);
}
return null;
}
@Override
public void registerCacheEntryListener(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) {
registerCacheEntryListener(cacheEntryListenerConfiguration, true);
}
private JCacheEventCodec.OSType osType;
private void registerCacheEntryListener(CacheEntryListenerConfiguration cacheEntryListenerConfiguration, boolean addToConfig) {
if (osType == null) {
RFuture> serverFuture = commandExecutor.readAsync((String) null, StringCodec.INSTANCE, RedisCommands.INFO_SERVER);
String os = serverFuture.toCompletableFuture().join().get("os");
if (os.contains("Windows")) {
osType = BaseEventCodec.OSType.WINDOWS;
} else if (os.contains("NONSTOP")) {
osType = BaseEventCodec.OSType.HPNONSTOP;
}
}
Factory> factory = cacheEntryListenerConfiguration.getCacheEntryListenerFactory();
final CacheEntryListener super K, ? super V> listener = factory.create();
Factory> filterFactory = cacheEntryListenerConfiguration.getCacheEntryEventFilterFactory();
final CacheEntryEventFilter super K, ? super V> filter;
if (filterFactory != null) {
filter = filterFactory.create();
} else {
filter = null;
}
Map