From da9c463210a5bf385505b355b27785b22e5684af Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 28 Dec 2023 18:52:36 +0600 Subject: [PATCH 01/48] Initial support for client-side caching (#3658) --- .../redis/clients/jedis/ClientSideCache.java | 58 +++++++++++++++ .../java/redis/clients/jedis/Connection.java | 21 +++++- .../clients/jedis/JedisClientSideCache.java | 45 ++++++++++++ .../java/redis/clients/jedis/Protocol.java | 27 ++++++- .../clients/jedis/util/RedisInputStream.java | 19 +++++ .../jedis/JedisClientSideCacheTest.java | 70 +++++++++++++++++++ 6 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/ClientSideCache.java create mode 100644 src/main/java/redis/clients/jedis/JedisClientSideCache.java create mode 100644 src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java diff --git a/src/main/java/redis/clients/jedis/ClientSideCache.java b/src/main/java/redis/clients/jedis/ClientSideCache.java new file mode 100644 index 0000000000..5dd31b17e9 --- /dev/null +++ b/src/main/java/redis/clients/jedis/ClientSideCache.java @@ -0,0 +1,58 @@ +package redis.clients.jedis; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import redis.clients.jedis.exceptions.JedisException; +import redis.clients.jedis.util.SafeEncoder; + +public class ClientSideCache { + + private final Map cache = new HashMap<>(); + + protected ClientSideCache() { + } + + protected void invalidateKeys(List list) { + if (list == null) { + cache.clear(); + return; + } + + list.forEach(this::invalidateKey); + } + + private void invalidateKey(Object key) { + if (key instanceof byte[]) { + cache.remove(convertKey((byte[]) key)); + } else { + throw new JedisException("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); + } + } + + protected void setKey(Object key, Object value) { + cache.put(getMapKey(key), value); + } + + protected T getValue(Object key) { + return (T) getMapValue(key); + } + + private Object getMapValue(Object key) { + return cache.get(getMapKey(key)); + } + + private ByteBuffer getMapKey(Object key) { + if (key instanceof byte[]) { + return convertKey((byte[]) key); + } else { + return convertKey(SafeEncoder.encode(String.valueOf(key))); + } + } + + private static ByteBuffer convertKey(byte[] b) { + return ByteBuffer.wrap(b); + } +} diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 50243e20d7..58bc941706 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -34,6 +34,7 @@ public class Connection implements Closeable { private Socket socket; private RedisOutputStream outputStream; private RedisInputStream inputStream; + private ClientSideCache clientSideCache; private int soTimeout = 0; private int infiniteSoTimeout = 0; private boolean broken = false; @@ -121,6 +122,10 @@ public void rollbackTimeout() { } } + final void setClientSideCache(ClientSideCache clientSideCache) { + this.clientSideCache = clientSideCache; + } + public Object executeCommand(final ProtocolCommand cmd) { return executeCommand(new CommandArguments(cmd)); } @@ -347,9 +352,10 @@ protected Object readProtocolWithCheckingBroken() { } try { + Protocol.readPushes(inputStream, clientSideCache); return Protocol.read(inputStream); // Object read = Protocol.read(inputStream); -// System.out.println(SafeEncoder.encodeObject(read)); +// System.out.println("REPLY: " + SafeEncoder.encodeObject(read)); // return read; } catch (JedisConnectionException exc) { broken = true; @@ -370,6 +376,19 @@ public List getMany(final int count) { return responses; } + protected void readPushesWithCheckingBroken() { + if (broken) { + throw new JedisConnectionException("Attempting to read pushes from a broken connection"); + } + + try { + Protocol.readPushes(inputStream, clientSideCache); + } catch (JedisConnectionException exc) { + broken = true; + throw exc; + } + } + /** * Check if the client name libname, libver, characters are legal * @param info the name diff --git a/src/main/java/redis/clients/jedis/JedisClientSideCache.java b/src/main/java/redis/clients/jedis/JedisClientSideCache.java new file mode 100644 index 0000000000..73f2a71124 --- /dev/null +++ b/src/main/java/redis/clients/jedis/JedisClientSideCache.java @@ -0,0 +1,45 @@ +package redis.clients.jedis; + +import redis.clients.jedis.exceptions.JedisException; + +public class JedisClientSideCache extends Jedis { + + private final ClientSideCache cache; + + public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig config) { + this(hostPort, config, new ClientSideCache()); + } + + public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig config, + ClientSideCache cache) { + super(hostPort, config); + if (config.getRedisProtocol() != RedisProtocol.RESP3) { + throw new JedisException("Client side caching is only supported with RESP3."); + } + + this.cache = cache; + this.connection.setClientSideCache(cache); + clientTrackingOn(); + } + + private void clientTrackingOn() { + String reply = connection.executeCommand(new CommandObject<>( + new CommandArguments(Protocol.Command.CLIENT).add("TRACKING").add("ON").add("BCAST"), + BuilderFactory.STRING)); + if (!"OK".equals(reply)) { + throw new JedisException("Could not enable client tracking. Reply: " + reply); + } + } + + @Override + public String get(String key) { + connection.readPushesWithCheckingBroken(); + String cachedValue = cache.getValue(key); + if (cachedValue != null) return cachedValue; + + String value = super.get(key); + if (value != null) cache.setKey(key, value); + return value; + } + +} diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 234b73bda9..0e276c0d93 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -4,8 +4,10 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Objects; import redis.clients.jedis.exceptions.*; import redis.clients.jedis.args.Rawable; @@ -57,6 +59,8 @@ public final class Protocol { private static final String WRONGPASS_PREFIX = "WRONGPASS"; private static final String NOPERM_PREFIX = "NOPERM"; + private static final byte[] INVALIDATE_BYTES = SafeEncoder.encode("invalidate"); + private Protocol() { throw new InstantiationError("Must not instantiate this class"); } @@ -133,7 +137,7 @@ private static String[] parseTargetHostAndSlot(String clusterRedirectResponse) { private static Object process(final RedisInputStream is) { final byte b = is.readByte(); - //System.out.println((char) b); + //System.out.println("BYTE: " + (char) b); switch (b) { case PLUS_BYTE: return is.readLineBytes(); @@ -167,6 +171,15 @@ private static Object process(final RedisInputStream is) { } } + private static void processPush(final RedisInputStream is, ClientSideCache cache) { + List list = processMultiBulkReply(is); + //System.out.println("PUSH: " + SafeEncoder.encodeObject(list)); + if (list.size() == 2 && list.get(0) instanceof byte[] + && Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) { + cache.invalidateKeys((List) list.get(1)); + } + } + private static byte[] processBulkReply(final RedisInputStream is) { final int len = is.readIntCrLf(); if (len == -1) { @@ -193,11 +206,13 @@ private static byte[] processBulkReply(final RedisInputStream is) { private static List processMultiBulkReply(final RedisInputStream is) { // private static List processMultiBulkReply(final int num, final RedisInputStream is) { final int num = is.readIntCrLf(); + //System.out.println("MULTI BULK: " + num); if (num == -1) return null; final List ret = new ArrayList<>(num); for (int i = 0; i < num; i++) { try { ret.add(process(is)); + //System.out.println("MULTI >> " + (i+1) + ": " + SafeEncoder.encodeObject(ret.get(i))); } catch (JedisDataException e) { ret.add(e); } @@ -221,6 +236,16 @@ public static Object read(final RedisInputStream is) { return process(is); } + static void readPushes(final RedisInputStream is, final ClientSideCache cache) { + if (cache != null) { + //System.out.println("PEEK: " + is.peekByte()); + while (Objects.equals(GREATER_THAN_BYTE, is.peekByte())) { + is.readByte(); + processPush(is, cache); + } + } + } + public static final byte[] toByteArray(final boolean value) { return value ? BYTES_TRUE : BYTES_FALSE; } diff --git a/src/main/java/redis/clients/jedis/util/RedisInputStream.java b/src/main/java/redis/clients/jedis/util/RedisInputStream.java index a0dad9d437..094ec762d8 100644 --- a/src/main/java/redis/clients/jedis/util/RedisInputStream.java +++ b/src/main/java/redis/clients/jedis/util/RedisInputStream.java @@ -43,6 +43,11 @@ public RedisInputStream(InputStream in) { this(in, INPUT_BUFFER_SIZE); } + public Byte peekByte() { + ensureFillSafe(); + return buf[count]; + } + public byte readByte() throws JedisConnectionException { ensureFill(); return buf[count++]; @@ -252,4 +257,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 + } + } + } } diff --git a/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java new file mode 100644 index 0000000000..2375fa5153 --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java @@ -0,0 +1,70 @@ +package redis.clients.jedis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +public class JedisClientSideCacheTest { + + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + + protected Jedis jedis; + + @Before + public void setUp() throws Exception { + jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().timeoutMillis(500).password("foobared").build()); + jedis.flushAll(); + } + + @After + public void tearDown() throws Exception { + jedis.close(); + } + + private static final JedisClientConfig configForCache = DefaultJedisClientConfig.builder() + .resp3().socketTimeoutMillis(20).password("foobared").build(); + + @Test + public void simple() { + try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache)) { + jedis.set("foo", "bar"); + assertEquals("bar", jCache.get("foo")); + jedis.del("foo"); + assertNull(jCache.get("foo")); + } + } + + @Test + public void simpleMock() { + ClientSideCache cache = Mockito.mock(ClientSideCache.class); + try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache, cache)) { + jedis.set("foo", "bar"); + assertEquals("bar", jCache.get("foo")); + jedis.del("foo"); + assertNull(jCache.get("foo")); + } + + InOrder inOrder = Mockito.inOrder(cache); + inOrder.verify(cache).invalidateKeys(Mockito.notNull()); + inOrder.verify(cache).getValue("foo"); + inOrder.verify(cache).setKey("foo", "bar"); + inOrder.verify(cache).invalidateKeys(Mockito.notNull()); + inOrder.verify(cache).getValue("foo"); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void flushall() { + try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache)) { + jedis.set("foo", "bar"); + assertEquals("bar", jCache.get("foo")); + jedis.flushAll(); + assertNull(jCache.get("foo")); + } + } +} From 89617c929ad27f7b5ddab79d7c56c172833e0adb Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:59:34 +0600 Subject: [PATCH 02/48] Support for client-side caching - phase 2 (#3673) * Code re-use? * Stop forcing to read push notifications before checking cache and remove BCAST * Rename variable * Remove ensureFillSafe() * Refactor peeking and reading push notifications * Cleanup comments --- .../java/redis/clients/jedis/Connection.java | 19 +----- .../clients/jedis/JedisClientSideCache.java | 3 +- .../java/redis/clients/jedis/Protocol.java | 29 +++++---- .../clients/jedis/util/RedisInputStream.java | 20 +------ .../jedis/JedisClientSideCacheTest.java | 60 +++++++++++++++---- 5 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 58bc941706..a8af741f42 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -352,11 +352,7 @@ protected Object readProtocolWithCheckingBroken() { } try { - Protocol.readPushes(inputStream, clientSideCache); - return Protocol.read(inputStream); -// Object read = Protocol.read(inputStream); -// System.out.println("REPLY: " + SafeEncoder.encodeObject(read)); -// return read; + return Protocol.read(inputStream, clientSideCache); } catch (JedisConnectionException exc) { broken = true; throw exc; @@ -376,19 +372,6 @@ public List getMany(final int count) { return responses; } - protected void readPushesWithCheckingBroken() { - if (broken) { - throw new JedisConnectionException("Attempting to read pushes from a broken connection"); - } - - try { - Protocol.readPushes(inputStream, clientSideCache); - } catch (JedisConnectionException exc) { - broken = true; - throw exc; - } - } - /** * Check if the client name libname, libver, characters are legal * @param info the name diff --git a/src/main/java/redis/clients/jedis/JedisClientSideCache.java b/src/main/java/redis/clients/jedis/JedisClientSideCache.java index 73f2a71124..7128f7a1d5 100644 --- a/src/main/java/redis/clients/jedis/JedisClientSideCache.java +++ b/src/main/java/redis/clients/jedis/JedisClientSideCache.java @@ -24,7 +24,7 @@ public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig private void clientTrackingOn() { String reply = connection.executeCommand(new CommandObject<>( - new CommandArguments(Protocol.Command.CLIENT).add("TRACKING").add("ON").add("BCAST"), + new CommandArguments(Protocol.Command.CLIENT).add("TRACKING").add("ON"), BuilderFactory.STRING)); if (!"OK".equals(reply)) { throw new JedisException("Could not enable client tracking. Reply: " + reply); @@ -33,7 +33,6 @@ private void clientTrackingOn() { @Override public String get(String key) { - connection.readPushesWithCheckingBroken(); String cachedValue = cache.getValue(key); if (cachedValue != null) return cachedValue; diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 0e276c0d93..489a331b3a 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -7,7 +7,6 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.Objects; import redis.clients.jedis.exceptions.*; import redis.clients.jedis.args.Rawable; @@ -171,15 +170,6 @@ private static Object process(final RedisInputStream is) { } } - private static void processPush(final RedisInputStream is, ClientSideCache cache) { - List list = processMultiBulkReply(is); - //System.out.println("PUSH: " + SafeEncoder.encodeObject(list)); - if (list.size() == 2 && list.get(0) instanceof byte[] - && Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) { - cache.invalidateKeys((List) list.get(1)); - } - } - private static byte[] processBulkReply(final RedisInputStream is) { final int len = is.readIntCrLf(); if (len == -1) { @@ -232,20 +222,35 @@ private static List processMapKeyValueReply(final RedisInputStream is) return ret; } + @Deprecated public static Object read(final RedisInputStream is) { return process(is); } - static void readPushes(final RedisInputStream is, final ClientSideCache cache) { + public static Object read(final RedisInputStream is, final ClientSideCache cache) { + readPushes(is, cache); + return process(is); + } + + private static void readPushes(final RedisInputStream is, final ClientSideCache cache) { if (cache != null) { //System.out.println("PEEK: " + is.peekByte()); - while (Objects.equals(GREATER_THAN_BYTE, is.peekByte())) { + while (is.peek(GREATER_THAN_BYTE)) { is.readByte(); processPush(is, cache); } } } + private static void processPush(final RedisInputStream is, ClientSideCache cache) { + List list = processMultiBulkReply(is); + //System.out.println("PUSH: " + SafeEncoder.encodeObject(list)); + if (list.size() == 2 && list.get(0) instanceof byte[] + && Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) { + cache.invalidateKeys((List) list.get(1)); + } + } + public static final byte[] toByteArray(final boolean value) { return value ? BYTES_TRUE : BYTES_FALSE; } diff --git a/src/main/java/redis/clients/jedis/util/RedisInputStream.java b/src/main/java/redis/clients/jedis/util/RedisInputStream.java index 094ec762d8..a0859c6bd4 100644 --- a/src/main/java/redis/clients/jedis/util/RedisInputStream.java +++ b/src/main/java/redis/clients/jedis/util/RedisInputStream.java @@ -43,9 +43,9 @@ public RedisInputStream(InputStream in) { this(in, INPUT_BUFFER_SIZE); } - public Byte peekByte() { - ensureFillSafe(); - return buf[count]; + public boolean peek(byte b) throws JedisConnectionException { + ensureFill(); // in current design, at least one reply is expected. so ensureFillSafe() is not necessary. + return buf[count] == b; } public byte readByte() throws JedisConnectionException { @@ -257,18 +257,4 @@ 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 - } - } - } } diff --git a/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java index 2375fa5153..5b9f8b2319 100644 --- a/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java @@ -1,8 +1,10 @@ package redis.clients.jedis; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,7 +19,7 @@ public class JedisClientSideCacheTest { @Before public void setUp() throws Exception { - jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().timeoutMillis(500).password("foobared").build()); + jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); jedis.flushAll(); } @@ -26,45 +28,83 @@ public void tearDown() throws Exception { jedis.close(); } - private static final JedisClientConfig configForCache = DefaultJedisClientConfig.builder() - .resp3().socketTimeoutMillis(20).password("foobared").build(); + private static final JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().resp3().password("foobared").build(); @Test public void simple() { - try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache)) { + try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig)) { jedis.set("foo", "bar"); assertEquals("bar", jCache.get("foo")); jedis.del("foo"); - assertNull(jCache.get("foo")); + assertThat(jCache.get("foo"), Matchers.oneOf("bar", null)); // ? } } @Test - public void simpleMock() { + public void simpleMoreAndMock() { ClientSideCache cache = Mockito.mock(ClientSideCache.class); - try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache, cache)) { + Mockito.when(cache.getValue("foo")).thenReturn(null, "bar", null); + + try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig, cache)) { jedis.set("foo", "bar"); + assertEquals("bar", jCache.get("foo")); + jedis.del("foo"); + + assertEquals("bar", jCache.get("foo")); + + // there should be an invalid pending; any connection command will make it read + jCache.ping(); + assertNull(jCache.get("foo")); } InOrder inOrder = Mockito.inOrder(cache); - inOrder.verify(cache).invalidateKeys(Mockito.notNull()); inOrder.verify(cache).getValue("foo"); inOrder.verify(cache).setKey("foo", "bar"); + inOrder.verify(cache).getValue("foo"); inOrder.verify(cache).invalidateKeys(Mockito.notNull()); inOrder.verify(cache).getValue("foo"); inOrder.verifyNoMoreInteractions(); } @Test - public void flushall() { - try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, configForCache)) { + public void flushAll() { + try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig)) { + jedis.set("foo", "bar"); + assertEquals("bar", jCache.get("foo")); + jedis.flushAll(); + assertThat(jCache.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void flushAllMoreAndMock() { + ClientSideCache cache = Mockito.mock(ClientSideCache.class); + Mockito.when(cache.getValue("foo")).thenReturn(null, "bar", null); + + try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig, cache)) { jedis.set("foo", "bar"); + assertEquals("bar", jCache.get("foo")); + jedis.flushAll(); + + assertEquals("bar", jCache.get("foo")); + + // there should be an invalid pending; any connection command will make it read + jCache.ping(); + assertNull(jCache.get("foo")); } + + InOrder inOrder = Mockito.inOrder(cache); + inOrder.verify(cache).getValue("foo"); + inOrder.verify(cache).setKey("foo", "bar"); + inOrder.verify(cache).getValue("foo"); + inOrder.verify(cache).invalidateKeys(Mockito.isNull()); + inOrder.verify(cache).getValue("foo"); + inOrder.verifyNoMoreInteractions(); } } From fca975f44520de2a8499f71d8010a19179201efa Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:27:27 +0600 Subject: [PATCH 03/48] Fix transaction failure tests using mock (#3683) Now we have to mock Protocol#read(RedisInputStream, ClientSideCache) instead of Protocol#read(RedisInputStream). --- .../jedis/commands/jedis/TransactionCommandsTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java index a2153e6862..c597b73302 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java @@ -2,7 +2,6 @@ import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static redis.clients.jedis.Protocol.Command.INCR; import static redis.clients.jedis.Protocol.Command.GET; import static redis.clients.jedis.Protocol.Command.SET; @@ -15,6 +14,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentMatchers; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -159,7 +159,9 @@ public void discardFail() { trans.set("b", "b"); try (MockedStatic protocol = Mockito.mockStatic(Protocol.class)) { - protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class); + //protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class); + protocol.when(() -> Protocol.read(ArgumentMatchers.any(), ArgumentMatchers.isNull())) + .thenThrow(JedisConnectionException.class); trans.discard(); fail("Should get mocked JedisConnectionException."); @@ -179,7 +181,9 @@ public void execFail() { trans.set("b", "b"); try (MockedStatic protocol = Mockito.mockStatic(Protocol.class)) { - protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class); + //protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class); + protocol.when(() -> Protocol.read(ArgumentMatchers.any(), ArgumentMatchers.isNull())) + .thenThrow(JedisConnectionException.class); trans.exec(); fail("Should get mocked JedisConnectionException."); From 3ab6bdc3857bbc5068bccb93c2e3cd7fbfb131f5 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:46:17 +0600 Subject: [PATCH 04/48] Support client-side caching from UnifiedJedis (#3691) * Support client side caching from UnifiedJedis * Support client side caching as a separate parameter * format imports * Support CSC in sentinel mode * undo change --- .../redis/clients/jedis/ClientSideCache.java | 21 +++- .../java/redis/clients/jedis/Connection.java | 35 ++++-- .../clients/jedis/ConnectionFactory.java | 18 ++- .../redis/clients/jedis/ConnectionPool.java | 9 ++ .../clients/jedis/JedisClientSideCache.java | 44 ------- .../redis/clients/jedis/JedisCluster.java | 54 +++++++-- .../clients/jedis/JedisClusterInfoCache.java | 43 ++++++- .../redis/clients/jedis/JedisFactory.java | 4 +- .../java/redis/clients/jedis/JedisPooled.java | 11 +- .../redis/clients/jedis/JedisSentineled.java | 13 +++ .../redis/clients/jedis/UnifiedJedis.java | 40 ++++++- .../providers/ClusterConnectionProvider.java | 18 +++ .../providers/PooledConnectionProvider.java | 14 ++- .../SentineledConnectionProvider.java | 48 +++++++- .../jedis/JedisClientSideCacheTest.java | 110 ------------------ .../JedisClusterClientSideCacheTest.java | 89 ++++++++++++++ .../redis/clients/jedis/JedisClusterTest.java | 32 ----- .../clients/jedis/JedisClusterTestBase.java | 10 +- .../jedis/JedisPooledClientSideCacheTest.java | 100 ++++++++++++++++ .../JedisSentineledClientSideCacheTest.java | 95 +++++++++++++++ .../SentineledConnectionProviderTest.java | 3 - 21 files changed, 572 insertions(+), 239 deletions(-) delete mode 100644 src/main/java/redis/clients/jedis/JedisClientSideCache.java delete mode 100644 src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java create mode 100644 src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java create mode 100644 src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java create mode 100644 src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java diff --git a/src/main/java/redis/clients/jedis/ClientSideCache.java b/src/main/java/redis/clients/jedis/ClientSideCache.java index 5dd31b17e9..62c5be28c2 100644 --- a/src/main/java/redis/clients/jedis/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/ClientSideCache.java @@ -10,14 +10,27 @@ public class ClientSideCache { - private final Map cache = new HashMap<>(); + private final Map cache; - protected ClientSideCache() { + public ClientSideCache() { + this.cache = new HashMap<>(); } - protected void invalidateKeys(List list) { + /** + * For testing purpose only. + * @param map + */ + ClientSideCache(Map map) { + this.cache = map; + } + + public final void clear() { + cache.clear(); + } + + public final void invalidateKeys(List list) { if (list == null) { - cache.clear(); + clear(); return; } diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index a8af741f42..bff5898f8d 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -52,9 +52,7 @@ public Connection(final HostAndPort hostAndPort) { } public Connection(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) { - this(new DefaultJedisSocketFactory(hostAndPort, clientConfig)); - this.infiniteSoTimeout = clientConfig.getBlockingSocketTimeoutMillis(); - initializeFromClientConfig(clientConfig); + this(new DefaultJedisSocketFactory(hostAndPort, clientConfig), clientConfig); } public Connection(final JedisSocketFactory socketFactory) { @@ -65,7 +63,15 @@ public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clie this.socketFactory = socketFactory; this.soTimeout = clientConfig.getSocketTimeoutMillis(); this.infiniteSoTimeout = clientConfig.getBlockingSocketTimeoutMillis(); - initializeFromClientConfig(clientConfig); + initializeConnection(clientConfig); + } + + public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, ClientSideCache csCache) { + this.socketFactory = socketFactory; + this.soTimeout = clientConfig.getSocketTimeoutMillis(); + this.infiniteSoTimeout = clientConfig.getBlockingSocketTimeoutMillis(); + initializeConnection(clientConfig); + initializeClientSideCache(csCache); } @Override @@ -122,10 +128,6 @@ public void rollbackTimeout() { } } - final void setClientSideCache(ClientSideCache clientSideCache) { - this.clientSideCache = clientSideCache; - } - public Object executeCommand(final ProtocolCommand cmd) { return executeCommand(new CommandArguments(cmd)); } @@ -389,7 +391,7 @@ private static boolean validateClientInfo(String info) { return true; } - private void initializeFromClientConfig(final JedisClientConfig config) { + private void initializeConnection(final JedisClientConfig config) { try { connect(); @@ -516,4 +518,19 @@ public boolean ping() { } return true; } + + private void initializeClientSideCache(ClientSideCache csCache) { + this.clientSideCache = csCache; + if (clientSideCache != null) { + if (protocol != RedisProtocol.RESP3) { + throw new JedisException("Client side caching is only supported with RESP3."); + } + + sendCommand(Protocol.Command.CLIENT, "TRACKING", "ON"); + String reply = getStatusCodeReply(); + if (!"OK".equals(reply)) { + throw new JedisException("Could not enable client tracking. Reply: " + reply); + } + } + } } diff --git a/src/main/java/redis/clients/jedis/ConnectionFactory.java b/src/main/java/redis/clients/jedis/ConnectionFactory.java index d286462347..5b43606205 100644 --- a/src/main/java/redis/clients/jedis/ConnectionFactory.java +++ b/src/main/java/redis/clients/jedis/ConnectionFactory.java @@ -17,8 +17,8 @@ public class ConnectionFactory implements PooledObjectFactory { private static final Logger logger = LoggerFactory.getLogger(ConnectionFactory.class); private final JedisSocketFactory jedisSocketFactory; - private final JedisClientConfig clientConfig; + private ClientSideCache clientSideCache = null; public ConnectionFactory(final HostAndPort hostAndPort) { this.clientConfig = DefaultJedisClientConfig.builder().build(); @@ -26,12 +26,18 @@ public ConnectionFactory(final HostAndPort hostAndPort) { } public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) { - this.clientConfig = DefaultJedisClientConfig.copyConfig(clientConfig); + this.clientConfig = clientConfig; this.jedisSocketFactory = new DefaultJedisSocketFactory(hostAndPort, this.clientConfig); } + public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache) { + this.clientConfig = clientConfig; + this.jedisSocketFactory = new DefaultJedisSocketFactory(hostAndPort, this.clientConfig); + this.clientSideCache = csCache; + } + public ConnectionFactory(final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) { - this.clientConfig = DefaultJedisClientConfig.copyConfig(clientConfig); + this.clientConfig = clientConfig; this.jedisSocketFactory = jedisSocketFactory; } @@ -54,9 +60,11 @@ public void destroyObject(PooledObject pooledConnection) throws Exce @Override public PooledObject makeObject() throws Exception { - Connection jedis = null; try { - jedis = new Connection(jedisSocketFactory, clientConfig); + Connection jedis = clientSideCache == null + ? new Connection(jedisSocketFactory, clientConfig) + : new Connection(jedisSocketFactory, clientConfig, clientSideCache); + return new DefaultPooledObject<>(jedis); } catch (JedisException je) { logger.debug("Error while makeObject", je); diff --git a/src/main/java/redis/clients/jedis/ConnectionPool.java b/src/main/java/redis/clients/jedis/ConnectionPool.java index 5899b22260..70202deeae 100644 --- a/src/main/java/redis/clients/jedis/ConnectionPool.java +++ b/src/main/java/redis/clients/jedis/ConnectionPool.java @@ -10,6 +10,10 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig) { this(new ConnectionFactory(hostAndPort, clientConfig)); } + public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache) { + this(new ConnectionFactory(hostAndPort, clientConfig, csCache)); + } + public ConnectionPool(PooledObjectFactory factory) { super(factory); } @@ -19,6 +23,11 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, this(new ConnectionFactory(hostAndPort, clientConfig), poolConfig); } + public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache, + GenericObjectPoolConfig poolConfig) { + this(new ConnectionFactory(hostAndPort, clientConfig, csCache), poolConfig); + } + public ConnectionPool(PooledObjectFactory factory, GenericObjectPoolConfig poolConfig) { super(factory, poolConfig); diff --git a/src/main/java/redis/clients/jedis/JedisClientSideCache.java b/src/main/java/redis/clients/jedis/JedisClientSideCache.java deleted file mode 100644 index 7128f7a1d5..0000000000 --- a/src/main/java/redis/clients/jedis/JedisClientSideCache.java +++ /dev/null @@ -1,44 +0,0 @@ -package redis.clients.jedis; - -import redis.clients.jedis.exceptions.JedisException; - -public class JedisClientSideCache extends Jedis { - - private final ClientSideCache cache; - - public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig config) { - this(hostPort, config, new ClientSideCache()); - } - - public JedisClientSideCache(final HostAndPort hostPort, final JedisClientConfig config, - ClientSideCache cache) { - super(hostPort, config); - if (config.getRedisProtocol() != RedisProtocol.RESP3) { - throw new JedisException("Client side caching is only supported with RESP3."); - } - - this.cache = cache; - this.connection.setClientSideCache(cache); - clientTrackingOn(); - } - - private void clientTrackingOn() { - String reply = connection.executeCommand(new CommandObject<>( - new CommandArguments(Protocol.Command.CLIENT).add("TRACKING").add("ON"), - BuilderFactory.STRING)); - if (!"OK".equals(reply)) { - throw new JedisException("Could not enable client tracking. Reply: " + reply); - } - } - - @Override - public String get(String key) { - String cachedValue = cache.getValue(key); - if (cachedValue != null) return cachedValue; - - String value = super.get(key); - if (value != null) cache.setKey(key, value); - return value; - } - -} diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index 55495e6513..0138058d66 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -6,7 +6,6 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; - import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.util.JedisClusterCRC16; @@ -198,6 +197,12 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi Duration.ofMillis((long) clientConfig.getSocketTimeoutMillis() * maxAttempts), poolConfig); } + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, int maxAttempts, + Duration maxTotalRetriesDuration, GenericObjectPoolConfig poolConfig) { + this(new ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig), maxAttempts, maxTotalRetriesDuration, + clientConfig.getRedisProtocol()); + } + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod, int maxAttempts, Duration maxTotalRetriesDuration) { @@ -205,21 +210,50 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol()); } - public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, int maxAttempts, - Duration maxTotalRetriesDuration, GenericObjectPoolConfig poolConfig) { - this(new ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig), maxAttempts, maxTotalRetriesDuration, - clientConfig.getRedisProtocol()); + private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, + RedisProtocol protocol) { + super(provider, maxAttempts, maxTotalRetriesDuration, protocol); } - // Uses a fetched connection to process protocol. Should be avoided if possible. - public JedisCluster(ClusterConnectionProvider provider, int maxAttempts, + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + this(clusterNodes, clientConfig, clientSideCache, DEFAULT_MAX_ATTEMPTS, + Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis())); + } + + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + int maxAttempts, Duration maxTotalRetriesDuration) { + this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache), maxAttempts, maxTotalRetriesDuration, + clientConfig.getRedisProtocol(), clientSideCache); + } + + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + int maxAttempts, Duration maxTotalRetriesDuration, GenericObjectPoolConfig poolConfig) { + this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig), + maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache); + } + + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + GenericObjectPoolConfig poolConfig) { + this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig), + DEFAULT_MAX_ATTEMPTS, Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis()), + clientConfig.getRedisProtocol(), clientSideCache); + } + + public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, + GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod, int maxAttempts, Duration maxTotalRetriesDuration) { - super(provider, maxAttempts, maxTotalRetriesDuration); + this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig, topologyRefreshPeriod), + maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache); } private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, - RedisProtocol protocol) { - super(provider, maxAttempts, maxTotalRetriesDuration, protocol); + RedisProtocol protocol, ClientSideCache clientSideCache) { + super(provider, maxAttempts, maxTotalRetriesDuration, protocol, clientSideCache); + } + + // Uses a fetched connection to process protocol. Should be avoided if possible. + public JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) { + super(provider, maxAttempts, maxTotalRetriesDuration); } public Map getClusterNodes() { diff --git a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java index bea4982fd4..5646dbfb59 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java +++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java @@ -42,6 +42,7 @@ public class JedisClusterInfoCache { private final GenericObjectPoolConfig poolConfig; private final JedisClientConfig clientConfig; + private final ClientSideCache clientSideCache; private final Set startNodes; private static final int MASTER_NODE_INDEX = 2; @@ -61,19 +62,35 @@ public void run() { } public JedisClusterInfoCache(final JedisClientConfig clientConfig, final Set startNodes) { - this(clientConfig, null, startNodes); + this(clientConfig, null, null, startNodes); + } + + public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache csCache, final Set startNodes) { + this(clientConfig, csCache, null, startNodes); } public JedisClusterInfoCache(final JedisClientConfig clientConfig, final GenericObjectPoolConfig poolConfig, final Set startNodes) { - this(clientConfig, poolConfig, startNodes, null); + this(clientConfig, null, poolConfig, startNodes); + } + + public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache csCache, + final GenericObjectPoolConfig poolConfig, final Set startNodes) { + this(clientConfig, csCache, poolConfig, startNodes, null); } public JedisClusterInfoCache(final JedisClientConfig clientConfig, final GenericObjectPoolConfig poolConfig, final Set startNodes, final Duration topologyRefreshPeriod) { + this(clientConfig, null, poolConfig, startNodes, topologyRefreshPeriod); + } + + public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache csCache, + final GenericObjectPoolConfig poolConfig, final Set startNodes, + final Duration topologyRefreshPeriod) { this.poolConfig = poolConfig; this.clientConfig = clientConfig; + this.clientSideCache = csCache; this.startNodes = startNodes; if (topologyRefreshPeriod != null) { logger.info("Cluster topology refresh start, period: {}, startNodes: {}", topologyRefreshPeriod, startNodes); @@ -209,6 +226,9 @@ private void discoverClusterSlots(Connection jedis) { try { Arrays.fill(slots, null); Arrays.fill(slotNodes, null); + if (clientSideCache != null) { + clientSideCache.clear(); + } Set hostAndPortKeys = new HashSet<>(); for (Object slotInfoObj : slotsInfo) { @@ -270,8 +290,7 @@ public ConnectionPool setupNodeIfNotExist(final HostAndPort node) { ConnectionPool existingPool = nodes.get(nodeKey); if (existingPool != null) return existingPool; - ConnectionPool nodePool = poolConfig == null ? new ConnectionPool(node, clientConfig) - : new ConnectionPool(node, clientConfig, poolConfig); + ConnectionPool nodePool = createNodePool(node); nodes.put(nodeKey, nodePool); return nodePool; } finally { @@ -279,6 +298,22 @@ public ConnectionPool setupNodeIfNotExist(final HostAndPort node) { } } + private ConnectionPool createNodePool(HostAndPort node) { + if (poolConfig == null) { + if (clientSideCache == null) { + return new ConnectionPool(node, clientConfig); + } else { + return new ConnectionPool(node, clientConfig, clientSideCache); + } + } else { + if (clientSideCache == null) { + return new ConnectionPool(node, clientConfig, poolConfig); + } else { + return new ConnectionPool(node, clientConfig, clientSideCache, poolConfig); + } + } + } + public void assignSlotToNode(int slot, HostAndPort targetNode) { w.lock(); try { diff --git a/src/main/java/redis/clients/jedis/JedisFactory.java b/src/main/java/redis/clients/jedis/JedisFactory.java index b84e2b05ae..820a066a0d 100644 --- a/src/main/java/redis/clients/jedis/JedisFactory.java +++ b/src/main/java/redis/clients/jedis/JedisFactory.java @@ -66,7 +66,7 @@ protected JedisFactory(final String host, final int port, final int connectionTi } protected JedisFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) { - this.clientConfig = DefaultJedisClientConfig.copyConfig(clientConfig); + this.clientConfig = clientConfig; this.jedisSocketFactory = new DefaultJedisSocketFactory(hostAndPort, this.clientConfig); } @@ -83,7 +83,7 @@ protected JedisFactory(final String host, final int port, final int connectionTi } protected JedisFactory(final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) { - this.clientConfig = DefaultJedisClientConfig.copyConfig(clientConfig); + this.clientConfig = clientConfig; this.jedisSocketFactory = jedisSocketFactory; } diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index c6d022e094..44e476d7b8 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -7,7 +7,6 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; - import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; @@ -76,6 +75,10 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client super(hostAndPort, clientConfig); } + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache) { + super(new PooledConnectionProvider(hostAndPort, clientConfig, csCache), clientConfig.getRedisProtocol(), csCache); + } + public JedisPooled(PooledObjectFactory factory) { this(new PooledConnectionProvider(factory)); } @@ -376,6 +379,12 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client super(new PooledConnectionProvider(hostAndPort, clientConfig, poolConfig), clientConfig.getRedisProtocol()); } + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache, + final GenericObjectPoolConfig poolConfig) { + super(new PooledConnectionProvider(hostAndPort, clientConfig, csCache, poolConfig), + clientConfig.getRedisProtocol(), csCache); + } + public JedisPooled(final GenericObjectPoolConfig poolConfig, final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) { super(new PooledConnectionProvider(new ConnectionFactory(jedisSocketFactory, clientConfig), poolConfig), diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index 0ea0221c1a..f1cb8ea650 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -12,6 +12,12 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo masterClientConfig.getRedisProtocol()); } + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, + Set sentinels, final JedisClientConfig sentinelClientConfig) { + super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, + sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache); + } + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { @@ -19,6 +25,13 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo masterClientConfig.getRedisProtocol()); } + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, + final GenericObjectPoolConfig poolConfig, + Set sentinels, final JedisClientConfig sentinelClientConfig) { + super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, poolConfig, + sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache); + } + public JedisSentineled(SentineledConnectionProvider sentineledConnectionProvider) { super(sentineledConnectionProvider); } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index b628be6933..ba7b36b134 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -48,7 +48,8 @@ public class UnifiedJedis implements JedisCommands, JedisBinaryCommands, SampleKeyedCommands, SampleBinaryKeyedCommands, RedisModuleCommands, AutoCloseable { - protected RedisProtocol protocol = null; + @Deprecated protected RedisProtocol protocol = null; + private final ClientSideCache clientSideCache; protected final ConnectionProvider provider; protected final CommandExecutor executor; protected final CommandObjects commandObjects; @@ -99,6 +100,10 @@ protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol) { this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol); } + protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, ClientSideCache clientSideCache) { + this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol, clientSideCache); + } + /** * The constructor to directly use a custom {@link JedisSocketFactory}. *

@@ -132,6 +137,7 @@ public UnifiedJedis(Connection connection) { RedisProtocol proto = connection.getRedisProtocol(); if (proto != null) this.commandObjects.setProtocol(proto); this.graphCommandObjects = new GraphCommandObjects(this); + this.clientSideCache = null; // TODO: } @Deprecated @@ -165,6 +171,12 @@ protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Dura new ClusterCommandObjects(), protocol); } + protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, + RedisProtocol protocol, ClientSideCache clientSideCache) { + this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, + new ClusterCommandObjects(), protocol, clientSideCache); + } + /** * @deprecated Sharding/Sharded feature will be removed in next major release. */ @@ -212,7 +224,7 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider) { // Uses a fetched connection to process protocol. Should be avoided if possible. private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects) { - this(executor, provider, commandObjects, null); + this(executor, provider, commandObjects, null, null); if (this.provider != null) { try (Connection conn = this.provider.getConnection()) { if (conn != null) { @@ -225,16 +237,26 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comm private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, RedisProtocol protocol) { + this(executor, provider, commandObjects, protocol, (ClientSideCache) null); + } + + private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, + RedisProtocol protocol, ClientSideCache clientSideCache) { + + if (clientSideCache != null && protocol != RedisProtocol.RESP3) { + throw new IllegalArgumentException("Client-side caching is only supported with RESP3."); + } + this.provider = provider; 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 @@ -727,6 +749,14 @@ public String set(String key, String value, SetParams params) { @Override public String get(String key) { + if (clientSideCache != null) { + String cachedValue = clientSideCache.getValue(key); + if (cachedValue != null) return cachedValue; + + String value = executeCommand(commandObjects.get(key)); + if (value != null) clientSideCache.setKey(key, value); + return value; + } return executeCommand(commandObjects.get(key)); } diff --git a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java index c21640713d..be83aa7388 100644 --- a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java @@ -8,6 +8,7 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.ClientSideCache; import redis.clients.jedis.ClusterCommandArguments; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.HostAndPort; @@ -29,18 +30,35 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi initializeSlotsCache(clusterNodes, clientConfig); } + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache csCache) { + this.cache = new JedisClusterInfoCache(clientConfig, csCache, clusterNodes); + initializeSlotsCache(clusterNodes, clientConfig); + } + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, GenericObjectPoolConfig poolConfig) { this.cache = new JedisClusterInfoCache(clientConfig, poolConfig, clusterNodes); initializeSlotsCache(clusterNodes, clientConfig); } + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache csCache, + GenericObjectPoolConfig poolConfig) { + this.cache = new JedisClusterInfoCache(clientConfig, csCache, poolConfig, clusterNodes); + initializeSlotsCache(clusterNodes, clientConfig); + } + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod) { this.cache = new JedisClusterInfoCache(clientConfig, poolConfig, clusterNodes, topologyRefreshPeriod); initializeSlotsCache(clusterNodes, clientConfig); } + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache csCache, + GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod) { + this.cache = new JedisClusterInfoCache(clientConfig, csCache, poolConfig, clusterNodes, topologyRefreshPeriod); + initializeSlotsCache(clusterNodes, clientConfig); + } + private void initializeSlotsCache(Set startNodes, JedisClientConfig clientConfig) { if (startNodes.isEmpty()) { throw new JedisClusterOperationException("No nodes to initialize cluster slots cache."); diff --git a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java index f7b90e2953..85fa3cecd2 100644 --- a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java @@ -5,6 +5,7 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.ClientSideCache; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionFactory; @@ -28,9 +29,20 @@ public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clien this.connectionMapKey = hostAndPort; } + public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache) { + this(new ConnectionPool(hostAndPort, clientConfig, csCache)); + this.connectionMapKey = hostAndPort; + } + public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, GenericObjectPoolConfig poolConfig) { - this(new ConnectionFactory(hostAndPort, clientConfig), poolConfig); + this(new ConnectionPool(hostAndPort, clientConfig, poolConfig)); + this.connectionMapKey = hostAndPort; + } + + public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache, + GenericObjectPoolConfig poolConfig) { + this(new ConnectionPool(hostAndPort, clientConfig, csCache, 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 5058f07179..e335803b62 100644 --- a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import redis.clients.jedis.ClientSideCache; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPool; @@ -35,6 +36,8 @@ public class SentineledConnectionProvider implements ConnectionProvider { private final JedisClientConfig masterClientConfig; + private final ClientSideCache clientSideCache; + private final GenericObjectPoolConfig masterPoolConfig; protected final Collection sentinelListeners = new ArrayList<>(); @@ -47,7 +50,12 @@ public class SentineledConnectionProvider implements ConnectionProvider { public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { - this(masterName, masterClientConfig, /*poolConfig*/ null, sentinels, sentinelClientConfig); + this(masterName, masterClientConfig, null, null, sentinels, sentinelClientConfig); + } + + public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, + ClientSideCache clientSideCache, Set sentinels, final JedisClientConfig sentinelClientConfig) { + this(masterName, masterClientConfig, clientSideCache, null, sentinels, sentinelClientConfig); } public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, @@ -57,13 +65,28 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS); } + public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, + ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, + Set sentinels, final JedisClientConfig sentinelClientConfig) { + this(masterName, masterClientConfig, clientSideCache, poolConfig, sentinels, sentinelClientConfig, + DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS); + } + public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig, final long subscribeRetryWaitTimeMillis) { + this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig, subscribeRetryWaitTimeMillis); + } + + public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, + ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, + Set sentinels, final JedisClientConfig sentinelClientConfig, + final long subscribeRetryWaitTimeMillis) { this.masterName = masterName; this.masterClientConfig = masterClientConfig; + this.clientSideCache = clientSideCache; this.masterPoolConfig = poolConfig; this.sentinelClientConfig = sentinelClientConfig; @@ -99,13 +122,14 @@ private void initMaster(HostAndPort master) { if (!master.equals(currentMaster)) { currentMaster = master; - ConnectionPool newPool = masterPoolConfig != null - ? new ConnectionPool(currentMaster, masterClientConfig, masterPoolConfig) - : new ConnectionPool(currentMaster, masterClientConfig); + ConnectionPool newPool = createNodePool(currentMaster); ConnectionPool existingPool = pool; pool = newPool; LOG.info("Created connection pool to master at {}.", master); + if (clientSideCache != null) { + clientSideCache.clear(); + } if (existingPool != null) { // although we clear the pool, we still have to check the returned object in getResource, @@ -117,6 +141,22 @@ private void initMaster(HostAndPort master) { } } + private ConnectionPool createNodePool(HostAndPort master) { + if (masterPoolConfig == null) { + if (clientSideCache == null) { + return new ConnectionPool(master, masterClientConfig); + } else { + return new ConnectionPool(master, masterClientConfig, clientSideCache); + } + } else { + if (clientSideCache == null) { + return new ConnectionPool(master, masterClientConfig, masterPoolConfig); + } else { + return new ConnectionPool(master, masterClientConfig, clientSideCache, masterPoolConfig); + } + } + } + private HostAndPort initSentinels(Set sentinels) { HostAndPort master = null; diff --git a/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java deleted file mode 100644 index 5b9f8b2319..0000000000 --- a/src/test/java/redis/clients/jedis/JedisClientSideCacheTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package redis.clients.jedis; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; - -public class JedisClientSideCacheTest { - - protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); - - protected Jedis jedis; - - @Before - public void setUp() throws Exception { - jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); - jedis.flushAll(); - } - - @After - public void tearDown() throws Exception { - jedis.close(); - } - - private static final JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().resp3().password("foobared").build(); - - @Test - public void simple() { - try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig)) { - jedis.set("foo", "bar"); - assertEquals("bar", jCache.get("foo")); - jedis.del("foo"); - assertThat(jCache.get("foo"), Matchers.oneOf("bar", null)); // ? - } - } - - @Test - public void simpleMoreAndMock() { - ClientSideCache cache = Mockito.mock(ClientSideCache.class); - Mockito.when(cache.getValue("foo")).thenReturn(null, "bar", null); - - try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig, cache)) { - jedis.set("foo", "bar"); - - assertEquals("bar", jCache.get("foo")); - - jedis.del("foo"); - - assertEquals("bar", jCache.get("foo")); - - // there should be an invalid pending; any connection command will make it read - jCache.ping(); - - assertNull(jCache.get("foo")); - } - - InOrder inOrder = Mockito.inOrder(cache); - inOrder.verify(cache).getValue("foo"); - inOrder.verify(cache).setKey("foo", "bar"); - inOrder.verify(cache).getValue("foo"); - inOrder.verify(cache).invalidateKeys(Mockito.notNull()); - inOrder.verify(cache).getValue("foo"); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void flushAll() { - try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig)) { - jedis.set("foo", "bar"); - assertEquals("bar", jCache.get("foo")); - jedis.flushAll(); - assertThat(jCache.get("foo"), Matchers.oneOf("bar", null)); // ? - } - } - - @Test - public void flushAllMoreAndMock() { - ClientSideCache cache = Mockito.mock(ClientSideCache.class); - Mockito.when(cache.getValue("foo")).thenReturn(null, "bar", null); - - try (JedisClientSideCache jCache = new JedisClientSideCache(hnp, clientConfig, cache)) { - jedis.set("foo", "bar"); - - assertEquals("bar", jCache.get("foo")); - - jedis.flushAll(); - - assertEquals("bar", jCache.get("foo")); - - // there should be an invalid pending; any connection command will make it read - jCache.ping(); - - assertNull(jCache.get("foo")); - } - - InOrder inOrder = Mockito.inOrder(cache); - inOrder.verify(cache).getValue("foo"); - inOrder.verify(cache).setKey("foo", "bar"); - inOrder.verify(cache).getValue("foo"); - inOrder.verify(cache).invalidateKeys(Mockito.isNull()); - inOrder.verify(cache).getValue("foo"); - inOrder.verifyNoMoreInteractions(); - } -} diff --git a/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java new file mode 100644 index 0000000000..3c8bc18c5c --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java @@ -0,0 +1,89 @@ +package redis.clients.jedis; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.nio.ByteBuffer; +import java.util.Arrays; +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.Test; + +public class JedisClusterClientSideCacheTest extends JedisClusterTestBase { + + private static final Set hnp = Arrays.asList(nodeInfo1, nodeInfo2, nodeInfo3).stream().collect(Collectors.toSet()); + + private static final Supplier clientConfig + = () -> DefaultJedisClientConfig.builder().resp3().password("cluster").build(); + + private static final Supplier> singleConnectionPoolConfig + = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + @Test + public void simple() { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache())) { + jedis.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + jedis.del("foo"); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void simpleWithSimpleMap() { + HashMap map = new HashMap<>(); + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + jedis.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.del("foo"); + assertThat(map, Matchers.aMapWithSize(1)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.ping(); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } + + @Test + public void flushAll() { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache())) { + jedis.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + jedis.flushAll(); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void flushAllWithSimpleMap() { + HashMap map = new HashMap<>(); + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + jedis.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.flushAll(); + assertThat(map, Matchers.aMapWithSize(1)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.ping(); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } +} diff --git a/src/test/java/redis/clients/jedis/JedisClusterTest.java b/src/test/java/redis/clients/jedis/JedisClusterTest.java index 8297eb90c6..f12175728c 100644 --- a/src/test/java/redis/clients/jedis/JedisClusterTest.java +++ b/src/test/java/redis/clients/jedis/JedisClusterTest.java @@ -108,16 +108,6 @@ public void testSetClientName() { try (JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_REDIRECTIONS, "cluster", clientName, DEFAULT_POOL_CONFIG)) { -// Map clusterNodes = jc.getClusterNodes(); -// Collection values = clusterNodes.values(); -// for (JedisPool jedisPool : values) { -// Jedis jedis = jedisPool.getResource(); -// try { -// assertEquals(clientName, jedis.clientGetname()); -// } finally { -// jedis.close(); -// } -// } for (Pool pool : jc.getClusterNodes().values()) { try (Jedis jedis = new Jedis(pool.getResource())) { assertEquals(clientName, jedis.clientGetname()); @@ -133,11 +123,6 @@ public void testSetClientNameWithConfig() { try (JedisCluster jc = new JedisCluster(Collections.singleton(hp), DefaultJedisClientConfig.builder().password("cluster").clientName(clientName).build(), DEFAULT_REDIRECTIONS, DEFAULT_POOL_CONFIG)) { -// jc.getClusterNodes().values().forEach(jedisPool -> { -// try (Jedis jedis = jedisPool.getResource()) { -// assertEquals(clientName, jedis.clientGetname()); -// } -// }); jc.getClusterNodes().values().forEach(pool -> { try (Jedis jedis = new Jedis(pool.getResource())) { assertEquals(clientName, jedis.clientGetname()); @@ -486,7 +471,6 @@ public void testStableSlotWhenMigratingNodeOrImportingNodeIsNotSpecified() } } -// @Test(expected = JedisExhaustedPoolException.class) @Test(expected = JedisException.class) public void testIfPoolConfigAppliesToClusterPools() { GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); @@ -533,12 +517,6 @@ public void testJedisClusterTimeout() { try (JedisCluster jc = new JedisCluster(jedisClusterNode, 4000, 4000, DEFAULT_REDIRECTIONS, "cluster", DEFAULT_POOL_CONFIG)) { -// for (JedisPool pool : jc.getClusterNodes().values()) { -// Jedis jedis = pool.getResource(); -// assertEquals(4000, jedis.getClient().getConnectionTimeout()); -// assertEquals(4000, jedis.getClient().getSoTimeout()); -// jedis.close(); -// } for (Pool pool : jc.getClusterNodes().values()) { try (Connection conn = pool.getResource()) { assertEquals(4000, conn.getSoTimeout()); @@ -555,10 +533,6 @@ public void testJedisClusterTimeoutWithConfig() { DEFAULT_REDIRECTIONS, DEFAULT_POOL_CONFIG)) { jc.getClusterNodes().values().forEach(pool -> { -// try (Jedis jedis = pool.getResource()) { -// assertEquals(4000, jedis.getClient().getConnectionTimeout()); -// assertEquals(4000, jedis.getClient().getSoTimeout()); -// } try (Connection conn = pool.getResource()) { assertEquals(4000, conn.getSoTimeout()); } @@ -606,10 +580,6 @@ public void testReturnConnectionOnJedisConnectionException() throws InterruptedE try (JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_REDIRECTIONS, "cluster", config)) { -// try (Jedis j = jc.getClusterNodes().get("127.0.0.1:7380").getResource()) { -// ClientKillerUtil.tagClient(j, "DEAD"); -// ClientKillerUtil.killClient(j, "DEAD"); -// } try (Connection c = jc.getClusterNodes().get("127.0.0.1:7380").getResource()) { Jedis j = new Jedis(c); ClientKillerUtil.tagClient(j, "DEAD"); @@ -647,7 +617,6 @@ public void testLocalhostNodeNotAddedWhen127Present() { try (JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_REDIRECTIONS, "cluster", config)) { -// Map clusterNodes = jc.getClusterNodes(); Map clusterNodes = jc.getClusterNodes(); assertEquals(3, clusterNodes.size()); assertFalse(clusterNodes.containsKey(JedisClusterInfoCache.getNodeKey(localhost))); @@ -664,7 +633,6 @@ public void testInvalidStartNodeNotAdded() { config.setMaxTotal(1); try (JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_REDIRECTIONS, "cluster", config)) { -// Map clusterNodes = jc.getClusterNodes(); Map clusterNodes = jc.getClusterNodes(); assertEquals(3, clusterNodes.size()); assertFalse(clusterNodes.containsKey(JedisClusterInfoCache.getNodeKey(invalidHost))); diff --git a/src/test/java/redis/clients/jedis/JedisClusterTestBase.java b/src/test/java/redis/clients/jedis/JedisClusterTestBase.java index 0746c2d37c..bb6656a812 100644 --- a/src/test/java/redis/clients/jedis/JedisClusterTestBase.java +++ b/src/test/java/redis/clients/jedis/JedisClusterTestBase.java @@ -15,11 +15,11 @@ public abstract class JedisClusterTestBase { protected static Jedis node4; protected static Jedis nodeSlave2; - protected HostAndPort nodeInfo1 = HostAndPorts.getClusterServers().get(0); - protected HostAndPort nodeInfo2 = HostAndPorts.getClusterServers().get(1); - protected HostAndPort nodeInfo3 = HostAndPorts.getClusterServers().get(2); - protected HostAndPort nodeInfo4 = HostAndPorts.getClusterServers().get(3); - protected HostAndPort nodeInfoSlave2 = HostAndPorts.getClusterServers().get(4); + protected static HostAndPort nodeInfo1 = HostAndPorts.getClusterServers().get(0); + protected static HostAndPort nodeInfo2 = HostAndPorts.getClusterServers().get(1); + protected static HostAndPort nodeInfo3 = HostAndPorts.getClusterServers().get(2); + protected static HostAndPort nodeInfo4 = HostAndPorts.getClusterServers().get(3); + protected static HostAndPort nodeInfoSlave2 = HostAndPorts.getClusterServers().get(4); protected static final String LOCAL_IP = "127.0.0.1"; diff --git a/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java new file mode 100644 index 0000000000..ad4313a4b7 --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java @@ -0,0 +1,100 @@ +package redis.clients.jedis; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.function.Supplier; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class JedisPooledClientSideCacheTest { + + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + + protected Jedis control; + + @Before + public void setUp() throws Exception { + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + + private static final Supplier clientConfig + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + + private static final Supplier> singleConnectionPoolConfig + = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + @Test + public void simple() { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache())) { + control.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + control.del("foo"); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void simpleWithSimpleMap() { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + control.del("foo"); + assertThat(map, Matchers.aMapWithSize(1)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.ping(); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } + + @Test + public void flushAll() { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache())) { + control.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + control.flushAll(); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void flushAllWithSimpleMap() { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + control.flushAll(); + assertThat(map, Matchers.aMapWithSize(1)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.ping(); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } +} diff --git a/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java new file mode 100644 index 0000000000..9af243ffc7 --- /dev/null +++ b/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java @@ -0,0 +1,95 @@ +package redis.clients.jedis; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.nio.ByteBuffer; +import java.util.Arrays; +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.Test; + +public class JedisSentineledClientSideCacheTest { + + private static final String MASTER_NAME = "mymaster"; + + protected static final HostAndPort sentinel1 = HostAndPorts.getSentinelServers().get(1); + protected static final HostAndPort sentinel2 = HostAndPorts.getSentinelServers().get(3); + + private static final Set sentinels = Arrays.asList(sentinel1, sentinel2).stream().collect(Collectors.toSet()); + + private static final JedisClientConfig masterClientConfig = DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + + private static final JedisClientConfig sentinelClientConfig = DefaultJedisClientConfig.builder().resp3().build(); + + private static final Supplier> singleConnectionPoolConfig + = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + @Test + public void simple() { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(), sentinels, sentinelClientConfig)) { + jedis.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + jedis.del("foo"); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void simpleWithSimpleMap() { + HashMap map = new HashMap<>(); + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(map), sentinels, sentinelClientConfig)) { + jedis.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.del("foo"); + assertThat(map, Matchers.aMapWithSize(1)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.ping(); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } + + @Test + public void flushAll() { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(), sentinels, sentinelClientConfig)) { + jedis.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + jedis.flushAll(); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void flushAllWithSimpleMap() { + HashMap map = new HashMap<>(); + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(map), sentinels, sentinelClientConfig)) { + jedis.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.flushAll(); + assertThat(map, Matchers.aMapWithSize(1)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + jedis.ping(); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } +} diff --git a/src/test/java/redis/clients/jedis/SentineledConnectionProviderTest.java b/src/test/java/redis/clients/jedis/SentineledConnectionProviderTest.java index 593875710b..746c50bc43 100644 --- a/src/test/java/redis/clients/jedis/SentineledConnectionProviderTest.java +++ b/src/test/java/redis/clients/jedis/SentineledConnectionProviderTest.java @@ -22,9 +22,6 @@ public class SentineledConnectionProviderTest { private static final String MASTER_NAME = "mymaster"; - //protected static HostAndPort master = HostAndPorts.getRedisServers().get(2); - //protected static HostAndPort slave1 = HostAndPorts.getRedisServers().get(3); - protected static final HostAndPort sentinel1 = HostAndPorts.getSentinelServers().get(1); protected static final HostAndPort sentinel2 = HostAndPorts.getSentinelServers().get(3); From 5f1d8c633862d95831475f72fd882dd6d8d41d2c Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:56:34 +0600 Subject: [PATCH 05/48] Client-side caching by hashing command arguments (#3700) * Support TTL in client side caching (using Caffeine library) * Also Guava cache * format pom.xml * Client-side caching by command arguments TODO: Compute hash code. * send keys * todo comment for clean-up * rename method to invalidate * Client-side caching by hashing command arguments * Hash command arguments for CaffeineCSC using OpenHFT hashing * Clean-up keyHashes map * added javadoc * rename method * remove lock * descriptive name * descriptive names and fix * common default values in base class --- pom.xml | 22 ++++ .../redis/clients/jedis/ClientSideCache.java | 109 ++++++++++++------ .../java/redis/clients/jedis/Protocol.java | 2 +- .../redis/clients/jedis/UnifiedJedis.java | 18 +-- .../redis/clients/jedis/util/CaffeineCSC.java | 93 +++++++++++++++ .../redis/clients/jedis/util/GuavaCSC.java | 90 +++++++++++++++ .../JedisClusterClientSideCacheTest.java | 14 +-- .../jedis/JedisPooledClientSideCacheTest.java | 14 +-- .../JedisSentineledClientSideCacheTest.java | 23 ++-- .../java/redis/clients/jedis/util/MapCSC.java | 50 ++++++++ 10 files changed, 357 insertions(+), 78 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/util/CaffeineCSC.java create mode 100644 src/main/java/redis/clients/jedis/util/GuavaCSC.java create mode 100644 src/test/java/redis/clients/jedis/util/MapCSC.java diff --git a/pom.xml b/pom.xml index c9fd15d2cb..e6ff5bc70b 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,27 @@ 2.10.1 + + + + com.google.guava + guava + 33.0.0-jre + true + + + com.github.ben-manes.caffeine + caffeine + 2.9.3 + true + + + net.openhft + zero-allocation-hashing + 0.16 + true + + com.kohlschutter.junixsocket @@ -90,6 +111,7 @@ 1.19.0 test + junit diff --git a/src/main/java/redis/clients/jedis/ClientSideCache.java b/src/main/java/redis/clients/jedis/ClientSideCache.java index 62c5be28c2..c2c9248acf 100644 --- a/src/main/java/redis/clients/jedis/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/ClientSideCache.java @@ -1,71 +1,104 @@ package redis.clients.jedis; import java.nio.ByteBuffer; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; - -import redis.clients.jedis.exceptions.JedisException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import redis.clients.jedis.util.SafeEncoder; -public class ClientSideCache { +/** + * 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.util.CaffeineCSC CaffeineCSC} or + * {@link redis.clients.jedis.util.GuavaCSC GuavaCSC} or a custom implementation of their own. + */ +public abstract class ClientSideCache { - private final Map cache; + protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; + protected static final int DEFAULT_EXPIRE_SECONDS = 100; - public ClientSideCache() { - this.cache = new HashMap<>(); - } + private final Map> keyToCommandHashes; - /** - * For testing purpose only. - * @param map - */ - ClientSideCache(Map map) { - this.cache = map; + protected ClientSideCache() { + this.keyToCommandHashes = new ConcurrentHashMap<>(); } + protected abstract void invalidateAllCommandHashes(); + + protected abstract void invalidateCommandHashes(Iterable hashes); + + protected abstract void put(long hash, Object value); + + protected abstract Object get(long hash); + + protected abstract long getCommandHash(CommandObject command); + public final void clear() { - cache.clear(); + invalidateAllKeysAndCommandHashes(); } - public final void invalidateKeys(List list) { + final void invalidate(List list) { if (list == null) { - clear(); + invalidateAllKeysAndCommandHashes(); return; } - list.forEach(this::invalidateKey); + list.forEach(this::invalidateKeyAndRespectiveCommandHashes); } - private void invalidateKey(Object key) { - if (key instanceof byte[]) { - cache.remove(convertKey((byte[]) key)); - } else { - throw new JedisException("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); - } + private void invalidateAllKeysAndCommandHashes() { + invalidateAllCommandHashes(); + keyToCommandHashes.clear(); } - protected void setKey(Object key, Object value) { - cache.put(getMapKey(key), value); - } + private void invalidateKeyAndRespectiveCommandHashes(Object key) { + if (!(key instanceof byte[])) { + throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); + } - protected T getValue(Object key) { - return (T) getMapValue(key); - } + final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key); - private Object getMapValue(Object key) { - return cache.get(getMapKey(key)); + Set hashes = keyToCommandHashes.get(mapKey); + if (hashes != null) { + invalidateCommandHashes(hashes); + keyToCommandHashes.remove(mapKey); + } } - private ByteBuffer getMapKey(Object key) { - if (key instanceof byte[]) { - return convertKey((byte[]) key); - } else { - return convertKey(SafeEncoder.encode(String.valueOf(key))); + final T getValue(Function, T> loader, CommandObject command, String... keys) { + + final long hash = getCommandHash(command); + + T value = (T) get(hash); + if (value != null) { + return value; } + + value = loader.apply(command); + if (value != null) { + put(hash, value); + for (String key : keys) { + ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key); + if (keyToCommandHashes.containsKey(mapKey)) { + keyToCommandHashes.get(mapKey).add(hash); + } else { + Set set = new HashSet<>(); + set.add(hash); + keyToCommandHashes.put(mapKey, set); + } + } + } + + return value; + } + + private ByteBuffer makeKeyForKeyToCommandHashes(String key) { + return makeKeyForKeyToCommandHashes(SafeEncoder.encode(key)); } - private static ByteBuffer convertKey(byte[] b) { + private static ByteBuffer makeKeyForKeyToCommandHashes(byte[] b) { return ByteBuffer.wrap(b); } } diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 4af1261cd4..4bd82fec1e 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -248,7 +248,7 @@ private static void processPush(final RedisInputStream is, ClientSideCache cache //System.out.println("PUSH: " + SafeEncoder.encodeObject(list)); if (list.size() == 2 && list.get(0) instanceof byte[] && Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) { - cache.invalidateKeys((List) list.get(1)); + cache.invalidate((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 ba7b36b134..3a2dec9d77 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -295,6 +295,14 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co this.commandObjects.setBroadcastAndRoundRobinConfig(this.broadcastAndRoundRobinConfig); } + private T executeClientSideCacheCommand(CommandObject command, String... keys) { + if (clientSideCache == null) { + return executeCommand(command); + } + + return clientSideCache.getValue((cmd) -> executeCommand(cmd), command, keys); + } + public String ping() { return checkAndBroadcastCommand(commandObjects.ping()); } @@ -749,15 +757,7 @@ public String set(String key, String value, SetParams params) { @Override public String get(String key) { - if (clientSideCache != null) { - String cachedValue = clientSideCache.getValue(key); - if (cachedValue != null) return cachedValue; - - String value = executeCommand(commandObjects.get(key)); - if (value != null) clientSideCache.setKey(key, value); - return value; - } - return executeCommand(commandObjects.get(key)); + return executeClientSideCacheCommand(commandObjects.get(key), key); } @Override diff --git a/src/main/java/redis/clients/jedis/util/CaffeineCSC.java b/src/main/java/redis/clients/jedis/util/CaffeineCSC.java new file mode 100644 index 0000000000..3bce3504b3 --- /dev/null +++ b/src/main/java/redis/clients/jedis/util/CaffeineCSC.java @@ -0,0 +1,93 @@ +package redis.clients.jedis.util; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import java.util.concurrent.TimeUnit; +import net.openhft.hashing.LongHashFunction; +import redis.clients.jedis.ClientSideCache; +import redis.clients.jedis.CommandObject; +import redis.clients.jedis.args.Rawable; + +public class CaffeineCSC extends ClientSideCache { + + private static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3(); + + private final Cache cache; + private final LongHashFunction function; + + public CaffeineCSC(Cache caffeineCache, LongHashFunction hashFunction) { + this.cache = caffeineCache; + this.function = hashFunction; + } + + @Override + protected final void invalidateAllCommandHashes() { + cache.invalidateAll(); + } + + @Override + protected void invalidateCommandHashes(Iterable hashes) { + cache.invalidateAll(hashes); + } + + @Override + protected void put(long hash, Object value) { + cache.put(hash, value); + } + + @Override + protected Object get(long hash) { + return cache.getIfPresent(hash); + } + + @Override + protected final long getCommandHash(CommandObject command) { + long[] nums = new long[command.getArguments().size() + 1]; + int idx = 0; + for (Rawable raw : command.getArguments()) { + nums[idx++] = function.hashBytes(raw.getRaw()); + } + nums[idx] = function.hashInt(command.getBuilder().hashCode()); + return function.hashLongs(nums); + } + + 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 LongHashFunction hashFunction = DEFAULT_HASH_FUNCTION; + + private Builder() { } + + public Builder maximumSize(int size) { + this.maximumSize = size; + return this; + } + + public Builder ttl(int seconds) { + this.expireTime = seconds; + return this; + } + + public Builder hashFunction(LongHashFunction function) { + this.hashFunction = function; + return this; + } + + public CaffeineCSC build() { + Caffeine cb = Caffeine.newBuilder(); + + cb.maximumSize(maximumSize); + + cb.expireAfterWrite(expireTime, expireTimeUnit); + + return new CaffeineCSC(cb.build(), hashFunction); + } + } +} diff --git a/src/main/java/redis/clients/jedis/util/GuavaCSC.java b/src/main/java/redis/clients/jedis/util/GuavaCSC.java new file mode 100644 index 0000000000..d9973b7dc6 --- /dev/null +++ b/src/main/java/redis/clients/jedis/util/GuavaCSC.java @@ -0,0 +1,90 @@ +package redis.clients.jedis.util; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hasher; +import java.util.concurrent.TimeUnit; +import redis.clients.jedis.ClientSideCache; +import redis.clients.jedis.CommandObject; + +public class GuavaCSC extends ClientSideCache { + + private static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011(); + + private final Cache cache; + private final HashFunction function; + + public GuavaCSC(Cache guavaCache, HashFunction hashFunction) { + this.cache = guavaCache; + this.function = hashFunction; + } + + @Override + protected final void invalidateAllCommandHashes() { + cache.invalidateAll(); + } + + @Override + protected void invalidateCommandHashes(Iterable hashes) { + cache.invalidateAll(hashes); + } + + @Override + protected void put(long hash, Object value) { + cache.put(hash, value); + } + + @Override + protected Object get(long hash) { + return cache.getIfPresent(hash); + } + + @Override + protected final long getCommandHash(CommandObject command) { + Hasher hasher = function.newHasher(); + command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw())); + hasher.putInt(command.getBuilder().hashCode()); + return hasher.hash().asLong(); + } + + 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 HashFunction hashFunction = DEFAULT_HASH_FUNCTION; + + private Builder() { } + + public Builder maximumSize(int size) { + this.maximumSize = size; + return this; + } + + public Builder ttl(int seconds) { + this.expireTime = seconds; + return this; + } + + public Builder hashFunction(HashFunction function) { + this.hashFunction = function; + return this; + } + + public GuavaCSC build() { + CacheBuilder cb = CacheBuilder.newBuilder(); + + cb.maximumSize(maximumSize); + + cb.expireAfterWrite(expireTime, expireTimeUnit); + + return new GuavaCSC(cb.build(), hashFunction); + } + } +} diff --git a/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java index 3c8bc18c5c..60ddf002d7 100644 --- a/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java @@ -4,7 +4,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Set; @@ -14,6 +13,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; import org.junit.Test; +import redis.clients.jedis.util.MapCSC; public class JedisClusterClientSideCacheTest extends JedisClusterTestBase { @@ -31,7 +31,7 @@ public class JedisClusterClientSideCacheTest extends JedisClusterTestBase { @Test public void simple() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC())) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.del("foo"); @@ -41,8 +41,8 @@ public void simple() { @Test public void simpleWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + HashMap map = new HashMap<>(); + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); @@ -60,7 +60,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC())) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.flushAll(); @@ -70,8 +70,8 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + HashMap map = new HashMap<>(); + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java index ad4313a4b7..2e641e0f3a 100644 --- a/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java @@ -4,7 +4,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.nio.ByteBuffer; import java.util.HashMap; import java.util.function.Supplier; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; @@ -12,6 +11,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import redis.clients.jedis.util.MapCSC; public class JedisPooledClientSideCacheTest { @@ -42,7 +42,7 @@ public void tearDown() throws Exception { @Test public void simple() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); @@ -52,8 +52,8 @@ public void simple() { @Test public void simpleWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); @@ -71,7 +71,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); @@ -81,8 +81,8 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new ClientSideCache(map), singleConnectionPoolConfig.get())) { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java index 9af243ffc7..9e5f720933 100644 --- a/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java @@ -4,16 +4,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.nio.ByteBuffer; import java.util.Arrays; 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.Test; +import redis.clients.jedis.util.MapCSC; public class JedisSentineledClientSideCacheTest { @@ -28,16 +26,9 @@ public class JedisSentineledClientSideCacheTest { private static final JedisClientConfig sentinelClientConfig = DefaultJedisClientConfig.builder().resp3().build(); - private static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; - @Test public void simple() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(), sentinels, sentinelClientConfig)) { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.del("foo"); @@ -47,8 +38,8 @@ public void simple() { @Test public void simpleWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(map), sentinels, sentinelClientConfig)) { + HashMap map = new HashMap<>(); + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); @@ -66,7 +57,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(), sentinels, sentinelClientConfig)) { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.flushAll(); @@ -76,8 +67,8 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new ClientSideCache(map), sentinels, sentinelClientConfig)) { + HashMap map = new HashMap<>(); + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/util/MapCSC.java b/src/test/java/redis/clients/jedis/util/MapCSC.java new file mode 100644 index 0000000000..eb229036ea --- /dev/null +++ b/src/test/java/redis/clients/jedis/util/MapCSC.java @@ -0,0 +1,50 @@ +package redis.clients.jedis.util; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import redis.clients.jedis.ClientSideCache; +import redis.clients.jedis.CommandObject; +import redis.clients.jedis.args.Rawable; + +public class MapCSC extends ClientSideCache { + + private final Map cache; + + public MapCSC() { + this(new ConcurrentHashMap<>()); + } + + public MapCSC(Map map) { + this.cache = map; + } + + @Override + protected final void invalidateAllCommandHashes() { + cache.clear(); + } + + @Override + protected void invalidateCommandHashes(Iterable hashes) { + hashes.forEach(hash -> cache.remove(hash)); + } + + @Override + protected void put(long hash, Object value) { + cache.put(hash, value); + } + + @Override + protected Object get(long hash) { + return cache.get(hash); + } + + @Override + protected final long getCommandHash(CommandObject command) { + long result = 1; + for (Rawable raw : command.getArguments()) { + result = 31 * result + Arrays.hashCode(raw.getRaw()); + } + return 31 * result + command.getBuilder().hashCode(); + } +} From 4cada223690ae5bcd76e5861bec644d70682bfc0 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:37:35 +0600 Subject: [PATCH 06/48] Cover Redis commands for client side caching (#3702) --- .../redis/clients/jedis/ClientSideCache.java | 11 +- .../redis/clients/jedis/UnifiedJedis.java | 448 +++++++++--------- 2 files changed, 231 insertions(+), 228 deletions(-) diff --git a/src/main/java/redis/clients/jedis/ClientSideCache.java b/src/main/java/redis/clients/jedis/ClientSideCache.java index c2c9248acf..389b795814 100644 --- a/src/main/java/redis/clients/jedis/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/ClientSideCache.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; + import java.util.function.Function; import redis.clients.jedis.util.SafeEncoder; @@ -67,7 +68,7 @@ private void invalidateKeyAndRespectiveCommandHashes(Object key) { } } - final T getValue(Function, T> loader, CommandObject command, String... keys) { + final T getValue(Function, T> loader, CommandObject command, Object... keys) { final long hash = getCommandHash(command); @@ -79,7 +80,7 @@ final T getValue(Function, T> loader, CommandObject comm value = loader.apply(command); if (value != null) { put(hash, value); - for (String key : keys) { + for (Object key : keys) { ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key); if (keyToCommandHashes.containsKey(mapKey)) { keyToCommandHashes.get(mapKey).add(hash); @@ -94,8 +95,10 @@ final T getValue(Function, T> loader, CommandObject comm return value; } - private ByteBuffer makeKeyForKeyToCommandHashes(String key) { - return makeKeyForKeyToCommandHashes(SafeEncoder.encode(key)); + private ByteBuffer makeKeyForKeyToCommandHashes(Object key) { + if (key instanceof byte[]) return makeKeyForKeyToCommandHashes((byte[]) key); + else if (key instanceof String) return makeKeyForKeyToCommandHashes(SafeEncoder.encode((String) key)); + else throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); } private static ByteBuffer makeKeyForKeyToCommandHashes(byte[] b) { diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 3a2dec9d77..eb32a5a796 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -295,7 +295,7 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co this.commandObjects.setBroadcastAndRoundRobinConfig(this.broadcastAndRoundRobinConfig); } - private T executeClientSideCacheCommand(CommandObject command, String... keys) { + private T checkAndClientSideCacheCommand(CommandObject command, Object... keys) { if (clientSideCache == null) { return executeCommand(command); } @@ -322,12 +322,12 @@ public String configSet(String parameter, String value) { // Key commands @Override public boolean exists(String key) { - return executeCommand(commandObjects.exists(key)); + return checkAndClientSideCacheCommand(commandObjects.exists(key), key); } @Override public long exists(String... keys) { - return executeCommand(commandObjects.exists(keys)); + return checkAndClientSideCacheCommand(commandObjects.exists(keys), (Object[]) keys); } @Override @@ -337,17 +337,17 @@ public long persist(String key) { @Override public String type(String key) { - return executeCommand(commandObjects.type(key)); + return checkAndClientSideCacheCommand(commandObjects.type(key), key); } @Override public boolean exists(byte[] key) { - return executeCommand(commandObjects.exists(key)); + return checkAndClientSideCacheCommand(commandObjects.exists(key), key); } @Override public long exists(byte[]... keys) { - return executeCommand(commandObjects.exists(keys)); + return checkAndClientSideCacheCommand(commandObjects.exists(keys), (Object[]) keys); } @Override @@ -357,7 +357,7 @@ public long persist(byte[] key) { @Override public String type(byte[] key) { - return executeCommand(commandObjects.type(key)); + return checkAndClientSideCacheCommand(commandObjects.type(key), key); } @Override @@ -757,7 +757,7 @@ public String set(String key, String value, SetParams params) { @Override public String get(String key) { - return executeClientSideCacheCommand(commandObjects.get(key), key); + return checkAndClientSideCacheCommand(commandObjects.get(key), key); } @Override @@ -792,7 +792,7 @@ public String set(byte[] key, byte[] value, SetParams params) { @Override public byte[] get(byte[] key) { - return executeCommand(commandObjects.get(key)); + return checkAndClientSideCacheCommand(commandObjects.get(key), key); } @Override @@ -822,7 +822,7 @@ public boolean setbit(String key, long offset, boolean value) { @Override public boolean getbit(String key, long offset) { - return executeCommand(commandObjects.getbit(key, offset)); + return checkAndClientSideCacheCommand(commandObjects.getbit(key, offset), key); } @Override @@ -832,7 +832,7 @@ public long setrange(String key, long offset, String value) { @Override public String getrange(String key, long startOffset, long endOffset) { - return executeCommand(commandObjects.getrange(key, startOffset, endOffset)); + return checkAndClientSideCacheCommand(commandObjects.getrange(key, startOffset, endOffset), key); } @Override @@ -842,7 +842,7 @@ public boolean setbit(byte[] key, long offset, boolean value) { @Override public boolean getbit(byte[] key, long offset) { - return executeCommand(commandObjects.getbit(key, offset)); + return checkAndClientSideCacheCommand(commandObjects.getbit(key, offset), key); } @Override @@ -852,7 +852,7 @@ public long setrange(byte[] key, long offset, byte[] value) { @Override public byte[] getrange(byte[] key, long startOffset, long endOffset) { - return executeCommand(commandObjects.getrange(key, startOffset, endOffset)); + return checkAndClientSideCacheCommand(commandObjects.getrange(key, startOffset, endOffset), key); } @Override @@ -947,7 +947,7 @@ public long decrBy(byte[] key, long decrement) { @Override public List mget(String... keys) { - return executeCommand(commandObjects.mget(keys)); + return checkAndClientSideCacheCommand(commandObjects.mget(keys), (Object[]) keys); } @Override @@ -962,7 +962,7 @@ public long msetnx(String... keysvalues) { @Override public List mget(byte[]... keys) { - return executeCommand(commandObjects.mget(keys)); + return checkAndClientSideCacheCommand(commandObjects.mget(keys), (Object[]) keys); } @Override @@ -982,12 +982,12 @@ public long append(String key, String value) { @Override public String substr(String key, int start, int end) { - return executeCommand(commandObjects.substr(key, start, end)); + return checkAndClientSideCacheCommand(commandObjects.substr(key, start, end), key); } @Override public long strlen(String key) { - return executeCommand(commandObjects.strlen(key)); + return checkAndClientSideCacheCommand(commandObjects.strlen(key), key); } @Override @@ -997,62 +997,62 @@ public long append(byte[] key, byte[] value) { @Override public byte[] substr(byte[] key, int start, int end) { - return executeCommand(commandObjects.substr(key, start, end)); + return checkAndClientSideCacheCommand(commandObjects.substr(key, start, end), key); } @Override public long strlen(byte[] key) { - return executeCommand(commandObjects.strlen(key)); + return checkAndClientSideCacheCommand(commandObjects.strlen(key), key); } @Override public long bitcount(String key) { - return executeCommand(commandObjects.bitcount(key)); + return checkAndClientSideCacheCommand(commandObjects.bitcount(key), key); } @Override public long bitcount(String key, long start, long end) { - return executeCommand(commandObjects.bitcount(key, start, end)); + return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end), key); } @Override public long bitcount(String key, long start, long end, BitCountOption option) { - return executeCommand(commandObjects.bitcount(key, start, end, option)); + return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end, option), key); } @Override public long bitpos(String key, boolean value) { - return executeCommand(commandObjects.bitpos(key, value)); + return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value), key); } @Override public long bitpos(String key, boolean value, BitPosParams params) { - return executeCommand(commandObjects.bitpos(key, value, params)); + return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value, params), key); } @Override public long bitcount(byte[] key) { - return executeCommand(commandObjects.bitcount(key)); + return checkAndClientSideCacheCommand(commandObjects.bitcount(key), key); } @Override public long bitcount(byte[] key, long start, long end) { - return executeCommand(commandObjects.bitcount(key, start, end)); + return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end), key); } @Override public long bitcount(byte[] key, long start, long end, BitCountOption option) { - return executeCommand(commandObjects.bitcount(key, start, end, option)); + return checkAndClientSideCacheCommand(commandObjects.bitcount(key, start, end, option), key); } @Override public long bitpos(byte[] key, boolean value) { - return executeCommand(commandObjects.bitpos(key, value)); + return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value), key); } @Override public long bitpos(byte[] key, boolean value, BitPosParams params) { - return executeCommand(commandObjects.bitpos(key, value, params)); + return checkAndClientSideCacheCommand(commandObjects.bitpos(key, value, params), key); } @Override @@ -1062,7 +1062,7 @@ public List bitfield(String key, String... arguments) { @Override public List bitfieldReadonly(String key, String... arguments) { - return executeCommand(commandObjects.bitfieldReadonly(key, arguments)); + return checkAndClientSideCacheCommand(commandObjects.bitfieldReadonly(key, arguments), key); } @Override @@ -1072,7 +1072,7 @@ public List bitfield(byte[] key, byte[]... arguments) { @Override public List bitfieldReadonly(byte[] key, byte[]... arguments) { - return executeCommand(commandObjects.bitfieldReadonly(key, arguments)); + return checkAndClientSideCacheCommand(commandObjects.bitfieldReadonly(key, arguments), key); } @Override @@ -1087,12 +1087,12 @@ public long bitop(BitOP op, byte[] destKey, byte[]... srcKeys) { @Override public LCSMatchResult lcs(String keyA, String keyB, LCSParams params) { - return executeCommand(commandObjects.lcs(keyA, keyB, params)); + return checkAndClientSideCacheCommand(commandObjects.lcs(keyA, keyB, params), keyA, keyB); } @Override public LCSMatchResult lcs(byte[] keyA, byte[] keyB, LCSParams params) { - return executeCommand(commandObjects.lcs(keyA, keyB, params)); + return checkAndClientSideCacheCommand(commandObjects.lcs(keyA, keyB, params), keyA, keyB); } // String commands @@ -1109,12 +1109,12 @@ public long lpush(String key, String... string) { @Override public long llen(String key) { - return executeCommand(commandObjects.llen(key)); + return checkAndClientSideCacheCommand(commandObjects.llen(key), key); } @Override public List lrange(String key, long start, long stop) { - return executeCommand(commandObjects.lrange(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.lrange(key, start, stop), key); } @Override @@ -1124,7 +1124,7 @@ public String ltrim(String key, long start, long stop) { @Override public String lindex(String key, long index) { - return executeCommand(commandObjects.lindex(key, index)); + return checkAndClientSideCacheCommand(commandObjects.lindex(key, index), key); } @Override @@ -1139,12 +1139,12 @@ public long lpush(byte[] key, byte[]... args) { @Override public long llen(byte[] key) { - return executeCommand(commandObjects.llen(key)); + return checkAndClientSideCacheCommand(commandObjects.llen(key), key); } @Override public List lrange(byte[] key, long start, long stop) { - return executeCommand(commandObjects.lrange(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.lrange(key, start, stop), key); } @Override @@ -1154,7 +1154,7 @@ public String ltrim(byte[] key, long start, long stop) { @Override public byte[] lindex(byte[] key, long index) { - return executeCommand(commandObjects.lindex(key, index)); + return checkAndClientSideCacheCommand(commandObjects.lindex(key, index), key); } @Override @@ -1199,32 +1199,32 @@ public List lpop(byte[] key, int count) { @Override public Long lpos(String key, String element) { - return executeCommand(commandObjects.lpos(key, element)); + return checkAndClientSideCacheCommand(commandObjects.lpos(key, element), key); } @Override public Long lpos(String key, String element, LPosParams params) { - return executeCommand(commandObjects.lpos(key, element, params)); + return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params), key); } @Override public List lpos(String key, String element, LPosParams params, long count) { - return executeCommand(commandObjects.lpos(key, element, params, count)); + return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params, count), key); } @Override public Long lpos(byte[] key, byte[] element) { - return executeCommand(commandObjects.lpos(key, element)); + return checkAndClientSideCacheCommand(commandObjects.lpos(key, element), key); } @Override public Long lpos(byte[] key, byte[] element, LPosParams params) { - return executeCommand(commandObjects.lpos(key, element, params)); + return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params), key); } @Override public List lpos(byte[] key, byte[] element, LPosParams params, long count) { - return executeCommand(commandObjects.lpos(key, element, params, count)); + return checkAndClientSideCacheCommand(commandObjects.lpos(key, element, params, count), key); } @Override @@ -1431,7 +1431,7 @@ public long hset(String key, Map hash) { @Override public String hget(String key, String field) { - return executeCommand(commandObjects.hget(key, field)); + return checkAndClientSideCacheCommand(commandObjects.hget(key, field), key); } @Override @@ -1446,7 +1446,7 @@ public String hmset(String key, Map hash) { @Override public List hmget(String key, String... fields) { - return executeCommand(commandObjects.hmget(key, fields)); + return checkAndClientSideCacheCommand(commandObjects.hmget(key, fields), key); } @Override @@ -1461,7 +1461,7 @@ public long hset(byte[] key, Map hash) { @Override public byte[] hget(byte[] key, byte[] field) { - return executeCommand(commandObjects.hget(key, field)); + return checkAndClientSideCacheCommand(commandObjects.hget(key, field), key); } @Override @@ -1476,7 +1476,7 @@ public String hmset(byte[] key, Map hash) { @Override public List hmget(byte[] key, byte[]... fields) { - return executeCommand(commandObjects.hmget(key, fields)); + return checkAndClientSideCacheCommand(commandObjects.hmget(key, fields), key); } @Override @@ -1491,7 +1491,7 @@ public double hincrByFloat(String key, String field, double value) { @Override public boolean hexists(String key, String field) { - return executeCommand(commandObjects.hexists(key, field)); + return checkAndClientSideCacheCommand(commandObjects.hexists(key, field), key); } @Override @@ -1501,7 +1501,7 @@ public long hdel(String key, String... field) { @Override public long hlen(String key) { - return executeCommand(commandObjects.hlen(key)); + return checkAndClientSideCacheCommand(commandObjects.hlen(key), key); } @Override @@ -1516,7 +1516,7 @@ public double hincrByFloat(byte[] key, byte[] field, double value) { @Override public boolean hexists(byte[] key, byte[] field) { - return executeCommand(commandObjects.hexists(key, field)); + return checkAndClientSideCacheCommand(commandObjects.hexists(key, field), key); } @Override @@ -1526,37 +1526,37 @@ public long hdel(byte[] key, byte[]... field) { @Override public long hlen(byte[] key) { - return executeCommand(commandObjects.hlen(key)); + return checkAndClientSideCacheCommand(commandObjects.hlen(key), key); } @Override public Set hkeys(String key) { - return executeCommand(commandObjects.hkeys(key)); + return checkAndClientSideCacheCommand(commandObjects.hkeys(key), key); } @Override public List hvals(String key) { - return executeCommand(commandObjects.hvals(key)); + return checkAndClientSideCacheCommand(commandObjects.hvals(key), key); } @Override public Map hgetAll(String key) { - return executeCommand(commandObjects.hgetAll(key)); + return checkAndClientSideCacheCommand(commandObjects.hgetAll(key), key); } @Override public Set hkeys(byte[] key) { - return executeCommand(commandObjects.hkeys(key)); + return checkAndClientSideCacheCommand(commandObjects.hkeys(key), key); } @Override public List hvals(byte[] key) { - return executeCommand(commandObjects.hvals(key)); + return checkAndClientSideCacheCommand(commandObjects.hvals(key), key); } @Override public Map hgetAll(byte[] key) { - return executeCommand(commandObjects.hgetAll(key)); + return checkAndClientSideCacheCommand(commandObjects.hgetAll(key), key); } @Override @@ -1581,7 +1581,7 @@ public ScanResult> hscan(String key, String cursor, Sc @Override public long hstrlen(String key, String field) { - return executeCommand(commandObjects.hstrlen(key, field)); + return checkAndClientSideCacheCommand(commandObjects.hstrlen(key, field), key); } @Override @@ -1606,7 +1606,7 @@ public ScanResult> hscan(byte[] key, byte[] cursor, Sc @Override public long hstrlen(byte[] key, byte[] field) { - return executeCommand(commandObjects.hstrlen(key, field)); + return checkAndClientSideCacheCommand(commandObjects.hstrlen(key, field), key); } // Hash commands @@ -1618,7 +1618,7 @@ public long sadd(String key, String... members) { @Override public Set smembers(String key) { - return executeCommand(commandObjects.smembers(key)); + return checkAndClientSideCacheCommand(commandObjects.smembers(key), key); } @Override @@ -1638,17 +1638,17 @@ public Set spop(String key, long count) { @Override public long scard(String key) { - return executeCommand(commandObjects.scard(key)); + return checkAndClientSideCacheCommand(commandObjects.scard(key), key); } @Override public boolean sismember(String key, String member) { - return executeCommand(commandObjects.sismember(key, member)); + return checkAndClientSideCacheCommand(commandObjects.sismember(key, member), key); } @Override public List smismember(String key, String... members) { - return executeCommand(commandObjects.smismember(key, members)); + return checkAndClientSideCacheCommand(commandObjects.smismember(key, members), key); } @Override @@ -1658,7 +1658,7 @@ public long sadd(byte[] key, byte[]... members) { @Override public Set smembers(byte[] key) { - return executeCommand(commandObjects.smembers(key)); + return checkAndClientSideCacheCommand(commandObjects.smembers(key), key); } @Override @@ -1678,17 +1678,17 @@ public Set spop(byte[] key, long count) { @Override public long scard(byte[] key) { - return executeCommand(commandObjects.scard(key)); + return checkAndClientSideCacheCommand(commandObjects.scard(key), key); } @Override public boolean sismember(byte[] key, byte[] member) { - return executeCommand(commandObjects.sismember(key, member)); + return checkAndClientSideCacheCommand(commandObjects.sismember(key, member), key); } @Override public List smismember(byte[] key, byte[]... members) { - return executeCommand(commandObjects.smismember(key, members)); + return checkAndClientSideCacheCommand(commandObjects.smismember(key, members), key); } @Override @@ -1723,7 +1723,7 @@ public ScanResult sscan(byte[] key, byte[] cursor, ScanParams params) { @Override public Set sdiff(String... keys) { - return executeCommand(commandObjects.sdiff(keys)); + return checkAndClientSideCacheCommand(commandObjects.sdiff(keys), (Object[]) keys); } @Override @@ -1733,7 +1733,7 @@ public long sdiffstore(String dstkey, String... keys) { @Override public Set sinter(String... keys) { - return executeCommand(commandObjects.sinter(keys)); + return checkAndClientSideCacheCommand(commandObjects.sinter(keys), (Object[]) keys); } @Override @@ -1753,7 +1753,7 @@ public long sintercard(int limit, String... keys) { @Override public Set sunion(String... keys) { - return executeCommand(commandObjects.sunion(keys)); + return checkAndClientSideCacheCommand(commandObjects.sunion(keys), (Object[]) keys); } @Override @@ -1768,7 +1768,7 @@ public long smove(String srckey, String dstkey, String member) { @Override public Set sdiff(byte[]... keys) { - return executeCommand(commandObjects.sdiff(keys)); + return checkAndClientSideCacheCommand(commandObjects.sdiff(keys), (Object[]) keys); } @Override @@ -1778,7 +1778,7 @@ public long sdiffstore(byte[] dstkey, byte[]... keys) { @Override public Set sinter(byte[]... keys) { - return executeCommand(commandObjects.sinter(keys)); + return checkAndClientSideCacheCommand(commandObjects.sinter(keys), (Object[]) keys); } @Override @@ -1798,7 +1798,7 @@ public long sintercard(int limit, byte[]... keys) { @Override public Set sunion(byte[]... keys) { - return executeCommand(commandObjects.sunion(keys)); + return checkAndClientSideCacheCommand(commandObjects.sunion(keys), (Object[]) keys); } @Override @@ -1880,22 +1880,22 @@ public Double zincrby(String key, double increment, String member, ZIncrByParams @Override public Long zrank(String key, String member) { - return executeCommand(commandObjects.zrank(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrank(key, member), key); } @Override public Long zrevrank(String key, String member) { - return executeCommand(commandObjects.zrevrank(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrevrank(key, member), key); } @Override public KeyValue zrankWithScore(String key, String member) { - return executeCommand(commandObjects.zrankWithScore(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrankWithScore(key, member), key); } @Override public KeyValue zrevrankWithScore(String key, String member) { - return executeCommand(commandObjects.zrevrankWithScore(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrevrankWithScore(key, member), key); } @Override @@ -1915,22 +1915,22 @@ public Double zincrby(byte[] key, double increment, byte[] member, ZIncrByParams @Override public Long zrank(byte[] key, byte[] member) { - return executeCommand(commandObjects.zrank(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrank(key, member), key); } @Override public Long zrevrank(byte[] key, byte[] member) { - return executeCommand(commandObjects.zrevrank(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrevrank(key, member), key); } @Override public KeyValue zrankWithScore(byte[] key, byte[] member) { - return executeCommand(commandObjects.zrankWithScore(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrankWithScore(key, member), key); } @Override public KeyValue zrevrankWithScore(byte[] key, byte[] member) { - return executeCommand(commandObjects.zrevrankWithScore(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zrevrankWithScore(key, member), key); } @Override @@ -1950,17 +1950,17 @@ public List zrandmemberWithScores(String key, long count) { @Override public long zcard(String key) { - return executeCommand(commandObjects.zcard(key)); + return checkAndClientSideCacheCommand(commandObjects.zcard(key), key); } @Override public Double zscore(String key, String member) { - return executeCommand(commandObjects.zscore(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zscore(key, member), key); } @Override public List zmscore(String key, String... members) { - return executeCommand(commandObjects.zmscore(key, members)); + return checkAndClientSideCacheCommand(commandObjects.zmscore(key, members), key); } @Override @@ -1980,17 +1980,17 @@ public List zrandmemberWithScores(byte[] key, long count) { @Override public long zcard(byte[] key) { - return executeCommand(commandObjects.zcard(key)); + return checkAndClientSideCacheCommand(commandObjects.zcard(key), key); } @Override public Double zscore(byte[] key, byte[] member) { - return executeCommand(commandObjects.zscore(key, member)); + return checkAndClientSideCacheCommand(commandObjects.zscore(key, member), key); } @Override public List zmscore(byte[] key, byte[]... members) { - return executeCommand(commandObjects.zmscore(key, members)); + return checkAndClientSideCacheCommand(commandObjects.zmscore(key, members), key); } @Override @@ -2015,12 +2015,12 @@ public List zpopmin(String key, int count) { @Override public long zcount(String key, double min, double max) { - return executeCommand(commandObjects.zcount(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); } @Override public long zcount(String key, String min, String max) { - return executeCommand(commandObjects.zcount(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); } @Override @@ -2045,42 +2045,42 @@ public List zpopmin(byte[] key, int count) { @Override public long zcount(byte[] key, double min, double max) { - return executeCommand(commandObjects.zcount(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); } @Override public long zcount(byte[] key, byte[] min, byte[] max) { - return executeCommand(commandObjects.zcount(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zcount(key, min, max), key); } @Override public List zrange(String key, long start, long stop) { - return executeCommand(commandObjects.zrange(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrange(key, start, stop), key); } @Override public List zrevrange(String key, long start, long stop) { - return executeCommand(commandObjects.zrevrange(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrevrange(key, start, stop), key); } @Override public List zrangeWithScores(String key, long start, long stop) { - return executeCommand(commandObjects.zrangeWithScores(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, start, stop), key); } @Override public List zrevrangeWithScores(String key, long start, long stop) { - return executeCommand(commandObjects.zrevrangeWithScores(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeWithScores(key, start, stop), key); } @Override public List zrange(String key, ZRangeParams zRangeParams) { - return executeCommand(commandObjects.zrange(key, zRangeParams)); + return checkAndClientSideCacheCommand(commandObjects.zrange(key, zRangeParams), key); } @Override public List zrangeWithScores(String key, ZRangeParams zRangeParams) { - return executeCommand(commandObjects.zrangeWithScores(key, zRangeParams)); + return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, zRangeParams), key); } @Override @@ -2090,112 +2090,112 @@ public long zrangestore(String dest, String src, ZRangeParams zRangeParams) { @Override public List zrangeByScore(String key, double min, double max) { - return executeCommand(commandObjects.zrangeByScore(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); } @Override public List zrangeByScore(String key, String min, String max) { - return executeCommand(commandObjects.zrangeByScore(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); } @Override public List zrevrangeByScore(String key, double max, double min) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); } @Override public List zrangeByScore(String key, double min, double max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); } @Override public List zrevrangeByScore(String key, String max, String min) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); } @Override public List zrangeByScore(String key, String min, String max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); } @Override public List zrevrangeByScore(String key, double max, double min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); } @Override public List zrangeByScoreWithScores(String key, double min, double max) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); } @Override public List zrevrangeByScoreWithScores(String key, double max, double min) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); } @Override public List zrangeByScoreWithScores(String key, double min, double max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); } @Override public List zrevrangeByScore(String key, String max, String min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); } @Override public List zrangeByScoreWithScores(String key, String min, String max) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); } @Override public List zrevrangeByScoreWithScores(String key, String max, String min) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); } @Override public List zrangeByScoreWithScores(String key, String min, String max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); } @Override public List zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); } @Override public List zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); } @Override public List zrange(byte[] key, long start, long stop) { - return executeCommand(commandObjects.zrange(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrange(key, start, stop), key); } @Override public List zrevrange(byte[] key, long start, long stop) { - return executeCommand(commandObjects.zrevrange(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrevrange(key, start, stop), key); } @Override public List zrangeWithScores(byte[] key, long start, long stop) { - return executeCommand(commandObjects.zrangeWithScores(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, start, stop), key); } @Override public List zrevrangeWithScores(byte[] key, long start, long stop) { - return executeCommand(commandObjects.zrevrangeWithScores(key, start, stop)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeWithScores(key, start, stop), key); } @Override public List zrange(byte[] key, ZRangeParams zRangeParams) { - return executeCommand(commandObjects.zrange(key, zRangeParams)); + return checkAndClientSideCacheCommand(commandObjects.zrange(key, zRangeParams), key); } @Override public List zrangeWithScores(byte[] key, ZRangeParams zRangeParams) { - return executeCommand(commandObjects.zrangeWithScores(key, zRangeParams)); + return checkAndClientSideCacheCommand(commandObjects.zrangeWithScores(key, zRangeParams), key); } @Override @@ -2205,82 +2205,82 @@ public long zrangestore(byte[] dest, byte[] src, ZRangeParams zRangeParams) { @Override public List zrangeByScore(byte[] key, double min, double max) { - return executeCommand(commandObjects.zrangeByScore(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); } @Override public List zrangeByScore(byte[] key, byte[] min, byte[] max) { - return executeCommand(commandObjects.zrangeByScore(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max), key); } @Override public List zrevrangeByScore(byte[] key, double max, double min) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); } @Override public List zrangeByScore(byte[] key, double min, double max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); } @Override public List zrevrangeByScore(byte[] key, byte[] max, byte[] min) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min), key); } @Override public List zrangeByScore(byte[] key, byte[] min, byte[] max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScore(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScore(key, min, max, offset, count), key); } @Override public List zrevrangeByScore(byte[] key, double max, double min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); } @Override public List zrangeByScoreWithScores(byte[] key, double min, double max) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); } @Override public List zrevrangeByScoreWithScores(byte[] key, double max, double min) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); } @Override public List zrangeByScoreWithScores(byte[] key, double min, double max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); } @Override public List zrevrangeByScore(byte[] key, byte[] max, byte[] min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScore(key, max, min, offset, count), key); } @Override public List zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max), key); } @Override public List zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min), key); } @Override public List zrangeByScoreWithScores(byte[] key, byte[] min, byte[] max, int offset, int count) { - return executeCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByScoreWithScores(key, min, max, offset, count), key); } @Override public List zrevrangeByScoreWithScores(byte[] key, double max, double min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); } @Override public List zrevrangeByScoreWithScores(byte[] key, byte[] max, byte[] min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByScoreWithScores(key, max, min, offset, count), key); } @Override @@ -2315,27 +2315,27 @@ public long zremrangeByScore(byte[] key, byte[] min, byte[] max) { @Override public long zlexcount(String key, String min, String max) { - return executeCommand(commandObjects.zlexcount(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zlexcount(key, min, max), key); } @Override public List zrangeByLex(String key, String min, String max) { - return executeCommand(commandObjects.zrangeByLex(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max), key); } @Override public List zrangeByLex(String key, String min, String max, int offset, int count) { - return executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max, offset, count), key); } @Override public List zrevrangeByLex(String key, String max, String min) { - return executeCommand(commandObjects.zrevrangeByLex(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min), key); } @Override public List zrevrangeByLex(String key, String max, String min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count), key); } @Override @@ -2345,27 +2345,27 @@ public long zremrangeByLex(String key, String min, String max) { @Override public long zlexcount(byte[] key, byte[] min, byte[] max) { - return executeCommand(commandObjects.zlexcount(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zlexcount(key, min, max), key); } @Override public List zrangeByLex(byte[] key, byte[] min, byte[] max) { - return executeCommand(commandObjects.zrangeByLex(key, min, max)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max), key); } @Override public List zrangeByLex(byte[] key, byte[] min, byte[] max, int offset, int count) { - return executeCommand(commandObjects.zrangeByLex(key, min, max, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrangeByLex(key, min, max, offset, count), key); } @Override public List zrevrangeByLex(byte[] key, byte[] max, byte[] min) { - return executeCommand(commandObjects.zrevrangeByLex(key, max, min)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min), key); } @Override public List zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count) { - return executeCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count)); + return checkAndClientSideCacheCommand(commandObjects.zrevrangeByLex(key, max, min, offset, count), key); } @Override @@ -2604,22 +2604,22 @@ public long geoadd(String key, GeoAddParams params, Map m @Override public Double geodist(String key, String member1, String member2) { - return executeCommand(commandObjects.geodist(key, member1, member2)); + return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2), key); } @Override public Double geodist(String key, String member1, String member2, GeoUnit unit) { - return executeCommand(commandObjects.geodist(key, member1, member2, unit)); + return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2, unit), key); } @Override public List geohash(String key, String... members) { - return executeCommand(commandObjects.geohash(key, members)); + return checkAndClientSideCacheCommand(commandObjects.geohash(key, members), key); } @Override public List geopos(String key, String... members) { - return executeCommand(commandObjects.geopos(key, members)); + return checkAndClientSideCacheCommand(commandObjects.geopos(key, members), key); } @Override @@ -2639,22 +2639,22 @@ public long geoadd(byte[] key, GeoAddParams params, Map m @Override public Double geodist(byte[] key, byte[] member1, byte[] member2) { - return executeCommand(commandObjects.geodist(key, member1, member2)); + return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2), key); } @Override public Double geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit) { - return executeCommand(commandObjects.geodist(key, member1, member2, unit)); + return checkAndClientSideCacheCommand(commandObjects.geodist(key, member1, member2, unit), key); } @Override public List geohash(byte[] key, byte[]... members) { - return executeCommand(commandObjects.geohash(key, members)); + return checkAndClientSideCacheCommand(commandObjects.geohash(key, members), key); } @Override public List geopos(byte[] key, byte[]... members) { - return executeCommand(commandObjects.geopos(key, members)); + return checkAndClientSideCacheCommand(commandObjects.geopos(key, members), key); } @Override @@ -2664,7 +2664,7 @@ public List georadius(String key, double longitude, double la @Override public List georadiusReadonly(String key, double longitude, double latitude, double radius, GeoUnit unit) { - return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit), key); } @Override @@ -2674,7 +2674,7 @@ public List georadius(String key, double longitude, double la @Override 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)); + return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param), key); } @Override @@ -2684,7 +2684,7 @@ public List georadiusByMember(String key, String member, doub @Override public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit) { - return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit), key); } @Override @@ -2694,7 +2694,7 @@ public List georadiusByMember(String key, String member, doub @Override public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) { - return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)); + return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param), key); } @Override @@ -2709,27 +2709,27 @@ public long georadiusByMemberStore(String key, String member, double radius, Geo @Override public List geosearch(String key, String member, double radius, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, member, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, radius, unit), key); } @Override public List geosearch(String key, GeoCoordinate coord, double radius, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, coord, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, radius, unit), key); } @Override public List geosearch(String key, String member, double width, double height, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, member, width, height, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, width, height, unit), key); } @Override public List geosearch(String key, GeoCoordinate coord, double width, double height, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, coord, width, height, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, width, height, unit), key); } @Override public List geosearch(String key, GeoSearchParam params) { - return executeCommand(commandObjects.geosearch(key, params)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, params), key); } @Override @@ -2769,7 +2769,7 @@ public List georadius(byte[] key, double longitude, double la @Override public List georadiusReadonly(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) { - return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit), key); } @Override @@ -2779,7 +2779,7 @@ public List georadius(byte[] key, double longitude, double la @Override 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)); + return checkAndClientSideCacheCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param), key); } @Override @@ -2789,7 +2789,7 @@ public List georadiusByMember(byte[] key, byte[] member, doub @Override public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit) { - return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit), key); } @Override @@ -2799,7 +2799,7 @@ public List georadiusByMember(byte[] key, byte[] member, doub @Override public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) { - return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param)); + return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param), key); } @Override @@ -2814,27 +2814,27 @@ public long georadiusByMemberStore(byte[] key, byte[] member, double radius, Geo @Override public List geosearch(byte[] key, byte[] member, double radius, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, member, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, radius, unit), key); } @Override public List geosearch(byte[] key, GeoCoordinate coord, double radius, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, coord, radius, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, radius, unit), key); } @Override public List geosearch(byte[] key, byte[] member, double width, double height, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, member, width, height, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, member, width, height, unit), key); } @Override public List geosearch(byte[] key, GeoCoordinate coord, double width, double height, GeoUnit unit) { - return executeCommand(commandObjects.geosearch(key, coord, width, height, unit)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, coord, width, height, unit), key); } @Override public List geosearch(byte[] key, GeoSearchParam params) { - return executeCommand(commandObjects.geosearch(key, params)); + return checkAndClientSideCacheCommand(commandObjects.geosearch(key, params), key); } @Override @@ -2923,47 +2923,47 @@ public StreamEntryID xadd(String key, XAddParams params, Map has @Override public long xlen(String key) { - return executeCommand(commandObjects.xlen(key)); + return checkAndClientSideCacheCommand(commandObjects.xlen(key), key); } @Override public List xrange(String key, StreamEntryID start, StreamEntryID end) { - return executeCommand(commandObjects.xrange(key, start, end)); + return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end), key); } @Override public List xrange(String key, StreamEntryID start, StreamEntryID end, int count) { - return executeCommand(commandObjects.xrange(key, start, end, count)); + return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end, count), key); } @Override public List xrevrange(String key, StreamEntryID end, StreamEntryID start) { - return executeCommand(commandObjects.xrevrange(key, end, start)); + return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start), key); } @Override public List xrevrange(String key, StreamEntryID end, StreamEntryID start, int count) { - return executeCommand(commandObjects.xrevrange(key, end, start, count)); + return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start, count), key); } @Override public List xrange(String key, String start, String end) { - return executeCommand(commandObjects.xrange(key, start, end)); + return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end), key); } @Override public List xrange(String key, String start, String end, int count) { - return executeCommand(commandObjects.xrange(key, start, end, count)); + return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end, count), key); } @Override public List xrevrange(String key, String end, String start) { - return executeCommand(commandObjects.xrevrange(key, end, start)); + return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start), key); } @Override public List xrevrange(String key, String end, String start, int count) { - return executeCommand(commandObjects.xrevrange(key, end, start, count)); + return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start, count), key); } @Override @@ -2998,12 +2998,12 @@ public long xgroupDelConsumer(String key, String groupName, String consumerName) @Override public StreamPendingSummary xpending(String key, String groupName) { - return executeCommand(commandObjects.xpending(key, groupName)); + return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName), key); } @Override public List xpending(String key, String groupName, XPendingParams params) { - return executeCommand(commandObjects.xpending(key, groupName, params)); + return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName, params), key); } @Override @@ -3089,27 +3089,27 @@ public byte[] xadd(byte[] key, XAddParams params, Map hash) { @Override public long xlen(byte[] key) { - return executeCommand(commandObjects.xlen(key)); + return checkAndClientSideCacheCommand(commandObjects.xlen(key), key); } @Override public List xrange(byte[] key, byte[] start, byte[] end) { - return executeCommand(commandObjects.xrange(key, start, end)); + return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end), key); } @Override public List xrange(byte[] key, byte[] start, byte[] end, int count) { - return executeCommand(commandObjects.xrange(key, start, end, count)); + return checkAndClientSideCacheCommand(commandObjects.xrange(key, start, end, count), key); } @Override public List xrevrange(byte[] key, byte[] end, byte[] start) { - return executeCommand(commandObjects.xrevrange(key, end, start)); + return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start), key); } @Override public List xrevrange(byte[] key, byte[] end, byte[] start, int count) { - return executeCommand(commandObjects.xrevrange(key, end, start, count)); + return checkAndClientSideCacheCommand(commandObjects.xrevrange(key, end, start, count), key); } @Override @@ -3159,12 +3159,12 @@ public long xtrim(byte[] key, XTrimParams params) { @Override public Object xpending(byte[] key, byte[] groupName) { - return executeCommand(commandObjects.xpending(key, groupName)); + return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName), key); } @Override public List xpending(byte[] key, byte[] groupName, XPendingParams params) { - return executeCommand(commandObjects.xpending(key, groupName, params)); + return checkAndClientSideCacheCommand(commandObjects.xpending(key, groupName, params), key); } @Override @@ -3982,47 +3982,47 @@ public String jsonMerge(String key, Path path, Object pojo) { @Override public Object jsonGet(String key) { - return executeCommand(commandObjects.jsonGet(key)); + return checkAndClientSideCacheCommand(commandObjects.jsonGet(key), key); } @Override @Deprecated public T jsonGet(String key, Class clazz) { - return executeCommand(commandObjects.jsonGet(key, clazz)); + return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, clazz), key); } @Override public Object jsonGet(String key, Path2... paths) { - return executeCommand(commandObjects.jsonGet(key, paths)); + return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, paths), key); } @Override @Deprecated public Object jsonGet(String key, Path... paths) { - return executeCommand(commandObjects.jsonGet(key, paths)); + return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, paths), key); } @Override @Deprecated public String jsonGetAsPlainString(String key, Path path) { - return executeCommand(commandObjects.jsonGetAsPlainString(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonGetAsPlainString(key, path), key); } @Override @Deprecated public T jsonGet(String key, Class clazz, Path... paths) { - return executeCommand(commandObjects.jsonGet(key, clazz, paths)); + return checkAndClientSideCacheCommand(commandObjects.jsonGet(key, clazz, paths), key); } @Override public List jsonMGet(Path2 path, String... keys) { - return executeCommand(commandObjects.jsonMGet(path, keys)); + return checkAndClientSideCacheCommand(commandObjects.jsonMGet(path, keys), (Object[]) keys); } @Override @Deprecated public List jsonMGet(Path path, Class clazz, String... keys) { - return executeCommand(commandObjects.jsonMGet(path, clazz, keys)); + return checkAndClientSideCacheCommand(commandObjects.jsonMGet(path, clazz, keys), (Object[]) keys); } @Override @@ -4071,18 +4071,18 @@ public String jsonToggle(String key, Path path) { @Override @Deprecated public Class jsonType(String key) { - return executeCommand(commandObjects.jsonType(key)); + return checkAndClientSideCacheCommand(commandObjects.jsonType(key), key); } @Override public List> jsonType(String key, Path2 path) { - return executeCommand(commandObjects.jsonType(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonType(key, path), key); } @Override @Deprecated public Class jsonType(String key, Path path) { - return executeCommand(commandObjects.jsonType(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonType(key, path), key); } @Override @@ -4105,18 +4105,18 @@ public long jsonStrAppend(String key, Path path, Object string) { @Override @Deprecated public Long jsonStrLen(String key) { - return executeCommand(commandObjects.jsonStrLen(key)); + return checkAndClientSideCacheCommand(commandObjects.jsonStrLen(key), key); } @Override public List jsonStrLen(String key, Path2 path) { - return executeCommand(commandObjects.jsonStrLen(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonStrLen(key, path), key); } @Override @Deprecated public Long jsonStrLen(String key, Path path) { - return executeCommand(commandObjects.jsonStrLen(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonStrLen(key, path), key); } @Override @@ -4148,18 +4148,18 @@ public Long jsonArrAppend(String key, Path path, Object... pojos) { @Override public List jsonArrIndex(String key, Path2 path, Object scalar) { - return executeCommand(commandObjects.jsonArrIndex(key, path, scalar)); + return checkAndClientSideCacheCommand(commandObjects.jsonArrIndex(key, path, scalar), key); } @Override public List jsonArrIndexWithEscape(String key, Path2 path, Object scalar) { - return executeCommand(commandObjects.jsonArrIndexWithEscape(key, path, scalar)); + return checkAndClientSideCacheCommand(commandObjects.jsonArrIndexWithEscape(key, path, scalar), key); } @Override @Deprecated public long jsonArrIndex(String key, Path path, Object scalar) { - return executeCommand(commandObjects.jsonArrIndex(key, path, scalar)); + return checkAndClientSideCacheCommand(commandObjects.jsonArrIndex(key, path, scalar), key); } @Override @@ -4227,18 +4227,18 @@ public T jsonArrPop(String key, Class clazz, Path path, int index) { @Override @Deprecated public Long jsonArrLen(String key) { - return executeCommand(commandObjects.jsonArrLen(key)); + return checkAndClientSideCacheCommand(commandObjects.jsonArrLen(key), key); } @Override public List jsonArrLen(String key, Path2 path) { - return executeCommand(commandObjects.jsonArrLen(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonArrLen(key, path), key); } @Override @Deprecated public Long jsonArrLen(String key, Path path) { - return executeCommand(commandObjects.jsonArrLen(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonArrLen(key, path), key); } @Override @@ -4255,35 +4255,35 @@ public Long jsonArrTrim(String key, Path path, int start, int stop) { @Override @Deprecated public Long jsonObjLen(String key) { - return executeCommand(commandObjects.jsonObjLen(key)); + return checkAndClientSideCacheCommand(commandObjects.jsonObjLen(key), key); } @Override @Deprecated public Long jsonObjLen(String key, Path path) { - return executeCommand(commandObjects.jsonObjLen(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonObjLen(key, path), key); } @Override public List jsonObjLen(String key, Path2 path) { - return executeCommand(commandObjects.jsonObjLen(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonObjLen(key, path), key); } @Override @Deprecated public List jsonObjKeys(String key) { - return executeCommand(commandObjects.jsonObjKeys(key)); + return checkAndClientSideCacheCommand(commandObjects.jsonObjKeys(key), key); } @Override @Deprecated public List jsonObjKeys(String key, Path path) { - return executeCommand(commandObjects.jsonObjKeys(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonObjKeys(key, path), key); } @Override public List> jsonObjKeys(String key, Path2 path) { - return executeCommand(commandObjects.jsonObjKeys(key, path)); + return checkAndClientSideCacheCommand(commandObjects.jsonObjKeys(key, path), key); } @Override @@ -4367,22 +4367,22 @@ public long tsDecrBy(String key, double value, long timestamp) { @Override public List tsRange(String key, long fromTimestamp, long toTimestamp) { - return executeCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp)); + return checkAndClientSideCacheCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp), key); } @Override public List tsRange(String key, TSRangeParams rangeParams) { - return executeCommand(commandObjects.tsRange(key, rangeParams)); + return checkAndClientSideCacheCommand(commandObjects.tsRange(key, rangeParams), key); } @Override public List tsRevRange(String key, long fromTimestamp, long toTimestamp) { - return executeCommand(commandObjects.tsRevRange(key, fromTimestamp, toTimestamp)); + return checkAndClientSideCacheCommand(commandObjects.tsRevRange(key, fromTimestamp, toTimestamp), key); } @Override public List tsRevRange(String key, TSRangeParams rangeParams) { - return executeCommand(commandObjects.tsRevRange(key, rangeParams)); + return checkAndClientSideCacheCommand(commandObjects.tsRevRange(key, rangeParams), key); } @Override @@ -4407,12 +4407,12 @@ public Map tsMRevRange(TSMRangeParams multiRangeParams @Override public TSElement tsGet(String key) { - return executeCommand(commandObjects.tsGet(key)); + return checkAndClientSideCacheCommand(commandObjects.tsGet(key), key); } @Override public TSElement tsGet(String key, TSGetParams getParams) { - return executeCommand(commandObjects.tsGet(key, getParams)); + return checkAndClientSideCacheCommand(commandObjects.tsGet(key, getParams), key); } @Override @@ -4442,7 +4442,7 @@ public List tsQueryIndex(String... filters) { @Override public TSInfo tsInfo(String key) { - return executor.executeCommand(commandObjects.tsInfo(key)); + return checkAndClientSideCacheCommand(commandObjects.tsInfo(key), key); } @Override From 2480b029a16c8b006095d59c8fdb4f0d1decbd42 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:36:41 +0600 Subject: [PATCH 07/48] Support Client-side caching through URI/URL (#3703) * Support Client-side caching through URI/URL * check idx of '=' sign * nicer exception * edit/fix condition * rename param * Throw IllegalArgumentException at all such cases --- .../java/redis/clients/jedis/JedisPooled.java | 4 +- .../redis/clients/jedis/UnifiedJedis.java | 8 +- .../clients/jedis/util/JedisURIHelper.java | 89 +++++++++++++++++-- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 44e476d7b8..a5840c6530 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -26,7 +26,7 @@ public JedisPooled() { * @param url */ public JedisPooled(final String url) { - this(URI.create(url)); + super(url); } /** @@ -76,7 +76,7 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client } public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache) { - super(new PooledConnectionProvider(hostAndPort, clientConfig, csCache), clientConfig.getRedisProtocol(), csCache); + super(hostAndPort, clientConfig, csCache); } public JedisPooled(PooledObjectFactory factory) { diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index eb32a5a796..37c4316ec0 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -72,7 +72,7 @@ public UnifiedJedis(final URI uri) { this(JedisURIHelper.getHostAndPort(uri), DefaultJedisClientConfig.builder() .user(JedisURIHelper.getUser(uri)).password(JedisURIHelper.getPassword(uri)) .database(JedisURIHelper.getDBIndex(uri)).protocol(JedisURIHelper.getRedisProtocol(uri)) - .ssl(JedisURIHelper.isRedisSSLScheme(uri)).build()); + .ssl(JedisURIHelper.isRedisSSLScheme(uri)).build(), JedisURIHelper.getClientSideCache(uri)); } public UnifiedJedis(final URI uri, JedisClientConfig config) { @@ -85,13 +85,17 @@ public UnifiedJedis(final URI uri, JedisClientConfig config) { .protocol(JedisURIHelper.getRedisProtocol(uri)) .ssl(JedisURIHelper.isRedisSSLScheme(uri)).sslSocketFactory(config.getSslSocketFactory()) .sslParameters(config.getSslParameters()).hostnameVerifier(config.getHostnameVerifier()) - .build()); + .build(), JedisURIHelper.getClientSideCache(uri)); } public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) { this(new PooledConnectionProvider(hostAndPort, clientConfig), clientConfig.getRedisProtocol()); } + public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(), clientSideCache); + } + public UnifiedJedis(ConnectionProvider provider) { this(new DefaultCommandExecutor(provider), provider); } diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java index 6bbd1599a8..2d73cf04d4 100644 --- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java +++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java @@ -1,6 +1,7 @@ package redis.clients.jedis.util; import java.net.URI; +import redis.clients.jedis.ClientSideCache; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Protocol; import redis.clients.jedis.RedisProtocol; @@ -54,11 +55,12 @@ public static int getDBIndex(URI uri) { public static RedisProtocol getRedisProtocol(URI uri) { if (uri.getQuery() == null) return null; - String[] pairs = uri.getQuery().split("&"); - for (String pair : pairs) { - int idx = pair.indexOf("="); - if ("protocol".equals(pair.substring(0, idx))) { - String ver = pair.substring(idx + 1); + String[] params = uri.getQuery().split("&"); + for (String param : params) { + int idx = param.indexOf("="); + if (idx < 0) continue; + if ("protocol".equals(param.substring(0, idx))) { + String ver = param.substring(idx + 1); for (RedisProtocol proto : RedisProtocol.values()) { if (proto.version().equals(ver)) { return proto; @@ -70,6 +72,83 @@ public static RedisProtocol getRedisProtocol(URI uri) { return null; // null (default) when not defined } + private static final Integer ZERO_INTEGER = 0; + + public static ClientSideCache getClientSideCache(URI uri) { + if (uri.getQuery() == null) return null; + + boolean guava = false, caffeine = false; // cache_lib + Integer maxSize = null; // cache_max_size --> 0 = disbale + Integer ttl = null; // cache_ttl --> 0 = no ttl + // cache-max-idle + + String[] params = uri.getQuery().split("&"); + for (String param : params) { + int idx = param.indexOf("="); + if (idx < 0) continue; + + String key = param.substring(0, idx); + String val = param.substring(idx + 1); + + switch (key) { + + case "cache_lib": + switch (val) { + case "guava": + guava = true; + break; + case "caffeine": + caffeine = true; + break; + default: + throw new IllegalArgumentException("Unsupported library " + val); + } + break; + + case "cache_max_size": + try { + maxSize = Integer.parseInt(val); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Value of cache_max_size must be an integer.", nfe); + } + break; + + case "cache_ttl": + try { + ttl = Integer.parseInt(val); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Value of cache_ttl must be an integer denoting seconds.", nfe); + } + break; + } + } + + // special cases + if (ZERO_INTEGER.equals(maxSize)) { + return null; + } + if (!guava && !caffeine && (maxSize != null || ttl != null)) { + throw new IllegalArgumentException("The cache library (guava OR caffeine) must be selected."); + } + if (ZERO_INTEGER.equals(ttl)) { + ttl = null; // below, only null will be checked + } + + if (guava) { + GuavaCSC.Builder guavaBuilder = GuavaCSC.builder(); + if (maxSize != null) guavaBuilder.maximumSize(maxSize); + if (ttl != null) guavaBuilder.ttl(ttl); + return guavaBuilder.build(); + } else if (caffeine) { + CaffeineCSC.Builder caffeineBuilder = CaffeineCSC.builder(); + if (maxSize != null) caffeineBuilder.maximumSize(maxSize); + if (ttl != null) caffeineBuilder.ttl(ttl); + return caffeineBuilder.build(); + } + + return null; // null (default) when not defined + } + public static boolean isValid(URI uri) { if (isEmpty(uri.getScheme()) || isEmpty(uri.getHost()) || uri.getPort() == -1) { return false; From 26606b93f1493bb7c8b3fed724cc672ab4e13426 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:51:22 +0600 Subject: [PATCH 08/48] Test GuavaCSC and CaffeineCSC (#3742) --- .../redis/clients/jedis/util/GuavaCSC.java | 4 + .../jedis/ClientSideCacheLibsTest.java | 121 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/test/java/redis/clients/jedis/ClientSideCacheLibsTest.java diff --git a/src/main/java/redis/clients/jedis/util/GuavaCSC.java b/src/main/java/redis/clients/jedis/util/GuavaCSC.java index d9973b7dc6..934d5b15ab 100644 --- a/src/main/java/redis/clients/jedis/util/GuavaCSC.java +++ b/src/main/java/redis/clients/jedis/util/GuavaCSC.java @@ -15,6 +15,10 @@ public class GuavaCSC extends ClientSideCache { private final Cache cache; private final HashFunction function; + public GuavaCSC(Cache guavaCache) { + this(guavaCache, DEFAULT_HASH_FUNCTION); + } + public GuavaCSC(Cache guavaCache, HashFunction hashFunction) { this.cache = guavaCache; this.function = hashFunction; diff --git a/src/test/java/redis/clients/jedis/ClientSideCacheLibsTest.java b/src/test/java/redis/clients/jedis/ClientSideCacheLibsTest.java new file mode 100644 index 0000000000..f6469c09b0 --- /dev/null +++ b/src/test/java/redis/clients/jedis/ClientSideCacheLibsTest.java @@ -0,0 +1,121 @@ +package redis.clients.jedis; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.cache.CacheBuilder; + +import java.util.function.Supplier; +import net.openhft.hashing.LongHashFunction; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import redis.clients.jedis.util.CaffeineCSC; +import redis.clients.jedis.util.GuavaCSC; + +public class ClientSideCacheLibsTest { + + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + + protected Jedis control; + + @Before + public void setUp() throws Exception { + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + + private static final Supplier clientConfig + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + + private static final Supplier> singleConnectionPoolConfig + = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + @Test + public void guavaSimple() { + GuavaCSC guava = GuavaCSC.builder().maximumSize(10).ttl(10).hashFunction(com.google.common.hash.Hashing.farmHashFingerprint64()).build(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { + control.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + control.del("foo"); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void guavaMore() { + + com.google.common.cache.Cache guava = CacheBuilder.newBuilder().recordStats().build(); + + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaCSC(guava), singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertEquals(0, guava.size()); + assertEquals("bar", jedis.get("foo")); + assertEquals(1, guava.size()); + control.flushAll(); + assertEquals(1, guava.size()); + assertEquals("bar", jedis.get("foo")); + assertEquals(1, guava.size()); + jedis.ping(); + assertEquals(0, guava.size()); + assertNull(jedis.get("foo")); + assertEquals(0, guava.size()); + } + + com.google.common.cache.CacheStats stats = guava.stats(); + assertEquals(1L, stats.hitCount()); + assertThat(stats.missCount(), Matchers.greaterThan(0L)); + } + + @Test + public void caffeineSimple() { + CaffeineCSC caffeine = CaffeineCSC.builder().maximumSize(10).ttl(10).hashFunction(LongHashFunction.xx()).build(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { + control.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + control.del("foo"); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void caffeineMore() { + + com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build(); + + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineCSC(caffeine, LongHashFunction.city_1_1()), + singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertEquals(0, caffeine.estimatedSize()); + assertEquals("bar", jedis.get("foo")); + assertEquals(1, caffeine.estimatedSize()); + control.flushAll(); + assertEquals(1, caffeine.estimatedSize()); + assertEquals("bar", jedis.get("foo")); + assertEquals(1, caffeine.estimatedSize()); + jedis.ping(); + assertEquals(0, caffeine.estimatedSize()); + assertNull(jedis.get("foo")); + assertEquals(0, caffeine.estimatedSize()); + } + + com.github.benmanes.caffeine.cache.stats.CacheStats stats = caffeine.stats(); + assertEquals(1L, stats.hitCount()); + assertThat(stats.missCount(), Matchers.greaterThan(0L)); + } +} From 333dcd74013601a4aba1c6ba2c4d557461991216 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:03:12 +0600 Subject: [PATCH 09/48] Support white-list and black-list commands and keys (#3755) * Create csc package * Create csc.util package * Create a config interface for client-side caching * Default isCacheable * Config to WhiteList/BlackList commands and String keys * Create csc test package(s) * Test white-list/black-list commands and keys * Merge fix * Remove csc.util package * Fix javadoc links * Added ClientSideCacheable interface and removed ClientSideCacheConfig interface * Format imports * Re-create csc.util package * Rename to allow/deny instead of white/black --- .../java/redis/clients/jedis/Connection.java | 1 + .../clients/jedis/ConnectionFactory.java | 2 +- .../redis/clients/jedis/ConnectionPool.java | 1 + .../redis/clients/jedis/JedisCluster.java | 1 + .../clients/jedis/JedisClusterInfoCache.java | 1 + .../java/redis/clients/jedis/JedisPooled.java | 1 + .../redis/clients/jedis/JedisSentineled.java | 1 + .../java/redis/clients/jedis/Protocol.java | 1 + .../redis/clients/jedis/UnifiedJedis.java | 7 +- .../jedis/{ => csc}/ClientSideCache.java | 45 ++++--- .../jedis/csc/ClientSideCacheable.java | 8 ++ .../jedis/csc/DefaultClientSideCacheable.java | 15 +++ .../util/AllowAndDenyListWithStringKeys.java | 36 ++++++ .../jedis/{ => csc}/util/CaffeineCSC.java | 43 +++++-- .../jedis/{ => csc}/util/GuavaCSC.java | 43 +++++-- .../providers/ClusterConnectionProvider.java | 2 +- .../providers/PooledConnectionProvider.java | 2 +- .../SentineledConnectionProvider.java | 2 +- .../clients/jedis/util/JedisURIHelper.java | 6 +- .../AllowAndDenyListClientSideCacheTest.java | 117 ++++++++++++++++++ .../{ => csc}/ClientSideCacheLibsTest.java | 27 ++-- .../JedisClusterClientSideCacheTest.java | 17 ++- .../JedisPooledClientSideCacheTest.java | 18 ++- .../JedisSentineledClientSideCacheTest.java | 22 ++-- .../clients/jedis/{util => csc}/MapCSC.java | 23 ++-- 25 files changed, 359 insertions(+), 83 deletions(-) rename src/main/java/redis/clients/jedis/{ => csc}/ClientSideCache.java (64%) create mode 100644 src/main/java/redis/clients/jedis/csc/ClientSideCacheable.java create mode 100644 src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java create mode 100644 src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java rename src/main/java/redis/clients/jedis/{ => csc}/util/CaffeineCSC.java (59%) rename src/main/java/redis/clients/jedis/{ => csc}/util/GuavaCSC.java (61%) create mode 100644 src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java rename src/test/java/redis/clients/jedis/{ => csc}/ClientSideCacheLibsTest.java (83%) rename src/test/java/redis/clients/jedis/{ => csc}/JedisClusterClientSideCacheTest.java (85%) rename src/test/java/redis/clients/jedis/{ => csc}/JedisPooledClientSideCacheTest.java (85%) rename src/test/java/redis/clients/jedis/{ => csc}/JedisSentineledClientSideCacheTest.java (83%) rename src/test/java/redis/clients/jedis/{util => csc}/MapCSC.java (60%) diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index bff5898f8d..b2d2aac27e 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -18,6 +18,7 @@ import redis.clients.jedis.args.ClientAttributeOption; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.exceptions.JedisException; diff --git a/src/main/java/redis/clients/jedis/ConnectionFactory.java b/src/main/java/redis/clients/jedis/ConnectionFactory.java index 5b43606205..ecfceb77ef 100644 --- a/src/main/java/redis/clients/jedis/ConnectionFactory.java +++ b/src/main/java/redis/clients/jedis/ConnectionFactory.java @@ -6,7 +6,7 @@ import org.apache.commons.pool2.impl.DefaultPooledObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisException; /** diff --git a/src/main/java/redis/clients/jedis/ConnectionPool.java b/src/main/java/redis/clients/jedis/ConnectionPool.java index 70202deeae..59f416649c 100644 --- a/src/main/java/redis/clients/jedis/ConnectionPool.java +++ b/src/main/java/redis/clients/jedis/ConnectionPool.java @@ -2,6 +2,7 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.util.Pool; public class ConnectionPool extends Pool { diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index 0138058d66..72a9495617 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -7,6 +7,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.providers.ClusterConnectionProvider; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.util.JedisClusterCRC16; public class JedisCluster extends UnifiedJedis { diff --git a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java index 5646dbfb59..482fe12cb5 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java +++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisClusterOperationException; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.util.SafeEncoder; diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index a5840c6530..4bda1d1ebb 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -7,6 +7,7 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index f1cb8ea650..164c665d3f 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -2,6 +2,7 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.providers.SentineledConnectionProvider; public class JedisSentineled extends UnifiedJedis { diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 4bd82fec1e..dc84974db7 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -11,6 +11,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.util.KeyValue; import redis.clients.jedis.util.RedisInputStream; import redis.clients.jedis.util.RedisOutputStream; diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 37c4316ec0..57fea68497 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -17,6 +17,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.exceptions.JedisException; import redis.clients.jedis.executors.*; import redis.clients.jedis.gears.TFunctionListParams; @@ -300,11 +301,11 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co } private T checkAndClientSideCacheCommand(CommandObject command, Object... keys) { - if (clientSideCache == null) { - return executeCommand(command); + if (clientSideCache != null) { + return clientSideCache.get((cmd) -> executeCommand(cmd), command, keys); } - return clientSideCache.getValue((cmd) -> executeCommand(cmd), command, keys); + return executeCommand(command); } public String ping() { diff --git a/src/main/java/redis/clients/jedis/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java similarity index 64% rename from src/main/java/redis/clients/jedis/ClientSideCache.java rename to src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 389b795814..22509e4e2c 100644 --- a/src/main/java/redis/clients/jedis/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -1,4 +1,4 @@ -package redis.clients.jedis; +package redis.clients.jedis.csc; import java.nio.ByteBuffer; import java.util.HashSet; @@ -6,41 +6,46 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - import java.util.function.Function; +import redis.clients.jedis.CommandObject; 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.util.CaffeineCSC CaffeineCSC} or - * {@link redis.clients.jedis.util.GuavaCSC GuavaCSC} or a custom implementation of their own. + * object; e.g. {@link redis.clients.jedis.csc.util.CaffeineCSC CaffeineCSC} or + * {@link redis.clients.jedis.csc.util.GuavaCSC GuavaCSC} or a custom implementation of their own. */ public abstract class ClientSideCache { protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; protected static final int DEFAULT_EXPIRE_SECONDS = 100; - private final Map> keyToCommandHashes; + private final Map> keyToCommandHashes = new ConcurrentHashMap<>(); + private final ClientSideCacheable cacheable; protected ClientSideCache() { - this.keyToCommandHashes = new ConcurrentHashMap<>(); + this.cacheable = DefaultClientSideCacheable.INSTANCE; + } + + protected ClientSideCache(ClientSideCacheable cacheable) { + this.cacheable = cacheable; } - protected abstract void invalidateAllCommandHashes(); + protected abstract void invalidateAllHashes(); - protected abstract void invalidateCommandHashes(Iterable hashes); + protected abstract void invalidateHashes(Iterable hashes); - protected abstract void put(long hash, Object value); + protected abstract void putValue(long hash, Object value); - protected abstract Object get(long hash); + protected abstract Object getValue(long hash); - protected abstract long getCommandHash(CommandObject command); + protected abstract long getHash(CommandObject command); public final void clear() { invalidateAllKeysAndCommandHashes(); } - final void invalidate(List list) { + public final void invalidate(List list) { if (list == null) { invalidateAllKeysAndCommandHashes(); return; @@ -50,7 +55,7 @@ final void invalidate(List list) { } private void invalidateAllKeysAndCommandHashes() { - invalidateAllCommandHashes(); + invalidateAllHashes(); keyToCommandHashes.clear(); } @@ -63,23 +68,27 @@ private void invalidateKeyAndRespectiveCommandHashes(Object key) { Set hashes = keyToCommandHashes.get(mapKey); if (hashes != null) { - invalidateCommandHashes(hashes); + invalidateHashes(hashes); keyToCommandHashes.remove(mapKey); } } - final T getValue(Function, T> loader, CommandObject command, Object... keys) { + public final T get(Function, T> loader, CommandObject command, Object... keys) { + + if (!cacheable.isCacheable(command.getArguments().getCommand(), keys)) { + return loader.apply(command); + } - final long hash = getCommandHash(command); + final long hash = getHash(command); - T value = (T) get(hash); + T value = (T) getValue(hash); if (value != null) { return value; } value = loader.apply(command); if (value != null) { - put(hash, value); + putValue(hash, value); for (Object key : keys) { ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key); if (keyToCommandHashes.containsKey(mapKey)) { diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCacheable.java b/src/main/java/redis/clients/jedis/csc/ClientSideCacheable.java new file mode 100644 index 0000000000..d3c782ad00 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCacheable.java @@ -0,0 +1,8 @@ +package redis.clients.jedis.csc; + +import redis.clients.jedis.commands.ProtocolCommand; + +public interface ClientSideCacheable { + + boolean isCacheable(ProtocolCommand command, Object... keys); +} diff --git a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java new file mode 100644 index 0000000000..d97ca6a2ee --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java @@ -0,0 +1,15 @@ +package redis.clients.jedis.csc; + +import redis.clients.jedis.commands.ProtocolCommand; + +public class DefaultClientSideCacheable implements ClientSideCacheable { + + public static final DefaultClientSideCacheable INSTANCE = new DefaultClientSideCacheable(); + + public DefaultClientSideCacheable() { } + + @Override + public boolean isCacheable(ProtocolCommand command, Object... keys) { + return true; + } +} diff --git a/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java b/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java new file mode 100644 index 0000000000..e9adbea37c --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java @@ -0,0 +1,36 @@ +package redis.clients.jedis.csc.util; + +import java.util.Set; +import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.csc.ClientSideCacheable; + +public class AllowAndDenyListWithStringKeys implements ClientSideCacheable { + + private final Set allowCommands; + private final Set denyCommands; + + private final Set allowKeys; + private final Set denyKeys; + + public AllowAndDenyListWithStringKeys(Set allowCommands, Set denyCommands, + Set allowKeys, Set denyKeys) { + this.allowCommands = allowCommands; + this.denyCommands = denyCommands; + this.allowKeys = allowKeys; + this.denyKeys = denyKeys; + } + + @Override + public boolean isCacheable(ProtocolCommand command, Object... keys) { + if (allowCommands != null && !allowCommands.contains(command)) return false; + if (denyCommands != null && denyCommands.contains(command)) return false; + + for (Object key : keys) { + if (!(key instanceof String)) return false; + if (allowKeys != null && !allowKeys.contains((String) key)) return false; + if (denyKeys != null && denyKeys.contains((String) key)) return false; + } + + return true; + } +} diff --git a/src/main/java/redis/clients/jedis/util/CaffeineCSC.java b/src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java similarity index 59% rename from src/main/java/redis/clients/jedis/util/CaffeineCSC.java rename to src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java index 3bce3504b3..4362169550 100644 --- a/src/main/java/redis/clients/jedis/util/CaffeineCSC.java +++ b/src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java @@ -1,54 +1,64 @@ -package redis.clients.jedis.util; +package redis.clients.jedis.csc.util; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; import net.openhft.hashing.LongHashFunction; -import redis.clients.jedis.ClientSideCache; + import redis.clients.jedis.CommandObject; import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.ClientSideCacheable; +import redis.clients.jedis.csc.DefaultClientSideCacheable; public class CaffeineCSC extends ClientSideCache { private static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3(); private final Cache cache; - private final LongHashFunction function; + private final LongHashFunction hashFunction; public CaffeineCSC(Cache caffeineCache, LongHashFunction hashFunction) { + super(); + this.cache = caffeineCache; + this.hashFunction = hashFunction; + } + + public CaffeineCSC(Cache caffeineCache, LongHashFunction function, ClientSideCacheable cacheable) { + super(cacheable); this.cache = caffeineCache; - this.function = hashFunction; + this.hashFunction = function; } @Override - protected final void invalidateAllCommandHashes() { + protected final void invalidateAllHashes() { cache.invalidateAll(); } @Override - protected void invalidateCommandHashes(Iterable hashes) { + protected void invalidateHashes(Iterable hashes) { cache.invalidateAll(hashes); } @Override - protected void put(long hash, Object value) { + protected void putValue(long hash, Object value) { cache.put(hash, value); } @Override - protected Object get(long hash) { + protected Object getValue(long hash) { return cache.getIfPresent(hash); } @Override - protected final long getCommandHash(CommandObject command) { + protected final long getHash(CommandObject command) { long[] nums = new long[command.getArguments().size() + 1]; int idx = 0; for (Rawable raw : command.getArguments()) { - nums[idx++] = function.hashBytes(raw.getRaw()); + nums[idx++] = hashFunction.hashBytes(raw.getRaw()); } - nums[idx] = function.hashInt(command.getBuilder().hashCode()); - return function.hashLongs(nums); + nums[idx] = hashFunction.hashInt(command.getBuilder().hashCode()); + return hashFunction.hashLongs(nums); } public static Builder builder() { @@ -63,6 +73,8 @@ public static class Builder { private LongHashFunction hashFunction = DEFAULT_HASH_FUNCTION; + private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; + private Builder() { } public Builder maximumSize(int size) { @@ -80,6 +92,11 @@ public Builder hashFunction(LongHashFunction function) { return this; } + public Builder cacheable(ClientSideCacheable cacheable) { + this.cacheable = cacheable; + return this; + } + public CaffeineCSC build() { Caffeine cb = Caffeine.newBuilder(); @@ -87,7 +104,7 @@ public CaffeineCSC build() { cb.expireAfterWrite(expireTime, expireTimeUnit); - return new CaffeineCSC(cb.build(), hashFunction); + return new CaffeineCSC(cb.build(), hashFunction, cacheable); } } } diff --git a/src/main/java/redis/clients/jedis/util/GuavaCSC.java b/src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java similarity index 61% rename from src/main/java/redis/clients/jedis/util/GuavaCSC.java rename to src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java index 934d5b15ab..e1c9e4f434 100644 --- a/src/main/java/redis/clients/jedis/util/GuavaCSC.java +++ b/src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java @@ -1,52 +1,66 @@ -package redis.clients.jedis.util; +package redis.clients.jedis.csc.util; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import java.util.concurrent.TimeUnit; -import redis.clients.jedis.ClientSideCache; + import redis.clients.jedis.CommandObject; +import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.ClientSideCacheable; +import redis.clients.jedis.csc.DefaultClientSideCacheable; public class GuavaCSC extends ClientSideCache { private static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011(); private final Cache cache; - private final HashFunction function; + private final HashFunction hashFunction; public GuavaCSC(Cache guavaCache) { this(guavaCache, DEFAULT_HASH_FUNCTION); } public GuavaCSC(Cache guavaCache, HashFunction hashFunction) { + super(); this.cache = guavaCache; - this.function = hashFunction; + this.hashFunction = hashFunction; + } + + public GuavaCSC(Cache guavaCache, ClientSideCacheable cacheable) { + this(guavaCache, DEFAULT_HASH_FUNCTION, cacheable); + } + + public GuavaCSC(Cache cache, HashFunction function, ClientSideCacheable cacheable) { + super(cacheable); + this.cache = cache; + this.hashFunction = function; } @Override - protected final void invalidateAllCommandHashes() { + protected final void invalidateAllHashes() { cache.invalidateAll(); } @Override - protected void invalidateCommandHashes(Iterable hashes) { + protected void invalidateHashes(Iterable hashes) { cache.invalidateAll(hashes); } @Override - protected void put(long hash, Object value) { + protected void putValue(long hash, Object value) { cache.put(hash, value); } @Override - protected Object get(long hash) { + protected Object getValue(long hash) { return cache.getIfPresent(hash); } @Override - protected final long getCommandHash(CommandObject command) { - Hasher hasher = function.newHasher(); + protected final long getHash(CommandObject command) { + Hasher hasher = hashFunction.newHasher(); command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw())); hasher.putInt(command.getBuilder().hashCode()); return hasher.hash().asLong(); @@ -64,6 +78,8 @@ public static class Builder { private HashFunction hashFunction = DEFAULT_HASH_FUNCTION; + private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; + private Builder() { } public Builder maximumSize(int size) { @@ -81,6 +97,11 @@ public Builder hashFunction(HashFunction function) { return this; } + public Builder cacheable(ClientSideCacheable cacheable) { + this.cacheable = cacheable; + return this; + } + public GuavaCSC build() { CacheBuilder cb = CacheBuilder.newBuilder(); @@ -88,7 +109,7 @@ public GuavaCSC build() { cb.expireAfterWrite(expireTime, expireTimeUnit); - return new GuavaCSC(cb.build(), hashFunction); + return new GuavaCSC(cb.build(), hashFunction, cacheable); } } } diff --git a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java index be83aa7388..6b148a5e77 100644 --- a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java @@ -8,7 +8,6 @@ import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import redis.clients.jedis.ClientSideCache; import redis.clients.jedis.ClusterCommandArguments; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.HostAndPort; @@ -16,6 +15,7 @@ import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPool; import redis.clients.jedis.JedisClusterInfoCache; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisClusterOperationException; import redis.clients.jedis.exceptions.JedisException; diff --git a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java index 85fa3cecd2..0d85fabad2 100644 --- a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java @@ -5,13 +5,13 @@ import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import redis.clients.jedis.ClientSideCache; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionFactory; import redis.clients.jedis.ConnectionPool; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisClientConfig; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.util.Pool; public class PooledConnectionProvider implements ConnectionProvider { diff --git a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java index e335803b62..1d7c5790c2 100644 --- a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java @@ -10,7 +10,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import redis.clients.jedis.ClientSideCache; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPool; @@ -18,6 +17,7 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.util.IOUtils; diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java index 2d73cf04d4..e8e62ae9b5 100644 --- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java +++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java @@ -1,11 +1,15 @@ package redis.clients.jedis.util; import java.net.URI; -import redis.clients.jedis.ClientSideCache; + import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Protocol; import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.csc.ClientSideCache; +import redis.clients.jedis.csc.util.GuavaCSC; +import redis.clients.jedis.csc.util.CaffeineCSC; + public final class JedisURIHelper { private static final String REDIS = "redis"; diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java new file mode 100644 index 0000000000..86c6dbc42f --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java @@ -0,0 +1,117 @@ +package redis.clients.jedis.csc; + +import static java.util.Collections.singleton; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.function.Supplier; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +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; +import redis.clients.jedis.Protocol; +import redis.clients.jedis.csc.util.AllowAndDenyListWithStringKeys; + +public class AllowAndDenyListClientSideCacheTest { + + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + + protected Jedis control; + + @Before + public void setUp() throws Exception { + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + + private static final Supplier clientConfig + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + + private static final Supplier> singleConnectionPoolConfig + = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + @Test + public void none() { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + new MapCSC(map, new AllowAndDenyListWithStringKeys(null, null, null, null)), + singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + } + } + + @Test + public void whiteListCommand() { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + new MapCSC(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)), + singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + } + } + + @Test + public void blackListCommand() { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + new MapCSC(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)), + singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } + + @Test + public void whiteListKey() { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + new MapCSC(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)), + singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + } + } + + @Test + public void blackListKey() { + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + new MapCSC(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))), + singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } +} diff --git a/src/test/java/redis/clients/jedis/ClientSideCacheLibsTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java similarity index 83% rename from src/test/java/redis/clients/jedis/ClientSideCacheLibsTest.java rename to src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java index f6469c09b0..31589458f8 100644 --- a/src/test/java/redis/clients/jedis/ClientSideCacheLibsTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java @@ -1,4 +1,4 @@ -package redis.clients.jedis; +package redis.clients.jedis.csc; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -6,18 +6,25 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.cache.CacheBuilder; - import java.util.function.Supplier; import net.openhft.hashing.LongHashFunction; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; -import redis.clients.jedis.util.CaffeineCSC; -import redis.clients.jedis.util.GuavaCSC; +import redis.clients.jedis.Connection; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +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; +import redis.clients.jedis.csc.util.CaffeineCSC; +import redis.clients.jedis.csc.util.GuavaCSC; public class ClientSideCacheLibsTest { @@ -48,7 +55,8 @@ public void tearDown() throws Exception { @Test public void guavaSimple() { - GuavaCSC guava = GuavaCSC.builder().maximumSize(10).ttl(10).hashFunction(com.google.common.hash.Hashing.farmHashFingerprint64()).build(); + GuavaCSC guava = GuavaCSC.builder().maximumSize(10).ttl(10) + .hashFunction(com.google.common.hash.Hashing.farmHashFingerprint64()).build(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -62,7 +70,8 @@ public void guavaMore() { com.google.common.cache.Cache guava = CacheBuilder.newBuilder().recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaCSC(guava), singleConnectionPoolConfig.get())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaCSC(guava), + singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertEquals(0, guava.size()); assertEquals("bar", jedis.get("foo")); @@ -98,8 +107,8 @@ public void caffeineMore() { com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineCSC(caffeine, LongHashFunction.city_1_1()), - singleConnectionPoolConfig.get())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + new CaffeineCSC(caffeine, LongHashFunction.city_1_1()), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertEquals(0, caffeine.estimatedSize()); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java similarity index 85% rename from src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java rename to src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index 60ddf002d7..3b52072560 100644 --- a/src/test/java/redis/clients/jedis/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -1,4 +1,4 @@ -package redis.clients.jedis; +package redis.clients.jedis.csc; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -13,7 +13,14 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; import org.junit.Test; -import redis.clients.jedis.util.MapCSC; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.JedisClientConfig; +import redis.clients.jedis.JedisCluster; +import redis.clients.jedis.JedisClusterTestBase; public class JedisClusterClientSideCacheTest extends JedisClusterTestBase { @@ -42,7 +49,8 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), + singleConnectionPoolConfig.get())) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); @@ -71,7 +79,8 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), + singleConnectionPoolConfig.get())) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java similarity index 85% rename from src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java rename to src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 2e641e0f3a..149b1730a0 100644 --- a/src/test/java/redis/clients/jedis/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -1,4 +1,4 @@ -package redis.clients.jedis; +package redis.clients.jedis.csc; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -11,7 +11,15 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import redis.clients.jedis.util.MapCSC; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +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 { @@ -53,7 +61,8 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), + singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); @@ -82,7 +91,8 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), singleConnectionPoolConfig.get())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), + singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java similarity index 83% rename from src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java rename to src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index 9e5f720933..6390ab3194 100644 --- a/src/test/java/redis/clients/jedis/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -1,4 +1,4 @@ -package redis.clients.jedis; +package redis.clients.jedis.csc; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -8,10 +8,14 @@ import java.util.HashMap; import java.util.Set; import java.util.stream.Collectors; - import org.hamcrest.Matchers; import org.junit.Test; -import redis.clients.jedis.util.MapCSC; + +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.JedisClientConfig; +import redis.clients.jedis.JedisSentineled; public class JedisSentineledClientSideCacheTest { @@ -28,7 +32,8 @@ public class JedisSentineledClientSideCacheTest { @Test public void simple() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), sentinels, sentinelClientConfig)) { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), + sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.del("foo"); @@ -39,7 +44,8 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), sentinels, sentinelClientConfig)) { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), + sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); @@ -57,7 +63,8 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), sentinels, sentinelClientConfig)) { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), + sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.flushAll(); @@ -68,7 +75,8 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), sentinels, sentinelClientConfig)) { + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), + sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/util/MapCSC.java b/src/test/java/redis/clients/jedis/csc/MapCSC.java similarity index 60% rename from src/test/java/redis/clients/jedis/util/MapCSC.java rename to src/test/java/redis/clients/jedis/csc/MapCSC.java index eb229036ea..ee08efb588 100644 --- a/src/test/java/redis/clients/jedis/util/MapCSC.java +++ b/src/test/java/redis/clients/jedis/csc/MapCSC.java @@ -1,9 +1,9 @@ -package redis.clients.jedis.util; +package redis.clients.jedis.csc; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import redis.clients.jedis.ClientSideCache; + import redis.clients.jedis.CommandObject; import redis.clients.jedis.args.Rawable; @@ -12,35 +12,40 @@ public class MapCSC extends ClientSideCache { private final Map cache; public MapCSC() { - this(new ConcurrentHashMap<>()); + this(new HashMap<>()); } public MapCSC(Map map) { this.cache = map; } + public MapCSC(Map cache, ClientSideCacheable cacheable) { + super(cacheable); + this.cache = cache; + } + @Override - protected final void invalidateAllCommandHashes() { + protected final void invalidateAllHashes() { cache.clear(); } @Override - protected void invalidateCommandHashes(Iterable hashes) { + protected void invalidateHashes(Iterable hashes) { hashes.forEach(hash -> cache.remove(hash)); } @Override - protected void put(long hash, Object value) { + protected void putValue(long hash, Object value) { cache.put(hash, value); } @Override - protected Object get(long hash) { + protected Object getValue(long hash) { return cache.get(hash); } @Override - protected final long getCommandHash(CommandObject command) { + protected final long getHash(CommandObject command) { long result = 1; for (Rawable raw : command.getArguments()) { result = 31 * result + Arrays.hashCode(raw.getRaw()); From e66f498e04da8a0f817dbf467c73c61dbb32f89b Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:51:33 +0600 Subject: [PATCH 10/48] Introduce interface(s) for hashing CommandObject (#3743) --- .../jedis/csc/{util => }/CaffeineCSC.java | 50 +++++++----------- .../clients/jedis/csc/ClientSideCache.java | 17 ++++--- .../jedis/csc/{util => }/GuavaCSC.java | 51 +++++++++---------- .../csc/hash/AbstractCommandHashing.java | 27 ++++++++++ .../jedis/csc/hash/CommandLongHashing.java | 16 ++++++ .../clients/jedis/csc/hash/GuavaHashing.java | 24 +++++++++ .../jedis/csc/hash/OpenHftHashing.java | 29 +++++++++++ .../jedis/csc/hash/PrimitiveArrayHashing.java | 23 +++++++++ .../clients/jedis/util/JedisURIHelper.java | 6 +-- .../jedis/csc/ClientSideCacheLibsTest.java | 10 ++-- .../java/redis/clients/jedis/csc/MapCSC.java | 26 ++++++---- 11 files changed, 194 insertions(+), 85 deletions(-) rename src/main/java/redis/clients/jedis/csc/{util => }/CaffeineCSC.java (52%) rename src/main/java/redis/clients/jedis/csc/{util => }/GuavaCSC.java (62%) create mode 100644 src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java diff --git a/src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java b/src/main/java/redis/clients/jedis/csc/CaffeineCSC.java similarity index 52% rename from src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java rename to src/main/java/redis/clients/jedis/csc/CaffeineCSC.java index 4362169550..82f4f0f9a8 100644 --- a/src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineCSC.java @@ -1,33 +1,27 @@ -package redis.clients.jedis.csc.util; +package redis.clients.jedis.csc; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; -import net.openhft.hashing.LongHashFunction; -import redis.clients.jedis.CommandObject; -import redis.clients.jedis.args.Rawable; -import redis.clients.jedis.csc.ClientSideCache; -import redis.clients.jedis.csc.ClientSideCacheable; -import redis.clients.jedis.csc.DefaultClientSideCacheable; +import redis.clients.jedis.csc.hash.CommandLongHashing; +import redis.clients.jedis.csc.hash.OpenHftHashing; public class CaffeineCSC extends ClientSideCache { - private static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3(); - private final Cache cache; - private final LongHashFunction hashFunction; - public CaffeineCSC(Cache caffeineCache, LongHashFunction hashFunction) { - super(); - this.cache = caffeineCache; - this.hashFunction = hashFunction; + public CaffeineCSC(Cache caffeineCache) { + this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), DefaultClientSideCacheable.INSTANCE); + } + + public CaffeineCSC(Cache caffeineCache, ClientSideCacheable cacheable) { + this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), cacheable); } - public CaffeineCSC(Cache caffeineCache, LongHashFunction function, ClientSideCacheable cacheable) { - super(cacheable); + public CaffeineCSC(Cache caffeineCache, CommandLongHashing hashing, ClientSideCacheable cacheable) { + super(hashing, cacheable); this.cache = caffeineCache; - this.hashFunction = function; } @Override @@ -50,17 +44,6 @@ protected Object getValue(long hash) { return cache.getIfPresent(hash); } - @Override - protected final long getHash(CommandObject command) { - long[] nums = new long[command.getArguments().size() + 1]; - int idx = 0; - for (Rawable raw : command.getArguments()) { - nums[idx++] = hashFunction.hashBytes(raw.getRaw()); - } - nums[idx] = hashFunction.hashInt(command.getBuilder().hashCode()); - return hashFunction.hashLongs(nums); - } - public static Builder builder() { return new Builder(); } @@ -71,7 +54,8 @@ public static class Builder { private long expireTime = DEFAULT_EXPIRE_SECONDS; private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; - private LongHashFunction hashFunction = DEFAULT_HASH_FUNCTION; + // not using a default value to avoid an object creation like 'new OpenHftHashing(hashFunction)' + private CommandLongHashing longHashing = null; private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; @@ -87,8 +71,8 @@ public Builder ttl(int seconds) { return this; } - public Builder hashFunction(LongHashFunction function) { - this.hashFunction = function; + public Builder hashing(CommandLongHashing hashing) { + this.longHashing = hashing; return this; } @@ -104,7 +88,9 @@ public CaffeineCSC build() { cb.expireAfterWrite(expireTime, expireTimeUnit); - return new CaffeineCSC(cb.build(), hashFunction, cacheable); + return longHashing != null + ? new CaffeineCSC(cb.build(), longHashing, cacheable) + : new CaffeineCSC(cb.build(), cacheable); } } } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 22509e4e2c..ef6b833a60 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -8,12 +8,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import redis.clients.jedis.CommandObject; +import redis.clients.jedis.csc.hash.CommandLongHashing; 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.util.CaffeineCSC CaffeineCSC} or - * {@link redis.clients.jedis.csc.util.GuavaCSC GuavaCSC} or a custom implementation of their own. + * object; e.g. {@link redis.clients.jedis.csc.CaffeineCSC CaffeineCSC} or + * {@link redis.clients.jedis.csc.GuavaCSC GuavaCSC} or a custom implementation of their own. */ public abstract class ClientSideCache { @@ -21,13 +22,15 @@ public abstract class ClientSideCache { protected static final int DEFAULT_EXPIRE_SECONDS = 100; private final Map> keyToCommandHashes = new ConcurrentHashMap<>(); + private final CommandLongHashing commandHashing; private final ClientSideCacheable cacheable; - protected ClientSideCache() { - this.cacheable = DefaultClientSideCacheable.INSTANCE; + protected ClientSideCache(CommandLongHashing commandHashing) { + this(commandHashing, DefaultClientSideCacheable.INSTANCE); } - protected ClientSideCache(ClientSideCacheable cacheable) { + protected ClientSideCache(CommandLongHashing commandHashing, ClientSideCacheable cacheable) { + this.commandHashing = commandHashing; this.cacheable = cacheable; } @@ -39,8 +42,6 @@ protected ClientSideCache(ClientSideCacheable cacheable) { protected abstract Object getValue(long hash); - protected abstract long getHash(CommandObject command); - public final void clear() { invalidateAllKeysAndCommandHashes(); } @@ -79,7 +80,7 @@ public final T get(Function, T> loader, CommandObject co return loader.apply(command); } - final long hash = getHash(command); + final long hash = commandHashing.hash(command); T value = (T) getValue(hash); if (value != null) { diff --git a/src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java b/src/main/java/redis/clients/jedis/csc/GuavaCSC.java similarity index 62% rename from src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java rename to src/main/java/redis/clients/jedis/csc/GuavaCSC.java index e1c9e4f434..c5c173f5d8 100644 --- a/src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaCSC.java @@ -1,41 +1,37 @@ -package redis.clients.jedis.csc.util; +package redis.clients.jedis.csc; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.hash.HashFunction; -import com.google.common.hash.Hasher; import java.util.concurrent.TimeUnit; -import redis.clients.jedis.CommandObject; -import redis.clients.jedis.csc.ClientSideCache; -import redis.clients.jedis.csc.ClientSideCacheable; -import redis.clients.jedis.csc.DefaultClientSideCacheable; +import redis.clients.jedis.csc.hash.CommandLongHashing; +import redis.clients.jedis.csc.hash.GuavaHashing; public class GuavaCSC extends ClientSideCache { - private static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011(); - private final Cache cache; - private final HashFunction hashFunction; public GuavaCSC(Cache guavaCache) { - this(guavaCache, DEFAULT_HASH_FUNCTION); + this(guavaCache, GuavaHashing.DEFAULT_HASH_FUNCTION); } public GuavaCSC(Cache guavaCache, HashFunction hashFunction) { - super(); + this(guavaCache, new GuavaHashing(hashFunction)); + } + + public GuavaCSC(Cache guavaCache, CommandLongHashing hashing) { + super(hashing); this.cache = guavaCache; - this.hashFunction = hashFunction; } public GuavaCSC(Cache guavaCache, ClientSideCacheable cacheable) { - this(guavaCache, DEFAULT_HASH_FUNCTION, cacheable); + this(guavaCache, new GuavaHashing(GuavaHashing.DEFAULT_HASH_FUNCTION), cacheable); } - public GuavaCSC(Cache cache, HashFunction function, ClientSideCacheable cacheable) { - super(cacheable); + public GuavaCSC(Cache cache, CommandLongHashing hashing, ClientSideCacheable cacheable) { + super(hashing, cacheable); this.cache = cache; - this.hashFunction = function; } @Override @@ -58,14 +54,6 @@ protected Object getValue(long hash) { return cache.getIfPresent(hash); } - @Override - protected final long getHash(CommandObject command) { - Hasher hasher = hashFunction.newHasher(); - command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw())); - hasher.putInt(command.getBuilder().hashCode()); - return hasher.hash().asLong(); - } - public static Builder builder() { return new Builder(); } @@ -76,7 +64,9 @@ public static class Builder { private long expireTime = DEFAULT_EXPIRE_SECONDS; private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; - private HashFunction hashFunction = DEFAULT_HASH_FUNCTION; + // not using a default value to avoid an object creation like 'new GuavaHashing(hashFunction)' + private HashFunction hashFunction = null; + private CommandLongHashing longHashing = null; private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; @@ -94,6 +84,13 @@ public Builder ttl(int seconds) { public Builder hashFunction(HashFunction function) { this.hashFunction = function; + this.longHashing = null; + return this; + } + + public Builder hashing(CommandLongHashing hashing) { + this.longHashing = hashing; + this.hashFunction = null; return this; } @@ -109,7 +106,9 @@ public GuavaCSC build() { cb.expireAfterWrite(expireTime, expireTimeUnit); - return new GuavaCSC(cb.build(), hashFunction, cacheable); + return longHashing != null ? new GuavaCSC(cb.build(), longHashing, cacheable) + : hashFunction != null ? new GuavaCSC(cb.build(), new GuavaHashing(hashFunction), cacheable) + : new GuavaCSC(cb.build(), cacheable); } } } diff --git a/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java b/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java new file mode 100644 index 0000000000..561217e299 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java @@ -0,0 +1,27 @@ +package redis.clients.jedis.csc.hash; + +import redis.clients.jedis.Builder; +import redis.clients.jedis.CommandObject; +import redis.clients.jedis.args.Rawable; + +public abstract class AbstractCommandHashing implements CommandLongHashing { + + @Override + public final long hash(CommandObject command) { + long[] nums = new long[command.getArguments().size() + 1]; + int idx = 0; + for (Rawable raw : command.getArguments()) { + nums[idx++] = hashRawable(raw); + } + nums[idx] = hashBuilder(command.getBuilder()); + return hashLongs(nums); + } + + protected abstract long hashLongs(long[] longs); + + protected abstract long hashRawable(Rawable raw); + + protected long hashBuilder(Builder builder) { + return builder.hashCode(); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java new file mode 100644 index 0000000000..6632f46e72 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java @@ -0,0 +1,16 @@ +package redis.clients.jedis.csc.hash; + +import redis.clients.jedis.CommandObject; + +/** + * The interface for hashing a command object for client-side caching. + */ +public interface CommandLongHashing { + + /** + * Produce a 64-bit signed hash from a command object. + * @param command the command object + * @return 64-bit signed hash + */ + long hash(CommandObject command); +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java b/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java new file mode 100644 index 0000000000..2b9ad6ff5a --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java @@ -0,0 +1,24 @@ +package redis.clients.jedis.csc.hash; + +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hasher; +import redis.clients.jedis.CommandObject; + +public class GuavaHashing implements CommandLongHashing { + + public static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011(); + + private final HashFunction function; + + public GuavaHashing(HashFunction function) { + this.function = function; + } + + @Override + public long hash(CommandObject command) { + Hasher hasher = function.newHasher(); + command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw())); + hasher.putInt(command.getBuilder().hashCode()); + return hasher.hash().asLong(); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java b/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java new file mode 100644 index 0000000000..b112980fd4 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java @@ -0,0 +1,29 @@ +package redis.clients.jedis.csc.hash; + +import net.openhft.hashing.LongHashFunction; + +public class OpenHftHashing extends PrimitiveArrayHashing implements CommandLongHashing { + + public static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3(); + + private final LongHashFunction function; + + public OpenHftHashing(LongHashFunction function) { + this.function = function; + } + + @Override + protected long hashLongs(long[] longs) { + return function.hashLongs(longs); + } + + @Override + protected long hashBytes(byte[] bytes) { + return function.hashBytes(bytes); + } + + @Override + protected long hashInt(int hashCode) { + return function.hashInt(hashCode); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java b/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java new file mode 100644 index 0000000000..385f97fdd2 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java @@ -0,0 +1,23 @@ +package redis.clients.jedis.csc.hash; + +import redis.clients.jedis.Builder; +import redis.clients.jedis.args.Rawable; + +public abstract class PrimitiveArrayHashing extends AbstractCommandHashing { + + @Override + protected final long hashRawable(Rawable raw) { + return hashBytes(raw.getRaw()); + } + + @Override + protected final long hashBuilder(Builder builder) { + return hashInt(builder.hashCode()); + } + + protected abstract long hashBytes(byte[] bytes); + + protected long hashInt(int hashCode) { + return hashCode; + } +} diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java index e8e62ae9b5..6abedd05c4 100644 --- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java +++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java @@ -1,14 +1,12 @@ package redis.clients.jedis.util; import java.net.URI; - import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Protocol; import redis.clients.jedis.RedisProtocol; - +import redis.clients.jedis.csc.CaffeineCSC; import redis.clients.jedis.csc.ClientSideCache; -import redis.clients.jedis.csc.util.GuavaCSC; -import redis.clients.jedis.csc.util.CaffeineCSC; +import redis.clients.jedis.csc.GuavaCSC; public final class JedisURIHelper { diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java index 31589458f8..b49d524f83 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java @@ -23,8 +23,7 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.csc.util.CaffeineCSC; -import redis.clients.jedis.csc.util.GuavaCSC; +import redis.clients.jedis.csc.hash.OpenHftHashing; public class ClientSideCacheLibsTest { @@ -93,7 +92,8 @@ public void guavaMore() { @Test public void caffeineSimple() { - CaffeineCSC caffeine = CaffeineCSC.builder().maximumSize(10).ttl(10).hashFunction(LongHashFunction.xx()).build(); + CaffeineCSC caffeine = CaffeineCSC.builder().maximumSize(10).ttl(10) + .hashing(new OpenHftHashing(LongHashFunction.xx())).build(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -107,8 +107,8 @@ public void caffeineMore() { com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new CaffeineCSC(caffeine, LongHashFunction.city_1_1()), singleConnectionPoolConfig.get())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineCSC(caffeine), + singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertEquals(0, caffeine.estimatedSize()); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/csc/MapCSC.java b/src/test/java/redis/clients/jedis/csc/MapCSC.java index ee08efb588..fcb6d232a8 100644 --- a/src/test/java/redis/clients/jedis/csc/MapCSC.java +++ b/src/test/java/redis/clients/jedis/csc/MapCSC.java @@ -6,9 +6,23 @@ import redis.clients.jedis.CommandObject; import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.csc.hash.PrimitiveArrayHashing; public class MapCSC extends ClientSideCache { + private static final PrimitiveArrayHashing HASHING = new PrimitiveArrayHashing() { + + @Override + protected long hashLongs(long[] longs) { + return Arrays.hashCode(longs); + } + + @Override + protected long hashBytes(byte[] bytes) { + return Arrays.hashCode(bytes); + } + }; + private final Map cache; public MapCSC() { @@ -16,11 +30,12 @@ public MapCSC() { } public MapCSC(Map map) { + super(HASHING); this.cache = map; } public MapCSC(Map cache, ClientSideCacheable cacheable) { - super(cacheable); + super(HASHING, cacheable); this.cache = cache; } @@ -43,13 +58,4 @@ protected void putValue(long hash, Object value) { protected Object getValue(long hash) { return cache.get(hash); } - - @Override - protected final long getHash(CommandObject command) { - long result = 1; - for (Rawable raw : command.getArguments()) { - result = 31 * result + Arrays.hashCode(raw.getRaw()); - } - return 31 * result + command.getBuilder().hashCode(); - } } From 1651b26a8a065329fdda60bfc3bd05f22ab49234 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Sun, 10 Mar 2024 20:18:51 +0600 Subject: [PATCH 11/48] Client-side cache related naming changes (#3758) Changes: 1. CommandLongHashing is renamed to CommandLongHasher. 2. Expanded the names of GuavaCSC (GuavaClientSideCache) and CaffeineCSC (CaffeineClientSideCache). --- ...eCSC.java => CaffeineClientSideCache.java} | 31 +++++++------- .../clients/jedis/csc/ClientSideCache.java | 19 +++++---- ...uavaCSC.java => GuavaClientSideCache.java} | 42 +++++++++---------- ...ashing.java => AbstractCommandHasher.java} | 2 +- .../jedis/csc/hash/CommandLongHasher.java | 16 +++++++ .../jedis/csc/hash/CommandLongHashing.java | 16 ------- ...vaHashing.java => GuavaCommandHasher.java} | 4 +- ...Hashing.java => OpenHftCommandHasher.java} | 4 +- ....java => PrimitiveArrayCommandHasher.java} | 7 +++- .../clients/jedis/util/JedisURIHelper.java | 8 ++-- .../AllowAndDenyListClientSideCacheTest.java | 10 ++--- .../jedis/csc/ClientSideCacheLibsTest.java | 12 +++--- .../csc/JedisClusterClientSideCacheTest.java | 8 ++-- .../csc/JedisPooledClientSideCacheTest.java | 8 ++-- .../JedisSentineledClientSideCacheTest.java | 8 ++-- .../{MapCSC.java => MapClientSideCache.java} | 14 +++---- 16 files changed, 106 insertions(+), 103 deletions(-) rename src/main/java/redis/clients/jedis/csc/{CaffeineCSC.java => CaffeineClientSideCache.java} (60%) rename src/main/java/redis/clients/jedis/csc/{GuavaCSC.java => GuavaClientSideCache.java} (56%) rename src/main/java/redis/clients/jedis/csc/hash/{AbstractCommandHashing.java => AbstractCommandHasher.java} (89%) create mode 100644 src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java delete mode 100644 src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java rename src/main/java/redis/clients/jedis/csc/hash/{GuavaHashing.java => GuavaCommandHasher.java} (84%) rename src/main/java/redis/clients/jedis/csc/hash/{OpenHftHashing.java => OpenHftCommandHasher.java} (77%) rename src/main/java/redis/clients/jedis/csc/hash/{PrimitiveArrayHashing.java => PrimitiveArrayCommandHasher.java} (53%) rename src/test/java/redis/clients/jedis/csc/{MapCSC.java => MapClientSideCache.java} (68%) diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineCSC.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java similarity index 60% rename from src/main/java/redis/clients/jedis/csc/CaffeineCSC.java rename to src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java index 82f4f0f9a8..8ff993d6c7 100644 --- a/src/main/java/redis/clients/jedis/csc/CaffeineCSC.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java @@ -4,23 +4,23 @@ import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; -import redis.clients.jedis.csc.hash.CommandLongHashing; -import redis.clients.jedis.csc.hash.OpenHftHashing; +import redis.clients.jedis.csc.hash.CommandLongHasher; +import redis.clients.jedis.csc.hash.OpenHftCommandHasher; -public class CaffeineCSC extends ClientSideCache { +public class CaffeineClientSideCache extends ClientSideCache { private final Cache cache; - public CaffeineCSC(Cache caffeineCache) { - this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), DefaultClientSideCacheable.INSTANCE); + public CaffeineClientSideCache(Cache caffeineCache) { + this(caffeineCache, DefaultClientSideCacheable.INSTANCE); } - public CaffeineCSC(Cache caffeineCache, ClientSideCacheable cacheable) { - this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), cacheable); + public CaffeineClientSideCache(Cache caffeineCache, ClientSideCacheable cacheable) { + this(caffeineCache, new OpenHftCommandHasher(OpenHftCommandHasher.DEFAULT_HASH_FUNCTION), cacheable); } - public CaffeineCSC(Cache caffeineCache, CommandLongHashing hashing, ClientSideCacheable cacheable) { - super(hashing, cacheable); + public CaffeineClientSideCache(Cache caffeineCache, CommandLongHasher commandHasher, ClientSideCacheable cacheable) { + super(commandHasher, cacheable); this.cache = caffeineCache; } @@ -55,7 +55,7 @@ public static class Builder { private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; // not using a default value to avoid an object creation like 'new OpenHftHashing(hashFunction)' - private CommandLongHashing longHashing = null; + private CommandLongHasher commandHasher = null; private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; @@ -71,8 +71,8 @@ public Builder ttl(int seconds) { return this; } - public Builder hashing(CommandLongHashing hashing) { - this.longHashing = hashing; + public Builder commandHasher(CommandLongHasher commandHasher) { + this.commandHasher = commandHasher; return this; } @@ -81,16 +81,15 @@ public Builder cacheable(ClientSideCacheable cacheable) { return this; } - public CaffeineCSC build() { + public CaffeineClientSideCache build() { Caffeine cb = Caffeine.newBuilder(); cb.maximumSize(maximumSize); cb.expireAfterWrite(expireTime, expireTimeUnit); - return longHashing != null - ? new CaffeineCSC(cb.build(), longHashing, cacheable) - : new CaffeineCSC(cb.build(), cacheable); + return commandHasher != null ? new CaffeineClientSideCache(cb.build(), commandHasher, cacheable) + : new CaffeineClientSideCache(cb.build(), cacheable); } } } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index ef6b833a60..d0ad06e6a4 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -7,14 +7,15 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; + import redis.clients.jedis.CommandObject; -import redis.clients.jedis.csc.hash.CommandLongHashing; +import redis.clients.jedis.csc.hash.CommandLongHasher; 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.CaffeineCSC CaffeineCSC} or - * {@link redis.clients.jedis.csc.GuavaCSC GuavaCSC} or a custom implementation of their own. + * 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. */ public abstract class ClientSideCache { @@ -22,15 +23,15 @@ public abstract class ClientSideCache { protected static final int DEFAULT_EXPIRE_SECONDS = 100; private final Map> keyToCommandHashes = new ConcurrentHashMap<>(); - private final CommandLongHashing commandHashing; + private final CommandLongHasher commandHasher; private final ClientSideCacheable cacheable; - protected ClientSideCache(CommandLongHashing commandHashing) { - this(commandHashing, DefaultClientSideCacheable.INSTANCE); + protected ClientSideCache(CommandLongHasher commandHasher) { + this(commandHasher, DefaultClientSideCacheable.INSTANCE); } - protected ClientSideCache(CommandLongHashing commandHashing, ClientSideCacheable cacheable) { - this.commandHashing = commandHashing; + protected ClientSideCache(CommandLongHasher commandHasher, ClientSideCacheable cacheable) { + this.commandHasher = commandHasher; this.cacheable = cacheable; } @@ -80,7 +81,7 @@ public final T get(Function, T> loader, CommandObject co return loader.apply(command); } - final long hash = commandHashing.hash(command); + final long hash = commandHasher.hash(command); T value = (T) getValue(hash); if (value != null) { diff --git a/src/main/java/redis/clients/jedis/csc/GuavaCSC.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java similarity index 56% rename from src/main/java/redis/clients/jedis/csc/GuavaCSC.java rename to src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java index c5c173f5d8..ca176e8b33 100644 --- a/src/main/java/redis/clients/jedis/csc/GuavaCSC.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java @@ -5,32 +5,32 @@ import com.google.common.hash.HashFunction; import java.util.concurrent.TimeUnit; -import redis.clients.jedis.csc.hash.CommandLongHashing; -import redis.clients.jedis.csc.hash.GuavaHashing; +import redis.clients.jedis.csc.hash.CommandLongHasher; +import redis.clients.jedis.csc.hash.GuavaCommandHasher; -public class GuavaCSC extends ClientSideCache { +public class GuavaClientSideCache extends ClientSideCache { private final Cache cache; - public GuavaCSC(Cache guavaCache) { - this(guavaCache, GuavaHashing.DEFAULT_HASH_FUNCTION); + public GuavaClientSideCache(Cache guavaCache) { + this(guavaCache, GuavaCommandHasher.DEFAULT_HASH_FUNCTION); } - public GuavaCSC(Cache guavaCache, HashFunction hashFunction) { - this(guavaCache, new GuavaHashing(hashFunction)); + public GuavaClientSideCache(Cache guavaCache, HashFunction hashFunction) { + this(guavaCache, new GuavaCommandHasher(hashFunction)); } - public GuavaCSC(Cache guavaCache, CommandLongHashing hashing) { - super(hashing); + public GuavaClientSideCache(Cache guavaCache, CommandLongHasher commandHasher) { + super(commandHasher); this.cache = guavaCache; } - public GuavaCSC(Cache guavaCache, ClientSideCacheable cacheable) { - this(guavaCache, new GuavaHashing(GuavaHashing.DEFAULT_HASH_FUNCTION), cacheable); + public GuavaClientSideCache(Cache guavaCache, ClientSideCacheable cacheable) { + this(guavaCache, new GuavaCommandHasher(GuavaCommandHasher.DEFAULT_HASH_FUNCTION), cacheable); } - public GuavaCSC(Cache cache, CommandLongHashing hashing, ClientSideCacheable cacheable) { - super(hashing, cacheable); + public GuavaClientSideCache(Cache cache, CommandLongHasher commandHasher, ClientSideCacheable cacheable) { + super(commandHasher, cacheable); this.cache = cache; } @@ -66,7 +66,7 @@ public static class Builder { // not using a default value to avoid an object creation like 'new GuavaHashing(hashFunction)' private HashFunction hashFunction = null; - private CommandLongHashing longHashing = null; + private CommandLongHasher commandHasher = null; private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; @@ -84,12 +84,12 @@ public Builder ttl(int seconds) { public Builder hashFunction(HashFunction function) { this.hashFunction = function; - this.longHashing = null; + this.commandHasher = null; return this; } - public Builder hashing(CommandLongHashing hashing) { - this.longHashing = hashing; + public Builder commandHasher(CommandLongHasher commandHasher) { + this.commandHasher = commandHasher; this.hashFunction = null; return this; } @@ -99,16 +99,16 @@ public Builder cacheable(ClientSideCacheable cacheable) { return this; } - public GuavaCSC build() { + public GuavaClientSideCache build() { CacheBuilder cb = CacheBuilder.newBuilder(); cb.maximumSize(maximumSize); cb.expireAfterWrite(expireTime, expireTimeUnit); - return longHashing != null ? new GuavaCSC(cb.build(), longHashing, cacheable) - : hashFunction != null ? new GuavaCSC(cb.build(), new GuavaHashing(hashFunction), cacheable) - : new GuavaCSC(cb.build(), cacheable); + return hashFunction != null ? new GuavaClientSideCache(cb.build(), new GuavaCommandHasher(hashFunction), cacheable) + : commandHasher != null ? new GuavaClientSideCache(cb.build(), commandHasher, cacheable) + : new GuavaClientSideCache(cb.build(), cacheable); } } } diff --git a/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java b/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHasher.java similarity index 89% rename from src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java rename to src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHasher.java index 561217e299..2c71e4ea80 100644 --- a/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java +++ b/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHasher.java @@ -4,7 +4,7 @@ import redis.clients.jedis.CommandObject; import redis.clients.jedis.args.Rawable; -public abstract class AbstractCommandHashing implements CommandLongHashing { +public abstract class AbstractCommandHasher implements CommandLongHasher { @Override public final long hash(CommandObject command) { diff --git a/src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java new file mode 100644 index 0000000000..bb0b03e072 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java @@ -0,0 +1,16 @@ +package redis.clients.jedis.csc.hash; + +import redis.clients.jedis.CommandObject; + +/** + * The interface for hashing a command object to support client-side caching. + */ +public interface CommandLongHasher { + + /** + * Produce a 64-bit signed hash value from a command object. + * @param command the command object + * @return 64-bit signed hash value + */ + long hash(CommandObject command); +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java deleted file mode 100644 index 6632f46e72..0000000000 --- a/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java +++ /dev/null @@ -1,16 +0,0 @@ -package redis.clients.jedis.csc.hash; - -import redis.clients.jedis.CommandObject; - -/** - * The interface for hashing a command object for client-side caching. - */ -public interface CommandLongHashing { - - /** - * Produce a 64-bit signed hash from a command object. - * @param command the command object - * @return 64-bit signed hash - */ - long hash(CommandObject command); -} diff --git a/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java b/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java similarity index 84% rename from src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java rename to src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java index 2b9ad6ff5a..8c07245273 100644 --- a/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java +++ b/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java @@ -4,13 +4,13 @@ import com.google.common.hash.Hasher; import redis.clients.jedis.CommandObject; -public class GuavaHashing implements CommandLongHashing { +public class GuavaCommandHasher implements CommandLongHasher { public static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011(); private final HashFunction function; - public GuavaHashing(HashFunction function) { + public GuavaCommandHasher(HashFunction function) { this.function = function; } diff --git a/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java b/src/main/java/redis/clients/jedis/csc/hash/OpenHftCommandHasher.java similarity index 77% rename from src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java rename to src/main/java/redis/clients/jedis/csc/hash/OpenHftCommandHasher.java index b112980fd4..e2a7bced3d 100644 --- a/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java +++ b/src/main/java/redis/clients/jedis/csc/hash/OpenHftCommandHasher.java @@ -2,13 +2,13 @@ import net.openhft.hashing.LongHashFunction; -public class OpenHftHashing extends PrimitiveArrayHashing implements CommandLongHashing { +public class OpenHftCommandHasher extends PrimitiveArrayCommandHasher implements CommandLongHasher { public static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3(); private final LongHashFunction function; - public OpenHftHashing(LongHashFunction function) { + public OpenHftCommandHasher(LongHashFunction function) { this.function = function; } diff --git a/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java b/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayCommandHasher.java similarity index 53% rename from src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java rename to src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayCommandHasher.java index 385f97fdd2..ed4a9ff97c 100644 --- a/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java +++ b/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayCommandHasher.java @@ -3,7 +3,12 @@ import redis.clients.jedis.Builder; import redis.clients.jedis.args.Rawable; -public abstract class PrimitiveArrayHashing extends AbstractCommandHashing { +/** + * It is possible to extend {@link PrimitiveArrayCommandHasher this abstract class} in order to implement + * {@link CommandLongHasher} as {@link PrimitiveArrayCommandHasher#hashLongs(long[])} and + * {@link PrimitiveArrayCommandHasher#hashBytes(byte[])} can be supported by almost all Java hashing libraries. + */ +public abstract class PrimitiveArrayCommandHasher extends AbstractCommandHasher { @Override protected final long hashRawable(Rawable raw) { diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java index 6abedd05c4..ab1ee1d66a 100644 --- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java +++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java @@ -4,9 +4,9 @@ import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Protocol; import redis.clients.jedis.RedisProtocol; -import redis.clients.jedis.csc.CaffeineCSC; +import redis.clients.jedis.csc.CaffeineClientSideCache; import redis.clients.jedis.csc.ClientSideCache; -import redis.clients.jedis.csc.GuavaCSC; +import redis.clients.jedis.csc.GuavaClientSideCache; public final class JedisURIHelper { @@ -137,12 +137,12 @@ public static ClientSideCache getClientSideCache(URI uri) { } if (guava) { - GuavaCSC.Builder guavaBuilder = GuavaCSC.builder(); + GuavaClientSideCache.Builder guavaBuilder = GuavaClientSideCache.builder(); if (maxSize != null) guavaBuilder.maximumSize(maxSize); if (ttl != null) guavaBuilder.ttl(ttl); return guavaBuilder.build(); } else if (caffeine) { - CaffeineCSC.Builder caffeineBuilder = CaffeineCSC.builder(); + CaffeineClientSideCache.Builder caffeineBuilder = CaffeineClientSideCache.builder(); if (maxSize != null) caffeineBuilder.maximumSize(maxSize); if (ttl != null) caffeineBuilder.ttl(ttl); return caffeineBuilder.build(); diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java index 86c6dbc42f..17672ff955 100644 --- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java @@ -54,7 +54,7 @@ public void tearDown() throws Exception { public void none() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new MapCSC(map, new AllowAndDenyListWithStringKeys(null, null, null, null)), + new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -67,7 +67,7 @@ public void none() { public void whiteListCommand() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new MapCSC(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)), + new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -80,7 +80,7 @@ public void whiteListCommand() { public void blackListCommand() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new MapCSC(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)), + new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -93,7 +93,7 @@ public void blackListCommand() { public void whiteListKey() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new MapCSC(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)), + new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -106,7 +106,7 @@ public void whiteListKey() { public void blackListKey() { HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new MapCSC(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))), + new MapClientSideCache(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/ClientSideCacheLibsTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java index b49d524f83..189332b260 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java @@ -23,7 +23,7 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.csc.hash.OpenHftHashing; +import redis.clients.jedis.csc.hash.OpenHftCommandHasher; public class ClientSideCacheLibsTest { @@ -54,7 +54,7 @@ public void tearDown() throws Exception { @Test public void guavaSimple() { - GuavaCSC guava = GuavaCSC.builder().maximumSize(10).ttl(10) + GuavaClientSideCache guava = GuavaClientSideCache.builder().maximumSize(10).ttl(10) .hashFunction(com.google.common.hash.Hashing.farmHashFingerprint64()).build(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { control.set("foo", "bar"); @@ -69,7 +69,7 @@ public void guavaMore() { com.google.common.cache.Cache guava = CacheBuilder.newBuilder().recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaCSC(guava), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertEquals(0, guava.size()); @@ -92,8 +92,8 @@ public void guavaMore() { @Test public void caffeineSimple() { - CaffeineCSC caffeine = CaffeineCSC.builder().maximumSize(10).ttl(10) - .hashing(new OpenHftHashing(LongHashFunction.xx())).build(); + CaffeineClientSideCache caffeine = CaffeineClientSideCache.builder().maximumSize(10).ttl(10) + .commandHasher(new OpenHftCommandHasher(LongHashFunction.xx())).build(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -107,7 +107,7 @@ public void caffeineMore() { com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineCSC(caffeine), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertEquals(0, caffeine.estimatedSize()); diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index 3b52072560..edd8c786e9 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -38,7 +38,7 @@ public class JedisClusterClientSideCacheTest extends JedisClusterTestBase { @Test public void simple() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache())) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.del("foo"); @@ -49,7 +49,7 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map), singleConnectionPoolConfig.get())) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -68,7 +68,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC())) { + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache())) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); jedis.flushAll(); @@ -79,7 +79,7 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapCSC(map), + try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map), singleConnectionPoolConfig.get())) { jedis.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 149b1730a0..39f78df68f 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -50,7 +50,7 @@ public void tearDown() throws Exception { @Test public void simple() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); @@ -61,7 +61,7 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -80,7 +80,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); @@ -91,7 +91,7 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapCSC(map), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(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 6390ab3194..9b8eac8f9f 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -32,7 +32,7 @@ public class JedisSentineledClientSideCacheTest { @Test public void simple() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -44,7 +44,7 @@ public void simple() { @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -63,7 +63,7 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -75,7 +75,7 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapCSC(map), + try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map), sentinels, sentinelClientConfig)) { jedis.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); diff --git a/src/test/java/redis/clients/jedis/csc/MapCSC.java b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java similarity index 68% rename from src/test/java/redis/clients/jedis/csc/MapCSC.java rename to src/test/java/redis/clients/jedis/csc/MapClientSideCache.java index fcb6d232a8..4ff4e4367a 100644 --- a/src/test/java/redis/clients/jedis/csc/MapCSC.java +++ b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java @@ -4,13 +4,11 @@ import java.util.HashMap; import java.util.Map; -import redis.clients.jedis.CommandObject; -import redis.clients.jedis.args.Rawable; -import redis.clients.jedis.csc.hash.PrimitiveArrayHashing; +import redis.clients.jedis.csc.hash.PrimitiveArrayCommandHasher; -public class MapCSC extends ClientSideCache { +public class MapClientSideCache extends ClientSideCache { - private static final PrimitiveArrayHashing HASHING = new PrimitiveArrayHashing() { + private static final PrimitiveArrayCommandHasher HASHING = new PrimitiveArrayCommandHasher() { @Override protected long hashLongs(long[] longs) { @@ -25,16 +23,16 @@ protected long hashBytes(byte[] bytes) { private final Map cache; - public MapCSC() { + public MapClientSideCache() { this(new HashMap<>()); } - public MapCSC(Map map) { + public MapClientSideCache(Map map) { super(HASHING); this.cache = map; } - public MapCSC(Map cache, ClientSideCacheable cacheable) { + public MapClientSideCache(Map cache, ClientSideCacheable cacheable) { super(HASHING, cacheable); this.cache = cache; } From b89709457a428ac8f3bf119486c84d5b0cf07fb0 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Mon, 11 Mar 2024 00:11:43 +0600 Subject: [PATCH 12/48] Reformat clientSideCache variable names (#3761) --- .../java/redis/clients/jedis/Connection.java | 11 +++++----- .../clients/jedis/ConnectionFactory.java | 5 +---- .../redis/clients/jedis/ConnectionPool.java | 8 ++++---- .../clients/jedis/JedisClusterInfoCache.java | 13 ++++++------ .../java/redis/clients/jedis/JedisPooled.java | 10 +++++----- .../java/redis/clients/jedis/Protocol.java | 20 ------------------- .../providers/ClusterConnectionProvider.java | 17 ++++++++-------- .../providers/PooledConnectionProvider.java | 8 ++++---- 8 files changed, 35 insertions(+), 57 deletions(-) diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index b2d2aac27e..cada041461 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -64,15 +64,16 @@ public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clie this.socketFactory = socketFactory; this.soTimeout = clientConfig.getSocketTimeoutMillis(); this.infiniteSoTimeout = clientConfig.getBlockingSocketTimeoutMillis(); - initializeConnection(clientConfig); + initializeFromClientConfig(clientConfig); } - public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, ClientSideCache csCache) { + public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, + ClientSideCache clientSideCache) { this.socketFactory = socketFactory; this.soTimeout = clientConfig.getSocketTimeoutMillis(); this.infiniteSoTimeout = clientConfig.getBlockingSocketTimeoutMillis(); - initializeConnection(clientConfig); - initializeClientSideCache(csCache); + initializeFromClientConfig(clientConfig); + initializeClientSideCache(clientSideCache); } @Override @@ -392,7 +393,7 @@ private static boolean validateClientInfo(String info) { return true; } - private void initializeConnection(final JedisClientConfig config) { + private void initializeFromClientConfig(final JedisClientConfig config) { try { connect(); diff --git a/src/main/java/redis/clients/jedis/ConnectionFactory.java b/src/main/java/redis/clients/jedis/ConnectionFactory.java index 2d122aab15..270cf1fca1 100644 --- a/src/main/java/redis/clients/jedis/ConnectionFactory.java +++ b/src/main/java/redis/clients/jedis/ConnectionFactory.java @@ -61,10 +61,7 @@ public void destroyObject(PooledObject pooledConnection) throws Exce @Override public PooledObject makeObject() throws Exception { try { - Connection jedis = clientSideCache == null - ? new Connection(jedisSocketFactory, clientConfig) - : new Connection(jedisSocketFactory, clientConfig, clientSideCache); - + Connection jedis = new Connection(jedisSocketFactory, clientConfig, clientSideCache); return new DefaultPooledObject<>(jedis); } catch (JedisException je) { logger.debug("Error while makeObject", je); diff --git a/src/main/java/redis/clients/jedis/ConnectionPool.java b/src/main/java/redis/clients/jedis/ConnectionPool.java index 59f416649c..b3c10ecd21 100644 --- a/src/main/java/redis/clients/jedis/ConnectionPool.java +++ b/src/main/java/redis/clients/jedis/ConnectionPool.java @@ -11,8 +11,8 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig) { this(new ConnectionFactory(hostAndPort, clientConfig)); } - public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache) { - this(new ConnectionFactory(hostAndPort, clientConfig, csCache)); + public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + this(new ConnectionFactory(hostAndPort, clientConfig, clientSideCache)); } public ConnectionPool(PooledObjectFactory factory) { @@ -24,9 +24,9 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, this(new ConnectionFactory(hostAndPort, clientConfig), poolConfig); } - public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache, + public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig) { - this(new ConnectionFactory(hostAndPort, clientConfig, csCache), poolConfig); + this(new ConnectionFactory(hostAndPort, clientConfig, clientSideCache), poolConfig); } public ConnectionPool(PooledObjectFactory factory, diff --git a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java index 482fe12cb5..48ab655b46 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java +++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java @@ -66,8 +66,9 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, final Set startNodes) { - this(clientConfig, csCache, null, startNodes); + public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache clientSideCache, + final Set startNodes) { + this(clientConfig, clientSideCache, null, startNodes); } public JedisClusterInfoCache(final JedisClientConfig clientConfig, @@ -75,9 +76,9 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, this(clientConfig, null, poolConfig, startNodes); } - public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache csCache, + public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, final Set startNodes) { - this(clientConfig, csCache, poolConfig, startNodes, null); + this(clientConfig, clientSideCache, poolConfig, startNodes, null); } public JedisClusterInfoCache(final JedisClientConfig clientConfig, @@ -86,12 +87,12 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, this(clientConfig, null, poolConfig, startNodes, topologyRefreshPeriod); } - public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache csCache, + public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, final Set startNodes, final Duration topologyRefreshPeriod) { this.poolConfig = poolConfig; this.clientConfig = clientConfig; - this.clientSideCache = csCache; + this.clientSideCache = clientSideCache; this.startNodes = startNodes; if (topologyRefreshPeriod != null) { logger.info("Cluster topology refresh start, period: {}, startNodes: {}", topologyRefreshPeriod, startNodes); diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 4bda1d1ebb..5be534fcff 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -76,8 +76,8 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client super(hostAndPort, clientConfig); } - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache) { - super(hostAndPort, clientConfig, csCache); + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + super(hostAndPort, clientConfig, clientSideCache); } public JedisPooled(PooledObjectFactory factory) { @@ -380,10 +380,10 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client super(new PooledConnectionProvider(hostAndPort, clientConfig, poolConfig), clientConfig.getRedisProtocol()); } - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache, + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig) { - super(new PooledConnectionProvider(hostAndPort, clientConfig, csCache, poolConfig), - clientConfig.getRedisProtocol(), csCache); + super(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache, poolConfig), + clientConfig.getRedisProtocol(), clientSideCache); } public JedisPooled(final GenericObjectPoolConfig poolConfig, diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index ad676d88ce..40989c2502 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -88,13 +88,9 @@ private static void processError(final RedisInputStream is) { // Maybe Read only first 5 bytes instead? if (message.startsWith(MOVED_PREFIX)) { String[] movedInfo = parseTargetHostAndSlot(message); -// throw new JedisMovedDataException(message, new HostAndPort(movedInfo[1], -// Integer.parseInt(movedInfo[2])), Integer.parseInt(movedInfo[0])); throw new JedisMovedDataException(message, HostAndPort.from(movedInfo[1]), Integer.parseInt(movedInfo[0])); } else if (message.startsWith(ASK_PREFIX)) { String[] askInfo = parseTargetHostAndSlot(message); -// throw new JedisAskDataException(message, new HostAndPort(askInfo[1], -// Integer.parseInt(askInfo[2])), Integer.parseInt(askInfo[0])); throw new JedisAskDataException(message, HostAndPort.from(askInfo[1]), Integer.parseInt(askInfo[0])); } else if (message.startsWith(CLUSTERDOWN_PREFIX)) { throw new JedisClusterException(message); @@ -119,15 +115,6 @@ public static String readErrorLineIfPossible(RedisInputStream is) { return is.readLine(); } -// private static String[] parseTargetHostAndSlot(String clusterRedirectResponse) { -// String[] response = new String[3]; -// String[] messageInfo = clusterRedirectResponse.split(" "); -// String[] targetHostAndPort = HostAndPort.extractParts(messageInfo[2]); -// response[0] = messageInfo[1]; -// response[1] = targetHostAndPort[0]; -// response[2] = targetHostAndPort[1]; -// return response; -// } private static String[] parseTargetHostAndSlot(String clusterRedirectResponse) { String[] response = new String[2]; String[] messageInfo = clusterRedirectResponse.split(" "); @@ -196,15 +183,12 @@ private static byte[] processBulkReply(final RedisInputStream is) { } private static List processMultiBulkReply(final RedisInputStream is) { - // private static List processMultiBulkReply(final int num, final RedisInputStream is) { final int num = is.readIntCrLf(); - //System.out.println("MULTI BULK: " + num); if (num == -1) return null; final List ret = new ArrayList<>(num); for (int i = 0; i < num; i++) { try { ret.add(process(is)); - //System.out.println("MULTI >> " + (i+1) + ": " + SafeEncoder.encodeObject(ret.get(i))); } catch (JedisDataException e) { ret.add(e); } @@ -212,8 +196,6 @@ private static List processMultiBulkReply(final RedisInputStream is) { return ret; } - // private static List processMultiBulkReply(final RedisInputStream is) { - // private static List processMultiBulkReply(final int num, final RedisInputStream is) { private static List processMapKeyValueReply(final RedisInputStream is) { final int num = is.readIntCrLf(); if (num == -1) return null; @@ -236,7 +218,6 @@ public static Object read(final RedisInputStream is, final ClientSideCache cache private static void readPushes(final RedisInputStream is, final ClientSideCache cache) { if (cache != null) { - //System.out.println("PEEK: " + is.peekByte()); while (is.peek(GREATER_THAN_BYTE)) { is.readByte(); processPush(is, cache); @@ -246,7 +227,6 @@ private static void readPushes(final RedisInputStream is, final ClientSideCache private static void processPush(final RedisInputStream is, ClientSideCache cache) { List list = processMultiBulkReply(is); - //System.out.println("PUSH: " + SafeEncoder.encodeObject(list)); if (list.size() == 2 && list.get(0) instanceof byte[] && Arrays.equals(INVALIDATE_BYTES, (byte[]) list.get(0))) { cache.invalidate((List) list.get(1)); diff --git a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java index 6b148a5e77..ad3c2e0872 100644 --- a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java @@ -30,8 +30,8 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi initializeSlotsCache(clusterNodes, clientConfig); } - public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache csCache) { - this.cache = new JedisClusterInfoCache(clientConfig, csCache, clusterNodes); + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, clusterNodes); initializeSlotsCache(clusterNodes, clientConfig); } @@ -41,9 +41,9 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi initializeSlotsCache(clusterNodes, clientConfig); } - public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache csCache, + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig) { - this.cache = new JedisClusterInfoCache(clientConfig, csCache, poolConfig, clusterNodes); + this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes); initializeSlotsCache(clusterNodes, clientConfig); } @@ -53,9 +53,9 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi initializeSlotsCache(clusterNodes, clientConfig); } - public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache csCache, + public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod) { - this.cache = new JedisClusterInfoCache(clientConfig, csCache, poolConfig, clusterNodes, topologyRefreshPeriod); + this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes, topologyRefreshPeriod); initializeSlotsCache(clusterNodes, clientConfig); } @@ -122,9 +122,8 @@ public Connection getConnection(CommandArguments args) { @Override public Connection getConnection() { - // In antirez's redis-rb-cluster implementation, getRandomConnection always - // return valid connection (able to ping-pong) or exception if all - // connections are invalid + // In antirez's redis-rb-cluster implementation, getRandomConnection always return + // valid connection (able to ping-pong) or exception if all connections are invalid List pools = cache.getShuffledNodesPool(); diff --git a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java index 0d85fabad2..0b50ec4e87 100644 --- a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java @@ -29,8 +29,8 @@ public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clien this.connectionMapKey = hostAndPort; } - public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache) { - this(new ConnectionPool(hostAndPort, clientConfig, csCache)); + public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { + this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache)); this.connectionMapKey = hostAndPort; } @@ -40,9 +40,9 @@ public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clien this.connectionMapKey = hostAndPort; } - public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache csCache, + public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig) { - this(new ConnectionPool(hostAndPort, clientConfig, csCache, poolConfig)); + this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache, poolConfig)); this.connectionMapKey = hostAndPort; } From a2f5d168c31d9f6d4463074bcee84f64f0e8ea09 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:33:05 +0600 Subject: [PATCH 13/48] Format tabs in pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 884b417183..33db3f2682 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ - + com.google.guava guava 33.0.0-jre From a4737e0e53969e1e8eb8d521079b2ea4c6417050 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:27:25 +0600 Subject: [PATCH 14/48] Use Experimental annotation --- src/main/java/redis/clients/jedis/Connection.java | 2 ++ src/main/java/redis/clients/jedis/ConnectionFactory.java | 3 ++- src/main/java/redis/clients/jedis/ConnectionPool.java | 3 +++ src/main/java/redis/clients/jedis/JedisCluster.java | 7 +++++++ .../java/redis/clients/jedis/JedisClusterInfoCache.java | 4 ++++ src/main/java/redis/clients/jedis/JedisPooled.java | 3 +++ src/main/java/redis/clients/jedis/JedisSentineled.java | 3 +++ src/main/java/redis/clients/jedis/Protocol.java | 2 ++ src/main/java/redis/clients/jedis/UnifiedJedis.java | 5 +++++ .../java/redis/clients/jedis/annots/Experimental.java | 2 +- .../java/redis/clients/jedis/csc/ClientSideCache.java | 2 ++ .../java/redis/clients/jedis/csc/hash/package-info.java | 8 ++++++++ src/main/java/redis/clients/jedis/csc/package-info.java | 7 +++++++ .../java/redis/clients/jedis/csc/util/package-info.java | 7 +++++++ src/main/java/redis/clients/jedis/mcf/package-info.java | 3 +++ .../jedis/providers/ClusterConnectionProvider.java | 4 ++++ .../clients/jedis/providers/PooledConnectionProvider.java | 3 +++ .../jedis/providers/SentineledConnectionProvider.java | 4 ++++ .../java/redis/clients/jedis/util/JedisURIHelper.java | 2 ++ .../java/redis/clients/jedis/util/RedisInputStream.java | 2 ++ 20 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/hash/package-info.java create mode 100644 src/main/java/redis/clients/jedis/csc/package-info.java create mode 100644 src/main/java/redis/clients/jedis/csc/util/package-info.java diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index cada041461..3bd5b56bf6 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -15,6 +15,7 @@ import redis.clients.jedis.Protocol.Command; import redis.clients.jedis.Protocol.Keyword; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.args.ClientAttributeOption; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.commands.ProtocolCommand; @@ -67,6 +68,7 @@ public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clie initializeFromClientConfig(clientConfig); } + @Experimental public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { this.socketFactory = socketFactory; diff --git a/src/main/java/redis/clients/jedis/ConnectionFactory.java b/src/main/java/redis/clients/jedis/ConnectionFactory.java index 270cf1fca1..2ac7e13f19 100644 --- a/src/main/java/redis/clients/jedis/ConnectionFactory.java +++ b/src/main/java/redis/clients/jedis/ConnectionFactory.java @@ -1,11 +1,11 @@ package redis.clients.jedis; - import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisException; @@ -30,6 +30,7 @@ public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig this.jedisSocketFactory = new DefaultJedisSocketFactory(hostAndPort, this.clientConfig); } + @Experimental public ConnectionFactory(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache csCache) { this.clientConfig = clientConfig; this.jedisSocketFactory = new DefaultJedisSocketFactory(hostAndPort, this.clientConfig); diff --git a/src/main/java/redis/clients/jedis/ConnectionPool.java b/src/main/java/redis/clients/jedis/ConnectionPool.java index b3c10ecd21..49b0fe803d 100644 --- a/src/main/java/redis/clients/jedis/ConnectionPool.java +++ b/src/main/java/redis/clients/jedis/ConnectionPool.java @@ -2,6 +2,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.util.Pool; @@ -11,6 +12,7 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig) { this(new ConnectionFactory(hostAndPort, clientConfig)); } + @Experimental public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { this(new ConnectionFactory(hostAndPort, clientConfig, clientSideCache)); } @@ -24,6 +26,7 @@ public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, this(new ConnectionFactory(hostAndPort, clientConfig), poolConfig); } + @Experimental public ConnectionPool(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache 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 72a9495617..083a6f6305 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -6,6 +6,7 @@ import java.util.Set; 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.util.JedisClusterCRC16; @@ -216,23 +217,27 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati super(provider, maxAttempts, maxTotalRetriesDuration, protocol); } + @Experimental public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { this(clusterNodes, clientConfig, clientSideCache, DEFAULT_MAX_ATTEMPTS, Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis())); } + @Experimental public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache 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, 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, GenericObjectPoolConfig poolConfig) { this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache, poolConfig), @@ -240,6 +245,7 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi clientConfig.getRedisProtocol(), clientSideCache); } + @Experimental public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod, int maxAttempts, Duration maxTotalRetriesDuration) { @@ -247,6 +253,7 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi maxAttempts, maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache); } + @Experimental private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, RedisProtocol protocol, ClientSideCache 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 4a08dfcee6..f3287897b7 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java +++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java @@ -23,6 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.annots.Internal; import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.exceptions.JedisClusterOperationException; @@ -70,6 +71,7 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, final Set startNodes) { this(clientConfig, clientSideCache, null, startNodes); @@ -80,6 +82,7 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, this(clientConfig, null, poolConfig, startNodes); } + @Experimental public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, final Set startNodes) { this(clientConfig, clientSideCache, poolConfig, startNodes, null); @@ -91,6 +94,7 @@ public JedisClusterInfoCache(final JedisClientConfig clientConfig, this(clientConfig, null, poolConfig, startNodes, topologyRefreshPeriod); } + @Experimental public JedisClusterInfoCache(final JedisClientConfig clientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, final Set startNodes, final Duration topologyRefreshPeriod) { diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 5be534fcff..504141404f 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -7,6 +7,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.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; @@ -76,6 +77,7 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client super(hostAndPort, clientConfig); } + @Experimental public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache clientSideCache) { super(hostAndPort, clientConfig, clientSideCache); } @@ -380,6 +382,7 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client super(new PooledConnectionProvider(hostAndPort, clientConfig, poolConfig), clientConfig.getRedisProtocol()); } + @Experimental public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig) { super(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache, poolConfig), diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index 164c665d3f..f89764d22c 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -2,6 +2,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.providers.SentineledConnectionProvider; @@ -13,6 +14,7 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo masterClientConfig.getRedisProtocol()); } + @Experimental public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, Set sentinels, final JedisClientConfig sentinelClientConfig) { super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, @@ -26,6 +28,7 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo masterClientConfig.getRedisProtocol()); } + @Experimental public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index c36eb6625b..f8ab4938c7 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -7,6 +7,7 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.exceptions.*; import redis.clients.jedis.args.Rawable; @@ -211,6 +212,7 @@ public static Object read(final RedisInputStream is) { return process(is); } + @Experimental public static Object read(final RedisInputStream is, final ClientSideCache cache) { readPushes(is, cache); return process(is); diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 21bbf24496..0f84de7bc0 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -94,6 +94,7 @@ public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) { this(new PooledConnectionProvider(hostAndPort, clientConfig), clientConfig.getRedisProtocol()); } + @Experimental public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(), clientSideCache); } @@ -106,6 +107,7 @@ protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol) { this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol); } + @Experimental protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, ClientSideCache clientSideCache) { this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol, clientSideCache); } @@ -177,6 +179,7 @@ protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Dura new ClusterCommandObjects(), protocol); } + @Experimental protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, RedisProtocol protocol, ClientSideCache clientSideCache) { this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, @@ -242,11 +245,13 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comm } } + @Experimental private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, RedisProtocol protocol) { this(executor, provider, commandObjects, protocol, (ClientSideCache) null); } + @Experimental private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, RedisProtocol protocol, ClientSideCache clientSideCache) { diff --git a/src/main/java/redis/clients/jedis/annots/Experimental.java b/src/main/java/redis/clients/jedis/annots/Experimental.java index e0c642e630..0d17084085 100644 --- a/src/main/java/redis/clients/jedis/annots/Experimental.java +++ b/src/main/java/redis/clients/jedis/annots/Experimental.java @@ -13,5 +13,5 @@ * If a type is marked with this annotation, all its members are considered experimental. */ @Documented -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR}) +@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR}) public @interface Experimental { } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index d0ad06e6a4..554064e60a 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -9,6 +9,7 @@ import java.util.function.Function; import redis.clients.jedis.CommandObject; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.csc.hash.CommandLongHasher; import redis.clients.jedis.util.SafeEncoder; @@ -17,6 +18,7 @@ * 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 { protected static final int DEFAULT_MAXIMUM_SIZE = 10_000; diff --git a/src/main/java/redis/clients/jedis/csc/hash/package-info.java b/src/main/java/redis/clients/jedis/csc/hash/package-info.java new file mode 100644 index 0000000000..0667e1f98b --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/package-info.java @@ -0,0 +1,8 @@ +/** + * This package contains the classes and interface for hashing command arguments to support + * Server-assisted Client-side Caching. + */ +@Experimental +package redis.clients.jedis.csc.hash; + +import redis.clients.jedis.annots.Experimental; \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/csc/package-info.java b/src/main/java/redis/clients/jedis/csc/package-info.java new file mode 100644 index 0000000000..d74aee56cd --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/package-info.java @@ -0,0 +1,7 @@ +/** + * This package contains the classes and interfaces related to Server-assisted Client-side Caching. + */ +@Experimental +package redis.clients.jedis.csc; + +import redis.clients.jedis.annots.Experimental; \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/csc/util/package-info.java b/src/main/java/redis/clients/jedis/csc/util/package-info.java new file mode 100644 index 0000000000..abd1e73b9e --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/util/package-info.java @@ -0,0 +1,7 @@ +/** + * This package contains the helper classes related to Server-assisted Client-side Caching. + */ +@Experimental +package redis.clients.jedis.csc.util; + +import redis.clients.jedis.annots.Experimental; \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/mcf/package-info.java b/src/main/java/redis/clients/jedis/mcf/package-info.java index 6b89d9c77b..60b1f9c123 100644 --- a/src/main/java/redis/clients/jedis/mcf/package-info.java +++ b/src/main/java/redis/clients/jedis/mcf/package-info.java @@ -1,4 +1,7 @@ /** * This package contains the classes that are related to Active-Active cluster(s) and Multi-Cluster failover. */ +@Experimental package redis.clients.jedis.mcf; + +import redis.clients.jedis.annots.Experimental; \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java index ad3c2e0872..e3587a022b 100644 --- a/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java @@ -15,6 +15,7 @@ import redis.clients.jedis.Connection; 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.exceptions.JedisClusterOperationException; import redis.clients.jedis.exceptions.JedisException; @@ -30,6 +31,7 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi initializeSlotsCache(clusterNodes, clientConfig); } + @Experimental public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, clusterNodes); initializeSlotsCache(clusterNodes, clientConfig); @@ -41,6 +43,7 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi initializeSlotsCache(clusterNodes, clientConfig); } + @Experimental public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig) { this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes); @@ -53,6 +56,7 @@ public ClusterConnectionProvider(Set clusterNodes, JedisClientConfi initializeSlotsCache(clusterNodes, clientConfig); } + @Experimental public ClusterConnectionProvider(Set clusterNodes, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig, Duration topologyRefreshPeriod) { this.cache = new JedisClusterInfoCache(clientConfig, clientSideCache, poolConfig, clusterNodes, topologyRefreshPeriod); diff --git a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java index 0b50ec4e87..14d1b2c9da 100644 --- a/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java @@ -11,6 +11,7 @@ import redis.clients.jedis.ConnectionPool; 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.util.Pool; @@ -29,6 +30,7 @@ public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clien this.connectionMapKey = hostAndPort; } + @Experimental public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) { this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache)); this.connectionMapKey = hostAndPort; @@ -40,6 +42,7 @@ public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clien this.connectionMapKey = hostAndPort; } + @Experimental public PooledConnectionProvider(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache, GenericObjectPoolConfig poolConfig) { this(new ConnectionPool(hostAndPort, clientConfig, clientSideCache, poolConfig)); diff --git a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java index 1d7c5790c2..5199279500 100644 --- a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java +++ b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java @@ -17,6 +17,7 @@ import redis.clients.jedis.Jedis; 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.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisException; @@ -53,6 +54,7 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m this(masterName, masterClientConfig, null, null, sentinels, sentinelClientConfig); } + @Experimental public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, Set sentinels, final JedisClientConfig sentinelClientConfig) { this(masterName, masterClientConfig, clientSideCache, null, sentinels, sentinelClientConfig); @@ -65,6 +67,7 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m DEFAULT_SUBSCRIBE_RETRY_WAIT_TIME_MILLIS); } + @Experimental public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { @@ -79,6 +82,7 @@ public SentineledConnectionProvider(String masterName, final JedisClientConfig m this(masterName, masterClientConfig, null, poolConfig, sentinels, sentinelClientConfig, subscribeRetryWaitTimeMillis); } + @Experimental public SentineledConnectionProvider(String masterName, final JedisClientConfig masterClientConfig, ClientSideCache clientSideCache, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig, diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java index ab1ee1d66a..9d83ff83a4 100644 --- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java +++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java @@ -4,6 +4,7 @@ import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Protocol; import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.csc.CaffeineClientSideCache; import redis.clients.jedis.csc.ClientSideCache; import redis.clients.jedis.csc.GuavaClientSideCache; @@ -76,6 +77,7 @@ public static RedisProtocol getRedisProtocol(URI uri) { private static final Integer ZERO_INTEGER = 0; + @Experimental public static ClientSideCache getClientSideCache(URI uri) { if (uri.getQuery() == null) return null; diff --git a/src/main/java/redis/clients/jedis/util/RedisInputStream.java b/src/main/java/redis/clients/jedis/util/RedisInputStream.java index a0859c6bd4..82d14fb972 100644 --- a/src/main/java/redis/clients/jedis/util/RedisInputStream.java +++ b/src/main/java/redis/clients/jedis/util/RedisInputStream.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; +import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.exceptions.JedisConnectionException; /** @@ -43,6 +44,7 @@ public RedisInputStream(InputStream in) { this(in, INPUT_BUFFER_SIZE); } + @Experimental public boolean peek(byte b) throws JedisConnectionException { ensureFill(); // in current design, at least one reply is expected. so ensureFillSafe() is not necessary. return buf[count] == b; From 767fc0165d045f47ea350d6995973273d115a752 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:14:31 +0600 Subject: [PATCH 15/48] Fix client side cache tests (#3799) Due to https://github.com/redis/redis/pull/13167 * Fix JedisClusterClientSideCacheTest * Fix JedisSentineledClientSideCacheTest --- .../csc/JedisClusterClientSideCacheTest.java | 40 +++++++++++++------ .../JedisSentineledClientSideCacheTest.java | 35 +++++++++++----- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index edd8c786e9..63e543cc1c 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -4,27 +4,28 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; 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; +import org.junit.Before; import org.junit.Test; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPoolConfig; import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.HostAndPorts; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.JedisClusterTestBase; -public class JedisClusterClientSideCacheTest extends JedisClusterTestBase { +public class JedisClusterClientSideCacheTest { - private static final Set hnp = Arrays.asList(nodeInfo1, nodeInfo2, nodeInfo3).stream().collect(Collectors.toSet()); + private static final Set hnp = new HashSet<>(HostAndPorts.getStableClusterServers()); private static final Supplier clientConfig = () -> DefaultJedisClientConfig.builder().resp3().password("cluster").build(); @@ -36,12 +37,25 @@ public class JedisClusterClientSideCacheTest extends JedisClusterTestBase { return poolConfig; }; + protected JedisCluster control; + + @Before + public void setUp() throws Exception { + control = new JedisCluster(hnp, clientConfig.get()); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + @Test public void simple() { try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache())) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); - jedis.del("foo"); + control.del("foo"); assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? } } @@ -51,11 +65,11 @@ public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map), singleConnectionPoolConfig.get())) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); - jedis.del("foo"); + control.del("foo"); assertThat(map, Matchers.aMapWithSize(1)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); @@ -69,9 +83,9 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache())) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); - jedis.flushAll(); + control.flushAll(); assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? } } @@ -81,11 +95,11 @@ public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map), singleConnectionPoolConfig.get())) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); - jedis.flushAll(); + control.flushAll(); assertThat(map, Matchers.aMapWithSize(1)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index 9b8eac8f9f..c065fc8b02 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -6,9 +6,11 @@ import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import redis.clients.jedis.DefaultJedisClientConfig; @@ -24,19 +26,32 @@ public class JedisSentineledClientSideCacheTest { protected static final HostAndPort sentinel1 = HostAndPorts.getSentinelServers().get(1); protected static final HostAndPort sentinel2 = HostAndPorts.getSentinelServers().get(3); - private static final Set sentinels = Arrays.asList(sentinel1, sentinel2).stream().collect(Collectors.toSet()); + private static final Set sentinels = new HashSet<>(Arrays.asList(sentinel1, sentinel2)); private static final JedisClientConfig masterClientConfig = DefaultJedisClientConfig.builder().resp3().password("foobared").build(); private static final JedisClientConfig sentinelClientConfig = DefaultJedisClientConfig.builder().resp3().build(); + protected JedisSentineled control; + + @Before + public void setUp() throws Exception { + control = new JedisSentineled(MASTER_NAME, masterClientConfig, sentinels, sentinelClientConfig); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + @Test public void simple() { try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(), sentinels, sentinelClientConfig)) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); - jedis.del("foo"); + control.del("foo"); assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? } } @@ -46,11 +61,11 @@ public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map), sentinels, sentinelClientConfig)) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); - jedis.del("foo"); + control.del("foo"); assertThat(map, Matchers.aMapWithSize(1)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); @@ -65,9 +80,9 @@ public void simpleWithSimpleMap() { public void flushAll() { try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(), sentinels, sentinelClientConfig)) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); - jedis.flushAll(); + control.flushAll(); assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? } } @@ -77,11 +92,11 @@ public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map), sentinels, sentinelClientConfig)) { - jedis.set("foo", "bar"); + control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); - jedis.flushAll(); + control.flushAll(); assertThat(map, Matchers.aMapWithSize(1)); assertEquals("bar", jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(1)); From 3bd45a4e640d200159aeff45c9d5384f24985cd4 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:23:12 +0600 Subject: [PATCH 16/48] Remove openhft hashing from source dependency (#3800) --- pom.xml | 2 +- .../jedis/csc/CaffeineClientSideCache.java | 15 ++++++----- ....java => AbstractSimpleCommandHasher.java} | 8 +++--- .../jedis/csc/hash/GuavaCommandHasher.java | 9 ++++++- .../jedis/csc/hash/SimpleCommandHasher.java | 25 +++++++++++++++++++ .../jedis/csc/ClientSideCacheLibsTest.java | 9 +++---- .../clients/jedis/csc/MapClientSideCache.java | 20 +++------------ .../jedis/csc}/OpenHftCommandHasher.java | 9 +++++-- 8 files changed, 60 insertions(+), 37 deletions(-) rename src/main/java/redis/clients/jedis/csc/hash/{PrimitiveArrayCommandHasher.java => AbstractSimpleCommandHasher.java} (64%) create mode 100644 src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java rename src/{main/java/redis/clients/jedis/csc/hash => test/java/redis/clients/jedis/csc}/OpenHftCommandHasher.java (70%) diff --git a/pom.xml b/pom.xml index 338847e068..8d08a75c03 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ net.openhft zero-allocation-hashing 0.16 - true + test diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java index 8ff993d6c7..5bf86f3e0b 100644 --- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java @@ -5,18 +5,22 @@ import java.util.concurrent.TimeUnit; import redis.clients.jedis.csc.hash.CommandLongHasher; -import redis.clients.jedis.csc.hash.OpenHftCommandHasher; +import redis.clients.jedis.csc.hash.SimpleCommandHasher; public class CaffeineClientSideCache extends ClientSideCache { private final Cache cache; public CaffeineClientSideCache(Cache caffeineCache) { - this(caffeineCache, DefaultClientSideCacheable.INSTANCE); + this(caffeineCache, SimpleCommandHasher.INSTANCE); + } + + public CaffeineClientSideCache(Cache caffeineCache, CommandLongHasher commandHasher) { + this(caffeineCache, commandHasher, DefaultClientSideCacheable.INSTANCE); } public CaffeineClientSideCache(Cache caffeineCache, ClientSideCacheable cacheable) { - this(caffeineCache, new OpenHftCommandHasher(OpenHftCommandHasher.DEFAULT_HASH_FUNCTION), cacheable); + this(caffeineCache, SimpleCommandHasher.INSTANCE, cacheable); } public CaffeineClientSideCache(Cache caffeineCache, CommandLongHasher commandHasher, ClientSideCacheable cacheable) { @@ -55,7 +59,7 @@ public static class Builder { private final TimeUnit expireTimeUnit = TimeUnit.SECONDS; // not using a default value to avoid an object creation like 'new OpenHftHashing(hashFunction)' - private CommandLongHasher commandHasher = null; + private CommandLongHasher commandHasher = SimpleCommandHasher.INSTANCE; private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; @@ -88,8 +92,7 @@ public CaffeineClientSideCache build() { cb.expireAfterWrite(expireTime, expireTimeUnit); - return commandHasher != null ? new CaffeineClientSideCache(cb.build(), commandHasher, cacheable) - : new CaffeineClientSideCache(cb.build(), cacheable); + return new CaffeineClientSideCache(cb.build(), commandHasher, cacheable); } } } diff --git a/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayCommandHasher.java b/src/main/java/redis/clients/jedis/csc/hash/AbstractSimpleCommandHasher.java similarity index 64% rename from src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayCommandHasher.java rename to src/main/java/redis/clients/jedis/csc/hash/AbstractSimpleCommandHasher.java index ed4a9ff97c..3b782c2341 100644 --- a/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayCommandHasher.java +++ b/src/main/java/redis/clients/jedis/csc/hash/AbstractSimpleCommandHasher.java @@ -4,11 +4,11 @@ import redis.clients.jedis.args.Rawable; /** - * It is possible to extend {@link PrimitiveArrayCommandHasher this abstract class} in order to implement - * {@link CommandLongHasher} as {@link PrimitiveArrayCommandHasher#hashLongs(long[])} and - * {@link PrimitiveArrayCommandHasher#hashBytes(byte[])} can be supported by almost all Java hashing libraries. + * It is possible to extend {@link AbstractSimpleCommandHasher this abstract class} in order to implement + * {@link CommandLongHasher} as {@link AbstractSimpleCommandHasher#hashLongs(long[])} and + * {@link AbstractSimpleCommandHasher#hashBytes(byte[])} are supported by almost all Java hashing libraries. */ -public abstract class PrimitiveArrayCommandHasher extends AbstractCommandHasher { +public abstract class AbstractSimpleCommandHasher extends AbstractCommandHasher { @Override protected final long hashRawable(Rawable raw) { diff --git a/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java b/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java index 8c07245273..3208049e3a 100644 --- a/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java +++ b/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java @@ -4,12 +4,19 @@ import com.google.common.hash.Hasher; import redis.clients.jedis.CommandObject; -public class GuavaCommandHasher implements CommandLongHasher { +/** + * An implementation of {@link CommandLongHasher} based on {@link HashFunction} from Google Guava library. + */ +public final class GuavaCommandHasher implements CommandLongHasher { public static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011(); private final HashFunction function; + /** + * It is advised to use a {@link HashFunction} capable of producing 64-bit hash. + * @param function an implementation of hash function + */ public GuavaCommandHasher(HashFunction function) { this.function = function; } diff --git a/src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java b/src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java new file mode 100644 index 0000000000..1414d97295 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java @@ -0,0 +1,25 @@ +package redis.clients.jedis.csc.hash; + +import java.util.Arrays; + +/** + * This {@link CommandLongHasher} implementation is simply based on {@link Arrays#hashCode(long[])} + * and {@link Arrays#hashCode(byte[])}. These methods actually produce 32-bit hash codes. It is + * advised to use proper 64-bit hash codes in production. + */ +public final class SimpleCommandHasher extends AbstractSimpleCommandHasher { + + public static final SimpleCommandHasher INSTANCE = new SimpleCommandHasher(); + + public SimpleCommandHasher() { } + + @Override + protected long hashLongs(long[] longs) { + return Arrays.hashCode(longs); + } + + @Override + protected long hashBytes(byte[] bytes) { + return Arrays.hashCode(bytes); + } +} diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java index 189332b260..b01449c9cf 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java @@ -7,8 +7,6 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.cache.CacheBuilder; import java.util.function.Supplier; -import net.openhft.hashing.LongHashFunction; - import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; import org.junit.After; @@ -23,7 +21,6 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.csc.hash.OpenHftCommandHasher; public class ClientSideCacheLibsTest { @@ -92,8 +89,7 @@ public void guavaMore() { @Test public void caffeineSimple() { - CaffeineClientSideCache caffeine = CaffeineClientSideCache.builder().maximumSize(10).ttl(10) - .commandHasher(new OpenHftCommandHasher(LongHashFunction.xx())).build(); + CaffeineClientSideCache caffeine = CaffeineClientSideCache.builder().maximumSize(10).ttl(10).build(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -107,7 +103,8 @@ public void caffeineMore() { com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine), + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + new CaffeineClientSideCache(caffeine, new OpenHftCommandHasher()), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertEquals(0, caffeine.estimatedSize()); diff --git a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java index 4ff4e4367a..421eed8b42 100644 --- a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java +++ b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java @@ -1,26 +1,12 @@ package redis.clients.jedis.csc; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import redis.clients.jedis.csc.hash.PrimitiveArrayCommandHasher; +import redis.clients.jedis.csc.hash.SimpleCommandHasher; public class MapClientSideCache extends ClientSideCache { - private static final PrimitiveArrayCommandHasher HASHING = new PrimitiveArrayCommandHasher() { - - @Override - protected long hashLongs(long[] longs) { - return Arrays.hashCode(longs); - } - - @Override - protected long hashBytes(byte[] bytes) { - return Arrays.hashCode(bytes); - } - }; - private final Map cache; public MapClientSideCache() { @@ -28,12 +14,12 @@ public MapClientSideCache() { } public MapClientSideCache(Map map) { - super(HASHING); + super(SimpleCommandHasher.INSTANCE); this.cache = map; } public MapClientSideCache(Map cache, ClientSideCacheable cacheable) { - super(HASHING, cacheable); + super(SimpleCommandHasher.INSTANCE, cacheable); this.cache = cache; } diff --git a/src/main/java/redis/clients/jedis/csc/hash/OpenHftCommandHasher.java b/src/test/java/redis/clients/jedis/csc/OpenHftCommandHasher.java similarity index 70% rename from src/main/java/redis/clients/jedis/csc/hash/OpenHftCommandHasher.java rename to src/test/java/redis/clients/jedis/csc/OpenHftCommandHasher.java index e2a7bced3d..147ad42e8f 100644 --- a/src/main/java/redis/clients/jedis/csc/hash/OpenHftCommandHasher.java +++ b/src/test/java/redis/clients/jedis/csc/OpenHftCommandHasher.java @@ -1,13 +1,18 @@ -package redis.clients.jedis.csc.hash; +package redis.clients.jedis.csc; import net.openhft.hashing.LongHashFunction; +import redis.clients.jedis.csc.hash.AbstractSimpleCommandHasher; -public class OpenHftCommandHasher extends PrimitiveArrayCommandHasher implements CommandLongHasher { +public class OpenHftCommandHasher extends AbstractSimpleCommandHasher { public static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3(); private final LongHashFunction function; + OpenHftCommandHasher() { + this(DEFAULT_HASH_FUNCTION); + } + public OpenHftCommandHasher(LongHashFunction function) { this.function = function; } From 82c0226cd04e2a9c36233eca304671aaa5758074 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Tue, 30 Apr 2024 00:20:00 +0600 Subject: [PATCH 17/48] Test different functionalities of client side cache (#3828) --- .../clients/jedis/csc/ClientSideCache.java | 18 ++- ....java => CaffeineClientSideCacheTest.java} | 117 +++++++------- .../csc/ClientSideCacheFunctionalityTest.java | 111 +++++++++++++ .../jedis/csc/GuavaClientSideCacheTest.java | 152 ++++++++++++++++++ .../csc/JedisPooledClientSideCacheTest.java | 1 + .../JedisSentineledClientSideCacheTest.java | 1 + 6 files changed, 339 insertions(+), 61 deletions(-) rename src/test/java/redis/clients/jedis/csc/{ClientSideCacheLibsTest.java => CaffeineClientSideCacheTest.java} (62%) create mode 100644 src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java create mode 100644 src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 554064e60a..0290ef437c 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -49,6 +49,10 @@ public final void clear() { invalidateAllKeysAndCommandHashes(); } + public final void removeKey(Object key) { + invalidateKeyAndRespectiveCommandHashes(key); + } + public final void invalidate(List list) { if (list == null) { invalidateAllKeysAndCommandHashes(); @@ -64,11 +68,13 @@ private void invalidateAllKeysAndCommandHashes() { } private void invalidateKeyAndRespectiveCommandHashes(Object key) { - if (!(key instanceof byte[])) { - 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 = makeKeyForKeyToCommandHashes(key); Set hashes = keyToCommandHashes.get(mapKey); if (hashes != null) { @@ -111,7 +117,7 @@ public final T get(Function, T> loader, CommandObject co private ByteBuffer makeKeyForKeyToCommandHashes(Object key) { if (key instanceof byte[]) return makeKeyForKeyToCommandHashes((byte[]) key); else if (key instanceof String) return makeKeyForKeyToCommandHashes(SafeEncoder.encode((String) key)); - else throw new AssertionError("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); + else throw new IllegalArgumentException("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key)); } private static ByteBuffer makeKeyForKeyToCommandHashes(byte[] b) { diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java similarity index 62% rename from src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java rename to src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index b01449c9cf..85d2167c90 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -4,8 +4,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import com.google.common.cache.CacheBuilder; +import com.github.benmanes.caffeine.cache.stats.CacheStats; + +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; @@ -22,73 +25,35 @@ import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; -public class ClientSideCacheLibsTest { - +public class CaffeineClientSideCacheTest { + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); - + protected Jedis control; - + @Before public void setUp() throws Exception { control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); control.flushAll(); } - + @After public void tearDown() throws Exception { control.close(); } - + private static final Supplier clientConfig = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); - + private static final Supplier> singleConnectionPoolConfig = () -> { ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); poolConfig.setMaxTotal(1); return poolConfig; }; - - @Test - public void guavaSimple() { - GuavaClientSideCache guava = GuavaClientSideCache.builder().maximumSize(10).ttl(10) - .hashFunction(com.google.common.hash.Hashing.farmHashFingerprint64()).build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? - } - } - - @Test - public void guavaMore() { - - com.google.common.cache.Cache guava = CacheBuilder.newBuilder().recordStats().build(); - - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava), - singleConnectionPoolConfig.get())) { - control.set("foo", "bar"); - assertEquals(0, guava.size()); - assertEquals("bar", jedis.get("foo")); - assertEquals(1, guava.size()); - control.flushAll(); - assertEquals(1, guava.size()); - assertEquals("bar", jedis.get("foo")); - assertEquals(1, guava.size()); - jedis.ping(); - assertEquals(0, guava.size()); - assertNull(jedis.get("foo")); - assertEquals(0, guava.size()); - } - - com.google.common.cache.CacheStats stats = guava.stats(); - assertEquals(1L, stats.hitCount()); - assertThat(stats.missCount(), Matchers.greaterThan(0L)); - } - + @Test - public void caffeineSimple() { + public void simple() { CaffeineClientSideCache caffeine = CaffeineClientSideCache.builder().maximumSize(10).ttl(10).build(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { control.set("foo", "bar"); @@ -97,12 +62,12 @@ public void caffeineSimple() { assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? } } - + @Test - public void caffeineMore() { - - com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build(); - + public void individualCommandsAndThenStats() { + + Cache caffeine = Caffeine.newBuilder().recordStats().build(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine, new OpenHftCommandHasher()), singleConnectionPoolConfig.get())) { @@ -119,9 +84,51 @@ public void caffeineMore() { assertNull(jedis.get("foo")); assertEquals(0, caffeine.estimatedSize()); } - - com.github.benmanes.caffeine.cache.stats.CacheStats stats = caffeine.stats(); + + CacheStats stats = caffeine.stats(); assertEquals(1L, stats.hitCount()); assertThat(stats.missCount(), Matchers.greaterThan(0L)); } + + @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 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.lessThan(maxEstimatedSize)); + } + } + assertThat(caffeine.stats().evictionCount(), Matchers.greaterThan(count - maxEstimatedSize)); + } + + @Test + public void timeToLive() throws InterruptedException { + 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))) { + for (int i = 0; i < count; i++) { + jedis.get("k" + i); + } + } + 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)); + } + } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java new file mode 100644 index 0000000000..54f1c9f988 --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -0,0 +1,111 @@ +package redis.clients.jedis.csc; + +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.LinkedHashMap; +import java.util.function.Supplier; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +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 ClientSideCacheFunctionalityTest { + + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + + protected Jedis control; + + @Before + public void setUp() throws Exception { + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + + private static final Supplier clientConfig + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + + private static final Supplier> singleConnectionPoolConfig + = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + @Test + public void flushEntireCache() { + int count = 1000; + for (int i = 0; i < count; i++) { + control.set("k" + i, "v" + i); + } + + HashMap map = new HashMap<>(); + ClientSideCache clientSideCache = new MapClientSideCache(map); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + for (int i = 0; i < count; i++) { + jedis.get("k" + i); + } + } + + assertEquals(count, map.size()); + clientSideCache.clear(); + assertEquals(0, map.size()); + } + + @Test + public void removeSpecificKey() { + int count = 1000; + for (int i = 0; i < count; i++) { + control.set("k" + i, "v" + i); + } + + // 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); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + 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; + Long hash = commandHashes.get(i); + assertTrue(map.containsKey(hash)); + clientSideCache.removeKey(key); + assertFalse(map.containsKey(hash)); + } + } + + @Test + public void multiKeyOperation() { + control.set("k1", "v1"); + control.set("k2", "v2"); + + HashMap map = new HashMap<>(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map))) { + jedis.mget("k1", "k2"); + assertEquals(1, map.size()); + } + } + +} diff --git a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java new file mode 100644 index 0000000000..60f93b3e54 --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java @@ -0,0 +1,152 @@ +package redis.clients.jedis.csc; + +import static org.hamcrest.MatcherAssert.assertThat; +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 com.google.common.hash.Hashing; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +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 GuavaClientSideCacheTest { + + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + + protected Jedis control; + + @Before + public void setUp() throws Exception { + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + + private static final Supplier clientConfig + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + + private static final Supplier> singleConnectionPoolConfig + = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + @Test + public void simple() { + GuavaClientSideCache guava = GuavaClientSideCache.builder().maximumSize(10).ttl(10) + .hashFunction(Hashing.farmHashFingerprint64()).build(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { + control.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + control.del("foo"); + assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + } + } + + @Test + public void individualCommandsAndThenStats() { + + Cache guava = CacheBuilder.newBuilder().recordStats().build(); + + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new GuavaClientSideCache(guava), + singleConnectionPoolConfig.get())) { + control.set("foo", "bar"); + assertEquals(0, guava.size()); + assertEquals("bar", jedis.get("foo")); + assertEquals(1, guava.size()); + control.flushAll(); + assertEquals(1, guava.size()); + assertEquals("bar", jedis.get("foo")); + assertEquals(1, guava.size()); + jedis.ping(); + assertEquals(0, guava.size()); + assertNull(jedis.get("foo")); + assertEquals(0, guava.size()); + } + + CacheStats stats = guava.stats(); + assertEquals(1L, stats.hitCount()); + assertThat(stats.missCount(), Matchers.greaterThan(0L)); + } + + @Test + 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()); + jedis.get("k1"); + assertEquals(1, guava.size()); + assertEquals(0, guava.stats().evictionCount()); + jedis.get("k2"); + assertEquals(1, guava.size()); + assertEquals(1, guava.stats().evictionCount()); + } + } + + @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 { + 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))) { + for (int i = 0; i < count; i++) { + jedis.get("k" + i); + } + } + 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)); + } + +} diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 39f78df68f..4b352a1fb9 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.function.Supplier; + import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; import org.junit.After; diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index c065fc8b02..b6eb6bec9f 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; + import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; From 6a1dfc8060df8a58a1b69ae1af4c9f9e238ccf54 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Tue, 7 May 2024 00:28:18 +0600 Subject: [PATCH 18/48] Test JedisURIHelper#getClientSideCache(URI) (#3835) --- .../clients/jedis/util/JedisURIHelper.java | 6 +- .../AllowAndDenyListClientSideCacheTest.java | 38 +--------- .../csc/CaffeineClientSideCacheTest.java | 72 ++++++++---------- .../csc/ClientSideCacheFunctionalityTest.java | 74 +++++++++---------- .../jedis/csc/ClientSideCacheTestBase.java | 45 +++++++++++ .../jedis/csc/GuavaClientSideCacheTest.java | 62 ++++++---------- 6 files changed, 138 insertions(+), 159 deletions(-) create mode 100644 src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java index 9d83ff83a4..4e4f96fee4 100644 --- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java +++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java @@ -113,7 +113,7 @@ public static ClientSideCache getClientSideCache(URI uri) { try { maxSize = Integer.parseInt(val); } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("Value of cache_max_size must be an integer.", nfe); + throw new IllegalArgumentException("Value of cache_max_size must be an integer (no of commands).", nfe); } break; @@ -121,7 +121,7 @@ public static ClientSideCache getClientSideCache(URI uri) { try { ttl = Integer.parseInt(val); } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("Value of cache_ttl must be an integer denoting seconds.", nfe); + throw new IllegalArgumentException("Value of cache_ttl must be an integer (in seconds).", nfe); } break; } @@ -132,7 +132,7 @@ public static ClientSideCache getClientSideCache(URI uri) { return null; } if (!guava && !caffeine && (maxSize != null || ttl != null)) { - throw new IllegalArgumentException("The cache library (guava OR caffeine) must be selected."); + throw new IllegalArgumentException("A supported caching library (guava OR caffeine) must be selected."); } if (ZERO_INTEGER.equals(ttl)) { ttl = null; // below, only null will be checked diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java index 17672ff955..ceb279032d 100644 --- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java @@ -5,50 +5,14 @@ import static org.junit.Assert.assertEquals; import java.util.HashMap; -import java.util.function.Supplier; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import redis.clients.jedis.Connection; -import redis.clients.jedis.ConnectionPoolConfig; -import redis.clients.jedis.DefaultJedisClientConfig; -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; import redis.clients.jedis.Protocol; import redis.clients.jedis.csc.util.AllowAndDenyListWithStringKeys; -public class AllowAndDenyListClientSideCacheTest { - - protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); - - protected Jedis control; - - @Before - public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); - control.flushAll(); - } - - @After - public void tearDown() throws Exception { - control.close(); - } - - private static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); - - private static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; +public class AllowAndDenyListClientSideCacheTest extends ClientSideCacheTestBase { @Test public void none() { diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index 85d2167c90..cd6664f797 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -8,50 +8,15 @@ 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 java.util.function.Supplier; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; import org.junit.Test; - -import redis.clients.jedis.Connection; -import redis.clients.jedis.ConnectionPoolConfig; -import redis.clients.jedis.DefaultJedisClientConfig; -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; +import redis.clients.jedis.util.JedisURIHelper; + +public class CaffeineClientSideCacheTest extends ClientSideCacheTestBase { -public class CaffeineClientSideCacheTest { - - protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); - - protected Jedis control; - - @Before - public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); - control.flushAll(); - } - - @After - public void tearDown() throws Exception { - control.close(); - } - - private static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); - - private static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; - @Test public void simple() { CaffeineClientSideCache caffeine = CaffeineClientSideCache.builder().maximumSize(10).ttl(10).build(); @@ -62,12 +27,12 @@ public void simple() { assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? } } - + @Test public void individualCommandsAndThenStats() { - + Cache caffeine = Caffeine.newBuilder().recordStats().build(); - + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineClientSideCache(caffeine, new OpenHftCommandHasher()), singleConnectionPoolConfig.get())) { @@ -84,7 +49,7 @@ public void individualCommandsAndThenStats() { assertNull(jedis.get("foo")); assertEquals(0, caffeine.estimatedSize()); } - + CacheStats stats = caffeine.stats(); assertEquals(1L, stats.hitCount()); assertThat(stats.missCount(), Matchers.greaterThan(0L)); @@ -131,4 +96,25 @@ public void timeToLive() throws InterruptedException { assertThat(caffeine.stats().evictionCount(), Matchers.equalTo((long) count)); } + @Test + public void uriSimple() { + URI uri = URI.create(baseUrl + "?cache_lib=caffeine"); + ClientSideCache cache = JedisURIHelper.getClientSideCache(uri); + assertThat(cache, Matchers.instanceOf(CaffeineClientSideCache.class)); + } + + @Test + public void uriAllParams() { + URI uri = URI.create(baseUrl + "?cache_lib=caffeine&cache_max_size=1000&cache_ttl=10"); + ClientSideCache cache = JedisURIHelper.getClientSideCache(uri); + assertThat(cache, Matchers.instanceOf(CaffeineClientSideCache.class)); + } + + @Test + public void uriMaxSizeZeroMeansNull() { + URI uri = URI.create(baseUrl + "?cache_lib=caffeine&cache_max_size=0"); + ClientSideCache cache = JedisURIHelper.getClientSideCache(uri); + assertThat(cache, Matchers.nullValue()); + } + } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index 54f1c9f988..6670da47d5 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -1,53 +1,21 @@ 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 java.util.function.Supplier; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.junit.After; -import org.junit.Before; import org.junit.Test; - -import redis.clients.jedis.Connection; -import redis.clients.jedis.ConnectionPoolConfig; -import redis.clients.jedis.DefaultJedisClientConfig; -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; +import redis.clients.jedis.util.JedisURIHelper; -public class ClientSideCacheFunctionalityTest { - - protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); - - protected Jedis control; - - @Before - public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); - control.flushAll(); - } - - @After - public void tearDown() throws Exception { - control.close(); - } - - private static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); - - private static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; +public class ClientSideCacheFunctionalityTest extends ClientSideCacheTestBase { @Test public void flushEntireCache() { @@ -108,4 +76,34 @@ public void multiKeyOperation() { } } + @Test + public void uriNoParam() { + URI uri = URI.create(baseUrl + "?"); + assertNull(JedisURIHelper.getClientSideCache(uri)); + } + + @Test + public void uriUnknownLib() { + URI uri = URI.create(baseUrl + "?cache_lib=unknown"); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, + () -> JedisURIHelper.getClientSideCache(uri)); + assertEquals("Unsupported library unknown", iae.getMessage()); + } + + @Test + public void uriNoLib() { + String[] otherParams + = new String[]{ + "?cache_max_size=1000", + "?cache_ttl=10", + "?cache_max_size=1000&cache_ttl=10" + }; + Arrays.stream(otherParams).forEach(urlParams -> { + URI uri = URI.create(baseUrl + urlParams); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, + () -> JedisURIHelper.getClientSideCache(uri)); + assertEquals("A supported caching library (guava OR caffeine) must be selected.", iae.getMessage()); + }); + } + } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java new file mode 100644 index 0000000000..6eae8bc8ae --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java @@ -0,0 +1,45 @@ +package redis.clients.jedis.csc; + +import java.util.function.Supplier; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.junit.After; +import org.junit.Before; + +import redis.clients.jedis.Connection; +import redis.clients.jedis.ConnectionPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisClientConfig; + +abstract class ClientSideCacheTestBase { + + protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + + protected static final String baseUrl = "redis://:foobared@" + hnp.toString() + "/"; + + protected Jedis control; + + @Before + public void setUp() throws Exception { + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + + protected static final Supplier clientConfig + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + + 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 60f93b3e54..33a23c55c0 100644 --- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java @@ -9,49 +9,14 @@ import com.google.common.cache.CacheStats; import com.google.common.hash.Hashing; +import java.net.URI; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; import org.junit.Test; - -import redis.clients.jedis.Connection; -import redis.clients.jedis.ConnectionPoolConfig; -import redis.clients.jedis.DefaultJedisClientConfig; -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; +import redis.clients.jedis.util.JedisURIHelper; -public class GuavaClientSideCacheTest { - - protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); - - protected Jedis control; - - @Before - public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); - control.flushAll(); - } - - @After - public void tearDown() throws Exception { - control.close(); - } - - private static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); - - private static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; +public class GuavaClientSideCacheTest extends ClientSideCacheTestBase { @Test public void simple() { @@ -149,4 +114,25 @@ public void timeToLive() throws InterruptedException { assertThat(guava.stats().evictionCount(), Matchers.equalTo((long) count)); } + @Test + public void uriSimple() { + URI uri = URI.create(baseUrl + "?cache_lib=guava"); + ClientSideCache cache = JedisURIHelper.getClientSideCache(uri); + assertThat(cache, Matchers.instanceOf(GuavaClientSideCache.class)); + } + + @Test + public void uriAllParams() { + URI uri = URI.create(baseUrl + "?cache_lib=guava&cache_max_size=1000&cache_ttl=10"); + ClientSideCache cache = JedisURIHelper.getClientSideCache(uri); + assertThat(cache, Matchers.instanceOf(GuavaClientSideCache.class)); + } + + @Test + public void uriMaxSizeZeroMeansNull() { + URI uri = URI.create(baseUrl + "?cache_lib=guava&cache_max_size=0"); + ClientSideCache cache = JedisURIHelper.getClientSideCache(uri); + assertThat(cache, Matchers.nullValue()); + } + } From 103575d0b48ca5e4567b5aeb6c98dba7ef914017 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:04:56 +0600 Subject: [PATCH 19/48] Merge fix: after introducing EndpointConfig in #3836 --- .../clients/jedis/csc/ClientSideCacheTestBase.java | 11 +++++++---- .../jedis/csc/JedisPooledClientSideCacheTest.java | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java index 6eae8bc8ae..15531a5dfb 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java @@ -8,6 +8,7 @@ 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; @@ -15,15 +16,17 @@ abstract class ClientSideCacheTestBase { - protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + private static final EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone1"); - protected static final String baseUrl = "redis://:foobared@" + hnp.toString() + "/"; + 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()); + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); // TODO: use EndpointConfig control.flushAll(); } @@ -33,7 +36,7 @@ public void tearDown() throws Exception { } protected static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); // TODO: use EndpointConfig protected static final Supplier> singleConnectionPoolConfig = () -> { diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java index 4b352a1fb9..2d1889fbc8 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -16,6 +16,7 @@ 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; @@ -24,13 +25,15 @@ public class JedisPooledClientSideCacheTest { - protected static final HostAndPort hnp = HostAndPorts.getRedisServers().get(1); + private static final EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone1"); + + protected static final HostAndPort hnp = endpoint.getHostAndPort(); protected Jedis control; @Before public void setUp() throws Exception { - control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); + control = new Jedis(hnp, DefaultJedisClientConfig.builder().password("foobared").build()); // TODO: use EndpointConfig control.flushAll(); } @@ -40,7 +43,7 @@ public void tearDown() throws Exception { } private static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); + = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); // TODO: use EndpointConfig private static final Supplier> singleConnectionPoolConfig = () -> { From a347d7c4d699d902843d6fe6a3eb27be5152daf0 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:29:30 +0600 Subject: [PATCH 20/48] Tweak maximumSize test in CaffeineClientSideCacheTest --- .../clients/jedis/csc/CaffeineClientSideCacheTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index cd6664f797..f5752457a3 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -58,7 +58,7 @@ public void individualCommandsAndThenStats() { @Test public void maximumSize() { final long maxSize = 10; - final long maxEstimatedSize = 40; + final long maxEstimatedSize = 42; int count = 1000; for (int i = 0; i < count; i++) { control.set("k" + i, "v" + i); @@ -68,10 +68,10 @@ public void maximumSize() { 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.lessThan(maxEstimatedSize)); + assertThat(caffeine.estimatedSize(), Matchers.lessThanOrEqualTo(maxEstimatedSize)); } } - assertThat(caffeine.stats().evictionCount(), Matchers.greaterThan(count - maxEstimatedSize)); + assertThat(caffeine.stats().evictionCount(), Matchers.greaterThanOrEqualTo(count - maxEstimatedSize)); } @Test From 11ce88e0b5070becfe8cd4cf44a8ea9ea033337e Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:58:52 +0600 Subject: [PATCH 21/48] Little more tweak maximumSize test in CaffeineClientSideCacheTest --- .../redis/clients/jedis/csc/CaffeineClientSideCacheTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index f5752457a3..92b8d14c3a 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -58,7 +58,7 @@ public void individualCommandsAndThenStats() { @Test public void maximumSize() { final long maxSize = 10; - final long maxEstimatedSize = 42; + final long maxEstimatedSize = 50; int count = 1000; for (int i = 0; i < count; i++) { control.set("k" + i, "v" + i); From 6b9d3387c63398bfc26f3c043ecc7f38b15c2381 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 6 Jun 2024 20:59:48 +0200 Subject: [PATCH 22/48] Fix incompatibilities with the latest RedisStack (#3855) * Fix tests - Skip Graph tests - Fix JSON RESP3 test * JSON.GET behaves identically on RESP2 and RESP3 --- .../redis/clients/jedis/CommandObjects.java | 3 +-- .../CommandObjectsJsonCommandsTest.java | 5 ++-- .../PipeliningBaseGraphCommandsTest.java | 2 ++ .../jedis/modules/graph/GraphAPITest.java | 6 ++--- .../modules/graph/GraphPipelineTest.java | 6 ++--- .../modules/graph/GraphTransactionTest.java | 2 ++ .../jedis/modules/graph/GraphValuesTest.java | 2 ++ .../jedis/modules/graph/PathBuilderTest.java | 2 ++ .../jedis/modules/json/RedisJsonV2Test.java | 25 ------------------- 9 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 896ef38293..da2f11999c 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -3495,8 +3495,7 @@ public final CommandObject jsonMerge(String key, Path path, Object pojo) } public final CommandObject jsonGet(String key) { - return new CommandObject<>(commandArguments(JsonCommand.GET).key(key), - protocol != RedisProtocol.RESP3 ? JSON_GENERIC_OBJECT : JsonBuilderFactory.JSON_OBJECT); + return new CommandObject<>(commandArguments(JsonCommand.GET).key(key), JSON_GENERIC_OBJECT); } @Deprecated diff --git a/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java b/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java index a416be2103..81af1bbc20 100644 --- a/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java @@ -269,14 +269,13 @@ public void testJsonGenericObjectResp3() { assertThat(setResult, equalTo("OK")); Object getRoot = exec(commandObjects.jsonGet(key)); - assertThat(getRoot, instanceOf(JSONArray.class)); + assertThat(getRoot, instanceOf(JSONObject.class)); JSONObject expectedPerson = new JSONObject(); expectedPerson.put("name", "John Doe"); expectedPerson.put("age", 30); - JSONArray expected = new JSONArray().put(expectedPerson); - assertThat(getRoot, jsonEquals(expected)); + assertThat(expectedPerson, jsonEquals(getRoot)); } @Test diff --git a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java index 7a16176e54..afb39e928b 100644 --- a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java +++ b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java @@ -9,10 +9,12 @@ import java.util.List; import java.util.Map; +import org.junit.Ignore; import org.junit.Test; import redis.clients.jedis.Response; import redis.clients.jedis.graph.ResultSet; +@Ignore public class PipeliningBaseGraphCommandsTest extends PipeliningBaseMockedTestBase { @Test diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java index 46b135070c..00a500c82e 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java @@ -9,10 +9,7 @@ import java.util.*; -import org.junit.After; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -25,6 +22,7 @@ import redis.clients.jedis.modules.RedisModuleCommandsTestBase; +@Ignore @RunWith(Parameterized.class) public class GraphAPITest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java index f69dd06712..095d639ea8 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java @@ -9,10 +9,7 @@ import java.util.Iterator; import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -26,6 +23,7 @@ import redis.clients.jedis.graph.entities.Property; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; +@Ignore @RunWith(Parameterized.class) public class GraphPipelineTest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java index 9a7a8e502c..b6252e5ffd 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java @@ -10,6 +10,7 @@ import java.util.List; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -23,6 +24,7 @@ import redis.clients.jedis.graph.entities.Property; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; +@Ignore @RunWith(Parameterized.class) public class GraphTransactionTest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java index ea9c6a7799..92c14bef5c 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -12,6 +13,7 @@ import redis.clients.jedis.graph.ResultSet; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; +@Ignore @RunWith(Parameterized.class) public class GraphValuesTest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java b/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java index 95011cc7bc..046d7e4b62 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java @@ -3,9 +3,11 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import org.junit.Ignore; import org.junit.Test; import redis.clients.jedis.graph.entities.Edge; +@Ignore public class PathBuilderTest { @Test diff --git a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java index 6168c2e2de..688e64a12e 100644 --- a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java +++ b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java @@ -50,8 +50,6 @@ public void setUp() { @Test public void basicSetGetShouldSucceed() { - Assume.assumeFalse(protocol == RedisProtocol.RESP3); - // naive set with a path jsonV2.jsonSetWithEscape("null", ROOT_PATH, (Object) null); assertJsonArrayEquals(jsonArray((Object) null), jsonV2.jsonGet("null", ROOT_PATH)); @@ -72,29 +70,6 @@ public void basicSetGetShouldSucceed() { assertJsonArrayEquals(jsonArray("strung"), jsonV2.jsonGet("obj", p)); } - @Test - public void basicSetGetShouldSucceedResp3() { - Assume.assumeTrue(protocol == RedisProtocol.RESP3); - - // naive set with a path - jsonV2.jsonSetWithEscape("null", ROOT_PATH, (Object) null); - assertJsonArrayEquals(jsonArray((Object) null), jsonV2.jsonGet("null", ROOT_PATH)); - - // real scalar value and no path - jsonV2.jsonSetWithEscape("str", "strong"); - assertJsonArrayEquals(jsonArray("strong"), jsonV2.jsonGet("str")); - - // a slightly more complex object - IRLObject obj = new IRLObject(); - jsonV2.jsonSetWithEscape("obj", obj); - assertJsonArrayEquals(jsonArray(new JSONObject(gson.toJson(obj))), jsonV2.jsonGet("obj")); - - // check an update - Path2 p = Path2.of(".str"); - jsonV2.jsonSet("obj", p, gson.toJson("strung")); - assertJsonArrayEquals(jsonArray("strung"), jsonV2.jsonGet("obj", p)); - } - @Test public void setExistingPathOnlyIfExistsShouldSucceed() { jsonV2.jsonSetWithEscape("obj", new IRLObject()); From a0b0c59558ad64bac8955e0ead7dfc10fca0e97f Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:34:10 +0600 Subject: [PATCH 23/48] Revert "Fix incompatibilities with the latest RedisStack (#3855)" This reverts commit 6b9d3387c63398bfc26f3c043ecc7f38b15c2381. --- .../redis/clients/jedis/CommandObjects.java | 3 ++- .../CommandObjectsJsonCommandsTest.java | 5 ++-- .../PipeliningBaseGraphCommandsTest.java | 2 -- .../jedis/modules/graph/GraphAPITest.java | 6 +++-- .../modules/graph/GraphPipelineTest.java | 6 +++-- .../modules/graph/GraphTransactionTest.java | 2 -- .../jedis/modules/graph/GraphValuesTest.java | 2 -- .../jedis/modules/graph/PathBuilderTest.java | 2 -- .../jedis/modules/json/RedisJsonV2Test.java | 25 +++++++++++++++++++ 9 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index da2f11999c..896ef38293 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -3495,7 +3495,8 @@ public final CommandObject jsonMerge(String key, Path path, Object pojo) } public final CommandObject jsonGet(String key) { - return new CommandObject<>(commandArguments(JsonCommand.GET).key(key), JSON_GENERIC_OBJECT); + return new CommandObject<>(commandArguments(JsonCommand.GET).key(key), + protocol != RedisProtocol.RESP3 ? JSON_GENERIC_OBJECT : JsonBuilderFactory.JSON_OBJECT); } @Deprecated diff --git a/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java b/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java index 81af1bbc20..a416be2103 100644 --- a/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/commandobjects/CommandObjectsJsonCommandsTest.java @@ -269,13 +269,14 @@ public void testJsonGenericObjectResp3() { assertThat(setResult, equalTo("OK")); Object getRoot = exec(commandObjects.jsonGet(key)); - assertThat(getRoot, instanceOf(JSONObject.class)); + assertThat(getRoot, instanceOf(JSONArray.class)); JSONObject expectedPerson = new JSONObject(); expectedPerson.put("name", "John Doe"); expectedPerson.put("age", 30); - assertThat(expectedPerson, jsonEquals(getRoot)); + JSONArray expected = new JSONArray().put(expectedPerson); + assertThat(getRoot, jsonEquals(expected)); } @Test diff --git a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java index afb39e928b..7a16176e54 100644 --- a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java +++ b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseGraphCommandsTest.java @@ -9,12 +9,10 @@ import java.util.List; import java.util.Map; -import org.junit.Ignore; import org.junit.Test; import redis.clients.jedis.Response; import redis.clients.jedis.graph.ResultSet; -@Ignore public class PipeliningBaseGraphCommandsTest extends PipeliningBaseMockedTestBase { @Test diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java index 00a500c82e..46b135070c 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphAPITest.java @@ -9,7 +9,10 @@ import java.util.*; -import org.junit.*; +import org.junit.After; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -22,7 +25,6 @@ import redis.clients.jedis.modules.RedisModuleCommandsTestBase; -@Ignore @RunWith(Parameterized.class) public class GraphAPITest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java index 095d639ea8..f69dd06712 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphPipelineTest.java @@ -9,7 +9,10 @@ import java.util.Iterator; import java.util.List; -import org.junit.*; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -23,7 +26,6 @@ import redis.clients.jedis.graph.entities.Property; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; -@Ignore @RunWith(Parameterized.class) public class GraphPipelineTest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java index b6252e5ffd..9a7a8e502c 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphTransactionTest.java @@ -10,7 +10,6 @@ import java.util.List; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -24,7 +23,6 @@ import redis.clients.jedis.graph.entities.Property; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; -@Ignore @RunWith(Parameterized.class) public class GraphTransactionTest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java b/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java index 92c14bef5c..ea9c6a7799 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/GraphValuesTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertEquals; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -13,7 +12,6 @@ import redis.clients.jedis.graph.ResultSet; import redis.clients.jedis.modules.RedisModuleCommandsTestBase; -@Ignore @RunWith(Parameterized.class) public class GraphValuesTest extends RedisModuleCommandsTestBase { diff --git a/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java b/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java index 046d7e4b62..95011cc7bc 100644 --- a/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java +++ b/src/test/java/redis/clients/jedis/modules/graph/PathBuilderTest.java @@ -3,11 +3,9 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import org.junit.Ignore; import org.junit.Test; import redis.clients.jedis.graph.entities.Edge; -@Ignore public class PathBuilderTest { @Test diff --git a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java index 688e64a12e..6168c2e2de 100644 --- a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java +++ b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java @@ -50,6 +50,8 @@ public void setUp() { @Test public void basicSetGetShouldSucceed() { + Assume.assumeFalse(protocol == RedisProtocol.RESP3); + // naive set with a path jsonV2.jsonSetWithEscape("null", ROOT_PATH, (Object) null); assertJsonArrayEquals(jsonArray((Object) null), jsonV2.jsonGet("null", ROOT_PATH)); @@ -70,6 +72,29 @@ public void basicSetGetShouldSucceed() { assertJsonArrayEquals(jsonArray("strung"), jsonV2.jsonGet("obj", p)); } + @Test + public void basicSetGetShouldSucceedResp3() { + Assume.assumeTrue(protocol == RedisProtocol.RESP3); + + // naive set with a path + jsonV2.jsonSetWithEscape("null", ROOT_PATH, (Object) null); + assertJsonArrayEquals(jsonArray((Object) null), jsonV2.jsonGet("null", ROOT_PATH)); + + // real scalar value and no path + jsonV2.jsonSetWithEscape("str", "strong"); + assertJsonArrayEquals(jsonArray("strong"), jsonV2.jsonGet("str")); + + // a slightly more complex object + IRLObject obj = new IRLObject(); + jsonV2.jsonSetWithEscape("obj", obj); + assertJsonArrayEquals(jsonArray(new JSONObject(gson.toJson(obj))), jsonV2.jsonGet("obj")); + + // check an update + Path2 p = Path2.of(".str"); + jsonV2.jsonSet("obj", p, gson.toJson("strung")); + assertJsonArrayEquals(jsonArray("strung"), jsonV2.jsonGet("obj", p)); + } + @Test public void setExistingPathOnlyIfExistsShouldSucceed() { jsonV2.jsonSetWithEscape("obj", new IRLObject()); From 92c09f3aab79629b505743b5a85169590410555e Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:23:51 +0600 Subject: [PATCH 24/48] [TEMPORARY] [TEST] Use redis-stack-server:7.4.0-rc1 image for testing --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1800f00d7e..6262d61ad4 100644 --- a/Makefile +++ b/Makefile @@ -446,7 +446,7 @@ start: stunnel cleanup compile-module echo "$$REDIS_UDS" | redis-server - echo "$$REDIS_UNAVAILABLE_CONF" | redis-server - redis-cli -a cluster --cluster create 127.0.0.1:7479 127.0.0.1:7480 127.0.0.1:7481 --cluster-yes - docker run -p 6479:6379 --name jedis-stack -d redis/redis-stack-server:edge + docker run -p 6479:6379 --name jedis-stack -d redis/redis-stack-server:7.4.0-rc1 cleanup: - rm -vf /tmp/redis_cluster_node*.conf 2>/dev/null From 33d47712553b809c540a88a274632dd559732e76 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:23:24 +0600 Subject: [PATCH 25/48] Support RediSearch DIALECT 5 (#3831) - [x] Avoid escaping at query time - [ ] Alias for tag fields (EXACT) - [x] Avoid repeating for numeral equality - [x] New dialect (5) --- .../clients/jedis/search/querybuilder/Values.java | 4 ++++ .../clients/jedis/modules/search/SearchTest.java | 13 +++++++++++++ .../modules/search/SearchWithParamsTest.java | 15 +++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/Values.java b/src/main/java/redis/clients/jedis/search/querybuilder/Values.java index 67256f2359..7b4971be19 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/Values.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/Values.java @@ -41,10 +41,14 @@ public static RangeValue between(int from, int to) { return new LongRangeValue(from, to); } + // TODO: change to simpler [d] available since RedisStack 7.4.0-rc1; + // currently kept for backward compatibility public static RangeValue eq(double d) { return new DoubleRangeValue(d, d); } + // TODO: change to simpler [i] available since RedisStack 7.4.0-rc1; + // currently kept for backward compatibility public static RangeValue eq(int i) { return new LongRangeValue(i, i); } diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java index 1bc6cb345d..7307e621c8 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java @@ -412,6 +412,9 @@ public void testQueryParams() { Query query = new Query("@numval:[$min $max]").addParam("min", 1).addParam("max", 2).dialect(2); assertEquals(2, client.ftSearch(index, query).getTotalResults()); + + query = new Query("@numval:[$eq]").addParam("eq", 2).dialect(5); + assertEquals(1, client.ftSearch(index, query).getTotalResults()); } @Test @@ -532,6 +535,14 @@ public void testJsonWithAlias() { res = client.ftSearch(index, new Query("@num:[0 10]")); assertEquals(1, res.getTotalResults()); assertEquals("king:2", res.getDocuments().get(0).getId()); + + res = client.ftSearch(index, new Query("@num:[42 42]")); + assertEquals(1, res.getTotalResults()); + assertEquals("king:1", res.getDocuments().get(0).getId()); + + res = client.ftSearch(index, new Query("@num:[42]").dialect(5)); + assertEquals(1, res.getTotalResults()); + assertEquals("king:1", res.getDocuments().get(0).getId()); } @Test @@ -773,6 +784,7 @@ public void getTagField() { assertEquals(1, client.ftSearch(index, new Query("@category:{yellow}")).getTotalResults()); assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults()); assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\;purple}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("@category:{orange;purple}").dialect(5)).getTotalResults()); assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults()); assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange;purple")), @@ -814,6 +826,7 @@ public void testGetTagFieldWithNonDefaultSeparator() { assertEquals(1, client.ftSearch(index, new Query("hello @category:{yellow}")).getTotalResults()); assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults()); assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\,purple}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("@category:{orange,purple}").dialect(5)).getTotalResults()); assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults()); assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange,purple")), diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java index 897da8eece..dff74f8465 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java @@ -517,6 +517,9 @@ public void testQueryParams() { assertEquals(2, client.ftSearch(index, "@numval:[$min $max]", FTSearchParams.searchParams().params(paramValues) .dialect(2)).getTotalResults()); + + assertEquals(1, client.ftSearch(index, "@numval:[$eq]", + FTSearchParams.searchParams().addParam("eq", 2).dialect(5)).getTotalResults()); } @Test @@ -574,6 +577,14 @@ public void testJsonWithAlias() { res = client.ftSearch(index, "@num:[0 10]"); assertEquals(1, res.getTotalResults()); assertEquals("king:2", res.getDocuments().get(0).getId()); + + res = client.ftSearch(index, "@num:[42 42]", FTSearchParams.searchParams()); + assertEquals(1, res.getTotalResults()); + assertEquals("king:1", res.getDocuments().get(0).getId()); + + res = client.ftSearch(index, "@num:[42]", FTSearchParams.searchParams().dialect(5)); + assertEquals(1, res.getTotalResults()); + assertEquals("king:1", res.getDocuments().get(0).getId()); } @Test @@ -777,6 +788,8 @@ public void getTagField() { assertEquals(1, client.ftSearch(index, "@category:{yellow}").getTotalResults()); assertEquals(0, client.ftSearch(index, "@category:{purple}").getTotalResults()); assertEquals(1, client.ftSearch(index, "@category:{orange\\;purple}").getTotalResults()); + assertEquals(1, client.ftSearch(index, "@category:{orange;purple}", + FTSearchParams.searchParams().dialect(5)).getTotalResults()); assertEquals(4, client.ftSearch(index, "hello").getTotalResults()); assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange;purple")), @@ -816,6 +829,8 @@ public void testGetTagFieldWithNonDefaultSeparator() { assertEquals(1, client.ftSearch(index, "hello @category:{yellow}").getTotalResults()); assertEquals(0, client.ftSearch(index, "@category:{purple}").getTotalResults()); assertEquals(1, client.ftSearch(index, "@category:{orange\\,purple}").getTotalResults()); + assertEquals(1, client.ftSearch(index, "@category:{orange,purple}", + FTSearchParams.searchParams().dialect(5)).getTotalResults()); assertEquals(4, client.ftSearch(index, "hello").getTotalResults()); assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange,purple")), From 7c898b8ddd4065ccb50200fc2ce65ae13bf2be41 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:24:55 +0600 Subject: [PATCH 26/48] Support FLOAT16 and BFLOAT16 VecSim storage types (#3849) --- .../modules/search/SearchWithParamsTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java index dff74f8465..e972124143 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java @@ -1071,6 +1071,28 @@ public void testFlatVectorSimilarity() { assertEquals("0", doc1.get("__v_score")); } + @Test + public void float16StorageType() { + assertOK(client.ftCreate(index, + VectorField.builder().fieldName("v") + .algorithm(VectorField.VectorAlgorithm.HNSW) + .addAttribute("TYPE", "FLOAT16") + .addAttribute("DIM", 4) + .addAttribute("DISTANCE_METRIC", "L2") + .build())); + } + + @Test + public void bfloat16StorageType() { + assertOK(client.ftCreate(index, + VectorField.builder().fieldName("v") + .algorithm(VectorField.VectorAlgorithm.HNSW) + .addAttribute("TYPE", "BFLOAT16") + .addAttribute("DIM", 4) + .addAttribute("DISTANCE_METRIC", "L2") + .build())); + } + @Test public void searchProfile() { assertOK(client.ftCreate(index, TextField.of("t1"), TextField.of("t2"))); From ef79d547157a2db010e68a6095bd51b683c258ae Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:28:13 +0600 Subject: [PATCH 27/48] Test: INTERSECTS and DIJOINT conditions support in GeoSearch (#3862) --- .../GeoShapeFieldsUsageInRediSearch.java | 29 ++++--- .../modules/search/SearchWithParamsTest.java | 80 +++++++++++-------- 2 files changed, 64 insertions(+), 45 deletions(-) diff --git a/src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java b/src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java index fd309def50..ea0570265d 100644 --- a/src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java +++ b/src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java @@ -1,6 +1,5 @@ package redis.clients.jedis.examples; -import java.util.Collections; import org.junit.Assert; import org.locationtech.jts.geom.Coordinate; @@ -14,16 +13,21 @@ import redis.clients.jedis.JedisPooled; import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.search.FTSearchParams; -import redis.clients.jedis.search.RediSearchUtil; import redis.clients.jedis.search.SearchResult; import redis.clients.jedis.search.schemafields.GeoShapeField; /** * As of RediSearch 2.8.4, advanced GEO querying with GEOSHAPE fields is supported. + *

+ * Notes: + *

    + *
  • As of RediSearch 2.8.4, only POLYGON and POINT objects are supported.
  • + *
  • As of RediSearch 2.8.4, only WITHIN and CONTAINS conditions are supported.
  • + *
  • As of RedisStack 7.4.0, support for INTERSECTS and DISJOINT conditions are added.
  • + *
* - * Any object/library producing a - * well-known - * text (WKT) in {@code toString()} method can be used. + * Any object/library producing a + * well-known text (WKT) in {@code toString()} method can be used. * * This example uses the JTS library. *
@@ -65,7 +69,8 @@ public static void main(String[] args) {
     );
 
     // client.hset("small", RediSearchUtil.toStringMap(Collections.singletonMap("geometry", small))); // setting data
-    client.hset("small", "geometry", small.toString()); // simplified setting data
+    // client.hset("small", "geometry", small.toString()); // simplified setting data
+    client.hsetObject("small", "geometry", small); // more simplified setting data
 
     final Polygon large = factory.createPolygon(
         new Coordinate[]{new Coordinate(34.9001, 29.7001),
@@ -74,7 +79,8 @@ public static void main(String[] args) {
     );
 
     // client.hset("large", RediSearchUtil.toStringMap(Collections.singletonMap("geometry", large))); // setting data
-    client.hset("large", "geometry", large.toString()); // simplified setting data
+    // client.hset("large", "geometry", large.toString()); // simplified setting data
+    client.hsetObject("large", "geometry", large); // more simplified setting data
 
     // searching
     final Polygon within = factory.createPolygon(
@@ -84,11 +90,10 @@ public static void main(String[] args) {
     );
 
     SearchResult res = client.ftSearch("geometry-index",
-        "@geometry:[within $poly]", // querying 'within' condition.
-                                    // RediSearch also supports 'contains' condition.
+        "@geometry:[within $poly]",     // query string
         FTSearchParams.searchParams()
             .addParam("poly", within)
-            .dialect(3) // DIALECT '3' is required for this query
+            .dialect(3)                 // DIALECT '3' is required for this query
     ); 
     Assert.assertEquals(1, res.getTotalResults());
     Assert.assertEquals(1, res.getDocuments().size());
@@ -98,10 +103,8 @@ public static void main(String[] args) {
       final WKTReader reader = new WKTReader();
       Geometry object = reader.read(res.getDocuments().get(0).getString("geometry"));
       Assert.assertEquals(small, object);
-    } catch (ParseException ex) {
+    } catch (ParseException ex) { // WKTReader#read throws ParseException
       ex.printStackTrace(System.err);
     }
   }
-
-  // Note: As of RediSearch 2.8.4, only POLYGON and POINT objects are supported.
 }
diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
index e972124143..cc24fc21de 100644
--- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
+++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
@@ -360,42 +360,42 @@ public void geoShapeFilterSpherical() throws ParseException {
     final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(34.9001, 29.7001),
         new Coordinate(34.9001, 29.7100), new Coordinate(34.9100, 29.7100),
         new Coordinate(34.9100, 29.7001), new Coordinate(34.9001, 29.7001)});
-    client.hset("small", "geom", small.toString());
+    client.hsetObject("small", "geom", small);
 
     final Polygon large = factory.createPolygon(new Coordinate[]{new Coordinate(34.9001, 29.7001),
         new Coordinate(34.9001, 29.7200), new Coordinate(34.9200, 29.7200),
         new Coordinate(34.9200, 29.7001), new Coordinate(34.9001, 29.7001)});
-    client.hset("large", "geom", large.toString());
+    client.hsetObject("large", "geom", large);
 
     // within condition
     final Polygon within = factory.createPolygon(new Coordinate[]{new Coordinate(34.9000, 29.7000),
         new Coordinate(34.9000, 29.7150), new Coordinate(34.9150, 29.7150),
         new Coordinate(34.9150, 29.7000), new Coordinate(34.9000, 29.7000)});
 
-    SearchResult res = client.ftSearch(index, "@geom:[within $poly]",
+    SearchResult result = client.ftSearch(index, "@geom:[within $poly]",
         FTSearchParams.searchParams().addParam("poly", within).dialect(3));
-    assertEquals(1, res.getTotalResults());
-    assertEquals(1, res.getDocuments().size());
-    assertEquals(small, reader.read(res.getDocuments().get(0).getString("geom")));
+    assertEquals(1, result.getTotalResults());
+    assertEquals(1, result.getDocuments().size());
+    assertEquals(small, reader.read(result.getDocuments().get(0).getString("geom")));
 
     // contains condition
     final Polygon contains = factory.createPolygon(new Coordinate[]{new Coordinate(34.9002, 29.7002),
         new Coordinate(34.9002, 29.7050), new Coordinate(34.9050, 29.7050),
         new Coordinate(34.9050, 29.7002), new Coordinate(34.9002, 29.7002)});
 
-    res = client.ftSearch(index, "@geom:[contains $poly]",
+    result = client.ftSearch(index, "@geom:[contains $poly]",
         FTSearchParams.searchParams().addParam("poly", contains).dialect(3));
-    assertEquals(2, res.getTotalResults());
-    assertEquals(2, res.getDocuments().size());
+    assertEquals(2, result.getTotalResults());
+    assertEquals(2, result.getDocuments().size());
 
     // point type
     final Point point = factory.createPoint(new Coordinate(34.9010, 29.7010));
     client.hset("point", "geom", point.toString());
 
-    res = client.ftSearch(index, "@geom:[within $poly]",
+    result = client.ftSearch(index, "@geom:[within $poly]",
         FTSearchParams.searchParams().addParam("poly", within).dialect(3));
-    assertEquals(2, res.getTotalResults());
-    assertEquals(2, res.getDocuments().size());
+    assertEquals(2, result.getTotalResults());
+    assertEquals(2, result.getDocuments().size());
   }
 
   @Test
@@ -406,41 +406,57 @@ public void geoShapeFilterFlat() throws ParseException {
     assertOK(client.ftCreate(index, GeoShapeField.of("geom", GeoShapeField.CoordinateSystem.FLAT)));
 
     // polygon type
-    final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(1, 1),
-        new Coordinate(1, 100), new Coordinate(100, 100), new Coordinate(100, 1), new Coordinate(1, 1)});
-    client.hset("small", "geom", small.toString());
+    final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(20, 20),
+        new Coordinate(20, 100), new Coordinate(100, 100), new Coordinate(100, 20), new Coordinate(20, 20)});
+    client.hsetObject("small", "geom", small);
 
-    final Polygon large = factory.createPolygon(new Coordinate[]{new Coordinate(1, 1),
-        new Coordinate(1, 200), new Coordinate(200, 200), new Coordinate(200, 1), new Coordinate(1, 1)});
-    client.hset("large", "geom", large.toString());
+    final Polygon large = factory.createPolygon(new Coordinate[]{new Coordinate(10, 10),
+        new Coordinate(10, 200), new Coordinate(200, 200), new Coordinate(200, 10), new Coordinate(10, 10)});
+    client.hsetObject("large", "geom", large);
 
     // within condition
     final Polygon within = factory.createPolygon(new Coordinate[]{new Coordinate(0, 0),
         new Coordinate(0, 150), new Coordinate(150, 150), new Coordinate(150, 0), new Coordinate(0, 0)});
 
-    SearchResult res = client.ftSearch(index, "@geom:[within $poly]",
+    SearchResult result = client.ftSearch(index, "@geom:[within $poly]",
         FTSearchParams.searchParams().addParam("poly", within).dialect(3));
-    assertEquals(1, res.getTotalResults());
-    assertEquals(1, res.getDocuments().size());
-    assertEquals(small, reader.read(res.getDocuments().get(0).getString("geom")));
+    assertEquals(1, result.getTotalResults());
+    assertEquals(1, result.getDocuments().size());
+    assertEquals(small, reader.read(result.getDocuments().get(0).getString("geom")));
 
     // contains condition
-    final Polygon contains = factory.createPolygon(new Coordinate[]{new Coordinate(2, 2),
-        new Coordinate(2, 50), new Coordinate(50, 50), new Coordinate(50, 2), new Coordinate(2, 2)});
+    final Polygon contains = factory.createPolygon(new Coordinate[]{new Coordinate(25, 25),
+        new Coordinate(25, 50), new Coordinate(50, 50), new Coordinate(50, 25), new Coordinate(25, 25)});
 
-    res = client.ftSearch(index, "@geom:[contains $poly]",
+    result = client.ftSearch(index, "@geom:[contains $poly]",
         FTSearchParams.searchParams().addParam("poly", contains).dialect(3));
-    assertEquals(2, res.getTotalResults());
-    assertEquals(2, res.getDocuments().size());
+    assertEquals(2, result.getTotalResults());
+    assertEquals(2, result.getDocuments().size());
+
+    // intersects and disjoint
+    final Polygon disjointersect = factory.createPolygon(new Coordinate[]{new Coordinate(150, 150),
+        new Coordinate(150, 250), new Coordinate(250, 250), new Coordinate(250, 150), new Coordinate(150, 150)});
+
+    result = client.ftSearch(index, "@geom:[intersects $poly]",
+        FTSearchParams.searchParams().addParam("poly", disjointersect).dialect(3));
+    assertEquals(1, result.getTotalResults());
+    assertEquals(1, result.getDocuments().size());
+    assertEquals(large, reader.read(result.getDocuments().get(0).getString("geom")));
+
+    result = client.ftSearch(index, "@geom:[disjoint $poly]",
+        FTSearchParams.searchParams().addParam("poly", disjointersect).dialect(3));
+    assertEquals(1, result.getTotalResults());
+    assertEquals(1, result.getDocuments().size());
+    assertEquals(small, reader.read(result.getDocuments().get(0).getString("geom")));
 
     // point type
-    final Point point = factory.createPoint(new Coordinate(10, 10));
-    client.hset("point", "geom", point.toString());
+    final Point point = factory.createPoint(new Coordinate(30, 30));
+    client.hsetObject("point", "geom", point);
 
-    res = client.ftSearch(index, "@geom:[within $poly]",
+    result = client.ftSearch(index, "@geom:[within $poly]",
         FTSearchParams.searchParams().addParam("poly", within).dialect(3));
-    assertEquals(2, res.getTotalResults());
-    assertEquals(2, res.getDocuments().size());
+    assertEquals(2, result.getTotalResults());
+    assertEquals(2, result.getDocuments().size());
   }
 
   @Test

From ac18fd00fd919b2f1b2294a2311a56d59fffd33c Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Fri, 14 Jun 2024 10:16:47 +0600
Subject: [PATCH 28/48] Support IGNORE and other optional arguments for
 timeseries commands (#3860)

* Re-implement TS.ADD command with optional arguments
* Implement TS.INCRBY and TS.DECRBY commands with optional arguments
* Support IGNORE argument for TS.[ CREATE | ALTER | ADD | INCRBY | DECRBY] commands

---

* Cover optional arguments for timeseries commands
   - Re-implement TS.ADD command with optional arguments
   - Implement TS.INCRBY and TS.DECRBY commands with optional arguments

* Introduce EncodingFormat enum for 

* Support IGNORE option
   and rename to TSIncrOrDecrByParams
---
 .../redis/clients/jedis/CommandObjects.java   |  20 ++-
 .../redis/clients/jedis/PipeliningBase.java   |  15 ++
 .../redis/clients/jedis/UnifiedJedis.java     |  15 ++
 .../jedis/timeseries/EncodingFormat.java      |  24 ++++
 .../timeseries/RedisTimeSeriesCommands.java   |  55 +++++++-
 .../RedisTimeSeriesPipelineCommands.java      |   7 +
 .../clients/jedis/timeseries/TSAddParams.java | 128 +++++++++++++++++
 .../jedis/timeseries/TSAlterParams.java       |  28 ++++
 .../jedis/timeseries/TSCreateParams.java      |  39 ++++--
 .../timeseries/TSIncrOrDecrByParams.java      | 132 ++++++++++++++++++
 .../jedis/timeseries/TimeSeriesProtocol.java  |   1 +
 .../PipeliningBaseTimeSeriesCommandsTest.java |  47 +++++--
 .../UnifiedJedisTimeSeriesCommandsTest.java   |  71 ++++++++--
 .../modules/timeseries/TimeSeriesTest.java    |  55 ++++++++
 14 files changed, 600 insertions(+), 37 deletions(-)
 create mode 100644 src/main/java/redis/clients/jedis/timeseries/EncodingFormat.java
 create mode 100644 src/main/java/redis/clients/jedis/timeseries/TSAddParams.java
 create mode 100644 src/main/java/redis/clients/jedis/timeseries/TSIncrOrDecrByParams.java

diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java
index 7226a014a7..421b81c4e2 100644
--- a/src/main/java/redis/clients/jedis/CommandObjects.java
+++ b/src/main/java/redis/clients/jedis/CommandObjects.java
@@ -3946,9 +3946,15 @@ public final CommandObject tsAdd(String key, long timestamp, double value)
     return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key).add(timestamp).add(value), BuilderFactory.LONG);
   }
 
+  @Deprecated
   public final CommandObject tsAdd(String key, long timestamp, double value, TSCreateParams createParams) {
-    return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key)
-        .add(timestamp).add(value).addParams(createParams), BuilderFactory.LONG);
+    return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key).add(timestamp).add(value)
+        .addParams(createParams), BuilderFactory.LONG);
+  }
+
+  public final CommandObject tsAdd(String key, long timestamp, double value, TSAddParams addParams) {
+    return new CommandObject<>(commandArguments(TimeSeriesCommand.ADD).key(key).add(timestamp).add(value)
+        .addParams(addParams), BuilderFactory.LONG);
   }
 
   public final CommandObject> tsMAdd(Map.Entry... entries) {
@@ -3968,6 +3974,11 @@ public final CommandObject tsIncrBy(String key, double value, long timesta
         .add(TimeSeriesKeyword.TIMESTAMP).add(timestamp), BuilderFactory.LONG);
   }
 
+  public final CommandObject tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams) {
+    return new CommandObject<>(commandArguments(TimeSeriesCommand.INCRBY).key(key).add(addend)
+        .addParams(incrByParams), BuilderFactory.LONG);
+  }
+
   public final CommandObject tsDecrBy(String key, double value) {
     return new CommandObject<>(commandArguments(TimeSeriesCommand.DECRBY).key(key).add(value), BuilderFactory.LONG);
   }
@@ -3977,6 +3988,11 @@ public final CommandObject tsDecrBy(String key, double value, long timesta
         .add(TimeSeriesKeyword.TIMESTAMP).add(timestamp), BuilderFactory.LONG);
   }
 
+  public final CommandObject tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams) {
+    return new CommandObject<>(commandArguments(TimeSeriesCommand.DECRBY).key(key).add(subtrahend)
+        .addParams(decrByParams), BuilderFactory.LONG);
+  }
+
   public final CommandObject> tsRange(String key, long fromTimestamp, long toTimestamp) {
     return new CommandObject<>(commandArguments(TimeSeriesCommand.RANGE).key(key)
         .add(fromTimestamp).add(toTimestamp), TimeSeriesBuilderFactory.TIMESERIES_ELEMENT_LIST);
diff --git a/src/main/java/redis/clients/jedis/PipeliningBase.java b/src/main/java/redis/clients/jedis/PipeliningBase.java
index 928126a704..9967a2e694 100644
--- a/src/main/java/redis/clients/jedis/PipeliningBase.java
+++ b/src/main/java/redis/clients/jedis/PipeliningBase.java
@@ -3948,6 +3948,11 @@ public Response tsAdd(String key, long timestamp, double value, TSCreatePa
     return appendCommand(commandObjects.tsAdd(key, timestamp, value, createParams));
   }
 
+  @Override
+  public Response tsAdd(String key, long timestamp, double value, TSAddParams addParams) {
+    return appendCommand(commandObjects.tsAdd(key, timestamp, value, addParams));
+  }
+
   @Override
   public Response> tsMAdd(Map.Entry... entries) {
     return appendCommand(commandObjects.tsMAdd(entries));
@@ -3963,6 +3968,11 @@ public Response tsIncrBy(String key, double value, long timestamp) {
     return appendCommand(commandObjects.tsIncrBy(key, value, timestamp));
   }
 
+  @Override
+  public Response tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams) {
+    return appendCommand(commandObjects.tsIncrBy(key, addend, incrByParams));
+  }
+
   @Override
   public Response tsDecrBy(String key, double value) {
     return appendCommand(commandObjects.tsDecrBy(key, value));
@@ -3973,6 +3983,11 @@ public Response tsDecrBy(String key, double value, long timestamp) {
     return appendCommand(commandObjects.tsDecrBy(key, value, timestamp));
   }
 
+  @Override
+  public Response tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams) {
+    return appendCommand(commandObjects.tsDecrBy(key, subtrahend, decrByParams));
+  }
+
   @Override
   public Response> tsRange(String key, long fromTimestamp, long toTimestamp) {
     return appendCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp));
diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java
index fa17cb72cd..53d08eed45 100644
--- a/src/main/java/redis/clients/jedis/UnifiedJedis.java
+++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java
@@ -4513,6 +4513,11 @@ public long tsAdd(String key, long timestamp, double value, TSCreateParams creat
     return executeCommand(commandObjects.tsAdd(key, timestamp, value, createParams));
   }
 
+  @Override
+  public long tsAdd(String key, long timestamp, double value, TSAddParams addParams) {
+    return executeCommand(commandObjects.tsAdd(key, timestamp, value, addParams));
+  }
+
   @Override
   public List tsMAdd(Map.Entry... entries) {
     return executeCommand(commandObjects.tsMAdd(entries));
@@ -4528,6 +4533,11 @@ public long tsIncrBy(String key, double value, long timestamp) {
     return executeCommand(commandObjects.tsIncrBy(key, value, timestamp));
   }
 
+  @Override
+  public long tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams) {
+    return executeCommand(commandObjects.tsIncrBy(key, addend, incrByParams));
+  }
+
   @Override
   public long tsDecrBy(String key, double value) {
     return executeCommand(commandObjects.tsDecrBy(key, value));
@@ -4538,6 +4548,11 @@ public long tsDecrBy(String key, double value, long timestamp) {
     return executeCommand(commandObjects.tsDecrBy(key, value, timestamp));
   }
 
+  @Override
+  public long tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams) {
+    return executeCommand(commandObjects.tsDecrBy(key, subtrahend, decrByParams));
+  }
+
   @Override
   public List tsRange(String key, long fromTimestamp, long toTimestamp) {
     return checkAndClientSideCacheCommand(commandObjects.tsRange(key, fromTimestamp, toTimestamp), key);
diff --git a/src/main/java/redis/clients/jedis/timeseries/EncodingFormat.java b/src/main/java/redis/clients/jedis/timeseries/EncodingFormat.java
new file mode 100644
index 0000000000..5130d7da25
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/timeseries/EncodingFormat.java
@@ -0,0 +1,24 @@
+package redis.clients.jedis.timeseries;
+
+import redis.clients.jedis.args.Rawable;
+import redis.clients.jedis.util.SafeEncoder;
+
+/**
+ * Specifies the series samples encoding format.
+ */
+public enum EncodingFormat implements Rawable {
+
+  COMPRESSED,
+  UNCOMPRESSED;
+
+  private final byte[] raw;
+
+  private EncodingFormat() {
+    raw = SafeEncoder.encode(name());
+  }
+
+  @Override
+  public byte[] getRaw() {
+    return raw;
+  }
+}
diff --git a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java
index c002b94c08..67c1b26fcf 100644
--- a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java
+++ b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java
@@ -59,16 +59,33 @@ public interface RedisTimeSeriesCommands {
   long tsAdd(String key, long timestamp, double value);
 
   /**
-   * {@code TS.ADD key timestamp value [RETENTION retentionTime] [ENCODING [COMPRESSED|UNCOMPRESSED]] [CHUNK_SIZE size] [ON_DUPLICATE policy] [LABELS label value..]}
-   *
    * @param key
    * @param timestamp
    * @param value
    * @param createParams
    * @return timestamp
+   * @deprecated Use {@link RedisTimeSeriesCommands#tsAdd(java.lang.String, long, double, redis.clients.jedis.timeseries.TSAddParams)}.
    */
+  @Deprecated
   long tsAdd(String key, long timestamp, double value, TSCreateParams createParams);
 
+  /**
+   * {@code TS.ADD key timestamp value
+   * [RETENTION retentionTime]
+   * [ENCODING ]
+   * [CHUNK_SIZE size]
+   * [DUPLICATE_POLICY policy]
+   * [ON_DUPLICATE policy_ovr]
+   * [LABELS label value..]}
+   *
+   * @param key
+   * @param timestamp
+   * @param value
+   * @param addParams
+   * @return timestamp
+   */
+  long tsAdd(String key, long timestamp, double value, TSAddParams addParams);
+
   /**
    * {@code TS.MADD key timestamp value [key timestamp value ...]}
    *
@@ -81,10 +98,44 @@ public interface RedisTimeSeriesCommands {
 
   long tsIncrBy(String key, double value, long timestamp);
 
+  /**
+   * {@code TS.INCRBY key addend
+   * [TIMESTAMP timestamp]
+   * [RETENTION retentionPeriod]
+   * [ENCODING ]
+   * [CHUNK_SIZE size]
+   * [DUPLICATE_POLICY policy]
+   * [IGNORE ignoreMaxTimediff ignoreMaxValDiff]
+   * [LABELS [label value ...]]}
+   *
+   * @param key
+   * @param addend
+   * @param incrByParams
+   * @return timestamp
+   */
+  long tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams);
+
   long tsDecrBy(String key, double value);
 
   long tsDecrBy(String key, double value, long timestamp);
 
+  /**
+   * {@code TS.DECRBY key subtrahend
+   * [TIMESTAMP timestamp]
+   * [RETENTION retentionPeriod]
+   * [ENCODING ]
+   * [CHUNK_SIZE size]
+   * [DUPLICATE_POLICY policy]
+   * [IGNORE ignoreMaxTimediff ignoreMaxValDiff]
+   * [LABELS [label value ...]]}
+   *
+   * @param key
+   * @param subtrahend
+   * @param decrByParams
+   * @return timestamp
+   */
+  long tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams);
+
   /**
    * {@code TS.RANGE key fromTimestamp toTimestamp}
    *
diff --git a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java
index 288b3f195e..b3304716dd 100644
--- a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java
+++ b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java
@@ -18,18 +18,25 @@ public interface RedisTimeSeriesPipelineCommands {
 
   Response tsAdd(String key, long timestamp, double value);
 
+  @Deprecated
   Response tsAdd(String key, long timestamp, double value, TSCreateParams createParams);
 
+  Response tsAdd(String key, long timestamp, double value, TSAddParams addParams);
+
   Response> tsMAdd(Map.Entry... entries);
 
   Response tsIncrBy(String key, double value);
 
   Response tsIncrBy(String key, double value, long timestamp);
 
+  Response tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams);
+
   Response tsDecrBy(String key, double value);
 
   Response tsDecrBy(String key, double value, long timestamp);
 
+  Response tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams);
+
   Response> tsRange(String key, long fromTimestamp, long toTimestamp);
 
   Response> tsRange(String key, TSRangeParams rangeParams);
diff --git a/src/main/java/redis/clients/jedis/timeseries/TSAddParams.java b/src/main/java/redis/clients/jedis/timeseries/TSAddParams.java
new file mode 100644
index 0000000000..0a9713cefb
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/timeseries/TSAddParams.java
@@ -0,0 +1,128 @@
+package redis.clients.jedis.timeseries;
+
+import static redis.clients.jedis.Protocol.toByteArray;
+import static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import redis.clients.jedis.CommandArguments;
+import redis.clients.jedis.params.IParams;
+
+/**
+ * Represents optional arguments of TS.ADD command.
+ */
+public class TSAddParams implements IParams {
+
+  private Long retentionPeriod;
+  private EncodingFormat encoding;
+  private Long chunkSize;
+  private DuplicatePolicy duplicatePolicy;
+  private DuplicatePolicy onDuplicate;
+
+  private boolean ignore;
+  private long ignoreMaxTimediff;
+  private double ignoreMaxValDiff;
+
+  private Map labels;
+
+  public TSAddParams() {
+  }
+
+  public static TSAddParams addParams() {
+    return new TSAddParams();
+  }
+
+  public TSAddParams retention(long retentionPeriod) {
+    this.retentionPeriod = retentionPeriod;
+    return this;
+  }
+
+  public TSAddParams encoding(EncodingFormat encoding) {
+    this.encoding = encoding;
+    return this;
+  }
+
+  public TSAddParams chunkSize(long chunkSize) {
+    this.chunkSize = chunkSize;
+    return this;
+  }
+
+  public TSAddParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {
+    this.duplicatePolicy = duplicatePolicy;
+    return this;
+  }
+
+  public TSAddParams onDuplicate(DuplicatePolicy onDuplicate) {
+    this.onDuplicate = onDuplicate;
+    return this;
+  }
+
+  public TSAddParams ignore(long maxTimediff, double maxValDiff) {
+    this.ignore = true;
+    this.ignoreMaxTimediff = maxTimediff;
+    this.ignoreMaxValDiff = maxValDiff;
+    return this;
+  }
+
+  /**
+   * Set label-value pairs
+   *
+   * @param labels label-value pairs
+   * @return the object itself
+   */
+  public TSAddParams labels(Map labels) {
+    this.labels = labels;
+    return this;
+  }
+
+  /**
+   * Add label-value pair. Multiple pairs can be added through chaining.
+   * @param label
+   * @param value
+   * @return the object itself
+   */
+  public TSAddParams label(String label, String value) {
+    if (this.labels == null) {
+      this.labels = new LinkedHashMap<>();
+    }
+    this.labels.put(label, value);
+    return this;
+  }
+
+  @Override
+  public void addParams(CommandArguments args) {
+
+    if (retentionPeriod != null) {
+      args.add(RETENTION).add(toByteArray(retentionPeriod));
+    }
+
+    if (encoding != null) {
+      args.add(ENCODING).add(encoding);
+    }
+
+    if (chunkSize != null) {
+      args.add(CHUNK_SIZE).add(toByteArray(chunkSize));
+    }
+
+    if (duplicatePolicy != null) {
+      args.add(DUPLICATE_POLICY).add(duplicatePolicy);
+    }
+
+    if (duplicatePolicy != null) {
+      args.add(DUPLICATE_POLICY).add(duplicatePolicy);
+    }
+
+    if (onDuplicate != null) {
+      args.add(ON_DUPLICATE).add(onDuplicate);
+    }
+
+    if (ignore) {
+      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);
+    }
+
+    if (labels != null) {
+      args.add(LABELS);
+      labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));
+    }
+  }
+}
diff --git a/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java b/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java
index 4576a1b6b7..50ba9723ac 100644
--- a/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java
+++ b/src/main/java/redis/clients/jedis/timeseries/TSAlterParams.java
@@ -17,6 +17,11 @@ public class TSAlterParams implements IParams {
   private Long retentionPeriod;
   private Long chunkSize;
   private DuplicatePolicy duplicatePolicy;
+
+  private boolean ignore;
+  private long ignoreMaxTimediff;
+  private double ignoreMaxValDiff;
+
   private Map labels;
 
   public TSAlterParams() {
@@ -41,11 +46,30 @@ public TSAlterParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {
     return this;
   }
 
+  public TSAlterParams ignore(long maxTimediff, double maxValDiff) {
+    this.ignore = true;
+    this.ignoreMaxTimediff = maxTimediff;
+    this.ignoreMaxValDiff = maxValDiff;
+    return this;
+  }
+
+  /**
+   * Set label-value pairs
+   *
+   * @param labels label-value pairs
+   * @return the object itself
+   */
   public TSAlterParams labels(Map labels) {
     this.labels = labels;
     return this;
   }
 
+  /**
+   * Add label-value pair. Multiple pairs can be added through chaining.
+   * @param label
+   * @param value
+   * @return the object itself
+   */
   public TSAlterParams label(String label, String value) {
     if (this.labels == null) {
       this.labels = new LinkedHashMap<>();
@@ -73,6 +97,10 @@ public void addParams(CommandArguments args) {
       args.add(DUPLICATE_POLICY).add(duplicatePolicy);
     }
 
+    if (ignore) {
+      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);
+    }
+
     if (labels != null) {
       args.add(LABELS);
       labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));
diff --git a/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java b/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java
index ca07de1f01..0611383d4d 100644
--- a/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java
+++ b/src/main/java/redis/clients/jedis/timeseries/TSCreateParams.java
@@ -14,10 +14,14 @@
 public class TSCreateParams implements IParams {
 
   private Long retentionPeriod;
-  private boolean uncompressed;
-  private boolean compressed;
+  private EncodingFormat encoding;
   private Long chunkSize;
   private DuplicatePolicy duplicatePolicy;
+
+  private boolean ignore;
+  private long ignoreMaxTimediff;
+  private double ignoreMaxValDiff;
+
   private Map labels;
 
   public TSCreateParams() {
@@ -32,13 +36,18 @@ public TSCreateParams retention(long retentionPeriod) {
     return this;
   }
 
+  // TODO: deprecate
   public TSCreateParams uncompressed() {
-    this.uncompressed = true;
-    return this;
+    return encoding(EncodingFormat.UNCOMPRESSED);
   }
 
+  // TODO: deprecate
   public TSCreateParams compressed() {
-    this.compressed = true;
+    return encoding(EncodingFormat.COMPRESSED);
+  }
+
+  public TSCreateParams encoding(EncodingFormat encoding) {
+    this.encoding = encoding;
     return this;
   }
 
@@ -52,6 +61,13 @@ public TSCreateParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {
     return this;
   }
 
+  public TSCreateParams ignore(long maxTimediff, double maxValDiff) {
+    this.ignore = true;
+    this.ignoreMaxTimediff = maxTimediff;
+    this.ignoreMaxValDiff = maxValDiff;
+    return this;
+  }
+
   /**
    * Set label-value pairs
    *
@@ -65,6 +81,9 @@ public TSCreateParams labels(Map labels) {
 
   /**
    * Add label-value pair. Multiple pairs can be added through chaining.
+   * @param label
+   * @param value
+   * @return the object itself
    */
   public TSCreateParams label(String label, String value) {
     if (this.labels == null) {
@@ -81,10 +100,8 @@ public void addParams(CommandArguments args) {
       args.add(RETENTION).add(toByteArray(retentionPeriod));
     }
 
-    if (uncompressed) {
-      args.add(ENCODING).add(UNCOMPRESSED);
-    } else if (compressed) {
-      args.add(ENCODING).add(COMPRESSED);
+    if (encoding != null) {
+      args.add(ENCODING).add(encoding);
     }
 
     if (chunkSize != null) {
@@ -95,6 +112,10 @@ public void addParams(CommandArguments args) {
       args.add(DUPLICATE_POLICY).add(duplicatePolicy);
     }
 
+    if (ignore) {
+      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);
+    }
+
     if (labels != null) {
       args.add(LABELS);
       labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));
diff --git a/src/main/java/redis/clients/jedis/timeseries/TSIncrOrDecrByParams.java b/src/main/java/redis/clients/jedis/timeseries/TSIncrOrDecrByParams.java
new file mode 100644
index 0000000000..fde848fb5a
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/timeseries/TSIncrOrDecrByParams.java
@@ -0,0 +1,132 @@
+package redis.clients.jedis.timeseries;
+
+import static redis.clients.jedis.Protocol.toByteArray;
+import static redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesKeyword.*;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import redis.clients.jedis.CommandArguments;
+import redis.clients.jedis.params.IParams;
+
+/**
+ * Represents optional arguments of TS.INCRBY or TS.DECRBY commands.
+ */
+public class TSIncrOrDecrByParams implements IParams {
+
+  private Long timestamp;
+  private Long retentionPeriod;
+  private EncodingFormat encoding;
+  private Long chunkSize;
+  private DuplicatePolicy duplicatePolicy;
+
+  private boolean ignore;
+  private long ignoreMaxTimediff;
+  private double ignoreMaxValDiff;
+
+  private Map labels;
+
+  public TSIncrOrDecrByParams() {
+  }
+
+  public static TSIncrOrDecrByParams params() {
+    return new TSIncrOrDecrByParams();
+  }
+
+  public static TSIncrOrDecrByParams incrByParams() {
+    return new TSIncrOrDecrByParams();
+  }
+
+  public static TSIncrOrDecrByParams decrByParams() {
+    return new TSIncrOrDecrByParams();
+  }
+
+  public TSIncrOrDecrByParams timestamp(long timestamp) {
+    this.timestamp = timestamp;
+    return this;
+  }
+
+  public TSIncrOrDecrByParams retention(long retentionPeriod) {
+    this.retentionPeriod = retentionPeriod;
+    return this;
+  }
+
+  public TSIncrOrDecrByParams encoding(EncodingFormat encoding) {
+    this.encoding = encoding;
+    return this;
+  }
+
+  public TSIncrOrDecrByParams chunkSize(long chunkSize) {
+    this.chunkSize = chunkSize;
+    return this;
+  }
+
+  public TSIncrOrDecrByParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {
+    this.duplicatePolicy = duplicatePolicy;
+    return this;
+  }
+
+  public TSIncrOrDecrByParams ignore(long maxTimediff, double maxValDiff) {
+    this.ignore = true;
+    this.ignoreMaxTimediff = maxTimediff;
+    this.ignoreMaxValDiff = maxValDiff;
+    return this;
+  }
+
+  /**
+   * Set label-value pairs
+   *
+   * @param labels label-value pairs
+   * @return the object itself
+   */
+  public TSIncrOrDecrByParams labels(Map labels) {
+    this.labels = labels;
+    return this;
+  }
+
+  /**
+   * Add label-value pair. Multiple pairs can be added through chaining.
+   * @param label
+   * @param value
+   * @return the object itself
+   */
+  public TSIncrOrDecrByParams label(String label, String value) {
+    if (this.labels == null) {
+      this.labels = new LinkedHashMap<>();
+    }
+    this.labels.put(label, value);
+    return this;
+  }
+
+  @Override
+  public void addParams(CommandArguments args) {
+
+    if (timestamp != null) {
+      args.add(TIMESTAMP).add(timestamp);
+    }
+
+    if (retentionPeriod != null) {
+      args.add(RETENTION).add(toByteArray(retentionPeriod));
+    }
+
+    if (encoding != null) {
+      args.add(ENCODING).add(encoding);
+    }
+
+    if (chunkSize != null) {
+      args.add(CHUNK_SIZE).add(toByteArray(chunkSize));
+    }
+
+    if (duplicatePolicy != null) {
+      args.add(DUPLICATE_POLICY).add(duplicatePolicy);
+    }
+
+    if (ignore) {
+      args.add(IGNORE).add(ignoreMaxTimediff).add(ignoreMaxValDiff);
+    }
+
+    if (labels != null) {
+      args.add(LABELS);
+      labels.entrySet().forEach((entry) -> args.add(entry.getKey()).add(entry.getValue()));
+    }
+  }
+}
diff --git a/src/main/java/redis/clients/jedis/timeseries/TimeSeriesProtocol.java b/src/main/java/redis/clients/jedis/timeseries/TimeSeriesProtocol.java
index 2476979f0d..384a454921 100644
--- a/src/main/java/redis/clients/jedis/timeseries/TimeSeriesProtocol.java
+++ b/src/main/java/redis/clients/jedis/timeseries/TimeSeriesProtocol.java
@@ -57,6 +57,7 @@ public enum TimeSeriesKeyword implements Rawable {
     UNCOMPRESSED,
     CHUNK_SIZE,
     DUPLICATE_POLICY,
+    IGNORE,
     ON_DUPLICATE,
     ALIGN,
     FILTER_BY_TS,
diff --git a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java
index 44e653c011..b8cfb85dc8 100644
--- a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java
+++ b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java
@@ -3,6 +3,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.util.AbstractMap;
@@ -11,17 +12,7 @@
 
 import org.junit.Test;
 import redis.clients.jedis.Response;
-import redis.clients.jedis.timeseries.AggregationType;
-import redis.clients.jedis.timeseries.TSAlterParams;
-import redis.clients.jedis.timeseries.TSCreateParams;
-import redis.clients.jedis.timeseries.TSElement;
-import redis.clients.jedis.timeseries.TSGetParams;
-import redis.clients.jedis.timeseries.TSInfo;
-import redis.clients.jedis.timeseries.TSMGetElement;
-import redis.clients.jedis.timeseries.TSMGetParams;
-import redis.clients.jedis.timeseries.TSMRangeElements;
-import redis.clients.jedis.timeseries.TSMRangeParams;
-import redis.clients.jedis.timeseries.TSRangeParams;
+import redis.clients.jedis.timeseries.*;
 
 public class PipeliningBaseTimeSeriesCommandsTest extends PipeliningBaseMockedTestBase {
 
@@ -57,6 +48,18 @@ public void testTsAddWithTimestampAndParams() {
     assertThat(response, is(predefinedResponse));
   }
 
+  @Test
+  public void testTsAddWithParams() {
+    TSAddParams addParams = mock(TSAddParams.class);
+
+    when(commandObjects.tsAdd("myTimeSeries", 1000L, 42.0, addParams)).thenReturn(longCommandObject);
+
+    Response response = pipeliningBase.tsAdd("myTimeSeries", 1000L, 42.0, addParams);
+
+    assertThat(commands, contains(longCommandObject));
+    assertThat(response, is(predefinedResponse));
+  }
+
   @Test
   public void testTsAlter() {
     TSAlterParams alterParams = TSAlterParams.alterParams();
@@ -138,6 +141,17 @@ public void testTsDecrByWithTimestamp() {
     assertThat(response, is(predefinedResponse));
   }
 
+  @Test
+  public void testTsDecrByWithParams() {
+    TSIncrOrDecrByParams DecrByParams = mock(TSIncrOrDecrByParams.class);
+    when(commandObjects.tsDecrBy("myTimeSeries", 1.0, DecrByParams)).thenReturn(longCommandObject);
+
+    Response response = pipeliningBase.tsDecrBy("myTimeSeries", 1.0, DecrByParams);
+
+    assertThat(commands, contains(longCommandObject));
+    assertThat(response, is(predefinedResponse));
+  }
+
   @Test
   public void testTsDel() {
     when(commandObjects.tsDel("myTimeSeries", 1000L, 2000L)).thenReturn(longCommandObject);
@@ -200,6 +214,17 @@ public void testTsIncrByWithTimestamp() {
     assertThat(response, is(predefinedResponse));
   }
 
+  @Test
+  public void testTsIncrByWithParams() {
+    TSIncrOrDecrByParams incrByParams = mock(TSIncrOrDecrByParams.class);
+    when(commandObjects.tsIncrBy("myTimeSeries", 1.0, incrByParams)).thenReturn(longCommandObject);
+
+    Response response = pipeliningBase.tsIncrBy("myTimeSeries", 1.0, incrByParams);
+
+    assertThat(commands, contains(longCommandObject));
+    assertThat(response, is(predefinedResponse));
+  }
+
   @Test
   public void testTsInfo() {
     when(commandObjects.tsInfo("myTimeSeries")).thenReturn(tsInfoCommandObject);
diff --git a/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java b/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java
index d9e06ce77c..53c673da49 100644
--- a/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java
+++ b/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java
@@ -15,17 +15,7 @@
 import java.util.Map;
 
 import org.junit.Test;
-import redis.clients.jedis.timeseries.AggregationType;
-import redis.clients.jedis.timeseries.TSAlterParams;
-import redis.clients.jedis.timeseries.TSCreateParams;
-import redis.clients.jedis.timeseries.TSElement;
-import redis.clients.jedis.timeseries.TSGetParams;
-import redis.clients.jedis.timeseries.TSInfo;
-import redis.clients.jedis.timeseries.TSMGetElement;
-import redis.clients.jedis.timeseries.TSMGetParams;
-import redis.clients.jedis.timeseries.TSMRangeElements;
-import redis.clients.jedis.timeseries.TSMRangeParams;
-import redis.clients.jedis.timeseries.TSRangeParams;
+import redis.clients.jedis.timeseries.*;
 
 public class UnifiedJedisTimeSeriesCommandsTest extends UnifiedJedisMockedTestBase {
 
@@ -83,6 +73,25 @@ public void testTsAddWithTimestampAndParams() {
     verify(commandObjects).tsAdd(key, timestamp, value, createParams);
   }
 
+  @Test
+  public void testTsAddWithParams() {
+    String key = "testKey";
+    long timestamp = 1582605077000L;
+    double value = 123.45;
+    TSAddParams createParams = mock(TSAddParams.class);
+    long expectedResponse = timestamp; // Timestamp of the added value
+
+    when(commandObjects.tsAdd(key, timestamp, value, createParams)).thenReturn(longCommandObject);
+    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);
+
+    long result = jedis.tsAdd(key, timestamp, value, createParams);
+
+    assertEquals(expectedResponse, result);
+
+    verify(commandExecutor).executeCommand(longCommandObject);
+    verify(commandObjects).tsAdd(key, timestamp, value, createParams);
+  }
+
   @Test
   public void testTsAlter() {
     String key = "testKey";
@@ -194,7 +203,7 @@ public void testTsDecrByWithTimestamp() {
     String key = "testKey";
     double value = 1.5;
     long timestamp = 1582605077000L;
-    long expectedResponse = -1L; // Assuming the decrement results in a total of -1
+    long expectedResponse = 5L;
 
     when(commandObjects.tsDecrBy(key, value, timestamp)).thenReturn(longCommandObject);
     when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);
@@ -207,6 +216,24 @@ public void testTsDecrByWithTimestamp() {
     verify(commandObjects).tsDecrBy(key, value, timestamp);
   }
 
+  @Test
+  public void testTsDecrByWithParams() {
+    String key = "testKey";
+    double value = 1.5;
+    TSIncrOrDecrByParams decrByParams = mock(TSIncrOrDecrByParams.class);
+    long expectedResponse = 5L;
+
+    when(commandObjects.tsDecrBy(key, value, decrByParams)).thenReturn(longCommandObject);
+    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);
+
+    long result = jedis.tsDecrBy(key, value, decrByParams);
+
+    assertEquals(expectedResponse, result);
+
+    verify(commandExecutor).executeCommand(longCommandObject);
+    verify(commandObjects).tsDecrBy(key, value, decrByParams);
+  }
+
   @Test
   public void testTsDel() {
     String key = "testKey";
@@ -297,7 +324,7 @@ public void testTsIncrByWithTimestamp() {
     String key = "testKey";
     double value = 2.5;
     long timestamp = 1582605077000L;
-    long expectedResponse = 5L; // Assuming the increment results in a total of 5
+    long expectedResponse = 5L;
 
     when(commandObjects.tsIncrBy(key, value, timestamp)).thenReturn(longCommandObject);
     when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);
@@ -310,6 +337,24 @@ public void testTsIncrByWithTimestamp() {
     verify(commandObjects).tsIncrBy(key, value, timestamp);
   }
 
+  @Test
+  public void testTsIncrByWithParams() {
+    String key = "testKey";
+    double value = 2.5;
+    TSIncrOrDecrByParams incrByParams = mock(TSIncrOrDecrByParams.class);
+    long expectedResponse = 5L;
+
+    when(commandObjects.tsIncrBy(key, value, incrByParams)).thenReturn(longCommandObject);
+    when(commandExecutor.executeCommand(longCommandObject)).thenReturn(expectedResponse);
+
+    long result = jedis.tsIncrBy(key, value, incrByParams);
+
+    assertEquals(expectedResponse, result);
+
+    verify(commandExecutor).executeCommand(longCommandObject);
+    verify(commandObjects).tsIncrBy(key, value, incrByParams);
+  }
+
   @Test
   public void testTsInfo() {
     String key = "testKey";
diff --git a/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java b/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java
index fe0f7d1604..dd0688f080 100644
--- a/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java
+++ b/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java
@@ -122,6 +122,23 @@ public void testAlter() {
     assertEquals("v33", info.getLabel("l3"));
   }
 
+  @Test
+  public void createAndAlterParams() {
+    Map labels = new HashMap<>();
+    labels.put("l1", "v1");
+    labels.put("l2", "v2");
+
+    assertEquals("OK", client.tsCreate("ts-params",
+        TSCreateParams.createParams().retention(60000).encoding(EncodingFormat.UNCOMPRESSED).chunkSize(4096)
+            .duplicatePolicy(DuplicatePolicy.BLOCK).ignore(50, 12.5).labels(labels)));
+
+    labels.put("l1", "v11");
+    labels.remove("l2");
+    labels.put("l3", "v33");
+    assertEquals("OK", client.tsAlter("ts-params", TSAlterParams.alterParams().retention(15000).chunkSize(8192)
+        .duplicatePolicy(DuplicatePolicy.SUM).ignore(50, 12.5).labels(labels)));
+  }
+
   @Test
   public void testRule() {
     assertEquals("OK", client.tsCreate("source"));
@@ -147,6 +164,21 @@ public void testRule() {
     }
   }
 
+  @Test
+  public void addParams() {
+    Map labels = new HashMap<>();
+    labels.put("l1", "v1");
+    labels.put("l2", "v2");
+
+    assertEquals(1000L, client.tsAdd("add1", 1000L, 1.1,
+        TSAddParams.addParams().retention(10000).encoding(EncodingFormat.UNCOMPRESSED).chunkSize(1000)
+            .duplicatePolicy(DuplicatePolicy.FIRST).onDuplicate(DuplicatePolicy.LAST).ignore(50, 12.5).labels(labels)));
+
+    assertEquals(1000L, client.tsAdd("add2", 1000L, 1.1,
+        TSAddParams.addParams().retention(10000).encoding(EncodingFormat.COMPRESSED).chunkSize(1000)
+            .duplicatePolicy(DuplicatePolicy.MIN).onDuplicate(DuplicatePolicy.MAX).ignore(50, 12.5).labels(labels)));
+  }
+
   @Test
   public void testAdd() {
     Map labels = new HashMap<>();
@@ -414,6 +446,29 @@ public void testIncrByDecrBy() throws InterruptedException {
     client.tsDecrBy("seriesIncDec", 33);
   }
 
+  @Test
+  public void incrByDecrByParams() {
+    Map labels = new HashMap<>();
+    labels.put("l1", "v1");
+    labels.put("l2", "v2");
+
+    assertEquals(1000L, client.tsIncrBy("incr1", 1.1,
+        TSIncrOrDecrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)
+            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.FIRST).ignore(50, 12.5).labels(labels)));
+
+    assertEquals(1000L, client.tsIncrBy("incr2", 1.1,
+        TSIncrOrDecrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)
+            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.MIN).ignore(50, 12.5).labels(labels)));
+
+    assertEquals(1000L, client.tsDecrBy("decr1", 1.1,
+        TSIncrOrDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)
+            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.LAST).ignore(50, 12.5).labels(labels)));
+
+    assertEquals(1000L, client.tsDecrBy("decr2", 1.1,
+        TSIncrOrDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)
+            .chunkSize(1000).duplicatePolicy(DuplicatePolicy.MAX).ignore(50, 12.5).labels(labels)));
+  }
+
   @Test
   public void align() {
     client.tsAdd("align", 1, 10d);

From 5cba18cd6396bf15097bf68f1744518105254cdd Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Fri, 14 Jun 2024 19:59:25 +0600
Subject: [PATCH 29/48] Polish #3860: Separate params for TS.INCRBY and
 TS.DECRBY (#3863)

---
 .../redis/clients/jedis/CommandObjects.java   |  4 +-
 .../redis/clients/jedis/PipeliningBase.java   |  4 +-
 .../redis/clients/jedis/UnifiedJedis.java     |  4 +-
 .../timeseries/RedisTimeSeriesCommands.java   |  4 +-
 .../RedisTimeSeriesPipelineCommands.java      |  4 +-
 ...DecrByParams.java => TSArithByParams.java} | 48 +++++++------------
 .../jedis/timeseries/TSDecrByParams.java      | 14 ++++++
 .../jedis/timeseries/TSIncrByParams.java      | 14 ++++++
 .../PipeliningBaseTimeSeriesCommandsTest.java |  8 ++--
 .../UnifiedJedisTimeSeriesCommandsTest.java   |  4 +-
 .../modules/timeseries/TimeSeriesTest.java    |  8 ++--
 11 files changed, 66 insertions(+), 50 deletions(-)
 rename src/main/java/redis/clients/jedis/timeseries/{TSIncrOrDecrByParams.java => TSArithByParams.java} (67%)
 create mode 100644 src/main/java/redis/clients/jedis/timeseries/TSDecrByParams.java
 create mode 100644 src/main/java/redis/clients/jedis/timeseries/TSIncrByParams.java

diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java
index 421b81c4e2..cbf0e19723 100644
--- a/src/main/java/redis/clients/jedis/CommandObjects.java
+++ b/src/main/java/redis/clients/jedis/CommandObjects.java
@@ -3974,7 +3974,7 @@ public final CommandObject tsIncrBy(String key, double value, long timesta
         .add(TimeSeriesKeyword.TIMESTAMP).add(timestamp), BuilderFactory.LONG);
   }
 
-  public final CommandObject tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams) {
+  public final CommandObject tsIncrBy(String key, double addend, TSIncrByParams incrByParams) {
     return new CommandObject<>(commandArguments(TimeSeriesCommand.INCRBY).key(key).add(addend)
         .addParams(incrByParams), BuilderFactory.LONG);
   }
@@ -3988,7 +3988,7 @@ public final CommandObject tsDecrBy(String key, double value, long timesta
         .add(TimeSeriesKeyword.TIMESTAMP).add(timestamp), BuilderFactory.LONG);
   }
 
-  public final CommandObject tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams) {
+  public final CommandObject tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams) {
     return new CommandObject<>(commandArguments(TimeSeriesCommand.DECRBY).key(key).add(subtrahend)
         .addParams(decrByParams), BuilderFactory.LONG);
   }
diff --git a/src/main/java/redis/clients/jedis/PipeliningBase.java b/src/main/java/redis/clients/jedis/PipeliningBase.java
index 9967a2e694..ffe1c2a31c 100644
--- a/src/main/java/redis/clients/jedis/PipeliningBase.java
+++ b/src/main/java/redis/clients/jedis/PipeliningBase.java
@@ -3969,7 +3969,7 @@ public Response tsIncrBy(String key, double value, long timestamp) {
   }
 
   @Override
-  public Response tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams) {
+  public Response tsIncrBy(String key, double addend, TSIncrByParams incrByParams) {
     return appendCommand(commandObjects.tsIncrBy(key, addend, incrByParams));
   }
 
@@ -3984,7 +3984,7 @@ public Response tsDecrBy(String key, double value, long timestamp) {
   }
 
   @Override
-  public Response tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams) {
+  public Response tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams) {
     return appendCommand(commandObjects.tsDecrBy(key, subtrahend, decrByParams));
   }
 
diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java
index 53d08eed45..dd551b60d4 100644
--- a/src/main/java/redis/clients/jedis/UnifiedJedis.java
+++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java
@@ -4534,7 +4534,7 @@ public long tsIncrBy(String key, double value, long timestamp) {
   }
 
   @Override
-  public long tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams) {
+  public long tsIncrBy(String key, double addend, TSIncrByParams incrByParams) {
     return executeCommand(commandObjects.tsIncrBy(key, addend, incrByParams));
   }
 
@@ -4549,7 +4549,7 @@ public long tsDecrBy(String key, double value, long timestamp) {
   }
 
   @Override
-  public long tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams) {
+  public long tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams) {
     return executeCommand(commandObjects.tsDecrBy(key, subtrahend, decrByParams));
   }
 
diff --git a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java
index 67c1b26fcf..513027c4cf 100644
--- a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java
+++ b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesCommands.java
@@ -113,7 +113,7 @@ public interface RedisTimeSeriesCommands {
    * @param incrByParams
    * @return timestamp
    */
-  long tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams);
+  long tsIncrBy(String key, double addend, TSIncrByParams incrByParams);
 
   long tsDecrBy(String key, double value);
 
@@ -134,7 +134,7 @@ public interface RedisTimeSeriesCommands {
    * @param decrByParams
    * @return timestamp
    */
-  long tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams);
+  long tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams);
 
   /**
    * {@code TS.RANGE key fromTimestamp toTimestamp}
diff --git a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java
index b3304716dd..71b6e4c881 100644
--- a/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java
+++ b/src/main/java/redis/clients/jedis/timeseries/RedisTimeSeriesPipelineCommands.java
@@ -29,13 +29,13 @@ public interface RedisTimeSeriesPipelineCommands {
 
   Response tsIncrBy(String key, double value, long timestamp);
 
-  Response tsIncrBy(String key, double addend, TSIncrOrDecrByParams incrByParams);
+  Response tsIncrBy(String key, double addend, TSIncrByParams incrByParams);
 
   Response tsDecrBy(String key, double value);
 
   Response tsDecrBy(String key, double value, long timestamp);
 
-  Response tsDecrBy(String key, double subtrahend, TSIncrOrDecrByParams decrByParams);
+  Response tsDecrBy(String key, double subtrahend, TSDecrByParams decrByParams);
 
   Response> tsRange(String key, long fromTimestamp, long toTimestamp);
 
diff --git a/src/main/java/redis/clients/jedis/timeseries/TSIncrOrDecrByParams.java b/src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java
similarity index 67%
rename from src/main/java/redis/clients/jedis/timeseries/TSIncrOrDecrByParams.java
rename to src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java
index fde848fb5a..1bc3df1c55 100644
--- a/src/main/java/redis/clients/jedis/timeseries/TSIncrOrDecrByParams.java
+++ b/src/main/java/redis/clients/jedis/timeseries/TSArithByParams.java
@@ -11,7 +11,7 @@
 /**
  * Represents optional arguments of TS.INCRBY or TS.DECRBY commands.
  */
-public class TSIncrOrDecrByParams implements IParams {
+class TSArithByParams> implements IParams {
 
   private Long timestamp;
   private Long retentionPeriod;
@@ -25,51 +25,39 @@ public class TSIncrOrDecrByParams implements IParams {
 
   private Map labels;
 
-  public TSIncrOrDecrByParams() {
+  TSArithByParams() {
   }
 
-  public static TSIncrOrDecrByParams params() {
-    return new TSIncrOrDecrByParams();
-  }
-
-  public static TSIncrOrDecrByParams incrByParams() {
-    return new TSIncrOrDecrByParams();
-  }
-
-  public static TSIncrOrDecrByParams decrByParams() {
-    return new TSIncrOrDecrByParams();
-  }
-
-  public TSIncrOrDecrByParams timestamp(long timestamp) {
+  public T timestamp(long timestamp) {
     this.timestamp = timestamp;
-    return this;
+    return (T) this;
   }
 
-  public TSIncrOrDecrByParams retention(long retentionPeriod) {
+  public T retention(long retentionPeriod) {
     this.retentionPeriod = retentionPeriod;
-    return this;
+    return (T) this;
   }
 
-  public TSIncrOrDecrByParams encoding(EncodingFormat encoding) {
+  public T encoding(EncodingFormat encoding) {
     this.encoding = encoding;
-    return this;
+    return (T) this;
   }
 
-  public TSIncrOrDecrByParams chunkSize(long chunkSize) {
+  public T chunkSize(long chunkSize) {
     this.chunkSize = chunkSize;
-    return this;
+    return (T) this;
   }
 
-  public TSIncrOrDecrByParams duplicatePolicy(DuplicatePolicy duplicatePolicy) {
+  public T duplicatePolicy(DuplicatePolicy duplicatePolicy) {
     this.duplicatePolicy = duplicatePolicy;
-    return this;
+    return (T) this;
   }
 
-  public TSIncrOrDecrByParams ignore(long maxTimediff, double maxValDiff) {
+  public T ignore(long maxTimediff, double maxValDiff) {
     this.ignore = true;
     this.ignoreMaxTimediff = maxTimediff;
     this.ignoreMaxValDiff = maxValDiff;
-    return this;
+    return (T) this;
   }
 
   /**
@@ -78,9 +66,9 @@ public TSIncrOrDecrByParams ignore(long maxTimediff, double maxValDiff) {
    * @param labels label-value pairs
    * @return the object itself
    */
-  public TSIncrOrDecrByParams labels(Map labels) {
+  public T labels(Map labels) {
     this.labels = labels;
-    return this;
+    return (T) this;
   }
 
   /**
@@ -89,12 +77,12 @@ public TSIncrOrDecrByParams labels(Map labels) {
    * @param value
    * @return the object itself
    */
-  public TSIncrOrDecrByParams label(String label, String value) {
+  public T label(String label, String value) {
     if (this.labels == null) {
       this.labels = new LinkedHashMap<>();
     }
     this.labels.put(label, value);
-    return this;
+    return (T) this;
   }
 
   @Override
diff --git a/src/main/java/redis/clients/jedis/timeseries/TSDecrByParams.java b/src/main/java/redis/clients/jedis/timeseries/TSDecrByParams.java
new file mode 100644
index 0000000000..afb776ad6b
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/timeseries/TSDecrByParams.java
@@ -0,0 +1,14 @@
+package redis.clients.jedis.timeseries;
+
+/**
+ * Represents optional arguments of TS.DECRBY command.
+ */
+public class TSDecrByParams extends TSArithByParams {
+
+  public TSDecrByParams() {
+  }
+
+  public static TSDecrByParams decrByParams() {
+    return new TSDecrByParams();
+  }
+}
diff --git a/src/main/java/redis/clients/jedis/timeseries/TSIncrByParams.java b/src/main/java/redis/clients/jedis/timeseries/TSIncrByParams.java
new file mode 100644
index 0000000000..db76a7ae26
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/timeseries/TSIncrByParams.java
@@ -0,0 +1,14 @@
+package redis.clients.jedis.timeseries;
+
+/**
+ * Represents optional arguments of TS.INCRBY command.
+ */
+public class TSIncrByParams extends TSArithByParams {
+
+  public TSIncrByParams() {
+  }
+
+  public static TSIncrByParams incrByParams() {
+    return new TSIncrByParams();
+  }
+}
diff --git a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java
index b8cfb85dc8..671fc83ef9 100644
--- a/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java
+++ b/src/test/java/redis/clients/jedis/mocked/pipeline/PipeliningBaseTimeSeriesCommandsTest.java
@@ -143,10 +143,10 @@ public void testTsDecrByWithTimestamp() {
 
   @Test
   public void testTsDecrByWithParams() {
-    TSIncrOrDecrByParams DecrByParams = mock(TSIncrOrDecrByParams.class);
-    when(commandObjects.tsDecrBy("myTimeSeries", 1.0, DecrByParams)).thenReturn(longCommandObject);
+    TSDecrByParams decrByParams = mock(TSDecrByParams.class);
+    when(commandObjects.tsDecrBy("myTimeSeries", 1.0, decrByParams)).thenReturn(longCommandObject);
 
-    Response response = pipeliningBase.tsDecrBy("myTimeSeries", 1.0, DecrByParams);
+    Response response = pipeliningBase.tsDecrBy("myTimeSeries", 1.0, decrByParams);
 
     assertThat(commands, contains(longCommandObject));
     assertThat(response, is(predefinedResponse));
@@ -216,7 +216,7 @@ public void testTsIncrByWithTimestamp() {
 
   @Test
   public void testTsIncrByWithParams() {
-    TSIncrOrDecrByParams incrByParams = mock(TSIncrOrDecrByParams.class);
+    TSIncrByParams incrByParams = mock(TSIncrByParams.class);
     when(commandObjects.tsIncrBy("myTimeSeries", 1.0, incrByParams)).thenReturn(longCommandObject);
 
     Response response = pipeliningBase.tsIncrBy("myTimeSeries", 1.0, incrByParams);
diff --git a/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java b/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java
index 53c673da49..bfc17620ea 100644
--- a/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java
+++ b/src/test/java/redis/clients/jedis/mocked/unified/UnifiedJedisTimeSeriesCommandsTest.java
@@ -220,7 +220,7 @@ public void testTsDecrByWithTimestamp() {
   public void testTsDecrByWithParams() {
     String key = "testKey";
     double value = 1.5;
-    TSIncrOrDecrByParams decrByParams = mock(TSIncrOrDecrByParams.class);
+    TSDecrByParams decrByParams = mock(TSDecrByParams.class);
     long expectedResponse = 5L;
 
     when(commandObjects.tsDecrBy(key, value, decrByParams)).thenReturn(longCommandObject);
@@ -341,7 +341,7 @@ public void testTsIncrByWithTimestamp() {
   public void testTsIncrByWithParams() {
     String key = "testKey";
     double value = 2.5;
-    TSIncrOrDecrByParams incrByParams = mock(TSIncrOrDecrByParams.class);
+    TSIncrByParams incrByParams = mock(TSIncrByParams.class);
     long expectedResponse = 5L;
 
     when(commandObjects.tsIncrBy(key, value, incrByParams)).thenReturn(longCommandObject);
diff --git a/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java b/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java
index dd0688f080..723e914d47 100644
--- a/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java
+++ b/src/test/java/redis/clients/jedis/modules/timeseries/TimeSeriesTest.java
@@ -453,19 +453,19 @@ public void incrByDecrByParams() {
     labels.put("l2", "v2");
 
     assertEquals(1000L, client.tsIncrBy("incr1", 1.1,
-        TSIncrOrDecrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)
+        TSIncrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)
             .chunkSize(1000).duplicatePolicy(DuplicatePolicy.FIRST).ignore(50, 12.5).labels(labels)));
 
     assertEquals(1000L, client.tsIncrBy("incr2", 1.1,
-        TSIncrOrDecrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)
+        TSIncrByParams.incrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)
             .chunkSize(1000).duplicatePolicy(DuplicatePolicy.MIN).ignore(50, 12.5).labels(labels)));
 
     assertEquals(1000L, client.tsDecrBy("decr1", 1.1,
-        TSIncrOrDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)
+        TSDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.COMPRESSED)
             .chunkSize(1000).duplicatePolicy(DuplicatePolicy.LAST).ignore(50, 12.5).labels(labels)));
 
     assertEquals(1000L, client.tsDecrBy("decr2", 1.1,
-        TSIncrOrDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)
+        TSDecrByParams.decrByParams().timestamp(1000).retention(10000).encoding(EncodingFormat.UNCOMPRESSED)
             .chunkSize(1000).duplicatePolicy(DuplicatePolicy.MAX).ignore(50, 12.5).labels(labels)));
   }
 

From f136c6f7bf78236d271b907b654a93ad98bcc813 Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Sat, 15 Jun 2024 20:54:54 +0600
Subject: [PATCH 30/48] Support indexing of MISSING and EMPTY values (#3866)

---
 .../clients/jedis/search/FTSearchParams.java  |   2 +-
 .../redis/clients/jedis/search/Query.java     |   2 +-
 .../redis/clients/jedis/search/Schema.java    |   2 +-
 .../clients/jedis/search/SearchProtocol.java  |   2 +-
 .../jedis/search/aggr/AggregationBuilder.java |   4 +-
 .../jedis/search/querybuilder/QueryNode.java  |   6 +-
 .../jedis/search/schemafields/GeoField.java   |  33 ++++-
 .../search/schemafields/GeoShapeField.java    |  23 +++-
 .../search/schemafields/NumericField.java     |  14 +-
 .../jedis/search/schemafields/TagField.java   |  78 +++++++----
 .../jedis/search/schemafields/TextField.java  |  85 +++++++-----
 .../search/schemafields/VectorField.java      |  15 +-
 .../modules/search/SearchWithParamsTest.java  | 130 ++++++++++++++++--
 13 files changed, 309 insertions(+), 87 deletions(-)

diff --git a/src/main/java/redis/clients/jedis/search/FTSearchParams.java b/src/main/java/redis/clients/jedis/search/FTSearchParams.java
index d2ca5d6d94..2d4c6e0057 100644
--- a/src/main/java/redis/clients/jedis/search/FTSearchParams.java
+++ b/src/main/java/redis/clients/jedis/search/FTSearchParams.java
@@ -143,7 +143,7 @@ public void addParams(CommandArguments args) {
     }
 
     if (params != null && !params.isEmpty()) {
-      args.add(PARAMS).add(params.size() * 2);
+      args.add(PARAMS).add(params.size() << 1);
       params.entrySet().forEach(entry -> args.add(entry.getKey()).add(entry.getValue()));
     }
 
diff --git a/src/main/java/redis/clients/jedis/search/Query.java b/src/main/java/redis/clients/jedis/search/Query.java
index 66cba96acf..66de3ce074 100644
--- a/src/main/java/redis/clients/jedis/search/Query.java
+++ b/src/main/java/redis/clients/jedis/search/Query.java
@@ -292,7 +292,7 @@ public void addParams(CommandArguments args) {
 
     if (_params != null && _params.size() > 0) {
       args.add(SearchKeyword.PARAMS.getRaw());
-      args.add(_params.size() * 2);
+      args.add(_params.size() << 1);
       for (Map.Entry entry : _params.entrySet()) {
         args.add(entry.getKey());
         args.add(entry.getValue());
diff --git a/src/main/java/redis/clients/jedis/search/Schema.java b/src/main/java/redis/clients/jedis/search/Schema.java
index 17f01cc9de..1403aab556 100644
--- a/src/main/java/redis/clients/jedis/search/Schema.java
+++ b/src/main/java/redis/clients/jedis/search/Schema.java
@@ -365,7 +365,7 @@ public VectorField(String name, VectorAlgo algorithm, Map attrib
     @Override
     public void addTypeArgs(CommandArguments args) {
       args.add(algorithm);
-      args.add(attributes.size() * 2);
+      args.add(attributes.size() << 1);
       for (Map.Entry entry : attributes.entrySet()) {
         args.add(entry.getKey());
         args.add(entry.getValue());
diff --git a/src/main/java/redis/clients/jedis/search/SearchProtocol.java b/src/main/java/redis/clients/jedis/search/SearchProtocol.java
index 7f2ad482fb..64482f4fbb 100644
--- a/src/main/java/redis/clients/jedis/search/SearchProtocol.java
+++ b/src/main/java/redis/clients/jedis/search/SearchProtocol.java
@@ -56,7 +56,7 @@ public enum SearchKeyword implements Rawable {
     LANGUAGE_FIELD, SCORE, SCORE_FIELD, SCORER, PARAMS, AS, DIALECT, SLOP, TIMEOUT, INORDER,
     EXPANDER, MAXTEXTFIELDS, SKIPINITIALSCAN, WITHSUFFIXTRIE, NOSTEM, NOINDEX, PHONETIC, WEIGHT,
     CASESENSITIVE, LOAD, APPLY, GROUPBY, MAXIDLE, WITHCURSOR, DISTANCE, TERMS, INCLUDE, EXCLUDE,
-    SEARCH, AGGREGATE, QUERY, LIMITED, COUNT, REDUCE;
+    SEARCH, AGGREGATE, QUERY, LIMITED, COUNT, REDUCE, INDEXMISSING, INDEXEMPTY;
 
     private final byte[] raw;
 
diff --git a/src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java b/src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java
index eb8e039d02..7c8b547339 100644
--- a/src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java
+++ b/src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java
@@ -66,7 +66,7 @@ public AggregationBuilder limit(int count) {
 
   public AggregationBuilder sortBy(SortedField... fields) {
     aggrArgs.add(SearchKeyword.SORTBY);
-    aggrArgs.add(Integer.toString(fields.length * 2));
+    aggrArgs.add(fields.length << 1);
     for (SortedField field : fields) {
       aggrArgs.add(field.getField());
       aggrArgs.add(field.getOrder());
@@ -172,7 +172,7 @@ public AggregationBuilder timeout(long timeout) {
 
   public AggregationBuilder params(Map params) {
     aggrArgs.add(SearchKeyword.PARAMS);
-    aggrArgs.add(params.size() * 2);
+    aggrArgs.add(params.size() << 1);
     params.forEach((k, v) -> {
       aggrArgs.add(k);
       aggrArgs.add(v);
diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java
index bc64374a5a..f5759b553d 100644
--- a/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java
+++ b/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java
@@ -61,11 +61,11 @@ public QueryNode add(Node... nodes) {
   protected boolean shouldParenthesize(Parenthesize mode) {
     if (mode == Parenthesize.ALWAYS) {
       return true;
-    }
-    if (mode == Parenthesize.NEVER) {
+    } else if (mode == Parenthesize.NEVER) {
       return false;
+    } else {
+      return children.size() > 1;
     }
-    return children.size() > 1;
   }
 
   @Override
diff --git a/src/main/java/redis/clients/jedis/search/schemafields/GeoField.java b/src/main/java/redis/clients/jedis/search/schemafields/GeoField.java
index 7ea421ab4f..c5878f21b8 100644
--- a/src/main/java/redis/clients/jedis/search/schemafields/GeoField.java
+++ b/src/main/java/redis/clients/jedis/search/schemafields/GeoField.java
@@ -1,12 +1,16 @@
 package redis.clients.jedis.search.schemafields;
 
-import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.GEO;
+import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;
 
 import redis.clients.jedis.CommandArguments;
 import redis.clients.jedis.search.FieldName;
 
 public class GeoField extends SchemaField {
 
+  private boolean indexMissing;
+  private boolean sortable;
+  private boolean noIndex;
+
   public GeoField(String fieldName) {
     super(fieldName);
   }
@@ -29,9 +33,36 @@ public GeoField as(String attribute) {
     return this;
   }
 
+  public GeoField indexMissing() {
+    this.indexMissing = true;
+    return this;
+  }
+
+  public GeoField sortable() {
+    this.sortable = true;
+    return this;
+  }
+
+  public GeoField noIndex() {
+    this.noIndex = true;
+    return this;
+  }
+
   @Override
   public void addParams(CommandArguments args) {
     args.addParams(fieldName);
     args.add(GEO);
+
+    if (indexMissing) {
+      args.add(INDEXMISSING);
+    }
+
+    if (sortable) {
+      args.add(SORTABLE);
+    }
+
+    if (noIndex) {
+      args.add(NOINDEX);
+    }
   }
 }
diff --git a/src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java b/src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java
index dd3b45e59e..fedfed1297 100644
--- a/src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java
+++ b/src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java
@@ -1,6 +1,6 @@
 package redis.clients.jedis.search.schemafields;
 
-import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.GEOSHAPE;
+import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;
 
 import redis.clients.jedis.CommandArguments;
 import redis.clients.jedis.search.FieldName;
@@ -22,6 +22,9 @@ public enum CoordinateSystem {
 
   private final CoordinateSystem system;
 
+  private boolean indexMissing;
+  private boolean noIndex;
+
   public GeoShapeField(String fieldName, CoordinateSystem system) {
     super(fieldName);
     this.system = system;
@@ -42,8 +45,26 @@ public GeoShapeField as(String attribute) {
     return this;
   }
 
+  public GeoShapeField indexMissing() {
+    this.indexMissing = true;
+    return this;
+  }
+
+  public GeoShapeField noIndex() {
+    this.noIndex = true;
+    return this;
+  }
+
   @Override
   public void addParams(CommandArguments args) {
     args.addParams(fieldName).add(GEOSHAPE).add(system);
+
+    if (indexMissing) {
+      args.add(INDEXMISSING);
+    }
+
+    if (noIndex) {
+      args.add(NOINDEX);
+    }
   }
 }
diff --git a/src/main/java/redis/clients/jedis/search/schemafields/NumericField.java b/src/main/java/redis/clients/jedis/search/schemafields/NumericField.java
index e1e39ef724..244cc42640 100644
--- a/src/main/java/redis/clients/jedis/search/schemafields/NumericField.java
+++ b/src/main/java/redis/clients/jedis/search/schemafields/NumericField.java
@@ -1,14 +1,13 @@
 package redis.clients.jedis.search.schemafields;
 
-import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.NOINDEX;
-import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.NUMERIC;
-import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.SORTABLE;
+import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.*;
 
 import redis.clients.jedis.CommandArguments;
 import redis.clients.jedis.search.FieldName;
 
 public class NumericField extends SchemaField {
 
+  private boolean indexMissing;
   private boolean sortable;
   private boolean noIndex;
 
@@ -34,6 +33,11 @@ public NumericField as(String attribute) {
     return this;
   }
 
+  public NumericField indexMissing() {
+    this.indexMissing = true;
+    return this;
+  }
+
   /**
    * Sorts the results by the value of this field.
    */
@@ -55,6 +59,10 @@ public void addParams(CommandArguments args) {
     args.addParams(fieldName);
     args.add(NUMERIC);
 
+    if (indexMissing) {
+      args.add(INDEXMISSING);
+    }
+
     if (sortable) {
       args.add(SORTABLE);
     }
diff --git a/src/main/java/redis/clients/jedis/search/schemafields/TagField.java b/src/main/java/redis/clients/jedis/search/schemafields/TagField.java
index 407c4dbddc..451b12aad5 100644
--- a/src/main/java/redis/clients/jedis/search/schemafields/TagField.java
+++ b/src/main/java/redis/clients/jedis/search/schemafields/TagField.java
@@ -8,12 +8,14 @@
 
 public class TagField extends SchemaField {
 
-  private boolean sortable;
-  private boolean sortableUNF;
-  private boolean noIndex;
+  private boolean indexMissing;
+  private boolean indexEmpty;
   private byte[] separator;
   private boolean caseSensitive;
   private boolean withSuffixTrie;
+  private boolean sortable;
+  private boolean sortableUNF;
+  private boolean noIndex;
 
   public TagField(String fieldName) {
     super(fieldName);
@@ -37,39 +39,19 @@ public TagField as(String attribute) {
     return this;
   }
 
-  /**
-   * Sorts the results by the value of this field.
-   */
-  public TagField sortable() {
-    this.sortable = true;
-    return this;
-  }
-
-  /**
-   * Sorts the results by the value of this field without normalization.
-   */
-  public TagField sortableUNF() {
-    this.sortableUNF = true;
+  public TagField indexMissing() {
+    this.indexMissing = true;
     return this;
   }
 
-  /**
-   * @see TextField#sortableUNF()
-   */
-  public TagField sortableUnNormalizedForm() {
-    return sortableUNF();
-  }
-
-  /**
-   * Avoid indexing.
-   */
-  public TagField noIndex() {
-    this.noIndex = true;
+  public TagField indexEmpty() {
+    this.indexEmpty = true;
     return this;
   }
 
   /**
    * Indicates how the text contained in the attribute is to be split into individual tags.
+   * @param separator
    */
   public TagField separator(char separator) {
     if (separator < 128) {
@@ -97,11 +79,51 @@ public TagField withSuffixTrie() {
     return this;
   }
 
+  /**
+   * Sorts the results by the value of this field.
+   */
+  public TagField sortable() {
+    this.sortable = true;
+    return this;
+  }
+
+  /**
+   * Sorts the results by the value of this field without normalization.
+   */
+  public TagField sortableUNF() {
+    this.sortableUNF = true;
+    return this;
+  }
+
+  /**
+   * @deprecated Use {@code TagField#sortableUNF()}.
+   * @see TagField#sortableUNF()
+   */
+  @Deprecated
+  public TagField sortableUnNormalizedForm() {
+    return sortableUNF();
+  }
+
+  /**
+   * Avoid indexing.
+   */
+  public TagField noIndex() {
+    this.noIndex = true;
+    return this;
+  }
+
   @Override
   public void addParams(CommandArguments args) {
     args.addParams(fieldName);
     args.add(TAG);
 
+    if (indexMissing) {
+      args.add(INDEXMISSING);
+    }
+    if (indexEmpty) {
+      args.add(INDEXEMPTY);
+    }
+
     if (separator != null) {
       args.add(SEPARATOR).add(separator);
     }
diff --git a/src/main/java/redis/clients/jedis/search/schemafields/TextField.java b/src/main/java/redis/clients/jedis/search/schemafields/TextField.java
index 573cae90a3..293104a189 100644
--- a/src/main/java/redis/clients/jedis/search/schemafields/TextField.java
+++ b/src/main/java/redis/clients/jedis/search/schemafields/TextField.java
@@ -7,13 +7,15 @@
 
 public class TextField extends SchemaField {
 
-  private boolean sortable;
-  private boolean sortableUNF;
+  private boolean indexMissing;
+  private boolean indexEmpty;
+  private Double weight;
   private boolean noStem;
-  private boolean noIndex;
   private String phoneticMatcher;
-  private Double weight;
   private boolean withSuffixTrie;
+  private boolean sortable;
+  private boolean sortableUNF;
+  private boolean noIndex;
 
   public TextField(String fieldName) {
     super(fieldName);
@@ -37,27 +39,24 @@ public TextField as(String attribute) {
     return this;
   }
 
-  /**
-   * Sorts the results by the value of this field.
-   */
-  public TextField sortable() {
-    this.sortable = true;
+  public TextField indexMissing() {
+    this.indexMissing = true;
     return this;
   }
 
-  /**
-   * Sorts the results by the value of this field without normalization.
-   */
-  public TextField sortableUNF() {
-    this.sortableUNF = true;
+  public TextField indexEmpty() {
+    this.indexEmpty = true;
     return this;
   }
 
   /**
-   * @see TextField#sortableUNF()
+   * Declares the importance of this attribute when calculating result accuracy. This is a
+   * multiplication factor.
+   * @param weight
    */
-  public TextField sortableUnNormalizedForm() {
-    return sortableUNF();
+  public TextField weight(double weight) {
+    this.weight = weight;
+    return this;
   }
 
   /**
@@ -69,36 +68,53 @@ public TextField noStem() {
   }
 
   /**
-   * Avoid indexing.
+   * Perform phonetic matching.
+   * @param matcher
    */
-  public TextField noIndex() {
-    this.noIndex = true;
+  public TextField phonetic(String matcher) {
+    this.phoneticMatcher = matcher;
     return this;
   }
 
   /**
-   * Perform phonetic matching.
+   * Keeps a suffix trie with all terms which match the suffix. It is used to optimize
+   * contains and suffix queries.
    */
-  public TextField phonetic(String matcher) {
-    this.phoneticMatcher = matcher;
+  public TextField withSuffixTrie() {
+    this.withSuffixTrie = true;
     return this;
   }
 
   /**
-   * Declares the importance of this attribute when calculating result accuracy. This is a
-   * multiplication factor.
+   * Sorts the results by the value of this field.
    */
-  public TextField weight(double weight) {
-    this.weight = weight;
+  public TextField sortable() {
+    this.sortable = true;
     return this;
   }
 
   /**
-   * Keeps a suffix trie with all terms which match the suffix. It is used to optimize
-   * contains and suffix queries.
+   * Sorts the results by the value of this field without normalization.
    */
-  public TextField withSuffixTrie() {
-    this.withSuffixTrie = true;
+  public TextField sortableUNF() {
+    this.sortableUNF = true;
+    return this;
+  }
+
+  /**
+   * @deprecated Use {@code TextField#sortableUNF()}.
+   * @see TextField#sortableUNF()
+   */
+  @Deprecated
+  public TextField sortableUnNormalizedForm() {
+    return sortableUNF();
+  }
+
+  /**
+   * Avoid indexing.
+   */
+  public TextField noIndex() {
+    this.noIndex = true;
     return this;
   }
 
@@ -107,6 +123,13 @@ public void addParams(CommandArguments args) {
     args.addParams(fieldName);
     args.add(TEXT);
 
+    if (indexMissing) {
+      args.add(INDEXMISSING);
+    }
+    if (indexEmpty) {
+      args.add(INDEXEMPTY);
+    }
+
     if (weight != null) {
       args.add(WEIGHT).add(weight);
     }
diff --git a/src/main/java/redis/clients/jedis/search/schemafields/VectorField.java b/src/main/java/redis/clients/jedis/search/schemafields/VectorField.java
index 02287a5be3..f550f66e77 100644
--- a/src/main/java/redis/clients/jedis/search/schemafields/VectorField.java
+++ b/src/main/java/redis/clients/jedis/search/schemafields/VectorField.java
@@ -1,5 +1,6 @@
 package redis.clients.jedis.search.schemafields;
 
+import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.INDEXMISSING;
 import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.VECTOR;
 
 import java.util.LinkedHashMap;
@@ -17,6 +18,9 @@ public enum VectorAlgorithm {
   private final VectorAlgorithm algorithm;
   private final Map attributes;
 
+  private boolean indexMissing;
+  // private boolean noIndex; // throws Field `NOINDEX` does not have a type
+
   public VectorField(String fieldName, VectorAlgorithm algorithm, Map attributes) {
     super(fieldName);
     this.algorithm = algorithm;
@@ -35,14 +39,23 @@ public VectorField as(String attribute) {
     return this;
   }
 
+  public VectorField indexMissing() {
+    this.indexMissing = true;
+    return this;
+  }
+
   @Override
   public void addParams(CommandArguments args) {
     args.addParams(fieldName);
     args.add(VECTOR);
 
     args.add(algorithm);
-    args.add(attributes.size() * 2);
+    args.add(attributes.size() << 1);
     attributes.forEach((name, value) -> args.add(name).add(value));
+
+    if (indexMissing) {
+      args.add(INDEXMISSING);
+    }
   }
 
   public static Builder builder() {
diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
index cc24fc21de..222d8248d7 100644
--- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
+++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
@@ -28,6 +28,7 @@
 import redis.clients.jedis.json.Path;
 import redis.clients.jedis.search.*;
 import redis.clients.jedis.search.schemafields.*;
+import redis.clients.jedis.search.schemafields.GeoShapeField.CoordinateSystem;
 import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;
 import redis.clients.jedis.modules.RedisModuleCommandsTestBase;
 
@@ -194,19 +195,19 @@ public void search() {
       addDocument(String.format("doc%d", i), fields);
     }
 
-    SearchResult res = client.ftSearch(index, "hello world",
+    SearchResult result = client.ftSearch(index, "hello world",
         FTSearchParams.searchParams().limit(0, 5).withScores());
-    assertEquals(100, res.getTotalResults());
-    assertEquals(5, res.getDocuments().size());
-    for (Document d : res.getDocuments()) {
+    assertEquals(100, result.getTotalResults());
+    assertEquals(5, result.getDocuments().size());
+    for (Document d : result.getDocuments()) {
       assertTrue(d.getId().startsWith("doc"));
       assertTrue(d.getScore() < 100);
     }
 
     client.del("doc0");
 
-    res = client.ftSearch(index, "hello world");
-    assertEquals(99, res.getTotalResults());
+    result = client.ftSearch(index, "hello world");
+    assertEquals(99, result.getTotalResults());
 
     assertEquals("OK", client.ftDropIndex(index));
     try {
@@ -216,6 +217,57 @@ public void search() {
     }
   }
 
+  @Test
+  public void textFieldParams() {
+    assertOK(client.ftCreate("testindex", TextField.of("title").indexMissing().indexEmpty()
+        .weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortable()));
+
+    assertOK(client.ftCreate("testunfindex", TextField.of("title").indexMissing().indexEmpty()
+        .weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortableUNF()));
+
+    assertOK(client.ftCreate("testnoindex", TextField.of("title").sortable().noIndex()));
+
+    assertOK(client.ftCreate("testunfnoindex", TextField.of("title").sortableUNF().noIndex()));
+  }
+
+  @Test
+  public void searchTextFieldsCondition() {
+    assertOK(client.ftCreate(index, FTCreateParams.createParams(), TextField.of("title"),
+        TextField.of("body").indexMissing().indexEmpty()));
+
+    Map regular = new HashMap<>();
+    regular.put("title", "hello world");
+    regular.put("body", "lorem ipsum");
+    client.hset("regular-doc", regular);
+
+    Map empty = new HashMap<>();
+    empty.put("title", "hello world");
+    empty.put("body", "");
+    client.hset("empty-doc", empty);
+
+    Map missing = new HashMap<>();
+    missing.put("title", "hello world");
+    client.hset("missing-doc", missing);
+
+    SearchResult result = client.ftSearch(index, "", FTSearchParams.searchParams().dialect(2));
+    assertEquals(0, result.getTotalResults());
+    assertEquals(0, result.getDocuments().size());
+
+    result = client.ftSearch(index, "*", FTSearchParams.searchParams().dialect(2));
+    assertEquals(3, result.getTotalResults());
+    assertEquals(3, result.getDocuments().size());
+
+    result = client.ftSearch(index, "@body:''", FTSearchParams.searchParams().dialect(2));
+    assertEquals(1, result.getTotalResults());
+    assertEquals(1, result.getDocuments().size());
+    assertEquals("empty-doc", result.getDocuments().get(0).getId());
+
+    result = client.ftSearch(index, "ismissing(@body)", FTSearchParams.searchParams().dialect(2));
+    assertEquals(1, result.getTotalResults());
+    assertEquals(1, result.getDocuments().size());
+    assertEquals("missing-doc", result.getDocuments().get(0).getId());
+  }
+
   @Test
   public void numericFilter() {
     assertOK(client.ftCreate(index, TextField.of("title"), NumericField.of("price")));
@@ -268,7 +320,15 @@ public void numericFilter() {
             .filter("price", Double.NEGATIVE_INFINITY, 10));
     assertEquals(11, res.getTotalResults());
     assertEquals(10, res.getDocuments().size());
+  }
 
+  @Test
+  public void numericFieldParams() {
+    assertOK(client.ftCreate("testindex", TextField.of("title"),
+        NumericField.of("price").as("px").indexMissing().sortable()));
+
+    assertOK(client.ftCreate("testnoindex", TextField.of("title"),
+        NumericField.of("price").as("px").sortable().noIndex()));
   }
 
   @Test
@@ -349,12 +409,19 @@ public void geoFilterAndGeoCoordinateObject() {
     assertEquals(2, res.getTotalResults());
   }
 
+  @Test
+  public void geoFieldParams() {
+    assertOK(client.ftCreate("testindex", TextField.of("title"), GeoField.of("location").as("loc").indexMissing().sortable()));
+
+    assertOK(client.ftCreate("testnoindex", TextField.of("title"), GeoField.of("location").as("loc").sortable().noIndex()));
+  }
+
   @Test
   public void geoShapeFilterSpherical() throws ParseException {
     final WKTReader reader = new WKTReader();
     final GeometryFactory factory = new GeometryFactory();
 
-    assertOK(client.ftCreate(index, GeoShapeField.of("geom", GeoShapeField.CoordinateSystem.SPHERICAL)));
+    assertOK(client.ftCreate(index, GeoShapeField.of("geom", CoordinateSystem.SPHERICAL)));
 
     // polygon type
     final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(34.9001, 29.7001),
@@ -403,7 +470,7 @@ public void geoShapeFilterFlat() throws ParseException {
     final WKTReader reader = new WKTReader();
     final GeometryFactory factory = new GeometryFactory();
 
-    assertOK(client.ftCreate(index, GeoShapeField.of("geom", GeoShapeField.CoordinateSystem.FLAT)));
+    assertOK(client.ftCreate(index, GeoShapeField.of("geom", CoordinateSystem.FLAT)));
 
     // polygon type
     final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(20, 20),
@@ -459,6 +526,13 @@ public void geoShapeFilterFlat() throws ParseException {
     assertEquals(2, result.getDocuments().size());
   }
 
+  @Test
+  public void geoShapeFieldParams() {
+    assertOK(client.ftCreate("testindex", GeoShapeField.of("geometry", CoordinateSystem.SPHERICAL).as("geom").indexMissing()));
+
+    assertOK(client.ftCreate("testnoindex", GeoShapeField.of("geometry", CoordinateSystem.SPHERICAL).as("geom").noIndex()));
+  }
+
   @Test
   public void testQueryFlags() {
     assertOK(client.ftCreate(index, TextField.of("title")));
@@ -871,6 +945,23 @@ public void caseSensitiveTagField() {
     assertEquals(1, client.ftSearch(index, "hello").getTotalResults());
   }
 
+  @Test
+  public void tagFieldParams() {
+    assertOK(client.ftCreate("testindex", TextField.of("title"),
+        TagField.of("category").as("cat").indexMissing().indexEmpty()
+        .separator(',').caseSensitive().withSuffixTrie().sortable()));
+
+    assertOK(client.ftCreate("testunfindex", TextField.of("title"),
+        TagField.of("category").as("cat").indexMissing().indexEmpty()
+        .separator(',').caseSensitive().withSuffixTrie().sortableUNF()));
+
+    assertOK(client.ftCreate("testnoindex", TextField.of("title"),
+        TagField.of("category").as("cat").sortable().noIndex()));
+
+    assertOK(client.ftCreate("testunfnoindex", TextField.of("title"),
+        TagField.of("category").as("cat").sortableUNF().noIndex()));
+  }
+
   @Test
   public void testReturnFields() {
     assertOK(client.ftCreate(index, TextField.of("field1"), TextField.of("field2")));
@@ -1038,14 +1129,14 @@ public void inOrder() {
   }
 
   @Test
-  public void testHNSWVVectorSimilarity() {
+  public void testHNSWVectorSimilarity() {
     Map attr = new HashMap<>();
     attr.put("TYPE", "FLOAT32");
     attr.put("DIM", 2);
     attr.put("DISTANCE_METRIC", "L2");
 
     assertOK(client.ftCreate(index, VectorField.builder().fieldName("v")
-        .algorithm(VectorField.VectorAlgorithm.HNSW).attributes(attr).build()));
+        .algorithm(VectorAlgorithm.HNSW).attributes(attr).build()));
 
     client.hset("a", "v", "aaaaaaaa");
     client.hset("b", "v", "aaaabaaa");
@@ -1065,7 +1156,7 @@ public void testHNSWVVectorSimilarity() {
   public void testFlatVectorSimilarity() {
     assertOK(client.ftCreate(index,
         VectorField.builder().fieldName("v")
-            .algorithm(VectorField.VectorAlgorithm.FLAT)
+            .algorithm(VectorAlgorithm.FLAT)
             .addAttribute("TYPE", "FLOAT32")
             .addAttribute("DIM", 2)
             .addAttribute("DISTANCE_METRIC", "L2")
@@ -1087,11 +1178,24 @@ public void testFlatVectorSimilarity() {
     assertEquals("0", doc1.get("__v_score"));
   }
 
+  @Test
+  public void vectorFieldParams() {
+    Map attr = new HashMap<>();
+    attr.put("TYPE", "FLOAT32");
+    attr.put("DIM", 2);
+    attr.put("DISTANCE_METRIC", "L2");
+
+    assertOK(client.ftCreate("testindex", new VectorField("vector", VectorAlgorithm.HNSW, attr).as("vec").indexMissing()));
+
+    // assertOK(client.ftCreate("testnoindex", new VectorField("vector", VectorAlgorithm.HNSW, attr).as("vec").noIndex()));
+    // throws Field `NOINDEX` does not have a type
+  }
+
   @Test
   public void float16StorageType() {
     assertOK(client.ftCreate(index,
         VectorField.builder().fieldName("v")
-            .algorithm(VectorField.VectorAlgorithm.HNSW)
+            .algorithm(VectorAlgorithm.HNSW)
             .addAttribute("TYPE", "FLOAT16")
             .addAttribute("DIM", 4)
             .addAttribute("DISTANCE_METRIC", "L2")
@@ -1102,7 +1206,7 @@ public void float16StorageType() {
   public void bfloat16StorageType() {
     assertOK(client.ftCreate(index,
         VectorField.builder().fieldName("v")
-            .algorithm(VectorField.VectorAlgorithm.HNSW)
+            .algorithm(VectorAlgorithm.HNSW)
             .addAttribute("TYPE", "BFLOAT16")
             .addAttribute("DIM", 4)
             .addAttribute("DISTANCE_METRIC", "L2")

From 3534996b897f543a3805bd547b4e21963aba3656 Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Sun, 30 Jun 2024 14:38:07 +0600
Subject: [PATCH 31/48] Little tweak maximumSize test in
 CaffeineClientSideCacheTest

---
 .../clients/jedis/csc/CaffeineClientSideCacheTest.java      | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
index 92b8d14c3a..7fc2d6a8a7 100644
--- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
@@ -58,7 +58,7 @@ public void individualCommandsAndThenStats() {
   @Test
   public void maximumSize() {
     final long maxSize = 10;
-    final long maxEstimatedSize = 50;
+    final long maxEstimatedSize = 52;
     int count = 1000;
     for (int i = 0; i < count; i++) {
       control.set("k" + i, "v" + i);
@@ -68,10 +68,10 @@ public void maximumSize() {
     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));
+        assertThat(caffeine.estimatedSize(), Matchers.lessThan(maxEstimatedSize));
       }
     }
-    assertThat(caffeine.stats().evictionCount(), Matchers.greaterThanOrEqualTo(count - maxEstimatedSize));
+    assertThat(caffeine.stats().evictionCount(), Matchers.greaterThan(count - maxEstimatedSize));
   }
 
   @Test

From 819447c33b4d00e4566aab362e6d73904fa5512f Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Thu, 4 Jul 2024 14:57:11 +0600
Subject: [PATCH 32/48] Inject ClientSideCacheable via set method (#3882)

---
 .../jedis/csc/CaffeineClientSideCache.java    | 19 ++--------------
 .../clients/jedis/csc/ClientSideCache.java    | 10 ++++-----
 .../jedis/csc/GuavaClientSideCache.java       | 22 +++----------------
 .../AllowAndDenyListClientSideCacheTest.java  | 17 +++++++++-----
 .../clients/jedis/csc/MapClientSideCache.java |  5 -----
 5 files changed, 22 insertions(+), 51 deletions(-)

diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
index 5bf86f3e0b..0fa05919e0 100644
--- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
@@ -16,15 +16,7 @@ public CaffeineClientSideCache(Cache caffeineCache) {
   }
 
   public CaffeineClientSideCache(Cache caffeineCache, CommandLongHasher commandHasher) {
-    this(caffeineCache, commandHasher, DefaultClientSideCacheable.INSTANCE);
-  }
-
-  public CaffeineClientSideCache(Cache caffeineCache, ClientSideCacheable cacheable) {
-    this(caffeineCache, SimpleCommandHasher.INSTANCE, cacheable);
-  }
-
-  public CaffeineClientSideCache(Cache caffeineCache, CommandLongHasher commandHasher, ClientSideCacheable cacheable) {
-    super(commandHasher, cacheable);
+    super(commandHasher);
     this.cache = caffeineCache;
   }
 
@@ -61,8 +53,6 @@ public static class Builder {
     // not using a default value to avoid an object creation like 'new OpenHftHashing(hashFunction)'
     private CommandLongHasher commandHasher = SimpleCommandHasher.INSTANCE;
 
-    private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE;
-
     private Builder() { }
 
     public Builder maximumSize(int size) {
@@ -80,11 +70,6 @@ public Builder commandHasher(CommandLongHasher commandHasher) {
       return this;
     }
 
-    public Builder cacheable(ClientSideCacheable cacheable) {
-      this.cacheable = cacheable;
-      return this;
-    }
-
     public CaffeineClientSideCache build() {
       Caffeine cb = Caffeine.newBuilder();
 
@@ -92,7 +77,7 @@ public CaffeineClientSideCache build() {
 
       cb.expireAfterWrite(expireTime, expireTimeUnit);
 
-      return new CaffeineClientSideCache(cb.build(), commandHasher, cacheable);
+      return new CaffeineClientSideCache(cb.build(), commandHasher);
     }
   }
 }
diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
index 0290ef437c..48a23d5ead 100644
--- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
@@ -4,6 +4,7 @@
 import java.util.HashSet;
 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;
@@ -26,15 +27,14 @@ public abstract class ClientSideCache {
 
   private final Map> keyToCommandHashes = new ConcurrentHashMap<>();
   private final CommandLongHasher commandHasher;
-  private final ClientSideCacheable cacheable;
+  private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile
 
   protected ClientSideCache(CommandLongHasher commandHasher) {
-    this(commandHasher, DefaultClientSideCacheable.INSTANCE);
+    this.commandHasher = commandHasher;
   }
 
-  protected ClientSideCache(CommandLongHasher commandHasher, ClientSideCacheable cacheable) {
-    this.commandHasher = commandHasher;
-    this.cacheable = cacheable;
+  public void setCacheable(ClientSideCacheable cacheable) {
+    this.cacheable = Objects.requireNonNull(cacheable, "'cacheable' must not be null");
   }
 
   protected abstract void invalidateAllHashes();
diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
index ca176e8b33..5f5aee86eb 100644
--- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
@@ -25,15 +25,6 @@ public GuavaClientSideCache(Cache guavaCache, CommandLongHasher co
     this.cache = guavaCache;
   }
 
-  public GuavaClientSideCache(Cache guavaCache, ClientSideCacheable cacheable) {
-    this(guavaCache, new GuavaCommandHasher(GuavaCommandHasher.DEFAULT_HASH_FUNCTION), cacheable);
-  }
-
-  public GuavaClientSideCache(Cache cache, CommandLongHasher commandHasher, ClientSideCacheable cacheable) {
-    super(commandHasher, cacheable);
-    this.cache = cache;
-  }
-
   @Override
   protected final void invalidateAllHashes() {
     cache.invalidateAll();
@@ -68,8 +59,6 @@ public static class Builder {
     private HashFunction hashFunction = null;
     private CommandLongHasher commandHasher = null;
 
-    private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE;
-
     private Builder() { }
 
     public Builder maximumSize(int size) {
@@ -94,11 +83,6 @@ public Builder commandHasher(CommandLongHasher commandHasher) {
       return this;
     }
 
-    public Builder cacheable(ClientSideCacheable cacheable) {
-      this.cacheable = cacheable;
-      return this;
-    }
-
     public GuavaClientSideCache build() {
       CacheBuilder cb = CacheBuilder.newBuilder();
 
@@ -106,9 +90,9 @@ public GuavaClientSideCache build() {
 
       cb.expireAfterWrite(expireTime, expireTimeUnit);
 
-      return hashFunction != null ? new GuavaClientSideCache(cb.build(), new GuavaCommandHasher(hashFunction), cacheable)
-          : commandHasher != null ? new GuavaClientSideCache(cb.build(), commandHasher, cacheable)
-              : new GuavaClientSideCache(cb.build(), cacheable);
+      return hashFunction != null ? new GuavaClientSideCache(cb.build(), new GuavaCommandHasher(hashFunction))
+          : commandHasher != null ? new GuavaClientSideCache(cb.build(), commandHasher)
+              : new GuavaClientSideCache(cb.build());
     }
   }
 }
diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
index ceb279032d..d31626ca90 100644
--- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
@@ -5,6 +5,7 @@
 import static org.junit.Assert.assertEquals;
 
 import java.util.HashMap;
+import java.util.Map;
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
@@ -14,11 +15,17 @@
 
 public class AllowAndDenyListClientSideCacheTest extends ClientSideCacheTestBase {
 
+  private static MapClientSideCache createMapClientSideCache(Map map, ClientSideCacheable cacheable) {
+    MapClientSideCache mapCache = new MapClientSideCache(map);
+    mapCache.setCacheable(cacheable);
+    return mapCache;
+  }
+
   @Test
   public void none() {
     HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
-        new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)),
+        createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
       assertThat(map, Matchers.aMapWithSize(0));
@@ -31,7 +38,7 @@ public void none() {
   public void whiteListCommand() {
     HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
-        new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)),
+        createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
       assertThat(map, Matchers.aMapWithSize(0));
@@ -44,7 +51,7 @@ public void whiteListCommand() {
   public void blackListCommand() {
     HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
-        new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)),
+        createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
       assertThat(map, Matchers.aMapWithSize(0));
@@ -57,7 +64,7 @@ public void blackListCommand() {
   public void whiteListKey() {
     HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
-        new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)),
+        createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
       assertThat(map, Matchers.aMapWithSize(0));
@@ -70,7 +77,7 @@ public void whiteListKey() {
   public void blackListKey() {
     HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
-        new MapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))),
+        createMapClientSideCache(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/MapClientSideCache.java b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
index 421eed8b42..e94930cb4a 100644
--- a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
+++ b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
@@ -18,11 +18,6 @@ public MapClientSideCache(Map map) {
     this.cache = map;
   }
 
-  public MapClientSideCache(Map cache, ClientSideCacheable cacheable) {
-    super(SimpleCommandHasher.INSTANCE, cacheable);
-    this.cache = cache;
-  }
-
   @Override
   protected final void invalidateAllHashes() {
     cache.clear();

From a89b2a92579708641ccd4cb94785c667e053df30 Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Thu, 4 Jul 2024 22:54:13 +0600
Subject: [PATCH 33/48] Use CommandObject(s) as cache-key (#3875)

and remove hashing of CommandObject(s).
---
 pom.xml                                       |  6 ---
 .../redis/clients/jedis/CommandObject.java    | 38 +++++++++++++
 .../redis/clients/jedis/args/Rawable.java     |  6 +++
 .../clients/jedis/args/RawableFactory.java    | 11 ++--
 .../jedis/csc/CaffeineClientSideCache.java    | 37 ++++---------
 .../clients/jedis/csc/ClientSideCache.java    | 36 ++++++-------
 .../jedis/csc/GuavaClientSideCache.java       | 53 +++++--------------
 .../jedis/csc/hash/AbstractCommandHasher.java | 27 ----------
 .../csc/hash/AbstractSimpleCommandHasher.java | 28 ----------
 .../jedis/csc/hash/CommandLongHasher.java     | 16 ------
 .../jedis/csc/hash/GuavaCommandHasher.java    | 31 -----------
 .../jedis/csc/hash/SimpleCommandHasher.java   | 25 ---------
 .../clients/jedis/csc/hash/package-info.java  |  8 ---
 .../AllowAndDenyListClientSideCacheTest.java  | 13 ++---
 .../csc/CaffeineClientSideCacheTest.java      |  3 +-
 .../csc/ClientSideCacheFunctionalityTest.java | 16 +++---
 .../jedis/csc/GuavaClientSideCacheTest.java   |  4 +-
 .../csc/JedisClusterClientSideCacheTest.java  |  5 +-
 .../csc/JedisPooledClientSideCacheTest.java   |  5 +-
 .../JedisSentineledClientSideCacheTest.java   |  5 +-
 .../clients/jedis/csc/MapClientSideCache.java | 22 ++++----
 .../jedis/csc/OpenHftCommandHasher.java       | 34 ------------
 22 files changed, 123 insertions(+), 306 deletions(-)
 delete mode 100644 src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHasher.java
 delete mode 100644 src/main/java/redis/clients/jedis/csc/hash/AbstractSimpleCommandHasher.java
 delete mode 100644 src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java
 delete mode 100644 src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java
 delete mode 100644 src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java
 delete mode 100644 src/main/java/redis/clients/jedis/csc/hash/package-info.java
 delete mode 100644 src/test/java/redis/clients/jedis/csc/OpenHftCommandHasher.java

diff --git a/pom.xml b/pom.xml
index 33a1c9bd36..f6b6fdab98 100644
--- a/pom.xml
+++ b/pom.xml
@@ -89,12 +89,6 @@
 			2.9.3
 			true
 		
-		
-			net.openhft
-			zero-allocation-hashing
-			0.16
-			test
-		
 
 		
 		
diff --git a/src/main/java/redis/clients/jedis/CommandObject.java b/src/main/java/redis/clients/jedis/CommandObject.java
index b4931f2634..c44a0be7de 100644
--- a/src/main/java/redis/clients/jedis/CommandObject.java
+++ b/src/main/java/redis/clients/jedis/CommandObject.java
@@ -1,5 +1,8 @@
 package redis.clients.jedis;
 
+import java.util.Iterator;
+import redis.clients.jedis.args.Rawable;
+
 public class CommandObject {
 
   private final CommandArguments arguments;
@@ -17,4 +20,39 @@ public CommandArguments getArguments() {
   public Builder getBuilder() {
     return builder;
   }
+
+  @Override
+  public int hashCode() {
+    int hashCode = 1;
+    for (Rawable e : arguments) {
+      hashCode = 31 * hashCode + e.hashCode();
+    }
+    hashCode = 31 * hashCode + builder.hashCode();
+    return hashCode;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == this) {
+      return true;
+    }
+    if (!(o instanceof CommandObject)) {
+      return false;
+    }
+
+    Iterator e1 = arguments.iterator();
+    Iterator e2 = ((CommandObject) o).arguments.iterator();
+    while (e1.hasNext() && e2.hasNext()) {
+      Rawable o1 = e1.next();
+      Rawable o2 = e2.next();
+      if (!(o1 == null ? o2 == null : o1.equals(o2))) {
+        return false;
+      }
+    }
+    if (e1.hasNext() || e2.hasNext()) {
+      return false;
+    }
+
+    return builder == ((CommandObject) o).builder;
+  }
 }
diff --git a/src/main/java/redis/clients/jedis/args/Rawable.java b/src/main/java/redis/clients/jedis/args/Rawable.java
index 7515386861..be266f58aa 100644
--- a/src/main/java/redis/clients/jedis/args/Rawable.java
+++ b/src/main/java/redis/clients/jedis/args/Rawable.java
@@ -10,4 +10,10 @@ public interface Rawable {
    * @return binary
    */
   byte[] getRaw();
+
+  @Override
+  int hashCode();
+
+  @Override
+  boolean equals(Object o);
 }
diff --git a/src/main/java/redis/clients/jedis/args/RawableFactory.java b/src/main/java/redis/clients/jedis/args/RawableFactory.java
index 813ddd021b..4a2ec782a7 100644
--- a/src/main/java/redis/clients/jedis/args/RawableFactory.java
+++ b/src/main/java/redis/clients/jedis/args/RawableFactory.java
@@ -96,17 +96,12 @@ public int hashCode() {
   /**
    * A {@link Rawable} wrapping a {@link String}.
    */
-  public static class RawString implements Rawable {
+  public static class RawString extends Raw {
 
-    private final byte[] raw;
+    // TODO: private final String str; ^ implements Rawable
 
     public RawString(String str) {
-      this.raw = SafeEncoder.encode(str);
-    }
-
-    @Override
-    public byte[] getRaw() {
-      return raw;
+      super(SafeEncoder.encode(str));
     }
   }
 
diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
index 0fa05919e0..0750c9fff7 100644
--- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
@@ -3,41 +3,34 @@
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import java.util.concurrent.TimeUnit;
-
-import redis.clients.jedis.csc.hash.CommandLongHasher;
-import redis.clients.jedis.csc.hash.SimpleCommandHasher;
+import redis.clients.jedis.CommandObject;
 
 public class CaffeineClientSideCache extends ClientSideCache {
 
-  private final Cache cache;
-
-  public CaffeineClientSideCache(Cache caffeineCache) {
-    this(caffeineCache, SimpleCommandHasher.INSTANCE);
-  }
+  private final Cache cache;
 
-  public CaffeineClientSideCache(Cache caffeineCache, CommandLongHasher commandHasher) {
-    super(commandHasher);
+  public CaffeineClientSideCache(Cache caffeineCache) {
     this.cache = caffeineCache;
   }
 
   @Override
-  protected final void invalidateAllHashes() {
+  protected final void invalidateFullCache() {
     cache.invalidateAll();
   }
 
   @Override
-  protected void invalidateHashes(Iterable hashes) {
-    cache.invalidateAll(hashes);
+  protected void invalidateCache(Iterable> commands) {
+    cache.invalidateAll(commands);
   }
 
   @Override
-  protected void putValue(long hash, Object value) {
-    cache.put(hash, value);
+  protected  void putValue(CommandObject command, T value) {
+    cache.put(command, value);
   }
 
   @Override
-  protected Object getValue(long hash) {
-    return cache.getIfPresent(hash);
+  protected  T getValue(CommandObject command) {
+    return (T) cache.getIfPresent(command);
   }
 
   public static Builder builder() {
@@ -50,9 +43,6 @@ public static class Builder {
     private long expireTime = DEFAULT_EXPIRE_SECONDS;
     private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;
 
-    // not using a default value to avoid an object creation like 'new OpenHftHashing(hashFunction)'
-    private CommandLongHasher commandHasher = SimpleCommandHasher.INSTANCE;
-
     private Builder() { }
 
     public Builder maximumSize(int size) {
@@ -65,11 +55,6 @@ public Builder ttl(int seconds) {
       return this;
     }
 
-    public Builder commandHasher(CommandLongHasher commandHasher) {
-      this.commandHasher = commandHasher;
-      return this;
-    }
-
     public CaffeineClientSideCache build() {
       Caffeine cb = Caffeine.newBuilder();
 
@@ -77,7 +62,7 @@ public CaffeineClientSideCache build() {
 
       cb.expireAfterWrite(expireTime, expireTimeUnit);
 
-      return new CaffeineClientSideCache(cb.build(), commandHasher);
+      return new CaffeineClientSideCache(cb.build());
     }
   }
 }
diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
index 48a23d5ead..31b1239821 100644
--- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
@@ -1,7 +1,6 @@
 package redis.clients.jedis.csc;
 
 import java.nio.ByteBuffer;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -11,7 +10,6 @@
 
 import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.annots.Experimental;
-import redis.clients.jedis.csc.hash.CommandLongHasher;
 import redis.clients.jedis.util.SafeEncoder;
 
 /**
@@ -25,25 +23,23 @@ public abstract class ClientSideCache {
   protected static final int DEFAULT_MAXIMUM_SIZE = 10_000;
   protected static final int DEFAULT_EXPIRE_SECONDS = 100;
 
-  private final Map> keyToCommandHashes = new ConcurrentHashMap<>();
-  private final CommandLongHasher commandHasher;
+  private final Map>> keyToCommandHashes = new ConcurrentHashMap<>();
   private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile
 
-  protected ClientSideCache(CommandLongHasher commandHasher) {
-    this.commandHasher = commandHasher;
+  protected ClientSideCache() {
   }
 
   public void setCacheable(ClientSideCacheable cacheable) {
     this.cacheable = Objects.requireNonNull(cacheable, "'cacheable' must not be null");
   }
 
-  protected abstract void invalidateAllHashes();
+  protected abstract void invalidateFullCache();
 
-  protected abstract void invalidateHashes(Iterable hashes);
+  protected abstract void invalidateCache(Iterable> commands);
 
-  protected abstract void putValue(long hash, Object value);
+  protected abstract  void putValue(CommandObject command, T value);
 
-  protected abstract Object getValue(long hash);
+  protected abstract  T getValue(CommandObject command);
 
   public final void clear() {
     invalidateAllKeysAndCommandHashes();
@@ -63,7 +59,7 @@ public final void invalidate(List list) {
   }
 
   private void invalidateAllKeysAndCommandHashes() {
-    invalidateAllHashes();
+    invalidateFullCache();
     keyToCommandHashes.clear();
   }
 
@@ -76,9 +72,9 @@ private void invalidateKeyAndRespectiveCommandHashes(Object key) {
 //    final ByteBuffer mapKey = makeKeyForKeyToCommandHashes((byte[]) key);
     final ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key);
 
-    Set hashes = keyToCommandHashes.get(mapKey);
-    if (hashes != null) {
-      invalidateHashes(hashes);
+    Set> commands = keyToCommandHashes.get(mapKey);
+    if (commands != null) {
+      invalidateCache(commands);
       keyToCommandHashes.remove(mapKey);
     }
   }
@@ -89,23 +85,21 @@ public final  T get(Function, T> loader, CommandObject co
       return loader.apply(command);
     }
 
-    final long hash = commandHasher.hash(command);
-
-    T value = (T) getValue(hash);
+    T value = getValue(command);
     if (value != null) {
       return value;
     }
 
     value = loader.apply(command);
     if (value != null) {
-      putValue(hash, value);
+      putValue(command, value);
       for (Object key : keys) {
         ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key);
         if (keyToCommandHashes.containsKey(mapKey)) {
-          keyToCommandHashes.get(mapKey).add(hash);
+          keyToCommandHashes.get(mapKey).add(command);
         } else {
-          Set set = new HashSet<>();
-          set.add(hash);
+          Set> set = ConcurrentHashMap.newKeySet();
+          set.add(command);
           keyToCommandHashes.put(mapKey, set);
         }
       }
diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
index 5f5aee86eb..ccde8af60c 100644
--- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
@@ -2,47 +2,36 @@
 
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
-import com.google.common.hash.HashFunction;
 import java.util.concurrent.TimeUnit;
-
-import redis.clients.jedis.csc.hash.CommandLongHasher;
-import redis.clients.jedis.csc.hash.GuavaCommandHasher;
+import redis.clients.jedis.CommandObject;
 
 public class GuavaClientSideCache extends ClientSideCache {
 
-  private final Cache cache;
-
-  public GuavaClientSideCache(Cache guavaCache) {
-    this(guavaCache, GuavaCommandHasher.DEFAULT_HASH_FUNCTION);
-  }
-
-  public GuavaClientSideCache(Cache guavaCache, HashFunction hashFunction) {
-    this(guavaCache, new GuavaCommandHasher(hashFunction));
-  }
+  private final Cache cache;
 
-  public GuavaClientSideCache(Cache guavaCache, CommandLongHasher commandHasher) {
-    super(commandHasher);
+  public GuavaClientSideCache(Cache guavaCache) {
+    super();
     this.cache = guavaCache;
   }
 
   @Override
-  protected final void invalidateAllHashes() {
+  protected final void invalidateFullCache() {
     cache.invalidateAll();
   }
 
   @Override
-  protected void invalidateHashes(Iterable hashes) {
-    cache.invalidateAll(hashes);
+  protected void invalidateCache(Iterable> commands) {
+    cache.invalidateAll(commands);
   }
 
   @Override
-  protected void putValue(long hash, Object value) {
-    cache.put(hash, value);
+  protected  void putValue(CommandObject command, T value) {
+    cache.put(command, value);
   }
 
   @Override
-  protected Object getValue(long hash) {
-    return cache.getIfPresent(hash);
+  protected  T getValue(CommandObject command) {
+    return (T) cache.getIfPresent(command);
   }
 
   public static Builder builder() {
@@ -55,10 +44,6 @@ public static class Builder {
     private long expireTime = DEFAULT_EXPIRE_SECONDS;
     private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;
 
-    // not using a default value to avoid an object creation like 'new GuavaHashing(hashFunction)'
-    private HashFunction hashFunction = null;
-    private CommandLongHasher commandHasher = null;
-
     private Builder() { }
 
     public Builder maximumSize(int size) {
@@ -71,18 +56,6 @@ public Builder ttl(int seconds) {
       return this;
     }
 
-    public Builder hashFunction(HashFunction function) {
-      this.hashFunction = function;
-      this.commandHasher = null;
-      return this;
-    }
-
-    public Builder commandHasher(CommandLongHasher commandHasher) {
-      this.commandHasher = commandHasher;
-      this.hashFunction = null;
-      return this;
-    }
-
     public GuavaClientSideCache build() {
       CacheBuilder cb = CacheBuilder.newBuilder();
 
@@ -90,9 +63,7 @@ public GuavaClientSideCache build() {
 
       cb.expireAfterWrite(expireTime, expireTimeUnit);
 
-      return hashFunction != null ? new GuavaClientSideCache(cb.build(), new GuavaCommandHasher(hashFunction))
-          : commandHasher != null ? new GuavaClientSideCache(cb.build(), commandHasher)
-              : new GuavaClientSideCache(cb.build());
+      return new GuavaClientSideCache(cb.build());
     }
   }
 }
diff --git a/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHasher.java b/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHasher.java
deleted file mode 100644
index 2c71e4ea80..0000000000
--- a/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHasher.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package redis.clients.jedis.csc.hash;
-
-import redis.clients.jedis.Builder;
-import redis.clients.jedis.CommandObject;
-import redis.clients.jedis.args.Rawable;
-
-public abstract class AbstractCommandHasher implements CommandLongHasher {
-  
-  @Override
-  public final long hash(CommandObject command) {
-    long[] nums = new long[command.getArguments().size() + 1];
-    int idx = 0;
-    for (Rawable raw : command.getArguments()) {
-      nums[idx++] = hashRawable(raw);
-    }
-    nums[idx] = hashBuilder(command.getBuilder());
-    return hashLongs(nums);
-  }
-
-  protected abstract long hashLongs(long[] longs);
-
-  protected abstract long hashRawable(Rawable raw);
-
-  protected long hashBuilder(Builder builder) {
-    return builder.hashCode();
-  }
-}
diff --git a/src/main/java/redis/clients/jedis/csc/hash/AbstractSimpleCommandHasher.java b/src/main/java/redis/clients/jedis/csc/hash/AbstractSimpleCommandHasher.java
deleted file mode 100644
index 3b782c2341..0000000000
--- a/src/main/java/redis/clients/jedis/csc/hash/AbstractSimpleCommandHasher.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package redis.clients.jedis.csc.hash;
-
-import redis.clients.jedis.Builder;
-import redis.clients.jedis.args.Rawable;
-
-/**
- * It is possible to extend {@link AbstractSimpleCommandHasher this abstract class} in order to implement
- * {@link CommandLongHasher} as {@link AbstractSimpleCommandHasher#hashLongs(long[])} and
- * {@link AbstractSimpleCommandHasher#hashBytes(byte[])} are supported by almost all Java hashing libraries.
- */
-public abstract class AbstractSimpleCommandHasher extends AbstractCommandHasher {
-
-  @Override
-  protected final long hashRawable(Rawable raw) {
-    return hashBytes(raw.getRaw());
-  }
-
-  @Override
-  protected final long hashBuilder(Builder builder) {
-    return hashInt(builder.hashCode());
-  }
-
-  protected abstract long hashBytes(byte[] bytes);
-
-  protected long hashInt(int hashCode) {
-    return hashCode;
-  }
-}
diff --git a/src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java
deleted file mode 100644
index bb0b03e072..0000000000
--- a/src/main/java/redis/clients/jedis/csc/hash/CommandLongHasher.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package redis.clients.jedis.csc.hash;
-
-import redis.clients.jedis.CommandObject;
-
-/**
- * The interface for hashing a command object to support client-side caching.
- */
-public interface CommandLongHasher {
-
-  /**
-   * Produce a 64-bit signed hash value from a command object.
-   * @param command the command object
-   * @return 64-bit signed hash value
-   */
-  long hash(CommandObject command);
-}
diff --git a/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java b/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java
deleted file mode 100644
index 3208049e3a..0000000000
--- a/src/main/java/redis/clients/jedis/csc/hash/GuavaCommandHasher.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package redis.clients.jedis.csc.hash;
-
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hasher;
-import redis.clients.jedis.CommandObject;
-
-/**
- * An implementation of {@link CommandLongHasher} based on {@link HashFunction} from Google Guava library.
- */
-public final class GuavaCommandHasher implements CommandLongHasher {
-
-  public static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011();
-
-  private final HashFunction function;
-
-  /**
-   * It is advised to use a {@link HashFunction} capable of producing 64-bit hash.
-   * @param function an implementation of hash function
-   */
-  public GuavaCommandHasher(HashFunction function) {
-    this.function = function;
-  }
-
-  @Override
-  public long hash(CommandObject command) {
-    Hasher hasher = function.newHasher();
-    command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw()));
-    hasher.putInt(command.getBuilder().hashCode());
-    return hasher.hash().asLong();
-  }
-}
diff --git a/src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java b/src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java
deleted file mode 100644
index 1414d97295..0000000000
--- a/src/main/java/redis/clients/jedis/csc/hash/SimpleCommandHasher.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package redis.clients.jedis.csc.hash;
-
-import java.util.Arrays;
-
-/**
- * This {@link CommandLongHasher} implementation is simply based on {@link Arrays#hashCode(long[])}
- * and {@link Arrays#hashCode(byte[])}. These methods actually produce 32-bit hash codes. It is
- * advised to use proper 64-bit hash codes in production.
- */
-public final class SimpleCommandHasher extends AbstractSimpleCommandHasher {
-
-  public static final SimpleCommandHasher INSTANCE = new SimpleCommandHasher();
-
-  public SimpleCommandHasher() { }
-
-  @Override
-  protected long hashLongs(long[] longs) {
-    return Arrays.hashCode(longs);
-  }
-
-  @Override
-  protected long hashBytes(byte[] bytes) {
-    return Arrays.hashCode(bytes);
-  }
-}
diff --git a/src/main/java/redis/clients/jedis/csc/hash/package-info.java b/src/main/java/redis/clients/jedis/csc/hash/package-info.java
deleted file mode 100644
index 0667e1f98b..0000000000
--- a/src/main/java/redis/clients/jedis/csc/hash/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * This package contains the classes and interface for hashing command arguments to support
- * Server-assisted Client-side Caching.
- */
-@Experimental
-package redis.clients.jedis.csc.hash;
-
-import redis.clients.jedis.annots.Experimental;
\ No newline at end of file
diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
index d31626ca90..21ad307668 100644
--- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
@@ -9,13 +9,14 @@
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
+import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.JedisPooled;
 import redis.clients.jedis.Protocol;
 import redis.clients.jedis.csc.util.AllowAndDenyListWithStringKeys;
 
 public class AllowAndDenyListClientSideCacheTest extends ClientSideCacheTestBase {
 
-  private static MapClientSideCache createMapClientSideCache(Map map, ClientSideCacheable cacheable) {
+  private static MapClientSideCache createMapClientSideCache(Map map, ClientSideCacheable cacheable) {
     MapClientSideCache mapCache = new MapClientSideCache(map);
     mapCache.setCacheable(cacheable);
     return mapCache;
@@ -23,7 +24,7 @@ private static MapClientSideCache createMapClientSideCache(Map map
 
   @Test
   public void none() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)),
         singleConnectionPoolConfig.get())) {
@@ -36,7 +37,7 @@ public void none() {
 
   @Test
   public void whiteListCommand() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)),
         singleConnectionPoolConfig.get())) {
@@ -49,7 +50,7 @@ public void whiteListCommand() {
 
   @Test
   public void blackListCommand() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)),
         singleConnectionPoolConfig.get())) {
@@ -62,7 +63,7 @@ public void blackListCommand() {
 
   @Test
   public void whiteListKey() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)),
         singleConnectionPoolConfig.get())) {
@@ -75,7 +76,7 @@ public void whiteListKey() {
 
   @Test
   public void blackListKey() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))),
         singleConnectionPoolConfig.get())) {
diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
index 7fc2d6a8a7..8c1074f097 100644
--- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
@@ -34,8 +34,7 @@ public void individualCommandsAndThenStats() {
     Cache caffeine = Caffeine.newBuilder().recordStats().build();
 
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
-        new CaffeineClientSideCache(caffeine, new OpenHftCommandHasher()),
-        singleConnectionPoolConfig.get())) {
+        new CaffeineClientSideCache(caffeine), singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
       assertEquals(0, caffeine.estimatedSize());
       assertEquals("bar", jedis.get("foo"));
diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
index 6670da47d5..a44f026fc5 100644
--- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
+++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
@@ -12,6 +12,8 @@
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import org.junit.Test;
+
+import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.JedisPooled;
 import redis.clients.jedis.util.JedisURIHelper;
 
@@ -24,7 +26,7 @@ public void flushEntireCache() {
       control.set("k" + i, "v" + i);
     }
 
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     ClientSideCache clientSideCache = new MapClientSideCache(map);
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) {
       for (int i = 0; i < count; i++) {
@@ -45,7 +47,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<>();
+    LinkedHashMap map = new LinkedHashMap<>();
     ClientSideCache clientSideCache = new MapClientSideCache(map);
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) {
       for (int i = 0; i < count; i++) {
@@ -53,14 +55,14 @@ public void removeSpecificKey() {
       }
     }
 
-    ArrayList commandHashes = new ArrayList<>(map.keySet());
+    ArrayList commandHashes = new ArrayList<>(map.keySet());
     assertEquals(count, map.size());
     for (int i = 0; i < count; i++) {
       String key = "k" + i;
-      Long hash = commandHashes.get(i);
-      assertTrue(map.containsKey(hash));
+      CommandObject command = commandHashes.get(i);
+      assertTrue(map.containsKey(command));
       clientSideCache.removeKey(key);
-      assertFalse(map.containsKey(hash));
+      assertFalse(map.containsKey(command));
     }
   }
 
@@ -69,7 +71,7 @@ public void multiKeyOperation() {
     control.set("k1", "v1");
     control.set("k2", "v2");
 
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map))) {
       jedis.mget("k1", "k2");
       assertEquals(1, map.size());
diff --git a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java
index 33a23c55c0..2bc1ab766f 100644
--- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java
@@ -7,7 +7,6 @@
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheStats;
-import com.google.common.hash.Hashing;
 
 import java.net.URI;
 import java.util.concurrent.TimeUnit;
@@ -20,8 +19,7 @@ public class GuavaClientSideCacheTest extends ClientSideCacheTestBase {
 
   @Test
   public void simple() {
-    GuavaClientSideCache guava = GuavaClientSideCache.builder().maximumSize(10).ttl(10)
-        .hashFunction(Hashing.farmHashFingerprint64()).build();
+    GuavaClientSideCache guava = GuavaClientSideCache.builder().maximumSize(10).ttl(10).build();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) {
       control.set("foo", "bar");
       assertEquals("bar", 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 63e543cc1c..158a83055e 100644
--- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java
@@ -15,6 +15,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.Connection;
 import redis.clients.jedis.ConnectionPoolConfig;
 import redis.clients.jedis.DefaultJedisClientConfig;
@@ -62,7 +63,7 @@ public void simple() {
 
   @Test
   public void simpleWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
@@ -92,7 +93,7 @@ public void flushAll() {
 
   @Test
   public void flushAllWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java
index 2d1889fbc8..e26c0b4d61 100644
--- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java
@@ -13,6 +13,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.Connection;
 import redis.clients.jedis.ConnectionPoolConfig;
 import redis.clients.jedis.DefaultJedisClientConfig;
@@ -64,7 +65,7 @@ public void simple() {
 
   @Test
   public void simpleWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
@@ -94,7 +95,7 @@ public void flushAll() {
 
   @Test
   public void flushAllWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java
index b6eb6bec9f..f00cc27ea9 100644
--- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java
@@ -14,6 +14,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.DefaultJedisClientConfig;
 import redis.clients.jedis.HostAndPort;
 import redis.clients.jedis.HostAndPorts;
@@ -59,7 +60,7 @@ public void simple() {
 
   @Test
   public void simpleWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map),
         sentinels, sentinelClientConfig)) {
       control.set("foo", "bar");
@@ -90,7 +91,7 @@ public void flushAll() {
 
   @Test
   public void flushAllWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map),
         sentinels, sentinelClientConfig)) {
       control.set("foo", "bar");
diff --git a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
index e94930cb4a..e8bc975231 100644
--- a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
+++ b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
@@ -3,38 +3,38 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import redis.clients.jedis.csc.hash.SimpleCommandHasher;
+import redis.clients.jedis.CommandObject;
 
 public class MapClientSideCache extends ClientSideCache {
 
-  private final Map cache;
+  private final Map cache;
 
   public MapClientSideCache() {
     this(new HashMap<>());
   }
 
-  public MapClientSideCache(Map map) {
-    super(SimpleCommandHasher.INSTANCE);
+  public MapClientSideCache(Map map) {
+    super();
     this.cache = map;
   }
 
   @Override
-  protected final void invalidateAllHashes() {
+  protected final void invalidateFullCache() {
     cache.clear();
   }
 
   @Override
-  protected void invalidateHashes(Iterable hashes) {
-    hashes.forEach(hash -> cache.remove(hash));
+  protected void invalidateCache(Iterable> commands) {
+    commands.forEach(hash -> cache.remove(hash));
   }
 
   @Override
-  protected void putValue(long hash, Object value) {
-    cache.put(hash, value);
+  protected  void putValue(CommandObject command, T value) {
+    cache.put(command, value);
   }
 
   @Override
-  protected Object getValue(long hash) {
-    return cache.get(hash);
+  protected  T getValue(CommandObject command) {
+    return (T) cache.get(command);
   }
 }
diff --git a/src/test/java/redis/clients/jedis/csc/OpenHftCommandHasher.java b/src/test/java/redis/clients/jedis/csc/OpenHftCommandHasher.java
deleted file mode 100644
index 147ad42e8f..0000000000
--- a/src/test/java/redis/clients/jedis/csc/OpenHftCommandHasher.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package redis.clients.jedis.csc;
-
-import net.openhft.hashing.LongHashFunction;
-import redis.clients.jedis.csc.hash.AbstractSimpleCommandHasher;
-
-public class OpenHftCommandHasher extends AbstractSimpleCommandHasher {
-
-  public static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3();
-
-  private final LongHashFunction function;
-
-  OpenHftCommandHasher() {
-    this(DEFAULT_HASH_FUNCTION);
-  }
-
-  public OpenHftCommandHasher(LongHashFunction function) {
-    this.function = function;
-  }
-
-  @Override
-  protected long hashLongs(long[] longs) {
-    return function.hashLongs(longs);
-  }
-
-  @Override
-  protected long hashBytes(byte[] bytes) {
-    return function.hashBytes(bytes);
-  }
-
-  @Override
-  protected long hashInt(int hashCode) {
-    return function.hashInt(hashCode);
-  }
-}

From 3d9c09e2c4badca5b70436a56a853594aa0e7ffb Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Mon, 15 Jul 2024 14:50:07 +0600
Subject: [PATCH 34/48] #3886 merge fix

---
 .../java/redis/clients/jedis/modules/search/SearchTest.java   | 2 --
 .../clients/jedis/modules/search/SearchWithParamsTest.java    | 4 ----
 2 files changed, 6 deletions(-)

diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java
index 13e490b569..0776eabcd4 100644
--- a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java
+++ b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java
@@ -784,7 +784,6 @@ public void getTagField() {
     assertEquals(1, client.ftSearch(index, new Query("@category:{yellow}")).getTotalResults());
     assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults());
     assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\;purple}")).getTotalResults());
-    assertEquals(1, client.ftSearch(index, new Query("@category:{orange;purple}").dialect(5)).getTotalResults());
     assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults());
 
     assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange;purple")),
@@ -826,7 +825,6 @@ public void testGetTagFieldWithNonDefaultSeparator() {
     assertEquals(1, client.ftSearch(index, new Query("hello @category:{yellow}")).getTotalResults());
     assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults());
     assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\,purple}")).getTotalResults());
-    assertEquals(1, client.ftSearch(index, new Query("@category:{orange,purple}").dialect(5)).getTotalResults());
     assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults());
 
     assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange,purple")),
diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
index 191f7c6d6d..173a5e8c95 100644
--- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
+++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
@@ -879,8 +879,6 @@ public void getTagField() {
     assertEquals(1, client.ftSearch(index, "@category:{yellow}").getTotalResults());
     assertEquals(0, client.ftSearch(index, "@category:{purple}").getTotalResults());
     assertEquals(1, client.ftSearch(index, "@category:{orange\\;purple}").getTotalResults());
-    assertEquals(1, client.ftSearch(index, "@category:{orange;purple}",
-        FTSearchParams.searchParams().dialect(5)).getTotalResults());
     assertEquals(4, client.ftSearch(index, "hello").getTotalResults());
 
     assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange;purple")),
@@ -920,8 +918,6 @@ public void testGetTagFieldWithNonDefaultSeparator() {
     assertEquals(1, client.ftSearch(index, "hello @category:{yellow}").getTotalResults());
     assertEquals(0, client.ftSearch(index, "@category:{purple}").getTotalResults());
     assertEquals(1, client.ftSearch(index, "@category:{orange\\,purple}").getTotalResults());
-    assertEquals(1, client.ftSearch(index, "@category:{orange,purple}",
-        FTSearchParams.searchParams().dialect(5)).getTotalResults());
     assertEquals(4, client.ftSearch(index, "hello").getTotalResults());
 
     assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange,purple")),

From 1c12fdd33c8323aba265109e659bdd1cae24c023 Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Mon, 15 Jul 2024 14:52:53 +0600
Subject: [PATCH 35/48] Revert "[TEMPORARY] [TEST] Use
 redis-stack-server:7.4.0-rc1 image for testing"

This reverts commit 92c09f3aab79629b505743b5a85169590410555e.
---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 6262d61ad4..1800f00d7e 100644
--- a/Makefile
+++ b/Makefile
@@ -446,7 +446,7 @@ start: stunnel cleanup compile-module
 	echo "$$REDIS_UDS" | redis-server -
 	echo "$$REDIS_UNAVAILABLE_CONF" | redis-server -
 	redis-cli -a cluster --cluster create 127.0.0.1:7479 127.0.0.1:7480 127.0.0.1:7481 --cluster-yes
-	docker run -p 6479:6379 --name jedis-stack -d redis/redis-stack-server:7.4.0-rc1
+	docker run -p 6479:6379 --name jedis-stack -d redis/redis-stack-server:edge
 
 cleanup:
 	- rm -vf /tmp/redis_cluster_node*.conf 2>/dev/null

From fed9aaf338dcc59558e2cc4c3381766792f5fcf2 Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Mon, 15 Jul 2024 16:08:30 +0600
Subject: [PATCH 36/48] More tweak maximumSize test in
 CaffeineClientSideCacheTest

This reverts and modifies commit 3534996b897f543a3805bd547b4e21963aba3656.
---
 .../redis/clients/jedis/csc/CaffeineClientSideCacheTest.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
index 8c1074f097..a884f85195 100644
--- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
@@ -67,10 +67,10 @@ public void maximumSize() {
     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.lessThan(maxEstimatedSize));
+        assertThat(caffeine.estimatedSize(), Matchers.lessThanOrEqualTo(maxEstimatedSize));
       }
     }
-    assertThat(caffeine.stats().evictionCount(), Matchers.greaterThan(count - maxEstimatedSize));
+    assertThat(caffeine.stats().evictionCount(), Matchers.greaterThanOrEqualTo(count - maxEstimatedSize));
   }
 
   @Test

From 94a2523a11159ec2f2fe9f0c5a4cfb1a81e46ce8 Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Mon, 15 Jul 2024 17:42:30 +0600
Subject: [PATCH 37/48] Remove client side cache support through uri/url
 (#3892)

This partially reverts #3703 and #3835
---
 .../redis/clients/jedis/UnifiedJedis.java     |  5 +-
 .../clients/jedis/util/JedisURIHelper.java    | 82 -------------------
 .../csc/CaffeineClientSideCacheTest.java      | 21 -----
 .../csc/ClientSideCacheFunctionalityTest.java | 30 -------
 .../jedis/csc/GuavaClientSideCacheTest.java   | 21 -----
 5 files changed, 2 insertions(+), 157 deletions(-)

diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java
index dd551b60d4..3a2dd9ab3e 100644
--- a/src/main/java/redis/clients/jedis/UnifiedJedis.java
+++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java
@@ -75,7 +75,7 @@ public UnifiedJedis(final URI uri) {
     this(JedisURIHelper.getHostAndPort(uri), DefaultJedisClientConfig.builder()
         .user(JedisURIHelper.getUser(uri)).password(JedisURIHelper.getPassword(uri))
         .database(JedisURIHelper.getDBIndex(uri)).protocol(JedisURIHelper.getRedisProtocol(uri))
-        .ssl(JedisURIHelper.isRedisSSLScheme(uri)).build(), JedisURIHelper.getClientSideCache(uri));
+        .ssl(JedisURIHelper.isRedisSSLScheme(uri)).build());
   }
 
   public UnifiedJedis(final URI uri, JedisClientConfig config) {
@@ -87,8 +87,7 @@ public UnifiedJedis(final URI uri, JedisClientConfig config) {
         .database(JedisURIHelper.getDBIndex(uri)).clientName(config.getClientName())
         .protocol(JedisURIHelper.getRedisProtocol(uri))
         .ssl(JedisURIHelper.isRedisSSLScheme(uri)).sslSocketFactory(config.getSslSocketFactory())
-        .sslParameters(config.getSslParameters()).hostnameVerifier(config.getHostnameVerifier())
-        .build(), JedisURIHelper.getClientSideCache(uri));
+        .sslParameters(config.getSslParameters()).hostnameVerifier(config.getHostnameVerifier()).build());
   }
 
   public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) {
diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java
index 4e4f96fee4..a565d38048 100644
--- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java
+++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java
@@ -4,10 +4,6 @@
 import redis.clients.jedis.HostAndPort;
 import redis.clients.jedis.Protocol;
 import redis.clients.jedis.RedisProtocol;
-import redis.clients.jedis.annots.Experimental;
-import redis.clients.jedis.csc.CaffeineClientSideCache;
-import redis.clients.jedis.csc.ClientSideCache;
-import redis.clients.jedis.csc.GuavaClientSideCache;
 
 public final class JedisURIHelper {
 
@@ -75,84 +71,6 @@ public static RedisProtocol getRedisProtocol(URI uri) {
     return null; // null (default) when not defined
   }
 
-  private static final Integer ZERO_INTEGER = 0;
-
-  @Experimental
-  public static ClientSideCache getClientSideCache(URI uri) {
-    if (uri.getQuery() == null) return null;
-
-    boolean guava = false, caffeine = false; // cache_lib
-    Integer maxSize = null; // cache_max_size --> 0 = disbale
-    Integer ttl = null; // cache_ttl --> 0 = no ttl
-    // cache-max-idle
-
-    String[] params = uri.getQuery().split("&");
-    for (String param : params) {
-      int idx = param.indexOf("=");
-      if (idx < 0) continue;
-
-      String key = param.substring(0, idx);
-      String val = param.substring(idx + 1);
-
-      switch (key) {
-
-        case "cache_lib":
-          switch (val) {
-            case "guava":
-              guava = true;
-              break;
-            case "caffeine":
-              caffeine = true;
-              break;
-            default:
-              throw new IllegalArgumentException("Unsupported library " + val);
-          }
-          break;
-
-        case "cache_max_size":
-          try {
-            maxSize = Integer.parseInt(val);
-          } catch (NumberFormatException nfe) {
-            throw new IllegalArgumentException("Value of cache_max_size must be an integer (no of commands).", nfe);
-          }
-          break;
-
-        case "cache_ttl":
-          try {
-            ttl = Integer.parseInt(val);
-          } catch (NumberFormatException nfe) {
-            throw new IllegalArgumentException("Value of cache_ttl must be an integer (in seconds).", nfe);
-          }
-          break;
-      }
-    }
-
-    // special cases
-    if (ZERO_INTEGER.equals(maxSize)) {
-      return null;
-    }
-    if (!guava && !caffeine && (maxSize != null || ttl != null)) {
-      throw new IllegalArgumentException("A supported caching library (guava OR caffeine) must be selected.");
-    }
-    if (ZERO_INTEGER.equals(ttl)) {
-      ttl = null; // below, only null will be checked
-    }
-
-    if (guava) {
-      GuavaClientSideCache.Builder guavaBuilder = GuavaClientSideCache.builder();
-      if (maxSize != null) guavaBuilder.maximumSize(maxSize);
-      if (ttl != null) guavaBuilder.ttl(ttl);
-      return guavaBuilder.build();
-    } else if (caffeine) {
-      CaffeineClientSideCache.Builder caffeineBuilder = CaffeineClientSideCache.builder();
-      if (maxSize != null) caffeineBuilder.maximumSize(maxSize);
-      if (ttl != null) caffeineBuilder.ttl(ttl);
-      return caffeineBuilder.build();
-    }
-
-    return null; // null (default) when not defined
-  }
-
   public static boolean isValid(URI uri) {
     if (isEmpty(uri.getScheme()) || isEmpty(uri.getHost()) || uri.getPort() == -1) {
       return false;
diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
index a884f85195..8308233603 100644
--- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java
@@ -95,25 +95,4 @@ public void timeToLive() throws InterruptedException {
     assertThat(caffeine.stats().evictionCount(), Matchers.equalTo((long) count));
   }
 
-  @Test
-  public void uriSimple() {
-    URI uri = URI.create(baseUrl + "?cache_lib=caffeine");
-    ClientSideCache cache = JedisURIHelper.getClientSideCache(uri);
-    assertThat(cache, Matchers.instanceOf(CaffeineClientSideCache.class));
-  }
-
-  @Test
-  public void uriAllParams() {
-    URI uri = URI.create(baseUrl + "?cache_lib=caffeine&cache_max_size=1000&cache_ttl=10");
-    ClientSideCache cache = JedisURIHelper.getClientSideCache(uri);
-    assertThat(cache, Matchers.instanceOf(CaffeineClientSideCache.class));
-  }
-
-  @Test
-  public void uriMaxSizeZeroMeansNull() {
-    URI uri = URI.create(baseUrl + "?cache_lib=caffeine&cache_max_size=0");
-    ClientSideCache cache = JedisURIHelper.getClientSideCache(uri);
-    assertThat(cache, Matchers.nullValue());
-  }
-
 }
diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
index a44f026fc5..3c53d61e21 100644
--- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
+++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
@@ -78,34 +78,4 @@ public void multiKeyOperation() {
     }
   }
 
-  @Test
-  public void uriNoParam() {
-    URI uri = URI.create(baseUrl + "?");
-    assertNull(JedisURIHelper.getClientSideCache(uri));
-  }
-
-  @Test
-  public void uriUnknownLib() {
-    URI uri = URI.create(baseUrl + "?cache_lib=unknown");
-    IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
-        () -> JedisURIHelper.getClientSideCache(uri));
-    assertEquals("Unsupported library unknown", iae.getMessage());
-  }
-
-  @Test
-  public void uriNoLib() {
-    String[] otherParams
-        = new String[]{
-          "?cache_max_size=1000",
-          "?cache_ttl=10",
-          "?cache_max_size=1000&cache_ttl=10"
-        };
-    Arrays.stream(otherParams).forEach(urlParams -> {
-      URI uri = URI.create(baseUrl + urlParams);
-      IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
-          () -> JedisURIHelper.getClientSideCache(uri));
-      assertEquals("A supported caching library (guava OR caffeine) must be selected.", iae.getMessage());
-    });
-  }
-
 }
diff --git a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java
index 2bc1ab766f..4c7362e63b 100644
--- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java
@@ -112,25 +112,4 @@ public void timeToLive() throws InterruptedException {
     assertThat(guava.stats().evictionCount(), Matchers.equalTo((long) count));
   }
 
-  @Test
-  public void uriSimple() {
-    URI uri = URI.create(baseUrl + "?cache_lib=guava");
-    ClientSideCache cache = JedisURIHelper.getClientSideCache(uri);
-    assertThat(cache, Matchers.instanceOf(GuavaClientSideCache.class));
-  }
-
-  @Test
-  public void uriAllParams() {
-    URI uri = URI.create(baseUrl + "?cache_lib=guava&cache_max_size=1000&cache_ttl=10");
-    ClientSideCache cache = JedisURIHelper.getClientSideCache(uri);
-    assertThat(cache, Matchers.instanceOf(GuavaClientSideCache.class));
-  }
-
-  @Test
-  public void uriMaxSizeZeroMeansNull() {
-    URI uri = URI.create(baseUrl + "?cache_lib=guava&cache_max_size=0");
-    ClientSideCache cache = JedisURIHelper.getClientSideCache(uri);
-    assertThat(cache, Matchers.nullValue());
-  }
-
 }

From 25ca055c1f1b0f7159311e4ff4eb6682544288ea Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Mon, 15 Jul 2024 17:46:39 +0600
Subject: [PATCH 38/48] Bump com.google.guava:guava from 33.0.0-jre to
 33.2.1-jre (#3893)

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index f6b6fdab98..9778783701 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,7 +80,7 @@
 		
 			com.google.guava
 			guava
-			33.0.0-jre
+			33.2.1-jre
 			true
 		
 		

From 8b83218d43a6cf353586def0bdc10ae4e124353f Mon Sep 17 00:00:00 2001
From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com>
Date: Thu, 25 Jul 2024 17:14:31 +0600
Subject: [PATCH 39/48] Prepare client side caching - design 2 (#3889)

* Separate CacheConnection

* Introduce CacheKey and CacheEntry

* Little tweak maximumSize test in CaffeineClientSideCacheTest

* Remove resetting timeout; we'll PING instead
---
 .../java/redis/clients/jedis/Connection.java  | 48 ++++-------
 .../clients/jedis/ConnectionFactory.java      |  5 +-
 .../clients/jedis/JedisClusterInfoCache.java  |  2 +-
 .../java/redis/clients/jedis/Protocol.java    | 18 ++--
 .../clients/jedis/csc/CacheConnection.java    | 66 +++++++++++++++
 .../redis/clients/jedis/csc/CacheEntry.java   | 30 +++++++
 .../redis/clients/jedis/csc/CacheKey.java     | 28 +++++++
 .../jedis/csc/CaffeineClientSideCache.java    | 19 ++---
 .../clients/jedis/csc/ClientSideCache.java    | 82 +++++++++++--------
 .../jedis/csc/GuavaClientSideCache.java       | 19 ++---
 .../SentineledConnectionProvider.java         |  2 +-
 .../jedis/TransactionCommandsTest.java        | 10 +--
 .../AllowAndDenyListClientSideCacheTest.java  | 13 ++-
 .../csc/ClientSideCacheFunctionalityTest.java | 15 ++--
 .../csc/JedisClusterClientSideCacheTest.java  |  5 +-
 .../csc/JedisPooledClientSideCacheTest.java   |  5 +-
 .../JedisSentineledClientSideCacheTest.java   |  5 +-
 .../clients/jedis/csc/MapClientSideCache.java | 20 ++---
 18 files changed, 247 insertions(+), 145 deletions(-)
 create mode 100644 src/main/java/redis/clients/jedis/csc/CacheConnection.java
 create mode 100644 src/main/java/redis/clients/jedis/csc/CacheEntry.java
 create mode 100644 src/main/java/redis/clients/jedis/csc/CacheKey.java

diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java
index b3a4584c75..c7c5f439d4 100644
--- a/src/main/java/redis/clients/jedis/Connection.java
+++ b/src/main/java/redis/clients/jedis/Connection.java
@@ -16,10 +16,10 @@
 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;
-import redis.clients.jedis.csc.ClientSideCache;
 import redis.clients.jedis.exceptions.JedisConnectionException;
 import redis.clients.jedis.exceptions.JedisDataException;
 import redis.clients.jedis.exceptions.JedisException;
@@ -31,12 +31,11 @@
 public class Connection implements Closeable {
 
   private ConnectionPool memberOf;
-  private RedisProtocol protocol;
+  protected RedisProtocol protocol;
   private final JedisSocketFactory socketFactory;
   private Socket socket;
   private RedisOutputStream outputStream;
   private RedisInputStream inputStream;
-  private ClientSideCache clientSideCache;
   private int soTimeout = 0;
   private int infiniteSoTimeout = 0;
   private boolean broken = false;
@@ -68,16 +67,6 @@ public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clie
     initializeFromClientConfig(clientConfig);
   }
 
-  @Experimental
-  public Connection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig,
-      ClientSideCache clientSideCache) {
-    this.socketFactory = socketFactory;
-    this.soTimeout = clientConfig.getSocketTimeoutMillis();
-    this.infiniteSoTimeout = clientConfig.getBlockingSocketTimeoutMillis();
-    initializeFromClientConfig(clientConfig);
-    initializeClientSideCache(clientSideCache);
-  }
-
   @Override
   public String toString() {
     return "Connection{" + socketFactory + "}";
@@ -352,16 +341,26 @@ protected void flush() {
     }
   }
 
+  @Experimental
+  @Internal
+  protected Object protocolRead(RedisInputStream is) {
+    return Protocol.read(is);
+  }
+
+  @Experimental
+  @Internal
+  protected void protocolReadPushes(RedisInputStream is) {
+  }
+
+  // TODO: final
   protected Object readProtocolWithCheckingBroken() {
     if (broken) {
       throw new JedisConnectionException("Attempting to read from a broken connection.");
     }
 
     try {
-      return Protocol.read(inputStream, clientSideCache);
-//      Object read = Protocol.read(inputStream);
-//      System.out.println(redis.clients.jedis.util.SafeEncoder.encodeObject(read));
-//      return read;
+      protocolReadPushes(inputStream);
+      return protocolRead(inputStream);
     } catch (JedisConnectionException exc) {
       broken = true;
       throw exc;
@@ -525,19 +524,4 @@ public boolean ping() {
     }
     return true;
   }
-
-  private void initializeClientSideCache(ClientSideCache csCache) {
-    this.clientSideCache = csCache;
-    if (clientSideCache != null) {
-      if (protocol != RedisProtocol.RESP3) {
-        throw new JedisException("Client side caching is only supported with RESP3.");
-      }
-
-      sendCommand(Protocol.Command.CLIENT, "TRACKING", "ON");
-      String reply = getStatusCodeReply();
-      if (!"OK".equals(reply)) {
-        throw new JedisException("Could not enable client tracking. Reply: " + reply);
-      }
-    }
-  }
 }
diff --git a/src/main/java/redis/clients/jedis/ConnectionFactory.java b/src/main/java/redis/clients/jedis/ConnectionFactory.java
index 2ac7e13f19..722c3e32c8 100644
--- a/src/main/java/redis/clients/jedis/ConnectionFactory.java
+++ b/src/main/java/redis/clients/jedis/ConnectionFactory.java
@@ -5,7 +5,9 @@
 import org.apache.commons.pool2.impl.DefaultPooledObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
 import redis.clients.jedis.annots.Experimental;
+import redis.clients.jedis.csc.CacheConnection;
 import redis.clients.jedis.csc.ClientSideCache;
 import redis.clients.jedis.exceptions.JedisException;
 
@@ -62,7 +64,8 @@ public void destroyObject(PooledObject pooledConnection) throws Exce
   @Override
   public PooledObject makeObject() throws Exception {
     try {
-      Connection jedis = new Connection(jedisSocketFactory, clientConfig, clientSideCache);
+      Connection jedis = clientSideCache == null ? new Connection(jedisSocketFactory, clientConfig)
+          : new CacheConnection(jedisSocketFactory, clientConfig, clientSideCache);
       return new DefaultPooledObject<>(jedis);
     } catch (JedisException je) {
       logger.debug("Error while makeObject", je);
diff --git a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java
index f3287897b7..bc150dbcb0 100644
--- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java
+++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java
@@ -237,7 +237,7 @@ private void discoverClusterSlots(Connection jedis) {
       Arrays.fill(slots, null);
       Arrays.fill(slotNodes, null);
       if (clientSideCache != null) {
-        clientSideCache.clear();
+        clientSideCache.flush();
       }
       Set hostAndPortKeys = new HashSet<>();
 
diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java
index ba15411197..c025e37a4a 100644
--- a/src/main/java/redis/clients/jedis/Protocol.java
+++ b/src/main/java/redis/clients/jedis/Protocol.java
@@ -7,8 +7,8 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-import redis.clients.jedis.annots.Experimental;
 
+import redis.clients.jedis.annots.Experimental;
 import redis.clients.jedis.exceptions.*;
 import redis.clients.jedis.args.Rawable;
 import redis.clients.jedis.commands.ProtocolCommand;
@@ -207,23 +207,15 @@ private static List processMapKeyValueReply(final RedisInputStream is)
     return ret;
   }
 
-  @Deprecated
   public static Object read(final RedisInputStream is) {
     return process(is);
   }
 
   @Experimental
-  public static Object read(final RedisInputStream is, final ClientSideCache cache) {
-    readPushes(is, cache);
-    return process(is);
-  }
-
-  private static void readPushes(final RedisInputStream is, final ClientSideCache cache) {
-    if (cache != null) {
-      while (is.peek(GREATER_THAN_BYTE)) {
-        is.readByte();
-        processPush(is, cache);
-      }
+  public static void readPushes(final RedisInputStream is, final ClientSideCache cache) {
+    while (is.peek(GREATER_THAN_BYTE)) {
+      is.readByte();
+      processPush(is, cache);
     }
   }
 
diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java
new file mode 100644
index 0000000000..0573cc1093
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java
@@ -0,0 +1,66 @@
+package redis.clients.jedis.csc;
+
+import java.util.Objects;
+import java.util.concurrent.locks.ReentrantLock;
+import redis.clients.jedis.Connection;
+import redis.clients.jedis.JedisClientConfig;
+import redis.clients.jedis.JedisSocketFactory;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.RedisProtocol;
+import redis.clients.jedis.exceptions.JedisException;
+import redis.clients.jedis.util.RedisInputStream;
+
+public class CacheConnection extends Connection {
+
+  private final ClientSideCache clientSideCache;
+  private final ReentrantLock lock;
+
+  public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig,
+      ClientSideCache clientSideCache) {
+
+    super(socketFactory, clientConfig);
+
+    if (protocol != RedisProtocol.RESP3) {
+      throw new JedisException("Client side caching is only supported with RESP3.");
+    }
+    this.clientSideCache = Objects.requireNonNull(clientSideCache);
+    initializeClientSideCache();
+
+    lock = new ReentrantLock();
+  }
+
+  @Override
+  protected Object protocolRead(RedisInputStream inputStream) {
+    if (lock != null) {
+      lock.lock();
+      try {
+        return Protocol.read(inputStream);
+      } finally {
+        lock.unlock();
+      }
+    } else {
+      return Protocol.read(inputStream);
+    }
+  }
+
+  @Override
+  protected void protocolReadPushes(RedisInputStream inputStream) {
+    if (lock != null && lock.tryLock()) {
+      try {
+        //super.setSoTimeout(1);
+        Protocol.readPushes(inputStream, clientSideCache);
+      } finally {
+        //super.rollbackTimeout();
+        lock.unlock();
+      }
+    }
+  }
+
+  private void initializeClientSideCache() {
+    sendCommand(Protocol.Command.CLIENT, "TRACKING", "ON");
+    String reply = getStatusCodeReply();
+    if (!"OK".equals(reply)) {
+      throw new JedisException("Could not enable client tracking. Reply: " + reply);
+    }
+  }
+}
diff --git a/src/main/java/redis/clients/jedis/csc/CacheEntry.java b/src/main/java/redis/clients/jedis/csc/CacheEntry.java
new file mode 100644
index 0000000000..f2a3243cf4
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/csc/CacheEntry.java
@@ -0,0 +1,30 @@
+package redis.clients.jedis.csc;
+
+import redis.clients.jedis.Connection;
+import redis.clients.jedis.annots.Internal;
+
+@Internal
+public class CacheEntry {
+
+  private final CacheKey cacheKey;
+  private final T value;
+  private final Connection connection;
+
+  public CacheEntry(CacheKey cacheKey, T value, Connection connection) {
+    this.cacheKey = cacheKey;
+    this.value = value;
+    this.connection = connection;
+  }
+
+  public CacheKey getCacheKey() {
+    return cacheKey;
+  }
+
+  public T getValue() {
+    return value;
+  }
+
+  public Connection getConnection() {
+    return connection;
+  }
+}
diff --git a/src/main/java/redis/clients/jedis/csc/CacheKey.java b/src/main/java/redis/clients/jedis/csc/CacheKey.java
new file mode 100644
index 0000000000..b3e8244b20
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/csc/CacheKey.java
@@ -0,0 +1,28 @@
+package redis.clients.jedis.csc;
+
+import java.util.Objects;
+import redis.clients.jedis.CommandObject;
+import redis.clients.jedis.annots.Internal;
+
+@Internal
+public class CacheKey {
+
+  private final CommandObject command;
+
+  public CacheKey(CommandObject command) {
+    this.command = Objects.requireNonNull(command);
+  }
+
+  @Override
+  public int hashCode() {
+    return command.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null || getClass() != obj.getClass()) return false;
+    final CacheKey other = (CacheKey) obj;
+    return Objects.equals(this.command, other.command);
+  }
+}
diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
index 0750c9fff7..fe320418e9 100644
--- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
@@ -3,34 +3,33 @@
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import java.util.concurrent.TimeUnit;
-import redis.clients.jedis.CommandObject;
 
 public class CaffeineClientSideCache extends ClientSideCache {
 
-  private final Cache cache;
+  private final Cache cache;
 
-  public CaffeineClientSideCache(Cache caffeineCache) {
+  public CaffeineClientSideCache(Cache caffeineCache) {
     this.cache = caffeineCache;
   }
 
   @Override
-  protected final void invalidateFullCache() {
+  protected final void clear() {
     cache.invalidateAll();
   }
 
   @Override
-  protected void invalidateCache(Iterable> commands) {
-    cache.invalidateAll(commands);
+  protected void remove(Iterable> keys) {
+    cache.invalidateAll(keys);
   }
 
   @Override
-  protected  void putValue(CommandObject command, T value) {
-    cache.put(command, value);
+  protected void put(CacheKey key, CacheEntry entry) {
+    cache.put(key, entry);
   }
 
   @Override
-  protected  T getValue(CommandObject command) {
-    return (T) cache.getIfPresent(command);
+  protected CacheEntry get(CacheKey key) {
+    return cache.getIfPresent(key);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
index 31b1239821..94a235873d 100644
--- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
@@ -23,7 +23,7 @@ public abstract class ClientSideCache {
   protected static final int DEFAULT_MAXIMUM_SIZE = 10_000;
   protected static final int DEFAULT_EXPIRE_SECONDS = 100;
 
-  private final Map>> keyToCommandHashes = new ConcurrentHashMap<>();
+  private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>();
   private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile
 
   protected ClientSideCache() {
@@ -33,49 +33,49 @@ public void setCacheable(ClientSideCacheable cacheable) {
     this.cacheable = Objects.requireNonNull(cacheable, "'cacheable' must not be null");
   }
 
-  protected abstract void invalidateFullCache();
+  protected abstract void clear();
 
-  protected abstract void invalidateCache(Iterable> commands);
+  protected abstract void remove(Iterable> keys);
 
-  protected abstract  void putValue(CommandObject command, T value);
+  protected abstract void put(CacheKey key, CacheEntry entry);
 
-  protected abstract  T getValue(CommandObject command);
+  protected abstract CacheEntry get(CacheKey key);
 
-  public final void clear() {
-    invalidateAllKeysAndCommandHashes();
+  public final void flush() {
+    invalidateAllRedisKeysAndCacheEntries();
   }
 
-  public final void removeKey(Object key) {
-    invalidateKeyAndRespectiveCommandHashes(key);
+  public final void invalidateKey(Object key) {
+    invalidateRedisKeyAndRespectiveCacheEntries(key);
   }
 
   public final void invalidate(List list) {
     if (list == null) {
-      invalidateAllKeysAndCommandHashes();
+      invalidateAllRedisKeysAndCacheEntries();
       return;
     }
 
-    list.forEach(this::invalidateKeyAndRespectiveCommandHashes);
+    list.forEach(this::invalidateRedisKeyAndRespectiveCacheEntries);
   }
 
-  private void invalidateAllKeysAndCommandHashes() {
-    invalidateFullCache();
-    keyToCommandHashes.clear();
+  private void invalidateAllRedisKeysAndCacheEntries() {
+    clear();
+    redisKeysToCacheKeys.clear();
   }
 
-  private void invalidateKeyAndRespectiveCommandHashes(Object key) {
+  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);
-    final ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key);
+    final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key);
 
-    Set> commands = keyToCommandHashes.get(mapKey);
+    Set> commands = redisKeysToCacheKeys.get(mapKey);
     if (commands != null) {
-      invalidateCache(commands);
-      keyToCommandHashes.remove(mapKey);
+      remove(commands);
+      redisKeysToCacheKeys.remove(mapKey);
     }
   }
 
@@ -85,22 +85,29 @@ public final  T get(Function, T> loader, CommandObject co
       return loader.apply(command);
     }
 
-    T value = getValue(command);
-    if (value != null) {
-      return value;
+    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();
     }
 
-    value = loader.apply(command);
+    // CACHE MISS!!
+    T value = loader.apply(command);
     if (value != null) {
-      putValue(command, value);
+      cacheEntry = new CacheEntry(cacheKey, value, /*connection*/ null); // TODO: connection
+      put(cacheKey, cacheEntry);
       for (Object key : keys) {
-        ByteBuffer mapKey = makeKeyForKeyToCommandHashes(key);
-        if (keyToCommandHashes.containsKey(mapKey)) {
-          keyToCommandHashes.get(mapKey).add(command);
+        ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key);
+        if (redisKeysToCacheKeys.containsKey(mapKey)) {
+          redisKeysToCacheKeys.get(mapKey).add(cacheKey);
         } else {
-          Set> set = ConcurrentHashMap.newKeySet();
-          set.add(command);
-          keyToCommandHashes.put(mapKey, set);
+          Set> set = ConcurrentHashMap.newKeySet();
+          set.add(cacheKey);
+          redisKeysToCacheKeys.put(mapKey, set);
         }
       }
     }
@@ -108,13 +115,18 @@ public final  T get(Function, T> loader, CommandObject co
     return value;
   }
 
-  private ByteBuffer makeKeyForKeyToCommandHashes(Object key) {
-    if (key instanceof byte[]) return makeKeyForKeyToCommandHashes((byte[]) key);
-    else if (key instanceof String) return makeKeyForKeyToCommandHashes(SafeEncoder.encode((String) key));
-    else throw new IllegalArgumentException("" + key.getClass().getSimpleName() + " is not supported. Value: " + String.valueOf(key));
+  private ByteBuffer makeKeyForRedisKeysToCacheKeys(Object key) {
+    if (key instanceof byte[]) {
+      return makeKeyForRedisKeysToCacheKeys((byte[]) key);
+    } else if (key instanceof String) {
+      return makeKeyForRedisKeysToCacheKeys(SafeEncoder.encode((String) key));
+    } else {
+      throw new IllegalArgumentException(key.getClass().getSimpleName() + " is not supported."
+          + " Value: \"" + String.valueOf(key) + "\".");
+    }
   }
 
-  private static ByteBuffer makeKeyForKeyToCommandHashes(byte[] b) {
+  private static ByteBuffer makeKeyForRedisKeysToCacheKeys(byte[] b) {
     return ByteBuffer.wrap(b);
   }
 }
diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
index ccde8af60c..8adfc69b26 100644
--- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java
@@ -3,35 +3,34 @@
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import java.util.concurrent.TimeUnit;
-import redis.clients.jedis.CommandObject;
 
 public class GuavaClientSideCache extends ClientSideCache {
 
-  private final Cache cache;
+  private final Cache cache;
 
-  public GuavaClientSideCache(Cache guavaCache) {
+  public GuavaClientSideCache(Cache guavaCache) {
     super();
     this.cache = guavaCache;
   }
 
   @Override
-  protected final void invalidateFullCache() {
+  protected final void clear() {
     cache.invalidateAll();
   }
 
   @Override
-  protected void invalidateCache(Iterable> commands) {
-    cache.invalidateAll(commands);
+  protected void remove(Iterable> keys) {
+    cache.invalidateAll(keys);
   }
 
   @Override
-  protected  void putValue(CommandObject command, T value) {
-    cache.put(command, value);
+  protected void put(CacheKey key, CacheEntry entry) {
+    cache.put(key, entry);
   }
 
   @Override
-  protected  T getValue(CommandObject command) {
-    return (T) cache.getIfPresent(command);
+  protected CacheEntry get(CacheKey key) {
+    return cache.getIfPresent(key);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java
index 22681fa40f..8a16d3e2e4 100644
--- a/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java
+++ b/src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java
@@ -136,7 +136,7 @@ private void initMaster(HostAndPort master) {
         pool = newPool;
         LOG.info("Created connection pool to master at {}.", master);
         if (clientSideCache != null) {
-          clientSideCache.clear();
+          clientSideCache.flush();
         }
 
         if (existingPool != null) {
diff --git a/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java
index aeb9ab4e4f..f6bedc1741 100644
--- a/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java
+++ b/src/test/java/redis/clients/jedis/commands/jedis/TransactionCommandsTest.java
@@ -1,6 +1,7 @@
 package redis.clients.jedis.commands.jedis;
 
 import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.any;
 
 import static redis.clients.jedis.Protocol.Command.INCR;
 import static redis.clients.jedis.Protocol.Command.GET;
@@ -16,7 +17,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.mockito.ArgumentMatchers;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
@@ -167,9 +167,7 @@ public void discardFail() {
     trans.set("b", "b");
 
     try (MockedStatic protocol = Mockito.mockStatic(Protocol.class)) {
-      //protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class);
-      protocol.when(() -> Protocol.read(ArgumentMatchers.any(), ArgumentMatchers.isNull()))
-          .thenThrow(JedisConnectionException.class);
+      protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class);
 
       trans.discard();
       fail("Should get mocked JedisConnectionException.");
@@ -189,9 +187,7 @@ public void execFail() {
     trans.set("b", "b");
 
     try (MockedStatic protocol = Mockito.mockStatic(Protocol.class)) {
-      //protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class);
-      protocol.when(() -> Protocol.read(ArgumentMatchers.any(), ArgumentMatchers.isNull()))
-          .thenThrow(JedisConnectionException.class);
+      protocol.when(() -> Protocol.read(any())).thenThrow(JedisConnectionException.class);
 
       trans.exec();
       fail("Should get mocked JedisConnectionException.");
diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
index 21ad307668..3d63a79fa2 100644
--- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java
@@ -9,14 +9,13 @@
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
-import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.JedisPooled;
 import redis.clients.jedis.Protocol;
 import redis.clients.jedis.csc.util.AllowAndDenyListWithStringKeys;
 
 public class AllowAndDenyListClientSideCacheTest extends ClientSideCacheTestBase {
 
-  private static MapClientSideCache createMapClientSideCache(Map map, ClientSideCacheable cacheable) {
+  private static MapClientSideCache createMapClientSideCache(Map map, ClientSideCacheable cacheable) {
     MapClientSideCache mapCache = new MapClientSideCache(map);
     mapCache.setCacheable(cacheable);
     return mapCache;
@@ -24,7 +23,7 @@ private static MapClientSideCache createMapClientSideCache(Map map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)),
         singleConnectionPoolConfig.get())) {
@@ -37,7 +36,7 @@ public void none() {
 
   @Test
   public void whiteListCommand() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)),
         singleConnectionPoolConfig.get())) {
@@ -50,7 +49,7 @@ public void whiteListCommand() {
 
   @Test
   public void blackListCommand() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)),
         singleConnectionPoolConfig.get())) {
@@ -63,7 +62,7 @@ public void blackListCommand() {
 
   @Test
   public void whiteListKey() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)),
         singleConnectionPoolConfig.get())) {
@@ -76,7 +75,7 @@ public void whiteListKey() {
 
   @Test
   public void blackListKey() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(),
         createMapClientSideCache(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))),
         singleConnectionPoolConfig.get())) {
diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
index 3c53d61e21..332648f424 100644
--- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
+++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java
@@ -13,7 +13,6 @@
 import java.util.LinkedHashMap;
 import org.junit.Test;
 
-import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.JedisPooled;
 import redis.clients.jedis.util.JedisURIHelper;
 
@@ -26,7 +25,7 @@ public void flushEntireCache() {
       control.set("k" + i, "v" + i);
     }
 
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     ClientSideCache clientSideCache = new MapClientSideCache(map);
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) {
       for (int i = 0; i < count; i++) {
@@ -35,7 +34,7 @@ public void flushEntireCache() {
     }
 
     assertEquals(count, map.size());
-    clientSideCache.clear();
+    clientSideCache.flush();
     assertEquals(0, map.size());
   }
 
@@ -47,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<>();
+    LinkedHashMap map = new LinkedHashMap<>();
     ClientSideCache clientSideCache = new MapClientSideCache(map);
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) {
       for (int i = 0; i < count; i++) {
@@ -55,13 +54,13 @@ public void removeSpecificKey() {
       }
     }
 
-    ArrayList commandHashes = new ArrayList<>(map.keySet());
+    ArrayList commandHashes = new ArrayList<>(map.keySet());
     assertEquals(count, map.size());
     for (int i = 0; i < count; i++) {
       String key = "k" + i;
-      CommandObject command = commandHashes.get(i);
+      CacheKey command = commandHashes.get(i);
       assertTrue(map.containsKey(command));
-      clientSideCache.removeKey(key);
+      clientSideCache.invalidateKey(key);
       assertFalse(map.containsKey(command));
     }
   }
@@ -71,7 +70,7 @@ public void multiKeyOperation() {
     control.set("k1", "v1");
     control.set("k2", "v2");
 
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map))) {
       jedis.mget("k1", "k2");
       assertEquals(1, map.size());
diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java
index 158a83055e..4c3f25b5e8 100644
--- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java
@@ -15,7 +15,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.Connection;
 import redis.clients.jedis.ConnectionPoolConfig;
 import redis.clients.jedis.DefaultJedisClientConfig;
@@ -63,7 +62,7 @@ public void simple() {
 
   @Test
   public void simpleWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
@@ -93,7 +92,7 @@ public void flushAll() {
 
   @Test
   public void flushAllWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java
index e26c0b4d61..20933fd237 100644
--- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java
@@ -13,7 +13,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.Connection;
 import redis.clients.jedis.ConnectionPoolConfig;
 import redis.clients.jedis.DefaultJedisClientConfig;
@@ -65,7 +64,7 @@ public void simple() {
 
   @Test
   public void simpleWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
@@ -95,7 +94,7 @@ public void flushAll() {
 
   @Test
   public void flushAllWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map),
         singleConnectionPoolConfig.get())) {
       control.set("foo", "bar");
diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java
index f00cc27ea9..1660396d23 100644
--- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java
+++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java
@@ -14,7 +14,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import redis.clients.jedis.CommandObject;
 import redis.clients.jedis.DefaultJedisClientConfig;
 import redis.clients.jedis.HostAndPort;
 import redis.clients.jedis.HostAndPorts;
@@ -60,7 +59,7 @@ public void simple() {
 
   @Test
   public void simpleWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map),
         sentinels, sentinelClientConfig)) {
       control.set("foo", "bar");
@@ -91,7 +90,7 @@ public void flushAll() {
 
   @Test
   public void flushAllWithSimpleMap() {
-    HashMap map = new HashMap<>();
+    HashMap map = new HashMap<>();
     try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new MapClientSideCache(map),
         sentinels, sentinelClientConfig)) {
       control.set("foo", "bar");
diff --git a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
index e8bc975231..a0b33c52d7 100644
--- a/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
+++ b/src/test/java/redis/clients/jedis/csc/MapClientSideCache.java
@@ -3,38 +3,36 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import redis.clients.jedis.CommandObject;
-
 public class MapClientSideCache extends ClientSideCache {
 
-  private final Map cache;
+  private final Map cache;
 
   public MapClientSideCache() {
     this(new HashMap<>());
   }
 
-  public MapClientSideCache(Map map) {
+  public MapClientSideCache(Map map) {
     super();
     this.cache = map;
   }
 
   @Override
-  protected final void invalidateFullCache() {
+  protected final void clear() {
     cache.clear();
   }
 
   @Override
-  protected void invalidateCache(Iterable> commands) {
-    commands.forEach(hash -> cache.remove(hash));
+  protected void remove(Iterable> keys) {
+    keys.forEach(hash -> cache.remove(hash));
   }
 
   @Override
-  protected  void putValue(CommandObject command, T value) {
-    cache.put(command, value);
+  protected void put(CacheKey key, CacheEntry entry) {
+    cache.put(key, entry);
   }
 
   @Override
-  protected  T getValue(CommandObject command) {
-    return (T) cache.get(command);
+  protected CacheEntry get(CacheKey key) {
+    return cache.get(key);
   }
 }

From 229399f3da5d9e5b31c6d45a00652694a6157e7e Mon Sep 17 00:00:00 2001
From: atakavci <58048133+atakavci@users.noreply.github.com>
Date: Tue, 13 Aug 2024 13:11:50 +0300
Subject: [PATCH 40/48] Refactor Client-Side Caching implementation (#3900)

* adding a DataProvider to access connection from cache

* resolve keys from commandarguments

* clean up in unifiiedjedis and add csc test with ssl

* - 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

* - add serialization for cache instances
- add unit test with UnifiedJedis
- add benchmark for CSC execution
- clean unused imports

* - 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

* - added SSLSocketWrapper and plug it to use 'available'
- handle exceptions properly
- fix some issues with unit tests

* implementing thread safety

* - 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

* - renmae abstract cache class
- add test case for returning new instance of cache object

* - change order of execution in sequential acces test

* -  flush the cache on any disconnect
- replace LRU policy references with EvictionPolicy interface
-  add some constructor overloads to enable custom eviction policies on cache

* fix testcache

* fix javadoc issue

* - fix multithreaded eviction policy issue
- update guava and caffeine implementations according to abstract cache
---
 .../redis/clients/jedis/CommandArguments.java |  13 +-
 .../java/redis/clients/jedis/Connection.java  |  29 +-
 .../clients/jedis/ConnectionFactory.java      |   6 +-
 .../redis/clients/jedis/ConnectionPool.java   |   6 +-
 .../jedis/DefaultJedisSocketFactory.java      |   2 +
 .../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    |  41 +-
 .../redis/clients/jedis/SSLSocketWrapper.java | 408 ++++++++++++++
 .../redis/clients/jedis/UnifiedJedis.java     | 519 +++++++++---------
 .../clients/jedis/csc/AbstractCache.java      | 230 ++++++++
 .../java/redis/clients/jedis/csc/Cache.java   | 108 ++++
 .../clients/jedis/csc/CacheConnection.java    |  89 ++-
 .../redis/clients/jedis/csc/CacheEntry.java   |  43 +-
 .../redis/clients/jedis/csc/CacheKey.java     |   8 +
 .../redis/clients/jedis/csc/CacheStats.java   |  89 +++
 .../jedis/csc/CaffeineClientSideCache.java    |  78 +--
 .../clients/jedis/csc/ClientSideCache.java    | 132 -----
 .../redis/clients/jedis/csc/DefaultCache.java |  71 +++
 .../jedis/csc/DefaultClientSideCacheable.java |  83 ++-
 .../clients/jedis/csc/EvictionPolicy.java     |  77 +++
 .../jedis/csc/GuavaClientSideCache.java       |  84 +--
 .../redis/clients/jedis/csc/LRUEviction.java  | 106 ++++
 .../util/AllowAndDenyListWithStringKeys.java  |  21 +-
 .../jedis/exceptions/JedisCacheException.java |  18 +
 .../providers/ClusterConnectionProvider.java  |   8 +-
 .../providers/PooledConnectionProvider.java   |   6 +-
 .../SentineledConnectionProvider.java         |  16 +-
 .../clients/jedis/util/RedisInputStream.java  |  17 +-
 .../jedis/benchmark/CSCPooleadBenchmark.java  |  79 +++
 .../AllowAndDenyListClientSideCacheTest.java  |  15 +-
 .../csc/CaffeineClientSideCacheTest.java      |  77 ++-
 .../csc/ClientSideCacheFunctionalityTest.java | 131 ++++-
 .../jedis/csc/ClientSideCacheTestBase.java    |  19 +-
 .../jedis/csc/GuavaClientSideCacheTest.java   |  85 +--
 .../csc/JedisClusterClientSideCacheTest.java  |  20 +-
 .../csc/JedisPooledClientSideCacheTest.java   | 329 ++++++++++-
 .../JedisSentineledClientSideCacheTest.java   |  20 +-
 .../clients/jedis/csc/MapClientSideCache.java |  38 --
 .../redis/clients/jedis/csc/TestCache.java    |  30 +
 42 files changed, 2393 insertions(+), 794 deletions(-)
 create mode 100644 src/main/java/redis/clients/jedis/SSLSocketWrapper.java
 create mode 100644 src/main/java/redis/clients/jedis/csc/AbstractCache.java
 create mode 100644 src/main/java/redis/clients/jedis/csc/Cache.java
 create mode 100644 src/main/java/redis/clients/jedis/csc/CacheStats.java
 delete mode 100644 src/main/java/redis/clients/jedis/csc/ClientSideCache.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
 create mode 100644 src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java
 create mode 100644 src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.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/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java
index c630ae76de..b0ebd7fd13 100644
--- a/src/main/java/redis/clients/jedis/CommandArguments.java
+++ b/src/main/java/redis/clients/jedis/CommandArguments.java
@@ -14,16 +14,14 @@
 public class CommandArguments implements Iterable {
 
   private final ArrayList args;
+  private final ArrayList keys;
 
   private boolean blocking;
 
-  private CommandArguments() {
-    throw new InstantiationError();
-  }
-
   public CommandArguments(ProtocolCommand command) {
     args = new ArrayList<>();
     args.add(command);
+    keys = new ArrayList<>();
   }
 
   public ProtocolCommand getCommand() {
@@ -115,6 +113,7 @@ public CommandArguments key(Object key) {
     } else {
       throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument.");
     }
+    keys.add(key);
     return this;
   }
 
@@ -134,7 +133,6 @@ public final CommandArguments addParams(IParams params) {
   }
 
   protected CommandArguments processKey(byte[] key) {
-    // do nothing
     return this;
   }
 
@@ -146,7 +144,6 @@ protected final CommandArguments processKeys(byte[]... keys) {
   }
 
   protected CommandArguments processKey(String key) {
-    // do nothing
     return this;
   }
 
@@ -166,6 +163,10 @@ public Iterator iterator() {
     return args.iterator();
   }
 
+  public Object[] getKeys() {
+    return keys.toArray();
+  }
+
   public boolean isBlocking() {
     return blocking;
   }
diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java
index c7c5f439d4..e0cb9e7ad6 100644
--- a/src/main/java/redis/clients/jedis/Connection.java
+++ b/src/main/java/redis/clients/jedis/Connection.java
@@ -16,7 +16,6 @@
 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;
@@ -342,24 +341,20 @@ protected void flush() {
   }
 
   @Experimental
-  @Internal
   protected Object protocolRead(RedisInputStream is) {
     return Protocol.read(is);
   }
 
   @Experimental
-  @Internal
   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);
       return protocolRead(inputStream);
     } catch (JedisConnectionException exc) {
       broken = true;
@@ -367,6 +362,24 @@ protected Object readProtocolWithCheckingBroken() {
     }
   }
 
+  protected void readPushesWithCheckingBroken() {
+    if (broken) {
+      throw new JedisConnectionException("Attempting to read from a broken connection.");
+    }
+
+    try {
+      if (inputStream.available() > 0) {
+        protocolReadPushes(inputStream);
+      }
+    } catch (IOException e) {
+      broken = true;
+      throw new JedisConnectionException("Failed to check buffer on connection.", e);
+    } catch (JedisConnectionException exc) {
+      broken = true;
+      throw exc;
+    }
+  }
+
   public List getMany(final int count) {
     flush();
     final List responses = new ArrayList<>(count);
@@ -382,6 +395,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
@@ -397,7 +411,7 @@ private static boolean validateClientInfo(String info) {
     return true;
   }
 
-  private void initializeFromClientConfig(final JedisClientConfig config) {
+  protected void initializeFromClientConfig(final JedisClientConfig config) {
     try {
       connect();
 
@@ -425,7 +439,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/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/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/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 c025e37a4a..174fdd142b 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;
@@ -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)));
@@ -212,18 +214,37 @@ public static Object read(final RedisInputStream is) {
   }
 
   @Experimental
-  public static void readPushes(final RedisInputStream is, final ClientSideCache cache) {
-    while (is.peek(GREATER_THAN_BYTE)) {
-      is.readByte();
-      processPush(is, cache);
+  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) {
+      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);
+      }
     }
   }
 
-  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/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/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java
index 3a2dd9ab3e..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;
@@ -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;
@@ -95,8 +95,9 @@ public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) {
   }
 
   @Experimental
-  public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, ClientSideCache clientSideCache) {
-    this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(), clientSideCache);
+  public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache) {
+    this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(),
+        clientSideCache);
   }
 
   public UnifiedJedis(ConnectionProvider provider) {
@@ -108,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);
   }
 
@@ -143,14 +144,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
@@ -181,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);
   }
@@ -199,7 +201,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) {
@@ -240,21 +243,23 @@ 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) {
+      }
     }
   }
 
   @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.");
@@ -264,12 +269,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 +301,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,14 +314,6 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co
     this.commandObjects.setBroadcastAndRoundRobinConfig(this.broadcastAndRoundRobinConfig);
   }
 
-  private  T checkAndClientSideCacheCommand(CommandObject command, Object... keys) {
-    if (clientSideCache != null) {
-      return clientSideCache.get((cmd) -> executeCommand(cmd), command, keys);
-    }
-
-    return executeCommand(command);
-  }
-
   public String ping() {
     return checkAndBroadcastCommand(commandObjects.ping());
   }
@@ -335,12 +333,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
@@ -350,17 +348,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
@@ -370,7 +368,7 @@ public long persist(byte[] key) {
 
   @Override
   public String type(byte[] key) {
-    return checkAndClientSideCacheCommand(commandObjects.type(key), key);
+    return executeCommand(commandObjects.type(key));
   }
 
   @Override
@@ -770,7 +768,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
@@ -805,7 +803,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
@@ -835,7 +833,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
@@ -845,7 +843,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
@@ -855,7 +853,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
@@ -865,7 +863,7 @@ 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));
   }
 
   /**
@@ -968,7 +966,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
@@ -983,7 +981,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
@@ -1003,12 +1001,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
@@ -1018,62 +1016,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
@@ -1083,7 +1081,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
@@ -1093,7 +1091,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
@@ -1108,12 +1106,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
 
@@ -1130,12 +1128,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
@@ -1145,7 +1143,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
@@ -1160,12 +1158,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
@@ -1175,7 +1173,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
@@ -1220,32 +1218,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
@@ -1452,7 +1450,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
@@ -1467,7 +1465,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
@@ -1482,7 +1480,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
@@ -1497,7 +1495,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
@@ -1512,7 +1510,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
@@ -1522,7 +1520,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
@@ -1537,7 +1535,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
@@ -1547,37 +1545,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
@@ -1607,7 +1605,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
@@ -1637,7 +1635,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
@@ -1779,7 +1777,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
@@ -1799,17 +1797,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
@@ -1819,7 +1817,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
@@ -1839,17 +1837,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
@@ -1884,7 +1882,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
@@ -1894,7 +1892,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
@@ -1914,7 +1912,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
@@ -1929,7 +1927,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
@@ -1939,7 +1937,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
@@ -1959,7 +1957,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
@@ -2041,22 +2039,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
@@ -2076,22 +2074,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
@@ -2111,17 +2109,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
@@ -2141,17 +2139,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
@@ -2176,12 +2174,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
@@ -2206,42 +2204,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
@@ -2251,112 +2249,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
@@ -2366,82 +2364,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
@@ -2476,27 +2474,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
@@ -2506,27 +2504,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
@@ -2765,22 +2763,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
@@ -2800,22 +2798,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
@@ -2825,7 +2823,7 @@ 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);
+    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));
   }
 
   @Override
@@ -2835,7 +2833,7 @@ public List georadius(String key, double longitude, double la
 
   @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);
+    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));
   }
 
   @Override
@@ -2845,7 +2843,7 @@ 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
@@ -2855,7 +2853,7 @@ public List georadiusByMember(String key, String member, doub
 
   @Override
   public List georadiusByMemberReadonly(String key, String member, double radius, GeoUnit unit, GeoRadiusParam param) {
-    return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param), key);
+    return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));
   }
 
   @Override
@@ -2870,27 +2868,27 @@ public long georadiusByMemberStore(String key, String member, double radius, Geo
 
   @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
@@ -2930,7 +2928,7 @@ 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);
+    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit));
   }
 
   @Override
@@ -2940,7 +2938,7 @@ public List georadius(byte[] key, double longitude, double la
 
   @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);
+    return executeCommand(commandObjects.georadiusReadonly(key, longitude, latitude, radius, unit, param));
   }
 
   @Override
@@ -2950,7 +2948,7 @@ 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
@@ -2960,7 +2958,7 @@ public List georadiusByMember(byte[] key, byte[] member, doub
 
   @Override
   public List georadiusByMemberReadonly(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {
-    return checkAndClientSideCacheCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param), key);
+    return executeCommand(commandObjects.georadiusByMemberReadonly(key, member, radius, unit, param));
   }
 
   @Override
@@ -2975,27 +2973,27 @@ public long georadiusByMemberStore(byte[] key, byte[] member, double radius, Geo
 
   @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
@@ -3084,47 +3082,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
@@ -3159,12 +3157,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
@@ -3243,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));
   }
 
@@ -3261,27 +3257,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
@@ -3331,12 +3327,12 @@ 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
@@ -3716,7 +3712,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
@@ -3726,7 +3722,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
@@ -3891,6 +3887,7 @@ 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
@@ -4022,7 +4019,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));
   }
 
@@ -4154,47 +4152,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
@@ -4243,18 +4241,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
@@ -4277,18 +4275,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
@@ -4320,18 +4318,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
@@ -4399,18 +4397,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
@@ -4427,35 +4425,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
@@ -4554,22 +4552,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
@@ -4594,12 +4592,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
@@ -4614,7 +4612,8 @@ 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));
+    return executeCommand(
+        commandObjects.tsCreateRule(sourceKey, destKey, aggregationType, bucketDuration, alignTimestamp));
   }
 
   @Override
@@ -4629,7 +4628,7 @@ public List tsQueryIndex(String... filters) {
 
   @Override
   public TSInfo tsInfo(String key) {
-    return checkAndClientSideCacheCommand(commandObjects.tsInfo(key), key);
+    return executeCommand(commandObjects.tsInfo(key));
   }
 
   @Override
@@ -5108,7 +5107,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) {
@@ -5116,7 +5116,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/AbstractCache.java b/src/main/java/redis/clients/jedis/csc/AbstractCache.java
new file mode 100644
index 0000000000..c5750d385b
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/csc/AbstractCache.java
@@ -0,0 +1,230 @@
+package redis.clients.jedis.csc;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+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;
+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.
+ */
+@Experimental
+public abstract class AbstractCache implements Cache {
+
+  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, DefaultClientSideCacheable.INSTANCE);
+  }
+
+  protected AbstractCache(int maximumSize, ClientSideCacheable cacheable) {
+    this.maximumSize = maximumSize;
+    this.cacheable = cacheable;
+  }
+
+  // Cache interface methods
+
+  @Override
+  public int getMaxSize() {
+    return maximumSize;
+  }
+
+  @Override
+  public abstract int getSize();
+
+  @Override
+  public abstract Collection getCacheEntries();
+
+  @Override
+  public CacheEntry get(CacheKey cacheKey) {
+    CacheEntry entry = getFromStore(cacheKey);
+    if (entry != null) {
+      getEvictionPolicy().touch(cacheKey);
+    }
+    return entry;
+  }
+
+  @Override
+  public CacheEntry set(CacheKey cacheKey, CacheEntry entry) {
+    lock.lock();
+    try {
+      entry = putIntoStore(cacheKey, entry);
+      EvictionPolicy policy = getEvictionPolicy();
+      policy.touch(cacheKey);
+      CacheKey evictedKey = policy.evictNext();
+      if (evictedKey != null) {
+        delete(evictedKey);
+        stats.evict();
+      }
+      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);
+        }
+      }
+      stats.load();
+      return entry;
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public Boolean delete(CacheKey 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);
+        Set> cacheKeysRelatedtoRedisKey = redisKeysToCacheKeys.get(mapKey);
+        if (cacheKeysRelatedtoRedisKey != null) {
+          cacheKeysRelatedtoRedisKey.remove(cacheKey);
+        }
+      }
+      return removed;
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public List delete(List cacheKeys) {
+    lock.lock();
+    try {
+      return cacheKeys.stream().map(this::delete).collect(Collectors.toList());
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public List deleteByRedisKey(Object key) {
+    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()));
+        stats.invalidationByServer(cacheKeys.size());
+        redisKeysToCacheKeys.remove(mapKey);
+      }
+      stats.invalidationMessages();
+      return cacheKeys;
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public List deleteByRedisKeys(List keys) {
+    if (keys == null) {
+      flush();
+      return null;
+    }
+    lock.lock();
+    try {
+      return ((List) keys).stream()
+          .map(this::deleteByRedisKey).flatMap(List::stream).collect(Collectors.toList());
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public int flush() {
+    lock.lock();
+    try {
+      int result = this.getSize();
+      clearStore();
+      redisKeysToCacheKeys.clear();
+      getEvictionPolicy().resetAll();
+      getStats().flush();
+      return result;
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public Boolean isCacheable(CacheKey cacheKey) {
+    return cacheable.isCacheable(cacheKey.getCommand().getArguments().getCommand(), cacheKey.getRedisKeys());
+  }
+
+  @Override
+  public Boolean hasCacheKey(CacheKey cacheKey) {
+    return containsKeyInStore(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
+  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);
+    } else if (key instanceof String) {
+      return makeKeyForRedisKeysToCacheKeys(SafeEncoder.encode((String) key));
+    } else {
+      throw new IllegalArgumentException(key.getClass().getSimpleName() + " is not supported."
+          + " Value: \"" + String.valueOf(key) + "\".");
+    }
+  }
+
+  private static ByteBuffer makeKeyForRedisKeysToCacheKeys(byte[] b) {
+    return ByteBuffer.wrap(b);
+  }
+
+}
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..3a7f801383
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/csc/Cache.java
@@ -0,0 +1,108 @@
+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();
+
+    /**
+     * @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 0573cc1093..e5851e7140 100644
--- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java
+++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java
@@ -1,7 +1,9 @@
 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;
 import redis.clients.jedis.Connection;
 import redis.clients.jedis.JedisClientConfig;
 import redis.clients.jedis.JedisSocketFactory;
@@ -12,12 +14,10 @@
 
 public class CacheConnection extends Connection {
 
-  private final ClientSideCache clientSideCache;
-  private final ReentrantLock lock;
-
-  public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig,
-      ClientSideCache clientSideCache) {
+  private final Cache clientSideCache;
+  private ReentrantLock lock;
 
+  public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, Cache clientSideCache) {
     super(socketFactory, clientConfig);
 
     if (protocol != RedisProtocol.RESP3) {
@@ -25,37 +25,73 @@ 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 {
-      return Protocol.read(inputStream);
+    lock.lock();
+    try {
+      return Protocol.read(inputStream, clientSideCache);
+    } finally {
+      lock.unlock();
     }
   }
 
   @Override
   protected void protocolReadPushes(RedisInputStream inputStream) {
-    if (lock != null && lock.tryLock()) {
+    if (lock.tryLock()) {
       try {
-        //super.setSoTimeout(1);
-        Protocol.readPushes(inputStream, clientSideCache);
+        Protocol.readPushes(inputStream, clientSideCache, true);
       } finally {
-        //super.rollbackTimeout();
         lock.unlock();
       }
     }
   }
 
+  @Override
+  public void disconnect() {
+    super.disconnect();
+    clientSideCache.flush();
+  }
+
+  @Override
+  public  T executeCommand(final CommandObject commandObject) {
+    CacheKey key = new CacheKey<>(commandObject);
+    if (!clientSideCache.isCacheable(key)) {
+      clientSideCache.getStats().nonCacheable();
+      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) {
+        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));
+      clientSideCache.set(cacheKey, cacheEntry);
+      // this line actually provides a deep copy of cached object instance 
+      value = cacheEntry.getValue();
+    }
+    return value;
+  }
+
   private void initializeClientSideCache() {
     sendCommand(Protocol.Command.CLIENT, "TRACKING", "ON");
     String reply = getStatusCodeReply();
@@ -63,4 +99,21 @@ private void initializeClientSideCache() {
       throw new JedisException("Could not enable client tracking. Reply: " + reply);
     }
   }
+
+  private CacheEntry validateEntry(CacheEntry cacheEntry) {
+    CacheConnection cacheOwner = (CacheConnection) cacheEntry.getConnection().get();
+    if (cacheOwner == null || cacheOwner.isBroken() || !cacheOwner.isConnected()) {
+      clientSideCache.delete(cacheEntry.getCacheKey());
+      return null;
+    } else {
+      try {
+        cacheOwner.readPushesWithCheckingBroken();
+      } catch (JedisException e) {
+        clientSideCache.delete(cacheEntry.getCacheKey());
+        return null;
+      }
+
+      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 f2a3243cf4..c0de029db3 100644
--- a/src/main/java/redis/clients/jedis/csc/CacheEntry.java
+++ b/src/main/java/redis/clients/jedis/csc/CacheEntry.java
@@ -1,19 +1,27 @@
 package redis.clients.jedis.csc;
 
+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;
+
 import redis.clients.jedis.Connection;
 import redis.clients.jedis.annots.Internal;
+import redis.clients.jedis.exceptions.JedisCacheException;
 
 @Internal
 public class CacheEntry {
 
   private final CacheKey cacheKey;
-  private final T value;
-  private final Connection connection;
+  private final WeakReference connection;
+  private final byte[] bytes;
 
-  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;
+    this.bytes = toBytes(value);
   }
 
   public CacheKey getCacheKey() {
@@ -21,10 +29,33 @@ public CacheKey getCacheKey() {
   }
 
   public T getValue() {
-    return value;
+    return toObject(bytes);
   }
 
-  public Connection getConnection() {
+  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 (IOException e) {
+      throw new JedisCacheException("Failed to serialize object", e);
+    }
+  }
+
+  private T toObject(byte[] data) {
+    try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        ObjectInputStream ois = new ObjectInputStream(bais)) {
+      return (T) ois.readObject();
+    } 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/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/CacheStats.java b/src/main/java/redis/clients/jedis/csc/CacheStats.java
new file mode 100644
index 0000000000..e689ea0d77
--- /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();
+    }
+
+    protected void nonCacheable() {
+        nonCacheable.incrementAndGet();
+    }
+
+    protected void flush() {
+        flush.incrementAndGet();
+    }
+
+    protected void invalidationByServer(int size) {
+        invalidationsByServer.addAndGet(size);
+    }
+
+    protected 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/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
index fe320418e9..85627dba29 100644
--- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
+++ b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java
@@ -2,66 +2,70 @@
 
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
-import java.util.concurrent.TimeUnit;
 
-public class CaffeineClientSideCache extends ClientSideCache {
+import redis.clients.jedis.annots.Experimental;
+
+import java.util.Collection;
+
+@Experimental
+public class CaffeineClientSideCache extends AbstractCache {
 
   private final Cache cache;
+  private final EvictionPolicy evictionPolicy;
 
-  public CaffeineClientSideCache(Cache caffeineCache) {
-    this.cache = caffeineCache;
+  public CaffeineClientSideCache(int maximumSize) {
+    this(maximumSize, new LRUEviction(maximumSize));
   }
 
-  @Override
-  protected final void clear() {
-    cache.invalidateAll();
+  public CaffeineClientSideCache(int maximumSize, EvictionPolicy evictionPolicy) {
+    super(maximumSize);
+    this.cache = Caffeine.newBuilder().build();
+    this.evictionPolicy = evictionPolicy;
+    this.evictionPolicy.setCache(this);
   }
 
   @Override
-  protected void remove(Iterable> keys) {
-    cache.invalidateAll(keys);
+  protected final void clearStore() {
+    cache.invalidateAll();
   }
 
   @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);
   }
 
-  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;
-    }
+  // TODO: we should discuss if/how we utilize Caffeine and get back to here !
 
-    public Builder ttl(int seconds) {
-      this.expireTime = seconds;
-      return this;
-    }
+  @Override
+  public int getSize() {
+    return (int) cache.estimatedSize();
+  }
 
-    public CaffeineClientSideCache build() {
-      Caffeine cb = Caffeine.newBuilder();
+  @Override
+  public Collection getCacheEntries() {
+    throw new UnsupportedOperationException("Unimplemented method 'getCacheEntries'");
+  }
 
-      cb.maximumSize(maximumSize);
+  @Override
+  public EvictionPolicy getEvictionPolicy() {
+    return this.evictionPolicy;
+  }
 
-      cb.expireAfterWrite(expireTime, expireTimeUnit);
+  @Override
+  protected Boolean removeFromStore(CacheKey cacheKey) {
+    cache.invalidate(cacheKey);
+    return true;
+  }
 
-      return new CaffeineClientSideCache(cb.build());
-    }
+  @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
deleted file mode 100644
index 94a235873d..0000000000
--- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package redis.clients.jedis.csc;
-
-import java.nio.ByteBuffer;
-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.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.
- */
-@Experimental
-public abstract class ClientSideCache {
-
-  protected static final int DEFAULT_MAXIMUM_SIZE = 10_000;
-  protected static final int DEFAULT_EXPIRE_SECONDS = 100;
-
-  private final Map>> redisKeysToCacheKeys = new ConcurrentHashMap<>();
-  private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE; // TODO: volatile
-
-  protected ClientSideCache() {
-  }
-
-  public void setCacheable(ClientSideCacheable cacheable) {
-    this.cacheable = Objects.requireNonNull(cacheable, "'cacheable' must not be null");
-  }
-
-  protected abstract void clear();
-
-  protected abstract void remove(Iterable> keys);
-
-  protected abstract void put(CacheKey key, CacheEntry entry);
-
-  protected abstract CacheEntry get(CacheKey key);
-
-  public final void flush() {
-    invalidateAllRedisKeysAndCacheEntries();
-  }
-
-  public final void invalidateKey(Object key) {
-    invalidateRedisKeyAndRespectiveCacheEntries(key);
-  }
-
-  public final void invalidate(List list) {
-    if (list == null) {
-      invalidateAllRedisKeysAndCacheEntries();
-      return;
-    }
-
-    list.forEach(this::invalidateRedisKeyAndRespectiveCacheEntries);
-  }
-
-  private void invalidateAllRedisKeysAndCacheEntries() {
-    clear();
-    redisKeysToCacheKeys.clear();
-  }
-
-  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);
-    final ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key);
-
-    Set> commands = redisKeysToCacheKeys.get(mapKey);
-    if (commands != null) {
-      remove(commands);
-      redisKeysToCacheKeys.remove(mapKey);
-    }
-  }
-
-  public final  T get(Function, T> loader, CommandObject command, Object... keys) {
-
-    if (!cacheable.isCacheable(command.getArguments().getCommand(), keys)) {
-      return loader.apply(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();
-    }
-
-    // CACHE MISS!!
-    T value = loader.apply(command);
-    if (value != null) {
-      cacheEntry = new CacheEntry(cacheKey, value, /*connection*/ null); // TODO: connection
-      put(cacheKey, cacheEntry);
-      for (Object key : keys) {
-        ByteBuffer mapKey = makeKeyForRedisKeysToCacheKeys(key);
-        if (redisKeysToCacheKeys.containsKey(mapKey)) {
-          redisKeysToCacheKeys.get(mapKey).add(cacheKey);
-        } else {
-          Set> set = ConcurrentHashMap.newKeySet();
-          set.add(cacheKey);
-          redisKeysToCacheKeys.put(mapKey, set);
-        }
-      }
-    }
-
-    return value;
-  }
-
-  private ByteBuffer makeKeyForRedisKeysToCacheKeys(Object key) {
-    if (key instanceof byte[]) {
-      return makeKeyForRedisKeysToCacheKeys((byte[]) key);
-    } else if (key instanceof String) {
-      return makeKeyForRedisKeysToCacheKeys(SafeEncoder.encode((String) key));
-    } else {
-      throw new IllegalArgumentException(key.getClass().getSimpleName() + " is not supported."
-          + " Value: \"" + String.valueOf(key) + "\".");
-    }
-  }
-
-  private static ByteBuffer makeKeyForRedisKeysToCacheKeys(byte[] b) {
-    return ByteBuffer.wrap(b);
-  }
-}
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..a382dc7623
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/csc/DefaultCache.java
@@ -0,0 +1,71 @@
+package redis.clients.jedis.csc;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultCache extends AbstractCache {
+
+    protected final Map cache;
+    private final EvictionPolicy evictionPolicy;
+
+    public DefaultCache(int maximumSize) {
+        this(maximumSize, new HashMap());
+    }
+
+    public DefaultCache(int maximumSize, Map map) {
+        this(maximumSize, map, DefaultClientSideCacheable.INSTANCE, new LRUEviction(maximumSize));
+    }
+
+    public DefaultCache(int maximumSize, ClientSideCacheable cacheable) {
+        this(maximumSize, new HashMap(), cacheable, new LRUEviction(maximumSize));
+    }
+
+    public DefaultCache(int maximumSize, Map map, ClientSideCacheable cacheable, EvictionPolicy evictionPolicy) {
+        super(maximumSize, cacheable);
+        this.cache = map;
+        this.evictionPolicy = evictionPolicy;
+        this.evictionPolicy.setCache(this);
+    }
+
+    @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/DefaultClientSideCacheable.java b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java
index d97ca6a2ee..b510b5bf0d 100644
--- a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java
+++ b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java
@@ -1,15 +1,94 @@
 package redis.clients.jedis.csc;
 
+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/EvictionPolicy.java b/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java
new file mode 100644
index 0000000000..217b04263e
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/csc/EvictionPolicy.java
@@ -0,0 +1,77 @@
+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(); + + /** + * Sets the cache that is associated to this policy instance + * @param cache The cache instance + */ + void setCache(Cache cache); + + /** + * @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..81853ad651 100644 --- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java @@ -2,67 +2,79 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import java.util.concurrent.TimeUnit; -public class GuavaClientSideCache extends ClientSideCache { +import redis.clients.jedis.annots.Experimental; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +@Experimental +public class GuavaClientSideCache extends AbstractCache { private final Cache cache; + private final EvictionPolicy evictionPolicy; - public GuavaClientSideCache(Cache guavaCache) { - super(); - this.cache = guavaCache; + 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); } @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); } - 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; - } + // TODO: we should discuss if/how we utilize Guava and get back to here ! - public Builder ttl(int seconds) { - this.expireTime = seconds; - return this; - } + @Override + public int getSize() { + return (int) cache.size(); + } - public GuavaClientSideCache build() { - CacheBuilder cb = CacheBuilder.newBuilder(); + @Override + public Collection getCacheEntries() { + throw new UnsupportedOperationException("Unimplemented method 'getCacheEntries'"); + } - cb.maximumSize(maximumSize); + @Override + public EvictionPolicy getEvictionPolicy() { + return this.evictionPolicy; + } - cb.expireAfterWrite(expireTime, expireTimeUnit); + @Override + protected Boolean removeFromStore(CacheKey cacheKey) { + cache.invalidate(cacheKey); + return true; + } - return new GuavaClientSideCache(cb.build()); - } + @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..b75c7338ba --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/LRUEviction.java @@ -0,0 +1,106 @@ +package redis.clients.jedis.csc; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * 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 + */ + protected Cache cache; + protected LinkedHashMap accessTimes; + + protected ArrayDeque pendingEvictions = new ArrayDeque(); + + protected ConcurrentLinkedQueue msg = new ConcurrentLinkedQueue(); + + private int initialCapacity; + + /** + * Constructor that gets the cache passed + * + * @param initialCapacity + */ + public LRUEviction(int initialCapacity) { + this.initialCapacity = initialCapacity; + } + + @Override + public void setCache(Cache cache) { + this.cache = cache; + this.accessTimes = new LinkedHashMap(initialCapacity, 1f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry 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; + } + }; + } + + @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 synchronized CacheKey evictNext() { + CacheKey cacheKey = pendingEvictions.pollFirst(); + while (cacheKey != null && !cache.hasCacheKey(cacheKey)) { + cacheKey = pendingEvictions.pollFirst(); + } + return cacheKey; + } + + @Override + 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 synchronized void touch(CacheKey cacheKey) { + this.accessTimes.put(cacheKey, new Date().getTime()); + } + + @Override + public synchronized boolean reset(CacheKey cacheKey) { + return this.accessTimes.remove(cacheKey) != null; + } + + @Override + public synchronized int resetAll() { + int result = this.accessTimes.size(); + accessTimes.clear(); + return result; + } + +} 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 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/main/java/redis/clients/jedis/util/RedisInputStream.java b/src/main/java/redis/clients/jedis/util/RedisInputStream.java index 0226961028..5baf1b3225 100644 --- a/src/main/java/redis/clients/jedis/util/RedisInputStream.java +++ b/src/main/java/redis/clients/jedis/util/RedisInputStream.java @@ -184,9 +184,12 @@ public boolean readBooleanCrLf() { ensureCrLf(); switch (b) { - case 't': return true; - case 'f': return false; - default: throw new JedisConnectionException("Unexpected character!"); + case 't': + return true; + case 'f': + return false; + default: + throw new JedisConnectionException("Unexpected character!"); } } @@ -260,4 +263,12 @@ private void ensureFill() throws JedisConnectionException { } } } + + @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/benchmark/CSCPooleadBenchmark.java b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java new file mode 100644 index 0000000000..8ee0580011 --- /dev/null +++ b/src/test/java/redis/clients/jedis/benchmark/CSCPooleadBenchmark.java @@ -0,0 +1,79 @@ +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.Cache; +import redis.clients.jedis.csc.TestCache; + +public class CSCPooleadBenchmark { + + private static EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone0"); + 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 = 50; + long withoutCache = 0; + long withCache = 0; + + for (int i = 0; i < totalRounds; i++) { + withoutCache += runBenchmark(null); + withCache += runBenchmark(new TestCache()); + } + for (int i = 0; i < totalRounds; i++) { + } + 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); + } + + private static long runBenchmark(Cache 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(Cache cache) throws Exception { + JedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP3) + .password(endpoint.getPassword()).build(); + List tds = new ArrayList<>(); + final AtomicInteger ind = new AtomicInteger(); + 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(); + } + } + } +} 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/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java index 8308233603..18a9b28d15 100644 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java @@ -8,91 +8,82 @@ 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 { @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")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @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("bar", jedis.get("foo")); - assertEquals(1, 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 332648f424..77e04c5e3b 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -1,67 +1,76 @@ 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.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.Mockito; +import redis.clients.jedis.CommandObjects; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.util.JedisURIHelper; +import redis.clients.jedis.UnifiedJedis; 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<>(); - ClientSideCache clientSideCache = new MapClientSideCache(map); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + Cache clientSideCache = new TestCache(map); + 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); } // 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); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + Cache clientSideCache = new TestCache(map); + 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.invalidateKey(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(); } } @@ -71,10 +80,82 @@ 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()); } } + @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(); + Cache mock = Mockito.spy(cache); + UnifiedJedis client = new UnifiedJedis(hnp, clientConfig.get(), mock); + 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(); + } + } + + @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/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 4c7362e63b..fad64df8ab 100644 --- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java @@ -4,54 +4,47 @@ 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")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @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("bar", jedis.get("foo")); - assertEquals(1, 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/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index 4c3f25b5e8..6434bf1111 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -52,18 +52,18 @@ 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"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @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)); @@ -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")); @@ -82,18 +82,18 @@ 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(); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @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)); @@ -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 20933fd237..de2e3e8144 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -3,37 +3,84 @@ 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.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; import org.junit.After; 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.locationtech.jts.util.Assert; +import redis.clients.jedis.CommandObjects; 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.ConcurrentHashMap; +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; - 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; + protected static final EndpointConfig sslEndpoint = HostAndPorts.getRedisEndpoint("standalone0-tls"); + + 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(); + } + + 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(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build()); control.flushAll(); } @@ -42,30 +89,37 @@ public void tearDown() throws Exception { control.close(); } - private static final Supplier clientConfig - = () -> DefaultJedisClientConfig.builder().resp3().password("foobared").build(); // TODO: use EndpointConfig + private final Supplier clientConfig = () -> endpoint.getClientConfigBuilder().resp3().build(); - private static final Supplier> singleConnectionPoolConfig - = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - return poolConfig; - }; + private final Supplier> singleConnectionPoolConfig = () -> { + ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); + poolConfig.setMaxTotal(1); + return poolConfig; + }; + + private void sleep() { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } @Test public void simple() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache())) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + sleep(); + assertNull(jedis.get("foo")); } } @Test public void simpleWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map), + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -73,10 +127,12 @@ 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)); - jedis.ping(); + sleep(); + assertNull(jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(0)); + sleep(); + assertThat(map, Matchers.aMapWithSize(0)); + sleep(); assertNull(jedis.get("foo")); assertThat(map, Matchers.aMapWithSize(0)); } @@ -84,18 +140,19 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache())) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + sleep(); + assertNull(jedis.get("foo")); } } @Test public void flushAllWithSimpleMap() { HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new MapClientSideCache(map), + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache(map), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertThat(map, Matchers.aMapWithSize(0)); @@ -103,12 +160,228 @@ 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)); - 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 testSequentialAccess() throws InterruptedException { + int threadCount = 10; + int iterations = 10000; + + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get())) { + jedis.set("foo", "0"); + } + + ReentrantLock lock = new ReentrantLock(true); + 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++) { + 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("OK", 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 + String finalValue = control.get("foo"); + assertEquals(threadCount * iterations, Integer.parseInt(finalValue)); + + } + + @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; + + HashMap map = new HashMap(); + 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 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, 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 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()); + } + } + + @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/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index 1660396d23..13130416ce 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -48,19 +48,19 @@ 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")); control.del("foo"); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @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)); @@ -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")); @@ -79,19 +79,19 @@ 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")); control.flushAll(); - assertThat(jedis.get("foo"), Matchers.oneOf("bar", null)); // ? + assertEquals(null, jedis.get("foo")); } } @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)); @@ -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")); 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..fd90b1229b --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -0,0 +1,30 @@ +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(10000, map); + } + + public TestCache(Map map, ClientSideCacheable cacheable) { + + super(10000, map, cacheable, new LRUEviction(10000)); + } + + public TestCache(int maxSize, Map map, ClientSideCacheable cacheable) { + this(maxSize, map, cacheable, new LRUEviction(maxSize)); + } + + public TestCache(int maxSize, Map map, ClientSideCacheable cacheable, + EvictionPolicy evictionPolicy) { + super(maxSize, map, cacheable, evictionPolicy); + } + +} From 9bddabd00307d38d3a6ae3dfedc6a57128a462a1 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:12:03 +0600 Subject: [PATCH 41/48] Jedis test plan coverage for CSC (#3918) * initial changes * cover tests for JedisPooled and functionality * fix javadoc * cover new tests for JedisCluster and JedisSentineled * Fix CSC allow-and-deny-list and rename Cacheable interface * Tag CommandArguments#getKeys() as Internal * cover lruEvictionTest * Address code reviews and more updates * fix format and more minor changes * format Connection * modify WeakReference usage --- pom.xml | 13 - .../redis/clients/jedis/CommandArguments.java | 37 +- .../java/redis/clients/jedis/Connection.java | 7 +- .../clients/jedis/csc/AbstractCache.java | 24 +- .../java/redis/clients/jedis/csc/Cache.java | 10 +- .../clients/jedis/csc/CacheConnection.java | 6 +- .../redis/clients/jedis/csc/CacheEntry.java | 17 +- .../redis/clients/jedis/csc/CacheKey.java | 11 +- .../redis/clients/jedis/csc/Cacheable.java | 9 + .../jedis/csc/CaffeineClientSideCache.java | 71 --- .../jedis/csc/ClientSideCacheable.java | 8 - .../redis/clients/jedis/csc/DefaultCache.java | 10 +- .../clients/jedis/csc/DefaultCacheable.java | 98 +++++ .../jedis/csc/DefaultClientSideCacheable.java | 94 ---- .../jedis/csc/GuavaClientSideCache.java | 80 ---- .../util/AllowAndDenyListWithStringKeys.java | 25 +- .../jedis/exceptions/JedisCacheException.java | 1 + .../redis/clients/jedis/SSLJedisTest.java | 2 +- ...ava => AllowAndDenyListCacheableTest.java} | 4 +- .../csc/CaffeineClientSideCacheTest.java | 89 ---- .../csc/ClientSideCacheFunctionalityTest.java | 406 ++++++++++++++++-- .../jedis/csc/ClientSideCacheTestBase.java | 2 +- .../jedis/csc/GuavaClientSideCacheTest.java | 86 ---- .../csc/JedisClusterClientSideCacheTest.java | 85 +--- .../csc/JedisPooledClientSideCacheTest.java | 378 +--------------- .../JedisPooledClientSideCacheTestBase.java | 57 +++ .../JedisSentineledClientSideCacheTest.java | 88 +--- .../SSLJedisPooledClientSideCacheTest.java | 16 + .../redis/clients/jedis/csc/TestCache.java | 7 +- .../UnifiedJedisClientSideCacheTestBase.java | 226 ++++++++++ 30 files changed, 894 insertions(+), 1073 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/Cacheable.java delete mode 100644 src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java delete mode 100644 src/main/java/redis/clients/jedis/csc/ClientSideCacheable.java create mode 100644 src/main/java/redis/clients/jedis/csc/DefaultCacheable.java delete mode 100644 src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java delete mode 100644 src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java rename src/test/java/redis/clients/jedis/csc/{AllowAndDenyListClientSideCacheTest.java => AllowAndDenyListCacheableTest.java} (96%) delete mode 100644 src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java delete mode 100644 src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java create mode 100644 src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java create mode 100644 src/test/java/redis/clients/jedis/csc/SSLJedisPooledClientSideCacheTest.java create mode 100644 src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java diff --git a/pom.xml b/pom.xml index 795a3570dc..73483f622e 100644 --- a/pom.xml +++ b/pom.xml @@ -76,19 +76,6 @@ - - - com.google.guava - guava - 33.2.1-jre - true - - - com.github.ben-manes.caffeine - caffeine - 2.9.3 - true - diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index b0ebd7fd13..556016cf67 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -3,8 +3,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; +import java.util.List; +import redis.clients.jedis.annots.Internal; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.args.RawableFactory; import redis.clients.jedis.commands.ProtocolCommand; @@ -14,14 +17,20 @@ public class CommandArguments implements Iterable { private final ArrayList args; - private final ArrayList keys; + + private List keys; private boolean blocking; + private CommandArguments() { + throw new InstantiationError(); + } + public CommandArguments(ProtocolCommand command) { args = new ArrayList<>(); args.add(command); - keys = new ArrayList<>(); + + keys = Collections.emptyList(); } public ProtocolCommand getCommand() { @@ -113,10 +122,25 @@ public CommandArguments key(Object key) { } else { throw new IllegalArgumentException("\"" + key.toString() + "\" is not a valid argument."); } - keys.add(key); + + addKeyInKeys(key); + return this; } + private void addKeyInKeys(Object key) { + if (keys.isEmpty()) { + keys = Collections.singletonList(key); + } else if (keys.size() == 1) { + List oldKeys = keys; + keys = new ArrayList(); + keys.addAll(oldKeys); + keys.add(key); + } else { + keys.add(key); + } + } + public final CommandArguments keys(Object... keys) { Arrays.stream(keys).forEach(this::key); return this; @@ -133,6 +157,7 @@ public final CommandArguments addParams(IParams params) { } protected CommandArguments processKey(byte[] key) { + // do nothing return this; } @@ -144,6 +169,7 @@ protected final CommandArguments processKeys(byte[]... keys) { } protected CommandArguments processKey(String key) { + // do nothing return this; } @@ -163,8 +189,9 @@ public Iterator iterator() { return args.iterator(); } - public Object[] getKeys() { - return keys.toArray(); + @Internal + public List getKeys() { + return keys; } public boolean isBlocking() { diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 3fe04195fb..df59241c34 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -102,7 +102,7 @@ public String toIdentityString() { .append(id) .append(", L:") .append(localAddr) - .append(broken? " ! " : " - ") + .append(broken ? " ! " : " - ") .append("R:") .append(remoteAddr) .append(']'); @@ -455,7 +455,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 @@ -499,8 +499,9 @@ protected void initializeFromClientConfig(final JedisClientConfig config) { } ClientSetInfoConfig setInfoConfig = config.getClientSetInfoConfig(); - if (setInfoConfig == null) + if (setInfoConfig == null) { setInfoConfig = ClientSetInfoConfig.DEFAULT; + } if (!setInfoConfig.isDisabled()) { String libName = JedisMetaInfo.getArtifactId(); diff --git a/src/main/java/redis/clients/jedis/csc/AbstractCache.java b/src/main/java/redis/clients/jedis/csc/AbstractCache.java index c5750d385b..fc936b5baf 100644 --- a/src/main/java/redis/clients/jedis/csc/AbstractCache.java +++ b/src/main/java/redis/clients/jedis/csc/AbstractCache.java @@ -14,25 +14,23 @@ 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 an of implementation of this class + * to the client object. */ @Experimental public abstract class AbstractCache implements Cache { - private ClientSideCacheable cacheable; + private Cacheable 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, DefaultClientSideCacheable.INSTANCE); + this(maximumSize, DefaultCacheable.INSTANCE); } - protected AbstractCache(int maximumSize, ClientSideCacheable cacheable) { + protected AbstractCache(int maximumSize, Cacheable cacheable) { this.maximumSize = maximumSize; this.cacheable = cacheable; } @@ -89,7 +87,7 @@ public CacheEntry set(CacheKey cacheKey, CacheEntry entry) { } @Override - public Boolean delete(CacheKey cacheKey) { + public boolean delete(CacheKey cacheKey) { lock.lock(); try { boolean removed = removeFromStore(cacheKey); @@ -171,12 +169,12 @@ public int flush() { } @Override - public Boolean isCacheable(CacheKey cacheKey) { - return cacheable.isCacheable(cacheKey.getCommand().getArguments().getCommand(), cacheKey.getRedisKeys()); + public boolean isCacheable(CacheKey cacheKey) { + return cacheable.isCacheable(cacheKey.getRedisCommand(), cacheKey.getRedisKeys()); } @Override - public Boolean hasCacheKey(CacheKey cacheKey) { + public boolean hasCacheKey(CacheKey cacheKey) { return containsKeyInStore(cacheKey); } @@ -202,13 +200,13 @@ public CacheStats getAndResetStats() { protected abstract CacheEntry putIntoStore(CacheKey cacheKey, CacheEntry entry); - protected abstract Boolean removeFromStore(CacheKey cacheKey); + protected abstract boolean removeFromStore(CacheKey cacheKey); // protected abstract Collection remove(Set> commands); protected abstract void clearStore(); - protected abstract Boolean containsKeyInStore(CacheKey cacheKey); + protected abstract boolean containsKeyInStore(CacheKey cacheKey); // End of abstract methods to be implemented by the concrete classes diff --git a/src/main/java/redis/clients/jedis/csc/Cache.java b/src/main/java/redis/clients/jedis/csc/Cache.java index 3a7f801383..49413bc0de 100644 --- a/src/main/java/redis/clients/jedis/csc/Cache.java +++ b/src/main/java/redis/clients/jedis/csc/Cache.java @@ -45,7 +45,7 @@ public interface Cache { * @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); + boolean delete(CacheKey cacheKey); /** * Delete entries by cache key from the cache @@ -82,14 +82,14 @@ public interface Cache { * @param cacheKey The key of the cache entry * @return True if the entry is cachable, false otherwise */ - Boolean isCacheable(CacheKey cacheKey); + 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); + boolean hasCacheKey(CacheKey cacheKey); /** * @return The eviction policy that is used by the cache @@ -99,10 +99,10 @@ public interface Cache { /** * @return The statistics of the cache */ - public CacheStats getStats(); + CacheStats getStats(); /** * @return The statistics of the cache */ - public CacheStats getAndResetStats(); + 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 e5851e7140..a6afe9cea2 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -1,8 +1,8 @@ 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; import redis.clients.jedis.Connection; import redis.clients.jedis.JedisClientConfig; @@ -84,7 +84,7 @@ public T executeCommand(final CommandObject commandObject) { clientSideCache.getStats().miss(); T value = super.executeCommand(commandObject); if (value != null) { - cacheEntry = new CacheEntry(cacheKey, value, new WeakReference(this)); + cacheEntry = new CacheEntry<>(cacheKey, value, this); clientSideCache.set(cacheKey, cacheEntry); // this line actually provides a deep copy of cached object instance value = cacheEntry.getValue(); @@ -101,7 +101,7 @@ private void initializeClientSideCache() { } private CacheEntry validateEntry(CacheEntry cacheEntry) { - CacheConnection cacheOwner = (CacheConnection) cacheEntry.getConnection().get(); + CacheConnection cacheOwner = cacheEntry.getConnection(); if (cacheOwner == null || cacheOwner.isBroken() || !cacheOwner.isConnected()) { clientSideCache.delete(cacheEntry.getCacheKey()); return null; diff --git a/src/main/java/redis/clients/jedis/csc/CacheEntry.java b/src/main/java/redis/clients/jedis/csc/CacheEntry.java index c0de029db3..36c308db8d 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheEntry.java +++ b/src/main/java/redis/clients/jedis/csc/CacheEntry.java @@ -7,20 +7,17 @@ import java.io.ObjectOutputStream; import java.lang.ref.WeakReference; -import redis.clients.jedis.Connection; -import redis.clients.jedis.annots.Internal; import redis.clients.jedis.exceptions.JedisCacheException; -@Internal public class CacheEntry { private final CacheKey cacheKey; - private final WeakReference connection; + private final WeakReference connection; private final byte[] bytes; - public CacheEntry(CacheKey cacheKey, T value, WeakReference connection) { + public CacheEntry(CacheKey cacheKey, T value, CacheConnection connection) { this.cacheKey = cacheKey; - this.connection = connection; + this.connection = new WeakReference<>(connection); this.bytes = toBytes(value); } @@ -32,8 +29,8 @@ public T getValue() { return toObject(bytes); } - public WeakReference getConnection() { - return connection; + public CacheConnection getConnection() { + return connection.get(); } private static byte[] toBytes(Object object) { @@ -52,9 +49,7 @@ private T toObject(byte[] data) { try (ByteArrayInputStream bais = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bais)) { return (T) ois.readObject(); - } catch (IOException e) { - throw new JedisCacheException("Failed to deserialize object", e); - } catch (ClassNotFoundException e) { + } catch (IOException | ClassNotFoundException e) { throw new JedisCacheException("Failed to deserialize object", e); } } diff --git a/src/main/java/redis/clients/jedis/csc/CacheKey.java b/src/main/java/redis/clients/jedis/csc/CacheKey.java index 4e854550fe..dedd88374e 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheKey.java +++ b/src/main/java/redis/clients/jedis/csc/CacheKey.java @@ -1,10 +1,11 @@ package redis.clients.jedis.csc; +import java.util.List; import java.util.Objects; + import redis.clients.jedis.CommandObject; -import redis.clients.jedis.annots.Internal; +import redis.clients.jedis.commands.ProtocolCommand; -@Internal public class CacheKey { private final CommandObject command; @@ -26,11 +27,11 @@ public boolean equals(Object obj) { return Objects.equals(this.command, other.command); } - public Object[] getRedisKeys() { + public List getRedisKeys() { return command.getArguments().getKeys(); } - public CommandObject getCommand() { - return command; + public ProtocolCommand getRedisCommand() { + return command.getArguments().getCommand(); } } diff --git a/src/main/java/redis/clients/jedis/csc/Cacheable.java b/src/main/java/redis/clients/jedis/csc/Cacheable.java new file mode 100644 index 0000000000..908b004cbb --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/Cacheable.java @@ -0,0 +1,9 @@ +package redis.clients.jedis.csc; + +import java.util.List; +import redis.clients.jedis.commands.ProtocolCommand; + +public interface Cacheable { + + boolean isCacheable(ProtocolCommand command, List keys); +} diff --git a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java b/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java deleted file mode 100644 index 85627dba29..0000000000 --- a/src/main/java/redis/clients/jedis/csc/CaffeineClientSideCache.java +++ /dev/null @@ -1,71 +0,0 @@ -package redis.clients.jedis.csc; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; - -import redis.clients.jedis.annots.Experimental; - -import java.util.Collection; - -@Experimental -public class CaffeineClientSideCache extends AbstractCache { - - private final Cache cache; - private final EvictionPolicy evictionPolicy; - - public CaffeineClientSideCache(int maximumSize) { - this(maximumSize, new LRUEviction(maximumSize)); - } - - public CaffeineClientSideCache(int maximumSize, EvictionPolicy evictionPolicy) { - super(maximumSize); - this.cache = Caffeine.newBuilder().build(); - this.evictionPolicy = evictionPolicy; - this.evictionPolicy.setCache(this); - } - - @Override - protected final void clearStore() { - cache.invalidateAll(); - } - - @Override - public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { - cache.put(key, entry); - return entry; - } - - @Override - public CacheEntry getFromStore(CacheKey key) { - return cache.getIfPresent(key); - } - - // TODO: we should discuss if/how we utilize Caffeine and get back to here ! - - @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/ClientSideCacheable.java b/src/main/java/redis/clients/jedis/csc/ClientSideCacheable.java deleted file mode 100644 index d3c782ad00..0000000000 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCacheable.java +++ /dev/null @@ -1,8 +0,0 @@ -package redis.clients.jedis.csc; - -import redis.clients.jedis.commands.ProtocolCommand; - -public interface ClientSideCacheable { - - boolean isCacheable(ProtocolCommand command, Object... keys); -} diff --git a/src/main/java/redis/clients/jedis/csc/DefaultCache.java b/src/main/java/redis/clients/jedis/csc/DefaultCache.java index a382dc7623..aee3c634b5 100644 --- a/src/main/java/redis/clients/jedis/csc/DefaultCache.java +++ b/src/main/java/redis/clients/jedis/csc/DefaultCache.java @@ -14,14 +14,14 @@ public DefaultCache(int maximumSize) { } public DefaultCache(int maximumSize, Map map) { - this(maximumSize, map, DefaultClientSideCacheable.INSTANCE, new LRUEviction(maximumSize)); + this(maximumSize, map, DefaultCacheable.INSTANCE, new LRUEviction(maximumSize)); } - public DefaultCache(int maximumSize, ClientSideCacheable cacheable) { + public DefaultCache(int maximumSize, Cacheable cacheable) { this(maximumSize, new HashMap(), cacheable, new LRUEviction(maximumSize)); } - public DefaultCache(int maximumSize, Map map, ClientSideCacheable cacheable, EvictionPolicy evictionPolicy) { + public DefaultCache(int maximumSize, Map map, Cacheable cacheable, EvictionPolicy evictionPolicy) { super(maximumSize, cacheable); this.cache = map; this.evictionPolicy = evictionPolicy; @@ -54,7 +54,7 @@ public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { } @Override - public Boolean removeFromStore(CacheKey key) { + public boolean removeFromStore(CacheKey key) { return cache.remove(key) != null; } @@ -64,7 +64,7 @@ protected final void clearStore() { } @Override - protected Boolean containsKeyInStore(CacheKey cacheKey) { + protected boolean containsKeyInStore(CacheKey cacheKey) { return cache.containsKey(cacheKey); } diff --git a/src/main/java/redis/clients/jedis/csc/DefaultCacheable.java b/src/main/java/redis/clients/jedis/csc/DefaultCacheable.java new file mode 100644 index 0000000000..47f9ca0ccc --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/DefaultCacheable.java @@ -0,0 +1,98 @@ +package redis.clients.jedis.csc; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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 DefaultCacheable implements Cacheable { + + public static final DefaultCacheable INSTANCE = new DefaultCacheable(); + + private static final Set DEFAULT_CACHEABLE_COMMANDS = new HashSet() { + { + add(Command.BITCOUNT); + add(Command.BITFIELD_RO); + add(Command.BITPOS); + add(Command.EXISTS); + add(Command.GEODIST); + add(Command.GEOHASH); + add(Command.GEOPOS); + add(Command.GEORADIUSBYMEMBER_RO); + add(Command.GEORADIUS_RO); + add(Command.GEOSEARCH); + add(Command.GET); + add(Command.GETBIT); + add(Command.GETRANGE); + add(Command.HEXISTS); + add(Command.HGET); + add(Command.HGETALL); + add(Command.HKEYS); + add(Command.HLEN); + add(Command.HMGET); + add(Command.HSTRLEN); + add(Command.HVALS); + add(JsonCommand.ARRINDEX); + add(JsonCommand.ARRLEN); + add(JsonCommand.GET); + add(JsonCommand.MGET); + add(JsonCommand.OBJKEYS); + add(JsonCommand.OBJLEN); + add(JsonCommand.STRLEN); + add(JsonCommand.TYPE); + add(Command.LCS); + add(Command.LINDEX); + add(Command.LLEN); + add(Command.LPOS); + add(Command.LRANGE); + add(Command.MGET); + add(Command.SCARD); + add(Command.SDIFF); + add(Command.SINTER); + add(Command.SISMEMBER); + add(Command.SMEMBERS); + add(Command.SMISMEMBER); + add(Command.STRLEN); + add(Command.SUBSTR); + add(Command.SUNION); + add(TimeSeriesCommand.GET); + add(TimeSeriesCommand.INFO); + add(TimeSeriesCommand.RANGE); + add(TimeSeriesCommand.REVRANGE); + add(Command.TYPE); + add(Command.XLEN); + add(Command.XPENDING); + add(Command.XRANGE); + add(Command.XREVRANGE); + add(Command.ZCARD); + add(Command.ZCOUNT); + add(Command.ZLEXCOUNT); + add(Command.ZMSCORE); + add(Command.ZRANGE); + add(Command.ZRANGEBYLEX); + add(Command.ZRANGEBYSCORE); + add(Command.ZRANK); + add(Command.ZREVRANGE); + add(Command.ZREVRANGEBYLEX); + add(Command.ZREVRANGEBYSCORE); + add(Command.ZREVRANK); + add(Command.ZSCORE); + } + }; + + public DefaultCacheable() { + } + + public static boolean isDefaultCacheableCommand(ProtocolCommand command) { + return DEFAULT_CACHEABLE_COMMANDS.contains(command); + } + + @Override + public boolean isCacheable(ProtocolCommand command, List keys) { + return isDefaultCacheableCommand(command); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java b/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java deleted file mode 100644 index b510b5bf0d..0000000000 --- a/src/main/java/redis/clients/jedis/csc/DefaultClientSideCacheable.java +++ /dev/null @@ -1,94 +0,0 @@ -package redis.clients.jedis.csc; - -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(); - - 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) { - Boolean cachable = commandsToCache.get(command); - return (cachable != null) ? cachable : false; - } -} diff --git a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java b/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java deleted file mode 100644 index 81853ad651..0000000000 --- a/src/main/java/redis/clients/jedis/csc/GuavaClientSideCache.java +++ /dev/null @@ -1,80 +0,0 @@ -package redis.clients.jedis.csc; - -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.stream.Collectors; -import java.util.stream.StreamSupport; - -@Experimental -public class GuavaClientSideCache extends AbstractCache { - - private final Cache cache; - private final EvictionPolicy evictionPolicy; - - 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); - } - - @Override - public final void clearStore() { - cache.invalidateAll(); - } - - public List remove(Iterable> keys) { - cache.invalidateAll(keys); - return StreamSupport.stream(keys.spliterator(), false) - .collect(Collectors.toList()); - } - - @Override - public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { - cache.put(key, entry); - return entry; - } - - @Override - public CacheEntry getFromStore(CacheKey key) { - return cache.getIfPresent(key); - } - - // TODO: we should discuss if/how we utilize Guava and get back to here ! - - @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/util/AllowAndDenyListWithStringKeys.java b/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java index f0167bc532..25fd89cff1 100644 --- a/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java +++ b/src/main/java/redis/clients/jedis/csc/util/AllowAndDenyListWithStringKeys.java @@ -1,10 +1,12 @@ package redis.clients.jedis.csc.util; +import java.util.List; import java.util.Set; import redis.clients.jedis.commands.ProtocolCommand; -import redis.clients.jedis.csc.DefaultClientSideCacheable; +import redis.clients.jedis.csc.DefaultCacheable; +import redis.clients.jedis.csc.Cacheable; -public class AllowAndDenyListWithStringKeys extends DefaultClientSideCacheable { +public class AllowAndDenyListWithStringKeys implements Cacheable { private final Set allowCommands; private final Set denyCommands; @@ -21,21 +23,26 @@ public AllowAndDenyListWithStringKeys(Set allowCommands, Set keys) { + if (allowCommands != null && !allowCommands.contains(command)) { return false; - if (denyCommands != null && denyCommands.contains(command)) + } + if (denyCommands != null && denyCommands.contains(command)) { return false; + } for (Object key : keys) { - if (!(key instanceof String)) + if (!(key instanceof String)) { return false; - if (allowKeys != null && !allowKeys.contains((String) key)) + } + if (allowKeys != null && !allowKeys.contains((String) key)) { return false; - if (denyKeys != null && denyKeys.contains((String) key)) + } + if (denyKeys != null && denyKeys.contains((String) key)) { return false; + } } - return super.isCacheable(command, keys); + return DefaultCacheable.isDefaultCacheableCommand(command); } } diff --git a/src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java b/src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java index 84fcb55ee3..94a745e1bf 100644 --- a/src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java +++ b/src/main/java/redis/clients/jedis/exceptions/JedisCacheException.java @@ -3,6 +3,7 @@ public class JedisCacheException extends JedisException { private static final long serialVersionUID = 3878126572474819403L; + public JedisCacheException(String message) { super(message); } diff --git a/src/test/java/redis/clients/jedis/SSLJedisTest.java b/src/test/java/redis/clients/jedis/SSLJedisTest.java index d3a5853f42..4ef4f969bb 100644 --- a/src/test/java/redis/clients/jedis/SSLJedisTest.java +++ b/src/test/java/redis/clients/jedis/SSLJedisTest.java @@ -32,7 +32,7 @@ public static void prepare() { setupTrustStore(); } - static void setupTrustStore() { + public static void setupTrustStore() { setJvmTrustStore("src/test/resources/truststore.jceks", "jceks"); } diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java similarity index 96% rename from src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java rename to src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java index 83371c7f44..c8d311865c 100644 --- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java @@ -13,9 +13,9 @@ import redis.clients.jedis.Protocol; import redis.clients.jedis.csc.util.AllowAndDenyListWithStringKeys; -public class AllowAndDenyListClientSideCacheTest extends ClientSideCacheTestBase { +public class AllowAndDenyListCacheableTest extends ClientSideCacheTestBase { - private static Cache createTestCache(Map map, ClientSideCacheable cacheable) { + private static Cache createTestCache(Map map, Cacheable cacheable) { Cache mapCache = new TestCache(map, cacheable); return mapCache; } diff --git a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java deleted file mode 100644 index 18a9b28d15..0000000000 --- a/src/test/java/redis/clients/jedis/csc/CaffeineClientSideCacheTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package redis.clients.jedis.csc; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.stats.CacheStats; - -import java.util.concurrent.TimeUnit; -import org.hamcrest.Matchers; -import org.junit.Test; -import redis.clients.jedis.JedisPooled; - -public class CaffeineClientSideCacheTest extends ClientSideCacheTestBase { - - @Test - public void simple() { - CaffeineClientSideCache caffeine = new CaffeineClientSideCache(10); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.del("foo"); - assertEquals(null, jedis.get("foo")); - } - } - - @Test - public void individualCommandsAndThenStats() { - - CaffeineClientSideCache caffeine = new CaffeineClientSideCache(100); - - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine, singleConnectionPoolConfig.get())) { - control.set("foo", "bar"); - assertEquals(0, caffeine.getSize()); - assertEquals("bar", jedis.get("foo")); // cache miss - assertEquals(1, caffeine.getSize()); - control.flushAll(); - assertEquals(1, caffeine.getSize()); - assertEquals(null, jedis.get("foo")); // cache miss - assertEquals(0, caffeine.getSize()); - jedis.ping(); - assertEquals(0, caffeine.getSize()); - assertNull(jedis.get("foo")); // cache miss - assertEquals(0, caffeine.getSize()); - } - - assertEquals(0, caffeine.getStats().getHitCount()); - assertEquals(caffeine.getStats().getMissCount(), 3); - } - - @Test - public void maximumSizeExact() { - control.set("k1", "v1"); - control.set("k2", "v2"); - - 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()); - } - } - - @Test - 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); - } - - 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.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 77e04c5e3b..2779586989 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -1,16 +1,29 @@ package redis.clients.jedis.csc; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.hasSize; 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.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Set; + import java.util.concurrent.ConcurrentHashMap; +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; +import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -21,32 +34,78 @@ public class ClientSideCacheFunctionalityTest extends ClientSideCacheTestBase { - @Test - public void flushEntireCache() { - int count = 100; + @Test // T.5.1 + public void flushAllTest() { + final 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); - JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache); - try { + Cache cache = new TestCache(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { for (int i = 0; i < count; i++) { jedis.get("k" + i); } - assertEquals(count, map.size()); - clientSideCache.flush(); - assertEquals(0, map.size()); - } finally { - jedis.close(); + assertEquals(count, cache.getSize()); + cache.flush(); + assertEquals(0, cache.getSize()); } } - @Test - public void removeSpecificKey() { - int count = 100; + @Test // T.4.1 + public void lruEvictionTest() { + final int count = 100; + final int extra = 10; + + // Add 100 + 10 keys to Redis + for (int i = 0; i < count + extra; i++) { + control.set("key:" + i, "value" + i); + } + + Map map = new LinkedHashMap<>(count); + Cache cache = new DefaultCache(count, map); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { + + // Retrieve the 100 keys in the same order + for (int i = 0; i < count; i++) { + jedis.get("key:" + i); + } + assertThat(map, aMapWithSize(count)); + + List earlierKeys = new ArrayList<>(map.keySet()).subList(0, extra); + // earlier keys in map + earlierKeys.forEach(cacheKey -> assertThat(map, Matchers.hasKey(cacheKey))); + + // Retrieve the 10 extra keys + for (int i = count; i < count + extra; i++) { + jedis.get("key:" + i); + } + + // earlier keys NOT in map + earlierKeys.forEach(cacheKey -> assertThat(map, Matchers.not(Matchers.hasKey(cacheKey)))); + assertThat(map, aMapWithSize(count)); + } + } + + @Test // T.5.2 + public void deleteByKeyUsingMGetTest() { + Cache clientSideCache = new TestCache(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + jedis.set("1", "one"); + jedis.set("2", "two"); + + assertEquals(Arrays.asList("one", "two"), jedis.mget("1", "2")); + assertEquals(1, clientSideCache.getSize()); + + assertThat(clientSideCache.deleteByRedisKey("1"), hasSize(1)); + assertEquals(0, clientSideCache.getSize()); + } + } + + @Test // T.5.2 + public void deleteByKeyTest() { + final int count = 100; for (int i = 0; i < count; i++) { control.set("k" + i, "v" + i); } @@ -54,23 +113,99 @@ 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); - JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache); - try { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { for (int i = 0; i < count; i++) { jedis.get("k" + i); } + assertThat(map, aMapWithSize(count)); - ArrayList commandHashes = new ArrayList<>(map.keySet()); - assertEquals(count, map.size()); + ArrayList cacheKeys = new ArrayList<>(map.keySet()); 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)); + CacheKey cacheKey = cacheKeys.get(i); + assertTrue(map.containsKey(cacheKey)); + assertThat(clientSideCache.deleteByRedisKey(key), hasSize(1)); + assertFalse(map.containsKey(cacheKey)); + assertThat(map, aMapWithSize(count - i - 1)); } - } finally { - jedis.close(); + assertThat(map, aMapWithSize(0)); + } + } + + @Test // T.5.2 + public void deleteByKeysTest() { + final int count = 100; + final int delete = 10; + + for (int i = 0; i < count; i++) { + control.set("k" + i, "v" + i); + } + + // 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)) { + for (int i = 0; i < count; i++) { + jedis.get("k" + i); + } + assertThat(map, aMapWithSize(count)); + + List keysToDelete = new ArrayList<>(delete); + for (int i = 0; i < delete; i++) { + String key = "k" + i; + keysToDelete.add(key); + } + assertThat(clientSideCache.deleteByRedisKeys(keysToDelete), hasSize(delete)); + assertThat(map, aMapWithSize(count - delete)); + } + } + + @Test // T.5.3 + public void deleteByEntryTest() { + final 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)) { + for (int i = 0; i < count; i++) { + jedis.get("k" + i); + } + assertThat(map, aMapWithSize(count)); + + List cacheKeys = new ArrayList<>(map.keySet()); + for (int i = 0; i < count; i++) { + CacheKey cacheKey = cacheKeys.get(i); + assertTrue(clientSideCache.delete(cacheKey)); + assertFalse(map.containsKey(cacheKey)); + assertThat(map, aMapWithSize(count - i - 1)); + } + } + } + + @Test // T.5.3 + public void deleteByEntriesTest() { + final int count = 100; + final int delete = 10; + 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)) { + for (int i = 0; i < count; i++) { + jedis.get("k" + i); + } + assertThat(map, aMapWithSize(count)); + + List cacheKeysToDelete = new ArrayList<>(map.keySet()).subList(0, delete); + List isDeleted = clientSideCache.delete(cacheKeysToDelete); + assertThat(isDeleted, hasSize(delete)); + isDeleted.forEach(Assert::assertTrue); + assertThat(map, aMapWithSize(count - delete)); } } @@ -79,10 +214,10 @@ public void multiKeyOperation() { control.set("k1", "v1"); control.set("k2", "v2"); - HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new TestCache(map))) { + Cache cache = new TestCache(); + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { jedis.mget("k1", "k2"); - assertEquals(1, map.size()); + assertEquals(1, cache.getSize()); } } @@ -134,7 +269,7 @@ public void testInvalidationWithUnifiedJedis() { @Test public void differentInstanceOnEachCacheHit() { - ConcurrentHashMap map = new ConcurrentHashMap(); + ConcurrentHashMap map = new ConcurrentHashMap<>(); TestCache testCache = new TestCache(map); // fill the cache for maxSize @@ -142,7 +277,7 @@ public void differentInstanceOnEachCacheHit() { jedis.sadd("foo", "a"); jedis.sadd("foo", "b"); - Set expected = new HashSet(); + Set expected = new HashSet<>(); expected.add("a"); expected.add("b"); @@ -158,4 +293,215 @@ public void differentInstanceOnEachCacheHit() { assertTrue(members1 != fromMap); } } + + @Test + public void testSequentialAccess() throws InterruptedException { + int threadCount = 10; + int iterations = 10000; + + control.set("foo", "0"); + + ReentrantLock lock = new ReentrantLock(true); + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { + // Submit multiple threads to perform concurrent operations + CountDownLatch latch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + for (int j = 0; j < iterations; j++) { + 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("OK", 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 + String finalValue = control.get("foo"); + assertEquals(threadCount * iterations, Integer.parseInt(finalValue)); + } + + @Test + public void testConcurrentAccessWithStats() throws InterruptedException { + int threadCount = 10; + int iterations = 10000; + + control.set("foo", "0"); + + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + + // Create the shared mock instance of cache + TestCache testCache = new TestCache(); + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + // Submit multiple threads to perform concurrent operations + CountDownLatch latch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + 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, DefaultCacheable.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; + + HashMap map = new HashMap<>(); + TestCache testCache = new TestCache(maxSize, map, DefaultCacheable.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 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, 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 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()); + } + } + + @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<>(), DefaultCacheable.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(); + } + + assertEquals(MAX_SIZE, cache.getSize()); + assertEquals(0, exceptions.size()); + } + } + } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java index 4bde3be30d..db53b085be 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheTestBase.java @@ -15,7 +15,7 @@ abstract class ClientSideCacheTestBase { - private static final EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone1"); + protected static final EndpointConfig endpoint = HostAndPorts.getRedisEndpoint("standalone1"); protected static final HostAndPort hnp = endpoint.getHostAndPort(); diff --git a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java deleted file mode 100644 index fad64df8ab..0000000000 --- a/src/test/java/redis/clients/jedis/csc/GuavaClientSideCacheTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package redis.clients.jedis.csc; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.util.concurrent.TimeUnit; -import org.hamcrest.Matchers; -import org.junit.Test; -import redis.clients.jedis.JedisPooled; - -public class GuavaClientSideCacheTest extends ClientSideCacheTestBase { - - @Test - public void simple() { - GuavaClientSideCache guava = new GuavaClientSideCache(10); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.del("foo"); - assertEquals(null, jedis.get("foo")); - } - } - - @Test - public void individualCommandsAndThenStats() { - - GuavaClientSideCache guava = new GuavaClientSideCache(10000); - - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava, - singleConnectionPoolConfig.get())) { - control.set("foo", "bar"); - assertEquals(0, guava.getSize()); - assertEquals("bar", jedis.get("foo")); // cache miss - assertEquals(1, guava.getSize()); - control.flushAll(); - assertEquals(1, guava.getSize()); - assertEquals(null, jedis.get("foo")); // cache miss - assertEquals(0, guava.getSize()); - jedis.ping(); - assertEquals(0, guava.getSize()); - assertNull(jedis.get("foo")); // cache miss - assertEquals(0, guava.getSize()); - } - - assertEquals(0, guava.getStats().getHitCount()); - assertEquals(guava.getStats().getMissCount(), 3); - } - - @Test - public void maximumSizeExact() { - control.set("k1", "v1"); - control.set("k2", "v2"); - - GuavaClientSideCache guava = new GuavaClientSideCache(1); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), guava)) { - assertEquals(0, guava.getSize()); - jedis.get("k1"); - assertEquals(1, guava.getSize()); - assertEquals(0, guava.getStats().getEvictCount()); - jedis.get("k2"); - assertEquals(1, guava.getSize()); - assertEquals(1, guava.getStats().getEvictCount()); - } - } - - @Test - public void maximumSize() { - final int maxSize = 10; - final int maxEstimatedSize = 40; - int count = 1000; - for (int i = 0; i < count; i++) { - control.set("k" + i, "v" + i); - } - - 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.getStats().getEvictCount(), Matchers.greaterThan((long) count - maxEstimatedSize)); - } - -} diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index 6434bf1111..aca94a545c 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -1,19 +1,10 @@ package redis.clients.jedis.csc; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; import redis.clients.jedis.Connection; import redis.clients.jedis.ConnectionPoolConfig; @@ -23,7 +14,7 @@ import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisCluster; -public class JedisClusterClientSideCacheTest { +public class JedisClusterClientSideCacheTest extends UnifiedJedisClientSideCacheTestBase { private static final Set hnp = new HashSet<>(HostAndPorts.getStableClusterServers()); @@ -37,76 +28,14 @@ public class JedisClusterClientSideCacheTest { return poolConfig; }; - protected JedisCluster control; - - @Before - public void setUp() throws Exception { - control = new JedisCluster(hnp, clientConfig.get()); - control.flushAll(); + @Override + protected JedisCluster createRegularJedis() { + return new JedisCluster(hnp, clientConfig.get()); } - @After - public void tearDown() throws Exception { - control.close(); + @Override + protected JedisCluster createCachedJedis(Cache cache) { + return new JedisCluster(hnp, clientConfig.get(), cache); } - @Test - public void simple() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache())) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.del("foo"); - assertEquals(null, jedis.get("foo")); - } - } - - @Test - public void simpleWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache(map), - singleConnectionPoolConfig.get())) { - control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); - control.del("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")); - assertThat(map, Matchers.aMapWithSize(0)); - } - } - - @Test - public void flushAll() { - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache())) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.flushAll(); - assertEquals(null, jedis.get("foo")); - } - } - - @Test - public void flushAllWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisCluster jedis = new JedisCluster(hnp, clientConfig.get(), new TestCache(map), - singleConnectionPoolConfig.get())) { - control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); - control.flushAll(); - 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")); - 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 de2e3e8144..d7b2bd4989 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTest.java @@ -1,387 +1,13 @@ package redis.clients.jedis.csc; -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.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; -import org.junit.After; -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.locationtech.jts.util.Assert; - -import redis.clients.jedis.CommandObjects; -import redis.clients.jedis.Connection; -import redis.clients.jedis.ConnectionPoolConfig; -import redis.clients.jedis.EndpointConfig; import redis.clients.jedis.HostAndPorts; -import redis.clients.jedis.Jedis; -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.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; - -@RunWith(Parameterized.class) -public class JedisPooledClientSideCacheTest { - - private EndpointConfig endpoint; - - protected Jedis control; - - protected static final EndpointConfig sslEndpoint = HostAndPorts.getRedisEndpoint("standalone0-tls"); - - 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") }, - }); - } +public class JedisPooledClientSideCacheTest extends JedisPooledClientSideCacheTestBase { @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(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build()); - control.flushAll(); - } - - @After - public void tearDown() throws Exception { - control.close(); - } - - private final Supplier clientConfig = () -> endpoint.getClientConfigBuilder().resp3().build(); - - private final Supplier> singleConnectionPoolConfig = () -> { - ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); - poolConfig.setMaxTotal(1); - 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"); - sleep(); - assertNull(jedis.get("foo")); - } - } - - @Test - public void simpleWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache(map), - singleConnectionPoolConfig.get())) { - control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); - control.del("foo"); - assertThat(map, Matchers.aMapWithSize(1)); - sleep(); - assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); - sleep(); - assertThat(map, Matchers.aMapWithSize(0)); - sleep(); - assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); - } - } - - @Test - public void flushAll() { - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.flushAll(); - sleep(); - assertNull(jedis.get("foo")); - } - } - - @Test - public void flushAllWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache(map), - singleConnectionPoolConfig.get())) { - control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); - control.flushAll(); - assertThat(map, Matchers.aMapWithSize(1)); - sleep(); - assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); - sleep(); - assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); - } - } - - @Test - public void testSequentialAccess() throws InterruptedException { - int threadCount = 10; - int iterations = 10000; - - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get())) { - jedis.set("foo", "0"); - } - - ReentrantLock lock = new ReentrantLock(true); - 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++) { - 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("OK", 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 - String finalValue = control.get("foo"); - assertEquals(threadCount * iterations, Integer.parseInt(finalValue)); - - } - - @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; - - HashMap map = new HashMap(); - 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 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, 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 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()); - } - } - - @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()); - } + endpoint = HostAndPorts.getRedisEndpoint("standalone1"); } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java new file mode 100644 index 0000000000..7becbe74d3 --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java @@ -0,0 +1,57 @@ +package redis.clients.jedis.csc; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import redis.clients.jedis.EndpointConfig; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.args.ClientType; +import redis.clients.jedis.exceptions.JedisConnectionException; +import redis.clients.jedis.params.ClientKillParams; + +public abstract class JedisPooledClientSideCacheTestBase extends UnifiedJedisClientSideCacheTestBase { + + protected static EndpointConfig endpoint; + + @Override + protected JedisPooled createRegularJedis() { + return new JedisPooled(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build()); + } + + @Override + protected JedisPooled createCachedJedis(Cache cache) { + return new JedisPooled(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().resp3().build(), cache); + } + + @Test + public void clearIfOneDiesTest() { + Cache cache = new TestCache(); + try (JedisPooled jedis = createCachedJedis(cache)) { + + // Create 100 keys + for (int i = 0; i < 100; i++) { + jedis.set("key" + i, "value" + i); + } + assertEquals(0, cache.getSize()); + + // Get 100 keys into the cache + for (int i = 0; i < 100; i++) { + jedis.get("key" + i); + } + assertEquals(100, cache.getSize()); + + try (Jedis killer = new Jedis(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build())) { + killer.clientKill(ClientKillParams.clientKillParams().type(ClientType.NORMAL).skipMe(ClientKillParams.SkipMe.YES)); + } + + try { + jedis.get("foo"); + } catch (JedisConnectionException jce) { + // expected + } + assertEquals(0, cache.getSize()); + } + } +} diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index 13130416ce..578ed6a316 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -1,26 +1,16 @@ package redis.clients.jedis.csc; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.Set; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPorts; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisSentineled; -public class JedisSentineledClientSideCacheTest { +public class JedisSentineledClientSideCacheTest extends UnifiedJedisClientSideCacheTestBase { private static final String MASTER_NAME = "mymaster"; @@ -33,78 +23,14 @@ public class JedisSentineledClientSideCacheTest { private static final JedisClientConfig sentinelClientConfig = DefaultJedisClientConfig.builder().resp3().build(); - protected JedisSentineled control; - - @Before - public void setUp() throws Exception { - control = new JedisSentineled(MASTER_NAME, masterClientConfig, sentinels, sentinelClientConfig); - control.flushAll(); - } - - @After - public void tearDown() throws Exception { - control.close(); + @Override + protected JedisSentineled createRegularJedis() { + return new JedisSentineled(MASTER_NAME, masterClientConfig, sentinels, sentinelClientConfig); } - @Test - public void simple() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(), - sentinels, sentinelClientConfig)) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.del("foo"); - assertEquals(null, jedis.get("foo")); - } + @Override + protected JedisSentineled createCachedJedis(Cache cache) { + return new JedisSentineled(MASTER_NAME, masterClientConfig, cache, sentinels, sentinelClientConfig); } - @Test - public void simpleWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(map), - sentinels, sentinelClientConfig)) { - control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); - control.del("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")); - assertThat(map, Matchers.aMapWithSize(0)); - } - } - - @Test - public void flushAll() { - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(), - sentinels, sentinelClientConfig)) { - control.set("foo", "bar"); - assertEquals("bar", jedis.get("foo")); - control.flushAll(); - assertEquals(null, jedis.get("foo")); - } - } - - @Test - public void flushAllWithSimpleMap() { - HashMap map = new HashMap<>(); - try (JedisSentineled jedis = new JedisSentineled(MASTER_NAME, masterClientConfig, new TestCache(map), - sentinels, sentinelClientConfig)) { - control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); - assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); - control.flushAll(); - 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")); - assertThat(map, Matchers.aMapWithSize(0)); - } - } } diff --git a/src/test/java/redis/clients/jedis/csc/SSLJedisPooledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/SSLJedisPooledClientSideCacheTest.java new file mode 100644 index 0000000000..b8df3910cd --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/SSLJedisPooledClientSideCacheTest.java @@ -0,0 +1,16 @@ +package redis.clients.jedis.csc; + +import org.junit.BeforeClass; +import redis.clients.jedis.HostAndPorts; +import redis.clients.jedis.SSLJedisTest; + +public class SSLJedisPooledClientSideCacheTest extends JedisPooledClientSideCacheTestBase { + + @BeforeClass + public static void prepare() { + SSLJedisTest.setupTrustStore(); + + endpoint = HostAndPorts.getRedisEndpoint("standalone0-tls"); + } + +} diff --git a/src/test/java/redis/clients/jedis/csc/TestCache.java b/src/test/java/redis/clients/jedis/csc/TestCache.java index fd90b1229b..ebebe03051 100644 --- a/src/test/java/redis/clients/jedis/csc/TestCache.java +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -13,16 +13,15 @@ public TestCache(Map map) { super(10000, map); } - public TestCache(Map map, ClientSideCacheable cacheable) { - + public TestCache(Map map, Cacheable cacheable) { super(10000, map, cacheable, new LRUEviction(10000)); } - public TestCache(int maxSize, Map map, ClientSideCacheable cacheable) { + public TestCache(int maxSize, Map map, Cacheable cacheable) { this(maxSize, map, cacheable, new LRUEviction(maxSize)); } - public TestCache(int maxSize, Map map, ClientSideCacheable cacheable, + public TestCache(int maxSize, Map map, Cacheable cacheable, EvictionPolicy evictionPolicy) { super(maxSize, map, cacheable, evictionPolicy); } diff --git a/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java new file mode 100644 index 0000000000..c72f297fc2 --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java @@ -0,0 +1,226 @@ +package redis.clients.jedis.csc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import redis.clients.jedis.UnifiedJedis; + +public abstract class UnifiedJedisClientSideCacheTestBase { + + protected UnifiedJedis control; + + protected abstract UnifiedJedis createRegularJedis(); + + protected abstract UnifiedJedis createCachedJedis(Cache cache); + + @Before + public void setUp() throws Exception { + control = createRegularJedis(); + control.flushAll(); + } + + @After + public void tearDown() throws Exception { + control.close(); + } + + @Test + public void simple() { + try (UnifiedJedis jedis = createCachedJedis(new TestCache())) { + control.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + control.del("foo"); + assertNull(jedis.get("foo")); + } + } + + @Test + public void simpleWithSimpleMap() { + HashMap map = new HashMap<>(); + try (UnifiedJedis jedis = createCachedJedis(new TestCache(map))) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + control.del("foo"); + assertThat(map, Matchers.aMapWithSize(1)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } + + @Test + public void flushAll() { + try (UnifiedJedis jedis = createCachedJedis(new TestCache())) { + control.set("foo", "bar"); + assertEquals("bar", jedis.get("foo")); + control.flushAll(); + assertNull(jedis.get("foo")); + } + } + + @Test + public void flushAllWithSimpleMap() { + HashMap map = new HashMap<>(); + try (UnifiedJedis jedis = createCachedJedis(new TestCache(map))) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + control.flushAll(); + assertThat(map, Matchers.aMapWithSize(1)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + assertNull(jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(0)); + } + } + + @Test + public void cacheNotEmptyTest() { + HashMap map = new HashMap<>(); + try (UnifiedJedis jedis = createCachedJedis(new TestCache(map))) { + control.set("foo", "bar"); + assertThat(map, Matchers.aMapWithSize(0)); + assertEquals("bar", jedis.get("foo")); + assertThat(map, Matchers.aMapWithSize(1)); + } + } + + @Test + public void cacheUsedTest() { + HashMap map = new HashMap<>(); + Cache cache = new TestCache(map); + try (UnifiedJedis jedis = createCachedJedis(cache)) { + control.set("foo", "bar"); + + assertEquals(0, cache.getStats().getMissCount()); + assertEquals(0, cache.getStats().getHitCount()); + + assertEquals("bar", jedis.get("foo")); + assertEquals(1, cache.getStats().getMissCount()); + assertEquals(0, cache.getStats().getHitCount()); + + assertEquals("bar", jedis.get("foo")); + assertEquals(1, cache.getStats().getMissCount()); + assertEquals(1, cache.getStats().getHitCount()); + } + } + + @Test + public void immutableCacheEntriesTest() { + try (UnifiedJedis jedis = createCachedJedis(new TestCache())) { + jedis.set("{csc}a", "AA"); + jedis.set("{csc}b", "BB"); + jedis.set("{csc}c", "CC"); + + List expected = Arrays.asList("AA", "BB", "CC"); + + List reply1 = jedis.mget("{csc}a", "{csc}b", "{csc}c"); + List reply2 = jedis.mget("{csc}a", "{csc}b", "{csc}c"); + + assertEquals(expected, reply1); + assertEquals(expected, reply2); + assertEquals(reply1, reply2); + assertNotSame(reply1, reply2); + } + } + + @Test + public void invalidationTest() { + Cache cache = new TestCache(); + try (UnifiedJedis jedis = createCachedJedis(cache)) { + jedis.set("{csc}1", "one"); + jedis.set("{csc}2", "two"); + jedis.set("{csc}3", "three"); + + assertEquals(0, cache.getSize()); + assertEquals(0, cache.getStats().getInvalidationCount()); + + List reply1 = jedis.mget("{csc}1", "{csc}2", "{csc}3"); + assertEquals(Arrays.asList("one", "two", "three"), reply1); + assertEquals(1, cache.getSize()); + assertEquals(0, cache.getStats().getInvalidationCount()); + + jedis.set("{csc}1", "new-one"); + List reply2 = jedis.mget("{csc}1", "{csc}2", "{csc}3"); + assertEquals(Arrays.asList("new-one", "two", "three"), reply2); + + assertEquals(1, cache.getSize()); + assertEquals(1, cache.getStats().getInvalidationCount()); + } + } + + @Test + public void getNumEntriesTest() { + Cache cache = new TestCache(); + try (UnifiedJedis jedis = createCachedJedis(cache)) { + + // Create 100 keys + for (int i = 0; i < 100; i++) { + jedis.set("key" + i, "value" + i); + } + assertEquals(0, cache.getSize()); + + // Get 100 keys into the cache + for (int i = 0; i < 100; i++) { + jedis.get("key" + i); + } + assertEquals(100, cache.getSize()); + } + } + + @Test + public void invalidationOnCacheHitTest() { + Cache cache = new TestCache(); + try (UnifiedJedis jedis = createCachedJedis(cache)) { + + // Create 100 keys + for (int i = 0; i < 100; i++) { + jedis.set("key" + i, "value" + i); + } + assertEquals(0, cache.getSize()); + + // Get 100 keys into the cache + for (int i = 0; i < 100; i++) { + jedis.get("key" + i); + } + assertEquals(100, cache.getSize()); + + assertEquals(100, cache.getStats().getLoadCount()); + assertEquals(0, cache.getStats().getInvalidationCount()); + + // Change 50 of the 100 keys + for (int i = 1; i < 100; i += 2) { + jedis.set("key" + i, "val" + i); + } + + assertEquals(100, cache.getStats().getLoadCount()); + // invalidation count is anything between 0 and 50 + + // Get the 100 keys again + for (int i = 0; i < 100; i++) { + jedis.get("key" + i); + } + assertEquals(100, cache.getSize()); + + assertEquals(150, cache.getStats().getLoadCount()); + assertEquals(50, cache.getStats().getInvalidationCount()); + } + } + +} From 67073275504bb0708da6022e9c67985a1a159b34 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:10:42 +0600 Subject: [PATCH 42/48] Use ExecutorService.shutdownNow() in tests (#3922) * Use ExecutorService.shutdownNow() * More ExecutorService.shutdownNow() and other changes --- .../csc/ClientSideCacheFunctionalityTest.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index 2779586989..ac3d29e264 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -331,6 +331,8 @@ public void testSequentialAccess() throws InterruptedException { latch.await(); } + executorService.shutdownNow(); + // Verify the final value of "foo" in Redis String finalValue = control.get("foo"); assertEquals(threadCount * iterations, Integer.parseInt(finalValue)); @@ -368,6 +370,8 @@ public void testConcurrentAccessWithStats() throws InterruptedException { latch.await(); } + executorService.shutdownNow(); + CacheStats stats = testCache.getStats(); assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount()); assertEquals(stats.getMissCount(), stats.getLoadCount()); @@ -385,26 +389,28 @@ public void testMaxSize() throws InterruptedException { // Create the shared mock instance of cache TestCache testCache = new TestCache(maxSize, map, DefaultCacheable.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); + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + // Submit multiple threads to perform concurrent operations + CountDownLatch latch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + 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); + } + } finally { + latch.countDown(); } - } catch (Exception e) { - e.printStackTrace(); - } finally { - latch.countDown(); - } - }); + }); + } + + // wait for all threads to complete + latch.await(); } - // wait for all threads to complete - latch.await(); + executorService.shutdownNow(); CacheStats stats = testCache.getStats(); From eb7e9fe7303cdba6de3d99a294c04112317a9059 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Sun, 25 Aug 2024 19:46:34 +0600 Subject: [PATCH 43/48] [minor change] Avoid creating same CacheKey twice --- .../redis/clients/jedis/csc/CacheConnection.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index a6afe9cea2..91d4813a59 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -62,25 +62,23 @@ public void disconnect() { @Override public T executeCommand(final CommandObject commandObject) { - CacheKey key = new CacheKey<>(commandObject); - if (!clientSideCache.isCacheable(key)) { + final CacheKey cacheKey = new CacheKey(commandObject); + if (!clientSideCache.isCacheable(cacheKey)) { clientSideCache.getStats().nonCacheable(); return super.executeCommand(commandObject); } - final CacheKey cacheKey = new CacheKey(commandObject); CacheEntry cacheEntry = clientSideCache.get(cacheKey); - - // CACHE HIT !! - if (cacheEntry != null) { + if (cacheEntry != null) { // (probable) CACHE HIT !! cacheEntry = validateEntry(cacheEntry); if (cacheEntry != null) { + // CACHE HIT confirmed !!! clientSideCache.getStats().hit(); - return (T) cacheEntry.getValue(); + return cacheEntry.getValue(); } } - // CACHE MISS!! + // CACHE MISS !! clientSideCache.getStats().miss(); T value = super.executeCommand(commandObject); if (value != null) { From e96e2e3ad6f5fab64492864772f802279a7c938f Mon Sep 17 00:00:00 2001 From: atakavci <58048133+atakavci@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:18:48 +0300 Subject: [PATCH 44/48] Support caching null values (#3939) * caching null results * add more assertion --- .../clients/jedis/csc/CacheConnection.java | 10 +++--- .../csc/ClientSideCacheFunctionalityTest.java | 35 +++++++++++++++++++ .../UnifiedJedisClientSideCacheTestBase.java | 9 +++-- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 91d4813a59..316b0ddd4d 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -81,12 +81,10 @@ public T executeCommand(final CommandObject commandObject) { // CACHE MISS !! clientSideCache.getStats().miss(); T value = super.executeCommand(commandObject); - if (value != null) { - cacheEntry = new CacheEntry<>(cacheKey, value, this); - clientSideCache.set(cacheKey, cacheEntry); - // this line actually provides a deep copy of cached object instance - value = cacheEntry.getValue(); - } + cacheEntry = new CacheEntry<>(cacheKey, value, this); + clientSideCache.set(cacheKey, cacheEntry); + // this line actually provides a deep copy of cached object instance + value = cacheEntry.getValue(); return value; } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index ac3d29e264..ab5ac2761b 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -5,6 +5,7 @@ import static org.hamcrest.Matchers.hasSize; 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.util.ArrayList; @@ -510,4 +511,38 @@ public void run() { } } + @Test + public void testNullValue() throws InterruptedException { + int MAX_SIZE = 20; + String nonExisting = "non-existing-key"; + control.del(nonExisting); + + TestCache cache = new TestCache(MAX_SIZE, new HashMap<>(), DefaultCacheable.INSTANCE); + + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { + CacheStats stats = cache.getStats(); + + String val = jedis.get(nonExisting); + assertNull(val); + assertEquals(1, cache.getSize()); + assertEquals(0, stats.getHitCount()); + assertEquals(1, stats.getMissCount()); + + val = jedis.get(nonExisting); + assertNull(val); + assertEquals(1, cache.getSize()); + assertNull(cache.getCacheEntries().iterator().next().getValue()); + assertEquals(1, stats.getHitCount()); + assertEquals(1, stats.getMissCount()); + + control.set(nonExisting, "bar"); + val = jedis.get(nonExisting); + assertEquals("bar", val); + assertEquals(1, cache.getSize()); + assertEquals("bar", cache.getCacheEntries().iterator().next().getValue()); + assertEquals(1, stats.getHitCount()); + assertEquals(2, stats.getMissCount()); + } + } + } diff --git a/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java index c72f297fc2..771e08d5df 100644 --- a/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java @@ -56,10 +56,9 @@ public void simpleWithSimpleMap() { control.del("foo"); assertThat(map, Matchers.aMapWithSize(1)); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); - assertThat(map, Matchers.aMapWithSize(0)); + assertThat(map, Matchers.aMapWithSize(1)); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); + assertThat(map, Matchers.aMapWithSize(1)); } } @@ -84,9 +83,9 @@ public void flushAllWithSimpleMap() { control.flushAll(); assertThat(map, Matchers.aMapWithSize(1)); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); + assertThat(map, Matchers.aMapWithSize(1)); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); + assertThat(map, Matchers.aMapWithSize(1)); } } From 88cf9bc2eb611b6a4bef7199eb11744974e69808 Mon Sep 17 00:00:00 2001 From: atakavci <58048133+atakavci@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:06:55 +0300 Subject: [PATCH 45/48] Adding CacheConfig (#3919) * add cacheconfig * remove empty file * -modify constructors with cache as public - trim guava caffeine * remove cachetype * - add getCache to UnifiedJedis - add builder method to CacheConfig * add evictionpolicy to cacheconfig * - unifiedjedis constructor with cacheconfig - wrap IOException on protocol read error * fix merge issue --------- Co-authored-by: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> --- .../redis/clients/jedis/JedisCluster.java | 10 +++- .../java/redis/clients/jedis/JedisPooled.java | 13 +++++ .../redis/clients/jedis/JedisSentineled.java | 9 ++++ .../java/redis/clients/jedis/Protocol.java | 4 +- .../redis/clients/jedis/UnifiedJedis.java | 38 +++++++++---- .../redis/clients/jedis/csc/CacheConfig.java | 53 +++++++++++++++++++ .../clients/jedis/csc/CacheConnection.java | 34 ++++++------ .../clients/jedis/csc/CacheProvider.java | 23 ++++++++ .../redis/clients/jedis/csc/DefaultCache.java | 8 +-- .../csc/ClientSideCacheFunctionalityTest.java | 4 +- .../csc/JedisClusterClientSideCacheTest.java | 5 ++ .../JedisPooledClientSideCacheTestBase.java | 5 ++ .../JedisSentineledClientSideCacheTest.java | 5 ++ .../redis/clients/jedis/csc/TestCache.java | 8 +-- .../UnifiedJedisClientSideCacheTestBase.java | 8 ++- 15 files changed, 186 insertions(+), 41 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/CacheConfig.java create mode 100644 src/main/java/redis/clients/jedis/csc/CacheProvider.java diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index 423b1682cb..4988b5ee2f 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -11,6 +11,8 @@ import redis.clients.jedis.executors.ClusterCommandExecutor; import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.csc.Cache; +import redis.clients.jedis.csc.CacheConfig; +import redis.clients.jedis.csc.CacheProvider; import redis.clients.jedis.util.JedisClusterCRC16; public class JedisCluster extends UnifiedJedis { @@ -225,10 +227,16 @@ public JedisCluster(Set clusterNodes, JedisClientConfig clientConfi Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis())); } + @Experimental + public JedisCluster(Set hnp, JedisClientConfig jedisClientConfig, CacheConfig cacheConfig) { + this(hnp, jedisClientConfig, new CacheProvider().getCache(cacheConfig)); + } + @Experimental public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache, int maxAttempts, Duration maxTotalRetriesDuration) { - this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache), maxAttempts, maxTotalRetriesDuration, + this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache), maxAttempts, + maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache); } diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 498bcb02c8..7eb67374ae 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -9,6 +9,8 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.csc.Cache; +import redis.clients.jedis.csc.CacheConfig; +import redis.clients.jedis.csc.CacheProvider; import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; @@ -82,6 +84,11 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client super(hostAndPort, clientConfig, clientSideCache); } + @Experimental + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig) { + this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); + } + public JedisPooled(PooledObjectFactory factory) { this(new PooledConnectionProvider(factory)); } @@ -389,6 +396,12 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client clientConfig.getRedisProtocol(), clientSideCache); } + @Experimental + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig, + final GenericObjectPoolConfig poolConfig) { + this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig), poolConfig); + } + public JedisPooled(final GenericObjectPoolConfig poolConfig, final JedisSocketFactory jedisSocketFactory, final JedisClientConfig clientConfig) { super(new PooledConnectionProvider(new ConnectionFactory(jedisSocketFactory, clientConfig), poolConfig), diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index 35585f713f..da9eba4baf 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -4,6 +4,8 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.csc.Cache; +import redis.clients.jedis.csc.CacheConfig; +import redis.clients.jedis.csc.CacheProvider; import redis.clients.jedis.providers.SentineledConnectionProvider; public class JedisSentineled extends UnifiedJedis { @@ -21,6 +23,13 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache); } + @Experimental + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, CacheConfig cacheConfig, + Set sentinels, final JedisClientConfig sentinelClientConfig) { + this(masterName, masterClientConfig, new CacheProvider().getCache(cacheConfig), + sentinels, sentinelClientConfig); + } + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, final GenericObjectPoolConfig poolConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index f4cd20489a..f8bb5bfb8b 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -235,10 +235,8 @@ public static void readPushes(final RedisInputStream is, final Cache cache, bool is.readByte(); processPush(is, cache); } - } catch (JedisConnectionException e) { - // TODO handle it properly } catch (IOException e) { - // TODO handle it properly + throw new JedisConnectionException("Failed to read pending buffer for push messages !", e); } } else { while (is.peek(GREATER_THAN_BYTE)) { diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index e9b01d1426..679cd8eb78 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -20,6 +20,9 @@ import redis.clients.jedis.commands.SampleKeyedCommands; import redis.clients.jedis.commands.RedisModuleCommands; import redis.clients.jedis.csc.Cache; +import redis.clients.jedis.csc.CacheConfig; +import redis.clients.jedis.csc.CacheConnection; +import redis.clients.jedis.csc.CacheProvider; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.executors.*; import redis.clients.jedis.gears.TFunctionListParams; @@ -58,6 +61,7 @@ public class UnifiedJedis implements JedisCommands, JedisBinaryCommands, protected final CommandObjects commandObjects; private final GraphCommandObjects graphCommandObjects; private JedisBroadcastAndRoundRobinConfig broadcastAndRoundRobinConfig = null; + private final Cache cache; public UnifiedJedis() { this(new HostAndPort(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT)); @@ -95,9 +99,14 @@ public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) { } @Experimental - public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache clientSideCache) { - this(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache), clientConfig.getRedisProtocol(), - clientSideCache); + public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache cache) { + this(new PooledConnectionProvider(hostAndPort, clientConfig, cache), clientConfig.getRedisProtocol(), + cache); + } + + @Experimental + public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, CacheConfig cacheConfig) { + this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); } public UnifiedJedis(ConnectionProvider provider) { @@ -109,8 +118,8 @@ protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol) { } @Experimental - protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, Cache clientSideCache) { - this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol, clientSideCache); + protected UnifiedJedis(ConnectionProvider provider, RedisProtocol protocol, Cache cache) { + this(new DefaultCommandExecutor(provider), provider, new CommandObjects(), protocol, cache); } /** @@ -147,6 +156,11 @@ public UnifiedJedis(Connection connection) { if (proto != null) this.commandObjects.setProtocol(proto); this.graphCommandObjects = new GraphCommandObjects(this); + if (connection instanceof CacheConnection) { + this.cache = ((CacheConnection) connection).getCache(); + } else { + this.cache = null; + } } @Deprecated @@ -183,9 +197,9 @@ protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Dura @Experimental protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, - RedisProtocol protocol, Cache clientSideCache) { + RedisProtocol protocol, Cache cache) { this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, - new ClusterCommandObjects(), protocol, clientSideCache); + new ClusterCommandObjects(), protocol, cache); } /** @@ -259,9 +273,9 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comm @Experimental private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, CommandObjects commandObjects, - RedisProtocol protocol, Cache clientSideCache) { + RedisProtocol protocol, Cache cache) { - if (clientSideCache != null && protocol != RedisProtocol.RESP3) { + if (cache != null && protocol != RedisProtocol.RESP3) { throw new IllegalArgumentException("Client-side caching is only supported with RESP3."); } @@ -274,7 +288,7 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comm this.graphCommandObjects = new GraphCommandObjects(this); this.graphCommandObjects.setBaseCommandArgumentsCreator((comm) -> this.commandObjects.commandArguments(comm)); - + this.cache = cache; } @Override @@ -314,6 +328,10 @@ public void setBroadcastAndRoundRobinConfig(JedisBroadcastAndRoundRobinConfig co this.commandObjects.setBroadcastAndRoundRobinConfig(this.broadcastAndRoundRobinConfig); } + public Cache getCache() { + return cache; + } + public String ping() { return checkAndBroadcastCommand(commandObjects.ping()); } diff --git a/src/main/java/redis/clients/jedis/csc/CacheConfig.java b/src/main/java/redis/clients/jedis/csc/CacheConfig.java new file mode 100644 index 0000000000..2c782a4a7c --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/CacheConfig.java @@ -0,0 +1,53 @@ +package redis.clients.jedis.csc; + +public class CacheConfig { + + private int maxSize; + private Cacheable cacheable; + private EvictionPolicy evictionPolicy; + + public int getMaxSize() { + return maxSize; + } + + public Cacheable getCacheable() { + return cacheable; + } + + public EvictionPolicy getEvictionPolicy() { + return evictionPolicy; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private int maxSize; + private Cacheable cacheable = DefaultCacheable.INSTANCE; + private EvictionPolicy evictionPolicy; + + public Builder maxSize(int maxSize) { + this.maxSize = maxSize; + return this; + } + + public Builder evictionPolicy(EvictionPolicy policy) { + this.evictionPolicy = policy; + return this; + } + + public Builder cacheable(Cacheable cacheable) { + this.cacheable = cacheable; + return this; + } + + public CacheConfig build() { + CacheConfig cacheConfig = new CacheConfig(); + cacheConfig.maxSize = this.maxSize; + cacheConfig.cacheable = this.cacheable; + cacheConfig.evictionPolicy = this.evictionPolicy; + return cacheConfig; + } + } +} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index 316b0ddd4d..c19dd319ec 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -14,16 +14,16 @@ public class CacheConnection extends Connection { - private final Cache clientSideCache; + private final Cache cache; private ReentrantLock lock; - public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, Cache clientSideCache) { + public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, Cache cache) { super(socketFactory, clientConfig); if (protocol != RedisProtocol.RESP3) { throw new JedisException("Client side caching is only supported with RESP3."); } - this.clientSideCache = Objects.requireNonNull(clientSideCache); + this.cache = Objects.requireNonNull(cache); initializeClientSideCache(); } @@ -37,7 +37,7 @@ protected void initializeFromClientConfig(JedisClientConfig config) { protected Object protocolRead(RedisInputStream inputStream) { lock.lock(); try { - return Protocol.read(inputStream, clientSideCache); + return Protocol.read(inputStream, cache); } finally { lock.unlock(); } @@ -47,7 +47,7 @@ protected Object protocolRead(RedisInputStream inputStream) { protected void protocolReadPushes(RedisInputStream inputStream) { if (lock.tryLock()) { try { - Protocol.readPushes(inputStream, clientSideCache, true); + Protocol.readPushes(inputStream, cache, true); } finally { lock.unlock(); } @@ -57,37 +57,41 @@ protected void protocolReadPushes(RedisInputStream inputStream) { @Override public void disconnect() { super.disconnect(); - clientSideCache.flush(); + cache.flush(); } @Override public T executeCommand(final CommandObject commandObject) { final CacheKey cacheKey = new CacheKey(commandObject); - if (!clientSideCache.isCacheable(cacheKey)) { - clientSideCache.getStats().nonCacheable(); + if (!cache.isCacheable(cacheKey)) { + cache.getStats().nonCacheable(); return super.executeCommand(commandObject); } - CacheEntry cacheEntry = clientSideCache.get(cacheKey); + CacheEntry cacheEntry = cache.get(cacheKey); if (cacheEntry != null) { // (probable) CACHE HIT !! cacheEntry = validateEntry(cacheEntry); if (cacheEntry != null) { // CACHE HIT confirmed !!! - clientSideCache.getStats().hit(); + cache.getStats().hit(); return cacheEntry.getValue(); } } // CACHE MISS !! - clientSideCache.getStats().miss(); + cache.getStats().miss(); T value = super.executeCommand(commandObject); cacheEntry = new CacheEntry<>(cacheKey, value, this); - clientSideCache.set(cacheKey, cacheEntry); + cache.set(cacheKey, cacheEntry); // this line actually provides a deep copy of cached object instance value = cacheEntry.getValue(); return value; } + public Cache getCache() { + return cache; + } + private void initializeClientSideCache() { sendCommand(Protocol.Command.CLIENT, "TRACKING", "ON"); String reply = getStatusCodeReply(); @@ -99,17 +103,17 @@ private void initializeClientSideCache() { private CacheEntry validateEntry(CacheEntry cacheEntry) { CacheConnection cacheOwner = cacheEntry.getConnection(); if (cacheOwner == null || cacheOwner.isBroken() || !cacheOwner.isConnected()) { - clientSideCache.delete(cacheEntry.getCacheKey()); + cache.delete(cacheEntry.getCacheKey()); return null; } else { try { cacheOwner.readPushesWithCheckingBroken(); } catch (JedisException e) { - clientSideCache.delete(cacheEntry.getCacheKey()); + cache.delete(cacheEntry.getCacheKey()); return null; } - return clientSideCache.get(cacheEntry.getCacheKey()); + return cache.get(cacheEntry.getCacheKey()); } } } diff --git a/src/main/java/redis/clients/jedis/csc/CacheProvider.java b/src/main/java/redis/clients/jedis/csc/CacheProvider.java new file mode 100644 index 0000000000..774f772c49 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/CacheProvider.java @@ -0,0 +1,23 @@ +package redis.clients.jedis.csc; + +import java.util.HashMap; + +public class CacheProvider { + + public Cache getCache(CacheConfig config) { + return getCache(config, new HashMap()); + } + + public Cache getCache(CacheConfig config, HashMap map) { + return new DefaultCache(config.getMaxSize(), map, config.getCacheable(), + getEvictionPolicy(config)); + } + + private EvictionPolicy getEvictionPolicy(CacheConfig config) { + if (config.getEvictionPolicy() == null) { + // It will be default to LRUEviction, until we have other eviction implementations + return new LRUEviction(config.getMaxSize()); + } + return config.getEvictionPolicy(); + } +} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/csc/DefaultCache.java b/src/main/java/redis/clients/jedis/csc/DefaultCache.java index aee3c634b5..ea561359eb 100644 --- a/src/main/java/redis/clients/jedis/csc/DefaultCache.java +++ b/src/main/java/redis/clients/jedis/csc/DefaultCache.java @@ -9,19 +9,19 @@ public class DefaultCache extends AbstractCache { protected final Map cache; private final EvictionPolicy evictionPolicy; - public DefaultCache(int maximumSize) { + protected DefaultCache(int maximumSize) { this(maximumSize, new HashMap()); } - public DefaultCache(int maximumSize, Map map) { + protected DefaultCache(int maximumSize, Map map) { this(maximumSize, map, DefaultCacheable.INSTANCE, new LRUEviction(maximumSize)); } - public DefaultCache(int maximumSize, Cacheable cacheable) { + protected DefaultCache(int maximumSize, Cacheable cacheable) { this(maximumSize, new HashMap(), cacheable, new LRUEviction(maximumSize)); } - public DefaultCache(int maximumSize, Map map, Cacheable cacheable, EvictionPolicy evictionPolicy) { + protected DefaultCache(int maximumSize, Map map, Cacheable cacheable, EvictionPolicy evictionPolicy) { super(maximumSize, cacheable); this.cache = map; this.evictionPolicy = evictionPolicy; diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index ab5ac2761b..0d171df89c 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -259,7 +259,6 @@ public void testInvalidationWithUnifiedJedis() { //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 { @@ -305,7 +304,8 @@ public void testSequentialAccess() throws InterruptedException { ReentrantLock lock = new ReentrantLock(true); ExecutorService executorService = Executors.newFixedThreadPool(threadCount); - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), new TestCache())) { + CacheConfig cacheConfig = CacheConfig.builder().maxSize(1000).build(); + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), cacheConfig)) { // Submit multiple threads to perform concurrent operations CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index aca94a545c..a5823dea56 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -38,4 +38,9 @@ protected JedisCluster createCachedJedis(Cache cache) { return new JedisCluster(hnp, clientConfig.get(), cache); } + @Override + protected JedisCluster createCachedJedis(CacheConfig cacheConfig) { + return new JedisCluster(hnp, clientConfig.get(), cacheConfig); + } + } diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java index 7becbe74d3..e86ef936af 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java @@ -25,6 +25,11 @@ protected JedisPooled createCachedJedis(Cache cache) { return new JedisPooled(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().resp3().build(), cache); } + @Override + protected JedisPooled createCachedJedis(CacheConfig cacheConfig) { + return new JedisPooled(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().resp3().build(), cacheConfig); + } + @Test public void clearIfOneDiesTest() { Cache cache = new TestCache(); diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index 578ed6a316..e81ec6e90f 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -33,4 +33,9 @@ protected JedisSentineled createCachedJedis(Cache cache) { return new JedisSentineled(MASTER_NAME, masterClientConfig, cache, sentinels, sentinelClientConfig); } + @Override + protected JedisSentineled createCachedJedis(CacheConfig cacheConfig) { + return new JedisSentineled(MASTER_NAME, masterClientConfig, cacheConfig, sentinels, sentinelClientConfig); + } + } diff --git a/src/test/java/redis/clients/jedis/csc/TestCache.java b/src/test/java/redis/clients/jedis/csc/TestCache.java index ebebe03051..c4ea43a9e7 100644 --- a/src/test/java/redis/clients/jedis/csc/TestCache.java +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -17,13 +17,13 @@ public TestCache(Map map, Cacheable cacheable) { super(10000, map, cacheable, new LRUEviction(10000)); } - public TestCache(int maxSize, Map map, Cacheable cacheable) { - this(maxSize, map, cacheable, new LRUEviction(maxSize)); + public TestCache(int maximumSize, Map map, Cacheable cacheable) { + this(maximumSize, map, cacheable, new LRUEviction(maximumSize)); } - public TestCache(int maxSize, Map map, Cacheable cacheable, + public TestCache(int maximumSize, Map map, Cacheable cacheable, EvictionPolicy evictionPolicy) { - super(maxSize, map, cacheable, evictionPolicy); + super(maximumSize, map, cacheable, evictionPolicy); } } diff --git a/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java index 771e08d5df..9d512e755e 100644 --- a/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java @@ -24,6 +24,8 @@ public abstract class UnifiedJedisClientSideCacheTestBase { protected abstract UnifiedJedis createCachedJedis(Cache cache); + protected abstract UnifiedJedis createCachedJedis(CacheConfig cacheConfig); + @Before public void setUp() throws Exception { control = createRegularJedis(); @@ -37,7 +39,8 @@ public void tearDown() throws Exception { @Test public void simple() { - try (UnifiedJedis jedis = createCachedJedis(new TestCache())) { + CacheConfig cacheConfig = CacheConfig.builder().maxSize(1000).build(); + try (UnifiedJedis jedis = createCachedJedis(cacheConfig)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.del("foo"); @@ -64,7 +67,8 @@ public void simpleWithSimpleMap() { @Test public void flushAll() { - try (UnifiedJedis jedis = createCachedJedis(new TestCache())) { + CacheConfig cacheConfig = CacheConfig.builder().maxSize(1000).build(); + try (UnifiedJedis jedis = createCachedJedis(cacheConfig)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); control.flushAll(); From 1d213bbf7860eda4c0b3bff8590843238d685c77 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Wed, 28 Aug 2024 22:42:59 +0600 Subject: [PATCH 46/48] Polish "Adding CacheConfig" Polish #3919 - address some pending change requests - Swap contructor placements - Fix grammar in exception message --- .../java/redis/clients/jedis/JedisCluster.java | 13 ++++++------- .../java/redis/clients/jedis/JedisPooled.java | 18 +++++++++--------- .../redis/clients/jedis/JedisSentineled.java | 12 ++++++------ .../java/redis/clients/jedis/Protocol.java | 2 +- .../java/redis/clients/jedis/UnifiedJedis.java | 18 ++++++++++-------- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index 4988b5ee2f..ca1dddc0f6 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -222,22 +222,21 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati } @Experimental - public JedisCluster(Set clusterNodes, JedisClientConfig clientConfig, Cache clientSideCache) { - this(clusterNodes, clientConfig, clientSideCache, DEFAULT_MAX_ATTEMPTS, - Duration.ofMillis(DEFAULT_MAX_ATTEMPTS * clientConfig.getSocketTimeoutMillis())); + public JedisCluster(Set hnp, JedisClientConfig jedisClientConfig, CacheConfig cacheConfig) { + this(hnp, jedisClientConfig, new CacheProvider().getCache(cacheConfig)); } @Experimental - public JedisCluster(Set hnp, JedisClientConfig jedisClientConfig, CacheConfig cacheConfig) { - this(hnp, jedisClientConfig, new CacheProvider().getCache(cacheConfig)); + 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, Cache clientSideCache, int maxAttempts, Duration maxTotalRetriesDuration) { this(new ClusterConnectionProvider(clusterNodes, clientConfig, clientSideCache), maxAttempts, - maxTotalRetriesDuration, - clientConfig.getRedisProtocol(), clientSideCache); + maxTotalRetriesDuration, clientConfig.getRedisProtocol(), clientSideCache); } @Experimental diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index 7eb67374ae..eff097c1be 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -80,13 +80,13 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client } @Experimental - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache) { - super(hostAndPort, clientConfig, clientSideCache); + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig) { + this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); } @Experimental - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig) { - this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache) { + super(hostAndPort, clientConfig, clientSideCache); } public JedisPooled(PooledObjectFactory factory) { @@ -390,16 +390,16 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client } @Experimental - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache, + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig, final GenericObjectPoolConfig poolConfig) { - super(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache, poolConfig), - clientConfig.getRedisProtocol(), clientSideCache); + this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig), poolConfig); } @Experimental - public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig, + public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, Cache clientSideCache, final GenericObjectPoolConfig poolConfig) { - this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig), poolConfig); + super(new PooledConnectionProvider(hostAndPort, clientConfig, clientSideCache, poolConfig), + clientConfig.getRedisProtocol(), clientSideCache); } public JedisPooled(final GenericObjectPoolConfig poolConfig, diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index da9eba4baf..03fbdbb9d6 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -17,17 +17,17 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo } @Experimental - public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, Cache clientSideCache, + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, CacheConfig cacheConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { - super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, - sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache); + this(masterName, masterClientConfig, new CacheProvider().getCache(cacheConfig), + sentinels, sentinelClientConfig); } @Experimental - public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, CacheConfig cacheConfig, + public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, Cache clientSideCache, Set sentinels, final JedisClientConfig sentinelClientConfig) { - this(masterName, masterClientConfig, new CacheProvider().getCache(cacheConfig), - sentinels, sentinelClientConfig); + super(new SentineledConnectionProvider(masterName, masterClientConfig, clientSideCache, + sentinels, sentinelClientConfig), masterClientConfig.getRedisProtocol(), clientSideCache); } public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index f8bb5bfb8b..cd6e41581f 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -236,7 +236,7 @@ public static void readPushes(final RedisInputStream is, final Cache cache, bool processPush(is, cache); } } catch (IOException e) { - throw new JedisConnectionException("Failed to read pending buffer for push messages !", e); + throw new JedisConnectionException("Failed to read pending buffer for push messages!", e); } } else { while (is.peek(GREATER_THAN_BYTE)) { diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 679cd8eb78..706c61be1b 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -99,14 +99,13 @@ public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) { } @Experimental - public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache cache) { - this(new PooledConnectionProvider(hostAndPort, clientConfig, cache), clientConfig.getRedisProtocol(), - cache); + public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, CacheConfig cacheConfig) { + this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); } @Experimental - public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, CacheConfig cacheConfig) { - this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); + public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, Cache cache) { + this(new PooledConnectionProvider(hostAndPort, clientConfig, cache), clientConfig.getRedisProtocol(), cache); } public UnifiedJedis(ConnectionProvider provider) { @@ -153,8 +152,9 @@ public UnifiedJedis(Connection connection) { this.executor = new SimpleCommandExecutor(connection); this.commandObjects = new CommandObjects(); RedisProtocol proto = connection.getRedisProtocol(); - if (proto != null) + if (proto != null) { this.commandObjects.setProtocol(proto); + } this.graphCommandObjects = new GraphCommandObjects(this); if (connection instanceof CacheConnection) { this.cache = ((CacheConnection) connection).getCache(); @@ -257,8 +257,9 @@ public UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comma try (Connection conn = this.provider.getConnection()) { if (conn != null) { RedisProtocol proto = conn.getRedisProtocol(); - if (proto != null) + if (proto != null) { this.commandObjects.setProtocol(proto); + } } } catch (JedisException je) { } @@ -283,8 +284,9 @@ private UnifiedJedis(CommandExecutor executor, ConnectionProvider provider, Comm this.executor = executor; this.commandObjects = commandObjects; - if (protocol != null) + if (protocol != null) { this.commandObjects.setProtocol(protocol); + } this.graphCommandObjects = new GraphCommandObjects(this); this.graphCommandObjects.setBaseCommandArgumentsCreator((comm) -> this.commandObjects.commandArguments(comm)); From ce210ea31c3e6dc89185d18de862cebcffecbccf Mon Sep 17 00:00:00 2001 From: atakavci <58048133+atakavci@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:56:15 +0300 Subject: [PATCH 47/48] Adding Cache class to CacheConfig (#3942) * adding cacheclass to cacheconfig * - add cachefactory test * - revert connection ctors to public - udpate some tests with UnifiedJedis.getCache - add ping to flaky tests * remove unnecessary anonymous types * change ctor access modifiers * fix test name * make cachefactory methods static * removing pings due to still flaky with inv messages * - drop CustomCache in tests and use TestCache - check null cacheable issue with defaultcache - support both ctors in custom cache classes regarding to value of cacheconfig.cacheable * remove unncessary maxsize * - remove inline anonymious --- .../redis/clients/jedis/JedisCluster.java | 4 +- .../java/redis/clients/jedis/JedisPooled.java | 6 +- .../redis/clients/jedis/JedisSentineled.java | 4 +- .../redis/clients/jedis/UnifiedJedis.java | 4 +- .../redis/clients/jedis/csc/CacheConfig.java | 14 +- .../redis/clients/jedis/csc/CacheFactory.java | 63 ++++++++ .../clients/jedis/csc/CacheProvider.java | 23 --- .../redis/clients/jedis/csc/DefaultCache.java | 4 + .../csc/AllowAndDenyListCacheableTest.java | 52 +++--- .../csc/ClientSideCacheFunctionalityTest.java | 152 ++++++++++-------- .../csc/JedisClusterClientSideCacheTest.java | 5 - .../JedisPooledClientSideCacheTestBase.java | 10 +- .../JedisSentineledClientSideCacheTest.java | 5 - .../redis/clients/jedis/csc/TestCache.java | 9 +- .../UnifiedJedisClientSideCacheTestBase.java | 62 ++++--- 15 files changed, 233 insertions(+), 184 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/CacheFactory.java delete mode 100644 src/main/java/redis/clients/jedis/csc/CacheProvider.java diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index ca1dddc0f6..0b9f5b49d1 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -12,7 +12,7 @@ import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.csc.Cache; import redis.clients.jedis.csc.CacheConfig; -import redis.clients.jedis.csc.CacheProvider; +import redis.clients.jedis.csc.CacheFactory; import redis.clients.jedis.util.JedisClusterCRC16; public class JedisCluster extends UnifiedJedis { @@ -223,7 +223,7 @@ private JedisCluster(ClusterConnectionProvider provider, int maxAttempts, Durati @Experimental public JedisCluster(Set hnp, JedisClientConfig jedisClientConfig, CacheConfig cacheConfig) { - this(hnp, jedisClientConfig, new CacheProvider().getCache(cacheConfig)); + this(hnp, jedisClientConfig, CacheFactory.getCache(cacheConfig)); } @Experimental diff --git a/src/main/java/redis/clients/jedis/JedisPooled.java b/src/main/java/redis/clients/jedis/JedisPooled.java index eff097c1be..c3429319e7 100644 --- a/src/main/java/redis/clients/jedis/JedisPooled.java +++ b/src/main/java/redis/clients/jedis/JedisPooled.java @@ -10,7 +10,7 @@ import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.csc.Cache; import redis.clients.jedis.csc.CacheConfig; -import redis.clients.jedis.csc.CacheProvider; +import redis.clients.jedis.csc.CacheFactory; import redis.clients.jedis.providers.PooledConnectionProvider; import redis.clients.jedis.util.JedisURIHelper; import redis.clients.jedis.util.Pool; @@ -81,7 +81,7 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client @Experimental public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig) { - this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); + this(hostAndPort, clientConfig, CacheFactory.getCache(cacheConfig)); } @Experimental @@ -392,7 +392,7 @@ public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig client @Experimental public JedisPooled(final HostAndPort hostAndPort, final JedisClientConfig clientConfig, CacheConfig cacheConfig, final GenericObjectPoolConfig poolConfig) { - this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig), poolConfig); + this(hostAndPort, clientConfig, CacheFactory.getCache(cacheConfig), poolConfig); } @Experimental diff --git a/src/main/java/redis/clients/jedis/JedisSentineled.java b/src/main/java/redis/clients/jedis/JedisSentineled.java index 03fbdbb9d6..26f208a03b 100644 --- a/src/main/java/redis/clients/jedis/JedisSentineled.java +++ b/src/main/java/redis/clients/jedis/JedisSentineled.java @@ -5,7 +5,7 @@ import redis.clients.jedis.annots.Experimental; import redis.clients.jedis.csc.Cache; import redis.clients.jedis.csc.CacheConfig; -import redis.clients.jedis.csc.CacheProvider; +import redis.clients.jedis.csc.CacheFactory; import redis.clients.jedis.providers.SentineledConnectionProvider; public class JedisSentineled extends UnifiedJedis { @@ -19,7 +19,7 @@ public JedisSentineled(String masterName, final JedisClientConfig masterClientCo @Experimental public JedisSentineled(String masterName, final JedisClientConfig masterClientConfig, CacheConfig cacheConfig, Set sentinels, final JedisClientConfig sentinelClientConfig) { - this(masterName, masterClientConfig, new CacheProvider().getCache(cacheConfig), + this(masterName, masterClientConfig, CacheFactory.getCache(cacheConfig), sentinels, sentinelClientConfig); } diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 706c61be1b..9ccf2538b2 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -22,7 +22,7 @@ import redis.clients.jedis.csc.Cache; import redis.clients.jedis.csc.CacheConfig; import redis.clients.jedis.csc.CacheConnection; -import redis.clients.jedis.csc.CacheProvider; +import redis.clients.jedis.csc.CacheFactory; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.executors.*; import redis.clients.jedis.gears.TFunctionListParams; @@ -100,7 +100,7 @@ public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig) { @Experimental public UnifiedJedis(HostAndPort hostAndPort, JedisClientConfig clientConfig, CacheConfig cacheConfig) { - this(hostAndPort, clientConfig, new CacheProvider().getCache(cacheConfig)); + this(hostAndPort, clientConfig, CacheFactory.getCache(cacheConfig)); } @Experimental diff --git a/src/main/java/redis/clients/jedis/csc/CacheConfig.java b/src/main/java/redis/clients/jedis/csc/CacheConfig.java index 2c782a4a7c..ab907dfbde 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConfig.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConfig.java @@ -5,6 +5,7 @@ public class CacheConfig { private int maxSize; private Cacheable cacheable; private EvictionPolicy evictionPolicy; + private Class cacheClass; public int getMaxSize() { return maxSize; @@ -18,14 +19,19 @@ public EvictionPolicy getEvictionPolicy() { return evictionPolicy; } + public Class getCacheClass() { + return cacheClass; + } public static Builder builder() { return new Builder(); } public static class Builder { - private int maxSize; + private final int DEFAULT_MAX_SIZE = 10000; + private int maxSize = DEFAULT_MAX_SIZE; private Cacheable cacheable = DefaultCacheable.INSTANCE; private EvictionPolicy evictionPolicy; + private Class cacheClass; public Builder maxSize(int maxSize) { this.maxSize = maxSize; @@ -42,11 +48,17 @@ public Builder cacheable(Cacheable cacheable) { return this; } + public Builder cacheClass(Class cacheClass) { + this.cacheClass = cacheClass; + return this; + } + public CacheConfig build() { CacheConfig cacheConfig = new CacheConfig(); cacheConfig.maxSize = this.maxSize; cacheConfig.cacheable = this.cacheable; cacheConfig.evictionPolicy = this.evictionPolicy; + cacheConfig.cacheClass = this.cacheClass; return cacheConfig; } } diff --git a/src/main/java/redis/clients/jedis/csc/CacheFactory.java b/src/main/java/redis/clients/jedis/csc/CacheFactory.java new file mode 100644 index 0000000000..0286783dfc --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/CacheFactory.java @@ -0,0 +1,63 @@ +package redis.clients.jedis.csc; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; + +import redis.clients.jedis.exceptions.JedisCacheException; + +public final class CacheFactory { + + public static Cache getCache(CacheConfig config) { + if (config.getCacheClass() == null) { + if (config.getCacheable() == null) { + throw new JedisCacheException("Cacheable is required to create the default cache!"); + } + return new DefaultCache(config.getMaxSize(), config.getCacheable(), getEvictionPolicy(config)); + } + return instantiateCustomCache(config); + } + + private static Cache instantiateCustomCache(CacheConfig config) { + try { + if (config.getCacheable() != null) { + Constructor ctorWithCacheable = findConstructorWithCacheable(config.getCacheClass()); + if (ctorWithCacheable != null) { + return (Cache) ctorWithCacheable.newInstance(config.getMaxSize(), getEvictionPolicy(config), config.getCacheable()); + } + } + Constructor ctor = getConstructor(config.getCacheClass()); + return (Cache) ctor.newInstance(config.getMaxSize(), getEvictionPolicy(config)); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | SecurityException e) { + throw new JedisCacheException("Failed to insantiate custom cache type!", e); + } + } + + private static Constructor findConstructorWithCacheable(Class customCacheType) { + return Arrays.stream(customCacheType.getConstructors()) + .filter(ctor -> Arrays.equals(ctor.getParameterTypes(), new Class[] { int.class, EvictionPolicy.class, Cacheable.class })) + .findFirst().orElse(null); + } + + private static Constructor getConstructor(Class customCacheType) { + try { + return customCacheType.getConstructor(int.class, EvictionPolicy.class); + } catch (NoSuchMethodException e) { + String className = customCacheType.getName(); + throw new JedisCacheException(String.format( + "Failed to find compatible constructor for custom cache type! Provide one of these;" + // give hints about the compatible constructors + + "\n - %s(int maxSize, EvictionPolicy evictionPolicy)\n - %s(int maxSize, EvictionPolicy evictionPolicy, Cacheable cacheable)", + className, className), e); + } + } + + private static EvictionPolicy getEvictionPolicy(CacheConfig config) { + if (config.getEvictionPolicy() == null) { + // It will be default to LRUEviction, until we have other eviction implementations + return new LRUEviction(config.getMaxSize()); + } + return config.getEvictionPolicy(); + } +} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/csc/CacheProvider.java b/src/main/java/redis/clients/jedis/csc/CacheProvider.java deleted file mode 100644 index 774f772c49..0000000000 --- a/src/main/java/redis/clients/jedis/csc/CacheProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -package redis.clients.jedis.csc; - -import java.util.HashMap; - -public class CacheProvider { - - public Cache getCache(CacheConfig config) { - return getCache(config, new HashMap()); - } - - public Cache getCache(CacheConfig config, HashMap map) { - return new DefaultCache(config.getMaxSize(), map, config.getCacheable(), - getEvictionPolicy(config)); - } - - private EvictionPolicy getEvictionPolicy(CacheConfig config) { - if (config.getEvictionPolicy() == null) { - // It will be default to LRUEviction, until we have other eviction implementations - return new LRUEviction(config.getMaxSize()); - } - return config.getEvictionPolicy(); - } -} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/csc/DefaultCache.java b/src/main/java/redis/clients/jedis/csc/DefaultCache.java index ea561359eb..5577cc0758 100644 --- a/src/main/java/redis/clients/jedis/csc/DefaultCache.java +++ b/src/main/java/redis/clients/jedis/csc/DefaultCache.java @@ -21,6 +21,10 @@ protected DefaultCache(int maximumSize, Cacheable cacheable) { this(maximumSize, new HashMap(), cacheable, new LRUEviction(maximumSize)); } + protected DefaultCache(int maximumSize, Cacheable cacheable, EvictionPolicy evictionPolicy) { + this(maximumSize, new HashMap(), cacheable, evictionPolicy); + } + protected DefaultCache(int maximumSize, Map map, Cacheable cacheable, EvictionPolicy evictionPolicy) { super(maximumSize, cacheable); this.cache = map; diff --git a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java index c8d311865c..a0b7d68381 100644 --- a/src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java +++ b/src/test/java/redis/clients/jedis/csc/AllowAndDenyListCacheableTest.java @@ -1,12 +1,8 @@ package redis.clients.jedis.csc; import static java.util.Collections.singleton; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import java.util.HashMap; -import java.util.Map; -import org.hamcrest.Matchers; import org.junit.Test; import redis.clients.jedis.JedisPooled; @@ -15,73 +11,69 @@ public class AllowAndDenyListCacheableTest extends ClientSideCacheTestBase { - private static Cache createTestCache(Map map, Cacheable cacheable) { - Cache mapCache = new TestCache(map, cacheable); - return mapCache; + private static CacheConfig createConfig(Cacheable cacheable) { + return CacheConfig.builder().cacheable(cacheable).cacheClass(TestCache.class).build(); } @Test public void none() { - HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createTestCache(map, new AllowAndDenyListWithStringKeys(null, null, null, null)), - singleConnectionPoolConfig.get())) { + createConfig(new AllowAndDenyListWithStringKeys(null, null, null, null)), singleConnectionPoolConfig.get())) { + Cache cache = jedis.getCache(); control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); } } @Test public void whiteListCommand() { - HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createTestCache(map, new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)), + createConfig(new AllowAndDenyListWithStringKeys(singleton(Protocol.Command.GET), null, null, null)), singleConnectionPoolConfig.get())) { + Cache cache = jedis.getCache(); control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); } } @Test public void blackListCommand() { - HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createTestCache(map, new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)), + createConfig(new AllowAndDenyListWithStringKeys(null, singleton(Protocol.Command.GET), null, null)), singleConnectionPoolConfig.get())) { + Cache cache = jedis.getCache(); control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); } } @Test public void whiteListKey() { - HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createTestCache(map, new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)), - singleConnectionPoolConfig.get())) { + createConfig(new AllowAndDenyListWithStringKeys(null, null, singleton("foo"), null)), singleConnectionPoolConfig.get())) { control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + Cache cache = jedis.getCache(); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); } } @Test public void blackListKey() { - HashMap map = new HashMap<>(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - createTestCache(map, new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))), - singleConnectionPoolConfig.get())) { + createConfig(new AllowAndDenyListWithStringKeys(null, null, null, singleton("foo"))), singleConnectionPoolConfig.get())) { + Cache cache = jedis.getCache(); control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); } } } diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java index 0d171df89c..d2032e5d23 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheFunctionalityTest.java @@ -17,12 +17,13 @@ import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; 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; +import java.util.stream.Collector; +import java.util.stream.Collectors; import org.hamcrest.Matchers; import org.junit.Assert; @@ -42,8 +43,8 @@ public void flushAllTest() { control.set("k" + i, "v" + i); } - Cache cache = new TestCache(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); for (int i = 0; i < count; i++) { jedis.get("k" + i); } @@ -91,8 +92,9 @@ public void lruEvictionTest() { @Test // T.5.2 public void deleteByKeyUsingMGetTest() { - Cache clientSideCache = new TestCache(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().build())) { + Cache clientSideCache = jedis.getCache(); + jedis.set("1", "one"); jedis.set("2", "two"); @@ -168,20 +170,19 @@ public void deleteByEntryTest() { control.set("k" + i, "v" + i); } - HashMap map = new HashMap<>(); - Cache clientSideCache = new TestCache(map); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); for (int i = 0; i < count; i++) { jedis.get("k" + i); } - assertThat(map, aMapWithSize(count)); + assertEquals(count, cache.getSize()); - List cacheKeys = new ArrayList<>(map.keySet()); + List cacheKeys = new ArrayList<>(cache.getCacheEntries()); for (int i = 0; i < count; i++) { - CacheKey cacheKey = cacheKeys.get(i); - assertTrue(clientSideCache.delete(cacheKey)); - assertFalse(map.containsKey(cacheKey)); - assertThat(map, aMapWithSize(count - i - 1)); + CacheKey cacheKey = cacheKeys.get(i).getCacheKey(); + assertTrue(cache.delete(cacheKey)); + assertFalse(cache.hasCacheKey(cacheKey)); + assertEquals(count - i - 1, cache.getSize()); } } } @@ -194,19 +195,19 @@ public void deleteByEntriesTest() { control.set("k" + i, "v" + i); } - HashMap map = new HashMap<>(); - Cache clientSideCache = new TestCache(map); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), clientSideCache)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); for (int i = 0; i < count; i++) { jedis.get("k" + i); } - assertThat(map, aMapWithSize(count)); + assertEquals(count, cache.getSize()); - List cacheKeysToDelete = new ArrayList<>(map.keySet()).subList(0, delete); - List isDeleted = clientSideCache.delete(cacheKeysToDelete); + List cacheKeysToDelete = new ArrayList<>(cache.getCacheEntries()).subList(0, delete).stream().map(e -> e.getCacheKey()) + .collect(Collectors.toList()); + List isDeleted = cache.delete(cacheKeysToDelete); assertThat(isDeleted, hasSize(delete)); isDeleted.forEach(Assert::assertTrue); - assertThat(map, aMapWithSize(count - delete)); + assertEquals(count - delete, cache.getSize()); } } @@ -215,10 +216,9 @@ public void multiKeyOperation() { control.set("k1", "v1"); control.set("k2", "v2"); - Cache cache = new TestCache(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().build())) { jedis.mget("k1", "k2"); - assertEquals(1, cache.getSize()); + assertEquals(1, jedis.getCache().getSize()); } } @@ -227,8 +227,8 @@ 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)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().maxSize(1).build())) { + Cache cache = jedis.getCache(); assertEquals(0, cache.getSize()); jedis.get("k1"); assertEquals(1, cache.getSize()); @@ -269,11 +269,10 @@ public void testInvalidationWithUnifiedJedis() { @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)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); jedis.sadd("foo", "a"); jedis.sadd("foo", "b"); @@ -284,8 +283,7 @@ 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) cache.get(new CacheKey<>(new CommandObjects().smembers("foo"))).getValue(); assertEquals(expected, members1); assertEquals(expected, members2); assertEquals(expected, fromMap); @@ -349,8 +347,8 @@ public void testConcurrentAccessWithStats() throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(threadCount); // Create the shared mock instance of cache - TestCache testCache = new TestCache(); - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); // Submit multiple threads to perform concurrent operations CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { @@ -369,13 +367,13 @@ public void testConcurrentAccessWithStats() throws InterruptedException { // wait for all threads to complete latch.await(); - } - executorService.shutdownNow(); + executorService.shutdownNow(); - CacheStats stats = testCache.getStats(); - assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount()); - assertEquals(stats.getMissCount(), stats.getLoadCount()); + CacheStats stats = cache.getStats(); + assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount()); + assertEquals(stats.getMissCount(), stats.getLoadCount()); + } } @Test @@ -386,11 +384,8 @@ public void testMaxSize() throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(threadCount); - ConcurrentHashMap map = new ConcurrentHashMap<>(); - // Create the shared mock instance of cache - TestCache testCache = new TestCache(maxSize, map, DefaultCacheable.INSTANCE); - - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), CacheConfig.builder().maxSize(maxSize).build())) { + Cache testCache = jedis.getCache(); // Submit multiple threads to perform concurrent operations CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { @@ -409,16 +404,16 @@ public void testMaxSize() throws InterruptedException { // wait for all threads to complete latch.await(); - } - executorService.shutdownNow(); + executorService.shutdownNow(); - CacheStats stats = testCache.getStats(); + 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()); + assertEquals(threadCount * iterations, stats.getMissCount() + stats.getHitCount()); + assertEquals(stats.getMissCount(), stats.getLoadCount()); + assertEquals(threadCount * iterations, stats.getNonCacheableCount()); + assertTrue(maxSize >= testCache.getSize()); + } } @Test @@ -427,11 +422,10 @@ public void testEvictionPolicy() throws InterruptedException { int expectedEvictions = 20; int touchOffset = 10; - HashMap map = new HashMap<>(); - TestCache testCache = new TestCache(maxSize, map, DefaultCacheable.INSTANCE); - // fill the cache for maxSize - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), testCache)) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), + CacheConfig.builder().maxSize(maxSize).build())) { + Cache cache = jedis.getCache(); for (int i = 0; i < maxSize; i++) { jedis.set("foo" + i, "bar" + i); assertEquals("bar" + i, jedis.get("foo" + i)); @@ -450,21 +444,20 @@ public void testEvictionPolicy() throws InterruptedException { // check touched keys not evicted for (int i = touchOffset; i < touchOffset + expectedEvictions; i++) { - - assertTrue(map.containsKey(new CacheKey(new CommandObjects().get("foo" + i)))); + assertTrue(cache.hasCacheKey(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)))); + assertTrue(!cache.hasCacheKey(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)))); + assertTrue(!cache.hasCacheKey(new CacheKey(new CommandObjects().get("foo" + i)))); } - assertEquals(maxSize, testCache.getSize()); + assertEquals(maxSize, cache.getSize()); } } @@ -476,10 +469,11 @@ public void testEvictionPolicyMultithreaded() throws InterruptedException { int MAX_SIZE = 20; List exceptions = new ArrayList<>(); - TestCache cache = new TestCache(MAX_SIZE, new HashMap<>(), DefaultCacheable.INSTANCE); List tds = new ArrayList<>(); final AtomicInteger ind = new AtomicInteger(); - try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), cache)) { + try (JedisPooled jedis = new JedisPooled(endpoint.getHostAndPort(), clientConfig.get(), + CacheConfig.builder().maxSize(MAX_SIZE).build())) { + Cache cache = jedis.getCache(); for (int i = 0; i < NUMBER_OF_THREADS; i++) { Thread hj = new Thread(new Runnable() { @Override @@ -517,9 +511,8 @@ public void testNullValue() throws InterruptedException { String nonExisting = "non-existing-key"; control.del(nonExisting); - TestCache cache = new TestCache(MAX_SIZE, new HashMap<>(), DefaultCacheable.INSTANCE); - - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), cache)) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().maxSize(MAX_SIZE).build())) { + Cache cache = jedis.getCache(); CacheStats stats = cache.getStats(); String val = jedis.get(nonExisting); @@ -545,4 +538,35 @@ public void testNullValue() throws InterruptedException { } } + @Test + public void testCacheFactory() throws InterruptedException { + // this checks the instantiation with parameters (int, EvictionPolicy, Cacheable) + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), CacheConfig.builder().cacheClass(TestCache.class).build())) { + Cache cache = jedis.getCache(); + CacheStats stats = cache.getStats(); + + String val = jedis.get("foo"); + val = jedis.get("foo"); + assertNull(val); + assertEquals(1, cache.getSize()); + assertNull(cache.getCacheEntries().iterator().next().getValue()); + assertEquals(1, stats.getHitCount()); + assertEquals(1, stats.getMissCount()); + } + + // this checks the instantiation with parameters (int, EvictionPolicy) + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), + CacheConfig.builder().cacheClass(TestCache.class).cacheable(null).build())) { + Cache cache = jedis.getCache(); + CacheStats stats = cache.getStats(); + + String val = jedis.get("foo"); + val = jedis.get("foo"); + assertNull(val); + assertEquals(1, cache.getSize()); + assertNull(cache.getCacheEntries().iterator().next().getValue()); + assertEquals(1, stats.getHitCount()); + assertEquals(1, stats.getMissCount()); + } + } } diff --git a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java index a5823dea56..89114d154f 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisClusterClientSideCacheTest.java @@ -33,11 +33,6 @@ protected JedisCluster createRegularJedis() { return new JedisCluster(hnp, clientConfig.get()); } - @Override - protected JedisCluster createCachedJedis(Cache cache) { - return new JedisCluster(hnp, clientConfig.get(), cache); - } - @Override protected JedisCluster createCachedJedis(CacheConfig cacheConfig) { return new JedisCluster(hnp, clientConfig.get(), cacheConfig); diff --git a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java index e86ef936af..133efcb3fc 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/JedisPooledClientSideCacheTestBase.java @@ -20,11 +20,6 @@ protected JedisPooled createRegularJedis() { return new JedisPooled(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().build()); } - @Override - protected JedisPooled createCachedJedis(Cache cache) { - return new JedisPooled(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().resp3().build(), cache); - } - @Override protected JedisPooled createCachedJedis(CacheConfig cacheConfig) { return new JedisPooled(endpoint.getHostAndPort(), endpoint.getClientConfigBuilder().resp3().build(), cacheConfig); @@ -32,9 +27,8 @@ protected JedisPooled createCachedJedis(CacheConfig cacheConfig) { @Test public void clearIfOneDiesTest() { - Cache cache = new TestCache(); - try (JedisPooled jedis = createCachedJedis(cache)) { - + try (JedisPooled jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); // Create 100 keys for (int i = 0; i < 100; i++) { jedis.set("key" + i, "value" + i); diff --git a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java index e81ec6e90f..82da0b14af 100644 --- a/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java +++ b/src/test/java/redis/clients/jedis/csc/JedisSentineledClientSideCacheTest.java @@ -28,11 +28,6 @@ protected JedisSentineled createRegularJedis() { return new JedisSentineled(MASTER_NAME, masterClientConfig, sentinels, sentinelClientConfig); } - @Override - protected JedisSentineled createCachedJedis(Cache cache) { - return new JedisSentineled(MASTER_NAME, masterClientConfig, cache, sentinels, sentinelClientConfig); - } - @Override protected JedisSentineled createCachedJedis(CacheConfig cacheConfig) { return new JedisSentineled(MASTER_NAME, masterClientConfig, cacheConfig, sentinels, sentinelClientConfig); diff --git a/src/test/java/redis/clients/jedis/csc/TestCache.java b/src/test/java/redis/clients/jedis/csc/TestCache.java index c4ea43a9e7..0c9db2dbba 100644 --- a/src/test/java/redis/clients/jedis/csc/TestCache.java +++ b/src/test/java/redis/clients/jedis/csc/TestCache.java @@ -17,13 +17,12 @@ public TestCache(Map map, Cacheable cacheable) { super(10000, map, cacheable, new LRUEviction(10000)); } - public TestCache(int maximumSize, Map map, Cacheable cacheable) { - this(maximumSize, map, cacheable, new LRUEviction(maximumSize)); + public TestCache(int maximumSize, EvictionPolicy evictionPolicy ) { + super(maximumSize, new HashMap(), DefaultCacheable.INSTANCE, evictionPolicy); } - public TestCache(int maximumSize, Map map, Cacheable cacheable, - EvictionPolicy evictionPolicy) { - super(maximumSize, map, cacheable, evictionPolicy); + public TestCache(int maximumSize, EvictionPolicy evictionPolicy, Cacheable cacheable ) { + super(maximumSize, new HashMap(), cacheable, evictionPolicy); } } diff --git a/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java index 9d512e755e..388113307b 100644 --- a/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java +++ b/src/test/java/redis/clients/jedis/csc/UnifiedJedisClientSideCacheTestBase.java @@ -1,15 +1,12 @@ package redis.clients.jedis.csc; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -22,8 +19,6 @@ public abstract class UnifiedJedisClientSideCacheTestBase { protected abstract UnifiedJedis createRegularJedis(); - protected abstract UnifiedJedis createCachedJedis(Cache cache); - protected abstract UnifiedJedis createCachedJedis(CacheConfig cacheConfig); @Before @@ -50,18 +45,18 @@ public void simple() { @Test public void simpleWithSimpleMap() { - HashMap map = new HashMap<>(); - try (UnifiedJedis jedis = createCachedJedis(new TestCache(map))) { + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); control.del("foo"); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); } } @@ -78,37 +73,37 @@ public void flushAll() { @Test public void flushAllWithSimpleMap() { - HashMap map = new HashMap<>(); - try (UnifiedJedis jedis = createCachedJedis(new TestCache(map))) { + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); control.flushAll(); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); assertNull(jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); } } @Test public void cacheNotEmptyTest() { - HashMap map = new HashMap<>(); - try (UnifiedJedis jedis = createCachedJedis(new TestCache(map))) { + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); control.set("foo", "bar"); - assertThat(map, Matchers.aMapWithSize(0)); + assertEquals(0, cache.getSize()); assertEquals("bar", jedis.get("foo")); - assertThat(map, Matchers.aMapWithSize(1)); + assertEquals(1, cache.getSize()); } } @Test public void cacheUsedTest() { - HashMap map = new HashMap<>(); - Cache cache = new TestCache(map); - try (UnifiedJedis jedis = createCachedJedis(cache)) { + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); + control.set("foo", "bar"); assertEquals(0, cache.getStats().getMissCount()); @@ -126,7 +121,7 @@ public void cacheUsedTest() { @Test public void immutableCacheEntriesTest() { - try (UnifiedJedis jedis = createCachedJedis(new TestCache())) { + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { jedis.set("{csc}a", "AA"); jedis.set("{csc}b", "BB"); jedis.set("{csc}c", "CC"); @@ -145,8 +140,8 @@ public void immutableCacheEntriesTest() { @Test public void invalidationTest() { - Cache cache = new TestCache(); - try (UnifiedJedis jedis = createCachedJedis(cache)) { + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); jedis.set("{csc}1", "one"); jedis.set("{csc}2", "two"); jedis.set("{csc}3", "three"); @@ -170,8 +165,8 @@ public void invalidationTest() { @Test public void getNumEntriesTest() { - Cache cache = new TestCache(); - try (UnifiedJedis jedis = createCachedJedis(cache)) { + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); // Create 100 keys for (int i = 0; i < 100; i++) { @@ -189,9 +184,8 @@ public void getNumEntriesTest() { @Test public void invalidationOnCacheHitTest() { - Cache cache = new TestCache(); - try (UnifiedJedis jedis = createCachedJedis(cache)) { - + try (UnifiedJedis jedis = createCachedJedis(CacheConfig.builder().build())) { + Cache cache = jedis.getCache(); // Create 100 keys for (int i = 0; i < 100; i++) { jedis.set("key" + i, "value" + i); From b94fdf289867c1c2707a6e8b66527c6647c49123 Mon Sep 17 00:00:00 2001 From: atakavci <58048133+atakavci@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:25:33 +0300 Subject: [PATCH 48/48] Server version check for CSC activation (#3954) * checking server version for CSC * fix format change * fix noauth hello exception in integration tests * fix version check * remove redundant check * remove unused imports * 'toString' for Version * rename to RedisVersion * moving RedisVersion package --- .../java/redis/clients/jedis/Connection.java | 81 ++++++++++--------- .../clients/jedis/csc/AbstractCache.java | 4 + .../java/redis/clients/jedis/csc/Cache.java | 5 ++ .../clients/jedis/csc/CacheConnection.java | 9 +++ .../redis/clients/jedis/csc/RedisVersion.java | 41 ++++++++++ .../redis/clients/jedis/csc/VersionTest.java | 30 +++++++ 6 files changed, 134 insertions(+), 36 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/csc/RedisVersion.java create mode 100644 src/test/java/redis/clients/jedis/csc/VersionTest.java diff --git a/src/main/java/redis/clients/jedis/Connection.java b/src/main/java/redis/clients/jedis/Connection.java index 68eaee3b74..2860866c6e 100644 --- a/src/main/java/redis/clients/jedis/Connection.java +++ b/src/main/java/redis/clients/jedis/Connection.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.function.Supplier; import redis.clients.jedis.Protocol.Command; @@ -41,6 +42,8 @@ public class Connection implements Closeable { private boolean broken = false; private boolean strValActive; private String strVal; + protected String server; + protected String version; public Connection() { this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT); @@ -453,12 +456,12 @@ protected void initializeFromClientConfig(final JedisClientConfig config) { final RedisCredentialsProvider redisCredentialsProvider = (RedisCredentialsProvider) credentialsProvider; try { redisCredentialsProvider.prepare(); - helloOrAuth(protocol, redisCredentialsProvider.get()); + helloAndAuth(protocol, redisCredentialsProvider.get()); } finally { redisCredentialsProvider.cleanUp(); } } else { - helloOrAuth(protocol, credentialsProvider != null ? credentialsProvider.get() + helloAndAuth(protocol, credentialsProvider != null ? credentialsProvider.get() : new DefaultRedisCredentials(config.getUser(), config.getPassword())); } @@ -517,50 +520,56 @@ protected void initializeFromClientConfig(final JedisClientConfig config) { } } - private void helloOrAuth(final RedisProtocol protocol, final RedisCredentials credentials) { - - if (credentials == null || credentials.getPassword() == null) { - if (protocol != null) { - sendCommand(Command.HELLO, encode(protocol.version())); - getOne(); + private void helloAndAuth(final RedisProtocol protocol, final RedisCredentials credentials) { + Map helloResult = null; + if (protocol != null && credentials != null && credentials.getUser() != null) { + byte[] rawPass = encodeToBytes(credentials.getPassword()); + try { + helloResult = hello(encode(protocol.version()), Keyword.AUTH.getRaw(), encode(credentials.getUser()), rawPass); + } finally { + Arrays.fill(rawPass, (byte) 0); // clear sensitive data } - return; + } else { + auth(credentials); + helloResult = protocol == null ? null : hello(encode(protocol.version())); + } + if (helloResult != null) { + server = (String) helloResult.get("server"); + version = (String) helloResult.get("version"); } - // Source: https://stackoverflow.com/a/9670279/4021802 - ByteBuffer passBuf = Protocol.CHARSET.encode(CharBuffer.wrap(credentials.getPassword())); - byte[] rawPass = Arrays.copyOfRange(passBuf.array(), passBuf.position(), passBuf.limit()); - Arrays.fill(passBuf.array(), (byte) 0); // clear sensitive data + // clearing 'char[] credentials.getPassword()' should be + // handled in RedisCredentialsProvider.cleanUp() + } + private void auth(RedisCredentials credentials) { + if (credentials == null || credentials.getPassword() == null) { + return; + } + byte[] rawPass = encodeToBytes(credentials.getPassword()); try { - /// actual HELLO or AUTH --> - if (protocol != null) { - if (credentials.getUser() != null) { - sendCommand(Command.HELLO, encode(protocol.version()), - Keyword.AUTH.getRaw(), encode(credentials.getUser()), rawPass); - getOne(); // Map - } else { - sendCommand(Command.AUTH, rawPass); - getStatusCodeReply(); // OK - sendCommand(Command.HELLO, encode(protocol.version())); - getOne(); // Map - } - } else { // protocol == null - if (credentials.getUser() != null) { - sendCommand(Command.AUTH, encode(credentials.getUser()), rawPass); - } else { - sendCommand(Command.AUTH, rawPass); - } - getStatusCodeReply(); // OK + if (credentials.getUser() == null) { + sendCommand(Command.AUTH, rawPass); + } else { + sendCommand(Command.AUTH, encode(credentials.getUser()), rawPass); } - /// <-- actual HELLO or AUTH } finally { - Arrays.fill(rawPass, (byte) 0); // clear sensitive data } + getStatusCodeReply(); + } - // clearing 'char[] credentials.getPassword()' should be - // handled in RedisCredentialsProvider.cleanUp() + protected Map hello(byte[]... args) { + sendCommand(Command.HELLO, args); + return BuilderFactory.ENCODED_OBJECT_MAP.build(getOne()); + } + + protected byte[] encodeToBytes(char[] chars) { + // Source: https://stackoverflow.com/a/9670279/4021802 + ByteBuffer passBuf = Protocol.CHARSET.encode(CharBuffer.wrap(chars)); + byte[] rawPass = Arrays.copyOfRange(passBuf.array(), passBuf.position(), passBuf.limit()); + Arrays.fill(passBuf.array(), (byte) 0); // clear sensitive data + return rawPass; } public String select(final int index) { diff --git a/src/main/java/redis/clients/jedis/csc/AbstractCache.java b/src/main/java/redis/clients/jedis/csc/AbstractCache.java index fc936b5baf..84b4d2ef81 100644 --- a/src/main/java/redis/clients/jedis/csc/AbstractCache.java +++ b/src/main/java/redis/clients/jedis/csc/AbstractCache.java @@ -193,6 +193,10 @@ public CacheStats getAndResetStats() { return result; } + @Override + public boolean compatibilityMode() { + return false; + } // End of Cache interface methods // abstract methods to be implemented by the concrete classes diff --git a/src/main/java/redis/clients/jedis/csc/Cache.java b/src/main/java/redis/clients/jedis/csc/Cache.java index 49413bc0de..0bf4592b59 100644 --- a/src/main/java/redis/clients/jedis/csc/Cache.java +++ b/src/main/java/redis/clients/jedis/csc/Cache.java @@ -105,4 +105,9 @@ public interface Cache { * @return The statistics of the cache */ CacheStats getAndResetStats(); + + /** + * @return The compatibility of cache against different Redis versions + */ + boolean compatibilityMode(); } diff --git a/src/main/java/redis/clients/jedis/csc/CacheConnection.java b/src/main/java/redis/clients/jedis/csc/CacheConnection.java index c19dd319ec..f157d95a94 100644 --- a/src/main/java/redis/clients/jedis/csc/CacheConnection.java +++ b/src/main/java/redis/clients/jedis/csc/CacheConnection.java @@ -16,6 +16,8 @@ public class CacheConnection extends Connection { private final Cache cache; private ReentrantLock lock; + private static final String REDIS = "redis"; + private static final String MIN_REDIS_VERSION = "7.4"; public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig clientConfig, Cache cache) { super(socketFactory, clientConfig); @@ -23,6 +25,13 @@ public CacheConnection(final JedisSocketFactory socketFactory, JedisClientConfig if (protocol != RedisProtocol.RESP3) { throw new JedisException("Client side caching is only supported with RESP3."); } + if (!cache.compatibilityMode()) { + RedisVersion current = new RedisVersion(version); + RedisVersion required = new RedisVersion(MIN_REDIS_VERSION); + if (!REDIS.equals(server) || current.compareTo(required) < 0) { + throw new JedisException(String.format("Client side caching is only supported with 'Redis %s' or later.", MIN_REDIS_VERSION)); + } + } this.cache = Objects.requireNonNull(cache); initializeClientSideCache(); } diff --git a/src/main/java/redis/clients/jedis/csc/RedisVersion.java b/src/main/java/redis/clients/jedis/csc/RedisVersion.java new file mode 100644 index 0000000000..2daf6393c7 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/RedisVersion.java @@ -0,0 +1,41 @@ +package redis.clients.jedis.csc; + +import java.util.Arrays; + +class RedisVersion implements Comparable { + + private String version; + private Integer[] numbers; + + public RedisVersion(String version) { + if (version == null) throw new IllegalArgumentException("Version can not be null"); + this.version = version; + this.numbers = Arrays.stream(version.split("\\.")).map(n -> Integer.parseInt(n)).toArray(Integer[]::new); + } + + @Override + public int compareTo(RedisVersion other) { + int max = Math.max(this.numbers.length, other.numbers.length); + for (int i = 0; i < max; i++) { + int thisNumber = this.numbers.length > i ? this.numbers[i]:0; + int otherNumber = other.numbers.length > i ? other.numbers[i]:0; + if (thisNumber < otherNumber) return -1; + if (thisNumber > otherNumber) return 1; + } + return 0; + } + + @Override + public String toString() { + return this.version; + } + + @Override + public boolean equals(Object that) { + if (this == that) return true; + if (that == null) return false; + if (this.getClass() != that.getClass()) return false; + return this.compareTo((RedisVersion) that) == 0; + } + +} diff --git a/src/test/java/redis/clients/jedis/csc/VersionTest.java b/src/test/java/redis/clients/jedis/csc/VersionTest.java new file mode 100644 index 0000000000..b154e88a05 --- /dev/null +++ b/src/test/java/redis/clients/jedis/csc/VersionTest.java @@ -0,0 +1,30 @@ +package redis.clients.jedis.csc; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class VersionTest { + + @Test + public void compareSameVersions() { + RedisVersion a = new RedisVersion("5.2.4"); + RedisVersion b = new RedisVersion("5.2.4"); + assertEquals(a, b); + + RedisVersion c = new RedisVersion("5.2.0.0"); + RedisVersion d = new RedisVersion("5.2"); + assertEquals(a, b); + } + + @Test + public void compareDifferentVersions() { + RedisVersion a = new RedisVersion("5.2.4"); + RedisVersion b = new RedisVersion("5.1.4"); + assertEquals(1, a.compareTo(b)); + + RedisVersion c = new RedisVersion("5.2.4"); + RedisVersion d = new RedisVersion("5.2.5"); + assertEquals(-1, c.compareTo(d)); + } +}