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;