From c9cef4f27cca2077151911f820012d9b969e4821 Mon Sep 17 00:00:00 2001 From: atakavci Date: Thu, 18 Jul 2024 16:17:18 +0300 Subject: [PATCH 01/15] adding a DataProvider to access connection from cache --- .../java/redis/clients/jedis/Connection.java | 40 ++++++++++++-- .../java/redis/clients/jedis/Protocol.java | 15 ++++-- .../redis/clients/jedis/UnifiedJedis.java | 4 -- .../clients/jedis/csc/CacheConnection.java | 29 ++++++++-- .../redis/clients/jedis/csc/CacheEntry.java | 8 +-- .../clients/jedis/csc/ClientSideCache.java | 54 ++++++++++++------- .../redis/clients/jedis/csc/DataProvider.java | 15 ++++++ .../clients/jedis/util/RedisInputStream.java | 20 +++++++ 8 files changed, 146 insertions(+), 39 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/DataProvider.java diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index c7c5f439d4..6c7e9bd194 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.function.Supplier; +import javax.net.ssl.SSLSocket; + import redis.clients.jedis.Protocol.Command; import redis.clients.jedis.Protocol.Keyword; import redis.clients.jedis.annots.Experimental; @@ -167,7 +169,8 @@ public void sendCommand(final CommandArguments args) { Protocol.sendCommand(outputStream, args); } catch (JedisConnectionException ex) { /* - * When client send request which formed by invalid protocol, Redis send back error message + * When client send request which formed by invalid protocol, Redis send back + * error message * before close connection. We try to read it to provide reason of failure. */ try { @@ -177,8 +180,10 @@ public void sendCommand(final CommandArguments args) { } } catch (Exception e) { /* - * Catch any IOException or JedisConnectionException occurred from InputStream#read and just - * ignore. This approach is safe because reading error message is optional and connection + * Catch any IOException or JedisConnectionException occurred from + * InputStream#read and just + * ignore. This approach is safe because reading error message is optional and + * connection * will eventually be closed. */ } @@ -192,7 +197,7 @@ public void connect() throws JedisConnectionException { if (!isConnected()) { try { socket = socketFactory.createSocket(); - soTimeout = socket.getSoTimeout(); //? + soTimeout = socket.getSoTimeout(); // ? outputStream = new RedisOutputStream(socket.getOutputStream()); inputStream = new RedisInputStream(socket.getInputStream()); @@ -367,6 +372,29 @@ protected Object readProtocolWithCheckingBroken() { } } + protected void readPushesWithCheckingBroken() { + if (broken) { + throw new JedisConnectionException("Attempting to read from a broken connection."); + } + + try { + if (socket instanceof SSLSocket) { + this.ping(); + } else { + try { + if (inputStream.available() > 0) { + protocolReadPushes(inputStream); + } + } catch (IOException e) { + // TODO: handle this properly + } + } + } catch (JedisConnectionException exc) { + broken = true; + throw exc; + } + } + public List getMany(final int count) { flush(); final List responses = new ArrayList<>(count); @@ -382,6 +410,7 @@ public List getMany(final int count) { /** * Check if the client name libname, libver, characters are legal + * * @param info the name * @return Returns true if legal, false throws exception * @throws JedisException if characters illegal @@ -425,7 +454,8 @@ private void initializeFromClientConfig(final JedisClientConfig config) { } ClientSetInfoConfig setInfoConfig = config.getClientSetInfoConfig(); - if (setInfoConfig == null) setInfoConfig = ClientSetInfoConfig.DEFAULT; + if (setInfoConfig == null) + setInfoConfig = ClientSetInfoConfig.DEFAULT; if (!setInfoConfig.isDisabled()) { String libName = JedisMetaInfo.getArtifactId(); diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index c025e37a4a..6777a2f1b0 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -126,7 +126,7 @@ private static String[] parseTargetHostAndSlot(String clusterRedirectResponse) { private static Object process(final RedisInputStream is) { final byte b = is.readByte(); - //System.out.println("BYTE: " + (char) b); + // System.out.println("BYTE: " + (char) b); switch (b) { case PLUS_BYTE: return is.readLineBytes(); @@ -185,7 +185,8 @@ private static byte[] processBulkReply(final RedisInputStream is) { private static List processMultiBulkReply(final RedisInputStream is) { final int num = is.readIntCrLf(); - if (num == -1) return null; + if (num == -1) + return null; final List ret = new ArrayList<>(num); for (int i = 0; i < num; i++) { try { @@ -199,7 +200,8 @@ private static List processMultiBulkReply(final RedisInputStream is) { private static List processMapKeyValueReply(final RedisInputStream is) { final int num = is.readIntCrLf(); - if (num == -1) return null; + if (num == -1) + return null; final List ret = new ArrayList<>(num); for (int i = 0; i < num; i++) { ret.add(new KeyValue(process(is), process(is))); @@ -213,7 +215,12 @@ public static Object read(final RedisInputStream is) { @Experimental public static void readPushes(final RedisInputStream is, final ClientSideCache cache) { - while (is.peek(GREATER_THAN_BYTE)) { + boolean available = false; + + int counter = 0; + // TODO : we need to find away to get away from peekSafe + while (is.peekSafe(GREATER_THAN_BYTE)) { + counter++; is.readByte(); processPush(is, cache); } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 3a2dd9ab3e..714e4da9df 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -309,10 +309,6 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co } private T checkAndClientSideCacheCommand(CommandObject command, Object... keys) { - if (clientSideCache != null) { - return clientSideCache.get((cmd) -> executeCommand(cmd), command, keys); - } - return executeCommand(command); } diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 0573cc1093..537cc1068a 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -2,6 +2,7 @@ import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; +import redis.clients.jedis.CommandObject; import redis.clients.jedis.Connection; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisSocketFactory; @@ -10,7 +11,7 @@ import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.util.RedisInputStream; -public class CacheConnection extends Connection { +public class CacheConnection extends Connection implements DataProvider { private final ClientSideCache clientSideCache; private final ReentrantLock lock; @@ -47,15 +48,22 @@ protected Object protocolRead(RedisInputStream inputStream) { protected void protocolReadPushes(RedisInputStream inputStream) { if (lock != null && lock.tryLock()) { try { - //super.setSoTimeout(1); + // super.setSoTimeout(1); Protocol.readPushes(inputStream, clientSideCache); } finally { - //super.rollbackTimeout(); + // super.rollbackTimeout(); lock.unlock(); } } } + @Override + public T executeCommand(final CommandObject commandObject) { + System.out.println("this is the interceptor for cache"); + T data = clientSideCache.get(this, commandObject); + return data; + } + private void initializeClientSideCache() { sendCommand(Protocol.Command.CLIENT, "TRACKING", "ON"); String reply = getStatusCodeReply(); @@ -63,4 +71,19 @@ private void initializeClientSideCache() { throw new JedisException("Could not enable client tracking. Reply: " + reply); } } + + @Override + public Connection getSource() { + return this; + } + + @Override + public T getData(CommandObject commandObject) { + return super.executeCommand(commandObject); + } + + @Override + public void consumeInvalidationMessages() { + this.readPushesWithCheckingBroken(); + } } diff --git a/src/main/java/redis/clients/jedis/csc/CacheEntry.java b/src/main/java/redis/clients/jedis/csc/CacheEntry.java index f2a3243cf4..358bf36391 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheEntry.java +++ b/src/main/java/redis/clients/jedis/csc/CacheEntry.java @@ -1,5 +1,7 @@ package redis.clients.jedis.csc; +import java.lang.ref.WeakReference; + import redis.clients.jedis.Connection; import redis.clients.jedis.annots.Internal; @@ -8,9 +10,9 @@ public class CacheEntry { private final CacheKey cacheKey; private final T value; - private final Connection connection; + private final WeakReference connection; - public CacheEntry(CacheKey cacheKey, T value, Connection connection) { + public CacheEntry(CacheKey cacheKey, T value, WeakReference connection) { this.cacheKey = cacheKey; this.value = value; this.connection = connection; @@ -24,7 +26,7 @@ public T getValue() { return value; } - public Connection getConnection() { + public WeakReference getConnection() { return connection; } } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 94a235873d..65b8cb152e 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -1,21 +1,25 @@ package redis.clients.jedis.csc; +import java.lang.ref.WeakReference; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - import redis.clients.jedis.CommandObject; +import redis.clients.jedis.Connection; import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.util.SafeEncoder; /** - * The class to manage the client-side caching. User can provide any of implementation of this class to the client - * object; e.g. {@link redis.clients.jedis.csc.CaffeineClientSideCache CaffeineClientSideCache} or - * {@link redis.clients.jedis.csc.GuavaClientSideCache GuavaClientSideCache} or a custom implementation of their own. + * The class to manage the client-side caching. User can provide any of + * implementation of this class to the client + * object; e.g. {@link redis.clients.jedis.csc.CaffeineClientSideCache + * CaffeineClientSideCache} or + * {@link redis.clients.jedis.csc.GuavaClientSideCache GuavaClientSideCache} or + * a custom implementation of their own. */ @Experimental public abstract class ClientSideCache { @@ -64,12 +68,14 @@ private void invalidateAllRedisKeysAndCacheEntries() { } private void invalidateRedisKeyAndRespectiveCacheEntries(Object key) { -// if (!(key instanceof byte[])) { -// // This should be called internally. That's why throwing AssertionError instead of IllegalArgumentException. -// throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); -// } -// -// final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key); + // if (!(key instanceof byte[])) { + // // This should be called internally. That's why throwing AssertionError + // instead of IllegalArgumentException. + // throw new AssertionError("" + key.getClass().getSimpleName() + " is not + // supported. Value: " + String.valueOf(key)); + // } + // + // final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key); final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); Set> commands = redisKeysToCacheKeys.get(mapKey); @@ -79,26 +85,34 @@ private void invalidateRedisKeyAndRespectiveCacheEntries(Object key) { } } - public final T get(Function, T> loader, CommandObject command, Object... keys) { + public final T get(DataProvider dataProvider, CommandObject command, Object... keys) { if (!cacheable.isCacheable(command.getArguments().getCommand(), keys)) { - return loader.apply(command); + return dataProvider.getData(command); } final CacheKey cacheKey = new CacheKey(command); CacheEntry cacheEntry = get(cacheKey); if (cacheEntry != null) { - // CACHE HIT!!! - // TODO: connection ... - //cacheEntry.getConnection().ping(); - //cacheEntry = get(cacheKey); // get cache again - return (T) cacheEntry.getValue(); + Connection cacheOwner = (Connection) cacheEntry.getConnection().get(); + if (cacheOwner == null) { + remove(Collections.singleton(cacheKey)); + // TODO: remove it from redisKeysToCacheKeys as well + } else { + if (cacheOwner == dataProvider.getSource()) { + dataProvider.consumeInvalidationMessages(); + } + cacheEntry = get(cacheKey); + if (cacheEntry != null) { + return (T) cacheEntry.getValue(); + } + } } // CACHE MISS!! - T value = loader.apply(command); + T value = dataProvider.getData(command); if (value != null) { - cacheEntry = new CacheEntry(cacheKey, value, /*connection*/ null); // TODO: connection + cacheEntry = new CacheEntry(cacheKey, value, new WeakReference(dataProvider.getSource())); put(cacheKey, cacheEntry); for (Object key : keys) { ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); diff --git a/src/main/java/redis/clients/jedis/csc/DataProvider.java b/src/main/java/redis/clients/jedis/csc/DataProvider.java new file mode 100644 index 0000000000..c00dd6c1b7 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/DataProvider.java @@ -0,0 +1,15 @@ +package redis.clients.jedis.csc; + +import java.lang.ref.WeakReference; + +import redis.clients.jedis.CommandObject; +import redis.clients.jedis.Connection; + +public interface DataProvider { + + public Connection getSource(); + + public T getData(CommandObject commandObject); + + public void consumeInvalidationMessages(); +} diff --git a/src/main/java/redis/clients/jedis/util/RedisInputStream.java b/src/main/java/redis/clients/jedis/util/RedisInputStream.java index 0226961028..70952049a7 100644 --- a/src/main/java/redis/clients/jedis/util/RedisInputStream.java +++ b/src/main/java/redis/clients/jedis/util/RedisInputStream.java @@ -51,6 +51,12 @@ public boolean peek(byte b) throws JedisConnectionException { return buf[count] == b; } + @Experimental + public boolean peekSafe(byte b) throws JedisConnectionException { + ensureFillSafe(); // in current design, at least one reply is expected. so ensureFillSafe() is not necessary. + return buf[count] == b; + } + public byte readByte() throws JedisConnectionException { ensureFill(); return buf[count++]; @@ -260,4 +266,18 @@ private void ensureFill() throws JedisConnectionException { } } } + + private void ensureFillSafe() { + if (count >= limit) { + try { + limit = in.read(buf); + count = 0; + if (limit == -1) { + throw new JedisConnectionException("Unexpected end of stream."); + } + } catch (IOException e) { + // do nothing + } + } + } } From 4bb55fc11967cf2b6b7e455e3416ca95fcdf7a46 Mon Sep 17 00:00:00 2001 From: atakavci Date: Fri, 19 Jul 2024 09:30:07 +0300 Subject: [PATCH 02/15] resolve keys from commandarguments --- .../java/redis/clients/jedis/CommandArguments.java | 10 ++++++++-- .../java/redis/clients/jedis/csc/CacheConnection.java | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index c630ae76de..0925cf3165 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -14,6 +14,7 @@ public class CommandArguments implements Iterable { private final ArrayList args; + private final ArrayList keys; private boolean blocking; @@ -24,6 +25,7 @@ private CommandArguments() { public CommandArguments(ProtocolCommand command) { args = new ArrayList<>(); args.add(command); + keys = new ArrayList<>(); } public ProtocolCommand getCommand() { @@ -134,7 +136,7 @@ public final CommandArguments addParams(IParams params) { } protected CommandArguments processKey(byte[] key) { - // do nothing + keys.add(key); return this; } @@ -146,7 +148,7 @@ protected final CommandArguments processKeys(byte[]... keys) { } protected CommandArguments processKey(String key) { - // do nothing + keys.add(key); return this; } @@ -166,6 +168,10 @@ public Iterator iterator() { return args.iterator(); } + public Object[] keys() { + return keys.toArray(); + } + public boolean isBlocking() { return blocking; } diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 537cc1068a..a0bf703b7d 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -60,7 +60,7 @@ protected void protocolReadPushes(RedisInputStream inputStream) { @Override public T executeCommand(final CommandObject commandObject) { System.out.println("this is the interceptor for cache"); - T data = clientSideCache.get(this, commandObject); + T data = clientSideCache.get(this, commandObject, commandObject.getArguments().keys()); return data; } From 077a2d0169b768c35e06d2aa79bbd3337fb9e359 Mon Sep 17 00:00:00 2001 From: atakavci Date: Fri, 19 Jul 2024 15:38:39 +0300 Subject: [PATCH 03/15] clean up in unifiiedjedis and add csc test with ssl --- .../redis/clients/jedis/UnifiedJedis.java | 615 ++++++++++-------- .../clients/jedis/csc/CacheConnection.java | 1 - .../csc/JedisPooledClientSideCacheTest.java | 58 +- 3 files changed, 378 insertions(+), 296 deletions(-) diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 714e4da9df..5bf6798f7b 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -51,8 +51,8 @@ public class UnifiedJedis implements JedisCommands, JedisBinaryCommands, SampleKeyedCommands, SampleBinaryKeyedCommands, RedisModuleCommands, AutoCloseable { - @Deprecated protected RedisProtocol protocol = null; - private final ClientSideCache clientSideCache; + @Deprecated + protected RedisProtocol protocol = null; protected final ConnectionProvider provider; protected final CommandExecutor executor; protected final CommandObjects commandObjects; @@ -96,7 +96,8 @@ public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) { @Experimental public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { - this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(), clientSideCache); + this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(), + clientSideCache); } public UnifiedJedis(ConnectionProvider provider) { @@ -115,7 +116,8 @@ protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, Clie /** * The constructor to directly use a custom {@link JedisSocketFactory}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be + * occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(JedisSocketFactory socketFactory) { @@ -125,7 +127,8 @@ public UnifiedJedis(JedisSocketFactory socketFactory) { /** * The constructor to directly use a custom {@link JedisSocketFactory}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be + * occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(JedisSocketFactory socketFactory, JedisClientConfig clientConfig) { @@ -135,7 +138,8 @@ public UnifiedJedis(JedisSocketFactory socketFactory, JedisClientConfig clientCo /** * The constructor to directly use a {@link Connection}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be + * occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(Connection connection) { @@ -143,14 +147,15 @@ public UnifiedJedis(Connection connection) { this.executor = new SimpleCommandExecutor(connection); this.commandObjects = new CommandObjects(); RedisProtocol proto = connection.getRedisProtocol(); - if (proto != null) this.commandObjects.setProtocol(proto); + if (proto != null) + this.commandObjects.setProtocol(proto); this.graphCommandObjects = new GraphCommandObjects(this); - this.clientSideCache = null; // TODO: } @Deprecated public UnifiedJedis(Set jedisClusterNodes, JedisClientConfig clientConfig, int maxAttempts) { - this(jedisClusterNodes, clientConfig, maxAttempts, Duration.ofMillis(maxAttempts * clientConfig.getSocketTimeoutMillis())); + this(jedisClusterNodes, clientConfig, maxAttempts, + Duration.ofMillis(maxAttempts * clientConfig.getSocketTimeoutMillis())); } @Deprecated @@ -199,7 +204,8 @@ public UnifiedJedis(ShardedConnectionProvider provider) { */ @Deprecated public UnifiedJedis(ShardedConnectionProvider provider, Pattern tagPattern) { - this(new DefaultCommandExecutor(provider), provider, new ShardedCommandObjects(provider.getHashingAlgo(), tagPattern)); + this(new DefaultCommandExecutor(provider), provider, + new ShardedCommandObjects(provider.getHashingAlgo(), tagPattern)); } public UnifiedJedis(ConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) { @@ -207,10 +213,13 @@ public UnifiedJedis(ConnectionProvider provider, int maxAttempts, Duration maxTo } /** - * Constructor which supports multiple cluster/database endpoints each with their own isolated connection pool. + * Constructor which supports multiple cluster/database endpoints each with + * their own isolated connection pool. *

- * With this Constructor users can seamlessly failover to Disaster Recovery (DR), Backup, and Active-Active cluster(s) - * by using simple configuration which is passed through from Resilience4j - https://resilience4j.readme.io/docs + * With this Constructor users can seamlessly failover to Disaster Recovery + * (DR), Backup, and Active-Active cluster(s) + * by using simple configuration which is passed through from Resilience4j - + * https://resilience4j.readme.io/docs *

*/ @Experimental @@ -221,7 +230,8 @@ public UnifiedJedis(MultiClusterPooledConnectionProvider provider) { /** * The constructor to use a custom {@link CommandExecutor}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be + * occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(CommandExecutor executor) { @@ -240,9 +250,11 @@ public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comma try (Connection conn = this.provider.getConnection()) { if (conn != null) { RedisProtocol proto = conn.getRedisProtocol(); - if (proto != null) this.commandObjects.setProtocol(proto); + if (proto != null) + this.commandObjects.setProtocol(proto); } - } catch (JedisException je) { } + } catch (JedisException je) { + } } } @@ -264,12 +276,12 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comm this.executor = executor; this.commandObjects = commandObjects; - if (protocol != null) this.commandObjects.setProtocol(protocol); + if (protocol != null) + this.commandObjects.setProtocol(protocol); this.graphCommandObjects = new GraphCommandObjects(this); this.graphCommandObjects.setBaseCommandArgumentsCreator((comm) -> this.commandObjects.commandArguments(comm)); - this.clientSideCache = clientSideCache; } @Override @@ -296,7 +308,8 @@ private T checkAndBroadcastCommand(CommandObject commandObject) { if (broadcastAndRoundRobinConfig == null) { } else if (commandObject.getArguments().getCommand() instanceof SearchProtocol.SearchCommand - && broadcastAndRoundRobinConfig.getRediSearchModeInCluster() == JedisBroadcastAndRoundRobinConfig.RediSearchMode.LIGHT) { + && broadcastAndRoundRobinConfig + .getRediSearchModeInCluster() == JedisBroadcastAndRoundRobinConfig.RediSearchMode.LIGHT) { broadcast = false; } @@ -308,10 +321,6 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co this.commandObjects.setBroadcastAndRoundRobinConfig(this.broadcastAndRoundRobinConfig); } - private T checkAndClientSideCacheCommand(CommandObject command, Object... keys) { - return executeCommand(command); - } - public String ping() { return checkAndBroadcastCommand(commandObjects.ping()); } @@ -331,12 +340,12 @@ public String configSet(String parameter, String value) { // Key commands @Override public boolean exists(String key) { - return checkAndClientSideCacheCommand(commandObjects.exists(key), key); + return executeCommand(commandObjects.exists(key)); } @Override public long exists(String... keys) { - return checkAndClientSideCacheCommand(commandObjects.exists(keys), (Object[]) keys); + return executeCommand(commandObjects.exists(keys)); } @Override @@ -346,17 +355,17 @@ public long persist(String key) { @Override public String type(String key) { - return checkAndClientSideCacheCommand(commandObjects.type(key), key); + return executeCommand(commandObjects.type(key)); } @Override public boolean exists(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.exists(key), key); + return executeCommand(commandObjects.exists(key)); } @Override public long exists(byte[]... keys) { - return checkAndClientSideCacheCommand(commandObjects.exists(keys), (Object[]) keys); + return executeCommand(commandObjects.exists(keys)); } @Override @@ -366,7 +375,7 @@ public long persist(byte[] key) { @Override public String type(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.type(key), key); + return executeCommand(commandObjects.type(key)); } @Override @@ -705,7 +714,7 @@ public ScanResult scan(String cursor, ScanParams params, String type) { /** * @param batchCount COUNT for each batch execution - * @param match pattern + * @param match pattern * @return scan iteration */ public ScanIteration scanIteration(int batchCount, String match) { @@ -714,8 +723,8 @@ public ScanIteration scanIteration(int batchCount, String match) { /** * @param batchCount COUNT for each batch execution - * @param match pattern - * @param type key type + * @param match pattern + * @param type key type * @return scan iteration */ public ScanIteration scanIteration(int batchCount, String match, String type) { @@ -766,7 +775,7 @@ public String set(String key, String value, SetParams params) { @Override public String get(String key) { - return checkAndClientSideCacheCommand(commandObjects.get(key), key); + return executeCommand(commandObjects.get(key)); } @Override @@ -801,7 +810,7 @@ public String set(byte[] key, byte[] value, SetParams params) { @Override public byte[] get(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.get(key), key); + return executeCommand(commandObjects.get(key)); } @Override @@ -831,7 +840,7 @@ public boolean setbit(String key, long offset, boolean value) { @Override public boolean getbit(String key, long offset) { - return checkAndClientSideCacheCommand(commandObjects.getbit(key, offset), key); + return executeCommand(commandObjects.getbit(key, offset)); } @Override @@ -841,7 +850,7 @@ public long setrange(String key, long offset, String value) { @Override public String getrange(String key, long startOffset, long endOffset) { - return checkAndClientSideCacheCommand(commandObjects.getrange(key, startOffset, endOffset), key); + return executeCommand(commandObjects.getrange(key, startOffset, endOffset)); } @Override @@ -851,7 +860,7 @@ public boolean setbit(byte[] key, long offset, boolean value) { @Override public boolean getbit(byte[] key, long offset) { - return checkAndClientSideCacheCommand(commandObjects.getbit(key, offset), key); + return executeCommand(commandObjects.getbit(key, offset)); } @Override @@ -861,11 +870,12 @@ public long setrange(byte[] key, long offset, byte[] value) { @Override public byte[] getrange(byte[] key, long startOffset, long endOffset) { - return checkAndClientSideCacheCommand(commandObjects.getrange(key, startOffset, endOffset), key); + return executeCommand(commandObjects.getrange(key, startOffset, endOffset)); } /** - * @deprecated Use {@link UnifiedJedis#setGet(java.lang.String, java.lang.String)}. + * @deprecated Use + * {@link UnifiedJedis#setGet(java.lang.String, java.lang.String)}. */ @Deprecated @Override @@ -964,7 +974,7 @@ public long decrBy(byte[] key, long decrement) { @Override public List mget(String... keys) { - return checkAndClientSideCacheCommand(commandObjects.mget(keys), (Object[]) keys); + return executeCommand(commandObjects.mget(keys)); } @Override @@ -979,7 +989,7 @@ public long msetnx(String... keysvalues) { @Override public List mget(byte[]... keys) { - return checkAndClientSideCacheCommand(commandObjects.mget(keys), (Object[]) keys); + return executeCommand(commandObjects.mget(keys)); } @Override @@ -999,12 +1009,12 @@ public long append(String key, String value) { @Override public String substr(String key, int start, int end) { - return checkAndClientSideCacheCommand(commandObjects.substr(key, start, end), key); + return executeCommand(commandObjects.substr(key, start, end)); } @Override public long strlen(String key) { - return checkAndClientSideCacheCommand(commandObjects.strlen(key), key); + return executeCommand(commandObjects.strlen(key)); } @Override @@ -1014,62 +1024,62 @@ public long append(byte[] key, byte[] value) { @Override public byte[] substr(byte[] key, int start, int end) { - return checkAndClientSideCacheCommand(commandObjects.substr(key, start, end), key); + return executeCommand(commandObjects.substr(key, start, end)); } @Override public long strlen(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.strlen(key), key); + return executeCommand(commandObjects.strlen(key)); } @Override public long bitcount(String key) { - return checkAndClientSideCacheCommand(commandObjects.bitcount(key), key); + return executeCommand(commandObjects.bitcount(key)); } @Override public long bitcount(String key, long start, long end) { - return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end), key); + return executeCommand(commandObjects.bitcount(key, start, end)); } @Override public long bitcount(String key, long start, long end, BitCountOption option) { - return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end, option), key); + return executeCommand(commandObjects.bitcount(key, start, end, option)); } @Override public long bitpos(String key, boolean value) { - return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value), key); + return executeCommand(commandObjects.bitpos(key, value)); } @Override public long bitpos(String key, boolean value, BitPosParams params) { - return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value, params), key); + return executeCommand(commandObjects.bitpos(key, value, params)); } @Override public long bitcount(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.bitcount(key), key); + return executeCommand(commandObjects.bitcount(key)); } @Override public long bitcount(byte[] key, long start, long end) { - return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end), key); + return executeCommand(commandObjects.bitcount(key, start, end)); } @Override public long bitcount(byte[] key, long start, long end, BitCountOption option) { - return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end, option), key); + return executeCommand(commandObjects.bitcount(key, start, end, option)); } @Override public long bitpos(byte[] key, boolean value) { - return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value), key); + return executeCommand(commandObjects.bitpos(key, value)); } @Override public long bitpos(byte[] key, boolean value, BitPosParams params) { - return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value, params), key); + return executeCommand(commandObjects.bitpos(key, value, params)); } @Override @@ -1079,7 +1089,7 @@ public List bitfield(String key, String... arguments) { @Override public List bitfieldReadonly(String key, String... arguments) { - return checkAndClientSideCacheCommand(commandObjects.bitfieldReadonly(key, arguments), key); + return executeCommand(commandObjects.bitfieldReadonly(key, arguments)); } @Override @@ -1089,7 +1099,7 @@ public List bitfield(byte[] key, byte[]... arguments) { @Override public List bitfieldReadonly(byte[] key, byte[]... arguments) { - return checkAndClientSideCacheCommand(commandObjects.bitfieldReadonly(key, arguments), key); + return executeCommand(commandObjects.bitfieldReadonly(key, arguments)); } @Override @@ -1104,12 +1114,12 @@ public long bitop(BitOP op, byte[] destKey, byte[]... srcKeys) { @Override public LCSMatchResult lcs(String keyA, String keyB, LCSParams params) { - return checkAndClientSideCacheCommand(commandObjects.lcs(keyA, keyB, params), keyA, keyB); + return executeCommand(commandObjects.lcs(keyA, keyB, params)); } @Override public LCSMatchResult lcs(byte[] keyA, byte[] keyB, LCSParams params) { - return checkAndClientSideCacheCommand(commandObjects.lcs(keyA, keyB, params), keyA, keyB); + return executeCommand(commandObjects.lcs(keyA, keyB, params)); } // String commands @@ -1126,12 +1136,12 @@ public long lpush(String key, String... string) { @Override public long llen(String key) { - return checkAndClientSideCacheCommand(commandObjects.llen(key), key); + return executeCommand(commandObjects.llen(key)); } @Override public List lrange(String key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.lrange(key, start, stop), key); + return executeCommand(commandObjects.lrange(key, start, stop)); } @Override @@ -1141,7 +1151,7 @@ public String ltrim(String key, long start, long stop) { @Override public String lindex(String key, long index) { - return checkAndClientSideCacheCommand(commandObjects.lindex(key, index), key); + return executeCommand(commandObjects.lindex(key, index)); } @Override @@ -1156,12 +1166,12 @@ public long lpush(byte[] key, byte[]... args) { @Override public long llen(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.llen(key), key); + return executeCommand(commandObjects.llen(key)); } @Override public List lrange(byte[] key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.lrange(key, start, stop), key); + return executeCommand(commandObjects.lrange(key, start, stop)); } @Override @@ -1171,7 +1181,7 @@ public String ltrim(byte[] key, long start, long stop) { @Override public byte[] lindex(byte[] key, long index) { - return checkAndClientSideCacheCommand(commandObjects.lindex(key, index), key); + return executeCommand(commandObjects.lindex(key, index)); } @Override @@ -1216,32 +1226,32 @@ public List lpop(byte[] key, int count) { @Override public Long lpos(String key, String element) { - return checkAndClientSideCacheCommand(commandObjects.lpos(key, element), key); + return executeCommand(commandObjects.lpos(key, element)); } @Override public Long lpos(String key, String element, LPosParams params) { - return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params), key); + return executeCommand(commandObjects.lpos(key, element, params)); } @Override public List lpos(String key, String element, LPosParams params, long count) { - return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params, count), key); + return executeCommand(commandObjects.lpos(key, element, params, count)); } @Override public Long lpos(byte[] key, byte[] element) { - return checkAndClientSideCacheCommand(commandObjects.lpos(key, element), key); + return executeCommand(commandObjects.lpos(key, element)); } @Override public Long lpos(byte[] key, byte[] element, LPosParams params) { - return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params), key); + return executeCommand(commandObjects.lpos(key, element, params)); } @Override public List lpos(byte[] key, byte[] element, LPosParams params, long count) { - return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params, count), key); + return executeCommand(commandObjects.lpos(key, element, params, count)); } @Override @@ -1448,7 +1458,7 @@ public long hset(String key, Map hash) { @Override public String hget(String key, String field) { - return checkAndClientSideCacheCommand(commandObjects.hget(key, field), key); + return executeCommand(commandObjects.hget(key, field)); } @Override @@ -1463,7 +1473,7 @@ public String hmset(String key, Map hash) { @Override public List hmget(String key, String... fields) { - return checkAndClientSideCacheCommand(commandObjects.hmget(key, fields), key); + return executeCommand(commandObjects.hmget(key, fields)); } @Override @@ -1478,7 +1488,7 @@ public long hset(byte[] key, Map hash) { @Override public byte[] hget(byte[] key, byte[] field) { - return checkAndClientSideCacheCommand(commandObjects.hget(key, field), key); + return executeCommand(commandObjects.hget(key, field)); } @Override @@ -1493,7 +1503,7 @@ public String hmset(byte[] key, Map hash) { @Override public List hmget(byte[] key, byte[]... fields) { - return checkAndClientSideCacheCommand(commandObjects.hmget(key, fields), key); + return executeCommand(commandObjects.hmget(key, fields)); } @Override @@ -1508,7 +1518,7 @@ public double hincrByFloat(String key, String field, double value) { @Override public boolean hexists(String key, String field) { - return checkAndClientSideCacheCommand(commandObjects.hexists(key, field), key); + return executeCommand(commandObjects.hexists(key, field)); } @Override @@ -1518,7 +1528,7 @@ public long hdel(String key, String... field) { @Override public long hlen(String key) { - return checkAndClientSideCacheCommand(commandObjects.hlen(key), key); + return executeCommand(commandObjects.hlen(key)); } @Override @@ -1533,7 +1543,7 @@ public double hincrByFloat(byte[] key, byte[] field, double value) { @Override public boolean hexists(byte[] key, byte[] field) { - return checkAndClientSideCacheCommand(commandObjects.hexists(key, field), key); + return executeCommand(commandObjects.hexists(key, field)); } @Override @@ -1543,37 +1553,37 @@ public long hdel(byte[] key, byte[]... field) { @Override public long hlen(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.hlen(key), key); + return executeCommand(commandObjects.hlen(key)); } @Override public Set hkeys(String key) { - return checkAndClientSideCacheCommand(commandObjects.hkeys(key), key); + return executeCommand(commandObjects.hkeys(key)); } @Override public List hvals(String key) { - return checkAndClientSideCacheCommand(commandObjects.hvals(key), key); + return executeCommand(commandObjects.hvals(key)); } @Override public Map hgetAll(String key) { - return checkAndClientSideCacheCommand(commandObjects.hgetAll(key), key); + return executeCommand(commandObjects.hgetAll(key)); } @Override public Set hkeys(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.hkeys(key), key); + return executeCommand(commandObjects.hkeys(key)); } @Override public List hvals(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.hvals(key), key); + return executeCommand(commandObjects.hvals(key)); } @Override public Map hgetAll(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.hgetAll(key), key); + return executeCommand(commandObjects.hgetAll(key)); } @Override @@ -1603,7 +1613,7 @@ public ScanResult hscanNoValues(String key, String cursor, ScanParams pa @Override public long hstrlen(String key, String field) { - return checkAndClientSideCacheCommand(commandObjects.hstrlen(key, field), key); + return executeCommand(commandObjects.hstrlen(key, field)); } @Override @@ -1633,7 +1643,7 @@ public ScanResult hscanNoValues(byte[] key, byte[] cursor, ScanParams pa @Override public long hstrlen(byte[] key, byte[] field) { - return checkAndClientSideCacheCommand(commandObjects.hstrlen(key, field), key); + return executeCommand(commandObjects.hstrlen(key, field)); } @Override @@ -1775,7 +1785,7 @@ public long sadd(String key, String... members) { @Override public Set smembers(String key) { - return checkAndClientSideCacheCommand(commandObjects.smembers(key), key); + return executeCommand(commandObjects.smembers(key)); } @Override @@ -1795,17 +1805,17 @@ public Set spop(String key, long count) { @Override public long scard(String key) { - return checkAndClientSideCacheCommand(commandObjects.scard(key), key); + return executeCommand(commandObjects.scard(key)); } @Override public boolean sismember(String key, String member) { - return checkAndClientSideCacheCommand(commandObjects.sismember(key, member), key); + return executeCommand(commandObjects.sismember(key, member)); } @Override public List smismember(String key, String... members) { - return checkAndClientSideCacheCommand(commandObjects.smismember(key, members), key); + return executeCommand(commandObjects.smismember(key, members)); } @Override @@ -1815,7 +1825,7 @@ public long sadd(byte[] key, byte[]... members) { @Override public Set smembers(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.smembers(key), key); + return executeCommand(commandObjects.smembers(key)); } @Override @@ -1835,17 +1845,17 @@ public Set spop(byte[] key, long count) { @Override public long scard(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.scard(key), key); + return executeCommand(commandObjects.scard(key)); } @Override public boolean sismember(byte[] key, byte[] member) { - return checkAndClientSideCacheCommand(commandObjects.sismember(key, member), key); + return executeCommand(commandObjects.sismember(key, member)); } @Override public List smismember(byte[] key, byte[]... members) { - return checkAndClientSideCacheCommand(commandObjects.smismember(key, members), key); + return executeCommand(commandObjects.smismember(key, members)); } @Override @@ -1880,7 +1890,7 @@ public ScanResult sscan(byte[] key, byte[] cursor, ScanParams params) { @Override public Set sdiff(String... keys) { - return checkAndClientSideCacheCommand(commandObjects.sdiff(keys), (Object[]) keys); + return executeCommand(commandObjects.sdiff(keys)); } @Override @@ -1890,7 +1900,7 @@ public long sdiffstore(String dstkey, String... keys) { @Override public Set sinter(String... keys) { - return checkAndClientSideCacheCommand(commandObjects.sinter(keys), (Object[]) keys); + return executeCommand(commandObjects.sinter(keys)); } @Override @@ -1910,7 +1920,7 @@ public long sintercard(int limit, String... keys) { @Override public Set sunion(String... keys) { - return checkAndClientSideCacheCommand(commandObjects.sunion(keys), (Object[]) keys); + return executeCommand(commandObjects.sunion(keys)); } @Override @@ -1925,7 +1935,7 @@ public long smove(String srckey, String dstkey, String member) { @Override public Set sdiff(byte[]... keys) { - return checkAndClientSideCacheCommand(commandObjects.sdiff(keys), (Object[]) keys); + return executeCommand(commandObjects.sdiff(keys)); } @Override @@ -1935,7 +1945,7 @@ public long sdiffstore(byte[] dstkey, byte[]... keys) { @Override public Set sinter(byte[]... keys) { - return checkAndClientSideCacheCommand(commandObjects.sinter(keys), (Object[]) keys); + return executeCommand(commandObjects.sinter(keys)); } @Override @@ -1955,7 +1965,7 @@ public long sintercard(int limit, byte[]... keys) { @Override public Set sunion(byte[]... keys) { - return checkAndClientSideCacheCommand(commandObjects.sunion(keys), (Object[]) keys); + return executeCommand(commandObjects.sunion(keys)); } @Override @@ -2037,22 +2047,22 @@ public Double zincrby(String key, double increment, String member, ZIncrByParams @Override public Long zrank(String key, String member) { - return checkAndClientSideCacheCommand(commandObjects.zrank(key, member), key); + return executeCommand(commandObjects.zrank(key, member)); } @Override public Long zrevrank(String key, String member) { - return checkAndClientSideCacheCommand(commandObjects.zrevrank(key, member), key); + return executeCommand(commandObjects.zrevrank(key, member)); } @Override public KeyValue zrankWithScore(String key, String member) { - return checkAndClientSideCacheCommand(commandObjects.zrankWithScore(key, member), key); + return executeCommand(commandObjects.zrankWithScore(key, member)); } @Override public KeyValue zrevrankWithScore(String key, String member) { - return checkAndClientSideCacheCommand(commandObjects.zrevrankWithScore(key, member), key); + return executeCommand(commandObjects.zrevrankWithScore(key, member)); } @Override @@ -2072,22 +2082,22 @@ public Double zincrby(byte[] key, double increment, byte[] member, ZIncrByParams @Override public Long zrank(byte[] key, byte[] member) { - return checkAndClientSideCacheCommand(commandObjects.zrank(key, member), key); + return executeCommand(commandObjects.zrank(key, member)); } @Override public Long zrevrank(byte[] key, byte[] member) { - return checkAndClientSideCacheCommand(commandObjects.zrevrank(key, member), key); + return executeCommand(commandObjects.zrevrank(key, member)); } @Override public KeyValue zrankWithScore(byte[] key, byte[] member) { - return checkAndClientSideCacheCommand(commandObjects.zrankWithScore(key, member), key); + return executeCommand(commandObjects.zrankWithScore(key, member)); } @Override public KeyValue zrevrankWithScore(byte[] key, byte[] member) { - return checkAndClientSideCacheCommand(commandObjects.zrevrankWithScore(key, member), key); + return executeCommand(commandObjects.zrevrankWithScore(key, member)); } @Override @@ -2107,17 +2117,17 @@ public List zrandmemberWithScores(String key, long count) { @Override public long zcard(String key) { - return checkAndClientSideCacheCommand(commandObjects.zcard(key), key); + return executeCommand(commandObjects.zcard(key)); } @Override public Double zscore(String key, String member) { - return checkAndClientSideCacheCommand(commandObjects.zscore(key, member), key); + return executeCommand(commandObjects.zscore(key, member)); } @Override public List zmscore(String key, String... members) { - return checkAndClientSideCacheCommand(commandObjects.zmscore(key, members), key); + return executeCommand(commandObjects.zmscore(key, members)); } @Override @@ -2137,17 +2147,17 @@ public List zrandmemberWithScores(byte[] key, long count) { @Override public long zcard(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.zcard(key), key); + return executeCommand(commandObjects.zcard(key)); } @Override public Double zscore(byte[] key, byte[] member) { - return checkAndClientSideCacheCommand(commandObjects.zscore(key, member), key); + return executeCommand(commandObjects.zscore(key, member)); } @Override public List zmscore(byte[] key, byte[]... members) { - return checkAndClientSideCacheCommand(commandObjects.zmscore(key, members), key); + return executeCommand(commandObjects.zmscore(key, members)); } @Override @@ -2172,12 +2182,12 @@ public List zpopmin(String key, int count) { @Override public long zcount(String key, double min, double max) { - return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); + return executeCommand(commandObjects.zcount(key, min, max)); } @Override public long zcount(String key, String min, String max) { - return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); + return executeCommand(commandObjects.zcount(key, min, max)); } @Override @@ -2202,42 +2212,42 @@ public List zpopmin(byte[] key, int count) { @Override public long zcount(byte[] key, double min, double max) { - return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); + return executeCommand(commandObjects.zcount(key, min, max)); } @Override public long zcount(byte[] key, byte[] min, byte[] max) { - return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); + return executeCommand(commandObjects.zcount(key, min, max)); } @Override public List zrange(String key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrange(key, start, stop), key); + return executeCommand(commandObjects.zrange(key, start, stop)); } @Override public List zrevrange(String key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrevrange(key, start, stop), key); + return executeCommand(commandObjects.zrevrange(key, start, stop)); } @Override public List zrangeWithScores(String key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, start, stop), key); + return executeCommand(commandObjects.zrangeWithScores(key, start, stop)); } @Override public List zrevrangeWithScores(String key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeWithScores(key, start, stop), key); + return executeCommand(commandObjects.zrevrangeWithScores(key, start, stop)); } @Override public List zrange(String key, ZRangeParams zRangeParams) { - return checkAndClientSideCacheCommand(commandObjects.zrange(key, zRangeParams), key); + return executeCommand(commandObjects.zrange(key, zRangeParams)); } @Override public List zrangeWithScores(String key, ZRangeParams zRangeParams) { - return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, zRangeParams), key); + return executeCommand(commandObjects.zrangeWithScores(key, zRangeParams)); } @Override @@ -2247,112 +2257,112 @@ public long zrangestore(String dest, String src, ZRangeParams zRangeParams) { @Override public List zrangeByScore(String key, double min, double max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max)); } @Override public List zrangeByScore(String key, String min, String max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max)); } @Override public List zrevrangeByScore(String key, double max, double min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); } @Override public List zrangeByScore(String key, double min, double max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); } @Override public List zrevrangeByScore(String key, String max, String min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); } @Override public List zrangeByScore(String key, String min, String max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); } @Override public List zrevrangeByScore(String key, double max, double min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); } @Override public List zrangeByScoreWithScores(String key, double min, double max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); } @Override public List zrevrangeByScoreWithScores(String key, double max, double min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); } @Override public List zrangeByScoreWithScores(String key, double min, double max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); } @Override public List zrevrangeByScore(String key, String max, String min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); } @Override public List zrangeByScoreWithScores(String key, String min, String max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); } @Override public List zrevrangeByScoreWithScores(String key, String max, String min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); } @Override public List zrangeByScoreWithScores(String key, String min, String max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); } @Override public List zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); } @Override public List zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); } @Override public List zrange(byte[] key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrange(key, start, stop), key); + return executeCommand(commandObjects.zrange(key, start, stop)); } @Override public List zrevrange(byte[] key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrevrange(key, start, stop), key); + return executeCommand(commandObjects.zrevrange(key, start, stop)); } @Override public List zrangeWithScores(byte[] key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, start, stop), key); + return executeCommand(commandObjects.zrangeWithScores(key, start, stop)); } @Override public List zrevrangeWithScores(byte[] key, long start, long stop) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeWithScores(key, start, stop), key); + return executeCommand(commandObjects.zrevrangeWithScores(key, start, stop)); } @Override public List zrange(byte[] key, ZRangeParams zRangeParams) { - return checkAndClientSideCacheCommand(commandObjects.zrange(key, zRangeParams), key); + return executeCommand(commandObjects.zrange(key, zRangeParams)); } @Override public List zrangeWithScores(byte[] key, ZRangeParams zRangeParams) { - return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, zRangeParams), key); + return executeCommand(commandObjects.zrangeWithScores(key, zRangeParams)); } @Override @@ -2362,82 +2372,82 @@ public long zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams) { @Override public List zrangeByScore(byte[] key, double min, double max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max)); } @Override public List zrangeByScore(byte[] key, byte[] min, byte[] max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max)); } @Override public List zrevrangeByScore(byte[] key, double max, double min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); } @Override public List zrangeByScore(byte[] key, double min, double max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); } @Override public List zrevrangeByScore(byte[] key, byte[] max, byte[] min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); } @Override public List zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); } @Override public List zrevrangeByScore(byte[] key, double max, double min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); } @Override public List zrangeByScoreWithScores(byte[] key, double min, double max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); } @Override public List zrevrangeByScoreWithScores(byte[] key, double max, double min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); } @Override public List zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); } @Override public List zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); } @Override public List zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); } @Override public List zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); } @Override public List zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); } @Override public List zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); } @Override public List zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); } @Override @@ -2472,27 +2482,27 @@ public long zremrangeByScore(byte[] key, byte[] min, byte[] max) { @Override public long zlexcount(String key, String min, String max) { - return checkAndClientSideCacheCommand(commandObjects.zlexcount(key, min, max), key); + return executeCommand(commandObjects.zlexcount(key, min, max)); } @Override public List zrangeByLex(String key, String min, String max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max), key); + return executeCommand(commandObjects.zrangeByLex(key, min, max)); } @Override public List zrangeByLex(String key, String min, String max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count)); } @Override public List zrevrangeByLex(String key, String max, String min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByLex(key, max, min)); } @Override public List zrevrangeByLex(String key, String max, String min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count)); } @Override @@ -2502,27 +2512,27 @@ public long zremrangeByLex(String key, String min, String max) { @Override public long zlexcount(byte[] key, byte[] min, byte[] max) { - return checkAndClientSideCacheCommand(commandObjects.zlexcount(key, min, max), key); + return executeCommand(commandObjects.zlexcount(key, min, max)); } @Override public List zrangeByLex(byte[] key, byte[] min, byte[] max) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max), key); + return executeCommand(commandObjects.zrangeByLex(key, min, max)); } @Override public List zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max, offset, count), key); + return executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count)); } @Override public List zrevrangeByLex(byte[] key, byte[] max, byte[] min) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min), key); + return executeCommand(commandObjects.zrevrangeByLex(key, max, min)); } @Override public List zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count) { - return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count), key); + return executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count)); } @Override @@ -2761,22 +2771,22 @@ public long geoadd(String key, GeoAddParams params, Map m @Override public Double geodist(String key, String member1, String member2) { - return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2), key); + return executeCommand(commandObjects.geodist(key, member1, member2)); } @Override public Double geodist(String key, String member1, String member2, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2, unit), key); + return executeCommand(commandObjects.geodist(key, member1, member2, unit)); } @Override public List geohash(String key, String... members) { - return checkAndClientSideCacheCommand(commandObjects.geohash(key, members), key); + return executeCommand(commandObjects.geohash(key, members)); } @Override public List geopos(String key, String... members) { - return checkAndClientSideCacheCommand(commandObjects.geopos(key, members), key); + return executeCommand(commandObjects.geopos(key, members)); } @Override @@ -2796,22 +2806,22 @@ public long geoadd(byte[] key, GeoAddParams params, Map m @Override public Double geodist(byte[] key, byte[] member1, byte[] member2) { - return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2), key); + return executeCommand(commandObjects.geodist(key, member1, member2)); } @Override public Double geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2, unit), key); + return executeCommand(commandObjects.geodist(key, member1, member2, unit)); } @Override public List geohash(byte[] key, byte[]... members) { - return checkAndClientSideCacheCommand(commandObjects.geohash(key, members), key); + return executeCommand(commandObjects.geohash(key, members)); } @Override public List geopos(byte[] key, byte[]... members) { - return checkAndClientSideCacheCommand(commandObjects.geopos(key, members), key); + return executeCommand(commandObjects.geopos(key, members)); } @Override @@ -2820,18 +2830,21 @@ public List georadius(String key, double longitude, double la } @Override - public List georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit), key); + public List georadiusReadonly(String key, double longitude, double latitude, double radius, + GeoUnit unit) { + return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)); } @Override - public List georadius(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { + public List georadius(String key, double longitude, double latitude, double radius, GeoUnit unit, + GeoRadiusParam param) { return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param)); } @Override - public List georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { - return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param), key); + public List georadiusReadonly(String key, double longitude, double latitude, double radius, + GeoUnit unit, GeoRadiusParam param) { + return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param)); } @Override @@ -2841,52 +2854,56 @@ public List georadiusByMember(String key, String member, doub @Override public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit), key); + return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit)); } @Override - public List georadiusByMember(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) { + public List georadiusByMember(String key, String member, double radius, GeoUnit unit, + GeoRadiusParam param) { return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param)); } @Override - public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) { - return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param), key); + public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, + GeoRadiusParam param) { + return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)); } @Override - public long georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { + public long georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, + GeoRadiusParam param, GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam)); } @Override - public long georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { + public long georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, + GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam)); } @Override public List geosearch(String key, String member, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, radius, unit), key); + return executeCommand(commandObjects.geosearch(key, member, radius, unit)); } @Override public List geosearch(String key, GeoCoordinate coord, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, radius, unit), key); + return executeCommand(commandObjects.geosearch(key, coord, radius, unit)); } @Override public List geosearch(String key, String member, double width, double height, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, width, height, unit), key); + return executeCommand(commandObjects.geosearch(key, member, width, height, unit)); } @Override public List geosearch(String key, GeoCoordinate coord, double width, double height, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, width, height, unit), key); + return executeCommand(commandObjects.geosearch(key, coord, width, height, unit)); } @Override public List geosearch(String key, GeoSearchParam params) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, params), key); + return executeCommand(commandObjects.geosearch(key, params)); } @Override @@ -2925,18 +2942,21 @@ public List georadius(byte[] key, double longitude, double la } @Override - public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit), key); + public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, + GeoUnit unit) { + return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)); } @Override - public List georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { + public List georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, + GeoRadiusParam param) { return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param)); } @Override - public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { - return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param), key); + public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, + GeoUnit unit, GeoRadiusParam param) { + return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param)); } @Override @@ -2946,52 +2966,56 @@ public List georadiusByMember(byte[] key, byte[] member, doub @Override public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit), key); + return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit)); } @Override - public List georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) { + public List georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, + GeoRadiusParam param) { return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param)); } @Override - public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) { - return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param), key); + public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, + GeoRadiusParam param) { + return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)); } @Override - public long georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { + public long georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, + GeoRadiusParam param, GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam)); } @Override - public long georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { + public long georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, + GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam)); } @Override public List geosearch(byte[] key, byte[] member, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, radius, unit), key); + return executeCommand(commandObjects.geosearch(key, member, radius, unit)); } @Override public List geosearch(byte[] key, GeoCoordinate coord, double radius, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, radius, unit), key); + return executeCommand(commandObjects.geosearch(key, coord, radius, unit)); } @Override public List geosearch(byte[] key, byte[] member, double width, double height, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, width, height, unit), key); + return executeCommand(commandObjects.geosearch(key, member, width, height, unit)); } @Override public List geosearch(byte[] key, GeoCoordinate coord, double width, double height, GeoUnit unit) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, width, height, unit), key); + return executeCommand(commandObjects.geosearch(key, coord, width, height, unit)); } @Override public List geosearch(byte[] key, GeoSearchParam params) { - return checkAndClientSideCacheCommand(commandObjects.geosearch(key, params), key); + return executeCommand(commandObjects.geosearch(key, params)); } @Override @@ -3080,47 +3104,47 @@ public StreamEntryID xadd(String key, XAddParams params, Map has @Override public long xlen(String key) { - return checkAndClientSideCacheCommand(commandObjects.xlen(key), key); + return executeCommand(commandObjects.xlen(key)); } @Override public List xrange(String key, StreamEntryID start, StreamEntryID end) { - return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end), key); + return executeCommand(commandObjects.xrange(key, start, end)); } @Override public List xrange(String key, StreamEntryID start, StreamEntryID end, int count) { - return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end, count), key); + return executeCommand(commandObjects.xrange(key, start, end, count)); } @Override public List xrevrange(String key, StreamEntryID end, StreamEntryID start) { - return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start), key); + return executeCommand(commandObjects.xrevrange(key, end, start)); } @Override public List xrevrange(String key, StreamEntryID end, StreamEntryID start, int count) { - return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start, count), key); + return executeCommand(commandObjects.xrevrange(key, end, start, count)); } @Override public List xrange(String key, String start, String end) { - return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end), key); + return executeCommand(commandObjects.xrange(key, start, end)); } @Override public List xrange(String key, String start, String end, int count) { - return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end, count), key); + return executeCommand(commandObjects.xrange(key, start, end, count)); } @Override public List xrevrange(String key, String end, String start) { - return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start), key); + return executeCommand(commandObjects.xrevrange(key, end, start)); } @Override public List xrevrange(String key, String end, String start, int count) { - return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start, count), key); + return executeCommand(commandObjects.xrevrange(key, end, start, count)); } @Override @@ -3155,12 +3179,12 @@ public long xgroupDelConsumer(String key, String groupName, String consumerName) @Override public StreamPendingSummary xpending(String key, String groupName) { - return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName), key); + return executeCommand(commandObjects.xpending(key, groupName)); } @Override public List xpending(String key, String groupName, XPendingParams params) { - return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName, params), key); + return executeCommand(commandObjects.xpending(key, groupName, params)); } @Override @@ -3179,22 +3203,26 @@ public long xtrim(String key, XTrimParams params) { } @Override - public List xclaim(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) { + public List xclaim(String key, String group, String consumerName, long minIdleTime, XClaimParams params, + StreamEntryID... ids) { return executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids)); } @Override - public List xclaimJustId(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) { + public List xclaimJustId(String key, String group, String consumerName, long minIdleTime, + XClaimParams params, StreamEntryID... ids) { return executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids)); } @Override - public Map.Entry> xautoclaim(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) { + public Map.Entry> xautoclaim(String key, String group, String consumerName, + long minIdleTime, StreamEntryID start, XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaim(key, group, consumerName, minIdleTime, start, params)); } @Override - public Map.Entry> xautoclaimJustId(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) { + public Map.Entry> xautoclaimJustId(String key, String group, String consumerName, + long minIdleTime, StreamEntryID start, XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaimJustId(key, group, consumerName, minIdleTime, start, params)); } @@ -3257,27 +3285,27 @@ public byte[] xadd(byte[] key, XAddParams params, Map hash) { @Override public long xlen(byte[] key) { - return checkAndClientSideCacheCommand(commandObjects.xlen(key), key); + return executeCommand(commandObjects.xlen(key)); } @Override public List xrange(byte[] key, byte[] start, byte[] end) { - return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end), key); + return executeCommand(commandObjects.xrange(key, start, end)); } @Override public List xrange(byte[] key, byte[] start, byte[] end, int count) { - return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end, count), key); + return executeCommand(commandObjects.xrange(key, start, end, count)); } @Override public List xrevrange(byte[] key, byte[] end, byte[] start) { - return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start), key); + return executeCommand(commandObjects.xrevrange(key, end, start)); } @Override public List xrevrange(byte[] key, byte[] end, byte[] start, int count) { - return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start, count), key); + return executeCommand(commandObjects.xrevrange(key, end, start, count)); } @Override @@ -3327,31 +3355,35 @@ public long xtrim(byte[] key, XTrimParams params) { @Override public Object xpending(byte[] key, byte[] groupName) { - return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName), key); + return executeCommand(commandObjects.xpending(key, groupName)); } @Override public List xpending(byte[] key, byte[] groupName, XPendingParams params) { - return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName, params), key); + return executeCommand(commandObjects.xpending(key, groupName, params)); } @Override - public List xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) { + public List xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, + byte[]... ids) { return executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids)); } @Override - public List xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) { + public List xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, + byte[]... ids) { return executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids)); } @Override - public List xautoclaim(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) { + public List xautoclaim(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, + XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaim(key, groupName, consumerName, minIdleTime, start, params)); } @Override - public List xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) { + public List xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, + byte[] start, XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params)); } @@ -3386,7 +3418,8 @@ public List xread(XReadParams xReadParams, Map.Entry... } @Override - public List xreadGroup(byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams, Map.Entry... streams) { + public List xreadGroup(byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams, + Map.Entry... streams) { return executeCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams)); } // Stream commands @@ -3712,7 +3745,7 @@ public List scriptExists(List sha1s) { @Override public Boolean scriptExists(String sha1, String sampleKey) { - return scriptExists(sampleKey, new String[]{sha1}).get(0); + return scriptExists(sampleKey, new String[] { sha1 }).get(0); } @Override @@ -3722,7 +3755,7 @@ public List scriptExists(String sampleKey, String... sha1s) { @Override public Boolean scriptExists(byte[] sha1, byte[] sampleKey) { - return scriptExists(sampleKey, new byte[][]{sha1}).get(0); + return scriptExists(sampleKey, new byte[][] { sha1 }).get(0); } @Override @@ -3887,10 +3920,11 @@ public SearchResult ftSearch(String indexName, String query, FTSearchParams para /** * {@link FTSearchParams#limit(int, int)} will be ignored. + * * @param batchSize batch size * @param indexName index name - * @param query query - * @param params limit will be ignored + * @param query query + * @param params limit will be ignored * @return search iteration */ public FtSearchIteration ftSearchIteration(int batchSize, String indexName, String query, FTSearchParams params) { @@ -3904,9 +3938,10 @@ public SearchResult ftSearch(String indexName, Query query) { /** * {@link Query#limit(java.lang.Integer, java.lang.Integer)} will be ignored. + * * @param batchSize batch size * @param indexName index name - * @param query limit will be ignored + * @param query limit will be ignored * @return search iteration */ public FtSearchIteration ftSearchIteration(int batchSize, String indexName, Query query) { @@ -3946,8 +3981,9 @@ public String ftCursorDel(String indexName, long cursorId) { /** * {@link AggregationBuilder#cursor(int, long) CURSOR} must be set. + * * @param indexName index name - * @param aggr cursor must be set + * @param aggr cursor must be set * @return aggregate iteration */ public FtAggregateIteration ftAggregateIteration(String indexName, AggregationBuilder aggr) { @@ -4018,7 +4054,8 @@ public Map> ftSpellCheck(String index, String query) } @Override - public Map> ftSpellCheck(String index, String query, FTSpellCheckParams spellCheckParams) { + public Map> ftSpellCheck(String index, String query, + FTSpellCheckParams spellCheckParams) { return executeCommand(commandObjects.ftSpellCheck(index, query, spellCheckParams)); } @@ -4150,47 +4187,47 @@ public String jsonMerge(String key, Path path, Object pojo) { @Override public Object jsonGet(String key) { - return checkAndClientSideCacheCommand(commandObjects.jsonGet(key), key); + return executeCommand(commandObjects.jsonGet(key)); } @Override @Deprecated public T jsonGet(String key, Class clazz) { - return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, clazz), key); + return executeCommand(commandObjects.jsonGet(key, clazz)); } @Override public Object jsonGet(String key, Path2... paths) { - return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, paths), key); + return executeCommand(commandObjects.jsonGet(key, paths)); } @Override @Deprecated public Object jsonGet(String key, Path... paths) { - return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, paths), key); + return executeCommand(commandObjects.jsonGet(key, paths)); } @Override @Deprecated public String jsonGetAsPlainString(String key, Path path) { - return checkAndClientSideCacheCommand(commandObjects.jsonGetAsPlainString(key, path), key); + return executeCommand(commandObjects.jsonGetAsPlainString(key, path)); } @Override @Deprecated public T jsonGet(String key, Class clazz, Path... paths) { - return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, clazz, paths), key); + return executeCommand(commandObjects.jsonGet(key, clazz, paths)); } @Override public List jsonMGet(Path2 path, String... keys) { - return checkAndClientSideCacheCommand(commandObjects.jsonMGet(path, keys), (Object[]) keys); + return executeCommand(commandObjects.jsonMGet(path, keys)); } @Override @Deprecated public List jsonMGet(Path path, Class clazz, String... keys) { - return checkAndClientSideCacheCommand(commandObjects.jsonMGet(path, clazz, keys), (Object[]) keys); + return executeCommand(commandObjects.jsonMGet(path, clazz, keys)); } @Override @@ -4239,18 +4276,18 @@ public String jsonToggle(String key, Path path) { @Override @Deprecated public Class jsonType(String key) { - return checkAndClientSideCacheCommand(commandObjects.jsonType(key), key); + return executeCommand(commandObjects.jsonType(key)); } @Override public List> jsonType(String key, Path2 path) { - return checkAndClientSideCacheCommand(commandObjects.jsonType(key, path), key); + return executeCommand(commandObjects.jsonType(key, path)); } @Override @Deprecated public Class jsonType(String key, Path path) { - return checkAndClientSideCacheCommand(commandObjects.jsonType(key, path), key); + return executeCommand(commandObjects.jsonType(key, path)); } @Override @@ -4273,18 +4310,18 @@ public long jsonStrAppend(String key, Path path, Object string) { @Override @Deprecated public Long jsonStrLen(String key) { - return checkAndClientSideCacheCommand(commandObjects.jsonStrLen(key), key); + return executeCommand(commandObjects.jsonStrLen(key)); } @Override public List jsonStrLen(String key, Path2 path) { - return checkAndClientSideCacheCommand(commandObjects.jsonStrLen(key, path), key); + return executeCommand(commandObjects.jsonStrLen(key, path)); } @Override @Deprecated public Long jsonStrLen(String key, Path path) { - return checkAndClientSideCacheCommand(commandObjects.jsonStrLen(key, path), key); + return executeCommand(commandObjects.jsonStrLen(key, path)); } @Override @@ -4316,18 +4353,18 @@ public Long jsonArrAppend(String key, Path path, Object... pojos) { @Override public List jsonArrIndex(String key, Path2 path, Object scalar) { - return checkAndClientSideCacheCommand(commandObjects.jsonArrIndex(key, path, scalar), key); + return executeCommand(commandObjects.jsonArrIndex(key, path, scalar)); } @Override public List jsonArrIndexWithEscape(String key, Path2 path, Object scalar) { - return checkAndClientSideCacheCommand(commandObjects.jsonArrIndexWithEscape(key, path, scalar), key); + return executeCommand(commandObjects.jsonArrIndexWithEscape(key, path, scalar)); } @Override @Deprecated public long jsonArrIndex(String key, Path path, Object scalar) { - return checkAndClientSideCacheCommand(commandObjects.jsonArrIndex(key, path, scalar), key); + return executeCommand(commandObjects.jsonArrIndex(key, path, scalar)); } @Override @@ -4395,18 +4432,18 @@ public T jsonArrPop(String key, Class clazz, Path path, int index) { @Override @Deprecated public Long jsonArrLen(String key) { - return checkAndClientSideCacheCommand(commandObjects.jsonArrLen(key), key); + return executeCommand(commandObjects.jsonArrLen(key)); } @Override public List jsonArrLen(String key, Path2 path) { - return checkAndClientSideCacheCommand(commandObjects.jsonArrLen(key, path), key); + return executeCommand(commandObjects.jsonArrLen(key, path)); } @Override @Deprecated public Long jsonArrLen(String key, Path path) { - return checkAndClientSideCacheCommand(commandObjects.jsonArrLen(key, path), key); + return executeCommand(commandObjects.jsonArrLen(key, path)); } @Override @@ -4423,35 +4460,35 @@ public Long jsonArrTrim(String key, Path path, int start, int stop) { @Override @Deprecated public Long jsonObjLen(String key) { - return checkAndClientSideCacheCommand(commandObjects.jsonObjLen(key), key); + return executeCommand(commandObjects.jsonObjLen(key)); } @Override @Deprecated public Long jsonObjLen(String key, Path path) { - return checkAndClientSideCacheCommand(commandObjects.jsonObjLen(key, path), key); + return executeCommand(commandObjects.jsonObjLen(key, path)); } @Override public List jsonObjLen(String key, Path2 path) { - return checkAndClientSideCacheCommand(commandObjects.jsonObjLen(key, path), key); + return executeCommand(commandObjects.jsonObjLen(key, path)); } @Override @Deprecated public List jsonObjKeys(String key) { - return checkAndClientSideCacheCommand(commandObjects.jsonObjKeys(key), key); + return executeCommand(commandObjects.jsonObjKeys(key)); } @Override @Deprecated public List jsonObjKeys(String key, Path path) { - return checkAndClientSideCacheCommand(commandObjects.jsonObjKeys(key, path), key); + return executeCommand(commandObjects.jsonObjKeys(key, path)); } @Override public List> jsonObjKeys(String key, Path2 path) { - return checkAndClientSideCacheCommand(commandObjects.jsonObjKeys(key, path), key); + return executeCommand(commandObjects.jsonObjKeys(key, path)); } @Override @@ -4550,22 +4587,22 @@ public long tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams) @Override public List tsRange(String key, long fromTimestamp, long toTimestamp) { - return checkAndClientSideCacheCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp), key); + return executeCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp)); } @Override public List tsRange(String key, TSRangeParams rangeParams) { - return checkAndClientSideCacheCommand(commandObjects.tsRange(key, rangeParams), key); + return executeCommand(commandObjects.tsRange(key, rangeParams)); } @Override public List tsRevRange(String key, long fromTimestamp, long toTimestamp) { - return checkAndClientSideCacheCommand(commandObjects.tsRevRange(key, fromTimestamp, toTimestamp), key); + return executeCommand(commandObjects.tsRevRange(key, fromTimestamp, toTimestamp)); } @Override public List tsRevRange(String key, TSRangeParams rangeParams) { - return checkAndClientSideCacheCommand(commandObjects.tsRevRange(key, rangeParams), key); + return executeCommand(commandObjects.tsRevRange(key, rangeParams)); } @Override @@ -4590,12 +4627,12 @@ public Map tsMRevRange(TSMRangeParams multiRangeParams @Override public TSElement tsGet(String key) { - return checkAndClientSideCacheCommand(commandObjects.tsGet(key), key); + return executeCommand(commandObjects.tsGet(key)); } @Override public TSElement tsGet(String key, TSGetParams getParams) { - return checkAndClientSideCacheCommand(commandObjects.tsGet(key, getParams), key); + return executeCommand(commandObjects.tsGet(key, getParams)); } @Override @@ -4609,8 +4646,10 @@ public String tsCreateRule(String sourceKey, String destKey, AggregationType agg } @Override - public String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, long alignTimestamp) { - return executeCommand(commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp)); + public String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, + long alignTimestamp) { + return executeCommand( + commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp)); } @Override @@ -4625,7 +4664,7 @@ public List tsQueryIndex(String... filters) { @Override public TSInfo tsInfo(String key) { - return checkAndClientSideCacheCommand(commandObjects.tsInfo(key), key); + return executeCommand(commandObjects.tsInfo(key)); } @Override @@ -5046,7 +5085,8 @@ public Object tFunctionCallAsync(String library, String function, List k // RedisGears commands /** - * @return pipeline object. Use {@link AbstractPipeline} instead of {@link PipelineBase}. + * @return pipeline object. Use {@link AbstractPipeline} instead of + * {@link PipelineBase}. */ public PipelineBase pipelined() { if (provider == null) { @@ -5066,7 +5106,8 @@ public AbstractTransaction multi() { } /** - * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI + * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH + * and MULTI * @return transaction object */ public AbstractTransaction transaction(boolean doMulti) { @@ -5104,7 +5145,8 @@ public Object sendCommand(byte[] sampleKey, ProtocolCommand cmd, byte[]... args) } public Object sendBlockingCommand(byte[] sampleKey, ProtocolCommand cmd, byte[]... args) { - return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking().processKey(sampleKey)); + return executeCommand( + commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking().processKey(sampleKey)); } public Object sendCommand(String sampleKey, ProtocolCommand cmd, String... args) { @@ -5112,7 +5154,8 @@ public Object sendCommand(String sampleKey, ProtocolCommand cmd, String... args) } public Object sendBlockingCommand(String sampleKey, ProtocolCommand cmd, String... args) { - return executeCommand(commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking().processKey(sampleKey)); + return executeCommand( + commandObjects.commandArguments(cmd).addObjects((Object[]) args).blocking().processKey(sampleKey)); } public Object executeCommand(CommandArguments args) { diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index a0bf703b7d..2fc5bb3473 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -59,7 +59,6 @@ protected void protocolReadPushes(RedisInputStream inputStream) { @Override public T executeCommand(final CommandObject commandObject) { - System.out.println("this is the interceptor for cache"); T data = clientSideCache.get(this, commandObject, commandObject.getArguments().keys()); return data; } diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 20933fd237..abc19bf8d7 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -3,7 +3,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import java.io.File; import java.util.HashMap; import java.util.function.Supplier; @@ -11,6 +13,7 @@ import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import redis.clients.jedis.Connection; @@ -31,10 +34,35 @@ public class JedisPooledClientSideCacheTest { protected Jedis control; + protected static final EndpointConfig sslEndpoint = HostAndPorts.getRedisEndpoint("standalone0-tls"); + + protected Jedis sslControl; + + @BeforeClass + public static void prepare() { + setupTrustStore(); + } + + static void setupTrustStore() { + setJvmTrustStore("src/test/resources/truststore.jceks", "jceks"); + } + + private static void setJvmTrustStore(String trustStoreFilePath, String trustStoreType) { + assertTrue(String.format("Could not find trust store at '%s'.", trustStoreFilePath), + new File(trustStoreFilePath).exists()); + System.setProperty("javax.net.ssl.trustStore", trustStoreFilePath); + System.setProperty("javax.net.ssl.trustStoreType", trustStoreType); + } + @Before public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); // TODO: use EndpointConfig + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); // TODO: use + // EndpointConfig control.flushAll(); + + sslControl = new Jedis(sslEndpoint.getHostAndPort(), + DefaultJedisClientConfig.builder().password("foobared").ssl(true).build()); + sslControl.flushAll(); } @After @@ -42,15 +70,14 @@ public void tearDown() throws Exception { control.close(); } - private static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); // TODO: use EndpointConfig + private static final Supplier clientConfig = () -> DefaultJedisClientConfig.builder().resp3() + .password("foobared").build(); // TODO: use EndpointConfig - private static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; + private static final Supplier> singleConnectionPoolConfig = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; @Test public void simple() { @@ -62,6 +89,18 @@ public void simple() { } } + @Test + public void simpleSSL() { + JedisClientConfig conf = DefaultJedisClientConfig.builder().resp3() + .password("foobared").ssl(true).build(); + try (JedisPooled jedis = new JedisPooled(sslEndpoint.getHostAndPort(), conf, new MapClientSideCache())) { + sslControl.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + sslControl.del("foo"); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); @@ -111,4 +150,5 @@ public void flushAllWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(0)); } } + } From 6bf22918ce7dd05b7076e1ac5b3ac19deeb4a454 Mon Sep 17 00:00:00 2001 From: atakavci Date: Sun, 21 Jul 2024 18:18:11 +0300 Subject: [PATCH 04/15] - fix readtimeout exception with sockets for consuming invalidations pending in buffer - apply a default list of cacheable commands to DefaultClientSideCacheable - fix failing unit tests with cacheable / non-cacheable keys - remove formatting changes --- .../redis/clients/jedis/CommandArguments.java | 5 +- .../java/redis/clients/jedis/Connection.java | 17 +-- .../java/redis/clients/jedis/Protocol.java | 24 +++- .../redis/clients/jedis/UnifiedJedis.java | 124 ++++++------------ .../clients/jedis/csc/CacheConnection.java | 8 +- .../clients/jedis/csc/ClientSideCache.java | 79 +++++++---- .../jedis/csc/DefaultClientSideCacheable.java | 84 +++++++++++- .../util/AllowAndDenyListWithStringKeys.java | 21 +-- .../clients/jedis/util/RedisInputStream.java | 33 ++--- .../csc/CaffeineClientSideCacheTest.java | 4 +- .../jedis/csc/GuavaClientSideCacheTest.java | 4 +- .../csc/JedisClusterClientSideCacheTest.java | 8 +- .../csc/JedisPooledClientSideCacheTest.java | 8 +- .../JedisSentineledClientSideCacheTest.java | 8 +- 14 files changed, 246 insertions(+), 181 deletions(-) diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index 0925cf3165..8088238223 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -117,6 +117,7 @@ public CommandArguments key(Object key) { } else { throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); } + keys.add(key); return this; } @@ -136,7 +137,6 @@ public final CommandArguments addParams(IParams params) { } protected CommandArguments processKey(byte[] key) { - keys.add(key); return this; } @@ -148,7 +148,6 @@ protected final CommandArguments processKeys(byte[]... keys) { } protected CommandArguments processKey(String key) { - keys.add(key); return this; } @@ -168,7 +167,7 @@ public Iterator iterator() { return args.iterator(); } - public Object[] keys() { + public Object[] getKeys() { return keys.toArray(); } diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 6c7e9bd194..40886aa079 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -169,8 +169,7 @@ public void sendCommand(final CommandArguments args) { Protocol.sendCommand(outputStream, args); } catch (JedisConnectionException ex) { /* - * When client send request which formed by invalid protocol, Redis send back - * error message + * When client send request which formed by invalid protocol, Redis send back error message * before close connection. We try to read it to provide reason of failure. */ try { @@ -180,10 +179,8 @@ public void sendCommand(final CommandArguments args) { } } catch (Exception e) { /* - * Catch any IOException or JedisConnectionException occurred from - * InputStream#read and just - * ignore. This approach is safe because reading error message is optional and - * connection + * Catch any IOException or JedisConnectionException occurred from InputStream#read and just + * ignore. This approach is safe because reading error message is optional and connection * will eventually be closed. */ } @@ -197,7 +194,7 @@ public void connect() throws JedisConnectionException { if (!isConnected()) { try { socket = socketFactory.createSocket(); - soTimeout = socket.getSoTimeout(); // ? + soTimeout = socket.getSoTimeout(); //? outputStream = new RedisOutputStream(socket.getOutputStream()); inputStream = new RedisInputStream(socket.getInputStream()); @@ -354,7 +351,7 @@ protected Object protocolRead(RedisInputStream is) { @Experimental @Internal - protected void protocolReadPushes(RedisInputStream is) { + protected void protocolReadPushes(RedisInputStream is, boolean onlyPendingBuffer) { } // TODO: final @@ -364,7 +361,7 @@ protected Object readProtocolWithCheckingBroken() { } try { - protocolReadPushes(inputStream); + protocolReadPushes(inputStream, false); return protocolRead(inputStream); } catch (JedisConnectionException exc) { broken = true; @@ -383,7 +380,7 @@ protected void readPushesWithCheckingBroken() { } else { try { if (inputStream.available() > 0) { - protocolReadPushes(inputStream); + protocolReadPushes(inputStream, true); } } catch (IOException e) { // TODO: handle this properly diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 6777a2f1b0..694d69a11d 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -214,15 +214,25 @@ public static Object read(final RedisInputStream is) { } @Experimental - public static void readPushes(final RedisInputStream is, final ClientSideCache cache) { + public static void readPushes(final RedisInputStream is, final ClientSideCache cache, boolean onlyPendingBuffer) { boolean available = false; - int counter = 0; - // TODO : we need to find away to get away from peekSafe - while (is.peekSafe(GREATER_THAN_BYTE)) { - counter++; - is.readByte(); - processPush(is, cache); + if (onlyPendingBuffer) { + try { + while (is.available() > 0 && is.peek(GREATER_THAN_BYTE)) { + is.readByte(); + processPush(is, cache); + } + } catch (JedisConnectionException e) { + // TODO handle it properly + } catch (IOException e) { + // TODO handle it properly + } + } else { + while (is.peek(GREATER_THAN_BYTE)) { + is.readByte(); + processPush(is, cache); + } } } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 5bf6798f7b..d89c109c85 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -116,8 +116,7 @@ protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, Clie /** * The constructor to directly use a custom {@link JedisSocketFactory}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be - * occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(JedisSocketFactory socketFactory) { @@ -127,8 +126,7 @@ public UnifiedJedis(JedisSocketFactory socketFactory) { /** * The constructor to directly use a custom {@link JedisSocketFactory}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be - * occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(JedisSocketFactory socketFactory, JedisClientConfig clientConfig) { @@ -138,8 +136,7 @@ public UnifiedJedis(JedisSocketFactory socketFactory, JedisClientConfig clientCo /** * The constructor to directly use a {@link Connection}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be - * occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(Connection connection) { @@ -213,13 +210,10 @@ public UnifiedJedis(ConnectionProvider provider, int maxAttempts, Duration maxTo } /** - * Constructor which supports multiple cluster/database endpoints each with - * their own isolated connection pool. + * Constructor which supports multiple cluster/database endpoints each with their own isolated connection pool. *

- * With this Constructor users can seamlessly failover to Disaster Recovery - * (DR), Backup, and Active-Active cluster(s) - * by using simple configuration which is passed through from Resilience4j - - * https://resilience4j.readme.io/docs + * With this Constructor users can seamlessly failover to Disaster Recovery (DR), Backup, and Active-Active cluster(s) + * by using simple configuration which is passed through from Resilience4j - https://resilience4j.readme.io/docs *

*/ @Experimental @@ -230,8 +224,7 @@ public UnifiedJedis(MultiClusterPooledConnectionProvider provider) { /** * The constructor to use a custom {@link CommandExecutor}. *

- * WARNING: Using this constructor means a {@link NullPointerException} will be - * occurred if + * WARNING: Using this constructor means a {@link NullPointerException} will be occurred if * {@link UnifiedJedis#provider} is accessed. */ public UnifiedJedis(CommandExecutor executor) { @@ -714,7 +707,7 @@ public ScanResult scan(String cursor, ScanParams params, String type) { /** * @param batchCount COUNT for each batch execution - * @param match pattern + * @param match pattern * @return scan iteration */ public ScanIteration scanIteration(int batchCount, String match) { @@ -723,8 +716,8 @@ public ScanIteration scanIteration(int batchCount, String match) { /** * @param batchCount COUNT for each batch execution - * @param match pattern - * @param type key type + * @param match pattern + * @param type key type * @return scan iteration */ public ScanIteration scanIteration(int batchCount, String match, String type) { @@ -874,8 +867,7 @@ public byte[] getrange(byte[] key, long startOffset, long endOffset) { } /** - * @deprecated Use - * {@link UnifiedJedis#setGet(java.lang.String, java.lang.String)}. + * @deprecated Use {@link UnifiedJedis#setGet(java.lang.String, java.lang.String)}. */ @Deprecated @Override @@ -2830,20 +2822,17 @@ public List georadius(String key, double longitude, double la } @Override - public List georadiusReadonly(String key, double longitude, double latitude, double radius, - GeoUnit unit) { + public List georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit) { return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)); } @Override - public List georadius(String key, double longitude, double latitude, double radius, GeoUnit unit, - GeoRadiusParam param) { + public List georadius(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param)); } @Override - public List georadiusReadonly(String key, double longitude, double latitude, double radius, - GeoUnit unit, GeoRadiusParam param) { + public List georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param)); } @@ -2858,26 +2847,22 @@ public List georadiusByMemberReadonly(String key, String memb } @Override - public List georadiusByMember(String key, String member, double radius, GeoUnit unit, - GeoRadiusParam param) { + public List georadiusByMember(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param)); } @Override - public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, - GeoRadiusParam param) { + public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)); } @Override - public long georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, - GeoRadiusParam param, GeoRadiusStoreParam storeParam) { + public long georadiusStore(String key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam)); } @Override - public long georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, - GeoRadiusStoreParam storeParam) { + public long georadiusByMemberStore(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam)); } @@ -2942,20 +2927,17 @@ public List georadius(byte[] key, double longitude, double la } @Override - public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, - GeoUnit unit) { + public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) { return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)); } @Override - public List georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, - GeoRadiusParam param) { + public List georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadius(key, longitude, latitude, radius, unit, param)); } @Override - public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, - GeoUnit unit, GeoRadiusParam param) { + public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param)); } @@ -2970,26 +2952,22 @@ public List georadiusByMemberReadonly(byte[] key, byte[] memb } @Override - public List georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, - GeoRadiusParam param) { + public List georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadiusByMember(key, member, radius, unit, param)); } @Override - public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, - GeoRadiusParam param) { + public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) { return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)); } @Override - public long georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, - GeoRadiusParam param, GeoRadiusStoreParam storeParam) { + public long georadiusStore(byte[] key, double longitude, double latitude, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusStore(key, longitude, latitude, radius, unit, param, storeParam)); } @Override - public long georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, - GeoRadiusStoreParam storeParam) { + public long georadiusByMemberStore(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param, GeoRadiusStoreParam storeParam) { return executeCommand(commandObjects.georadiusByMemberStore(key, member, radius, unit, param, storeParam)); } @@ -3203,26 +3181,22 @@ public long xtrim(String key, XTrimParams params) { } @Override - public List xclaim(String key, String group, String consumerName, long minIdleTime, XClaimParams params, - StreamEntryID... ids) { + public List xclaim(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) { return executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids)); } @Override - public List xclaimJustId(String key, String group, String consumerName, long minIdleTime, - XClaimParams params, StreamEntryID... ids) { + public List xclaimJustId(String key, String group, String consumerName, long minIdleTime, XClaimParams params, StreamEntryID... ids) { return executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids)); } @Override - public Map.Entry> xautoclaim(String key, String group, String consumerName, - long minIdleTime, StreamEntryID start, XAutoClaimParams params) { + public Map.Entry> xautoclaim(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaim(key, group, consumerName, minIdleTime, start, params)); } @Override - public Map.Entry> xautoclaimJustId(String key, String group, String consumerName, - long minIdleTime, StreamEntryID start, XAutoClaimParams params) { + public Map.Entry> xautoclaimJustId(String key, String group, String consumerName, long minIdleTime, StreamEntryID start, XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaimJustId(key, group, consumerName, minIdleTime, start, params)); } @@ -3267,14 +3241,12 @@ public Map> xreadAsMap(XReadParams xReadParams, Map>> xreadGroup(String groupName, String consumer, - XReadGroupParams xReadGroupParams, Map streams) { + public List>> xreadGroup(String groupName, String consumer, XReadGroupParams xReadGroupParams, Map streams) { return executeCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams)); } @Override - public Map> xreadGroupAsMap(String groupName, String consumer, - XReadGroupParams xReadGroupParams, Map streams) { + public Map> xreadGroupAsMap(String groupName, String consumer, XReadGroupParams xReadGroupParams, Map streams) { return executeCommand(commandObjects.xreadGroupAsMap(groupName, consumer, xReadGroupParams, streams)); } @@ -3364,26 +3336,22 @@ public List xpending(byte[] key, byte[] groupName, XPendingParams params } @Override - public List xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, - byte[]... ids) { + public List xclaim(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) { return executeCommand(commandObjects.xclaim(key, group, consumerName, minIdleTime, params, ids)); } @Override - public List xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, - byte[]... ids) { + public List xclaimJustId(byte[] key, byte[] group, byte[] consumerName, long minIdleTime, XClaimParams params, byte[]... ids) { return executeCommand(commandObjects.xclaimJustId(key, group, consumerName, minIdleTime, params, ids)); } @Override - public List xautoclaim(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, - XAutoClaimParams params) { + public List xautoclaim(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaim(key, groupName, consumerName, minIdleTime, start, params)); } @Override - public List xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, - byte[] start, XAutoClaimParams params) { + public List xautoclaimJustId(byte[] key, byte[] groupName, byte[] consumerName, long minIdleTime, byte[] start, XAutoClaimParams params) { return executeCommand(commandObjects.xautoclaimJustId(key, groupName, consumerName, minIdleTime, start, params)); } @@ -3418,8 +3386,7 @@ public List xread(XReadParams xReadParams, Map.Entry... } @Override - public List xreadGroup(byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams, - Map.Entry... streams) { + public List xreadGroup(byte[] groupName, byte[] consumer, XReadGroupParams xReadGroupParams, Map.Entry... streams) { return executeCommand(commandObjects.xreadGroup(groupName, consumer, xReadGroupParams, streams)); } // Stream commands @@ -3923,8 +3890,8 @@ public SearchResult ftSearch(String indexName, String query, FTSearchParams para * * @param batchSize batch size * @param indexName index name - * @param query query - * @param params limit will be ignored + * @param query query + * @param params limit will be ignored * @return search iteration */ public FtSearchIteration ftSearchIteration(int batchSize, String indexName, String query, FTSearchParams params) { @@ -3938,10 +3905,9 @@ public SearchResult ftSearch(String indexName, Query query) { /** * {@link Query#limit(java.lang.Integer, java.lang.Integer)} will be ignored. - * * @param batchSize batch size * @param indexName index name - * @param query limit will be ignored + * @param query limit will be ignored * @return search iteration */ public FtSearchIteration ftSearchIteration(int batchSize, String indexName, Query query) { @@ -3981,9 +3947,8 @@ public String ftCursorDel(String indexName, long cursorId) { /** * {@link AggregationBuilder#cursor(int, long) CURSOR} must be set. - * * @param indexName index name - * @param aggr cursor must be set + * @param aggr cursor must be set * @return aggregate iteration */ public FtAggregateIteration ftAggregateIteration(String indexName, AggregationBuilder aggr) { @@ -4646,8 +4611,7 @@ public String tsCreateRule(String sourceKey, String destKey, AggregationType agg } @Override - public String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, - long alignTimestamp) { + public String tsCreateRule(String sourceKey, String destKey, AggregationType aggregationType, long bucketDuration, long alignTimestamp) { return executeCommand( commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp)); } @@ -5085,8 +5049,7 @@ public Object tFunctionCallAsync(String library, String function, List k // RedisGears commands /** - * @return pipeline object. Use {@link AbstractPipeline} instead of - * {@link PipelineBase}. + * @return pipeline object. Use {@link AbstractPipeline} instead of {@link PipelineBase}. */ public PipelineBase pipelined() { if (provider == null) { @@ -5106,8 +5069,7 @@ public AbstractTransaction multi() { } /** - * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH - * and MULTI + * @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI * @return transaction object */ public AbstractTransaction transaction(boolean doMulti) { diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 2fc5bb3473..51e0317b63 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -45,13 +45,11 @@ protected Object protocolRead(RedisInputStream inputStream) { } @Override - protected void protocolReadPushes(RedisInputStream inputStream) { + protected void protocolReadPushes(RedisInputStream inputStream, boolean onlyPendingBuffer) { if (lock != null && lock.tryLock()) { try { - // super.setSoTimeout(1); - Protocol.readPushes(inputStream, clientSideCache); + Protocol.readPushes(inputStream, clientSideCache, onlyPendingBuffer); } finally { - // super.rollbackTimeout(); lock.unlock(); } } @@ -59,7 +57,7 @@ protected void protocolReadPushes(RedisInputStream inputStream) { @Override public T executeCommand(final CommandObject commandObject) { - T data = clientSideCache.get(this, commandObject, commandObject.getArguments().keys()); + T data = clientSideCache.get(this, commandObject, commandObject.getArguments().getKeys()); return data; } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 65b8cb152e..ec20ce64ff 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -14,12 +14,10 @@ import redis.clients.jedis.util.SafeEncoder; /** - * The class to manage the client-side caching. User can provide any of - * implementation of this class to the client - * object; e.g. {@link redis.clients.jedis.csc.CaffeineClientSideCache - * CaffeineClientSideCache} or - * {@link redis.clients.jedis.csc.GuavaClientSideCache GuavaClientSideCache} or - * a custom implementation of their own. + * The class to manage the client-side caching. User can provide any of implementation of this class + * to the client object; e.g. {@link redis.clients.jedis.csc.CaffeineClientSideCache + * CaffeineClientSideCache} or {@link redis.clients.jedis.csc.GuavaClientSideCache + * GuavaClientSideCache} or a custom implementation of their own. */ @Experimental public abstract class ClientSideCache { @@ -78,6 +76,10 @@ private void invalidateRedisKeyAndRespectiveCacheEntries(Object key) { // final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key); final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); + // TODO: dont forget to clean up this + // System.out.println( + // "invalidation message received for :" + (key instanceof String ? (String) key + // : new String((byte[]) key))); Set> commands = redisKeysToCacheKeys.get(mapKey); if (commands != null) { remove(commands); @@ -93,40 +95,61 @@ public final T get(DataProvider dataProvider, CommandObject command, Obje final CacheKey cacheKey = new CacheKey(command); CacheEntry cacheEntry = get(cacheKey); + + // CACHE HIT !! if (cacheEntry != null) { - Connection cacheOwner = (Connection) cacheEntry.getConnection().get(); - if (cacheOwner == null) { - remove(Collections.singleton(cacheKey)); - // TODO: remove it from redisKeysToCacheKeys as well - } else { - if (cacheOwner == dataProvider.getSource()) { - dataProvider.consumeInvalidationMessages(); - } - cacheEntry = get(cacheKey); - if (cacheEntry != null) { - return (T) cacheEntry.getValue(); - } + cacheEntry = validateEntry(dataProvider, cacheKey, cacheEntry, keys); + if (cacheEntry != null) { + return (T) cacheEntry.getValue(); } } // CACHE MISS!! T value = dataProvider.getData(command); if (value != null) { - cacheEntry = new CacheEntry(cacheKey, value, new WeakReference(dataProvider.getSource())); - put(cacheKey, cacheEntry); - for (Object key : keys) { - ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); + storeInCache(dataProvider, cacheKey, value, keys); + } + return value; + } + + private CacheEntry validateEntry(DataProvider provider, CacheKey cacheKey, CacheEntry cacheEntry, + Object... redisKeys) { + Connection cacheOwner = (Connection) cacheEntry.getConnection().get(); + if (cacheOwner == null) { + remove(Collections.singleton(cacheKey)); + + // TODO: remove it from redisKeysToCacheKeys as well + for (Object redisKey : redisKeys) { + ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); if (redisKeysToCacheKeys.containsKey(mapKey)) { - redisKeysToCacheKeys.get(mapKey).add(cacheKey); - } else { - Set> set = ConcurrentHashMap.newKeySet(); - set.add(cacheKey); - redisKeysToCacheKeys.put(mapKey, set); + redisKeysToCacheKeys.get(mapKey).remove(cacheKey); } } + cacheEntry = null; + } else { + if (cacheOwner == provider.getSource()) { + provider.consumeInvalidationMessages(); + cacheEntry = get(cacheKey); + } } + return cacheEntry; + } - return value; + private void storeInCache(DataProvider provider, CacheKey cacheKey, Object value, + Object... redisKeys) { + CacheEntry cacheEntry = new CacheEntry(cacheKey, value, + new WeakReference(provider.getSource())); + put(cacheKey, cacheEntry); + for (Object redisKey : redisKeys) { + ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); + if (redisKeysToCacheKeys.containsKey(mapKey)) { + redisKeysToCacheKeys.get(mapKey).add(cacheKey); + } else { + Set> set = ConcurrentHashMap.newKeySet(); + set.add(cacheKey); + redisKeysToCacheKeys.put(mapKey, set); + } + } } private ByteBuffer makeKeyForRedisKeysToCacheKeys(Object key) { diff --git a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java index d97ca6a2ee..fe818068a1 100644 --- a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java +++ b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java @@ -1,15 +1,95 @@ package redis.clients.jedis.csc; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import redis.clients.jedis.Protocol.Command; import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.json.JsonProtocol.JsonCommand; +import redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesCommand; public class DefaultClientSideCacheable implements ClientSideCacheable { public static final DefaultClientSideCacheable INSTANCE = new DefaultClientSideCacheable(); - public DefaultClientSideCacheable() { } + private Map commandsToCache = new HashMap() { + { + put(Command.BITCOUNT, true); + put(Command.BITFIELD_RO, true); + put(Command.BITPOS, true); + put(Command.EXISTS, true); + put(Command.GEODIST, true); + put(Command.GEOHASH, true); + put(Command.GEOPOS, true); + put(Command.GEORADIUSBYMEMBER_RO, true); + put(Command.GEORADIUS_RO, true); + put(Command.GEOSEARCH, true); + put(Command.GET, true); + put(Command.GETBIT, true); + put(Command.GETRANGE, true); + put(Command.HEXISTS, true); + put(Command.HGET, true); + put(Command.HGETALL, true); + put(Command.HKEYS, true); + put(Command.HLEN, true); + put(Command.HMGET, true); + put(Command.HSTRLEN, true); + put(Command.HVALS, true); + put(JsonCommand.ARRINDEX, true); + put(JsonCommand.ARRLEN, true); + put(JsonCommand.GET, true); + put(JsonCommand.MGET, true); + put(JsonCommand.OBJKEYS, true); + put(JsonCommand.OBJLEN, true); + put(JsonCommand.STRLEN, true); + put(JsonCommand.TYPE, true); + put(Command.LCS, true); + put(Command.LINDEX, true); + put(Command.LLEN, true); + put(Command.LPOS, true); + put(Command.LRANGE, true); + put(Command.MGET, true); + put(Command.SCARD, true); + put(Command.SDIFF, true); + put(Command.SINTER, true); + put(Command.SISMEMBER, true); + put(Command.SMEMBERS, true); + put(Command.SMISMEMBER, true); + put(Command.STRLEN, true); + put(Command.SUBSTR, true); + put(Command.SUNION, true); + put(TimeSeriesCommand.GET, true); + put(TimeSeriesCommand.INFO, true); + put(TimeSeriesCommand.RANGE, true); + put(TimeSeriesCommand.REVRANGE, true); + put(Command.TYPE, true); + put(Command.XLEN, true); + put(Command.XPENDING, true); + put(Command.XRANGE, true); + put(Command.XREVRANGE, true); + put(Command.ZCARD, true); + put(Command.ZCOUNT, true); + put(Command.ZLEXCOUNT, true); + put(Command.ZMSCORE, true); + put(Command.ZRANGE, true); + put(Command.ZRANGEBYLEX, true); + put(Command.ZRANGEBYSCORE, true); + put(Command.ZRANK, true); + put(Command.ZREVRANGE, true); + put(Command.ZREVRANGEBYLEX, true); + put(Command.ZREVRANGEBYSCORE, true); + put(Command.ZREVRANK, true); + put(Command.ZSCORE, true); + } + }; + + public DefaultClientSideCacheable() { + } @Override public boolean isCacheable(ProtocolCommand command, Object... keys) { - return true; + Boolean cachable = commandsToCache.get(command); + return (cachable != null) ? cachable : false; } } diff --git a/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java b/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java index e9adbea37c..f0167bc532 100644 --- a/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java +++ b/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java @@ -2,9 +2,9 @@ import java.util.Set; import redis.clients.jedis.commands.ProtocolCommand; -import redis.clients.jedis.csc.ClientSideCacheable; +import redis.clients.jedis.csc.DefaultClientSideCacheable; -public class AllowAndDenyListWithStringKeys implements ClientSideCacheable { +public class AllowAndDenyListWithStringKeys extends DefaultClientSideCacheable { private final Set allowCommands; private final Set denyCommands; @@ -22,15 +22,20 @@ public AllowAndDenyListWithStringKeys(Set allowCommands, Set= limit) { - try { - limit = in.read(buf); - count = 0; - if (limit == -1) { - throw new JedisConnectionException("Unexpected end of stream."); - } - } catch (IOException e) { - // do nothing - } - } + @Override + public int available() throws IOException { + int availableInBuf = limit - count; + int availableInSocket = this.in.available(); + return (availableInBuf > availableInSocket) ? availableInBuf : availableInSocket; } + } diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index 8308233603..7f51e7b736 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -41,8 +41,8 @@ public void individualCommandsAndThenStats() { assertEquals(1, caffeine.estimatedSize()); control.flushAll(); assertEquals(1, caffeine.estimatedSize()); - assertEquals("bar", jedis.get("foo")); - assertEquals(1, caffeine.estimatedSize()); + assertEquals(null, jedis.get("foo")); + assertEquals(0, caffeine.estimatedSize()); jedis.ping(); assertEquals(0, caffeine.estimatedSize()); assertNull(jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java index 4c7362e63b..ea2310e29a 100644 --- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java @@ -41,8 +41,8 @@ public void individualCommandsAndThenStats() { assertEquals(1, guava.size()); control.flushAll(); assertEquals(1, guava.size()); - assertEquals("bar", jedis.get("foo")); - assertEquals(1, guava.size()); + assertEquals(null, jedis.get("foo")); + assertEquals(0, guava.size()); jedis.ping(); assertEquals(0, guava.size()); assertNull(jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index 4c3f25b5e8..fd8ad0b4f3 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -71,8 +71,8 @@ public void simpleWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.del("foo"); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(null, jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); jedis.ping(); assertThat(map, Matchers.aMapWithSize(0)); assertNull(jedis.get("foo")); @@ -101,8 +101,8 @@ public void flushAllWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.flushAll(); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(null, jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); jedis.ping(); assertThat(map, Matchers.aMapWithSize(0)); assertNull(jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index abc19bf8d7..962e9c549c 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -112,8 +112,8 @@ public void simpleWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.del("foo"); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(null, jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); jedis.ping(); assertThat(map, Matchers.aMapWithSize(0)); assertNull(jedis.get("foo")); @@ -142,8 +142,8 @@ public void flushAllWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.flushAll(); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(null, jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); jedis.ping(); assertThat(map, Matchers.aMapWithSize(0)); assertNull(jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index 1660396d23..356c90333e 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -68,8 +68,8 @@ public void simpleWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.del("foo"); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(null, jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); jedis.ping(); assertThat(map, Matchers.aMapWithSize(0)); assertNull(jedis.get("foo")); @@ -99,8 +99,8 @@ public void flushAllWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.flushAll(); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(null, jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); jedis.ping(); assertThat(map, Matchers.aMapWithSize(0)); assertNull(jedis.get("foo")); From f7c4fbf9ef00ec59602f968597d122ee762e9a3f Mon Sep 17 00:00:00 2001 From: atakavci Date: Wed, 24 Jul 2024 10:36:17 +0300 Subject: [PATCH 05/15] - add serialization for cache instances - add unit test with UnifiedJedis - add benchmark for CSC execution - clean unused imports --- .../java/redis/clients/jedis/Protocol.java | 2 - .../redis/clients/jedis/csc/CacheEntry.java | 33 ++++++- .../clients/jedis/csc/ClientSideCache.java | 7 +- .../redis/clients/jedis/csc/DataProvider.java | 2 - .../jedis/csc/DefaultClientSideCacheable.java | 1 - .../jedis/benchmark/CSCPooleadBenchmark.java | 85 +++++++++++++++++++ .../csc/ClientSideCacheFunctionalityTest.java | 43 ++++++++-- 7 files changed, 156 insertions(+), 17 deletions(-) create mode 100644 src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 694d69a11d..2a091dce0c 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -215,8 +215,6 @@ public static Object read(final RedisInputStream is) { @Experimental public static void readPushes(final RedisInputStream is, final ClientSideCache cache, boolean onlyPendingBuffer) { - boolean available = false; - if (onlyPendingBuffer) { try { while (is.available() > 0 && is.peek(GREATER_THAN_BYTE)) { diff --git a/src/main/java/redis/clients/jedis/csc/CacheEntry.java b/src/main/java/redis/clients/jedis/csc/CacheEntry.java index 358bf36391..000cfe7d9b 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheEntry.java +++ b/src/main/java/redis/clients/jedis/csc/CacheEntry.java @@ -1,5 +1,9 @@ package redis.clients.jedis.csc; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.lang.ref.WeakReference; import redis.clients.jedis.Connection; @@ -9,13 +13,13 @@ public class CacheEntry { private final CacheKey cacheKey; - private final T value; private final WeakReference connection; + private final byte[] bytes; public CacheEntry(CacheKey cacheKey, T value, WeakReference connection) { this.cacheKey = cacheKey; - this.value = value; this.connection = connection; + this.bytes = toBytes(value); } public CacheKey getCacheKey() { @@ -23,10 +27,33 @@ public CacheKey getCacheKey() { } public T getValue() { - return value; + return toObject(bytes); } public WeakReference getConnection() { return connection; } + + private static byte[] toBytes(Object object) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(object); + oos.flush(); + oos.close(); + return baos.toByteArray(); + } catch (Exception e) { + // TODO: handle this properly + throw new RuntimeException(e); + } + } + + private T toObject(byte[] data) { + try (ByteArrayInputStream bais = new ByteArrayInputStream(data); + ObjectInputStream ois = new ObjectInputStream(bais)) { + return (T) ois.readObject(); + } catch (Exception e) { + // TODO: handle this properly + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index ec20ce64ff..321123efbf 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -87,7 +87,7 @@ private void invalidateRedisKeyAndRespectiveCacheEntries(Object key) { } } - public final T get(DataProvider dataProvider, CommandObject command, Object... keys) { + protected final T get(DataProvider dataProvider, CommandObject command, Object... keys) { if (!cacheable.isCacheable(command.getArguments().getCommand(), keys)) { return dataProvider.getData(command); @@ -98,7 +98,7 @@ public final T get(DataProvider dataProvider, CommandObject command, Obje // CACHE HIT !! if (cacheEntry != null) { - cacheEntry = validateEntry(dataProvider, cacheKey, cacheEntry, keys); + cacheEntry = validateEntry(dataProvider, cacheEntry, keys); if (cacheEntry != null) { return (T) cacheEntry.getValue(); } @@ -112,8 +112,9 @@ public final T get(DataProvider dataProvider, CommandObject command, Obje return value; } - private CacheEntry validateEntry(DataProvider provider, CacheKey cacheKey, CacheEntry cacheEntry, + private CacheEntry validateEntry(DataProvider provider, CacheEntry cacheEntry, Object... redisKeys) { + CacheKey cacheKey = cacheEntry.getCacheKey(); Connection cacheOwner = (Connection) cacheEntry.getConnection().get(); if (cacheOwner == null) { remove(Collections.singleton(cacheKey)); diff --git a/src/main/java/redis/clients/jedis/csc/DataProvider.java b/src/main/java/redis/clients/jedis/csc/DataProvider.java index c00dd6c1b7..e5dfb36eab 100644 --- a/src/main/java/redis/clients/jedis/csc/DataProvider.java +++ b/src/main/java/redis/clients/jedis/csc/DataProvider.java @@ -1,7 +1,5 @@ package redis.clients.jedis.csc; -import java.lang.ref.WeakReference; - import redis.clients.jedis.CommandObject; import redis.clients.jedis.Connection; diff --git a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java index fe818068a1..b510b5bf0d 100644 --- a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java +++ b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java @@ -1,6 +1,5 @@ package redis.clients.jedis.csc; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; diff --git a/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java new file mode 100644 index 0000000000..60485129af --- /dev/null +++ b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java @@ -0,0 +1,85 @@ +package redis.clients.jedis.benchmark; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import redis.clients.jedis.*; +import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.MapClientSideCache; + +public class CSCPooleadBenchmark { + + private static EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone0"); + private static final int TOTAL_OPERATIONS = 100000; + + public static void main(String[] args) throws Exception { + try (Jedis j = new Jedis(endpoint.getHost(), endpoint.getPort())) { + j.auth(endpoint.getPassword()); + j.flushAll(); + j.disconnect(); + } + + int totalRounds = 10; + long withoutCache = 0; + long withCache = 0; + for (int i = 0; i < totalRounds; i++) { + withoutCache += runBenchmark(null); + } + for (int i = 0; i < totalRounds; i++) { + withCache += runBenchmark(new MapClientSideCache()); + } + System.out.println(String.format("after first round withoutCache: %d ms, withCache: %d ms", withoutCache, withCache)); + + for (int i = 0; i < totalRounds; i++) { + withoutCache += runBenchmark(null); + } + for (int i = 0; i < totalRounds; i++) { + withCache += runBenchmark(new MapClientSideCache()); + } + System.out.println(String.format("after second round withoutCache: %d ms, withCache: %d ms", withoutCache, withCache)); + System.out.println("execution time ratio: " + (double) withCache / withoutCache); + } + + private static long runBenchmark(ClientSideCache cache) throws Exception { + long start = System.currentTimeMillis(); + withPool(cache); + long elapsed = System.currentTimeMillis() - start; + System.out.println(String.format("%s round elapsed: %d ms", cache == null ? "no cache" : "cached", elapsed)); + return elapsed; + } + + private static void withPool(ClientSideCache cache) throws Exception { + JedisPooled jedis = null; + JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP3).password(endpoint.getPassword()).build(); + jedis = new JedisPooled(endpoint.getHostAndPort(), config, cache); + final JedisPooled j = jedis; + List tds = new ArrayList<>(); + + final AtomicInteger ind = new AtomicInteger(); + for (int i = 0; i < 50; i++) { + Thread hj = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) { + try { + final String key = "foo" + i; + j.set(key, key); + j.get(key); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }); + tds.add(hj); + hj.start(); + } + + for (Thread t : tds) { + t.join(); + } + + j.close(); + } +} diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index 332648f424..2dea19c908 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -1,20 +1,19 @@ package redis.clients.jedis.csc; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; - -import java.net.URI; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; + +import org.junit.Assert; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.util.JedisURIHelper; +import redis.clients.jedis.UnifiedJedis; public class ClientSideCacheFunctionalityTest extends ClientSideCacheTestBase { @@ -77,4 +76,36 @@ public void multiKeyOperation() { } } + @Test + public void testInvalidationWithUnifiedJedis() { + GuavaClientSideCache clientSideCacheGuava = GuavaClientSideCache.builder() + .maximumSize(1000) + .ttl(100) + .build(); + + CaffeineClientSideCache clientSideCacheCaffeine = CaffeineClientSideCache.builder() + .maximumSize(1000) + .ttl(100) + .build(); + + GuavaClientSideCache mock = Mockito.spy(clientSideCacheGuava); + UnifiedJedis client = new UnifiedJedis(hnp, clientConfig.get(), mock); + UnifiedJedis clientCaffeine = new UnifiedJedis(hnp, clientConfig.get(), clientSideCacheCaffeine); + + // "foo" is cached + client.set("foo", "bar"); + client.get("foo"); // read from the server + Assert.assertEquals("bar", client.get("foo")); // cache hit + + // Using another connection + clientCaffeine.set("foo", "bar2"); + Assert.assertEquals("bar2", clientCaffeine.get("foo")); + + //invalidating the cache and read it back from server + Assert.assertEquals("bar2", client.get("foo")); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(GuavaClientSideCache.class); + Mockito.verify(mock, Mockito.times(1)).invalidate(Mockito.anyList()); + Mockito.verify(mock, Mockito.times(2)).put(Mockito.any(CacheKey.class), Mockito.any(CacheEntry.class)); + } } From fc137c01b409305954e4a741f4ec2e43a876a13d Mon Sep 17 00:00:00 2001 From: atakavci Date: Fri, 26 Jul 2024 14:20:11 +0300 Subject: [PATCH 06/15] - added 'Cache' interface and 'DefaultCache' implementation in regard to design doc - added 'EvictionPolicy' interface and LRU implementation - move cache object validation and cache control stuf from 'ClientSideCache' into 'CacheConnection' - make guava and caffeine caches experimental --- .../clients/jedis/ConnectionFactory.java | 6 +- .../redis/clients/jedis/ConnectionPool.java | 6 +- .../redis/clients/jedis/JedisCluster.java | 14 +- .../clients/jedis/JedisClusterInfoCache.java | 10 +- .../java/redis/clients/jedis/JedisPooled.java | 6 +- .../redis/clients/jedis/JedisSentineled.java | 6 +- .../java/redis/clients/jedis/Protocol.java | 8 +- .../redis/clients/jedis/UnifiedJedis.java | 12 +- .../java/redis/clients/jedis/csc/Cache.java | 98 +++++++++ .../clients/jedis/csc/CacheConnection.java | 61 ++++-- .../redis/clients/jedis/csc/CacheKey.java | 8 + .../jedis/csc/CaffeineClientSideCache.java | 52 ++++- .../clients/jedis/csc/ClientSideCache.java | 202 +++++++++--------- .../redis/clients/jedis/csc/DataProvider.java | 13 -- .../redis/clients/jedis/csc/DefaultCache.java | 72 +++++++ .../clients/jedis/csc/EvictionPolicy.java | 71 ++++++ .../jedis/csc/GuavaClientSideCache.java | 56 ++++- .../redis/clients/jedis/csc/LRUEviction.java | 90 ++++++++ .../providers/ClusterConnectionProvider.java | 8 +- .../providers/PooledConnectionProvider.java | 6 +- .../SentineledConnectionProvider.java | 16 +- .../jedis/benchmark/CSCPooleadBenchmark.java | 12 +- .../AllowAndDenyListClientSideCacheTest.java | 15 +- .../csc/ClientSideCacheFunctionalityTest.java | 60 +++--- .../csc/JedisClusterClientSideCacheTest.java | 8 +- .../csc/JedisPooledClientSideCacheTest.java | 10 +- .../JedisSentineledClientSideCacheTest.java | 8 +- .../clients/jedis/csc/MapClientSideCache.java | 38 ---- .../redis/clients/jedis/csc/TestCache.java | 20 ++ 29 files changed, 699 insertions(+), 293 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/Cache.java delete mode 100644 src/main/java/redis/clients/jedis/csc/DataProvider.java create mode 100644 src/main/java/redis/clients/jedis/csc/DefaultCache.java create mode 100644 src/main/java/redis/clients/jedis/csc/EvictionPolicy.java create mode 100644 src/main/java/redis/clients/jedis/csc/LRUEviction.java delete mode 100644 src/test/java/redis/clients/jedis/csc/MapClientSideCache.java create mode 100644 src/test/java/redis/clients/jedis/csc/TestCache.java diff --git a/src/main/java/redis/clients/jedis/ConnectionFactory.java b/src/main/java/redis/clients/jedis/ConnectionFactory.java index 722c3e32c8..cc53df56f0 100644 --- a/src/main/java/redis/clients/jedis/ConnectionFactory.java +++ b/src/main/java/redis/clients/jedis/ConnectionFactory.java @@ -7,8 +7,8 @@ import org.slf4j.LoggerFactory; import redis.clients.jedis.annots.Experimental; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.csc.CacheConnection; -import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisException; /** @@ -20,7 +20,7 @@ public class ConnectionFactory implements PooledObjectFactory { private final JedisSocketFactory jedisSocketFactory; private final JedisClientConfig clientConfig; - private ClientSideCache clientSideCache = null; + private Cache clientSideCache = null; public ConnectionFactory(final HostAndPort hostAndPort) { this.clientConfig = DefaultJedisClientConfig.builder().build(); @@ -33,7 +33,7 @@ public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig } @Experimental - public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache) { + public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache csCache) { this.clientConfig = clientConfig; this.jedisSocketFactory = new DefaultJedisSocketFactory(hostAndPort, this.clientConfig); this.clientSideCache = csCache; diff --git a/src/main/java/redis/clients/jedis/ConnectionPool.java b/src/main/java/redis/clients/jedis/ConnectionPool.java index 49b0fe803d..40d4861f98 100644 --- a/src/main/java/redis/clients/jedis/ConnectionPool.java +++ b/src/main/java/redis/clients/jedis/ConnectionPool.java @@ -3,7 +3,7 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.annots.Experimental; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.util.Pool; public class ConnectionPool extends Pool { @@ -13,7 +13,7 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig) { } @Experimental - public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache) { this(new ConnectionFactory(hostAndPort, clientConfig, clientSideCache)); } @@ -27,7 +27,7 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, } @Experimental - public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache, GenericObjectPoolConfig poolConfig) { this(new ConnectionFactory(hostAndPort, clientConfig, clientSideCache), poolConfig); } diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index 6646409a24..94e3f561e0 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -8,7 +8,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.providers.ClusterConnectionProvider; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.util.JedisClusterCRC16; public class JedisCluster extends UnifiedJedis { @@ -218,27 +218,27 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati } @Experimental - public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache) { this(clusterNodes, clientConfig, clientSideCache, DEFAULT_MAX_ATTEMPTS, Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis())); } @Experimental - public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache, int maxAttempts, Duration maxTotalRetriesDuration) { this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache), maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache); } @Experimental - public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache, int maxAttempts, Duration maxTotalRetriesDuration, GenericObjectPoolConfig poolConfig) { this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig), maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache); } @Experimental - public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache, GenericObjectPoolConfig poolConfig) { this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig), DEFAULT_MAX_ATTEMPTS, Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis()), @@ -246,7 +246,7 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi } @Experimental - public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache, GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod, int maxAttempts, Duration maxTotalRetriesDuration) { this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig, topologyRefreshPeriod), @@ -255,7 +255,7 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi @Experimental private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, - RedisProtocol protocol, ClientSideCache clientSideCache) { + RedisProtocol protocol, Cache clientSideCache) { super(provider, maxAttempts, maxTotalRetriesDuration, protocol, clientSideCache); } diff --git a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java index bc150dbcb0..700f49e272 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java +++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java @@ -25,7 +25,7 @@ import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.annots.Internal; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.exceptions.JedisClusterOperationException; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.util.SafeEncoder; @@ -48,7 +48,7 @@ public class JedisClusterInfoCache { private final GenericObjectPoolConfig poolConfig; private final JedisClientConfig clientConfig; - private final ClientSideCache clientSideCache; + private final Cache clientSideCache; private final Set startNodes; private static final int MASTER_NODE_INDEX = 2; @@ -72,7 +72,7 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, final Set startNodes) { this(clientConfig, clientSideCache, null, startNodes); } @@ -83,7 +83,7 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, } @Experimental - public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public JedisClusterInfoCache(final JedisClientConfig clientConfig, Cache clientSideCache, final GenericObjectPoolConfig poolConfig, final Set startNodes) { this(clientConfig, clientSideCache, poolConfig, startNodes, null); } @@ -95,7 +95,7 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, } @Experimental - public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public JedisClusterInfoCache(final JedisClientConfig clientConfig, Cache clientSideCache, final GenericObjectPoolConfig poolConfig, final Set startNodes, final Duration topologyRefreshPeriod) { this.poolConfig = poolConfig; diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 504141404f..498bcb02c8 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -8,7 +8,7 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.annots.Experimental; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; @@ -78,7 +78,7 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client } @Experimental - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache) { super(hostAndPort, clientConfig, clientSideCache); } @@ -383,7 +383,7 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client } @Experimental - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache, final GenericObjectPoolConfig poolConfig) { super(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache, poolConfig), clientConfig.getRedisProtocol(), clientSideCache); diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index f89764d22c..35585f713f 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -3,7 +3,7 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.annots.Experimental; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.providers.SentineledConnectionProvider; public class JedisSentineled extends UnifiedJedis { @@ -15,7 +15,7 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo } @Experimental - public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, Cache clientSideCache, Set sentinels, final JedisClientConfig sentinelClientConfig) { super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache); @@ -29,7 +29,7 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo } @Experimental - public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, Cache clientSideCache, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, poolConfig, diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 2a091dce0c..80b485b40c 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -12,7 +12,7 @@ import redis.clients.jedis.exceptions.*; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.commands.ProtocolCommand; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.util.KeyValue; import redis.clients.jedis.util.RedisInputStream; import redis.clients.jedis.util.RedisOutputStream; @@ -214,7 +214,7 @@ public static Object read(final RedisInputStream is) { } @Experimental - public static void readPushes(final RedisInputStream is, final ClientSideCache cache, boolean onlyPendingBuffer) { + public static void readPushes(final RedisInputStream is, final Cache cache, boolean onlyPendingBuffer) { if (onlyPendingBuffer) { try { while (is.available() > 0 && is.peek(GREATER_THAN_BYTE)) { @@ -234,11 +234,11 @@ public static void readPushes(final RedisInputStream is, final ClientSideCache c } } - private static void processPush(final RedisInputStream is, ClientSideCache cache) { + private static void processPush(final RedisInputStream is, Cache cache) { List list = processMultiBulkReply(is); if (list.size() == 2 && list.get(0) instanceof byte[] && Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) { - cache.invalidate((List) list.get(1)); + cache.deleteByRedisKeys((List) list.get(1)); } } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index d89c109c85..405220b83e 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -19,7 +19,7 @@ import redis.clients.jedis.commands.SampleBinaryKeyedCommands; import redis.clients.jedis.commands.SampleKeyedCommands; import redis.clients.jedis.commands.RedisModuleCommands; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.executors.*; import redis.clients.jedis.gears.TFunctionListParams; @@ -95,7 +95,7 @@ public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) { } @Experimental - public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache) { this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(), clientSideCache); } @@ -109,7 +109,7 @@ protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol) { } @Experimental - protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, ClientSideCache clientSideCache) { + protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, Cache clientSideCache) { this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol, clientSideCache); } @@ -183,7 +183,7 @@ protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Dura @Experimental protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, - RedisProtocol protocol, ClientSideCache clientSideCache) { + RedisProtocol protocol, Cache clientSideCache) { this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, new ClusterCommandObjects(), protocol, clientSideCache); } @@ -254,12 +254,12 @@ public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comma @Experimental private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, RedisProtocol protocol) { - this(executor, provider, commandObjects, protocol, (ClientSideCache) null); + this(executor, provider, commandObjects, protocol, (Cache) null); } @Experimental private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, - RedisProtocol protocol, ClientSideCache clientSideCache) { + RedisProtocol protocol, Cache clientSideCache) { if (clientSideCache != null && protocol != RedisProtocol.RESP3) { throw new IllegalArgumentException("Client-side caching is only supported with RESP3."); diff --git a/src/main/java/redis/clients/jedis/csc/Cache.java b/src/main/java/redis/clients/jedis/csc/Cache.java new file mode 100644 index 0000000000..04ab0217df --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/Cache.java @@ -0,0 +1,98 @@ +package redis.clients.jedis.csc; + +import java.util.Collection; +import java.util.List; + +/** + * The cache that is used by a connection + */ +public interface Cache { + + /** + * @return The size of the cache + */ + int getMaxSize(); + + /** + * @return The current size of the cache + */ + int getSize(); + + /** + * @return All the entries within the cache + */ + Collection getCacheEntries(); + + /** + * Fetches a value from the cache + * + * @param cacheKey The key within the cache + * @return The entry within the cache + */ + CacheEntry get(CacheKey cacheKey); + + /** + * Puts a value into the cache + * + * @param cacheKey The key by which the value can be accessed within the cache + * @param value The value to be put into the cache + * @return The cache entry + */ + CacheEntry set(CacheKey cacheKey, CacheEntry value); + + /** + * Delete an entry by cache key + * @param cacheKey The cache key of the entry in the cache + * @return True if the entry could be deleted, false if the entry wasn't found. + */ + Boolean delete(CacheKey cacheKey); + + /** + * Delete entries by cache key from the cache + * + * @param cacheKeys The cache keys of the entries that should be deleted + * @return True for every entry that could be deleted. False if the entry was not there. + */ + List delete(List cacheKeys); + + /** + * Delete an entry by the Redis key from the cache + * + * @param key The Redis key as binary + * @return True if the entry could be deleted. False if the entry was not there. + */ + List deleteByRedisKey(Object key); + + /** + * Delete entries by the Redis key from the cache + * + * @param keys The Redis keys as binaries + * @return True for every entry that could be deleted. False if the entry was not there. + */ + List deleteByRedisKeys(List keys); + + /** + * Flushes the entire cache + * + * @return Return the number of entries that were flushed + */ + int flush(); + + /** + * @param cacheKey The key of the cache entry + * @return True if the entry is cachable, false otherwise + */ + Boolean isCacheable(CacheKey cacheKey); + + /** + * + * @param cacheKey The key of the cache entry + * @return True if the cache already contains the key + */ + Boolean hasCacheKey(CacheKey cacheKey); + + /** + * @return The eviction policy that is used by the cache + */ + EvictionPolicy getEvictionPolicy(); +} diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 51e0317b63..8a59b3ecdc 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -1,5 +1,6 @@ package redis.clients.jedis.csc; +import java.lang.ref.WeakReference; import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; import redis.clients.jedis.CommandObject; @@ -11,14 +12,12 @@ import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.util.RedisInputStream; -public class CacheConnection extends Connection implements DataProvider { +public class CacheConnection extends Connection { - private final ClientSideCache clientSideCache; + private final Cache clientSideCache; private final ReentrantLock lock; - public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, - ClientSideCache clientSideCache) { - + public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, Cache clientSideCache) { super(socketFactory, clientConfig); if (protocol != RedisProtocol.RESP3) { @@ -57,8 +56,31 @@ protected void protocolReadPushes(RedisInputStream inputStream, boolean onlyPend @Override public T executeCommand(final CommandObject commandObject) { - T data = clientSideCache.get(this, commandObject, commandObject.getArguments().getKeys()); - return data; + CacheKey key = new CacheKey<>(commandObject); + if (!clientSideCache.isCacheable(key)) { + return super.executeCommand(commandObject); + } + + final CacheKey cacheKey = new CacheKey(commandObject); + CacheEntry cacheEntry = clientSideCache.get(cacheKey); + + // CACHE HIT !! + if (cacheEntry != null) { + cacheEntry = validateEntry(cacheEntry); + if (cacheEntry != null) { + return (T) cacheEntry.getValue(); + } + } + + // CACHE MISS!! + T value = super.executeCommand(commandObject); + if (value != null) { + cacheEntry = new CacheEntry(cacheKey, value, new WeakReference(this)); + clientSideCache.set(cacheKey, cacheEntry); + // this line actually provides a deep copy of cached object instance + value = cacheEntry.getValue(); + } + return value; } private void initializeClientSideCache() { @@ -69,18 +91,17 @@ private void initializeClientSideCache() { } } - @Override - public Connection getSource() { - return this; - } - - @Override - public T getData(CommandObject commandObject) { - return super.executeCommand(commandObject); - } - - @Override - public void consumeInvalidationMessages() { - this.readPushesWithCheckingBroken(); + private CacheEntry validateEntry(CacheEntry cacheEntry) { + Connection cacheOwner = (Connection) cacheEntry.getConnection().get(); + if (cacheOwner == null) { + clientSideCache.delete(cacheEntry.getCacheKey()); + return null; + } else { + if (cacheOwner == this) { + this.readPushesWithCheckingBroken(); + cacheEntry = clientSideCache.get(cacheEntry.getCacheKey()); + } + } + return cacheEntry; } } diff --git a/src/main/java/redis/clients/jedis/csc/CacheKey.java b/src/main/java/redis/clients/jedis/csc/CacheKey.java index b3e8244b20..4e854550fe 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheKey.java +++ b/src/main/java/redis/clients/jedis/csc/CacheKey.java @@ -25,4 +25,12 @@ public boolean equals(Object obj) { final CacheKey other = (CacheKey) obj; return Objects.equals(this.command, other.command); } + + public Object[] getRedisKeys() { + return command.getArguments().getKeys(); + } + + public CommandObject getCommand() { + return command; + } } diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java index fe320418e9..0230f23cc3 100644 --- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java @@ -2,33 +2,39 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; + +import redis.clients.jedis.annots.Experimental; + +import java.util.Collection; import java.util.concurrent.TimeUnit; +@Experimental public class CaffeineClientSideCache extends ClientSideCache { private final Cache cache; + protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; + protected static final int DEFAULT_EXPIRE_SECONDS = 100; + private final LRUEviction evictionPolicy; public CaffeineClientSideCache(Cache caffeineCache) { + super(DEFAULT_MAXIMUM_SIZE); this.cache = caffeineCache; + this.evictionPolicy = new LRUEviction(this, DEFAULT_MAXIMUM_SIZE); } @Override - protected final void clear() { + protected final void clearStore() { cache.invalidateAll(); } @Override - protected void remove(Iterable> keys) { - cache.invalidateAll(keys); - } - - @Override - protected void put(CacheKey key, CacheEntry entry) { + public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { cache.put(key, entry); + return entry; } @Override - protected CacheEntry get(CacheKey key) { + public CacheEntry getFromStore(CacheKey key) { return cache.getIfPresent(key); } @@ -42,7 +48,8 @@ public static class Builder { private long expireTime = DEFAULT_EXPIRE_SECONDS; private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; - private Builder() { } + private Builder() { + } public Builder maximumSize(int size) { this.maximumSize = size; @@ -64,4 +71,31 @@ public CaffeineClientSideCache build() { return new CaffeineClientSideCache(cb.build()); } } + + @Override + public int getSize() { + return (int) cache.estimatedSize(); + } + + @Override + public Collection getCacheEntries() { + throw new UnsupportedOperationException("Unimplemented method 'getCacheEntries'"); + } + + @Override + public EvictionPolicy getEvictionPolicy() { + return this.evictionPolicy; + } + + @Override + protected Boolean removeFromStore(CacheKey cacheKey) { + cache.invalidate(cacheKey); + return true; + } + + @Override + protected Boolean containsKeyInStore(CacheKey cacheKey) { + return cache.getIfPresent(cacheKey) != null; + } + } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 321123efbf..67d26dee15 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -1,15 +1,14 @@ package redis.clients.jedis.csc; -import java.lang.ref.WeakReference; import java.nio.ByteBuffer; -import java.util.Collections; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import redis.clients.jedis.CommandObject; -import redis.clients.jedis.Connection; +import java.util.stream.Collectors; + import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.util.SafeEncoder; @@ -20,139 +19,146 @@ * GuavaClientSideCache} or a custom implementation of their own. */ @Experimental -public abstract class ClientSideCache { - - protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; - protected static final int DEFAULT_EXPIRE_SECONDS = 100; +public abstract class ClientSideCache implements Cache { - private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>(); private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile + private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>(); + private final int maximumSize; - protected ClientSideCache() { + protected ClientSideCache(int maximumSize) { + this.maximumSize = maximumSize; } - public void setCacheable(ClientSideCacheable cacheable) { - this.cacheable = Objects.requireNonNull(cacheable, "'cacheable' must not be null"); + protected ClientSideCache(int maximumSize,ClientSideCacheable cacheable ) { + this.maximumSize = maximumSize; + this.cacheable = cacheable; } - protected abstract void clear(); + // Cache interface methods - protected abstract void remove(Iterable> keys); + @Override + public int getMaxSize() { + return maximumSize; + } - protected abstract void put(CacheKey key, CacheEntry entry); + @Override + public abstract int getSize(); - protected abstract CacheEntry get(CacheKey key); + @Override + public abstract Collection getCacheEntries(); - public final void flush() { - invalidateAllRedisKeysAndCacheEntries(); + @Override + public CacheEntry get(CacheKey cacheKey) { + return getFromStore(cacheKey); } - public final void invalidateKey(Object key) { - invalidateRedisKeyAndRespectiveCacheEntries(key); + @Override + public CacheEntry set(CacheKey cacheKey, CacheEntry entry) { + entry = putIntoStore(cacheKey, entry); + getEvictionPolicy().touch(cacheKey); + getEvictionPolicy().evictNext(); + for (Object redisKey : cacheKey.getRedisKeys()) { + ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); + if (redisKeysToCacheKeys.containsKey(mapKey)) { + redisKeysToCacheKeys.get(mapKey).add(cacheKey); + } else { + Set> set = ConcurrentHashMap.newKeySet(); + set.add(cacheKey); + redisKeysToCacheKeys.put(mapKey, set); + } + } + return entry; } - public final void invalidate(List list) { - if (list == null) { - invalidateAllRedisKeysAndCacheEntries(); - return; - } + @Override + public Boolean delete(CacheKey cacheKey) { + boolean removed = removeFromStore(cacheKey); + getEvictionPolicy().reset(cacheKey); - list.forEach(this::invalidateRedisKeyAndRespectiveCacheEntries); + // removing it from redisKeysToCacheKeys as well + // TODO: considering not doing it, what is the impact of not doing it ?? + for (Object redisKey : cacheKey.getRedisKeys()) { + ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); + if (redisKeysToCacheKeys.containsKey(mapKey)) { + redisKeysToCacheKeys.get(mapKey).remove(cacheKey); + } + } + return removed; } - private void invalidateAllRedisKeysAndCacheEntries() { - clear(); - redisKeysToCacheKeys.clear(); + @Override + public List delete(List cacheKeys) { + return cacheKeys.stream().map(this::delete).collect(Collectors.toList()); } - private void invalidateRedisKeyAndRespectiveCacheEntries(Object key) { - // if (!(key instanceof byte[])) { - // // This should be called internally. That's why throwing AssertionError - // instead of IllegalArgumentException. - // throw new AssertionError("" + key.getClass().getSimpleName() + " is not - // supported. Value: " + String.valueOf(key)); - // } - // - // final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key); + @Override + public List deleteByRedisKey(Object key) { final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); // TODO: dont forget to clean up this // System.out.println( // "invalidation message received for :" + (key instanceof String ? (String) key // : new String((byte[]) key))); + Set> commands = redisKeysToCacheKeys.get(mapKey); + List cacheKeys = new ArrayList<>(); if (commands != null) { - remove(commands); + cacheKeys.addAll(commands.stream().filter(this::removeFromStore).collect(Collectors.toList())); redisKeysToCacheKeys.remove(mapKey); } + return cacheKeys; } - protected final T get(DataProvider dataProvider, CommandObject command, Object... keys) { - - if (!cacheable.isCacheable(command.getArguments().getCommand(), keys)) { - return dataProvider.getData(command); - } - - final CacheKey cacheKey = new CacheKey(command); - CacheEntry cacheEntry = get(cacheKey); - - // CACHE HIT !! - if (cacheEntry != null) { - cacheEntry = validateEntry(dataProvider, cacheEntry, keys); - if (cacheEntry != null) { - return (T) cacheEntry.getValue(); - } + @Override + public List deleteByRedisKeys(List keys) { + if (keys == null) { + flush(); + return null; } + + return ((List) keys).stream() + .map(this::deleteByRedisKey).flatMap(List::stream).collect(Collectors.toList()); + } - // CACHE MISS!! - T value = dataProvider.getData(command); - if (value != null) { - storeInCache(dataProvider, cacheKey, value, keys); - } - return value; + @Override + public int flush() { + int result = this.getSize(); + clearStore(); + redisKeysToCacheKeys.clear(); + getEvictionPolicy().resetAll(); + return result; } - private CacheEntry validateEntry(DataProvider provider, CacheEntry cacheEntry, - Object... redisKeys) { - CacheKey cacheKey = cacheEntry.getCacheKey(); - Connection cacheOwner = (Connection) cacheEntry.getConnection().get(); - if (cacheOwner == null) { - remove(Collections.singleton(cacheKey)); - - // TODO: remove it from redisKeysToCacheKeys as well - for (Object redisKey : redisKeys) { - ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); - if (redisKeysToCacheKeys.containsKey(mapKey)) { - redisKeysToCacheKeys.get(mapKey).remove(cacheKey); - } - } - cacheEntry = null; - } else { - if (cacheOwner == provider.getSource()) { - provider.consumeInvalidationMessages(); - cacheEntry = get(cacheKey); - } - } - return cacheEntry; + @Override + public Boolean isCacheable(CacheKey cacheKey) { + return cacheable.isCacheable(cacheKey.getCommand().getArguments().getCommand(), cacheKey.getRedisKeys()); } - private void storeInCache(DataProvider provider, CacheKey cacheKey, Object value, - Object... redisKeys) { - CacheEntry cacheEntry = new CacheEntry(cacheKey, value, - new WeakReference(provider.getSource())); - put(cacheKey, cacheEntry); - for (Object redisKey : redisKeys) { - ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); - if (redisKeysToCacheKeys.containsKey(mapKey)) { - redisKeysToCacheKeys.get(mapKey).add(cacheKey); - } else { - Set> set = ConcurrentHashMap.newKeySet(); - set.add(cacheKey); - redisKeysToCacheKeys.put(mapKey, set); - } - } + @Override + public Boolean hasCacheKey(CacheKey cacheKey) { + return containsKeyInStore(cacheKey); } + @Override + public abstract EvictionPolicy getEvictionPolicy(); + + // End of Cache interface methods + + // abstract methods to be implemented by the concrete classes + protected abstract CacheEntry getFromStore(CacheKey cacheKey); + + protected abstract CacheEntry putIntoStore(CacheKey cacheKey, CacheEntry entry); + + protected abstract Boolean removeFromStore(CacheKey cacheKey); + + // protected abstract Collection remove(Set> commands); + + protected abstract void clearStore(); + + protected abstract Boolean containsKeyInStore(CacheKey cacheKey); + + // End of abstract methods to be implemented by the concrete classes + private ByteBuffer makeKeyForRedisKeysToCacheKeys(Object key) { if (key instanceof byte[]) { return makeKeyForRedisKeysToCacheKeys((byte[]) key); diff --git a/src/main/java/redis/clients/jedis/csc/DataProvider.java b/src/main/java/redis/clients/jedis/csc/DataProvider.java deleted file mode 100644 index e5dfb36eab..0000000000 --- a/src/main/java/redis/clients/jedis/csc/DataProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package redis.clients.jedis.csc; - -import redis.clients.jedis.CommandObject; -import redis.clients.jedis.Connection; - -public interface DataProvider { - - public Connection getSource(); - - public T getData(CommandObject commandObject); - - public void consumeInvalidationMessages(); -} diff --git a/src/main/java/redis/clients/jedis/csc/DefaultCache.java b/src/main/java/redis/clients/jedis/csc/DefaultCache.java new file mode 100644 index 0000000000..1ef77b3f61 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/DefaultCache.java @@ -0,0 +1,72 @@ +package redis.clients.jedis.csc; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class DefaultCache extends ClientSideCache { + + protected final Map cache; + private final LRUEviction evictionPolicy; + + public DefaultCache(int maximumSize) { + this(maximumSize, new HashMap()); + } + + public DefaultCache(int maximumSize, Map map) { + super(maximumSize); + this.cache = map; + this.evictionPolicy = new LRUEviction(this, maximumSize); + } + + public DefaultCache(int maximumSize, ClientSideCacheable cacheable) { + this(maximumSize, null, cacheable); + } + + public DefaultCache(int maximumSize, Map map, ClientSideCacheable cacheable) { + super(maximumSize, cacheable); + this.cache = map; + this.evictionPolicy = new LRUEviction(this, maximumSize); + } + + @Override + public int getSize() { + return cache.size(); + } + + @Override + public Collection getCacheEntries() { + return cache.values(); + } + + @Override + public EvictionPolicy getEvictionPolicy() { + return this.evictionPolicy; + } + + @Override + public CacheEntry getFromStore(CacheKey key) { + return cache.get(key); + } + + @Override + public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { + return cache.put(key, entry); + } + + @Override + public Boolean removeFromStore(CacheKey key) { + return cache.remove(key) != null; + } + + @Override + protected final void clearStore() { + cache.clear(); + } + + @Override + protected Boolean containsKeyInStore(CacheKey cacheKey) { + return cache.containsKey(cacheKey); + } + +} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java b/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java new file mode 100644 index 0000000000..b4b4f29128 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java @@ -0,0 +1,71 @@ +package redis.clients.jedis.csc; + +import java.util.List; + +/** + * Describes the properties and functionality of an eviction policy + *

+ * One policy instance belongs to exactly one cache instance + */ +public interface EvictionPolicy { + + /** + * Types of eviction policies + * + * AGE - based on the time of access, e.g., LRU + * FREQ - based on the frequency of access, e.g., LFU + * HYBR - AGE + FREQ, e.g., CLOCK + * MISC - Anythin that isn't time based, frequency based or a combination of the two, e.g., FIFO + */ + enum EvictionType { + AGE, FREQ, HYBR, MISC + } + + /** + * @return The cache that is associated to this policy instance + */ + Cache getCache(); + + /** + * @return The type of policy + */ + EvictionType getType(); + + /** + * @return The name of the policy + */ + String getName(); + + /** + * Evict the next element from the cache + * This one should provide O(1) complexity + * @return The key of the entry that was evicted + */ + CacheKey evictNext(); + + /** + * + * @param n The number of entries to evict + * @return The list of keys of evicted entries + */ + List evictMany(int n); + + /** + * Indicates that a cache key was touched + * This one should provide O(1) complexity + * @param cacheKey The key within the cache + */ + void touch(CacheKey cacheKey); + + /** + * Resets the state that the eviction policy maintains about the cache key + * @param cacheKey + */ + boolean reset(CacheKey cacheKey); + + /** + * Resets the entire state of the eviction data + * @return True if the reset could be performed successfully + */ + int resetAll(); +} diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java index 8adfc69b26..6eb79db1d3 100644 --- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java @@ -2,34 +2,48 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; + +import redis.clients.jedis.annots.Experimental; + +import java.util.Collection; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +@Experimental public class GuavaClientSideCache extends ClientSideCache { private final Cache cache; + protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; + protected static final int DEFAULT_EXPIRE_SECONDS = 100; + private final LRUEviction evictionPolicy; public GuavaClientSideCache(Cache guavaCache) { - super(); + super(DEFAULT_MAXIMUM_SIZE); this.cache = guavaCache; + this.evictionPolicy = new LRUEviction(this, DEFAULT_MAXIMUM_SIZE); } @Override - protected final void clear() { + public final void clearStore() { cache.invalidateAll(); } - @Override - protected void remove(Iterable> keys) { + public List remove(Iterable> keys) { cache.invalidateAll(keys); + return StreamSupport.stream(keys.spliterator(), false) + .collect(Collectors.toList()); } @Override - protected void put(CacheKey key, CacheEntry entry) { + public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { cache.put(key, entry); + return entry; } @Override - protected CacheEntry get(CacheKey key) { + public CacheEntry getFromStore(CacheKey key) { return cache.getIfPresent(key); } @@ -43,7 +57,8 @@ public static class Builder { private long expireTime = DEFAULT_EXPIRE_SECONDS; private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; - private Builder() { } + private Builder() { + } public Builder maximumSize(int size) { this.maximumSize = size; @@ -65,4 +80,31 @@ public GuavaClientSideCache build() { return new GuavaClientSideCache(cb.build()); } } + + @Override + public int getSize() { + return (int) cache.size(); + } + + @Override + public Collection getCacheEntries() { + throw new UnsupportedOperationException("Unimplemented method 'getCacheEntries'"); + } + + @Override + public EvictionPolicy getEvictionPolicy() { + return this.evictionPolicy; + } + + @Override + protected Boolean removeFromStore(CacheKey cacheKey) { + cache.invalidate(cacheKey); + return true; + } + + @Override + protected Boolean containsKeyInStore(CacheKey cacheKey) { + return cache.getIfPresent(cacheKey) != null; + } + } diff --git a/src/main/java/redis/clients/jedis/csc/LRUEviction.java b/src/main/java/redis/clients/jedis/csc/LRUEviction.java new file mode 100644 index 0000000000..d749fe9d79 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/LRUEviction.java @@ -0,0 +1,90 @@ +package redis.clients.jedis.csc; + +import java.util.*; + +/** + * Simple L(east) R(ecently) U(sed) eviction policy + * ATTENTION: this class is not thread safe + */ +public class LRUEviction implements EvictionPolicy { + + // For future reference, in case there is a need to make it thread safe, + // the LinkedHashMap can be wrapped in a Collections.synchronizedMap + + /** + * The cache that is associated to that policy instance + */ + private final Cache cache; + private final LinkedHashMap accessTimes; + private CacheKey lastEvicted = null; + + /** + * Constructor that gets the cache passed + * + * @param cache + * @param initialCapacity + */ + public LRUEviction(Cache cache, int initialCapacity) { + this.cache = cache; + this.accessTimes = new LinkedHashMap(initialCapacity, 0.75F, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + boolean result = size() > LRUEviction.this.cache.getMaxSize(); + if (result) { + lastEvicted = eldest.getKey(); + } + return result; + } + }; + } + + @Override + public Cache getCache() { + return this.cache; + } + + @Override + public EvictionType getType() { + return EvictionType.AGE; + } + + @Override + public String getName() { + return "Simple L(east) R(ecently) U(sed)"; + } + + @Override + public CacheKey evictNext() { + // its already done, thanks to the LinkedHashMap + CacheKey temp = lastEvicted; + lastEvicted = null; + return temp; + } + + @Override + public List evictMany(int n) { + List result = new ArrayList<>(); + for (int i = 0; i < n; i++) { + result.add(this.evictNext()); + } + + return result; + } + + @Override + public void touch(CacheKey cacheKey) { + this.accessTimes.put(cacheKey, new Date().getTime()); + } + + @Override + public boolean reset(CacheKey cacheKey) { + return this.accessTimes.remove(cacheKey) != null; + } + + @Override + public int resetAll() { + int result = this.accessTimes.size(); + accessTimes.clear(); + return result; + } +} diff --git a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java index e3587a022b..5282e3eb69 100644 --- a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java @@ -16,7 +16,7 @@ import redis.clients.jedis.ConnectionPool; import redis.clients.jedis.JedisClusterInfoCache; import redis.clients.jedis.annots.Experimental; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.exceptions.JedisClusterOperationException; import redis.clients.jedis.exceptions.JedisException; @@ -32,7 +32,7 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi } @Experimental - public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache) { this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, clusterNodes); initializeSlotsCache(clusterNodes, clientConfig); } @@ -44,7 +44,7 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi } @Experimental - public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache, GenericObjectPoolConfig poolConfig) { this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes); initializeSlotsCache(clusterNodes, clientConfig); @@ -57,7 +57,7 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi } @Experimental - public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache, GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod) { this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes, topologyRefreshPeriod); initializeSlotsCache(clusterNodes, clientConfig); diff --git a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java index 14d1b2c9da..ddbd768f9b 100644 --- a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java @@ -12,7 +12,7 @@ import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.annots.Experimental; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.util.Pool; public class PooledConnectionProvider implements ConnectionProvider { @@ -31,7 +31,7 @@ public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clien } @Experimental - public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache) { this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache)); this.connectionMapKey = hostAndPort; } @@ -43,7 +43,7 @@ public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clien } @Experimental - public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache, GenericObjectPoolConfig poolConfig) { this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache, poolConfig)); this.connectionMapKey = hostAndPort; diff --git a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java index 8a16d3e2e4..dedf34fb69 100644 --- a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java @@ -20,7 +20,7 @@ import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPubSub; import redis.clients.jedis.annots.Experimental; -import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.Cache; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.util.IOUtils; @@ -39,7 +39,7 @@ public class SentineledConnectionProvider implements ConnectionProvider { private final JedisClientConfig masterClientConfig; - private final ClientSideCache clientSideCache; + private final Cache clientSideCache; private final GenericObjectPoolConfig masterPoolConfig; @@ -58,7 +58,7 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m @Experimental public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, - ClientSideCache clientSideCache, Set sentinels, final JedisClientConfig sentinelClientConfig) { + Cache clientSideCache, Set sentinels, final JedisClientConfig sentinelClientConfig) { this(masterName, masterClientConfig, clientSideCache, null, sentinels, sentinelClientConfig); } @@ -71,7 +71,7 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m @Experimental public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, - ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, + Cache clientSideCache, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { this(masterName, masterClientConfig, clientSideCache, poolConfig, sentinels, sentinelClientConfig, DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS); @@ -86,7 +86,7 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m @Experimental public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, - ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, + Cache clientSideCache, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig, final long subscribeRetryWaitTimeMillis) { @@ -125,7 +125,7 @@ public HostAndPort getCurrentMaster() { private void initMaster(HostAndPort master) { initPoolLock.lock(); - + try { if (!master.equals(currentMaster)) { currentMaster = master; @@ -283,8 +283,8 @@ public void onMessage(String channel, String message) { initMaster(toHostAndPort(switchMasterMsg[3], switchMasterMsg[4])); } else { LOG.debug( - "Ignoring message on +switch-master for master {}. Our master is {}.", - switchMasterMsg[0], masterName); + "Ignoring message on +switch-master for master {}. Our master is {}.", + switchMasterMsg[0], masterName); } } else { diff --git a/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java index 60485129af..f28f499f95 100644 --- a/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java +++ b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java @@ -5,8 +5,8 @@ import java.util.concurrent.atomic.AtomicInteger; import redis.clients.jedis.*; -import redis.clients.jedis.csc.ClientSideCache; -import redis.clients.jedis.csc.MapClientSideCache; +import redis.clients.jedis.csc.Cache; +import redis.clients.jedis.csc.TestCache; public class CSCPooleadBenchmark { @@ -27,7 +27,7 @@ public static void main(String[] args) throws Exception { withoutCache += runBenchmark(null); } for (int i = 0; i < totalRounds; i++) { - withCache += runBenchmark(new MapClientSideCache()); + withCache += runBenchmark(new TestCache()); } System.out.println(String.format("after first round withoutCache: %d ms, withCache: %d ms", withoutCache, withCache)); @@ -35,13 +35,13 @@ public static void main(String[] args) throws Exception { withoutCache += runBenchmark(null); } for (int i = 0; i < totalRounds; i++) { - withCache += runBenchmark(new MapClientSideCache()); + withCache += runBenchmark(new TestCache()); } System.out.println(String.format("after second round withoutCache: %d ms, withCache: %d ms", withoutCache, withCache)); System.out.println("execution time ratio: " + (double) withCache / withoutCache); } - private static long runBenchmark(ClientSideCache cache) throws Exception { + private static long runBenchmark(Cache cache) throws Exception { long start = System.currentTimeMillis(); withPool(cache); long elapsed = System.currentTimeMillis() - start; @@ -49,7 +49,7 @@ private static long runBenchmark(ClientSideCache cache) throws Exception { return elapsed; } - private static void withPool(ClientSideCache cache) throws Exception { + private static void withPool(Cache cache) throws Exception { JedisPooled jedis = null; JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP3).password(endpoint.getPassword()).build(); jedis = new JedisPooled(endpoint.getHostAndPort(), config, cache); diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java index 3d63a79fa2..83371c7f44 100644 --- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java @@ -15,9 +15,8 @@ public class AllowAndDenyListClientSideCacheTest extends ClientSideCacheTestBase { - private static MapClientSideCache createMapClientSideCache(Map map, ClientSideCacheable cacheable) { - MapClientSideCache mapCache = new MapClientSideCache(map); - mapCache.setCacheable(cacheable); + private static Cache createTestCache(Map map, ClientSideCacheable cacheable) { + Cache mapCache = new TestCache(map, cacheable); return mapCache; } @@ -25,7 +24,7 @@ private static MapClientSideCache createMapClientSideCache(Map map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)), + createTestCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -38,7 +37,7 @@ public void none() { public void whiteListCommand() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)), + createTestCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -51,7 +50,7 @@ public void whiteListCommand() { public void blackListCommand() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)), + createTestCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -64,7 +63,7 @@ public void blackListCommand() { public void whiteListKey() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)), + createTestCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -77,7 +76,7 @@ public void whiteListKey() { public void blackListKey() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))), + createTestCache(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index 2dea19c908..70d3e35ff8 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -25,7 +25,7 @@ public void flushEntireCache() { } HashMap map = new HashMap<>(); - ClientSideCache clientSideCache = new MapClientSideCache(map); + Cache clientSideCache = new TestCache(map); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { for (int i = 0; i < count; i++) { jedis.get("k" + i); @@ -46,7 +46,7 @@ public void removeSpecificKey() { // By using LinkedHashMap, we can get the hashes (map keys) at the same order of the actual keys. LinkedHashMap map = new LinkedHashMap<>(); - ClientSideCache clientSideCache = new MapClientSideCache(map); + Cache clientSideCache = new TestCache(map); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { for (int i = 0; i < count; i++) { jedis.get("k" + i); @@ -59,7 +59,7 @@ public void removeSpecificKey() { String key = "k" + i; CacheKey command = commandHashes.get(i); assertTrue(map.containsKey(command)); - clientSideCache.invalidateKey(key); + clientSideCache.deleteByRedisKey(key); assertFalse(map.containsKey(command)); } } @@ -70,7 +70,7 @@ public void multiKeyOperation() { control.set("k2", "v2"); HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map))) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache(map))) { jedis.mget("k1", "k2"); assertEquals(1, map.size()); } @@ -78,34 +78,30 @@ public void multiKeyOperation() { @Test public void testInvalidationWithUnifiedJedis() { - GuavaClientSideCache clientSideCacheGuava = GuavaClientSideCache.builder() - .maximumSize(1000) - .ttl(100) - .build(); - - CaffeineClientSideCache clientSideCacheCaffeine = CaffeineClientSideCache.builder() - .maximumSize(1000) - .ttl(100) - .build(); - - GuavaClientSideCache mock = Mockito.spy(clientSideCacheGuava); + Cache cache = new TestCache(); + Cache mock = Mockito.spy(cache); UnifiedJedis client = new UnifiedJedis(hnp, clientConfig.get(), mock); - UnifiedJedis clientCaffeine = new UnifiedJedis(hnp, clientConfig.get(), clientSideCacheCaffeine); - - // "foo" is cached - client.set("foo", "bar"); - client.get("foo"); // read from the server - Assert.assertEquals("bar", client.get("foo")); // cache hit - - // Using another connection - clientCaffeine.set("foo", "bar2"); - Assert.assertEquals("bar2", clientCaffeine.get("foo")); - - //invalidating the cache and read it back from server - Assert.assertEquals("bar2", client.get("foo")); - - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(GuavaClientSideCache.class); - Mockito.verify(mock, Mockito.times(1)).invalidate(Mockito.anyList()); - Mockito.verify(mock, Mockito.times(2)).put(Mockito.any(CacheKey.class), Mockito.any(CacheEntry.class)); + UnifiedJedis controlClient = new UnifiedJedis(hnp, clientConfig.get()); + + try { + // "foo" is cached + client.set("foo", "bar"); + client.get("foo"); // read from the server + Assert.assertEquals("bar", client.get("foo")); // cache hit + + // Using another connection + controlClient.set("foo", "bar2"); + Assert.assertEquals("bar2", controlClient.get("foo")); + + //invalidating the cache and read it back from server + Assert.assertEquals("bar2", client.get("foo")); + + // ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(GuavaClientSideCache.class); + Mockito.verify(mock, Mockito.times(1)).deleteByRedisKeys(Mockito.anyList()); + Mockito.verify(mock, Mockito.times(2)).set(Mockito.any(CacheKey.class), Mockito.any(CacheEntry.class)); + } finally { + client.close(); + controlClient.close(); + } } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index fd8ad0b4f3..b73b65018b 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -52,7 +52,7 @@ public void tearDown() throws Exception { @Test public void simple() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); @@ -63,7 +63,7 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map), + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -82,7 +82,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); @@ -93,7 +93,7 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map), + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 962e9c549c..d46a357006 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -81,7 +81,7 @@ public void tearDown() throws Exception { @Test public void simple() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); @@ -93,7 +93,7 @@ public void simple() { public void simpleSSL() { JedisClientConfig conf = DefaultJedisClientConfig.builder().resp3() .password("foobared").ssl(true).build(); - try (JedisPooled jedis = new JedisPooled(sslEndpoint.getHostAndPort(), conf, new MapClientSideCache())) { + try (JedisPooled jedis = new JedisPooled(sslEndpoint.getHostAndPort(), conf, new TestCache())) { sslControl.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); sslControl.del("foo"); @@ -104,7 +104,7 @@ public void simpleSSL() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -123,7 +123,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); @@ -134,7 +134,7 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index 356c90333e..fa3a80e9da 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -48,7 +48,7 @@ public void tearDown() throws Exception { @Test public void simple() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(), sentinels, sentinelClientConfig)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -60,7 +60,7 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(map), sentinels, sentinelClientConfig)) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -79,7 +79,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(), sentinels, sentinelClientConfig)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -91,7 +91,7 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(map), sentinels, sentinelClientConfig)) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); diff --git a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java deleted file mode 100644 index a0b33c52d7..0000000000 --- a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java +++ /dev/null @@ -1,38 +0,0 @@ -package redis.clients.jedis.csc; - -import java.util.HashMap; -import java.util.Map; - -public class MapClientSideCache extends ClientSideCache { - - private final Map cache; - - public MapClientSideCache() { - this(new HashMap<>()); - } - - public MapClientSideCache(Map map) { - super(); - this.cache = map; - } - - @Override - protected final void clear() { - cache.clear(); - } - - @Override - protected void remove(Iterable> keys) { - keys.forEach(hash -> cache.remove(hash)); - } - - @Override - protected void put(CacheKey key, CacheEntry entry) { - cache.put(key, entry); - } - - @Override - protected CacheEntry get(CacheKey key) { - return cache.get(key); - } -} diff --git a/src/test/java/redis/clients/jedis/csc/TestCache.java b/src/test/java/redis/clients/jedis/csc/TestCache.java new file mode 100644 index 0000000000..adcbe400ee --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -0,0 +1,20 @@ +package redis.clients.jedis.csc; + +import java.util.HashMap; +import java.util.Map; + +public class TestCache extends DefaultCache { + + public TestCache() { + this(new HashMap()); + } + + public TestCache(Map map) { + super(1000, map); + } + + public TestCache(Map map, ClientSideCacheable cacheable) { + super(1000, map, cacheable); + } + +} From 6d56da636c3a4f2ed926e9130a32c162d1129d6a Mon Sep 17 00:00:00 2001 From: atakavci Date: Fri, 26 Jul 2024 18:42:08 +0300 Subject: [PATCH 07/15] - added SSLSocketWrapper and plug it to use 'available' - handle exceptions properly - fix some issues with unit tests --- .../java/redis/clients/jedis/Connection.java | 15 +- .../jedis/DefaultJedisSocketFactory.java | 2 + .../redis/clients/jedis/SSLSocketWrapper.java | 408 ++++++++++++++++++ .../redis/clients/jedis/csc/CacheEntry.java | 7 +- .../jedis/csc/CaffeineClientSideCache.java | 2 + .../clients/jedis/csc/ClientSideCache.java | 5 - .../jedis/csc/GuavaClientSideCache.java | 2 + .../jedis/exceptions/JedisCacheException.java | 18 + .../csc/CaffeineClientSideCacheTest.java | 2 +- .../jedis/csc/ClientSideCacheTestBase.java | 19 +- .../jedis/csc/GuavaClientSideCacheTest.java | 2 +- .../csc/JedisClusterClientSideCacheTest.java | 4 +- .../csc/JedisPooledClientSideCacheTest.java | 18 +- .../JedisSentineledClientSideCacheTest.java | 4 +- 14 files changed, 461 insertions(+), 47 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/SSLSocketWrapper.java create mode 100644 src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 40886aa079..82117304a4 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -375,17 +375,12 @@ protected void readPushesWithCheckingBroken() { } try { - if (socket instanceof SSLSocket) { - this.ping(); - } else { - try { - if (inputStream.available() > 0) { - protocolReadPushes(inputStream, true); - } - } catch (IOException e) { - // TODO: handle this properly - } + if (inputStream.available() > 0) { + protocolReadPushes(inputStream, true); } + } catch (IOException e) { + broken = true; + throw new JedisConnectionException("Failed to check buffer on connection.", e); } catch (JedisConnectionException exc) { broken = true; throw exc; diff --git a/src/main/java/redis/clients/jedis/DefaultJedisSocketFactory.java b/src/main/java/redis/clients/jedis/DefaultJedisSocketFactory.java index c9ef6646ba..0d41693d0f 100644 --- a/src/main/java/redis/clients/jedis/DefaultJedisSocketFactory.java +++ b/src/main/java/redis/clients/jedis/DefaultJedisSocketFactory.java @@ -94,11 +94,13 @@ public Socket createSocket() throws JedisConnectionException { if (null == _sslSocketFactory) { _sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); } + Socket plainSocket = socket; socket = _sslSocketFactory.createSocket(socket, _hostAndPort.getHost(), _hostAndPort.getPort(), true); if (null != sslParameters) { ((SSLSocket) socket).setSSLParameters(sslParameters); } + socket = new SSLSocketWrapper((SSLSocket) socket, plainSocket); if (null != hostnameVerifier && !hostnameVerifier.verify(_hostAndPort.getHost(), ((SSLSocket) socket).getSession())) { diff --git a/src/main/java/redis/clients/jedis/SSLSocketWrapper.java b/src/main/java/redis/clients/jedis/SSLSocketWrapper.java new file mode 100644 index 0000000000..a2b9e5b74c --- /dev/null +++ b/src/main/java/redis/clients/jedis/SSLSocketWrapper.java @@ -0,0 +1,408 @@ +package redis.clients.jedis; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.util.function.BiFunction; +import java.util.List; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +public class SSLSocketWrapper extends SSLSocket { + + SSLSocket actual; + Socket underlying; + InputStream wrapper; + + private class InputStreamWrapper extends InputStream { + private InputStream actual; + private InputStream underlying; + + public InputStreamWrapper(InputStream actual, InputStream underlying) { + this.actual = actual; + this.underlying = underlying; + } + + @Override + public int read() throws IOException { + return actual.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return actual.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return actual.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return actual.skip(n); + } + + @Override + public int available() throws IOException { + return underlying.available(); + } + + @Override + public void close() throws IOException { + actual.close(); + } + + @Override + public synchronized void mark(int readlimit) { + actual.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + actual.reset(); + } + + @Override + public boolean markSupported() { + return actual.markSupported(); + } + } + + public SSLSocketWrapper(SSLSocket actual, Socket underlying) throws IOException { + this.actual = actual; + this.underlying = underlying; + this.wrapper = new InputStreamWrapper(actual.getInputStream(), underlying.getInputStream()); + } + + @Override + public void connect(SocketAddress endpoint) throws IOException { + actual.connect(endpoint); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + actual.connect(endpoint, timeout); + } + + @Override + public void bind(SocketAddress bindpoint) throws IOException { + actual.bind(bindpoint); + } + + @Override + public InetAddress getInetAddress() { + return actual.getInetAddress(); + } + + @Override + public InetAddress getLocalAddress() { + return actual.getLocalAddress(); + } + + @Override + public int getPort() { + return actual.getPort(); + } + + @Override + public int getLocalPort() { + return actual.getLocalPort(); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return actual.getRemoteSocketAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return actual.getLocalSocketAddress(); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException { + actual.setTcpNoDelay(on); + } + + @Override + public boolean getTcpNoDelay() throws SocketException { + return actual.getTcpNoDelay(); + } + + @Override + public void setSoLinger(boolean on, int linger) throws SocketException { + actual.setSoLinger(on, linger); + } + + @Override + public int getSoLinger() throws SocketException { + return actual.getSoLinger(); + } + + @Override + public void sendUrgentData(int data) throws IOException { + actual.sendUrgentData(data); + } + + @Override + public void setOOBInline(boolean on) throws SocketException { + actual.setOOBInline(on); + } + + @Override + public boolean getOOBInline() throws SocketException { + return actual.getOOBInline(); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + actual.setSoTimeout(timeout); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + return actual.getSoTimeout(); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + actual.setSendBufferSize(size); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + return actual.getSendBufferSize(); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + actual.setReceiveBufferSize(size); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return actual.getReceiveBufferSize(); + } + + @Override + public void setKeepAlive(boolean on) throws SocketException { + actual.setKeepAlive(on); + } + + @Override + public boolean getKeepAlive() throws SocketException { + return actual.getKeepAlive(); + } + + @Override + public void setTrafficClass(int tc) throws SocketException { + actual.setTrafficClass(tc); + } + + @Override + public int getTrafficClass() throws SocketException { + return actual.getTrafficClass(); + } + + @Override + public void setReuseAddress(boolean on) throws SocketException { + actual.setReuseAddress(on); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return actual.getReuseAddress(); + } + + @Override + public synchronized void close() throws IOException { + actual.close(); + } + + @Override + public void shutdownInput() throws IOException { + actual.shutdownInput(); + } + + @Override + public void shutdownOutput() throws IOException { + actual.shutdownOutput(); + } + + @Override + public String toString() { + return actual.toString(); + } + + @Override + public boolean isConnected() { + return actual.isConnected(); + } + + @Override + public boolean isBound() { + return actual.isBound(); + } + + @Override + public boolean isClosed() { + return actual.isClosed(); + } + + @Override + public boolean isInputShutdown() { + return actual.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() { + return actual.isOutputShutdown(); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + actual.setPerformancePreferences(connectionTime, latency, bandwidth); + } + + @Override + public InputStream getInputStream() throws IOException { + return wrapper; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return actual.getOutputStream(); + } + + @Override + public String[] getSupportedCipherSuites() { + return actual.getSupportedCipherSuites(); + } + + @Override + public String[] getEnabledCipherSuites() { + return actual.getEnabledCipherSuites(); + } + + @Override + public void setEnabledCipherSuites(String[] var1) { + actual.setEnabledCipherSuites(var1); + } + + @Override + public String[] getSupportedProtocols() { + return actual.getSupportedProtocols(); + } + + @Override + public String[] getEnabledProtocols() { + return actual.getEnabledProtocols(); + } + + @Override + public void setEnabledProtocols(String[] var1) { + actual.setEnabledProtocols(var1); + } + + @Override + public SSLSession getSession() { + return actual.getSession(); + } + + @Override + public SSLSession getHandshakeSession() { + return actual.getHandshakeSession(); + } + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener var1) { + actual.addHandshakeCompletedListener(var1); + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener var1) { + actual.removeHandshakeCompletedListener(var1); + } + + @Override + public void startHandshake() throws IOException { + actual.startHandshake(); + } + + @Override + public void setUseClientMode(boolean var1) { + actual.setUseClientMode(var1); + } + + @Override + public boolean getUseClientMode() { + return actual.getUseClientMode(); + } + + @Override + public void setNeedClientAuth(boolean var1) { + actual.setNeedClientAuth(var1); + } + + @Override + public boolean getNeedClientAuth() { + return actual.getNeedClientAuth(); + } + + @Override + public void setWantClientAuth(boolean var1) { + actual.setWantClientAuth(var1); + } + + @Override + public boolean getWantClientAuth() { + return actual.getWantClientAuth(); + } + + @Override + public void setEnableSessionCreation(boolean var1) { + actual.setEnableSessionCreation(var1); + } + + @Override + public boolean getEnableSessionCreation() { + return actual.getEnableSessionCreation(); + } + + @Override + public SSLParameters getSSLParameters() { + return actual.getSSLParameters(); + } + + @Override + public void setSSLParameters(SSLParameters var1) { + actual.setSSLParameters(var1); + } + + @Override + public String getApplicationProtocol() { + return actual.getApplicationProtocol(); + } + + @Override + public String getHandshakeApplicationProtocol() { + return actual.getHandshakeApplicationProtocol(); + } + + @Override + public void setHandshakeApplicationProtocolSelector(BiFunction, String> var1) { + actual.setHandshakeApplicationProtocolSelector(var1); + } + + @Override + public BiFunction, String> getHandshakeApplicationProtocolSelector() { + return actual.getHandshakeApplicationProtocolSelector(); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/CacheEntry.java b/src/main/java/redis/clients/jedis/csc/CacheEntry.java index 000cfe7d9b..62798db074 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheEntry.java +++ b/src/main/java/redis/clients/jedis/csc/CacheEntry.java @@ -8,6 +8,7 @@ import redis.clients.jedis.Connection; import redis.clients.jedis.annots.Internal; +import redis.clients.jedis.exceptions.JedisCacheException; @Internal public class CacheEntry { @@ -42,8 +43,7 @@ private static byte[] toBytes(Object object) { oos.close(); return baos.toByteArray(); } catch (Exception e) { - // TODO: handle this properly - throw new RuntimeException(e); + throw new JedisCacheException("Failed to serialize object", e); } } @@ -52,8 +52,7 @@ private T toObject(byte[] data) { ObjectInputStream ois = new ObjectInputStream(bais)) { return (T) ois.readObject(); } catch (Exception e) { - // TODO: handle this properly - throw new RuntimeException(e); + throw new JedisCacheException("Failed to deserialize object", e); } } } diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java index 0230f23cc3..e203672e58 100644 --- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java @@ -72,6 +72,8 @@ public CaffeineClientSideCache build() { } } + // TODO: we should discuss if/how we utilize Caffeine and get back to here ! + @Override public int getSize() { return (int) cache.estimatedSize(); diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 67d26dee15..99cb02458b 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -95,11 +95,6 @@ public List delete(List cacheKeys) { public List deleteByRedisKey(Object key) { final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); - // TODO: dont forget to clean up this - // System.out.println( - // "invalidation message received for :" + (key instanceof String ? (String) key - // : new String((byte[]) key))); - Set> commands = redisKeysToCacheKeys.get(mapKey); List cacheKeys = new ArrayList<>(); if (commands != null) { diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java index 6eb79db1d3..8cb3f9194e 100644 --- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java @@ -81,6 +81,8 @@ public GuavaClientSideCache build() { } } + // TODO: we should discuss if/how we utilize Guava and get back to here ! + @Override public int getSize() { return (int) cache.size(); diff --git a/src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java b/src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java new file mode 100644 index 0000000000..84fcb55ee3 --- /dev/null +++ b/src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java @@ -0,0 +1,18 @@ +package redis.clients.jedis.exceptions; + +public class JedisCacheException extends JedisException { + + private static final long serialVersionUID = 3878126572474819403L; + public JedisCacheException(String message) { + super(message); + } + + public JedisCacheException(Throwable cause) { + super(cause); + } + + public JedisCacheException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index 7f51e7b736..2f88256aa0 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -24,7 +24,7 @@ public void simple() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java index 15531a5dfb..4bde3be30d 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java @@ -7,7 +7,6 @@ import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPoolConfig; -import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.EndpointConfig; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPorts; @@ -20,13 +19,11 @@ abstract class ClientSideCacheTestBase { protected static final HostAndPort hnp = endpoint.getHostAndPort(); - protected static final String baseUrl = "redis://:foobared@" + hnp.toString() + "/"; // TODO: use EndpointConfig - protected Jedis control; @Before public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); // TODO: use EndpointConfig + control = new Jedis(hnp, endpoint.getClientConfigBuilder().build()); control.flushAll(); } @@ -35,14 +32,12 @@ public void tearDown() throws Exception { control.close(); } - protected static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); // TODO: use EndpointConfig + protected static final Supplier clientConfig = () -> endpoint.getClientConfigBuilder().resp3().build(); - protected static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; + protected static final Supplier> singleConnectionPoolConfig = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; } diff --git a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java index ea2310e29a..697a947dce 100644 --- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java @@ -24,7 +24,7 @@ public void simple() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index b73b65018b..6434bf1111 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -56,7 +56,7 @@ public void simple() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @@ -86,7 +86,7 @@ public void flushAll() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index d46a357006..8340435003 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -56,12 +56,11 @@ private static void setJvmTrustStore(String trustStoreFilePath, String trustStor @Before public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); // TODO: use - // EndpointConfig + + control = new Jedis(hnp, endpoint.getClientConfigBuilder().build()); control.flushAll(); - - sslControl = new Jedis(sslEndpoint.getHostAndPort(), - DefaultJedisClientConfig.builder().password("foobared").ssl(true).build()); + + sslControl = new Jedis(sslEndpoint.getHostAndPort(), sslEndpoint.getClientConfigBuilder().build()); sslControl.flushAll(); } @@ -70,8 +69,7 @@ public void tearDown() throws Exception { control.close(); } - private static final Supplier clientConfig = () -> DefaultJedisClientConfig.builder().resp3() - .password("foobared").build(); // TODO: use EndpointConfig + private static final Supplier clientConfig = () -> endpoint.getClientConfigBuilder().resp3().build(); private static final Supplier> singleConnectionPoolConfig = () -> { ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); @@ -85,7 +83,7 @@ public void simple() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @@ -97,7 +95,7 @@ public void simpleSSL() { sslControl.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); sslControl.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @@ -127,7 +125,7 @@ public void flushAll() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index fa3a80e9da..13130416ce 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -53,7 +53,7 @@ public void simple() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @@ -84,7 +84,7 @@ public void flushAll() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } From 281c92d1cffb0c3d0ecfadfa379d51f7d6e4f1d8 Mon Sep 17 00:00:00 2001 From: atakavci Date: Wed, 31 Jul 2024 09:51:25 +0300 Subject: [PATCH 08/15] implementing thread safety --- .../java/redis/clients/jedis/Connection.java | 2 +- .../clients/jedis/csc/CacheConnection.java | 36 +++--- .../clients/jedis/csc/ClientSideCache.java | 111 +++++++++++------ .../csc/JedisPooledClientSideCacheTest.java | 115 ++++++++++++++---- 4 files changed, 180 insertions(+), 84 deletions(-) diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 82117304a4..2fd6f90a3d 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -418,7 +418,7 @@ private static boolean validateClientInfo(String info) { return true; } - private void initializeFromClientConfig(final JedisClientConfig config) { + protected void initializeFromClientConfig(final JedisClientConfig config) { try { connect(); diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 8a59b3ecdc..86118368b6 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -15,7 +15,7 @@ public class CacheConnection extends Connection { private final Cache clientSideCache; - private final ReentrantLock lock; + private ReentrantLock lock; public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, Cache clientSideCache) { super(socketFactory, clientConfig); @@ -25,27 +25,27 @@ public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig } this.clientSideCache = Objects.requireNonNull(clientSideCache); initializeClientSideCache(); + } + @Override + protected void initializeFromClientConfig(JedisClientConfig config) { lock = new ReentrantLock(); + super.initializeFromClientConfig(config); } @Override protected Object protocolRead(RedisInputStream inputStream) { - if (lock != null) { - lock.lock(); - try { - return Protocol.read(inputStream); - } finally { - lock.unlock(); - } - } else { + lock.lock(); + try { return Protocol.read(inputStream); + } finally { + lock.unlock(); } } @Override protected void protocolReadPushes(RedisInputStream inputStream, boolean onlyPendingBuffer) { - if (lock != null && lock.tryLock()) { + if (lock.tryLock()) { try { Protocol.readPushes(inputStream, clientSideCache, onlyPendingBuffer); } finally { @@ -92,16 +92,20 @@ private void initializeClientSideCache() { } private CacheEntry validateEntry(CacheEntry cacheEntry) { - Connection cacheOwner = (Connection) cacheEntry.getConnection().get(); - if (cacheOwner == null) { + CacheConnection cacheOwner = (CacheConnection) cacheEntry.getConnection().get(); + if (cacheOwner == null || cacheOwner.isBroken() || !cacheOwner.isConnected()) { clientSideCache.delete(cacheEntry.getCacheKey()); return null; } else { - if (cacheOwner == this) { - this.readPushesWithCheckingBroken(); - cacheEntry = clientSideCache.get(cacheEntry.getCacheKey()); + try { + cacheOwner.readPushesWithCheckingBroken(); + } catch (JedisException e) { + clientSideCache.delete(cacheEntry.getCacheKey()); + return null; } + + cacheEntry = clientSideCache.get(cacheEntry.getCacheKey()); + return cacheEntry; } - return cacheEntry; } } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 99cb02458b..1be6ce5f44 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import redis.clients.jedis.annots.Experimental; @@ -24,12 +25,13 @@ public abstract class ClientSideCache implements Cache { private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>(); private final int maximumSize; + private ReentrantLock lock = new ReentrantLock(); protected ClientSideCache(int maximumSize) { this.maximumSize = maximumSize; } - protected ClientSideCache(int maximumSize,ClientSideCacheable cacheable ) { + protected ClientSideCache(int maximumSize, ClientSideCacheable cacheable) { this.maximumSize = maximumSize; this.cacheable = cacheable; } @@ -54,54 +56,74 @@ public CacheEntry get(CacheKey cacheKey) { @Override public CacheEntry set(CacheKey cacheKey, CacheEntry entry) { - entry = putIntoStore(cacheKey, entry); - getEvictionPolicy().touch(cacheKey); - getEvictionPolicy().evictNext(); - for (Object redisKey : cacheKey.getRedisKeys()) { - ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); - if (redisKeysToCacheKeys.containsKey(mapKey)) { - redisKeysToCacheKeys.get(mapKey).add(cacheKey); - } else { - Set> set = ConcurrentHashMap.newKeySet(); - set.add(cacheKey); - redisKeysToCacheKeys.put(mapKey, set); + lock.lock(); + try { + entry = putIntoStore(cacheKey, entry); + getEvictionPolicy().touch(cacheKey); + getEvictionPolicy().evictNext(); + for (Object redisKey : cacheKey.getRedisKeys()) { + ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); + if (redisKeysToCacheKeys.containsKey(mapKey)) { + redisKeysToCacheKeys.get(mapKey).add(cacheKey); + } else { + Set> set = ConcurrentHashMap.newKeySet(); + set.add(cacheKey); + redisKeysToCacheKeys.put(mapKey, set); + } } + return entry; + } finally { + lock.unlock(); } - return entry; } @Override public Boolean delete(CacheKey cacheKey) { - boolean removed = removeFromStore(cacheKey); - getEvictionPolicy().reset(cacheKey); - - // removing it from redisKeysToCacheKeys as well - // TODO: considering not doing it, what is the impact of not doing it ?? - for (Object redisKey : cacheKey.getRedisKeys()) { - ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); - if (redisKeysToCacheKeys.containsKey(mapKey)) { - redisKeysToCacheKeys.get(mapKey).remove(cacheKey); + lock.lock(); + try { + boolean removed = removeFromStore(cacheKey); + getEvictionPolicy().reset(cacheKey); + + // removing it from redisKeysToCacheKeys as well + // TODO: considering not doing it, what is the impact of not doing it ?? + for (Object redisKey : cacheKey.getRedisKeys()) { + ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); + if (redisKeysToCacheKeys.containsKey(mapKey)) { + redisKeysToCacheKeys.get(mapKey).remove(cacheKey); + } } + return removed; + } finally { + lock.unlock(); } - return removed; } @Override public List delete(List cacheKeys) { - return cacheKeys.stream().map(this::delete).collect(Collectors.toList()); + lock.lock(); + try { + return cacheKeys.stream().map(this::delete).collect(Collectors.toList()); + } finally { + lock.unlock(); + } } @Override public List deleteByRedisKey(Object key) { - final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); - - Set> commands = redisKeysToCacheKeys.get(mapKey); - List cacheKeys = new ArrayList<>(); - if (commands != null) { - cacheKeys.addAll(commands.stream().filter(this::removeFromStore).collect(Collectors.toList())); - redisKeysToCacheKeys.remove(mapKey); + lock.lock(); + try { + final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key); + + Set> commands = redisKeysToCacheKeys.get(mapKey); + List cacheKeys = new ArrayList<>(); + if (commands != null) { + cacheKeys.addAll(commands.stream().filter(this::removeFromStore).collect(Collectors.toList())); + redisKeysToCacheKeys.remove(mapKey); + } + return cacheKeys; + } finally { + lock.unlock(); } - return cacheKeys; } @Override @@ -110,18 +132,27 @@ public List deleteByRedisKeys(List keys) { flush(); return null; } - - return ((List) keys).stream() - .map(this::deleteByRedisKey).flatMap(List::stream).collect(Collectors.toList()); + lock.lock(); + try { + return ((List) keys).stream() + .map(this::deleteByRedisKey).flatMap(List::stream).collect(Collectors.toList()); + } finally { + lock.unlock(); + } } @Override public int flush() { - int result = this.getSize(); - clearStore(); - redisKeysToCacheKeys.clear(); - getEvictionPolicy().resetAll(); - return result; + lock.lock(); + try { + int result = this.getSize(); + clearStore(); + redisKeysToCacheKeys.clear(); + getEvictionPolicy().resetAll(); + return result; + } finally { + lock.unlock(); + } } @Override diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 8340435003..40487fea2c 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -4,8 +4,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.File; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.function.Supplier; @@ -15,22 +23,32 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPoolConfig; -import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.EndpointConfig; -import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPorts; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; -public class JedisPooledClientSideCacheTest { +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; - private static final EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone1"); +@RunWith(Parameterized.class) +public class JedisPooledClientSideCacheTest { - protected static final HostAndPort hnp = endpoint.getHostAndPort(); + private EndpointConfig endpoint; protected Jedis control; @@ -38,6 +56,18 @@ public class JedisPooledClientSideCacheTest { protected Jedis sslControl; + public JedisPooledClientSideCacheTest(EndpointConfig endpoint) { + this.endpoint = endpoint; + } + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + { HostAndPorts.getRedisEndpoint("standalone1") }, + { HostAndPorts.getRedisEndpoint("standalone0-tls") }, + }); + } + @BeforeClass public static void prepare() { setupTrustStore(); @@ -57,11 +87,8 @@ private static void setJvmTrustStore(String trustStoreFilePath, String trustStor @Before public void setUp() throws Exception { - control = new Jedis(hnp, endpoint.getClientConfigBuilder().build()); + control = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build()); control.flushAll(); - - sslControl = new Jedis(sslEndpoint.getHostAndPort(), sslEndpoint.getClientConfigBuilder().build()); - sslControl.flushAll(); } @After @@ -69,9 +96,9 @@ public void tearDown() throws Exception { control.close(); } - private static final Supplier clientConfig = () -> endpoint.getClientConfigBuilder().resp3().build(); + private final Supplier clientConfig = () -> endpoint.getClientConfigBuilder().resp3().build(); - private static final Supplier> singleConnectionPoolConfig = () -> { + private final Supplier> singleConnectionPoolConfig = () -> { ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); poolConfig.setMaxTotal(1); return poolConfig; @@ -79,7 +106,7 @@ public void tearDown() throws Exception { @Test public void simple() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache())) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); @@ -87,22 +114,10 @@ public void simple() { } } - @Test - public void simpleSSL() { - JedisClientConfig conf = DefaultJedisClientConfig.builder().resp3() - .password("foobared").ssl(true).build(); - try (JedisPooled jedis = new JedisPooled(sslEndpoint.getHostAndPort(), conf, new TestCache())) { - sslControl.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - sslControl.del("foo"); - assertEquals(null, jedis.get("foo")); - } - } - @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache(map), + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -121,7 +136,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache())) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); @@ -132,7 +147,7 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache(map), + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -149,4 +164,50 @@ public void flushAllWithSimpleMap() { } } + @Test + public void testConcurrentAccess() throws InterruptedException { + int threadCount = 10; + int iterations = 1000; + + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get())) { + String finalValue = jedis.set("foo", "0"); + } + + ReentrantLock lock = new ReentrantLock(true); + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + + // Create the shared mock instance of cache + TestCache mockedCache = Mockito.spy(new TestCache()); + + // Submit multiple threads to perform concurrent operations + CountDownLatch latch = new CountDownLatch(10); + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), mockedCache)) { + for (int j = 0; j < iterations; j++) { + lock.lock(); + try { + // Simulate continious get and update operations and consume invalidation events meanwhile + Integer value = new Integer(jedis.get("foo")) + 1; + jedis.set("foo", value.toString()); + } finally { + lock.unlock(); + } + } + } finally { + latch.countDown(); + } + }); + } + + // wait for all threads to complete + latch.await(); + + // Verify the final value of "foo" in Redis + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get())) { + String finalValue = jedis.get("foo"); + assertEquals(threadCount * iterations, Integer.parseInt(finalValue)); + } + } + } From 9d42da856ded288e36a87193f62da652f8f63c51 Mon Sep 17 00:00:00 2001 From: atakavci Date: Thu, 1 Aug 2024 12:12:08 +0300 Subject: [PATCH 09/15] - fix eviction issue and add related test - fix consuming invalidation messages on a response read - introduce cachestats - fix potential issue with cacheKeysRelatedtoRedisKey cleanup - tests for sequential access, concurrent acces and maxsize --- .../redis/clients/jedis/CommandArguments.java | 4 - .../java/redis/clients/jedis/Connection.java | 11 +- .../java/redis/clients/jedis/Protocol.java | 6 + .../java/redis/clients/jedis/csc/Cache.java | 10 + .../clients/jedis/csc/CacheConnection.java | 9 +- .../redis/clients/jedis/csc/CacheStats.java | 89 ++++++++ .../clients/jedis/csc/ClientSideCache.java | 27 ++- .../redis/clients/jedis/csc/LRUEviction.java | 15 +- .../csc/CaffeineClientSideCacheTest.java | 2 - .../csc/JedisPooledClientSideCacheTest.java | 190 +++++++++++++++--- .../redis/clients/jedis/csc/TestCache.java | 4 + 11 files changed, 314 insertions(+), 53 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/CacheStats.java diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index 8088238223..b0ebd7fd13 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -18,10 +18,6 @@ public class CommandArguments implements Iterable { private boolean blocking; - private CommandArguments() { - throw new InstantiationError(); - } - public CommandArguments(ProtocolCommand command) { args = new ArrayList<>(); args.add(command); diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 2fd6f90a3d..e0cb9e7ad6 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -13,12 +13,9 @@ import java.util.List; import java.util.function.Supplier; -import javax.net.ssl.SSLSocket; - import redis.clients.jedis.Protocol.Command; import redis.clients.jedis.Protocol.Keyword; import redis.clients.jedis.annots.Experimental; -import redis.clients.jedis.annots.Internal; import redis.clients.jedis.args.ClientAttributeOption; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.commands.ProtocolCommand; @@ -344,24 +341,20 @@ protected void flush() { } @Experimental - @Internal protected Object protocolRead(RedisInputStream is) { return Protocol.read(is); } @Experimental - @Internal - protected void protocolReadPushes(RedisInputStream is, boolean onlyPendingBuffer) { + protected void protocolReadPushes(RedisInputStream is) { } - // TODO: final protected Object readProtocolWithCheckingBroken() { if (broken) { throw new JedisConnectionException("Attempting to read from a broken connection."); } try { - protocolReadPushes(inputStream, false); return protocolRead(inputStream); } catch (JedisConnectionException exc) { broken = true; @@ -376,7 +369,7 @@ protected void readPushesWithCheckingBroken() { try { if (inputStream.available() > 0) { - protocolReadPushes(inputStream, true); + protocolReadPushes(inputStream); } } catch (IOException e) { broken = true; diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 80b485b40c..174fdd142b 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -213,6 +213,12 @@ public static Object read(final RedisInputStream is) { return process(is); } + @Experimental + public static Object read(final RedisInputStream is, final Cache cache) { + readPushes(is, cache, false); + return process(is); + } + @Experimental public static void readPushes(final RedisInputStream is, final Cache cache, boolean onlyPendingBuffer) { if (onlyPendingBuffer) { diff --git a/src/main/java/redis/clients/jedis/csc/Cache.java b/src/main/java/redis/clients/jedis/csc/Cache.java index 04ab0217df..3a7f801383 100644 --- a/src/main/java/redis/clients/jedis/csc/Cache.java +++ b/src/main/java/redis/clients/jedis/csc/Cache.java @@ -95,4 +95,14 @@ public interface Cache { * @return The eviction policy that is used by the cache */ EvictionPolicy getEvictionPolicy(); + + /** + * @return The statistics of the cache + */ + public CacheStats getStats(); + + /** + * @return The statistics of the cache + */ + public CacheStats getAndResetStats(); } diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 86118368b6..67218cd22d 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -37,17 +37,17 @@ protected void initializeFromClientConfig(JedisClientConfig config) { protected Object protocolRead(RedisInputStream inputStream) { lock.lock(); try { - return Protocol.read(inputStream); + return Protocol.read(inputStream, clientSideCache); } finally { lock.unlock(); } } @Override - protected void protocolReadPushes(RedisInputStream inputStream, boolean onlyPendingBuffer) { + protected void protocolReadPushes(RedisInputStream inputStream) { if (lock.tryLock()) { try { - Protocol.readPushes(inputStream, clientSideCache, onlyPendingBuffer); + Protocol.readPushes(inputStream, clientSideCache, true); } finally { lock.unlock(); } @@ -58,6 +58,7 @@ protected void protocolReadPushes(RedisInputStream inputStream, boolean onlyPend public T executeCommand(final CommandObject commandObject) { CacheKey key = new CacheKey<>(commandObject); if (!clientSideCache.isCacheable(key)) { + clientSideCache.getStats().nonCacheable(); return super.executeCommand(commandObject); } @@ -68,11 +69,13 @@ public T executeCommand(final CommandObject commandObject) { if (cacheEntry != null) { cacheEntry = validateEntry(cacheEntry); if (cacheEntry != null) { + clientSideCache.getStats().hit(); return (T) cacheEntry.getValue(); } } // CACHE MISS!! + clientSideCache.getStats().miss(); T value = super.executeCommand(commandObject); if (value != null) { cacheEntry = new CacheEntry(cacheKey, value, new WeakReference(this)); diff --git a/src/main/java/redis/clients/jedis/csc/CacheStats.java b/src/main/java/redis/clients/jedis/csc/CacheStats.java new file mode 100644 index 0000000000..7554cc52c0 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/CacheStats.java @@ -0,0 +1,89 @@ +package redis.clients.jedis.csc; + +import java.util.concurrent.atomic.AtomicLong; + +public class CacheStats { + + private AtomicLong hits = new AtomicLong(0); + private AtomicLong misses = new AtomicLong(0); + private AtomicLong loads = new AtomicLong(0); + private AtomicLong evicts = new AtomicLong(0); + private AtomicLong nonCacheable = new AtomicLong(0); + private AtomicLong flush = new AtomicLong(0); + private AtomicLong invalidationsByServer = new AtomicLong(0); + private AtomicLong invalidationMessages = new AtomicLong(0); + + protected void hit() { + hits.incrementAndGet(); + } + + protected void miss() { + misses.incrementAndGet(); + } + + protected void load() { + loads.incrementAndGet(); + } + + protected void evict() { + evicts.incrementAndGet(); + } + + public void nonCacheable() { + nonCacheable.incrementAndGet(); + } + + public void flush() { + flush.incrementAndGet(); + } + + public void invalidationByServer(int size) { + invalidationsByServer.addAndGet(size); + } + + public void invalidationMessages() { + invalidationMessages.incrementAndGet(); + } + + public long getHitCount() { + return hits.get(); + } + + public long getMissCount() { + return misses.get(); + } + + public long getLoadCount() { + return loads.get(); + } + + public long getEvictCount() { + return evicts.get(); + } + + public long getNonCacheableCount() { + return nonCacheable.get(); + } + + public long getFlushCount() { + return flush.get(); + } + + public long getInvalidationCount() { + return invalidationsByServer.get(); + } + + public String toString() { + return "CacheStats{" + + "hits=" + hits + + ", misses=" + misses + + ", loads=" + loads + + ", evicts=" + evicts + + ", nonCacheable=" + nonCacheable + + ", flush=" + flush + + ", invalidationsByServer=" + invalidationsByServer + + ", invalidationMessages=" + invalidationMessages + + '}'; + } + +} diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 1be6ce5f44..6588a355ec 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -26,6 +26,7 @@ public abstract class ClientSideCache implements Cache { private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>(); private final int maximumSize; private ReentrantLock lock = new ReentrantLock(); + private volatile CacheStats stats = new CacheStats(); protected ClientSideCache(int maximumSize) { this.maximumSize = maximumSize; @@ -60,7 +61,9 @@ public CacheEntry set(CacheKey cacheKey, CacheEntry entry) { try { entry = putIntoStore(cacheKey, entry); getEvictionPolicy().touch(cacheKey); - getEvictionPolicy().evictNext(); + if (getEvictionPolicy().evictNext() != null) { + stats.evict(); + } for (Object redisKey : cacheKey.getRedisKeys()) { ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); if (redisKeysToCacheKeys.containsKey(mapKey)) { @@ -71,6 +74,7 @@ public CacheEntry set(CacheKey cacheKey, CacheEntry entry) { redisKeysToCacheKeys.put(mapKey, set); } } + stats.load(); return entry; } finally { lock.unlock(); @@ -88,8 +92,9 @@ public Boolean delete(CacheKey cacheKey) { // TODO: considering not doing it, what is the impact of not doing it ?? for (Object redisKey : cacheKey.getRedisKeys()) { ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(redisKey); - if (redisKeysToCacheKeys.containsKey(mapKey)) { - redisKeysToCacheKeys.get(mapKey).remove(cacheKey); + Set> cacheKeysRelatedtoRedisKey = redisKeysToCacheKeys.get(mapKey); + if (cacheKeysRelatedtoRedisKey != null) { + cacheKeysRelatedtoRedisKey.remove(cacheKey); } } return removed; @@ -118,8 +123,10 @@ public List deleteByRedisKey(Object key) { List cacheKeys = new ArrayList<>(); if (commands != null) { cacheKeys.addAll(commands.stream().filter(this::removeFromStore).collect(Collectors.toList())); + stats.invalidationByServer(cacheKeys.size()); redisKeysToCacheKeys.remove(mapKey); } + stats.invalidationMessages(); return cacheKeys; } finally { lock.unlock(); @@ -149,6 +156,7 @@ public int flush() { clearStore(); redisKeysToCacheKeys.clear(); getEvictionPolicy().resetAll(); + getStats().flush(); return result; } finally { lock.unlock(); @@ -168,6 +176,18 @@ public Boolean hasCacheKey(CacheKey cacheKey) { @Override public abstract EvictionPolicy getEvictionPolicy(); + @Override + public CacheStats getStats() { + return stats; + } + + @Override + public CacheStats getAndResetStats() { + CacheStats result = stats; + stats = new CacheStats(); + return result; + } + // End of Cache interface methods // abstract methods to be implemented by the concrete classes @@ -199,4 +219,5 @@ private ByteBuffer makeKeyForRedisKeysToCacheKeys(Object key) { private static ByteBuffer makeKeyForRedisKeysToCacheKeys(byte[] b) { return ByteBuffer.wrap(b); } + } diff --git a/src/main/java/redis/clients/jedis/csc/LRUEviction.java b/src/main/java/redis/clients/jedis/csc/LRUEviction.java index d749fe9d79..db26c5da5d 100644 --- a/src/main/java/redis/clients/jedis/csc/LRUEviction.java +++ b/src/main/java/redis/clients/jedis/csc/LRUEviction.java @@ -1,6 +1,7 @@ package redis.clients.jedis.csc; import java.util.*; +import java.util.concurrent.atomic.AtomicLong; /** * Simple L(east) R(ecently) U(sed) eviction policy @@ -17,6 +18,7 @@ public class LRUEviction implements EvictionPolicy { private final Cache cache; private final LinkedHashMap accessTimes; private CacheKey lastEvicted = null; + private AtomicLong evictedCount = new AtomicLong(0); /** * Constructor that gets the cache passed @@ -26,12 +28,15 @@ public class LRUEviction implements EvictionPolicy { */ public LRUEviction(Cache cache, int initialCapacity) { this.cache = cache; - this.accessTimes = new LinkedHashMap(initialCapacity, 0.75F, true) { + this.accessTimes = new LinkedHashMap(initialCapacity, 1F, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { boolean result = size() > LRUEviction.this.cache.getMaxSize(); if (result) { - lastEvicted = eldest.getKey(); + if (LRUEviction.this.cache.delete(eldest.getKey())) { + lastEvicted = eldest.getKey(); + evictedCount.incrementAndGet(); + } } return result; } @@ -56,9 +61,7 @@ public String getName() { @Override public CacheKey evictNext() { // its already done, thanks to the LinkedHashMap - CacheKey temp = lastEvicted; - lastEvicted = null; - return temp; + return evictedCount.decrementAndGet() == 0 ? null : lastEvicted; } @Override @@ -85,6 +88,8 @@ public boolean reset(CacheKey cacheKey) { public int resetAll() { int result = this.accessTimes.size(); accessTimes.clear(); + lastEvicted = null; + evictedCount.set(0); return result; } } diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index 2f88256aa0..a0a20ba741 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -8,12 +8,10 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.stats.CacheStats; -import java.net.URI; import java.util.concurrent.TimeUnit; import org.hamcrest.Matchers; import org.junit.Test; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.util.JedisURIHelper; public class CaffeineClientSideCacheTest extends ClientSideCacheTestBase { diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 40487fea2c..d26efaacf7 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -4,18 +4,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.io.File; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.Set; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; @@ -26,11 +21,8 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import redis.clients.jedis.CommandObjects; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPoolConfig; import redis.clients.jedis.EndpointConfig; @@ -39,11 +31,13 @@ import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import java.util.ArrayList; +import java.util.List; @RunWith(Parameterized.class) public class JedisPooledClientSideCacheTest { @@ -104,13 +98,22 @@ public void tearDown() throws Exception { return poolConfig; }; + private void sleep() { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + @Test public void simple() { try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); - assertEquals(null, jedis.get("foo")); + sleep(); + assertNull(jedis.get("foo")); } } @@ -125,10 +128,12 @@ public void simpleWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.del("foo"); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals(null, jedis.get("foo")); + sleep(); + assertNull(jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(0)); - jedis.ping(); + sleep(); assertThat(map, Matchers.aMapWithSize(0)); + sleep(); assertNull(jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(0)); } @@ -140,7 +145,8 @@ public void flushAll() { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); - assertEquals(null, jedis.get("foo")); + sleep(); + assertNull(jedis.get("foo")); } } @@ -155,41 +161,42 @@ public void flushAllWithSimpleMap() { assertThat(map, Matchers.aMapWithSize(1)); control.flushAll(); assertThat(map, Matchers.aMapWithSize(1)); - assertEquals(null, jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); - jedis.ping(); + sleep(); + assertNull(jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(0)); + sleep(); assertNull(jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(0)); } } @Test - public void testConcurrentAccess() throws InterruptedException { + public void testSequentialAccess() throws InterruptedException { int threadCount = 10; - int iterations = 1000; + int iterations = 10000; try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get())) { - String finalValue = jedis.set("foo", "0"); + jedis.set("foo", "0"); } ReentrantLock lock = new ReentrantLock(true); ExecutorService executorService = Executors.newFixedThreadPool(threadCount); // Create the shared mock instance of cache - TestCache mockedCache = Mockito.spy(new TestCache()); + TestCache testCache = new TestCache(); // Submit multiple threads to perform concurrent operations - CountDownLatch latch = new CountDownLatch(10); + CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { executorService.submit(() -> { - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), mockedCache)) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { for (int j = 0; j < iterations; j++) { lock.lock(); try { // Simulate continious get and update operations and consume invalidation events meanwhile - Integer value = new Integer(jedis.get("foo")) + 1; - jedis.set("foo", value.toString()); + Integer value = new Integer(jedis.get("foo")); + assertEquals(control.get("foo"), value.toString()); + assertEquals("OK", jedis.set("foo", (++value).toString())); } finally { lock.unlock(); } @@ -210,4 +217,133 @@ public void testConcurrentAccess() throws InterruptedException { } } + @Test + public void testConcurrentAccessWithStats() throws InterruptedException { + int threadCount = 10; + int iterations = 10000; + + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get())) { + jedis.set("foo", "0"); + } + + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + + // Create the shared mock instance of cache + TestCache testCache = new TestCache(); + + // Submit multiple threads to perform concurrent operations + CountDownLatch latch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + for (int j = 0; j < iterations; j++) { + // Simulate continious get and update operations and consume invalidation events meanwhile + Integer value = new Integer(jedis.get("foo")) + 1; + assertEquals("OK", jedis.set("foo", value.toString())); + } + } finally { + latch.countDown(); + } + }); + } + + // wait for all threads to complete + latch.await(); + + CacheStats stats = testCache.getStats(); + assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount()); + assertEquals(stats.getMissCount(), stats.getLoadCount()); + } + + @Test + public void testMaxSize() throws InterruptedException { + int threadCount = 10; + int iterations = 11000; + int maxSize = 1000; + + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + + ConcurrentHashMap map = new ConcurrentHashMap(); + // Create the shared mock instance of cache + TestCache testCache = new TestCache(maxSize, map, DefaultClientSideCacheable.INSTANCE); + + // Submit multiple threads to perform concurrent operations + CountDownLatch latch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + for (int j = 0; j < iterations; j++) { + // Simulate continious get and update operations and consume invalidation events meanwhile + assertEquals("OK", jedis.set("foo" + j, "foo" + j)); + jedis.get("foo" + j); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + latch.countDown(); + } + }); + } + + // wait for all threads to complete + latch.await(); + + CacheStats stats = testCache.getStats(); + + assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount()); + assertEquals(stats.getMissCount(), stats.getLoadCount()); + assertEquals(threadCount * iterations, stats.getNonCacheableCount()); + assertTrue(maxSize >= testCache.getSize()); + } + + @Test + public void testEvictionPolicy() throws InterruptedException { + int maxSize = 100; + int expectedEvictions = 20; + int touchOffset = 10; + + ConcurrentHashMap map = new ConcurrentHashMap(); + TestCache testCache = new TestCache(maxSize, map, DefaultClientSideCacheable.INSTANCE); + + // fill the cache for maxSize + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + for (int i = 0; i < maxSize; i++) { + jedis.set("foo" + i, "bar" + i); + assertEquals("bar" + i, jedis.get("foo" + i)); + } + } + + // touch a set of keys to prevent from eviction + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) { + assertEquals("bar" + i, jedis.get("foo" + i)); + } + } + + // add more keys to trigger eviction + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + for (int i = maxSize; i < maxSize + expectedEvictions; i++) { + jedis.set("foo" + i, "bar" + i); + assertEquals("bar" + i, jedis.get("foo" + i)); + } + } + + // check touched keys not evicted + for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) { + assertTrue(map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + } + + // check expected evictions are done till the offset + for (int i = 0; i < touchOffset; i++) { + assertTrue(!map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + } + + /// check expected evictions are done after the touched keys + for (int i = touchOffset + expectedEvictions; i < (2 * expectedEvictions); i++) { + assertTrue(!map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + } + + assertEquals(maxSize, testCache.getSize()); + } + } diff --git a/src/test/java/redis/clients/jedis/csc/TestCache.java b/src/test/java/redis/clients/jedis/csc/TestCache.java index adcbe400ee..74ae8671e8 100644 --- a/src/test/java/redis/clients/jedis/csc/TestCache.java +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -17,4 +17,8 @@ public TestCache(Map map, ClientSideCacheable cacheable) { super(1000, map, cacheable); } + public TestCache(int maxSize, Map map, ClientSideCacheable cacheable) { + super(maxSize, map, cacheable); + } + } From 67f2d771d463a950f6c790af727f7122f2a17428 Mon Sep 17 00:00:00 2001 From: atakavci Date: Thu, 1 Aug 2024 15:29:14 +0300 Subject: [PATCH 10/15] - renmae abstract cache class - add test case for returning new instance of cache object --- ...lientSideCache.java => AbstractCache.java} | 6 ++-- .../jedis/csc/CaffeineClientSideCache.java | 2 +- .../redis/clients/jedis/csc/DefaultCache.java | 2 +- .../jedis/csc/GuavaClientSideCache.java | 2 +- .../csc/ClientSideCacheFunctionalityTest.java | 32 ++++++++++++++++++- .../csc/JedisPooledClientSideCacheTest.java | 6 +--- 6 files changed, 38 insertions(+), 12 deletions(-) rename src/main/java/redis/clients/jedis/csc/{ClientSideCache.java => AbstractCache.java} (97%) diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/AbstractCache.java similarity index 97% rename from src/main/java/redis/clients/jedis/csc/ClientSideCache.java rename to src/main/java/redis/clients/jedis/csc/AbstractCache.java index 6588a355ec..ddd45b0b65 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/AbstractCache.java @@ -20,7 +20,7 @@ * GuavaClientSideCache} or a custom implementation of their own. */ @Experimental -public abstract class ClientSideCache implements Cache { +public abstract class AbstractCache implements Cache { private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>(); @@ -28,11 +28,11 @@ public abstract class ClientSideCache implements Cache { private ReentrantLock lock = new ReentrantLock(); private volatile CacheStats stats = new CacheStats(); - protected ClientSideCache(int maximumSize) { + protected AbstractCache(int maximumSize) { this.maximumSize = maximumSize; } - protected ClientSideCache(int maximumSize, ClientSideCacheable cacheable) { + protected AbstractCache(int maximumSize, ClientSideCacheable cacheable) { this.maximumSize = maximumSize; this.cacheable = cacheable; } diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java index e203672e58..500950507c 100644 --- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java @@ -9,7 +9,7 @@ import java.util.concurrent.TimeUnit; @Experimental -public class CaffeineClientSideCache extends ClientSideCache { +public class CaffeineClientSideCache extends AbstractCache { private final Cache cache; protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; diff --git a/src/main/java/redis/clients/jedis/csc/DefaultCache.java b/src/main/java/redis/clients/jedis/csc/DefaultCache.java index 1ef77b3f61..b51c343cd5 100644 --- a/src/main/java/redis/clients/jedis/csc/DefaultCache.java +++ b/src/main/java/redis/clients/jedis/csc/DefaultCache.java @@ -4,7 +4,7 @@ import java.util.HashMap; import java.util.Map; -public class DefaultCache extends ClientSideCache { +public class DefaultCache extends AbstractCache { protected final Map cache; private final LRUEviction evictionPolicy; diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java index 8cb3f9194e..0bec24dd93 100644 --- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java @@ -12,7 +12,7 @@ import java.util.stream.StreamSupport; @Experimental -public class GuavaClientSideCache extends ClientSideCache { +public class GuavaClientSideCache extends AbstractCache { private final Cache cache; protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index 70d3e35ff8..7cb7f99d17 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -3,15 +3,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.junit.Assert; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import redis.clients.jedis.CommandObjects; import redis.clients.jedis.JedisPooled; import redis.clients.jedis.UnifiedJedis; @@ -104,4 +108,30 @@ public void testInvalidationWithUnifiedJedis() { controlClient.close(); } } + + @Test + public void differentInstanceOnEachCacheHit() { + ConcurrentHashMap map = new ConcurrentHashMap(); + TestCache testCache = new TestCache(map); + + // fill the cache for maxSize + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), testCache)) { + jedis.sadd("foo", "a"); + jedis.sadd("foo", "b"); + + Set expected = new HashSet(); + expected.add("a"); + expected.add("b"); + + Set members1 = jedis.smembers("foo"); + Set members2 = jedis.smembers("foo"); + + Set fromMap = (Set) testCache.get(new CacheKey<>(new CommandObjects().smembers("foo"))).getValue(); + assertEquals(expected, members1); + assertEquals(expected, members2); + assertEquals(expected, fromMap); + assertTrue(members1 != members2); + assertTrue(members1 != fromMap); + } + } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index d26efaacf7..9b9c28fe7f 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -8,10 +8,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.Set; import java.util.function.Supplier; -import java.util.stream.Collectors; - import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; import org.junit.After; @@ -36,8 +33,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantLock; -import java.util.ArrayList; -import java.util.List; @RunWith(Parameterized.class) public class JedisPooledClientSideCacheTest { @@ -346,4 +341,5 @@ public void testEvictionPolicy() throws InterruptedException { assertEquals(maxSize, testCache.getSize()); } + } From 195dd3e1043d27b0de1d3e2666c91c03f5add461 Mon Sep 17 00:00:00 2001 From: atakavci Date: Fri, 2 Aug 2024 10:52:47 +0300 Subject: [PATCH 11/15] - change order of execution in sequential acces test --- .../jedis/csc/JedisPooledClientSideCacheTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 9b9c28fe7f..6747742bcc 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -189,8 +189,8 @@ public void testSequentialAccess() throws InterruptedException { lock.lock(); try { // Simulate continious get and update operations and consume invalidation events meanwhile + assertEquals(control.get("foo"), jedis.get("foo")); Integer value = new Integer(jedis.get("foo")); - assertEquals(control.get("foo"), value.toString()); assertEquals("OK", jedis.set("foo", (++value).toString())); } finally { lock.unlock(); @@ -206,10 +206,9 @@ public void testSequentialAccess() throws InterruptedException { latch.await(); // Verify the final value of "foo" in Redis - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get())) { - String finalValue = jedis.get("foo"); - assertEquals(threadCount * iterations, Integer.parseInt(finalValue)); - } + String finalValue = control.get("foo"); + assertEquals(threadCount * iterations, Integer.parseInt(finalValue)); + } @Test @@ -341,5 +340,4 @@ public void testEvictionPolicy() throws InterruptedException { assertEquals(maxSize, testCache.getSize()); } - } From 0069c5db7a5f14847a9545f63ad2cfe2067b6eed Mon Sep 17 00:00:00 2001 From: atakavci Date: Tue, 6 Aug 2024 11:57:22 +0300 Subject: [PATCH 12/15] - flush the cache on any disconnect - replace LRU policy references with EvictionPolicy interface - add some constructor overloads to enable custom eviction policies on cache --- .../clients/jedis/csc/AbstractCache.java | 4 +-- .../clients/jedis/csc/CacheConnection.java | 6 ++++ .../jedis/csc/CaffeineClientSideCache.java | 9 +++-- .../redis/clients/jedis/csc/DefaultCache.java | 13 ++++--- .../clients/jedis/csc/EvictionPolicy.java | 6 ++++ .../jedis/csc/GuavaClientSideCache.java | 5 +-- .../redis/clients/jedis/csc/LRUEviction.java | 34 ++++++++++++------- 7 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/main/java/redis/clients/jedis/csc/AbstractCache.java b/src/main/java/redis/clients/jedis/csc/AbstractCache.java index ddd45b0b65..9e140bffd4 100644 --- a/src/main/java/redis/clients/jedis/csc/AbstractCache.java +++ b/src/main/java/redis/clients/jedis/csc/AbstractCache.java @@ -22,14 +22,14 @@ @Experimental public abstract class AbstractCache implements Cache { - private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile + private ClientSideCacheable cacheable; private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>(); private final int maximumSize; private ReentrantLock lock = new ReentrantLock(); private volatile CacheStats stats = new CacheStats(); protected AbstractCache(int maximumSize) { - this.maximumSize = maximumSize; + this(maximumSize, DefaultClientSideCacheable.INSTANCE); } protected AbstractCache(int maximumSize, ClientSideCacheable cacheable) { diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 67218cd22d..6638383ca9 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -54,6 +54,12 @@ protected void protocolReadPushes(RedisInputStream inputStream) { } } + @Override + public void disconnect() { + super.disconnect(); + clientSideCache.flush(); + } + @Override public T executeCommand(final CommandObject commandObject) { CacheKey key = new CacheKey<>(commandObject); diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java index 500950507c..d12be857b3 100644 --- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java @@ -14,12 +14,17 @@ public class CaffeineClientSideCache extends AbstractCache { private final Cache cache; protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; protected static final int DEFAULT_EXPIRE_SECONDS = 100; - private final LRUEviction evictionPolicy; + private final EvictionPolicy evictionPolicy; public CaffeineClientSideCache(Cache caffeineCache) { + this(caffeineCache, new LRUEviction(DEFAULT_MAXIMUM_SIZE)); + } + + public CaffeineClientSideCache(Cache caffeineCache, EvictionPolicy evictionPolicy) { super(DEFAULT_MAXIMUM_SIZE); this.cache = caffeineCache; - this.evictionPolicy = new LRUEviction(this, DEFAULT_MAXIMUM_SIZE); + this.evictionPolicy = evictionPolicy; + this.evictionPolicy.setCache(this); } @Override diff --git a/src/main/java/redis/clients/jedis/csc/DefaultCache.java b/src/main/java/redis/clients/jedis/csc/DefaultCache.java index b51c343cd5..a382dc7623 100644 --- a/src/main/java/redis/clients/jedis/csc/DefaultCache.java +++ b/src/main/java/redis/clients/jedis/csc/DefaultCache.java @@ -7,26 +7,25 @@ public class DefaultCache extends AbstractCache { protected final Map cache; - private final LRUEviction evictionPolicy; + private final EvictionPolicy evictionPolicy; public DefaultCache(int maximumSize) { this(maximumSize, new HashMap()); } public DefaultCache(int maximumSize, Map map) { - super(maximumSize); - this.cache = map; - this.evictionPolicy = new LRUEviction(this, maximumSize); + this(maximumSize, map, DefaultClientSideCacheable.INSTANCE, new LRUEviction(maximumSize)); } public DefaultCache(int maximumSize, ClientSideCacheable cacheable) { - this(maximumSize, null, cacheable); + this(maximumSize, new HashMap(), cacheable, new LRUEviction(maximumSize)); } - public DefaultCache(int maximumSize, Map map, ClientSideCacheable cacheable) { + public DefaultCache(int maximumSize, Map map, ClientSideCacheable cacheable, EvictionPolicy evictionPolicy) { super(maximumSize, cacheable); this.cache = map; - this.evictionPolicy = new LRUEviction(this, maximumSize); + this.evictionPolicy = evictionPolicy; + this.evictionPolicy.setCache(this); } @Override diff --git a/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java b/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java index b4b4f29128..217b04263e 100644 --- a/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java +++ b/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java @@ -26,6 +26,12 @@ enum EvictionType { */ Cache getCache(); + /** + * Sets the cache that is associated to this policy instance + * @param cache The cache instance + */ + void setCache(Cache cache); + /** * @return The type of policy */ diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java index 0bec24dd93..c8a7e6d0d5 100644 --- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java @@ -17,12 +17,13 @@ public class GuavaClientSideCache extends AbstractCache { private final Cache cache; protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; protected static final int DEFAULT_EXPIRE_SECONDS = 100; - private final LRUEviction evictionPolicy; + private final EvictionPolicy evictionPolicy; public GuavaClientSideCache(Cache guavaCache) { super(DEFAULT_MAXIMUM_SIZE); this.cache = guavaCache; - this.evictionPolicy = new LRUEviction(this, DEFAULT_MAXIMUM_SIZE); + this.evictionPolicy = new LRUEviction(DEFAULT_MAXIMUM_SIZE); + this.evictionPolicy.setCache(this); } @Override diff --git a/src/main/java/redis/clients/jedis/csc/LRUEviction.java b/src/main/java/redis/clients/jedis/csc/LRUEviction.java index db26c5da5d..91e1681981 100644 --- a/src/main/java/redis/clients/jedis/csc/LRUEviction.java +++ b/src/main/java/redis/clients/jedis/csc/LRUEviction.java @@ -15,10 +15,11 @@ public class LRUEviction implements EvictionPolicy { /** * The cache that is associated to that policy instance */ - private final Cache cache; - private final LinkedHashMap accessTimes; + private Cache cache; + private LinkedHashMap accessTimes; private CacheKey lastEvicted = null; private AtomicLong evictedCount = new AtomicLong(0); + private int initialCapacity; /** * Constructor that gets the cache passed @@ -26,19 +27,17 @@ public class LRUEviction implements EvictionPolicy { * @param cache * @param initialCapacity */ - public LRUEviction(Cache cache, int initialCapacity) { + public LRUEviction(int initialCapacity) { + this.initialCapacity = initialCapacity; + } + + @Override + public void setCache(Cache cache) { this.cache = cache; - this.accessTimes = new LinkedHashMap(initialCapacity, 1F, true) { + this.accessTimes = new LinkedHashMap(initialCapacity, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - boolean result = size() > LRUEviction.this.cache.getMaxSize(); - if (result) { - if (LRUEviction.this.cache.delete(eldest.getKey())) { - lastEvicted = eldest.getKey(); - evictedCount.incrementAndGet(); - } - } - return result; + return evictFromCache(eldest); } }; } @@ -92,4 +91,15 @@ public int resetAll() { evictedCount.set(0); return result; } + + private boolean evictFromCache(Map.Entry eldest) { + boolean result = accessTimes.size() > cache.getMaxSize(); + if (result) { + if (cache.delete(eldest.getKey())) { + lastEvicted = eldest.getKey(); + evictedCount.incrementAndGet(); + } + } + return result; + } } From 8f57e451f616ce663bef3185f9eaff51511bf0b6 Mon Sep 17 00:00:00 2001 From: atakavci Date: Thu, 8 Aug 2024 11:10:10 +0300 Subject: [PATCH 13/15] fix testcache --- src/test/java/redis/clients/jedis/csc/TestCache.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/redis/clients/jedis/csc/TestCache.java b/src/test/java/redis/clients/jedis/csc/TestCache.java index 74ae8671e8..018586aa12 100644 --- a/src/test/java/redis/clients/jedis/csc/TestCache.java +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -14,11 +14,12 @@ public TestCache(Map map) { } public TestCache(Map map, ClientSideCacheable cacheable) { - super(1000, map, cacheable); + + super(1000, map, cacheable, new LRUEviction(1000)); } public TestCache(int maxSize, Map map, ClientSideCacheable cacheable) { - super(maxSize, map, cacheable); + super(maxSize, map, cacheable, new LRUEviction(maxSize)); } } From 04c196b0421d710ad883408a6f5a4c65214a7c35 Mon Sep 17 00:00:00 2001 From: atakavci Date: Thu, 8 Aug 2024 11:59:13 +0300 Subject: [PATCH 14/15] fix javadoc issue --- src/main/java/redis/clients/jedis/csc/LRUEviction.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/redis/clients/jedis/csc/LRUEviction.java b/src/main/java/redis/clients/jedis/csc/LRUEviction.java index 91e1681981..0425d3f425 100644 --- a/src/main/java/redis/clients/jedis/csc/LRUEviction.java +++ b/src/main/java/redis/clients/jedis/csc/LRUEviction.java @@ -24,7 +24,6 @@ public class LRUEviction implements EvictionPolicy { /** * Constructor that gets the cache passed * - * @param cache * @param initialCapacity */ public LRUEviction(int initialCapacity) { From 9c7119d72af47b774977b1b5184a81b4c595221d Mon Sep 17 00:00:00 2001 From: atakavci Date: Sun, 11 Aug 2024 16:39:21 +0300 Subject: [PATCH 15/15] - fix multithreaded eviction policy issue - update guava and caffeine implementations according to abstract cache --- .../clients/jedis/csc/AbstractCache.java | 13 ++- .../clients/jedis/csc/CacheConnection.java | 3 +- .../redis/clients/jedis/csc/CacheEntry.java | 7 +- .../redis/clients/jedis/csc/CacheStats.java | 8 +- .../jedis/csc/CaffeineClientSideCache.java | 47 ++--------- .../jedis/csc/GuavaClientSideCache.java | 49 ++--------- .../redis/clients/jedis/csc/LRUEviction.java | 56 +++++++------ .../jedis/benchmark/CSCPooleadBenchmark.java | 68 +++++++-------- .../csc/CaffeineClientSideCacheTest.java | 73 ++++++++-------- .../csc/ClientSideCacheFunctionalityTest.java | 60 +++++++++---- .../jedis/csc/GuavaClientSideCacheTest.java | 83 ++++++------------ .../csc/JedisPooledClientSideCacheTest.java | 84 ++++++++++++++----- .../redis/clients/jedis/csc/TestCache.java | 11 ++- 13 files changed, 267 insertions(+), 295 deletions(-) diff --git a/src/main/java/redis/clients/jedis/csc/AbstractCache.java b/src/main/java/redis/clients/jedis/csc/AbstractCache.java index 9e140bffd4..c5750d385b 100644 --- a/src/main/java/redis/clients/jedis/csc/AbstractCache.java +++ b/src/main/java/redis/clients/jedis/csc/AbstractCache.java @@ -52,7 +52,11 @@ public int getMaxSize() { @Override public CacheEntry get(CacheKey cacheKey) { - return getFromStore(cacheKey); + CacheEntry entry = getFromStore(cacheKey); + if (entry != null) { + getEvictionPolicy().touch(cacheKey); + } + return entry; } @Override @@ -60,8 +64,11 @@ public CacheEntry set(CacheKey cacheKey, CacheEntry entry) { lock.lock(); try { entry = putIntoStore(cacheKey, entry); - getEvictionPolicy().touch(cacheKey); - if (getEvictionPolicy().evictNext() != null) { + EvictionPolicy policy = getEvictionPolicy(); + policy.touch(cacheKey); + CacheKey evictedKey = policy.evictNext(); + if (evictedKey != null) { + delete(evictedKey); stats.evict(); } for (Object redisKey : cacheKey.getRedisKeys()) { diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 6638383ca9..e5851e7140 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -113,8 +113,7 @@ private CacheEntry validateEntry(CacheEntry cacheEntry) { return null; } - cacheEntry = clientSideCache.get(cacheEntry.getCacheKey()); - return cacheEntry; + return clientSideCache.get(cacheEntry.getCacheKey()); } } } diff --git a/src/main/java/redis/clients/jedis/csc/CacheEntry.java b/src/main/java/redis/clients/jedis/csc/CacheEntry.java index 62798db074..c0de029db3 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheEntry.java +++ b/src/main/java/redis/clients/jedis/csc/CacheEntry.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.ref.WeakReference; @@ -42,7 +43,7 @@ private static byte[] toBytes(Object object) { oos.flush(); oos.close(); return baos.toByteArray(); - } catch (Exception e) { + } catch (IOException e) { throw new JedisCacheException("Failed to serialize object", e); } } @@ -51,7 +52,9 @@ private T toObject(byte[] data) { try (ByteArrayInputStream bais = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bais)) { return (T) ois.readObject(); - } catch (Exception e) { + } catch (IOException e) { + throw new JedisCacheException("Failed to deserialize object", e); + } catch (ClassNotFoundException e) { throw new JedisCacheException("Failed to deserialize object", e); } } diff --git a/src/main/java/redis/clients/jedis/csc/CacheStats.java b/src/main/java/redis/clients/jedis/csc/CacheStats.java index 7554cc52c0..e689ea0d77 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheStats.java +++ b/src/main/java/redis/clients/jedis/csc/CacheStats.java @@ -29,19 +29,19 @@ protected void evict() { evicts.incrementAndGet(); } - public void nonCacheable() { + protected void nonCacheable() { nonCacheable.incrementAndGet(); } - public void flush() { + protected void flush() { flush.incrementAndGet(); } - public void invalidationByServer(int size) { + protected void invalidationByServer(int size) { invalidationsByServer.addAndGet(size); } - public void invalidationMessages() { + protected void invalidationMessages() { invalidationMessages.incrementAndGet(); } diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java index d12be857b3..85627dba29 100644 --- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java @@ -6,23 +6,20 @@ import redis.clients.jedis.annots.Experimental; import java.util.Collection; -import java.util.concurrent.TimeUnit; @Experimental public class CaffeineClientSideCache extends AbstractCache { private final Cache cache; - protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; - protected static final int DEFAULT_EXPIRE_SECONDS = 100; private final EvictionPolicy evictionPolicy; - public CaffeineClientSideCache(Cache caffeineCache) { - this(caffeineCache, new LRUEviction(DEFAULT_MAXIMUM_SIZE)); + public CaffeineClientSideCache(int maximumSize) { + this(maximumSize, new LRUEviction(maximumSize)); } - public CaffeineClientSideCache(Cache caffeineCache, EvictionPolicy evictionPolicy) { - super(DEFAULT_MAXIMUM_SIZE); - this.cache = caffeineCache; + public CaffeineClientSideCache(int maximumSize, EvictionPolicy evictionPolicy) { + super(maximumSize); + this.cache = Caffeine.newBuilder().build(); this.evictionPolicy = evictionPolicy; this.evictionPolicy.setCache(this); } @@ -43,40 +40,6 @@ public CacheEntry getFromStore(CacheKey key) { return cache.getIfPresent(key); } - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private long maximumSize = DEFAULT_MAXIMUM_SIZE; - private long expireTime = DEFAULT_EXPIRE_SECONDS; - private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; - - private Builder() { - } - - public Builder maximumSize(int size) { - this.maximumSize = size; - return this; - } - - public Builder ttl(int seconds) { - this.expireTime = seconds; - return this; - } - - public CaffeineClientSideCache build() { - Caffeine cb = Caffeine.newBuilder(); - - cb.maximumSize(maximumSize); - - cb.expireAfterWrite(expireTime, expireTimeUnit); - - return new CaffeineClientSideCache(cb.build()); - } - } - // TODO: we should discuss if/how we utilize Caffeine and get back to here ! @Override diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java index c8a7e6d0d5..81853ad651 100644 --- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -15,14 +14,16 @@ public class GuavaClientSideCache extends AbstractCache { private final Cache cache; - protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; - protected static final int DEFAULT_EXPIRE_SECONDS = 100; private final EvictionPolicy evictionPolicy; - public GuavaClientSideCache(Cache guavaCache) { - super(DEFAULT_MAXIMUM_SIZE); - this.cache = guavaCache; - this.evictionPolicy = new LRUEviction(DEFAULT_MAXIMUM_SIZE); + public GuavaClientSideCache(int maximumSize) { + this(maximumSize, new LRUEviction(maximumSize)); + } + + public GuavaClientSideCache(int maximumSize, EvictionPolicy evictionPolicy) { + super(maximumSize); + this.cache = CacheBuilder.newBuilder().build(); + this.evictionPolicy = evictionPolicy; this.evictionPolicy.setCache(this); } @@ -48,40 +49,6 @@ public CacheEntry getFromStore(CacheKey key) { return cache.getIfPresent(key); } - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private long maximumSize = DEFAULT_MAXIMUM_SIZE; - private long expireTime = DEFAULT_EXPIRE_SECONDS; - private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; - - private Builder() { - } - - public Builder maximumSize(int size) { - this.maximumSize = size; - return this; - } - - public Builder ttl(int seconds) { - this.expireTime = seconds; - return this; - } - - public GuavaClientSideCache build() { - CacheBuilder cb = CacheBuilder.newBuilder(); - - cb.maximumSize(maximumSize); - - cb.expireAfterWrite(expireTime, expireTimeUnit); - - return new GuavaClientSideCache(cb.build()); - } - } - // TODO: we should discuss if/how we utilize Guava and get back to here ! @Override diff --git a/src/main/java/redis/clients/jedis/csc/LRUEviction.java b/src/main/java/redis/clients/jedis/csc/LRUEviction.java index 0425d3f425..b75c7338ba 100644 --- a/src/main/java/redis/clients/jedis/csc/LRUEviction.java +++ b/src/main/java/redis/clients/jedis/csc/LRUEviction.java @@ -1,7 +1,7 @@ package redis.clients.jedis.csc; import java.util.*; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.ConcurrentLinkedQueue; /** * Simple L(east) R(ecently) U(sed) eviction policy @@ -15,10 +15,13 @@ public class LRUEviction implements EvictionPolicy { /** * The cache that is associated to that policy instance */ - private Cache cache; - private LinkedHashMap accessTimes; - private CacheKey lastEvicted = null; - private AtomicLong evictedCount = new AtomicLong(0); + protected Cache cache; + protected LinkedHashMap accessTimes; + + protected ArrayDeque pendingEvictions = new ArrayDeque(); + + protected ConcurrentLinkedQueue msg = new ConcurrentLinkedQueue(); + private int initialCapacity; /** @@ -33,10 +36,19 @@ public LRUEviction(int initialCapacity) { @Override public void setCache(Cache cache) { this.cache = cache; - this.accessTimes = new LinkedHashMap(initialCapacity, 0.75f, true) { + this.accessTimes = new LinkedHashMap(initialCapacity, 1f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - return evictFromCache(eldest); + boolean evictionRequired = cache.getSize() > cache.getMaxSize() + || accessTimes.size() > cache.getMaxSize(); + // here the cache check is only for performance gain; we are trying to avoid the sequence add + poll + hasCacheKey + // and prefer to check it in cache once in early stage. + // if there is nothing to remove in actual cache as of now, stop worrying about it. + if (evictionRequired && cache.hasCacheKey(eldest.getKey())) { + pendingEvictions.addLast(eldest.getKey()); + + } + return evictionRequired; } }; } @@ -57,48 +69,38 @@ public String getName() { } @Override - public CacheKey evictNext() { - // its already done, thanks to the LinkedHashMap - return evictedCount.decrementAndGet() == 0 ? null : lastEvicted; + public synchronized CacheKey evictNext() { + CacheKey cacheKey = pendingEvictions.pollFirst(); + while (cacheKey != null && !cache.hasCacheKey(cacheKey)) { + cacheKey = pendingEvictions.pollFirst(); + } + return cacheKey; } @Override - public List evictMany(int n) { + public synchronized List evictMany(int n) { List result = new ArrayList<>(); for (int i = 0; i < n; i++) { result.add(this.evictNext()); } - return result; } @Override - public void touch(CacheKey cacheKey) { + public synchronized void touch(CacheKey cacheKey) { this.accessTimes.put(cacheKey, new Date().getTime()); } @Override - public boolean reset(CacheKey cacheKey) { + public synchronized boolean reset(CacheKey cacheKey) { return this.accessTimes.remove(cacheKey) != null; } @Override - public int resetAll() { + public synchronized int resetAll() { int result = this.accessTimes.size(); accessTimes.clear(); - lastEvicted = null; - evictedCount.set(0); return result; } - private boolean evictFromCache(Map.Entry eldest) { - boolean result = accessTimes.size() > cache.getMaxSize(); - if (result) { - if (cache.delete(eldest.getKey())) { - lastEvicted = eldest.getKey(); - evictedCount.incrementAndGet(); - } - } - return result; - } } diff --git a/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java index f28f499f95..8ee0580011 100644 --- a/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java +++ b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java @@ -11,33 +11,29 @@ public class CSCPooleadBenchmark { private static EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone0"); - private static final int TOTAL_OPERATIONS = 100000; + private static final int TOTAL_OPERATIONS = 1000000; + private static final int NUMBER_OF_THREADS = 50; public static void main(String[] args) throws Exception { + try (Jedis j = new Jedis(endpoint.getHost(), endpoint.getPort())) { j.auth(endpoint.getPassword()); j.flushAll(); j.disconnect(); } - int totalRounds = 10; + int totalRounds = 50; long withoutCache = 0; long withCache = 0; - for (int i = 0; i < totalRounds; i++) { - withoutCache += runBenchmark(null); - } - for (int i = 0; i < totalRounds; i++) { - withCache += runBenchmark(new TestCache()); - } - System.out.println(String.format("after first round withoutCache: %d ms, withCache: %d ms", withoutCache, withCache)); for (int i = 0; i < totalRounds; i++) { withoutCache += runBenchmark(null); + withCache += runBenchmark(new TestCache()); } for (int i = 0; i < totalRounds; i++) { - withCache += runBenchmark(new TestCache()); } - System.out.println(String.format("after second round withoutCache: %d ms, withCache: %d ms", withoutCache, withCache)); + System.out.println(String.format("after %d rounds withoutCache: %d ms, withCache: %d ms", totalRounds, + withoutCache, withCache)); System.out.println("execution time ratio: " + (double) withCache / withoutCache); } @@ -50,36 +46,34 @@ private static long runBenchmark(Cache cache) throws Exception { } private static void withPool(Cache cache) throws Exception { - JedisPooled jedis = null; - JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP3).password(endpoint.getPassword()).build(); - jedis = new JedisPooled(endpoint.getHostAndPort(), config, cache); - final JedisPooled j = jedis; + JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP3) + .password(endpoint.getPassword()).build(); List tds = new ArrayList<>(); - final AtomicInteger ind = new AtomicInteger(); - for (int i = 0; i < 50; i++) { - Thread hj = new Thread(new Runnable() { - @Override - public void run() { - for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) { - try { - final String key = "foo" + i; - j.set(key, key); - j.get(key); - } catch (Exception e) { - e.printStackTrace(); + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), config, cache)) { + for (int i = 0; i < NUMBER_OF_THREADS; i++) { + Thread hj = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) { + try { + final String key = "foo" + i; + jedis.set(key, key); + jedis.get(key); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } } } - } - }); - tds.add(hj); - hj.start(); - } - - for (Thread t : tds) { - t.join(); - } + }); + tds.add(hj); + hj.start(); + } - j.close(); + for (Thread t : tds) { + t.join(); + } + } } } diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index a0a20ba741..18a9b28d15 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -17,7 +17,7 @@ public class CaffeineClientSideCacheTest extends ClientSideCacheTestBase { @Test public void simple() { - CaffeineClientSideCache caffeine = CaffeineClientSideCache.builder().maximumSize(10).ttl(10).build(); + CaffeineClientSideCache caffeine = new CaffeineClientSideCache(10); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -29,68 +29,61 @@ public void simple() { @Test public void individualCommandsAndThenStats() { - Cache caffeine = Caffeine.newBuilder().recordStats().build(); + CaffeineClientSideCache caffeine = new CaffeineClientSideCache(100); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new CaffeineClientSideCache(caffeine), singleConnectionPoolConfig.get())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine, singleConnectionPoolConfig.get())) { control.set("foo", "bar"); - assertEquals(0, caffeine.estimatedSize()); - assertEquals("bar", jedis.get("foo")); - assertEquals(1, caffeine.estimatedSize()); + assertEquals(0, caffeine.getSize()); + assertEquals("bar", jedis.get("foo")); // cache miss + assertEquals(1, caffeine.getSize()); control.flushAll(); - assertEquals(1, caffeine.estimatedSize()); - assertEquals(null, jedis.get("foo")); - assertEquals(0, caffeine.estimatedSize()); + assertEquals(1, caffeine.getSize()); + assertEquals(null, jedis.get("foo")); // cache miss + assertEquals(0, caffeine.getSize()); jedis.ping(); - assertEquals(0, caffeine.estimatedSize()); - assertNull(jedis.get("foo")); - assertEquals(0, caffeine.estimatedSize()); + assertEquals(0, caffeine.getSize()); + assertNull(jedis.get("foo")); // cache miss + assertEquals(0, caffeine.getSize()); } - CacheStats stats = caffeine.stats(); - assertEquals(1L, stats.hitCount()); - assertThat(stats.missCount(), Matchers.greaterThan(0L)); + assertEquals(0, caffeine.getStats().getHitCount()); + assertEquals(caffeine.getStats().getMissCount(), 3); } @Test - public void maximumSize() { - final long maxSize = 10; - final long maxEstimatedSize = 52; - int count = 1000; - for (int i = 0; i < count; i++) { - control.set("k" + i, "v" + i); - } + public void maximumSizeExact() { + control.set("k1", "v1"); + control.set("k2", "v2"); - Cache caffeine = Caffeine.newBuilder().maximumSize(maxSize).recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine))) { - for (int i = 0; i < count; i++) { - jedis.get("k" + i); - assertThat(caffeine.estimatedSize(), Matchers.lessThanOrEqualTo(maxEstimatedSize)); - } + CaffeineClientSideCache caffeine = new CaffeineClientSideCache(1); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { + assertEquals(0, caffeine.getSize()); + jedis.get("k1"); + assertEquals(1, caffeine.getSize()); + assertEquals(0, caffeine.getStats().getEvictCount()); + jedis.get("k2"); + assertEquals(1, caffeine.getSize()); + assertEquals(1, caffeine.getStats().getEvictCount()); } - assertThat(caffeine.stats().evictionCount(), Matchers.greaterThanOrEqualTo(count - maxEstimatedSize)); } @Test - public void timeToLive() throws InterruptedException { + public void maximumSize() { + final int maxSize = 10; + final int maxEstimatedSize = 10; int count = 1000; for (int i = 0; i < count; i++) { control.set("k" + i, "v" + i); } - Cache caffeine = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine))) { + CaffeineClientSideCache caffeine = new CaffeineClientSideCache(maxSize); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { for (int i = 0; i < count; i++) { jedis.get("k" + i); + assertThat(caffeine.getSize(), Matchers.lessThanOrEqualTo(maxEstimatedSize)); } } - assertThat(caffeine.estimatedSize(), Matchers.equalTo((long) count)); - assertThat(caffeine.stats().evictionCount(), Matchers.equalTo(0L)); - - TimeUnit.SECONDS.sleep(2); - caffeine.cleanUp(); - assertThat(caffeine.estimatedSize(), Matchers.equalTo(0L)); - assertThat(caffeine.stats().evictionCount(), Matchers.equalTo((long) count)); + assertThat(caffeine.getStats().getEvictCount(), Matchers.greaterThanOrEqualTo((long) count - maxEstimatedSize)); } } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index 7cb7f99d17..77e04c5e3b 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -23,27 +23,30 @@ public class ClientSideCacheFunctionalityTest extends ClientSideCacheTestBase { @Test public void flushEntireCache() { - int count = 1000; + int count = 100; for (int i = 0; i < count; i++) { control.set("k" + i, "v" + i); } HashMap map = new HashMap<>(); Cache clientSideCache = new TestCache(map); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache); + try { for (int i = 0; i < count; i++) { jedis.get("k" + i); } - } - assertEquals(count, map.size()); - clientSideCache.flush(); - assertEquals(0, map.size()); + assertEquals(count, map.size()); + clientSideCache.flush(); + assertEquals(0, map.size()); + } finally { + jedis.close(); + } } @Test public void removeSpecificKey() { - int count = 1000; + int count = 100; for (int i = 0; i < count; i++) { control.set("k" + i, "v" + i); } @@ -51,20 +54,23 @@ public void removeSpecificKey() { // By using LinkedHashMap, we can get the hashes (map keys) at the same order of the actual keys. LinkedHashMap map = new LinkedHashMap<>(); Cache clientSideCache = new TestCache(map); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache); + try { for (int i = 0; i < count; i++) { jedis.get("k" + i); } - } - ArrayList commandHashes = new ArrayList<>(map.keySet()); - assertEquals(count, map.size()); - for (int i = 0; i < count; i++) { - String key = "k" + i; - CacheKey command = commandHashes.get(i); - assertTrue(map.containsKey(command)); - clientSideCache.deleteByRedisKey(key); - assertFalse(map.containsKey(command)); + ArrayList commandHashes = new ArrayList<>(map.keySet()); + assertEquals(count, map.size()); + for (int i = 0; i < count; i++) { + String key = "k" + i; + CacheKey command = commandHashes.get(i); + assertTrue(map.containsKey(command)); + clientSideCache.deleteByRedisKey(key); + assertFalse(map.containsKey(command)); + } + } finally { + jedis.close(); } } @@ -80,6 +86,23 @@ public void multiKeyOperation() { } } + @Test + public void maximumSizeExact() { + control.set("k1", "v1"); + control.set("k2", "v2"); + + DefaultCache cache = new DefaultCache(1); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { + assertEquals(0, cache.getSize()); + jedis.get("k1"); + assertEquals(1, cache.getSize()); + assertEquals(0, cache.getStats().getEvictCount()); + jedis.get("k2"); + assertEquals(1, cache.getSize()); + assertEquals(1, cache.getStats().getEvictCount()); + } + } + @Test public void testInvalidationWithUnifiedJedis() { Cache cache = new TestCache(); @@ -126,7 +149,8 @@ public void differentInstanceOnEachCacheHit() { Set members1 = jedis.smembers("foo"); Set members2 = jedis.smembers("foo"); - Set fromMap = (Set) testCache.get(new CacheKey<>(new CommandObjects().smembers("foo"))).getValue(); + Set fromMap = (Set) testCache.get(new CacheKey<>(new CommandObjects().smembers("foo"))) + .getValue(); assertEquals(expected, members1); assertEquals(expected, members2); assertEquals(expected, fromMap); diff --git a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java index 697a947dce..fad64df8ab 100644 --- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java @@ -4,22 +4,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheStats; - -import java.net.URI; import java.util.concurrent.TimeUnit; import org.hamcrest.Matchers; import org.junit.Test; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.util.JedisURIHelper; public class GuavaClientSideCacheTest extends ClientSideCacheTestBase { @Test public void simple() { - GuavaClientSideCache guava = GuavaClientSideCache.builder().maximumSize(10).ttl(10).build(); + GuavaClientSideCache guava = new GuavaClientSideCache(10); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -31,27 +25,26 @@ public void simple() { @Test public void individualCommandsAndThenStats() { - Cache guava = CacheBuilder.newBuilder().recordStats().build(); + GuavaClientSideCache guava = new GuavaClientSideCache(10000); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava, singleConnectionPoolConfig.get())) { control.set("foo", "bar"); - assertEquals(0, guava.size()); - assertEquals("bar", jedis.get("foo")); - assertEquals(1, guava.size()); + assertEquals(0, guava.getSize()); + assertEquals("bar", jedis.get("foo")); // cache miss + assertEquals(1, guava.getSize()); control.flushAll(); - assertEquals(1, guava.size()); - assertEquals(null, jedis.get("foo")); - assertEquals(0, guava.size()); + assertEquals(1, guava.getSize()); + assertEquals(null, jedis.get("foo")); // cache miss + assertEquals(0, guava.getSize()); jedis.ping(); - assertEquals(0, guava.size()); - assertNull(jedis.get("foo")); - assertEquals(0, guava.size()); + assertEquals(0, guava.getSize()); + assertNull(jedis.get("foo")); // cache miss + assertEquals(0, guava.getSize()); } - CacheStats stats = guava.stats(); - assertEquals(1L, stats.hitCount()); - assertThat(stats.missCount(), Matchers.greaterThan(0L)); + assertEquals(0, guava.getStats().getHitCount()); + assertEquals(guava.getStats().getMissCount(), 3); } @Test @@ -59,57 +52,35 @@ public void maximumSizeExact() { control.set("k1", "v1"); control.set("k2", "v2"); - Cache guava = CacheBuilder.newBuilder().maximumSize(1).recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava))) { - assertEquals(0, guava.size()); + GuavaClientSideCache guava = new GuavaClientSideCache(1); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { + assertEquals(0, guava.getSize()); jedis.get("k1"); - assertEquals(1, guava.size()); - assertEquals(0, guava.stats().evictionCount()); + assertEquals(1, guava.getSize()); + assertEquals(0, guava.getStats().getEvictCount()); jedis.get("k2"); - assertEquals(1, guava.size()); - assertEquals(1, guava.stats().evictionCount()); + assertEquals(1, guava.getSize()); + assertEquals(1, guava.getStats().getEvictCount()); } } @Test public void maximumSize() { - final long maxSize = 10; - final long maxEstimatedSize = 40; - int count = 1000; - for (int i = 0; i < count; i++) { - control.set("k" + i, "v" + i); - } - - Cache guava = CacheBuilder.newBuilder().maximumSize(maxSize).recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava))) { - for (int i = 0; i < count; i++) { - jedis.get("k" + i); - assertThat(guava.size(), Matchers.lessThanOrEqualTo(maxEstimatedSize)); - } - } - assertThat(guava.stats().evictionCount(), Matchers.greaterThan(count - maxEstimatedSize)); - } - - @Test - public void timeToLive() throws InterruptedException { + final int maxSize = 10; + final int maxEstimatedSize = 40; int count = 1000; for (int i = 0; i < count; i++) { control.set("k" + i, "v" + i); } - Cache guava = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava))) { + GuavaClientSideCache guava = new GuavaClientSideCache(maxSize); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { for (int i = 0; i < count; i++) { jedis.get("k" + i); + assertThat(guava.getSize(), Matchers.lessThanOrEqualTo(maxEstimatedSize)); } } - assertThat(guava.size(), Matchers.equalTo((long) count)); - assertThat(guava.stats().evictionCount(), Matchers.equalTo(0L)); - - TimeUnit.SECONDS.sleep(2); - guava.cleanUp(); - assertThat(guava.size(), Matchers.equalTo(0L)); - assertThat(guava.stats().evictionCount(), Matchers.equalTo((long) count)); + assertThat(guava.getStats().getEvictCount(), Matchers.greaterThan((long) count - maxEstimatedSize)); } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 6747742bcc..de2e3e8144 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -5,9 +5,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.function.Supplier; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; @@ -18,6 +20,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import org.locationtech.jts.util.Assert; import redis.clients.jedis.CommandObjects; import redis.clients.jedis.Connection; @@ -32,6 +35,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; @RunWith(Parameterized.class) @@ -296,7 +300,7 @@ public void testEvictionPolicy() throws InterruptedException { int expectedEvictions = 20; int touchOffset = 10; - ConcurrentHashMap map = new ConcurrentHashMap(); + HashMap map = new HashMap(); TestCache testCache = new TestCache(maxSize, map, DefaultClientSideCacheable.INSTANCE); // fill the cache for maxSize @@ -305,39 +309,79 @@ public void testEvictionPolicy() throws InterruptedException { jedis.set("foo" + i, "bar" + i); assertEquals("bar" + i, jedis.get("foo" + i)); } - } - // touch a set of keys to prevent from eviction - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + // touch a set of keys to prevent from eviction from index 10 to 29 for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) { assertEquals("bar" + i, jedis.get("foo" + i)); } - } - // add more keys to trigger eviction - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + // add more keys to trigger eviction, adding from 100 to 119 for (int i = maxSize; i < maxSize + expectedEvictions; i++) { jedis.set("foo" + i, "bar" + i); assertEquals("bar" + i, jedis.get("foo" + i)); } - } - // check touched keys not evicted - for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) { - assertTrue(map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); - } + // check touched keys not evicted + for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) { - // check expected evictions are done till the offset - for (int i = 0; i < touchOffset; i++) { - assertTrue(!map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); - } + assertTrue(map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + } + + // check expected evictions are done till the offset + for (int i = 0; i < touchOffset; i++) { + assertTrue(!map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + } - /// check expected evictions are done after the touched keys - for (int i = touchOffset + expectedEvictions; i < (2 * expectedEvictions); i++) { - assertTrue(!map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + /// check expected evictions are done after the touched keys + for (int i = touchOffset + expectedEvictions; i < (2 * expectedEvictions); i++) { + assertTrue(!map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + } + + assertEquals(maxSize, testCache.getSize()); } + } - assertEquals(maxSize, testCache.getSize()); + @Test + public void testEvictionPolicyMultithreaded() throws InterruptedException { + int NUMBER_OF_THREADS = 100; + int TOTAL_OPERATIONS = 1000000; + int NUMBER_OF_DISTINCT_KEYS = 53; + int MAX_SIZE = 20; + List exceptions = new ArrayList<>(); + + TestCache cache = new TestCache(MAX_SIZE, new HashMap<>(), DefaultClientSideCacheable.INSTANCE); + List tds = new ArrayList<>(); + final AtomicInteger ind = new AtomicInteger(); + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), cache)) { + for (int i = 0; i < NUMBER_OF_THREADS; i++) { + Thread hj = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; (i = ind.getAndIncrement()) < TOTAL_OPERATIONS;) { + try { + final String key = "foo" + i % NUMBER_OF_DISTINCT_KEYS; + if (i < NUMBER_OF_DISTINCT_KEYS) { + jedis.set(key, key); + } + jedis.get(key); + } catch (Exception e) { + exceptions.add(e); + throw e; + } + } + } + }); + tds.add(hj); + hj.start(); + } + + for (Thread t : tds) { + t.join(); + } + + Assert.equals(MAX_SIZE, cache.getSize()); + Assert.equals(0, exceptions.size()); + } } } diff --git a/src/test/java/redis/clients/jedis/csc/TestCache.java b/src/test/java/redis/clients/jedis/csc/TestCache.java index 018586aa12..fd90b1229b 100644 --- a/src/test/java/redis/clients/jedis/csc/TestCache.java +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -10,16 +10,21 @@ public TestCache() { } public TestCache(Map map) { - super(1000, map); + super(10000, map); } public TestCache(Map map, ClientSideCacheable cacheable) { - super(1000, map, cacheable, new LRUEviction(1000)); + super(10000, map, cacheable, new LRUEviction(10000)); } public TestCache(int maxSize, Map map, ClientSideCacheable cacheable) { - super(maxSize, map, cacheable, new LRUEviction(maxSize)); + this(maxSize, map, cacheable, new LRUEviction(maxSize)); + } + + public TestCache(int maxSize, Map map, ClientSideCacheable cacheable, + EvictionPolicy evictionPolicy) { + super(maxSize, map, cacheable, evictionPolicy); } }