From 0e18dd96f55dac5893c0bf6d979c849457a78eee Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 15 Mar 2016 00:21:44 +0000 Subject: [PATCH 01/47] Make RedisRunner Accepts redis-server cli options WIP --- src/test/java/org/redisson/RedisRunner.java | 44 ++++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index dfdf7bdc6..0be95b479 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -3,20 +3,60 @@ package org.redisson; import java.io.File; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; public class RedisRunner { + private static final String redisFolder = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; + private static final String redisBinary = redisFolder + "redis-server.exe"; + + private final List options = new ArrayList<>(); - public static Process runRedis(String configPath) throws IOException, InterruptedException { + { + options.add(Optional.ofNullable(System.getProperty("redisBinary")).orElse(redisBinary)); + } + + /** + * To change the redisBinary system property for running the test, + * use argLine option from surefire plugin: + * + * $ mvn -DargLine="-DredisBinary=`which redis-server`" -Punit-test clean \ + * verify + * + * @param configPath + * @return Process running redis instance + * @throws IOException + * @throws InterruptedException + * @see + * + * http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#argLine + */ + public static Process runRedisWithConfigFile(String configPath) throws IOException, InterruptedException { URL resource = RedisRunner.class.getResource(configPath); - ProcessBuilder master = new ProcessBuilder(redisFolder + "redis-server.exe", resource.getFile().substring(1)); + ProcessBuilder master = new ProcessBuilder( + Optional.ofNullable(System.getProperty("redisBinary")).orElse(redisBinary), + resource.getFile().substring(1)); master.directory(new File(redisFolder)); Process p = master.start(); Thread.sleep(1000); return p; } + public RedisRunner withPort(int port) { + this.options.add("--port " + port); + return this; + } + + public Process run() throws IOException, InterruptedException { + ProcessBuilder master = new ProcessBuilder(options); + master.directory(new File(redisBinary).getParentFile()); + Process p = master.start(); + Thread.sleep(1000); + return p; + } } From 424758bed4373b07e4ad68a215b5ea68a3274282 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 15 Mar 2016 11:19:22 +0300 Subject: [PATCH 02/47] few comments added --- src/main/java/org/redisson/core/RMultimap.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/core/RMultimap.java b/src/main/java/org/redisson/core/RMultimap.java index b7c7d3c7a..7f39b53ce 100644 --- a/src/main/java/org/redisson/core/RMultimap.java +++ b/src/main/java/org/redisson/core/RMultimap.java @@ -120,6 +120,7 @@ public interface RMultimap extends RExpirable, RMultimapAsync { *

Once this method returns, {@code key} will not be mapped to any values, * so it will not appear in {@link #keySet()}, {@link #asMap()}, or any other * views. + *

Use {@link #fastRemove()} if values are not needed.

* * @return the values that were removed (possibly empty). The returned * collection may be modifiable, but updating it will have no @@ -186,7 +187,7 @@ public interface RMultimap extends RExpirable, RMultimapAsync { /** * Removes keys from map by one operation * - * Works faster than RMap.remove but not returning + * Works faster than RMultimap.remove but not returning * the value associated with key * * @param keys From 5b1818516b667c234002faeb5a97bd42fc768064 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 15 Mar 2016 23:12:33 +0000 Subject: [PATCH 03/47] added all the redis v3.0.7 config options as enum type --- src/test/java/org/redisson/RedisRunner.java | 169 +++++++++++++++++++- 1 file changed, 162 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index 0be95b479..8d59e57de 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -3,20 +3,148 @@ package org.redisson; import java.io.File; import java.io.IOException; import java.net.URL; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.Optional; +import java.util.stream.Collectors; public class RedisRunner { + enum OPTIONS { + BINARY_PATH, + DAEMONIZE, + PIDFILE, + PORT, + TCP_BACKLOG, + BIND, + UNIXSOCKET, + UNIXSOCKETPERM, + TIMEOUT, + TCP_KEEPALIVE, + LOGLEVEL, + LOGFILE, + SYSLOG_ENABLED, + SYSLOG_IDENT, + SYSLOG_FACILITY, + DATABASES, + SAVE, + STOP_WRITES_ON_BGSAVE_ERROR, + RDBCOMPRESSION, + RDBCHECKSUM, + DBFILENAME, + DIR, + SLAVEOF, + MASTERAUTH, + SLAVE_SERVE_STALE_DATA, + SLAVE_READ_ONLY, + REPL_DISKLESS_SYNC, + REPL_DISKLESS_SYNC_DELAY, + REPL_PING_SLAVE_PERIOD, + REPL_TIMEOUT, + REPL_DISABLE_TCP_NODELAY, + REPL_BACKLOG_SIZE, + REPL_BACKLOG_TTL, + SLAVE_PRIORITY, + MIN_SLAVES_TO_WRITE, + MIN_SLAVES_MAX_LAG, + REQUREPASS, + RENAME_COMMAND, + MAXCLIENTS, + MAXMEMORY, + MAXMEMORY_POLICY, + MAXMEMORY_SAMPLE, + APPEND_ONLY, + APPENDFILENAME, + APPENDFSYNC, + NO_APPENDFSYNC_ON_REWRITE, + AUTO_AOF_REWRITE_PERCENTAGE, + AUTO_AOF_REWRITE_MIN_SIZE, + AOF_LOAD_TRUNCATED, + LUA_TIME_LIMIT, + CLUSTER_ENABLED, + CLUSTER_CONFIG_FILE, + CLUSTER_NODE_TIMEOUT, + CLUSTER_SLAVE_VALIDITY_FACTOR, + CLUSTER_MIGRATION_BARRIER, + CLUSTER_REQUIRE_FULL_COVERAGE, + SLOWLOG_LOG_SLOWER_THAN, + SLOWLOG_MAX_LEN, + LATENCY_MONITOR_THRESHOLD, + NOFITY_KEYSPACE_EVENTS, + HASH_MAX_ZIPLIST_ENTRIES, + HASH_MAX_ZIPLIST_VALUE, + LIST_MAX_ZIPLIST_ENTRIES, + LIST_MAX_ZIPLIST_VALUE, + SET_MAX_INTSET_ENTRIES, + ZSET_MAX_ZIPLIST_ENTRIES, + ZSET_MAX_ZIPLIST_VALUE, + HLL_SPARSE_MAX_BYTES, + ACTIVEREHASHING, + CLIENT_OUTPUT_BUFFER_LIMIT,//MULTI + HZ, + AOF_REWRITE_INCREMENTAL_FSYNC + } + + enum LOGLEVEL_OPTIONS { + DEBUG, + VERBOSE, + NOTICE, + WARNING + } + + enum SYSLOG_FACILITY_OPTIONS { + USER, + LOCAL0, + LOCAL1, + LOCAL2, + LOCAL3, + LOCAL4, + LOCAL5, + LOCAL6, + LOCAL7 + } + + enum MAX_MEMORY_POLICY_OPTIONS { + VOLATILE_LRU, + ALLKEYS_LRU, + VOLATILE_RANDOM, + ALLKEYS_RANDOM, + VOLATILE_TTL, + NOEVICTION + } + enum APPEND_FSYNC_MODE_OPTIONS { + ALWAYS, + EVERYSEC, + NO + } + + enum KEYSPACE_EVENTS_OPTIONS { + K, + E, + g, + $, + l, + s, + h, + z, + x, + e, + A + } + + enum CLIENT_CLASS_OPTIONS { + NORMAL, + SLAVE, + PUBSUB + } private static final String redisFolder = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; private static final String redisBinary = redisFolder + "redis-server.exe"; - private final List options = new ArrayList<>(); + private final LinkedHashMap options = new LinkedHashMap<>(); { - options.add(Optional.ofNullable(System.getProperty("redisBinary")).orElse(redisBinary)); + options.put(OPTIONS.BINARY_PATH, Optional.ofNullable(System.getProperty("redisBinary")).orElse(redisBinary)); } /** @@ -46,13 +174,40 @@ public class RedisRunner { return p; } - public RedisRunner withPort(int port) { - this.options.add("--port " + port); + private void addConfigOption(OPTIONS option, String... args) { + StringBuilder sb = new StringBuilder("--"); + sb.append(option.toString().replaceAll("_", "-").toLowerCase()); + sb.append(" "); + sb.append(Arrays.stream(args).collect(Collectors.joining(" "))); + this.options.put(option, sb.toString()); + } + + private String convertBoolean(boolean b) { + return b ? "yes" : "no"; + } + + public RedisRunner daemonize(boolean daemonize) { + addConfigOption(OPTIONS.DAEMONIZE, convertBoolean(daemonize)); + return this; + } + + public RedisRunner pidfile(String pidfile) { + addConfigOption(OPTIONS.PIDFILE, pidfile); + return this; + } + + public RedisRunner port(int port) { + addConfigOption(OPTIONS.PORT, port + ""); + return this; + } + + public RedisRunner tcpBacklog(int tcpBacklog) { + addConfigOption(OPTIONS.TCP_BACKLOG, "" + tcpBacklog); return this; } public Process run() throws IOException, InterruptedException { - ProcessBuilder master = new ProcessBuilder(options); + ProcessBuilder master = new ProcessBuilder(options.values().stream().collect(Collectors.joining(" "))); master.directory(new File(redisBinary).getParentFile()); Process p = master.start(); Thread.sleep(1000); From 76c5ece35abc4a50b0a5e878c0576152a1ad7e89 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 15 Mar 2016 23:13:04 +0000 Subject: [PATCH 04/47] changed existing test code to use runRedisWithConfigFile method --- src/test/java/org/redisson/RedissonMultiLockTest.java | 6 +++--- src/test/java/org/redisson/RedissonTest.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/redisson/RedissonMultiLockTest.java b/src/test/java/org/redisson/RedissonMultiLockTest.java index d7a2e2a2e..22e0cd1e7 100644 --- a/src/test/java/org/redisson/RedissonMultiLockTest.java +++ b/src/test/java/org/redisson/RedissonMultiLockTest.java @@ -17,9 +17,9 @@ public class RedissonMultiLockTest { @Test public void test() throws IOException, InterruptedException { - Process redis1 = RedisRunner.runRedis("/redis_multiLock_test_instance1.conf"); - Process redis2 = RedisRunner.runRedis("/redis_multiLock_test_instance2.conf"); - Process redis3 = RedisRunner.runRedis("/redis_multiLock_test_instance3.conf"); + Process redis1 = RedisRunner.runRedisWithConfigFile("/redis_multiLock_test_instance1.conf"); + Process redis2 = RedisRunner.runRedisWithConfigFile("/redis_multiLock_test_instance2.conf"); + Process redis3 = RedisRunner.runRedisWithConfigFile("/redis_multiLock_test_instance3.conf"); NioEventLoopGroup group = new NioEventLoopGroup(); Config config1 = new Config(); diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 449669448..73588a006 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -61,7 +61,7 @@ public class RedissonTest { @Test(expected = RedisOutOfMemoryException.class) public void testMemoryScript() throws IOException, InterruptedException { - Process p = RedisRunner.runRedis("/redis_oom_test.conf"); + Process p = RedisRunner.runRedisWithConfigFile("/redis_oom_test.conf"); Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6319").setTimeout(100000); @@ -78,7 +78,7 @@ public class RedissonTest { @Test(expected = RedisOutOfMemoryException.class) public void testMemoryCommand() throws IOException, InterruptedException { - Process p = RedisRunner.runRedis("/redis_oom_test.conf"); + Process p = RedisRunner.runRedisWithConfigFile("/redis_oom_test.conf"); Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6319").setTimeout(100000); @@ -97,7 +97,7 @@ public class RedissonTest { @Test public void testConnectionListener() throws IOException, InterruptedException, TimeoutException { - Process p = RedisRunner.runRedis("/redis_connectionListener_test.conf"); + Process p = RedisRunner.runRedisWithConfigFile("/redis_connectionListener_test.conf"); final AtomicInteger connectCounter = new AtomicInteger(); final AtomicInteger disconnectCounter = new AtomicInteger(); @@ -134,7 +134,7 @@ public class RedissonTest { } catch (Exception e) { } - p = RedisRunner.runRedis("/redis_connectionListener_test.conf"); + p = RedisRunner.runRedisWithConfigFile("/redis_connectionListener_test.conf"); r.getBucket("1").get(); From d9c97a881e26b2558bdc7c5283ffd5a2809479d2 Mon Sep 17 00:00:00 2001 From: Dmitry Bobrov Date: Wed, 16 Mar 2016 04:11:12 +0300 Subject: [PATCH 05/47] Stop HashedWheelTimer after exception in constructor --- .../redisson/cluster/ClusterConnectionManager.java | 6 +----- .../connection/MasterSlaveConnectionManager.java | 14 +++++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java index 22729473c..d0bc0cf36 100644 --- a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java +++ b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java @@ -96,11 +96,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { } if (lastPartitions.isEmpty()) { - try { - group.shutdownGracefully().await(); - } catch (Exception e) { - // skip it - } + stopThreads(); throw new RedisConnectionException("Can't connect to servers!", lastException); } diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 5b6698058..1fb69f386 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -202,11 +202,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { try { initEntry(config); } catch (RuntimeException e) { - try { - group.shutdownGracefully().await(); - } catch (Exception e1) { - // skip - } + stopThreads(); throw e; } } @@ -741,4 +737,12 @@ public class MasterSlaveConnectionManager implements ConnectionManager { return connectionEventsHub; } + protected void stopThreads() { + timer.stop(); + try { + group.shutdownGracefully().await(); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } } From d53ebb1153f7189b73de10ac278bacbe9453a8db Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 16 Mar 2016 13:32:47 +0300 Subject: [PATCH 06/47] Publish/Subscribe exhausted connection poll error message fixed. #438 --- src/main/java/org/redisson/connection/pool/ConnectionPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/connection/pool/ConnectionPool.java b/src/main/java/org/redisson/connection/pool/ConnectionPool.java index b29baa9f9..58998a2dd 100644 --- a/src/main/java/org/redisson/connection/pool/ConnectionPool.java +++ b/src/main/java/org/redisson/connection/pool/ConnectionPool.java @@ -153,7 +153,7 @@ abstract class ConnectionPool { } } - StringBuilder errorMsg = new StringBuilder("Connection pool exhausted! All connections are busy. Try to increase connection pool size."); + StringBuilder errorMsg = new StringBuilder("Publish/Subscribe connection pool exhausted! All connections are busy. Try to increase Publish/Subscribe connection pool size."); // if (!freezed.isEmpty()) { // errorMsg.append(" Disconnected hosts: " + freezed); // } From 225f2bd78a94774d4257e86be8671088be611bc1 Mon Sep 17 00:00:00 2001 From: Dmitry Bobrov Date: Wed, 16 Mar 2016 17:58:59 +0300 Subject: [PATCH 07/47] Untie tests from Windows absolute paths --- src/test/java/org/redisson/RedisRunner.java | 67 +++++++++++++++++-- .../org/redisson/RedissonMultiLockTest.java | 6 +- src/test/java/org/redisson/RedissonTest.java | 4 +- .../redis_connectionListener_test.conf | 2 +- .../redis_multiLock_test_instance1.conf | 2 +- .../redis_multiLock_test_instance2.conf | 2 +- .../redis_multiLock_test_instance3.conf | 2 +- src/test/resources/redis_oom_test.conf | 2 +- 8 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index dfdf7bdc6..72508d834 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -1,22 +1,77 @@ package org.redisson; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.*; +import java.util.Objects; public class RedisRunner { - private static final String redisFolder = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; + public static final int REDIS_EXIT_CODE; + + private static final String defaultRedisDirectory = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; + private static final String defaultRedisExecutable = defaultRedisDirectory + "redis-server.exe"; + private static final String redisExecutable; + + static { + String path = System.getenv("PATH"); + if (path != null) { + String os = System.getProperty("os.name"); + String pathSeparator = ":"; + String executableName = "redis-server"; + if (os.toLowerCase().startsWith("windows")) { + pathSeparator = ";"; + executableName = "redis-server.exe"; + REDIS_EXIT_CODE = 1; + } else { + REDIS_EXIT_CODE = 0; + } + + String[] pathEntries = path.split(pathSeparator); + Path fullExecutablePath = null; + for (String pathEntry : pathEntries) { + fullExecutablePath = Paths.get(pathEntry, executableName); + if (Files.exists(fullExecutablePath)) { + break; + } + } + + if (fullExecutablePath != null) { + redisExecutable = fullExecutablePath.toString(); + } else { + redisExecutable = defaultRedisExecutable; + } + } else { + redisExecutable = defaultRedisExecutable; + REDIS_EXIT_CODE = 1; + } + + if (!new File(redisExecutable).exists()) { + throw new RuntimeException("Redis executable not found"); + } + } public static Process runRedis(String configPath) throws IOException, InterruptedException { URL resource = RedisRunner.class.getResource(configPath); + String fullConfigPath = Paths.get(resource.getFile()).toAbsolutePath().toString(); - ProcessBuilder master = new ProcessBuilder(redisFolder + "redis-server.exe", resource.getFile().substring(1)); - master.directory(new File(redisFolder)); + ProcessBuilder master = new ProcessBuilder(redisExecutable, fullConfigPath, "--dir", getWorkingDirectory()); + master.directory(new File(getWorkingDirectory())); Process p = master.start(); Thread.sleep(1000); + + if (!p.isAlive()) { + throw new RuntimeException("Redis executable stopped with exit code " + p.exitValue()); + } + return p; } - + private static String getWorkingDirectory() { + if (redisExecutable.equals(defaultRedisExecutable)) { + return defaultRedisDirectory; + } + return System.getProperty("user.dir"); + } } diff --git a/src/test/java/org/redisson/RedissonMultiLockTest.java b/src/test/java/org/redisson/RedissonMultiLockTest.java index d7a2e2a2e..fab1544e1 100644 --- a/src/test/java/org/redisson/RedissonMultiLockTest.java +++ b/src/test/java/org/redisson/RedissonMultiLockTest.java @@ -66,13 +66,13 @@ public class RedissonMultiLockTest { lock.unlock(); redis1.destroy(); - assertThat(redis1.waitFor()).isEqualTo(1); + assertThat(redis1.waitFor()).isEqualTo(RedisRunner.REDIS_EXIT_CODE); redis2.destroy(); - assertThat(redis2.waitFor()).isEqualTo(1); + assertThat(redis2.waitFor()).isEqualTo(RedisRunner.REDIS_EXIT_CODE); redis3.destroy(); - assertThat(redis3.waitFor()).isEqualTo(1); + assertThat(redis3.waitFor()).isEqualTo(RedisRunner.REDIS_EXIT_CODE); } } diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 449669448..0b92ab911 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -127,7 +127,7 @@ public class RedissonTest { r.getBucket("1").get(); p.destroy(); - Assert.assertEquals(1, p.waitFor()); + Assert.assertEquals(RedisRunner.REDIS_EXIT_CODE, p.waitFor()); try { r.getBucket("1").get(); @@ -141,7 +141,7 @@ public class RedissonTest { r.shutdown(); p.destroy(); - Assert.assertEquals(1, p.waitFor()); + Assert.assertEquals(RedisRunner.REDIS_EXIT_CODE, p.waitFor()); await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(connectCounter.get()).isEqualTo(2)); await().until(() -> assertThat(disconnectCounter.get()).isEqualTo(1)); diff --git a/src/test/resources/redis_connectionListener_test.conf b/src/test/resources/redis_connectionListener_test.conf index d91a36715..fad6ea95e 100644 --- a/src/test/resources/redis_connectionListener_test.conf +++ b/src/test/resources/redis_connectionListener_test.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -dir "C:\\Devel\\projects\\redis" +# dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_multiLock_test_instance1.conf b/src/test/resources/redis_multiLock_test_instance1.conf index fea303d1e..296d25aae 100644 --- a/src/test/resources/redis_multiLock_test_instance1.conf +++ b/src/test/resources/redis_multiLock_test_instance1.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -dir "C:\\Devel\\projects\\redis" +# dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_multiLock_test_instance2.conf b/src/test/resources/redis_multiLock_test_instance2.conf index 1105e15c8..963a6c23b 100644 --- a/src/test/resources/redis_multiLock_test_instance2.conf +++ b/src/test/resources/redis_multiLock_test_instance2.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -dir "C:\\Devel\\projects\\redis" +# dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_multiLock_test_instance3.conf b/src/test/resources/redis_multiLock_test_instance3.conf index dda5c9b08..10b261c0a 100644 --- a/src/test/resources/redis_multiLock_test_instance3.conf +++ b/src/test/resources/redis_multiLock_test_instance3.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -dir "C:\\Devel\\projects\\redis" +# dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_oom_test.conf b/src/test/resources/redis_oom_test.conf index 19e4bb24e..0f3374148 100644 --- a/src/test/resources/redis_oom_test.conf +++ b/src/test/resources/redis_oom_test.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -dir "C:\\Devel\\projects\\redis" +# dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# From c1695e0ac8734353dc8130bb43c87b115c7f1ff9 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Thu, 17 Mar 2016 00:09:57 +0000 Subject: [PATCH 08/47] WIP update --- src/test/java/org/redisson/RedisRunner.java | 128 ++++++++++++++------ 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index 8d59e57de..d4c4db1b6 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -10,13 +10,14 @@ import java.util.stream.Collectors; public class RedisRunner { - enum OPTIONS { + public enum OPTIONS { + BINARY_PATH, DAEMONIZE, PIDFILE, PORT, TCP_BACKLOG, - BIND, + BIND(true), UNIXSOCKET, UNIXSOCKETPERM, TIMEOUT, @@ -27,7 +28,7 @@ public class RedisRunner { SYSLOG_IDENT, SYSLOG_FACILITY, DATABASES, - SAVE, + SAVE(true), STOP_WRITES_ON_BGSAVE_ERROR, RDBCOMPRESSION, RDBCHECKSUM, @@ -48,7 +49,7 @@ public class RedisRunner { MIN_SLAVES_TO_WRITE, MIN_SLAVES_MAX_LAG, REQUREPASS, - RENAME_COMMAND, + RENAME_COMMAND(true), MAXCLIENTS, MAXMEMORY, MAXMEMORY_POLICY, @@ -80,19 +81,37 @@ public class RedisRunner { ZSET_MAX_ZIPLIST_VALUE, HLL_SPARSE_MAX_BYTES, ACTIVEREHASHING, - CLIENT_OUTPUT_BUFFER_LIMIT,//MULTI + CLIENT_OUTPUT_BUFFER_LIMIT$NORMAL, + CLIENT_OUTPUT_BUFFER_LIMIT$SLAVE, + CLIENT_OUTPUT_BUFFER_LIMIT$PUBSUB, HZ, - AOF_REWRITE_INCREMENTAL_FSYNC + AOF_REWRITE_INCREMENTAL_FSYNC; + + private final boolean allowMutiple; + + private OPTIONS() { + this.allowMutiple = false; + } + + private OPTIONS(boolean allowMutiple) { + this.allowMutiple = allowMutiple; + } + + public boolean isAllowMultiple() { + return allowMutiple; + } } - - enum LOGLEVEL_OPTIONS { + + public enum LOGLEVEL_OPTIONS { + DEBUG, VERBOSE, NOTICE, WARNING } - - enum SYSLOG_FACILITY_OPTIONS { + + public enum SYSLOG_FACILITY_OPTIONS { + USER, LOCAL0, LOCAL1, @@ -103,8 +122,9 @@ public class RedisRunner { LOCAL6, LOCAL7 } - - enum MAX_MEMORY_POLICY_OPTIONS { + + public enum MAX_MEMORY_POLICY_OPTIONS { + VOLATILE_LRU, ALLKEYS_LRU, VOLATILE_RANDOM, @@ -112,14 +132,16 @@ public class RedisRunner { VOLATILE_TTL, NOEVICTION } - - enum APPEND_FSYNC_MODE_OPTIONS { + + public enum APPEND_FSYNC_MODE_OPTIONS { + ALWAYS, EVERYSEC, NO } - - enum KEYSPACE_EVENTS_OPTIONS { + + public enum KEYSPACE_EVENTS_OPTIONS { + K, E, g, @@ -132,21 +154,18 @@ public class RedisRunner { e, A } - - enum CLIENT_CLASS_OPTIONS { - NORMAL, - SLAVE, - PUBSUB - } + private static final String redisFolder = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; private static final String redisBinary = redisFolder + "redis-server.exe"; - + private final LinkedHashMap options = new LinkedHashMap<>(); { - options.put(OPTIONS.BINARY_PATH, Optional.ofNullable(System.getProperty("redisBinary")).orElse(redisBinary)); + options.put(OPTIONS.BINARY_PATH, + Optional.ofNullable(System.getProperty("redisBinary")) + .orElse(redisBinary)); } - + /** * To change the redisBinary system property for running the test, * use argLine option from surefire plugin: @@ -166,7 +185,8 @@ public class RedisRunner { URL resource = RedisRunner.class.getResource(configPath); ProcessBuilder master = new ProcessBuilder( - Optional.ofNullable(System.getProperty("redisBinary")).orElse(redisBinary), + Optional.ofNullable(System.getProperty("redisBinary")) + .orElse(redisBinary), resource.getFile().substring(1)); master.directory(new File(redisFolder)); Process p = master.start(); @@ -175,39 +195,73 @@ public class RedisRunner { } private void addConfigOption(OPTIONS option, String... args) { - StringBuilder sb = new StringBuilder("--"); - sb.append(option.toString().replaceAll("_", "-").toLowerCase()); - sb.append(" "); - sb.append(Arrays.stream(args).collect(Collectors.joining(" "))); - this.options.put(option, sb.toString()); + StringBuilder sb = new StringBuilder(" --") + .append(option.toString() + .replaceAll("_", "-") + .replaceAll("\\$", " ") + .toLowerCase()) + .append(" ") + .append(Arrays.stream(args) + .collect(Collectors.joining(" "))); + this.options.put(option, + option.isAllowMultiple() + ? sb.insert(0, this.options.getOrDefault(option, "")).toString() + : sb.toString()); } - + private String convertBoolean(boolean b) { return b ? "yes" : "no"; } - + public RedisRunner daemonize(boolean daemonize) { addConfigOption(OPTIONS.DAEMONIZE, convertBoolean(daemonize)); return this; } - + public RedisRunner pidfile(String pidfile) { addConfigOption(OPTIONS.PIDFILE, pidfile); return this; } - + public RedisRunner port(int port) { addConfigOption(OPTIONS.PORT, port + ""); return this; } - + public RedisRunner tcpBacklog(int tcpBacklog) { addConfigOption(OPTIONS.TCP_BACKLOG, "" + tcpBacklog); return this; } + + public RedisRunner bind(String bind) { + addConfigOption(OPTIONS.BIND, bind); + return this; + } + + public RedisRunner timeout(long timeout) { + addConfigOption(OPTIONS.TIMEOUT, "" + timeout); + return this; + } + + public RedisRunner tcpKeepalive(long tcpKeepalive) { + addConfigOption(OPTIONS.TCP_KEEPALIVE, "" + tcpKeepalive); + return this; + } + public RedisRunner loglevel(LOGLEVEL_OPTIONS loglevel) { + addConfigOption(OPTIONS.LOGLEVEL, loglevel.toString()); + return this; + } + + public RedisRunner logfile(String logfile) { + addConfigOption(OPTIONS.LOGLEVEL, logfile); + return this; + } + public Process run() throws IOException, InterruptedException { - ProcessBuilder master = new ProcessBuilder(options.values().stream().collect(Collectors.joining(" "))); + ProcessBuilder master = new ProcessBuilder( + options.values().stream() + .collect(Collectors.joining())); master.directory(new File(redisBinary).getParentFile()); Process p = master.start(); Thread.sleep(1000); From 6ef404b2782ee6a1779ef5ab3e6045d4277ec81b Mon Sep 17 00:00:00 2001 From: jackygurui Date: Thu, 17 Mar 2016 00:13:55 +0000 Subject: [PATCH 09/47] WIP update --- src/test/java/org/redisson/RedisRunner.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index d4c4db1b6..f04506021 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -238,6 +238,16 @@ public class RedisRunner { return this; } + public RedisRunner unixsocket(String unixsocket) { + addConfigOption(OPTIONS.UNIXSOCKET, unixsocket); + return this; + } + + public RedisRunner unixsocketperm(String unixsocketperm) { + addConfigOption(OPTIONS.UNIXSOCKETPERM, unixsocketperm); + return this; + } + public RedisRunner timeout(long timeout) { addConfigOption(OPTIONS.TIMEOUT, "" + timeout); return this; From 95f1a5d9ce708f4c2e05f9af4664b67b1ca0ffc5 Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Thu, 17 Mar 2016 11:53:33 +0300 Subject: [PATCH 10/47] Revert "Untie tests from Windows absolute paths" --- src/test/java/org/redisson/RedisRunner.java | 67 ++----------------- .../org/redisson/RedissonMultiLockTest.java | 6 +- src/test/java/org/redisson/RedissonTest.java | 4 +- .../redis_connectionListener_test.conf | 2 +- .../redis_multiLock_test_instance1.conf | 2 +- .../redis_multiLock_test_instance2.conf | 2 +- .../redis_multiLock_test_instance3.conf | 2 +- src/test/resources/redis_oom_test.conf | 2 +- 8 files changed, 16 insertions(+), 71 deletions(-) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index 72508d834..dfdf7bdc6 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -1,77 +1,22 @@ package org.redisson; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.*; -import java.util.Objects; public class RedisRunner { - public static final int REDIS_EXIT_CODE; - - private static final String defaultRedisDirectory = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; - private static final String defaultRedisExecutable = defaultRedisDirectory + "redis-server.exe"; - private static final String redisExecutable; - - static { - String path = System.getenv("PATH"); - if (path != null) { - String os = System.getProperty("os.name"); - String pathSeparator = ":"; - String executableName = "redis-server"; - if (os.toLowerCase().startsWith("windows")) { - pathSeparator = ";"; - executableName = "redis-server.exe"; - REDIS_EXIT_CODE = 1; - } else { - REDIS_EXIT_CODE = 0; - } - - String[] pathEntries = path.split(pathSeparator); - Path fullExecutablePath = null; - for (String pathEntry : pathEntries) { - fullExecutablePath = Paths.get(pathEntry, executableName); - if (Files.exists(fullExecutablePath)) { - break; - } - } - - if (fullExecutablePath != null) { - redisExecutable = fullExecutablePath.toString(); - } else { - redisExecutable = defaultRedisExecutable; - } - } else { - redisExecutable = defaultRedisExecutable; - REDIS_EXIT_CODE = 1; - } - - if (!new File(redisExecutable).exists()) { - throw new RuntimeException("Redis executable not found"); - } - } + private static final String redisFolder = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; public static Process runRedis(String configPath) throws IOException, InterruptedException { URL resource = RedisRunner.class.getResource(configPath); - String fullConfigPath = Paths.get(resource.getFile()).toAbsolutePath().toString(); - ProcessBuilder master = new ProcessBuilder(redisExecutable, fullConfigPath, "--dir", getWorkingDirectory()); - master.directory(new File(getWorkingDirectory())); + ProcessBuilder master = new ProcessBuilder(redisFolder + "redis-server.exe", resource.getFile().substring(1)); + master.directory(new File(redisFolder)); Process p = master.start(); Thread.sleep(1000); - - if (!p.isAlive()) { - throw new RuntimeException("Redis executable stopped with exit code " + p.exitValue()); - } - return p; } - private static String getWorkingDirectory() { - if (redisExecutable.equals(defaultRedisExecutable)) { - return defaultRedisDirectory; - } - return System.getProperty("user.dir"); - } + } diff --git a/src/test/java/org/redisson/RedissonMultiLockTest.java b/src/test/java/org/redisson/RedissonMultiLockTest.java index fab1544e1..d7a2e2a2e 100644 --- a/src/test/java/org/redisson/RedissonMultiLockTest.java +++ b/src/test/java/org/redisson/RedissonMultiLockTest.java @@ -66,13 +66,13 @@ public class RedissonMultiLockTest { lock.unlock(); redis1.destroy(); - assertThat(redis1.waitFor()).isEqualTo(RedisRunner.REDIS_EXIT_CODE); + assertThat(redis1.waitFor()).isEqualTo(1); redis2.destroy(); - assertThat(redis2.waitFor()).isEqualTo(RedisRunner.REDIS_EXIT_CODE); + assertThat(redis2.waitFor()).isEqualTo(1); redis3.destroy(); - assertThat(redis3.waitFor()).isEqualTo(RedisRunner.REDIS_EXIT_CODE); + assertThat(redis3.waitFor()).isEqualTo(1); } } diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 0b92ab911..449669448 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -127,7 +127,7 @@ public class RedissonTest { r.getBucket("1").get(); p.destroy(); - Assert.assertEquals(RedisRunner.REDIS_EXIT_CODE, p.waitFor()); + Assert.assertEquals(1, p.waitFor()); try { r.getBucket("1").get(); @@ -141,7 +141,7 @@ public class RedissonTest { r.shutdown(); p.destroy(); - Assert.assertEquals(RedisRunner.REDIS_EXIT_CODE, p.waitFor()); + Assert.assertEquals(1, p.waitFor()); await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(connectCounter.get()).isEqualTo(2)); await().until(() -> assertThat(disconnectCounter.get()).isEqualTo(1)); diff --git a/src/test/resources/redis_connectionListener_test.conf b/src/test/resources/redis_connectionListener_test.conf index fad6ea95e..d91a36715 100644 --- a/src/test/resources/redis_connectionListener_test.conf +++ b/src/test/resources/redis_connectionListener_test.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -# dir "C:\\Devel\\projects\\redis" +dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_multiLock_test_instance1.conf b/src/test/resources/redis_multiLock_test_instance1.conf index 296d25aae..fea303d1e 100644 --- a/src/test/resources/redis_multiLock_test_instance1.conf +++ b/src/test/resources/redis_multiLock_test_instance1.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -# dir "C:\\Devel\\projects\\redis" +dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_multiLock_test_instance2.conf b/src/test/resources/redis_multiLock_test_instance2.conf index 963a6c23b..1105e15c8 100644 --- a/src/test/resources/redis_multiLock_test_instance2.conf +++ b/src/test/resources/redis_multiLock_test_instance2.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -# dir "C:\\Devel\\projects\\redis" +dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_multiLock_test_instance3.conf b/src/test/resources/redis_multiLock_test_instance3.conf index 10b261c0a..dda5c9b08 100644 --- a/src/test/resources/redis_multiLock_test_instance3.conf +++ b/src/test/resources/redis_multiLock_test_instance3.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -# dir "C:\\Devel\\projects\\redis" +dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# diff --git a/src/test/resources/redis_oom_test.conf b/src/test/resources/redis_oom_test.conf index 0f3374148..19e4bb24e 100644 --- a/src/test/resources/redis_oom_test.conf +++ b/src/test/resources/redis_oom_test.conf @@ -150,7 +150,7 @@ rdbchecksum yes # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. -# dir "C:\\Devel\\projects\\redis" +dir "C:\\Devel\\projects\\redis" ################################# REPLICATION ################################# From 2ca0f2d00853a50b9717648d62e8ebfa921779d4 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 17 Mar 2016 18:12:44 +0300 Subject: [PATCH 11/47] revRank and revRankAsync methods added. #433 --- .../org/redisson/RedissonScoredSortedSet.java | 10 ++++++++++ .../redisson/client/protocol/RedisCommands.java | 1 + .../java/org/redisson/core/RScoredSortedSet.java | 2 ++ .../org/redisson/core/RScoredSortedSetAsync.java | 2 ++ .../org/redisson/RedissonScoredSortedSetTest.java | 15 +++++++++++++++ 5 files changed, 30 insertions(+) diff --git a/src/main/java/org/redisson/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index e6751cc78..e4755f679 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -480,4 +480,14 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE_ENTRY, getName(), startValue, endValue, "WITHSCORES", "LIMIT", offset, count); } + @Override + public Future revRankAsync(V o) { + return commandExecutor.readAsync(getName(), codec, RedisCommands.ZREVRANK_INT, getName(), o); + } + + @Override + public int revRank(V o) { + return get(revRankAsync(o)); + } + } diff --git a/src/main/java/org/redisson/client/protocol/RedisCommands.java b/src/main/java/org/redisson/client/protocol/RedisCommands.java index f4b741800..7d62ca62e 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -83,6 +83,7 @@ public interface RedisCommands { RedisCommand ZSCORE_CONTAINS = new RedisCommand("ZSCORE", new BooleanNotNullReplayConvertor(), 2); RedisStrictCommand ZSCORE = new RedisStrictCommand("ZSCORE", new DoubleReplayConvertor(), 2); RedisCommand ZRANK_INT = new RedisCommand("ZRANK", new IntegerReplayConvertor(), 2); + RedisCommand ZREVRANK_INT = new RedisCommand("ZREVRANK", new IntegerReplayConvertor(), 2); RedisStrictCommand ZRANK = new RedisStrictCommand("ZRANK", 2); RedisCommand ZRANGE_SINGLE = new RedisCommand("ZRANGE", new ObjectFirstResultReplayDecoder()); RedisCommand> ZRANGE = new RedisCommand>("ZRANGE", new ObjectListReplayDecoder()); diff --git a/src/main/java/org/redisson/core/RScoredSortedSet.java b/src/main/java/org/redisson/core/RScoredSortedSet.java index ddaeef9b9..ce4e6fccf 100644 --- a/src/main/java/org/redisson/core/RScoredSortedSet.java +++ b/src/main/java/org/redisson/core/RScoredSortedSet.java @@ -37,6 +37,8 @@ public interface RScoredSortedSet extends RScoredSortedSetAsync, Iterable< int removeRangeByRank(int startIndex, int endIndex); int rank(V o); + + int revRank(V o); Double getScore(V o); diff --git a/src/main/java/org/redisson/core/RScoredSortedSetAsync.java b/src/main/java/org/redisson/core/RScoredSortedSetAsync.java index fe6ea081a..83e41798e 100644 --- a/src/main/java/org/redisson/core/RScoredSortedSetAsync.java +++ b/src/main/java/org/redisson/core/RScoredSortedSetAsync.java @@ -39,6 +39,8 @@ public interface RScoredSortedSetAsync extends RExpirableAsync { Future removeRangeByRankAsync(int startIndex, int endIndex); Future rankAsync(V o); + + Future revRankAsync(V o); Future getScoreAsync(V o); diff --git a/src/test/java/org/redisson/RedissonScoredSortedSetTest.java b/src/test/java/org/redisson/RedissonScoredSortedSetTest.java index 8da3dacd0..3aeab3d72 100644 --- a/src/test/java/org/redisson/RedissonScoredSortedSetTest.java +++ b/src/test/java/org/redisson/RedissonScoredSortedSetTest.java @@ -161,6 +161,21 @@ public class RedissonScoredSortedSetTest extends BaseTest { Assert.assertEquals(3, (int)set.rank("d")); } + + @Test + public void testRevRank() { + RScoredSortedSet set = redisson.getScoredSortedSet("simple"); + set.add(0.1, "a"); + set.add(0.2, "b"); + set.add(0.3, "c"); + set.add(0.4, "d"); + set.add(0.5, "e"); + set.add(0.6, "f"); + set.add(0.7, "g"); + + Assert.assertEquals(1, (int)set.revRank("f")); + } + @Test public void testAddAsync() throws InterruptedException, ExecutionException { From 4caa8f48294588a5fd3941c2192eb5d4a5ee1785 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Fri, 18 Mar 2016 01:40:26 +0000 Subject: [PATCH 12/47] Completed RedisRunner mods * RedisRunner now able to launch redis-server using command line options. * Added RedisProcess class which wraps Process class and ensures unified exit code value in both windows and Mac/Unix/Linux. * Removed argLine configuration from surefire plugin in pom.xml because it shadows the command line options. * Changed test files to launch redis-server programmatically. --- pom.xml | 3 - src/test/java/org/redisson/RedisRunner.java | 441 ++++++++++++++++-- .../org/redisson/RedissonMultiLockTest.java | 35 +- src/test/java/org/redisson/RedissonTest.java | 31 +- 4 files changed, 444 insertions(+), 66 deletions(-) diff --git a/pom.xml b/pom.xml index 2411aa90c..8907f379a 100644 --- a/pom.xml +++ b/pom.xml @@ -351,9 +351,6 @@ org.apache.maven.plugins maven-surefire-plugin 2.16 - - -Dfile.encoding=utf-8 - diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index f04506021..54a152693 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -1,16 +1,24 @@ package org.redisson; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Inet4Address; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; public class RedisRunner { - public enum OPTIONS { + public enum REDIS_OPTIONS { BINARY_PATH, DAEMONIZE, @@ -54,7 +62,7 @@ public class RedisRunner { MAXMEMORY, MAXMEMORY_POLICY, MAXMEMORY_SAMPLE, - APPEND_ONLY, + APPENDONLY, APPENDFILENAME, APPENDFSYNC, NO_APPENDFSYNC_ON_REWRITE, @@ -89,11 +97,11 @@ public class RedisRunner { private final boolean allowMutiple; - private OPTIONS() { + private REDIS_OPTIONS() { this.allowMutiple = false; } - private OPTIONS(boolean allowMutiple) { + private REDIS_OPTIONS(boolean allowMutiple) { this.allowMutiple = allowMutiple; } @@ -155,15 +163,17 @@ public class RedisRunner { A } - private static final String redisFolder = "C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\"; - private static final String redisBinary = redisFolder + "redis-server.exe"; + private static final String redisBinary; - private final LinkedHashMap options = new LinkedHashMap<>(); + private final LinkedHashMap options = new LinkedHashMap<>(); + + static { + redisBinary = Optional.ofNullable(System.getProperty("redisBinary")) + .orElse("C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\redis-server.exe"); + } { - options.put(OPTIONS.BINARY_PATH, - Optional.ofNullable(System.getProperty("redisBinary")) - .orElse(redisBinary)); + this.options.put(REDIS_OPTIONS.BINARY_PATH, redisBinary); } /** @@ -181,21 +191,40 @@ public class RedisRunner { * * http://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#argLine */ - public static Process runRedisWithConfigFile(String configPath) throws IOException, InterruptedException { + public static RedisProcess runRedisWithConfigFile(String configPath) throws IOException, InterruptedException { URL resource = RedisRunner.class.getResource(configPath); + return runWithOptions(redisBinary, resource.getFile()); + } - ProcessBuilder master = new ProcessBuilder( - Optional.ofNullable(System.getProperty("redisBinary")) - .orElse(redisBinary), - resource.getFile().substring(1)); - master.directory(new File(redisFolder)); + private static RedisProcess runWithOptions(String... options) throws IOException, InterruptedException { + System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(options)); + ProcessBuilder master = new ProcessBuilder(options) + .redirectErrorStream(true) + .directory(new File(redisBinary).getParentFile()); Process p = master.start(); + new Thread(() -> { + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + try { + while (p.isAlive() && (line = reader.readLine()) != null) { + System.out.println("REDIS PROCESS: " + line); + } + } catch (IOException ex) { + System.out.println("Exception: " + ex.getLocalizedMessage()); + } + }).start(); Thread.sleep(1000); - return p; + return new RedisProcess(p); + } + + + + public RedisProcess run() throws IOException, InterruptedException { + return runWithOptions(options.values().toArray(new String[0])); } - private void addConfigOption(OPTIONS option, String... args) { - StringBuilder sb = new StringBuilder(" --") + private void addConfigOption(REDIS_OPTIONS option, String... args) { + StringBuilder sb = new StringBuilder("--") .append(option.toString() .replaceAll("_", "-") .replaceAll("\\$", " ") @@ -203,7 +232,7 @@ public class RedisRunner { .append(" ") .append(Arrays.stream(args) .collect(Collectors.joining(" "))); - this.options.put(option, + this.options.put(option, option.isAllowMultiple() ? sb.insert(0, this.options.getOrDefault(option, "")).toString() : sb.toString()); @@ -214,68 +243,394 @@ public class RedisRunner { } public RedisRunner daemonize(boolean daemonize) { - addConfigOption(OPTIONS.DAEMONIZE, convertBoolean(daemonize)); + addConfigOption(REDIS_OPTIONS.DAEMONIZE, convertBoolean(daemonize)); return this; } public RedisRunner pidfile(String pidfile) { - addConfigOption(OPTIONS.PIDFILE, pidfile); + addConfigOption(REDIS_OPTIONS.PIDFILE, pidfile); return this; } public RedisRunner port(int port) { - addConfigOption(OPTIONS.PORT, port + ""); + addConfigOption(REDIS_OPTIONS.PORT, "" + port); return this; } - public RedisRunner tcpBacklog(int tcpBacklog) { - addConfigOption(OPTIONS.TCP_BACKLOG, "" + tcpBacklog); + public RedisRunner tcpBacklog(long tcpBacklog) { + addConfigOption(REDIS_OPTIONS.TCP_BACKLOG, "" + tcpBacklog); return this; } public RedisRunner bind(String bind) { - addConfigOption(OPTIONS.BIND, bind); + addConfigOption(REDIS_OPTIONS.BIND, bind); return this; } public RedisRunner unixsocket(String unixsocket) { - addConfigOption(OPTIONS.UNIXSOCKET, unixsocket); + addConfigOption(REDIS_OPTIONS.UNIXSOCKET, unixsocket); return this; } - public RedisRunner unixsocketperm(String unixsocketperm) { - addConfigOption(OPTIONS.UNIXSOCKETPERM, unixsocketperm); + public RedisRunner unixsocketperm(int unixsocketperm) { + addConfigOption(REDIS_OPTIONS.UNIXSOCKETPERM, "" + unixsocketperm); return this; } public RedisRunner timeout(long timeout) { - addConfigOption(OPTIONS.TIMEOUT, "" + timeout); + addConfigOption(REDIS_OPTIONS.TIMEOUT, "" + timeout); return this; } public RedisRunner tcpKeepalive(long tcpKeepalive) { - addConfigOption(OPTIONS.TCP_KEEPALIVE, "" + tcpKeepalive); + addConfigOption(REDIS_OPTIONS.TCP_KEEPALIVE, "" + tcpKeepalive); return this; } - + public RedisRunner loglevel(LOGLEVEL_OPTIONS loglevel) { - addConfigOption(OPTIONS.LOGLEVEL, loglevel.toString()); + addConfigOption(REDIS_OPTIONS.LOGLEVEL, loglevel.toString()); return this; } - + public RedisRunner logfile(String logfile) { - addConfigOption(OPTIONS.LOGLEVEL, logfile); + addConfigOption(REDIS_OPTIONS.LOGLEVEL, logfile); return this; } - public Process run() throws IOException, InterruptedException { - ProcessBuilder master = new ProcessBuilder( - options.values().stream() - .collect(Collectors.joining())); - master.directory(new File(redisBinary).getParentFile()); - Process p = master.start(); - Thread.sleep(1000); - return p; + public RedisRunner syslogEnabled(boolean syslogEnabled) { + addConfigOption(REDIS_OPTIONS.SYSLOG_ENABLED, convertBoolean(syslogEnabled)); + return this; + } + + public RedisRunner syslogIdent(String syslogIdent) { + addConfigOption(REDIS_OPTIONS.SYSLOG_IDENT, syslogIdent); + return this; + } + + public RedisRunner syslogFacility(SYSLOG_FACILITY_OPTIONS syslogFacility) { + addConfigOption(REDIS_OPTIONS.SYSLOG_IDENT, syslogFacility.toString()); + return this; + } + + public RedisRunner databases(int databases) { + addConfigOption(REDIS_OPTIONS.DATABASES, "" + databases); + return this; + } + + public RedisRunner save(long seconds, long changes) { + addConfigOption(REDIS_OPTIONS.SAVE, "" + seconds, "" + changes); + return this; + } + + public RedisRunner stopWritesOnBgsaveError(boolean stopWritesOnBgsaveError) { + addConfigOption(REDIS_OPTIONS.STOP_WRITES_ON_BGSAVE_ERROR, convertBoolean(stopWritesOnBgsaveError)); + return this; + } + + public RedisRunner rdbcompression(boolean rdbcompression) { + addConfigOption(REDIS_OPTIONS.RDBCOMPRESSION, convertBoolean(rdbcompression)); + return this; + } + + public RedisRunner rdbchecksum(boolean rdbchecksum) { + addConfigOption(REDIS_OPTIONS.RDBCHECKSUM, convertBoolean(rdbchecksum)); + return this; + } + + public RedisRunner dbfilename(String dbfilename) { + addConfigOption(REDIS_OPTIONS.DBFILENAME, dbfilename); + return this; + } + + public RedisRunner dir(String dir) { + addConfigOption(REDIS_OPTIONS.DIR, dir); + return this; + } + + public RedisRunner slaveof(Inet4Address masterip, int port) { + addConfigOption(REDIS_OPTIONS.SLAVEOF, masterip.getHostAddress(), "" + port); + return this; + } + + public RedisRunner masterauth(String masterauth) { + addConfigOption(REDIS_OPTIONS.MASTERAUTH, masterauth); + return this; + } + + public RedisRunner slaveServeStaleData(boolean slaveServeStaleData) { + addConfigOption(REDIS_OPTIONS.SLAVE_SERVE_STALE_DATA, convertBoolean(slaveServeStaleData)); + return this; + } + + public RedisRunner slaveReadOnly(boolean slaveReadOnly) { + addConfigOption(REDIS_OPTIONS.SLAVE_READ_ONLY, convertBoolean(slaveReadOnly)); + return this; + } + + public RedisRunner replDisklessSync(boolean replDisklessSync) { + addConfigOption(REDIS_OPTIONS.REPL_DISKLESS_SYNC, convertBoolean(replDisklessSync)); + return this; + } + + public RedisRunner replDisklessSyncDelay(long replDisklessSyncDelay) { + addConfigOption(REDIS_OPTIONS.REPL_DISKLESS_SYNC_DELAY, "" + replDisklessSyncDelay); + return this; + } + + public RedisRunner replPingSlavePeriod(long replPingSlavePeriod) { + addConfigOption(REDIS_OPTIONS.REPL_PING_SLAVE_PERIOD, "" + replPingSlavePeriod); + return this; + } + + public RedisRunner replTimeout(long replTimeout) { + addConfigOption(REDIS_OPTIONS.REPL_TIMEOUT, "" + replTimeout); + return this; + } + + public RedisRunner replDisableTcpNodelay(boolean replDisableTcpNodelay) { + addConfigOption(REDIS_OPTIONS.REPL_DISABLE_TCP_NODELAY, convertBoolean(replDisableTcpNodelay)); + return this; + } + + public RedisRunner replBacklogSize(String replBacklogSize) { + addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_SIZE, "" + replBacklogSize); + return this; + } + + public RedisRunner replBacklogTtl(long replBacklogTtl) { + addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_TTL, "" + replBacklogTtl); + return this; + } + + public RedisRunner slavePriority(long slavePriority) { + addConfigOption(REDIS_OPTIONS.SLAVE_PRIORITY, "" + slavePriority); + return this; + } + + public RedisRunner minSlaveToWrite(long minSlaveToWrite) { + addConfigOption(REDIS_OPTIONS.MIN_SLAVES_TO_WRITE, "" + minSlaveToWrite); + return this; + } + + public RedisRunner minSlaveMaxLag(long minSlaveMaxLag) { + addConfigOption(REDIS_OPTIONS.MIN_SLAVES_MAX_LAG, "" + minSlaveMaxLag); + return this; + } + + public RedisRunner requirepass(String requirepass) { + addConfigOption(REDIS_OPTIONS.REQUREPASS, requirepass); + return this; + } + + public RedisRunner renameCommand(String renameCommand) { + addConfigOption(REDIS_OPTIONS.RENAME_COMMAND, renameCommand); + return this; + } + + public RedisRunner maxclients(long maxclients) { + addConfigOption(REDIS_OPTIONS.MAXCLIENTS, "" + maxclients); + return this; + } + + public RedisRunner maxmemory(String maxmemory) { + addConfigOption(REDIS_OPTIONS.MAXMEMORY, "" + maxmemory); + return this; + } + + public RedisRunner maxmemoryPolicy(MAX_MEMORY_POLICY_OPTIONS maxmemoryPolicy) { + addConfigOption(REDIS_OPTIONS.MAXMEMORY, maxmemoryPolicy.toString()); + return this; + } + + public RedisRunner maxmemorySamples(long maxmemorySamples) { + addConfigOption(REDIS_OPTIONS.MAXMEMORY, "" + maxmemorySamples); + return this; + } + + public RedisRunner appendonly(boolean appendonly) { + addConfigOption(REDIS_OPTIONS.APPENDONLY, convertBoolean(appendonly)); + return this; + } + + public RedisRunner appendfilename(String appendfilename) { + addConfigOption(REDIS_OPTIONS.APPENDFILENAME, appendfilename); + return this; + } + + public RedisRunner appendfsync(APPEND_FSYNC_MODE_OPTIONS appendfsync) { + addConfigOption(REDIS_OPTIONS.APPENDFSYNC, appendfsync.toString()); + return this; + } + + public RedisRunner noAppendfsyncOnRewrite(boolean noAppendfsyncOnRewrite) { + addConfigOption(REDIS_OPTIONS.NO_APPENDFSYNC_ON_REWRITE, convertBoolean(noAppendfsyncOnRewrite)); + return this; + } + + public RedisRunner autoAofRewritePercentage(int autoAofRewritePercentage) { + addConfigOption(REDIS_OPTIONS.AUTO_AOF_REWRITE_PERCENTAGE, "" + autoAofRewritePercentage); + return this; + } + + public RedisRunner autoAofRewriteMinSize(String autoAofRewriteMinSize) { + addConfigOption(REDIS_OPTIONS.AUTO_AOF_REWRITE_MIN_SIZE, autoAofRewriteMinSize); + return this; + } + + public RedisRunner aofLoadTruncated(boolean aofLoadTruncated) { + addConfigOption(REDIS_OPTIONS.AOF_LOAD_TRUNCATED, convertBoolean(aofLoadTruncated)); + return this; + } + + public RedisRunner luaTimeLimit(long luaTimeLimit) { + addConfigOption(REDIS_OPTIONS.AOF_LOAD_TRUNCATED, "" + luaTimeLimit); + return this; + } + + public RedisRunner clusterEnabled(boolean clusterEnabled) { + addConfigOption(REDIS_OPTIONS.CLUSTER_ENABLED, convertBoolean(clusterEnabled)); + return this; + } + + public RedisRunner clusterConfigFile(String clusterConfigFile) { + addConfigOption(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, clusterConfigFile); + return this; + } + + public RedisRunner clusterNodeTimeout(long clusterNodeTimeout) { + addConfigOption(REDIS_OPTIONS.CLUSTER_NODE_TIMEOUT, "" + clusterNodeTimeout); + return this; + } + + public RedisRunner clusterSlaveValidityFactor(long clusterSlaveValidityFactor) { + addConfigOption(REDIS_OPTIONS.CLUSTER_SLAVE_VALIDITY_FACTOR, "" + clusterSlaveValidityFactor); + return this; + } + + public RedisRunner clusterMigrationBarrier(long clusterMigrationBarrier) { + addConfigOption(REDIS_OPTIONS.CLUSTER_MIGRATION_BARRIER, "" + clusterMigrationBarrier); + return this; + } + + public RedisRunner clusterRequireFullCoverage(boolean clusterRequireFullCoverage) { + addConfigOption(REDIS_OPTIONS.CLUSTER_REQUIRE_FULL_COVERAGE, convertBoolean(clusterRequireFullCoverage)); + return this; + } + + public RedisRunner slowlogLogSlowerThan(long slowlogLogSlowerThan) { + addConfigOption(REDIS_OPTIONS.SLOWLOG_LOG_SLOWER_THAN, "" + slowlogLogSlowerThan); + return this; + } + + public RedisRunner slowlogMaxLen(long slowlogMaxLen) { + addConfigOption(REDIS_OPTIONS.SLOWLOG_MAX_LEN, "" + slowlogMaxLen); + return this; + } + + public RedisRunner latencyMonitorThreshold(long latencyMonitorThreshold) { + addConfigOption(REDIS_OPTIONS.LATENCY_MONITOR_THRESHOLD, "" + latencyMonitorThreshold); + return this; + } + + public RedisRunner notifyKeyspaceEvents(KEYSPACE_EVENTS_OPTIONS notifyKeyspaceEvents) { + String existing = this.options.getOrDefault(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, ""); + addConfigOption(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, + existing.contains(notifyKeyspaceEvents.toString()) + ? existing + : (existing + notifyKeyspaceEvents.toString())); + return this; + } + + public RedisRunner hashMaxZiplistEntries(long hashMaxZiplistEntries) { + addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_ENTRIES, "" + hashMaxZiplistEntries); + return this; + } + + public RedisRunner hashMaxZiplistValue(long hashMaxZiplistValue) { + addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_VALUE, "" + hashMaxZiplistValue); + return this; + } + + public RedisRunner listMaxZiplistEntries(long listMaxZiplistEntries) { + addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_ENTRIES, "" + listMaxZiplistEntries); + return this; + } + + public RedisRunner listMaxZiplistValue(long listMaxZiplistValue) { + addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_VALUE, "" + listMaxZiplistValue); + return this; + } + + public RedisRunner setMaxIntsetEntries(long setMaxIntsetEntries) { + addConfigOption(REDIS_OPTIONS.SET_MAX_INTSET_ENTRIES, "" + setMaxIntsetEntries); + return this; + } + + public RedisRunner zsetMaxZiplistEntries(long zsetMaxZiplistEntries) { + addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_ENTRIES, "" + zsetMaxZiplistEntries); + return this; + } + + public RedisRunner zsetMaxZiplistValue(long zsetMaxZiplistValue) { + addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_VALUE, "" + zsetMaxZiplistValue); + return this; + } + + public RedisRunner hllSparseMaxBytes(long hllSparseMaxBytes) { + addConfigOption(REDIS_OPTIONS.HLL_SPARSE_MAX_BYTES, "" + hllSparseMaxBytes); + return this; } + public RedisRunner activerehashing(boolean activerehashing) { + addConfigOption(REDIS_OPTIONS.ACTIVEREHASHING, convertBoolean(activerehashing)); + return this; + } + + public RedisRunner clientOutputBufferLimit$Normal(String hardLimit, String softLimit, long softSeconds) { + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$NORMAL, hardLimit, softLimit, "" + softSeconds); + return this; + } + + public RedisRunner clientOutputBufferLimit$Slave(String hardLimit, String softLimit, long softSeconds) { + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$SLAVE, hardLimit, softLimit, "" + softSeconds); + return this; + } + + public RedisRunner clientOutputBufferLimit$Pubsub(String hardLimit, String softLimit, long softSeconds) { + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$PUBSUB, hardLimit, softLimit, "" + softSeconds); + return this; + } + + public RedisRunner hz(int hz) { + addConfigOption(REDIS_OPTIONS.HZ, "" + hz); + return this; + } + + public RedisRunner aofRewriteIncrementalFsync(boolean aofRewriteIncrementalFsync) { + addConfigOption(REDIS_OPTIONS.AOF_REWRITE_INCREMENTAL_FSYNC, convertBoolean(aofRewriteIncrementalFsync)); + return this; + } + + public static final class RedisProcess { + private final Process redisProcess; + + private RedisProcess(Process redisProcess) { + this.redisProcess = redisProcess; + } + + public int stop() throws InterruptedException { + redisProcess.destroy(); + int exitCode = redisProcess.waitFor(); + return exitCode == 1 && isWindows() ? 0 : exitCode ; + } + + public Process getRedisProcess() { + return redisProcess; + } + + private boolean isWindows() { + return System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH).contains("win"); + } + } + } diff --git a/src/test/java/org/redisson/RedissonMultiLockTest.java b/src/test/java/org/redisson/RedissonMultiLockTest.java index 22e0cd1e7..9b5446a80 100644 --- a/src/test/java/org/redisson/RedissonMultiLockTest.java +++ b/src/test/java/org/redisson/RedissonMultiLockTest.java @@ -12,14 +12,15 @@ import org.redisson.core.RLock; import org.redisson.core.RedissonMultiLock; import io.netty.channel.nio.NioEventLoopGroup; +import org.redisson.RedisRunner.RedisProcess; public class RedissonMultiLockTest { @Test public void test() throws IOException, InterruptedException { - Process redis1 = RedisRunner.runRedisWithConfigFile("/redis_multiLock_test_instance1.conf"); - Process redis2 = RedisRunner.runRedisWithConfigFile("/redis_multiLock_test_instance2.conf"); - Process redis3 = RedisRunner.runRedisWithConfigFile("/redis_multiLock_test_instance3.conf"); + RedisProcess redis1 = redisTestMultilockInstance1(); + RedisProcess redis2 = redisTestMultilockInstance2(); + RedisProcess redis3 = redisTestMultilockInstance3(); NioEventLoopGroup group = new NioEventLoopGroup(); Config config1 = new Config(); @@ -65,14 +66,28 @@ public class RedissonMultiLockTest { lock.unlock(); - redis1.destroy(); - assertThat(redis1.waitFor()).isEqualTo(1); + assertThat(redis1.stop()).isEqualTo(0); - redis2.destroy(); - assertThat(redis2.waitFor()).isEqualTo(1); + assertThat(redis2.stop()).isEqualTo(0); - redis3.destroy(); - assertThat(redis3.waitFor()).isEqualTo(1); + assertThat(redis3.stop()).isEqualTo(0); + } + + private RedisProcess redisTestMultilockInstance1() throws IOException, InterruptedException { + return new RedisRunner() + .port(6320) + .run(); + } + + private RedisProcess redisTestMultilockInstance2() throws IOException, InterruptedException { + return new RedisRunner() + .port(6321) + .run(); + } + + private RedisProcess redisTestMultilockInstance3() throws IOException, InterruptedException { + return new RedisRunner() + .port(6322) + .run(); } - } diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 73588a006..1cbeff418 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -41,6 +41,7 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.GenericFutureListener; import static com.jayway.awaitility.Awaitility.*; +import org.redisson.RedisRunner.RedisProcess; public class RedissonTest { @@ -61,7 +62,7 @@ public class RedissonTest { @Test(expected = RedisOutOfMemoryException.class) public void testMemoryScript() throws IOException, InterruptedException { - Process p = RedisRunner.runRedisWithConfigFile("/redis_oom_test.conf"); + RedisProcess p = redisTestSmallMemory(); Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6319").setTimeout(100000); @@ -72,13 +73,13 @@ public class RedissonTest { r.getMap("test").put("" + i, "" + i); } } finally { - p.destroy(); + p.stop(); } } @Test(expected = RedisOutOfMemoryException.class) public void testMemoryCommand() throws IOException, InterruptedException { - Process p = RedisRunner.runRedisWithConfigFile("/redis_oom_test.conf"); + RedisProcess p = redisTestSmallMemory(); Config config = new Config(); config.useSingleServer().setAddress("127.0.0.1:6319").setTimeout(100000); @@ -89,7 +90,7 @@ public class RedissonTest { r.getMap("test").fastPut("" + i, "" + i); } } finally { - p.destroy(); + p.stop(); } } @@ -97,7 +98,7 @@ public class RedissonTest { @Test public void testConnectionListener() throws IOException, InterruptedException, TimeoutException { - Process p = RedisRunner.runRedisWithConfigFile("/redis_connectionListener_test.conf"); + RedisProcess p = redisTestConnection(); final AtomicInteger connectCounter = new AtomicInteger(); final AtomicInteger disconnectCounter = new AtomicInteger(); @@ -126,22 +127,20 @@ public class RedissonTest { assertThat(id).isNotZero(); r.getBucket("1").get(); - p.destroy(); - Assert.assertEquals(1, p.waitFor()); + Assert.assertEquals(0, p.stop()); try { r.getBucket("1").get(); } catch (Exception e) { } - p = RedisRunner.runRedisWithConfigFile("/redis_connectionListener_test.conf"); + p = redisTestConnection(); r.getBucket("1").get(); r.shutdown(); - p.destroy(); - Assert.assertEquals(1, p.waitFor()); + Assert.assertEquals(0, p.stop()); await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(connectCounter.get()).isEqualTo(2)); await().until(() -> assertThat(disconnectCounter.get()).isEqualTo(1)); @@ -282,5 +281,17 @@ public class RedissonTest { r.shutdown(); } + private RedisProcess redisTestSmallMemory() throws IOException, InterruptedException { + return new RedisRunner() + .maxmemory("1mb") + .port(6319) + .run(); + } + private RedisProcess redisTestConnection() throws IOException, InterruptedException { + return new RedisRunner() + .port(6319) + .run(); + } + } From c40168ed89324feef688afc917205149f79f73ac Mon Sep 17 00:00:00 2001 From: Rui Gu Date: Fri, 18 Mar 2016 14:23:14 +0000 Subject: [PATCH 13/47] RedisRunner mods * removed the need to do String aggregation on parameters. * fixed ```invalid parameter``` error on windows environment. --- src/test/java/org/redisson/RedisRunner.java | 114 ++++++++++---------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index 54a152693..3995ac351 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -6,14 +6,10 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.Inet4Address; import java.net.URL; -import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; public class RedisRunner { @@ -169,7 +165,7 @@ public class RedisRunner { static { redisBinary = Optional.ofNullable(System.getProperty("redisBinary")) - .orElse("C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\redis-server.exe"); + .orElse("C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\redis-server.exe") + " "; } { @@ -197,8 +193,13 @@ public class RedisRunner { } private static RedisProcess runWithOptions(String... options) throws IOException, InterruptedException { - System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(options)); - ProcessBuilder master = new ProcessBuilder(options) + System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(Arrays.stream(options) + .collect(Collectors.joining()) + .split(" "))); + ProcessBuilder master = new ProcessBuilder( + Arrays.stream(options) + .collect(Collectors.joining()) + .split(" ")) .redirectErrorStream(true) .directory(new File(redisBinary).getParentFile()); Process p = master.start(); @@ -217,20 +218,18 @@ public class RedisRunner { return new RedisProcess(p); } - - public RedisProcess run() throws IOException, InterruptedException { return runWithOptions(options.values().toArray(new String[0])); } - private void addConfigOption(REDIS_OPTIONS option, String... args) { + private void addConfigOption(REDIS_OPTIONS option, Object... args) { StringBuilder sb = new StringBuilder("--") .append(option.toString() .replaceAll("_", "-") .replaceAll("\\$", " ") .toLowerCase()) .append(" ") - .append(Arrays.stream(args) + .append(Arrays.stream(args).map(Object::toString) .collect(Collectors.joining(" "))); this.options.put(option, option.isAllowMultiple() @@ -253,12 +252,12 @@ public class RedisRunner { } public RedisRunner port(int port) { - addConfigOption(REDIS_OPTIONS.PORT, "" + port); + addConfigOption(REDIS_OPTIONS.PORT, port); return this; } public RedisRunner tcpBacklog(long tcpBacklog) { - addConfigOption(REDIS_OPTIONS.TCP_BACKLOG, "" + tcpBacklog); + addConfigOption(REDIS_OPTIONS.TCP_BACKLOG, tcpBacklog); return this; } @@ -273,17 +272,17 @@ public class RedisRunner { } public RedisRunner unixsocketperm(int unixsocketperm) { - addConfigOption(REDIS_OPTIONS.UNIXSOCKETPERM, "" + unixsocketperm); + addConfigOption(REDIS_OPTIONS.UNIXSOCKETPERM, unixsocketperm); return this; } public RedisRunner timeout(long timeout) { - addConfigOption(REDIS_OPTIONS.TIMEOUT, "" + timeout); + addConfigOption(REDIS_OPTIONS.TIMEOUT, timeout); return this; } public RedisRunner tcpKeepalive(long tcpKeepalive) { - addConfigOption(REDIS_OPTIONS.TCP_KEEPALIVE, "" + tcpKeepalive); + addConfigOption(REDIS_OPTIONS.TCP_KEEPALIVE, tcpKeepalive); return this; } @@ -313,12 +312,12 @@ public class RedisRunner { } public RedisRunner databases(int databases) { - addConfigOption(REDIS_OPTIONS.DATABASES, "" + databases); + addConfigOption(REDIS_OPTIONS.DATABASES, databases); return this; } public RedisRunner save(long seconds, long changes) { - addConfigOption(REDIS_OPTIONS.SAVE, "" + seconds, "" + changes); + addConfigOption(REDIS_OPTIONS.SAVE, seconds, changes); return this; } @@ -348,7 +347,7 @@ public class RedisRunner { } public RedisRunner slaveof(Inet4Address masterip, int port) { - addConfigOption(REDIS_OPTIONS.SLAVEOF, masterip.getHostAddress(), "" + port); + addConfigOption(REDIS_OPTIONS.SLAVEOF, masterip.getHostAddress(), port); return this; } @@ -373,17 +372,17 @@ public class RedisRunner { } public RedisRunner replDisklessSyncDelay(long replDisklessSyncDelay) { - addConfigOption(REDIS_OPTIONS.REPL_DISKLESS_SYNC_DELAY, "" + replDisklessSyncDelay); + addConfigOption(REDIS_OPTIONS.REPL_DISKLESS_SYNC_DELAY, replDisklessSyncDelay); return this; } public RedisRunner replPingSlavePeriod(long replPingSlavePeriod) { - addConfigOption(REDIS_OPTIONS.REPL_PING_SLAVE_PERIOD, "" + replPingSlavePeriod); + addConfigOption(REDIS_OPTIONS.REPL_PING_SLAVE_PERIOD, replPingSlavePeriod); return this; } public RedisRunner replTimeout(long replTimeout) { - addConfigOption(REDIS_OPTIONS.REPL_TIMEOUT, "" + replTimeout); + addConfigOption(REDIS_OPTIONS.REPL_TIMEOUT, replTimeout); return this; } @@ -393,27 +392,27 @@ public class RedisRunner { } public RedisRunner replBacklogSize(String replBacklogSize) { - addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_SIZE, "" + replBacklogSize); + addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_SIZE, replBacklogSize); return this; } public RedisRunner replBacklogTtl(long replBacklogTtl) { - addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_TTL, "" + replBacklogTtl); + addConfigOption(REDIS_OPTIONS.REPL_BACKLOG_TTL, replBacklogTtl); return this; } public RedisRunner slavePriority(long slavePriority) { - addConfigOption(REDIS_OPTIONS.SLAVE_PRIORITY, "" + slavePriority); + addConfigOption(REDIS_OPTIONS.SLAVE_PRIORITY, slavePriority); return this; } public RedisRunner minSlaveToWrite(long minSlaveToWrite) { - addConfigOption(REDIS_OPTIONS.MIN_SLAVES_TO_WRITE, "" + minSlaveToWrite); + addConfigOption(REDIS_OPTIONS.MIN_SLAVES_TO_WRITE, minSlaveToWrite); return this; } public RedisRunner minSlaveMaxLag(long minSlaveMaxLag) { - addConfigOption(REDIS_OPTIONS.MIN_SLAVES_MAX_LAG, "" + minSlaveMaxLag); + addConfigOption(REDIS_OPTIONS.MIN_SLAVES_MAX_LAG, minSlaveMaxLag); return this; } @@ -428,12 +427,12 @@ public class RedisRunner { } public RedisRunner maxclients(long maxclients) { - addConfigOption(REDIS_OPTIONS.MAXCLIENTS, "" + maxclients); + addConfigOption(REDIS_OPTIONS.MAXCLIENTS, maxclients); return this; } public RedisRunner maxmemory(String maxmemory) { - addConfigOption(REDIS_OPTIONS.MAXMEMORY, "" + maxmemory); + addConfigOption(REDIS_OPTIONS.MAXMEMORY, maxmemory); return this; } @@ -443,7 +442,7 @@ public class RedisRunner { } public RedisRunner maxmemorySamples(long maxmemorySamples) { - addConfigOption(REDIS_OPTIONS.MAXMEMORY, "" + maxmemorySamples); + addConfigOption(REDIS_OPTIONS.MAXMEMORY, maxmemorySamples); return this; } @@ -468,7 +467,7 @@ public class RedisRunner { } public RedisRunner autoAofRewritePercentage(int autoAofRewritePercentage) { - addConfigOption(REDIS_OPTIONS.AUTO_AOF_REWRITE_PERCENTAGE, "" + autoAofRewritePercentage); + addConfigOption(REDIS_OPTIONS.AUTO_AOF_REWRITE_PERCENTAGE, autoAofRewritePercentage); return this; } @@ -483,7 +482,7 @@ public class RedisRunner { } public RedisRunner luaTimeLimit(long luaTimeLimit) { - addConfigOption(REDIS_OPTIONS.AOF_LOAD_TRUNCATED, "" + luaTimeLimit); + addConfigOption(REDIS_OPTIONS.AOF_LOAD_TRUNCATED, luaTimeLimit); return this; } @@ -498,17 +497,17 @@ public class RedisRunner { } public RedisRunner clusterNodeTimeout(long clusterNodeTimeout) { - addConfigOption(REDIS_OPTIONS.CLUSTER_NODE_TIMEOUT, "" + clusterNodeTimeout); + addConfigOption(REDIS_OPTIONS.CLUSTER_NODE_TIMEOUT, clusterNodeTimeout); return this; } public RedisRunner clusterSlaveValidityFactor(long clusterSlaveValidityFactor) { - addConfigOption(REDIS_OPTIONS.CLUSTER_SLAVE_VALIDITY_FACTOR, "" + clusterSlaveValidityFactor); + addConfigOption(REDIS_OPTIONS.CLUSTER_SLAVE_VALIDITY_FACTOR, clusterSlaveValidityFactor); return this; } public RedisRunner clusterMigrationBarrier(long clusterMigrationBarrier) { - addConfigOption(REDIS_OPTIONS.CLUSTER_MIGRATION_BARRIER, "" + clusterMigrationBarrier); + addConfigOption(REDIS_OPTIONS.CLUSTER_MIGRATION_BARRIER, clusterMigrationBarrier); return this; } @@ -518,17 +517,17 @@ public class RedisRunner { } public RedisRunner slowlogLogSlowerThan(long slowlogLogSlowerThan) { - addConfigOption(REDIS_OPTIONS.SLOWLOG_LOG_SLOWER_THAN, "" + slowlogLogSlowerThan); + addConfigOption(REDIS_OPTIONS.SLOWLOG_LOG_SLOWER_THAN, slowlogLogSlowerThan); return this; } public RedisRunner slowlogMaxLen(long slowlogMaxLen) { - addConfigOption(REDIS_OPTIONS.SLOWLOG_MAX_LEN, "" + slowlogMaxLen); + addConfigOption(REDIS_OPTIONS.SLOWLOG_MAX_LEN, slowlogMaxLen); return this; } public RedisRunner latencyMonitorThreshold(long latencyMonitorThreshold) { - addConfigOption(REDIS_OPTIONS.LATENCY_MONITOR_THRESHOLD, "" + latencyMonitorThreshold); + addConfigOption(REDIS_OPTIONS.LATENCY_MONITOR_THRESHOLD, latencyMonitorThreshold); return this; } @@ -536,48 +535,48 @@ public class RedisRunner { String existing = this.options.getOrDefault(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, ""); addConfigOption(REDIS_OPTIONS.CLUSTER_CONFIG_FILE, existing.contains(notifyKeyspaceEvents.toString()) - ? existing - : (existing + notifyKeyspaceEvents.toString())); + ? existing + : (existing + notifyKeyspaceEvents.toString())); return this; } public RedisRunner hashMaxZiplistEntries(long hashMaxZiplistEntries) { - addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_ENTRIES, "" + hashMaxZiplistEntries); + addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_ENTRIES, hashMaxZiplistEntries); return this; } public RedisRunner hashMaxZiplistValue(long hashMaxZiplistValue) { - addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_VALUE, "" + hashMaxZiplistValue); + addConfigOption(REDIS_OPTIONS.HASH_MAX_ZIPLIST_VALUE, hashMaxZiplistValue); return this; } public RedisRunner listMaxZiplistEntries(long listMaxZiplistEntries) { - addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_ENTRIES, "" + listMaxZiplistEntries); + addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_ENTRIES, listMaxZiplistEntries); return this; } public RedisRunner listMaxZiplistValue(long listMaxZiplistValue) { - addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_VALUE, "" + listMaxZiplistValue); + addConfigOption(REDIS_OPTIONS.LIST_MAX_ZIPLIST_VALUE, listMaxZiplistValue); return this; } public RedisRunner setMaxIntsetEntries(long setMaxIntsetEntries) { - addConfigOption(REDIS_OPTIONS.SET_MAX_INTSET_ENTRIES, "" + setMaxIntsetEntries); + addConfigOption(REDIS_OPTIONS.SET_MAX_INTSET_ENTRIES, setMaxIntsetEntries); return this; } public RedisRunner zsetMaxZiplistEntries(long zsetMaxZiplistEntries) { - addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_ENTRIES, "" + zsetMaxZiplistEntries); + addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_ENTRIES, zsetMaxZiplistEntries); return this; } public RedisRunner zsetMaxZiplistValue(long zsetMaxZiplistValue) { - addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_VALUE, "" + zsetMaxZiplistValue); + addConfigOption(REDIS_OPTIONS.ZSET_MAX_ZIPLIST_VALUE, zsetMaxZiplistValue); return this; } public RedisRunner hllSparseMaxBytes(long hllSparseMaxBytes) { - addConfigOption(REDIS_OPTIONS.HLL_SPARSE_MAX_BYTES, "" + hllSparseMaxBytes); + addConfigOption(REDIS_OPTIONS.HLL_SPARSE_MAX_BYTES, hllSparseMaxBytes); return this; } @@ -587,22 +586,22 @@ public class RedisRunner { } public RedisRunner clientOutputBufferLimit$Normal(String hardLimit, String softLimit, long softSeconds) { - addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$NORMAL, hardLimit, softLimit, "" + softSeconds); + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$NORMAL, hardLimit, softLimit, softSeconds); return this; } public RedisRunner clientOutputBufferLimit$Slave(String hardLimit, String softLimit, long softSeconds) { - addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$SLAVE, hardLimit, softLimit, "" + softSeconds); + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$SLAVE, hardLimit, softLimit, softSeconds); return this; } public RedisRunner clientOutputBufferLimit$Pubsub(String hardLimit, String softLimit, long softSeconds) { - addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$PUBSUB, hardLimit, softLimit, "" + softSeconds); + addConfigOption(REDIS_OPTIONS.CLIENT_OUTPUT_BUFFER_LIMIT$PUBSUB, hardLimit, softLimit, softSeconds); return this; } public RedisRunner hz(int hz) { - addConfigOption(REDIS_OPTIONS.HZ, "" + hz); + addConfigOption(REDIS_OPTIONS.HZ, hz); return this; } @@ -612,25 +611,26 @@ public class RedisRunner { } public static final class RedisProcess { + private final Process redisProcess; - + private RedisProcess(Process redisProcess) { this.redisProcess = redisProcess; } - + public int stop() throws InterruptedException { redisProcess.destroy(); int exitCode = redisProcess.waitFor(); - return exitCode == 1 && isWindows() ? 0 : exitCode ; + return exitCode == 1 && isWindows() ? 0 : exitCode; } public Process getRedisProcess() { return redisProcess; } - + private boolean isWindows() { return System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH).contains("win"); } } - + } From dddad4d4d771a92da814300eabb573e3040d8742 Mon Sep 17 00:00:00 2001 From: Rui Gu Date: Fri, 18 Mar 2016 14:30:47 +0000 Subject: [PATCH 14/47] introducing launchOptions local var --- .gitignore | 2 ++ src/test/java/org/redisson/RedisRunner.java | 10 ++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 6cfdf14b5..0f61eb300 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ /target /*.cmd + +nb-configuration.xml diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index 3995ac351..2f0b61e99 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -193,13 +193,11 @@ public class RedisRunner { } private static RedisProcess runWithOptions(String... options) throws IOException, InterruptedException { - System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(Arrays.stream(options) + String[] launchOptions = Arrays.stream(options) .collect(Collectors.joining()) - .split(" "))); - ProcessBuilder master = new ProcessBuilder( - Arrays.stream(options) - .collect(Collectors.joining()) - .split(" ")) + .split(" "); + System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(launchOptions)); + ProcessBuilder master = new ProcessBuilder(launchOptions) .redirectErrorStream(true) .directory(new File(redisBinary).getParentFile()); Process p = master.start(); From b22468172de7b800727bb5728b5ea2ff95b2af73 Mon Sep 17 00:00:00 2001 From: Rui Gu Date: Fri, 18 Mar 2016 14:41:57 +0000 Subject: [PATCH 15/47] Introducing the launchOptions with more elegant way Thanks to @mrniko --- src/test/java/org/redisson/RedisRunner.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/redisson/RedisRunner.java b/src/test/java/org/redisson/RedisRunner.java index 2f0b61e99..532e9c1b1 100644 --- a/src/test/java/org/redisson/RedisRunner.java +++ b/src/test/java/org/redisson/RedisRunner.java @@ -8,6 +8,7 @@ import java.net.Inet4Address; import java.net.URL; import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.stream.Collectors; @@ -165,7 +166,7 @@ public class RedisRunner { static { redisBinary = Optional.ofNullable(System.getProperty("redisBinary")) - .orElse("C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\redis-server.exe") + " "; + .orElse("C:\\Devel\\projects\\redis\\Redis-x64-3.0.500\\redis-server.exe"); } { @@ -193,10 +194,10 @@ public class RedisRunner { } private static RedisProcess runWithOptions(String... options) throws IOException, InterruptedException { - String[] launchOptions = Arrays.stream(options) - .collect(Collectors.joining()) - .split(" "); - System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(launchOptions)); + List launchOptions = Arrays.stream(options) + .map(x -> Arrays.asList(x.split(" "))).flatMap(x -> x.stream()) + .collect(Collectors.toList()); + System.out.println("REDIS LAUNCH OPTIONS: " + Arrays.toString(launchOptions.toArray())); ProcessBuilder master = new ProcessBuilder(launchOptions) .redirectErrorStream(true) .directory(new File(redisBinary).getParentFile()); From dbc0149da2c840b6fb1553297515f7a998761d19 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 19 Mar 2016 13:55:56 +0300 Subject: [PATCH 16/47] RSetMultimapCache added. #428 --- src/main/java/org/redisson/Redisson.java | 10 + .../java/org/redisson/RedissonClient.java | 36 +- .../redisson/RedissonSetMultimapCache.java | 216 +++++++++ .../redisson/RedissonSetMultimapValues.java | 446 ++++++++++++++++++ .../org/redisson/core/RMultimapCache.java | 24 + .../redisson/core/RMultimapCacheAsync.java | 26 + .../org/redisson/core/RSetMultimapCache.java | 20 + .../RedissonSetMultimapCacheTest.java | 115 +++++ 8 files changed, 887 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonSetMultimapCache.java create mode 100644 src/main/java/org/redisson/RedissonSetMultimapValues.java create mode 100644 src/main/java/org/redisson/core/RMultimapCache.java create mode 100644 src/main/java/org/redisson/core/RMultimapCacheAsync.java create mode 100644 src/main/java/org/redisson/core/RSetMultimapCache.java create mode 100644 src/test/java/org/redisson/RedissonSetMultimapCacheTest.java diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index f02e14c0d..060192064 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -57,6 +57,7 @@ import org.redisson.core.RListMultimap; import org.redisson.core.RLock; import org.redisson.core.RMap; import org.redisson.core.RMapCache; +import org.redisson.core.RMultimapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; @@ -66,6 +67,7 @@ import org.redisson.core.RSemaphore; import org.redisson.core.RSet; import org.redisson.core.RSetCache; import org.redisson.core.RSetMultimap; +import org.redisson.core.RSetMultimapCache; import org.redisson.core.RSortedSet; import org.redisson.core.RTopic; @@ -265,6 +267,14 @@ public class Redisson implements RedissonClient { public RSetMultimap getSetMultimap(String name) { return new RedissonSetMultimap(commandExecutor, name); } + + public RSetMultimapCache getSetMultimapCache(String name) { + return new RedissonSetMultimapCache(commandExecutor, name); + } + + public RSetMultimapCache getSetMultimapCache(String name, Codec codec) { + return new RedissonSetMultimapCache(codec, commandExecutor, name); + } @Override public RSetMultimap getSetMultimap(String name, Codec codec) { diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index 66fe5b49f..0c1502d4a 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -31,7 +31,6 @@ import org.redisson.core.RBlockingDeque; import org.redisson.core.RBlockingQueue; import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; -import org.redisson.core.RMapCache; import org.redisson.core.RCountDownLatch; import org.redisson.core.RDeque; import org.redisson.core.RHyperLogLog; @@ -41,6 +40,7 @@ import org.redisson.core.RList; import org.redisson.core.RListMultimap; import org.redisson.core.RLock; import org.redisson.core.RMap; +import org.redisson.core.RMapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; @@ -50,6 +50,7 @@ import org.redisson.core.RSemaphore; import org.redisson.core.RSet; import org.redisson.core.RSetCache; import org.redisson.core.RSetMultimap; +import org.redisson.core.RSetMultimapCache; import org.redisson.core.RSortedSet; import org.redisson.core.RTopic; @@ -218,7 +219,7 @@ public interface RedissonClient { RList getList(String name, Codec codec); /** - * Returns List based MultiMap instance by name. + * Returns List based Multimap instance by name. * * @param name * @return @@ -226,7 +227,7 @@ public interface RedissonClient { RListMultimap getListMultimap(String name); /** - * Returns List based MultiMap instance by name + * Returns List based Multimap instance by name * using provided codec for both map keys and values. * * @param name @@ -254,15 +255,15 @@ public interface RedissonClient { RMap getMap(String name, Codec codec); /** - * Returns Set based MultiMap instance by name. + * Returns Set based Multimap instance by name. * * @param name * @return */ RSetMultimap getSetMultimap(String name); - + /** - * Returns Set based MultiMap instance by name + * Returns Set based Multimap instance by name * using provided codec for both map keys and values. * * @param name @@ -271,6 +272,29 @@ public interface RedissonClient { */ RSetMultimap getSetMultimap(String name, Codec codec); + /** + * Returns Set based Multimap instance by name. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String)}.

+ * + * @param name + * @return + */ + RSetMultimapCache getSetMultimapCache(String name); + + /** + * Returns Set based Multimap instance by name + * using provided codec for both map keys and values. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String, Codec)}.

+ * + * @param name + * @return + */ + RSetMultimapCache getSetMultimapCache(String name, Codec codec); + /** * Returns semaphore instance by name * diff --git a/src/main/java/org/redisson/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java new file mode 100644 index 000000000..927411875 --- /dev/null +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -0,0 +1,216 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RSetMultimapCache; + +import io.netty.util.concurrent.Future; + +/** + * @author Nikita Koksharov + * + * @param key + * @param value + */ +public class RedissonSetMultimapCache extends RedissonSetMultimap implements RSetMultimapCache { + + private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + + RedissonSetMultimapCache(CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + } + + RedissonSetMultimapCache(Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + } + + public Future containsKeyAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String setName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value ~= false then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('scard', ARGV[3]) > 0 and 1 or 0;" + + "end;" + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), keyState, setName); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + String getTimeoutSetName() { + return "redisson_set_multimap_ttl{" + getName() + "}"; + } + + + public Future containsValueAsync(Object value) { + try { + byte[] valueState = codec.getMapValueEncoder().encode(value); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local keys = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(keys) do " + + "if i % 2 == 0 then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], keys[i-1]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[2]) then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "if redis.call('sismember', name, ARGV[1]) == 1 then " + + "return 1; " + + "end;" + + "end; " + + "end;" + + "end; " + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), valueState, System.currentTimeMillis()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Future containsEntryAsync(Object key, Object value) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + byte[] valueState = codec.getMapValueEncoder().encode(value); + + String setName = getValuesName(keyHash); + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "if redis.call('sismember', KEYS[1], ARGV[3]) == 1 then " + + "return 1; " + + "end;" + + "end; " + + "return 0; ", + Arrays.asList(setName, getTimeoutSetName()), System.currentTimeMillis(), keyState, valueState); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Set get(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String setName = getValuesName(keyHash); + + return new RedissonSetMultimapValues(codec, commandExecutor, setName, getTimeoutSetName(), key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public Future> getAllAsync(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String setName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_SET, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "return redis.call('smembers', KEYS[1]); " + + "end; " + + "return {}; ", + Arrays.asList(setName, getTimeoutSetName()), System.currentTimeMillis(), keyState); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + public Future> removeAllAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String setName = getValuesName(keyHash); + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET, + "redis.call('hdel', KEYS[1], ARGV[1]); " + + "local members = redis.call('smembers', KEYS[2]); " + + "redis.call('del', KEYS[2]); " + + "redis.call('zrem', KEYS[3], ARGV[1]); " + + "return members; ", + Arrays.asList(getName(), setName, getTimeoutSetName()), keyState); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean expireKey(K key, long timeToLive, TimeUnit timeUnit) { + return get(expireKeyAsync(key, timeToLive, timeUnit)); + } + + @Override + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_EXPIRE_KEY, + "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " + + "if tonumber(ARGV[1]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "end; " + + "return 1; " + + "else " + + "return 0; " + + "end", + Arrays.asList(getName(), getTimeoutSetName()), ttlTimeout, key); + } + +} diff --git a/src/main/java/org/redisson/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java new file mode 100644 index 000000000..be9fab67c --- /dev/null +++ b/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -0,0 +1,446 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.client.protocol.convertor.IntegerReplayConvertor; +import org.redisson.client.protocol.decoder.ListScanResult; +import org.redisson.client.protocol.decoder.ListScanResultReplayDecoder; +import org.redisson.client.protocol.decoder.NestedMultiDecoder; +import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; +import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RSet; + +import io.netty.util.concurrent.Future; + +/** + * Distributed and concurrent implementation of {@link java.util.Set} + * + * @author Nikita Koksharov + * + * @param value + */ +public class RedissonSetMultimapValues extends RedissonExpirable implements RSet { + + private static final RedisCommand> EVAL_SSCAN = new RedisCommand>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder(), new ListScanResultReplayDecoder()), 7, ValueType.MAP_KEY, ValueType.OBJECT); + private static final RedisCommand EVAL_SIZE = new RedisCommand("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY); + private static final RedisCommand> EVAL_READALL = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_CONTAINS_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS); + + private final Object key; + private final String timeoutSetName; + + public RedissonSetMultimapValues(Codec codec, CommandAsyncExecutor commandExecutor, String name, String timeoutSetName, Object key) { + super(codec, commandExecutor, name); + this.timeoutSetName = timeoutSetName; + this.key = key; + } + + @Override + public int size() { + return get(sizeAsync()); + } + + @Override + public Future sizeAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_SIZE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('scard', KEYS[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return get(containsAsync(o)); + } + + @Override + public Future containsAsync(Object o) { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_VALUE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('sismember', KEYS[2], ARGV[3]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + private ListScanResult scanIterator(InetSocketAddress client, long startPos) { + Future> f = commandExecutor.evalReadAsync(client, getName(), codec, EVAL_SSCAN, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return {0, {}};" + + "end;" + + + "return redis.call('sscan', KEYS[2], ARGV[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), startPos, key); + return get(f); + } + + @Override + public Iterator iterator() { + return new Iterator() { + + private List firstValues; + private Iterator iter; + private InetSocketAddress client; + private long nextIterPos; + + private boolean currentElementRemoved; + private boolean removeExecuted; + private V value; + + @Override + public boolean hasNext() { + if (iter == null || !iter.hasNext()) { + if (nextIterPos == -1) { + return false; + } + long prevIterPos = nextIterPos; + ListScanResult res = scanIterator(client, nextIterPos); + client = res.getRedisClient(); + if (nextIterPos == 0 && firstValues == null) { + firstValues = res.getValues(); + } else if (res.getValues().equals(firstValues)) { + return false; + } + iter = res.getValues().iterator(); + nextIterPos = res.getPos(); + if (prevIterPos == nextIterPos && !removeExecuted) { + nextIterPos = -1; + } + } + return iter.hasNext(); + } + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException("No such element at index"); + } + + value = iter.next(); + currentElementRemoved = false; + return value; + } + + @Override + public void remove() { + if (currentElementRemoved) { + throw new IllegalStateException("Element been already deleted"); + } + if (iter == null) { + throw new IllegalStateException(); + } + + iter.remove(); + RedissonSetMultimapValues.this.remove(value); + currentElementRemoved = true; + removeExecuted = true; + } + + }; + } + + @Override + public Future> readAllAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_READALL, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return {};" + + "end; " + + "return redis.call('smembers', KEYS[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public Set readAll() { + return get(readAllAsync()); + } + + @Override + public Object[] toArray() { + Set res = (Set) get(readAllAsync()); + return res.toArray(); + } + + @Override + public T[] toArray(T[] a) { + Set res = (Set) get(readAllAsync()); + return res.toArray(a); + } + + @Override + public boolean add(V e) { + return get(addAsync(e)); + } + + @Override + public Future addAsync(V e) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_SINGLE, getName(), e); + } + + @Override + public V removeRandom() { + return get(removeRandomAsync()); + } + + @Override + public Future removeRandomAsync() { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SPOP_SINGLE, getName()); + } + + @Override + public Future removeAsync(Object o) { + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('srem', KEYS[2], ARGV[3]) > 0 and 1 or 0;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public boolean remove(Object value) { + return get(removeAsync((V)value)); + } + + @Override + public Future moveAsync(String destination, V member) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SMOVE, getName(), destination, member); + } + + @Override + public boolean move(String destination, V member) { + return get(moveAsync(destination, member)); + } + + @Override + public boolean containsAll(Collection c) { + return get(containsAllAsync(c)); + } + + @Override + public Future containsAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "local s = redis.call('smembers', KEYS[2]);" + + "for i = 0, table.getn(s), 1 do " + + "for j = 2, table.getn(ARGV), 1 do " + + "if ARGV[j] == s[i] " + + "then table.remove(ARGV, j) end " + + "end; " + + "end;" + + "return table.getn(ARGV) == 2 and 1 or 0; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public boolean addAll(Collection c) { + if (c.isEmpty()) { + return false; + } + + return get(addAllAsync(c)); + } + + @Override + public Future addAllAsync(Collection c) { + List args = new ArrayList(c.size() + 1); + args.add(getName()); + args.addAll(c); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_BOOL, args.toArray()); + } + + @Override + public boolean retainAll(Collection c) { + return get(retainAllAsync(c)); + } + + @Override + public Future retainAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local changed = 0 " + + "local s = redis.call('smembers', KEYS[2]) " + + "local i = 0 " + + "while i <= table.getn(s) do " + + "local element = s[i] " + + "local isInAgrs = false " + + "for j = 2, table.getn(ARGV), 1 do " + + "if ARGV[j] == element then " + + "isInAgrs = true " + + "break " + + "end " + + "end " + + "if isInAgrs == false then " + + "redis.call('SREM', KEYS[2], element) " + + "changed = 1 " + + "end " + + "i = i + 1 " + + "end " + + "return changed ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public Future removeAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local v = 0 " + + "for i = 2, table.getn(ARGV), 1 do " + + "if redis.call('srem', KEYS[2], ARGV[i]) == 1 " + + "then v = 1 end " + +"end " + + "return v ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public boolean removeAll(Collection c) { + return get(removeAllAsync(c)); + } + + @Override + public int union(String... names) { + return get(unionAsync(names)); + } + + @Override + public Future unionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SUNIONSTORE_INT, args.toArray()); + } + + @Override + public Set readUnion(String... names) { + return get(readUnionAsync(names)); + } + + @Override + public Future> readUnionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SUNION, args.toArray()); + } + + @Override + public void clear() { + delete(); + } + +} diff --git a/src/main/java/org/redisson/core/RMultimapCache.java b/src/main/java/org/redisson/core/RMultimapCache.java new file mode 100644 index 000000000..e1438d775 --- /dev/null +++ b/src/main/java/org/redisson/core/RMultimapCache.java @@ -0,0 +1,24 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +import java.util.concurrent.TimeUnit; + +public interface RMultimapCache extends RMultimap, RMultimapCacheAsync { + + boolean expireKey(K key, long timeToLive, TimeUnit timeUnit); + +} diff --git a/src/main/java/org/redisson/core/RMultimapCacheAsync.java b/src/main/java/org/redisson/core/RMultimapCacheAsync.java new file mode 100644 index 000000000..f95054215 --- /dev/null +++ b/src/main/java/org/redisson/core/RMultimapCacheAsync.java @@ -0,0 +1,26 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +import java.util.concurrent.TimeUnit; + +import io.netty.util.concurrent.Future; + +public interface RMultimapCacheAsync extends RMultimapAsync { + + Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit); + +} diff --git a/src/main/java/org/redisson/core/RSetMultimapCache.java b/src/main/java/org/redisson/core/RSetMultimapCache.java new file mode 100644 index 000000000..1029b2c3f --- /dev/null +++ b/src/main/java/org/redisson/core/RSetMultimapCache.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public interface RSetMultimapCache extends RSetMultimap, RMultimapCache { + +} diff --git a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java new file mode 100644 index 000000000..66415c84a --- /dev/null +++ b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java @@ -0,0 +1,115 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.redisson.core.RMultimapCache; + +public class RedissonSetMultimapCacheTest extends BaseTest { + + @Test + public void testContains() { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.containsKey("1")).isTrue(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isTrue(); + assertThat(multimap.containsValue("3")).isTrue(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isTrue(); + assertThat(multimap.containsEntry("1", "3")).isTrue(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testContainsExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.containsKey("1")).isFalse(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isFalse(); + assertThat(multimap.containsValue("3")).isFalse(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isFalse(); + assertThat(multimap.containsEntry("1", "3")).isFalse(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testGetAll() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.getAll("1")).containsOnlyOnce("1", "2", "3"); + } + + @Test + public void testGetAllExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.getAll("1")).isEmpty(); + } + + @Test + public void testValues() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.put("1", "3"); + + assertThat(multimap.get("1").size()).isEqualTo(3); + assertThat(multimap.get("1")).containsOnlyOnce("1", "2", "3"); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").contains("2")).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1", "2"))).isTrue(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isTrue(); + } + + @Test + public void testValuesExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getSetMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.get("1").size()).isZero(); + assertThat(multimap.get("1")).contains(); + assertThat(multimap.get("1").remove("3")).isFalse(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isFalse(); + } + +} From e9077dd9cc094ddbf12438028a665ee674f6d074 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 12:04:11 +0300 Subject: [PATCH 17/47] testRetainAll tuned --- src/test/java/org/redisson/RedissonSetCacheTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index 69acd3519..a864425fd 100644 --- a/src/test/java/org/redisson/RedissonSetCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheTest.java @@ -169,7 +169,7 @@ public class RedissonSetCacheTest extends BaseTest { } @Test - public void testRetainAll() { + public void testRetainAll() throws InterruptedException { RSetCache set = redisson.getSetCache("set"); for (int i = 0; i < 10000; i++) { set.add(i); @@ -177,6 +177,7 @@ public class RedissonSetCacheTest extends BaseTest { } Assert.assertTrue(set.retainAll(Arrays.asList(1, 2))); + Thread.sleep(500); assertThat(set).containsOnly(1, 2); Assert.assertEquals(2, set.size()); } From d2c441a0adee9436ce0dfb5a82f6ebe78020002e Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 12:07:06 +0300 Subject: [PATCH 18/47] testCluster commented out --- src/test/java/org/redisson/RedissonTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 1cbeff418..74aeef7d5 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -211,7 +211,7 @@ public class RedissonTest { assertThat(c.toJSON()).isEqualTo(t); } - @Test +// @Test public void testCluster() { NodesGroup nodes = redisson.getClusterNodesGroup(); Assert.assertEquals(2, nodes.getNodes().size()); From 021b826aff86e530402bde101064eb90d3156624 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 13:19:09 +0300 Subject: [PATCH 19/47] EvictionScheduler added to RedissonSetMultimapCache. #428 --- .../java/org/redisson/EvictionScheduler.java | 44 ++++++++++++++++--- src/main/java/org/redisson/Redisson.java | 4 +- .../redisson/RedissonSetMultimapCache.java | 6 ++- .../RedissonSetMultimapCacheTest.java | 26 +++++++++++ 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/redisson/EvictionScheduler.java b/src/main/java/org/redisson/EvictionScheduler.java index 570db18b8..d73f861cc 100644 --- a/src/main/java/org/redisson/EvictionScheduler.java +++ b/src/main/java/org/redisson/EvictionScheduler.java @@ -49,6 +49,7 @@ public class EvictionScheduler { final String name; final String timeoutSetName; final String maxIdleSetName; + final boolean multimap; final Deque sizeHistory = new LinkedList(); int delay = 10; @@ -56,10 +57,11 @@ public class EvictionScheduler { final int maxDelay = 2*60*60; final int keysLimit = 300; - public RedissonCacheTask(String name, String timeoutSetName, String maxIdleSetName) { + public RedissonCacheTask(String name, String timeoutSetName, String maxIdleSetName, boolean multimap) { this.name = name; this.timeoutSetName = timeoutSetName; this.maxIdleSetName = maxIdleSetName; + this.multimap = multimap; } public void schedule() { @@ -68,7 +70,7 @@ public class EvictionScheduler { @Override public void run() { - Future future = cleanupExpiredEntires(name, timeoutSetName, maxIdleSetName, keysLimit); + Future future = cleanupExpiredEntires(name, timeoutSetName, maxIdleSetName, keysLimit, multimap); future.addListener(new FutureListener() { @Override @@ -123,16 +125,25 @@ public class EvictionScheduler { this.executor = executor; } + public void scheduleCleanMultimap(String name, String timeoutSetName) { + RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null, true); + RedissonCacheTask prevTask = tasks.putIfAbsent(name, task); + if (prevTask == null) { + task.schedule(); + } + } + public void schedule(String name, String timeoutSetName) { - RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null); + RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null, false); RedissonCacheTask prevTask = tasks.putIfAbsent(name, task); if (prevTask == null) { task.schedule(); } } + public void schedule(String name, String timeoutSetName, String maxIdleSetName) { - RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName); + RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName, false); RedissonCacheTask prevTask = tasks.putIfAbsent(name, task); if (prevTask == null) { task.schedule(); @@ -155,7 +166,7 @@ public class EvictionScheduler { return; } - Future future = cleanupExpiredEntires(name, timeoutSetName, null, valuesAmountToClean); + Future future = cleanupExpiredEntires(name, timeoutSetName, null, valuesAmountToClean, false); future.addListener(new FutureListener() { @Override @@ -175,7 +186,27 @@ public class EvictionScheduler { }); } - private Future cleanupExpiredEntires(String name, String timeoutSetName, String maxIdleSetName, int keysLimit) { + private Future cleanupExpiredEntires(String name, String timeoutSetName, String maxIdleSetName, int keysLimit, boolean multimap) { + if (multimap) { + return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER, + "local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); " + + "if #expiredKeys > 0 then " + + "redis.call('zrem', KEYS[2], unpack(expiredKeys)); " + + + "local values = redis.call('hmget', KEYS[1], unpack(expiredKeys)); " + + "local keys = {}; " + + "for i, v in ipairs(values) do " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end; " + + "redis.call('del', unpack(keys)); " + + + "redis.call('hdel', KEYS[1], unpack(expiredKeys)); " + + "end; " + + "return #expiredKeys;", + Arrays.asList(name, timeoutSetName), System.currentTimeMillis(), keysLimit); + } + if (maxIdleSetName != null) { return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER, "local expiredKeys1 = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); " @@ -193,6 +224,7 @@ public class EvictionScheduler { + "return #expiredKeys1 + #expiredKeys2;", Arrays.asList(name, timeoutSetName, maxIdleSetName), System.currentTimeMillis(), keysLimit); } + return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER, "local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); " + "if #expiredKeys > 0 then " diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 060192064..40c860773 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -269,11 +269,11 @@ public class Redisson implements RedissonClient { } public RSetMultimapCache getSetMultimapCache(String name) { - return new RedissonSetMultimapCache(commandExecutor, name); + return new RedissonSetMultimapCache(evictionScheduler, commandExecutor, name); } public RSetMultimapCache getSetMultimapCache(String name, Codec codec) { - return new RedissonSetMultimapCache(codec, commandExecutor, name); + return new RedissonSetMultimapCache(evictionScheduler, codec, commandExecutor, name); } @Override diff --git a/src/main/java/org/redisson/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java index 927411875..9b7dee226 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapCache.java +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -41,12 +41,14 @@ public class RedissonSetMultimapCache extends RedissonSetMultimap im private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); - RedissonSetMultimapCache(CommandAsyncExecutor connectionManager, String name) { + RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); } - RedissonSetMultimapCache(Codec codec, CommandAsyncExecutor connectionManager, String name) { + RedissonSetMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { diff --git a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java index 66415c84a..7fe68f557 100644 --- a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java @@ -5,8 +5,11 @@ import static org.assertj.core.api.Assertions.*; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import org.junit.Assert; import org.junit.Test; +import org.redisson.codec.MsgPackJacksonCodec; import org.redisson.core.RMultimapCache; +import org.redisson.core.RSetCache; public class RedissonSetMultimapCacheTest extends BaseTest { @@ -111,5 +114,28 @@ public class RedissonSetMultimapCacheTest extends BaseTest { assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isFalse(); assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isFalse(); } + + @Test + public void testScheduler() throws InterruptedException { + RMultimapCache cache = redisson.getSetMultimapCache("simple33"); + assertThat(cache.put("1", "1")).isTrue(); + assertThat(cache.put("1", "2")).isTrue(); + assertThat(cache.put("1", "3")).isTrue(); + assertThat(cache.put("2", "1")).isTrue(); + assertThat(cache.put("2", "2")).isTrue(); + assertThat(cache.put("2", "3")).isTrue(); + + assertThat(cache.expireKey("1", 2, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("2", 3, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("3", 3, TimeUnit.SECONDS)).isFalse(); + + assertThat(cache.size()).isEqualTo(6); + + Thread.sleep(10000); + + assertThat(cache.size()).isZero(); + + } + } From f10fb062571463833e8e8eb8c9f3b48bffad6293 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 13:22:25 +0300 Subject: [PATCH 20/47] Few comments added --- src/main/java/org/redisson/core/RMultimapCache.java | 9 +++++++++ src/main/java/org/redisson/core/RMultimapCacheAsync.java | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/org/redisson/core/RMultimapCache.java b/src/main/java/org/redisson/core/RMultimapCache.java index e1438d775..7fc06a11a 100644 --- a/src/main/java/org/redisson/core/RMultimapCache.java +++ b/src/main/java/org/redisson/core/RMultimapCache.java @@ -19,6 +19,15 @@ import java.util.concurrent.TimeUnit; public interface RMultimapCache extends RMultimap, RMultimapCacheAsync { + /** + * Set a timeout for key. After the timeout has expired, + * the key and its values will automatically be deleted. + * + * @param key + * @param timeToLive - timeout before key will be deleted + * @param timeUnit - timeout time unit + * @return true if key exists and the timeout was set and false if key not exists + */ boolean expireKey(K key, long timeToLive, TimeUnit timeUnit); } diff --git a/src/main/java/org/redisson/core/RMultimapCacheAsync.java b/src/main/java/org/redisson/core/RMultimapCacheAsync.java index f95054215..e910a7405 100644 --- a/src/main/java/org/redisson/core/RMultimapCacheAsync.java +++ b/src/main/java/org/redisson/core/RMultimapCacheAsync.java @@ -21,6 +21,15 @@ import io.netty.util.concurrent.Future; public interface RMultimapCacheAsync extends RMultimapAsync { + /** + * Set a timeout for key in async mode. After the timeout has expired, + * the key and its values will automatically be deleted. + * + * @param key + * @param timeToLive - timeout before key will be deleted + * @param timeUnit - timeout time unit + * @return true if key exists and the timeout was set and false if key not exists + */ Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit); } From 419168951e8d56c314fe16dd4f179d439fbd7bc5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 15:03:14 +0300 Subject: [PATCH 21/47] RedissonListMultimapCache added. #428 --- src/main/java/org/redisson/Redisson.java | 18 + .../java/org/redisson/RedissonClient.java | 24 + .../redisson/RedissonListMultimapCache.java | 226 ++++++ .../redisson/RedissonListMultimapValues.java | 688 ++++++++++++++++++ .../org/redisson/core/RListMultimapCache.java | 20 + .../RedissonListMultimapCacheTest.java | 139 ++++ 6 files changed, 1115 insertions(+) create mode 100644 src/main/java/org/redisson/RedissonListMultimapCache.java create mode 100644 src/main/java/org/redisson/RedissonListMultimapValues.java create mode 100644 src/main/java/org/redisson/core/RListMultimapCache.java create mode 100644 src/test/java/org/redisson/RedissonListMultimapCacheTest.java diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 40c860773..2a0d66edb 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -54,6 +54,7 @@ import org.redisson.core.RKeys; import org.redisson.core.RLexSortedSet; import org.redisson.core.RList; import org.redisson.core.RListMultimap; +import org.redisson.core.RListMultimapCache; import org.redisson.core.RLock; import org.redisson.core.RMap; import org.redisson.core.RMapCache; @@ -181,10 +182,12 @@ public class Redisson implements RedissonClient { return buckets; } + @Override public Map loadBucketValues(Collection keys) { return loadBucketValues(keys.toArray(new String[keys.size()])); } + @Override public Map loadBucketValues(String ... keys) { if (keys.length == 0) { return Collections.emptyMap(); @@ -205,6 +208,7 @@ public class Redisson implements RedissonClient { return result; } + @Override public void saveBuckets(Map buckets) { if (buckets.isEmpty()) { return; @@ -268,14 +272,26 @@ public class Redisson implements RedissonClient { return new RedissonSetMultimap(commandExecutor, name); } + @Override public RSetMultimapCache getSetMultimapCache(String name) { return new RedissonSetMultimapCache(evictionScheduler, commandExecutor, name); } + @Override public RSetMultimapCache getSetMultimapCache(String name, Codec codec) { return new RedissonSetMultimapCache(evictionScheduler, codec, commandExecutor, name); } + @Override + public RListMultimapCache getListMultimapCache(String name) { + return new RedissonListMultimapCache(evictionScheduler, commandExecutor, name); + } + + @Override + public RListMultimapCache getListMultimapCache(String name, Codec codec) { + return new RedissonListMultimapCache(evictionScheduler, codec, commandExecutor, name); + } + @Override public RSetMultimap getSetMultimap(String name, Codec codec) { return new RedissonSetMultimap(codec, commandExecutor, name); @@ -471,10 +487,12 @@ public class Redisson implements RedissonClient { return config; } + @Override public NodesGroup getNodesGroup() { return new RedisNodes(connectionManager); } + @Override public NodesGroup getClusterNodesGroup() { if (!config.isClusterConfig()) { throw new IllegalStateException("Redisson is not in cluster mode!"); diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index 0c1502d4a..270877e9f 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -38,6 +38,7 @@ import org.redisson.core.RKeys; import org.redisson.core.RLexSortedSet; import org.redisson.core.RList; import org.redisson.core.RListMultimap; +import org.redisson.core.RListMultimapCache; import org.redisson.core.RLock; import org.redisson.core.RMap; import org.redisson.core.RMapCache; @@ -236,6 +237,29 @@ public interface RedissonClient { */ RListMultimap getListMultimap(String name, Codec codec); + /** + * Returns List based Multimap instance by name. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String)}.

+ * + * @param name + * @return + */ + RListMultimapCache getListMultimapCache(String name); + + /** + * Returns List based Multimap instance by name + * using provided codec for both map keys and values. + * Supports key-entry eviction with a given TTL value. + * + *

If eviction is not required then it's better to use regular map {@link #getSetMultimap(String, Codec)}.

+ * + * @param name + * @return + */ + RListMultimapCache getListMultimapCache(String name, Codec codec); + /** * Returns map instance by name. * diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java new file mode 100644 index 000000000..62a4800ec --- /dev/null +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -0,0 +1,226 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RListMultimapCache; + +import io.netty.util.concurrent.Future; + +/** + * @author Nikita Koksharov + * + * @param key + * @param value + */ +public class RedissonListMultimapCache extends RedissonListMultimap implements RListMultimapCache { + + private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + + RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + } + + RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + } + + public Future containsKeyAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String valuesName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value ~= false then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('llen', ARGV[3]) > 0 and 1 or 0;" + + "end;" + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), keyState, valuesName); + + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + String getTimeoutSetName() { + return "redisson_list_multimap_ttl{" + getName() + "}"; + } + + + public Future containsValueAsync(Object value) { + try { + byte[] valueState = codec.getMapValueEncoder().encode(value); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local keys = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(keys) do " + + "if i % 2 == 0 then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], keys[i-1]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[2]) then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + + "local items = redis.call('lrange', name, 0, -1) " + + "for i=1,#items do " + + "if items[i] == ARGV[1] then " + + "return 1; " + + "end; " + + "end; " + + + "end; " + + "end;" + + "end; " + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), valueState, System.currentTimeMillis()); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public Future containsEntryAsync(Object key, Object value) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + byte[] valueState = codec.getMapValueEncoder().encode(value); + + String valuesName = getValuesName(keyHash); + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "local items = redis.call('lrange', KEYS[1], 0, -1); " + + "for i=0, #items do " + + "if items[i] == ARGV[3] then " + + "return 1; " + + "end; " + + "end; " + + "end; " + + "return 0; ", + Arrays.asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState, valueState); + + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public List get(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String valuesName = getValuesName(keyHash); + + return new RedissonListMultimapValues(codec, commandExecutor, valuesName, getTimeoutSetName(), key); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + public Future> getAllAsync(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String valuesName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LIST, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "return redis.call('lrange', KEYS[1], 0, -1); " + + "end; " + + "return {}; ", + Arrays.asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + + } + + public Future> removeAllAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String valuesName = getValuesName(keyHash); + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET, + "redis.call('hdel', KEYS[1], ARGV[1]); " + + "local members = redis.call('lrange', KEYS[2], 0, -1); " + + "redis.call('del', KEYS[2]); " + + "redis.call('zrem', KEYS[3], ARGV[1]); " + + "return members; ", + Arrays.asList(getName(), valuesName, getTimeoutSetName()), keyState); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public boolean expireKey(K key, long timeToLive, TimeUnit timeUnit) { + return get(expireKeyAsync(key, timeToLive, timeUnit)); + } + + @Override + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_EXPIRE_KEY, + "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " + + "if tonumber(ARGV[1]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "end; " + + "return 1; " + + "else " + + "return 0; " + + "end", + Arrays.asList(getName(), getTimeoutSetName()), ttlTimeout, key); + } + +} diff --git a/src/main/java/org/redisson/RedissonListMultimapValues.java b/src/main/java/org/redisson/RedissonListMultimapValues.java new file mode 100644 index 000000000..1c6ed6d5f --- /dev/null +++ b/src/main/java/org/redisson/RedissonListMultimapValues.java @@ -0,0 +1,688 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import static org.redisson.client.protocol.RedisCommands.EVAL_OBJECT; +import static org.redisson.client.protocol.RedisCommands.LPOP; +import static org.redisson.client.protocol.RedisCommands.LPUSH_BOOLEAN; +import static org.redisson.client.protocol.RedisCommands.RPUSH_BOOLEAN; + +import java.io.IOException; +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 java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanNumberReplayConvertor; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.client.protocol.convertor.Convertor; +import org.redisson.client.protocol.convertor.IntegerReplayConvertor; +import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RList; + +import io.netty.util.concurrent.Future; + +/** + * List based Multimap Cache values holder + * + * @author Nikita Koksharov + * + * @param the type of elements held in this collection + */ +public class RedissonListMultimapValues extends RedissonExpirable implements RList { + + private static final RedisCommand LAST_INDEX = new RedisCommand("EVAL", new IntegerReplayConvertor(), 4, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_SIZE = new RedisCommand("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_GET = new RedisCommand("EVAL", 7, ValueType.MAP_KEY); + private static final RedisCommand> EVAL_READALL = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_CONTAINS_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS); + + + public static final RedisCommand EVAL_BOOLEAN_ARGS2 = new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS); + + private final Object key; + private final String timeoutSetName; + + public RedissonListMultimapValues(Codec codec, CommandAsyncExecutor commandExecutor, String name, String timeoutSetName, Object key) { + super(codec, commandExecutor, name); + this.timeoutSetName = timeoutSetName; + this.key = key; + } + + @Override + public int size() { + return get(sizeAsync()); + } + + public Future sizeAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_SIZE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('llen', KEYS[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return get(containsAsync(o)); + } + + @Override + public Iterator iterator() { + return listIterator(); + } + + @Override + public Object[] toArray() { + List list = readAll(); + return list.toArray(); + } + + @Override + public List readAll() { + return get(readAllAsync()); + } + + @Override + public Future> readAllAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_READALL, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return {};" + + "end; " + + "return redis.call('lrange', KEYS[2], 0, -1);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public T[] toArray(T[] a) { + List list = readAll(); + return list.toArray(a); + } + + @Override + public boolean add(V e) { + return get(addAsync(e)); + } + + @Override + public Future addAsync(V e) { + return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, getName(), e); + } + + @Override + public boolean remove(Object o) { + return get(removeAsync(o)); + } + + @Override + public Future removeAsync(Object o) { + return removeAsync(o, 1); + } + + protected Future removeAsync(Object o, int count) { + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('lrem', KEYS[2], ARGV[2], ARGV[4]) > 0 and 1 or 0;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), count, key, o); + } + + protected boolean remove(Object o, int count) { + return get(removeAsync(o, count)); + } + + @Override + public Future containsAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "local items = redis.call('lrange', KEYS[2], 0, -1);" + + "for i = 0, #items, 1 do " + + "for j = 2, table.getn(ARGV), 1 do " + + "if ARGV[j] == items[i] " + + "then table.remove(ARGV, j) end " + + "end; " + + "end;" + + "return table.getn(ARGV) == 2 and 1 or 0; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + + } + + @Override + public boolean containsAll(Collection c) { + return get(containsAllAsync(c)); + } + + @Override + public boolean addAll(Collection c) { + return get(addAllAsync(c)); + } + + @Override + public Future addAllAsync(final Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(false); + } + + List args = new ArrayList(c.size() + 1); + args.add(getName()); + args.addAll(c); + return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, args.toArray()); + } + + public Future addAllAsync(int index, Collection coll) { + if (index < 0) { + throw new IndexOutOfBoundsException("index: " + index); + } + + if (coll.isEmpty()) { + return newSucceededFuture(false); + } + + if (index == 0) { // prepend elements to list + List elements = new ArrayList(coll); + Collections.reverse(elements); + elements.add(0, getName()); + + return commandExecutor.writeAsync(getName(), codec, LPUSH_BOOLEAN, elements.toArray()); + } + + List args = new ArrayList(coll.size() + 1); + args.add(index); + args.addAll(coll); + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_BOOLEAN_ARGS2, + "local ind = table.remove(ARGV, 1); " + // index is the first parameter + "local size = redis.call('llen', KEYS[1]); " + + "assert(tonumber(ind) <= size, 'index: ' .. ind .. ' but current size: ' .. size); " + + "local tail = redis.call('lrange', KEYS[1], ind, -1); " + + "redis.call('ltrim', KEYS[1], 0, ind - 1); " + + "for i=1, #ARGV, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); " + + "end " + + "if #tail > 0 then " + + "for i=1, #tail, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); " + + "end " + + "end;" + + "return 1;", + Collections.singletonList(getName()), args.toArray()); + } + + @Override + public boolean addAll(int index, Collection coll) { + return get(addAllAsync(index, coll)); + } + + @Override + public Future removeAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local v = 0 " + + "for i = 2, #ARGV, 1 do " + + "if redis.call('lrem', KEYS[2], 0, ARGV[i]) == 1 " + + "then v = 1 end " + +"end " + + "return v ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public boolean removeAll(Collection c) { + return get(removeAllAsync(c)); + } + + @Override + public boolean retainAll(Collection c) { + return get(retainAllAsync(c)); + } + + @Override + public Future retainAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local changed = 0; " + + "local s = redis.call('lrange', KEYS[2], 0, -1); " + + "local i = 0; " + + "while i <= #s do " + + "local element = s[i]; " + + "local isInAgrs = false; " + + "for j = 2, #ARGV, 1 do " + + "if ARGV[j] == element then " + + "isInAgrs = true; " + + "break; " + + "end; " + + "end; " + + "if isInAgrs == false then " + + "redis.call('lrem', KEYS[2], 0, element); " + + "changed = 1; " + + "end; " + + "i = i + 1; " + + "end; " + + "return changed; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + + @Override + public void clear() { + delete(); + } + + @Override + public Future getAsync(int index) { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_GET, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return nil;" + + "end; " + + "return redis.call('lindex', KEYS[2], ARGV[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), index, key); + } + + @Override + public V get(int index) { + checkIndex(index); + return getValue(index); + } + + V getValue(int index) { + return get(getAsync(index)); + } + + private void checkIndex(int index) { + int size = size(); + if (!isInRange(index, size)) + throw new IndexOutOfBoundsException("index: " + index + " but current size: "+ size); + } + + private boolean isInRange(int index, int size) { + return index >= 0 && index < size; + } + + @Override + public V set(int index, V element) { + checkIndex(index); + return get(setAsync(index, element)); + } + + @Override + public Future setAsync(int index, V element) { + return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", 5), + "local v = redis.call('lindex', KEYS[1], ARGV[1]); " + + "redis.call('lset', KEYS[1], ARGV[1], ARGV[2]); " + + "return v", + Collections.singletonList(getName()), index, element); + } + + @Override + public void fastSet(int index, V element) { + get(fastSetAsync(index, element)); + } + + @Override + public Future fastSetAsync(int index, V element) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LSET, getName(), index, element); + } + + @Override + public void add(int index, V element) { + addAll(index, Collections.singleton(element)); + } + + @Override + public V remove(int index) { + checkIndex(index); + + if (index == 0) { + Future f = commandExecutor.writeAsync(getName(), codec, LPOP, getName()); + return get(f); + } + + Future f = commandExecutor.evalWriteAsync(getName(), codec, EVAL_OBJECT, + "local v = redis.call('lindex', KEYS[1], ARGV[1]); " + + "local tail = redis.call('lrange', KEYS[1], ARGV[1] + 1, -1);" + + "redis.call('ltrim', KEYS[1], 0, ARGV[1] - 1);" + + "if #tail > 0 then " + + "for i=1, #tail, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); " + + "end " + + "end;" + + "return v", + Collections.singletonList(getName()), index); + return get(f); + } + + @Override + public int indexOf(Object o) { + return get(indexOfAsync(o)); + } + + @Override + public Future containsAsync(Object o) { + return indexOfAsync(o, new BooleanNumberReplayConvertor(-1L)); + } + + private Future indexOfAsync(Object o, Convertor convertor) { + return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", convertor, 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)), + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return -1;" + + "end; " + + + "local items = redis.call('lrange', KEYS[2], 0, -1); " + + "for i=1,#items do " + + "if items[i] == ARGV[3] then " + + "return i - 1; " + + "end; " + + "end; " + + "return -1;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public Future indexOfAsync(Object o) { + return indexOfAsync(o, new IntegerReplayConvertor()); + } + + @Override + public int lastIndexOf(Object o) { + return get(lastIndexOfAsync(o)); + } + + @Override + public Future lastIndexOfAsync(Object o) { + return commandExecutor.evalReadAsync(getName(), codec, LAST_INDEX, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return -1;" + + "end; " + + + "local items = redis.call('lrange', KEYS[1], 0, -1) " + + "for i = #items, 0, -1 do " + + "if items[i] == ARGV[1] then " + + "return i - 1 " + + "end " + + "end " + + "return -1", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public void trim(int fromIndex, int toIndex) { + get(trimAsync(fromIndex, toIndex)); + } + + @Override + public Future trimAsync(int fromIndex, int toIndex) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LTRIM, getName(), fromIndex, toIndex); + } + + @Override + public ListIterator listIterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator(final int ind) { + return new ListIterator() { + + private V prevCurrentValue; + private V nextCurrentValue; + private V currentValueHasRead; + private int currentIndex = ind - 1; + private boolean hasBeenModified = true; + + @Override + public boolean hasNext() { + V val = RedissonListMultimapValues.this.getValue(currentIndex+1); + if (val != null) { + nextCurrentValue = val; + } + return val != null; + } + + @Override + public V next() { + if (nextCurrentValue == null && !hasNext()) { + throw new NoSuchElementException("No such element at index " + currentIndex); + } + currentIndex++; + currentValueHasRead = nextCurrentValue; + nextCurrentValue = null; + hasBeenModified = false; + return currentValueHasRead; + } + + @Override + public void remove() { + if (currentValueHasRead == null) { + throw new IllegalStateException("Neither next nor previous have been called"); + } + if (hasBeenModified) { + throw new IllegalStateException("Element been already deleted"); + } + RedissonListMultimapValues.this.remove(currentIndex); + currentIndex--; + hasBeenModified = true; + currentValueHasRead = null; + } + + @Override + public boolean hasPrevious() { + if (currentIndex < 0) { + return false; + } + V val = RedissonListMultimapValues.this.getValue(currentIndex); + if (val != null) { + prevCurrentValue = val; + } + return val != null; + } + + @Override + public V previous() { + if (prevCurrentValue == null && !hasPrevious()) { + throw new NoSuchElementException("No such element at index " + currentIndex); + } + currentIndex--; + hasBeenModified = false; + currentValueHasRead = prevCurrentValue; + prevCurrentValue = null; + return currentValueHasRead; + } + + @Override + public int nextIndex() { + return currentIndex + 1; + } + + @Override + public int previousIndex() { + return currentIndex; + } + + @Override + public void set(V e) { + if (hasBeenModified) { + throw new IllegalStateException(); + } + + RedissonListMultimapValues.this.fastSet(currentIndex, e); + } + + @Override + public void add(V e) { + RedissonListMultimapValues.this.add(currentIndex+1, e); + currentIndex++; + hasBeenModified = true; + } + }; + } + + @Override + public RList subList(int fromIndex, int toIndex) { + int size = size(); + if (fromIndex < 0 || toIndex > size) { + throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " toIndex: " + toIndex + " size: " + size); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex: " + fromIndex + " toIndex: " + toIndex); + } + + return new RedissonSubList(codec, commandExecutor, getName(), fromIndex, toIndex); + } + + public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + V e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof List)) + return false; + + Iterator e1 = iterator(); + Iterator e2 = ((List) o).iterator(); + while (e1.hasNext() && e2.hasNext()) { + V o1 = e1.next(); + Object o2 = e2.next(); + if (!(o1==null ? o2==null : o1.equals(o2))) + return false; + } + return !(e1.hasNext() || e2.hasNext()); + } + + @Override + public int hashCode() { + int hashCode = 1; + for (V e : this) { + hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); + } + return hashCode; + } + +} diff --git a/src/main/java/org/redisson/core/RListMultimapCache.java b/src/main/java/org/redisson/core/RListMultimapCache.java new file mode 100644 index 000000000..01d97baa5 --- /dev/null +++ b/src/main/java/org/redisson/core/RListMultimapCache.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public interface RListMultimapCache extends RListMultimap, RMultimapCache { + +} diff --git a/src/test/java/org/redisson/RedissonListMultimapCacheTest.java b/src/test/java/org/redisson/RedissonListMultimapCacheTest.java new file mode 100644 index 000000000..5f5e500e4 --- /dev/null +++ b/src/test/java/org/redisson/RedissonListMultimapCacheTest.java @@ -0,0 +1,139 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.redisson.core.RMultimapCache; + +public class RedissonListMultimapCacheTest extends BaseTest { + + @Test + public void testContains() { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.containsKey("1")).isTrue(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isTrue(); + assertThat(multimap.containsValue("3")).isTrue(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isTrue(); + assertThat(multimap.containsEntry("1", "3")).isTrue(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testContainsExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.containsKey("1")).isFalse(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isFalse(); + assertThat(multimap.containsValue("3")).isFalse(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isFalse(); + assertThat(multimap.containsEntry("1", "3")).isFalse(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testGetAll() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.getAll("1")).containsOnlyOnce("1", "2", "3"); + } + + @Test + public void testGetAllExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.getAll("1")).isEmpty(); + } + + @Test + public void testValues() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.put("1", "3"); + + assertThat(multimap.get("1").size()).isEqualTo(4); + assertThat(multimap.get("1")).containsExactly("1", "2", "3", "3"); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").contains("2")).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1", "2"))).isTrue(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isTrue(); + } + + @Test + public void testValuesExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.get("1").size()).isZero(); + assertThat(multimap.get("1")).contains(); + assertThat(multimap.get("1").remove("3")).isFalse(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isFalse(); + } + + @Test + public void testScheduler() throws InterruptedException { + RMultimapCache cache = redisson.getListMultimapCache("simple33"); + assertThat(cache.put("1", "1")).isTrue(); + assertThat(cache.put("1", "2")).isTrue(); + assertThat(cache.put("1", "3")).isTrue(); + assertThat(cache.put("2", "1")).isTrue(); + assertThat(cache.put("2", "2")).isTrue(); + assertThat(cache.put("2", "3")).isTrue(); + + assertThat(cache.expireKey("1", 2, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("2", 3, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("3", 3, TimeUnit.SECONDS)).isFalse(); + + assertThat(cache.size()).isEqualTo(6); + + Thread.sleep(10000); + + assertThat(cache.size()).isZero(); + + } + + +} From 3207687300a6d5be6c375f6fca881ea08c0a8746 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 15:03:37 +0300 Subject: [PATCH 22/47] RuntimeExeception replaced with IllegalArgumentException --- .../org/redisson/RedissonListMultimap.java | 20 +++++++++---------- .../org/redisson/RedissonSetMultimap.java | 20 +++++++++---------- .../redisson/RedissonSetMultimapValues.java | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/redisson/RedissonListMultimap.java b/src/main/java/org/redisson/RedissonListMultimap.java index 3eb98f9ea..d2e9db44e 100644 --- a/src/main/java/org/redisson/RedissonListMultimap.java +++ b/src/main/java/org/redisson/RedissonListMultimap.java @@ -74,7 +74,7 @@ public class RedissonListMultimap extends RedissonMultimap implement String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, LLEN_VALUE, setName); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -99,7 +99,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 0; ", Arrays.asList(getName()), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -126,7 +126,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 0; ", Collections.singletonList(setName), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -148,7 +148,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 1; ", Arrays.asList(getName(), setName), keyState, keyHash, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -167,7 +167,7 @@ public class RedissonListMultimap extends RedissonMultimap implement + "return res; ", Arrays.asList(getName(), setName), keyState, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -189,7 +189,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return redis.call('rpush', KEYS[2], unpack(ARGV, 3, #ARGV)); ", Arrays.asList(getName(), setName), params.toArray()); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -203,7 +203,7 @@ public class RedissonListMultimap extends RedissonMultimap implement return new RedissonList(codec, commandExecutor, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -220,7 +220,7 @@ public class RedissonListMultimap extends RedissonMultimap implement return commandExecutor.readAsync(getName(), codec, RedisCommands.LRANGE, setName, 0, -1); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -242,7 +242,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return members; ", Arrays.asList(getName(), setName), keyState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -272,7 +272,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return members; ", Arrays.asList(getName(), setName), params.toArray()); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/redisson/RedissonSetMultimap.java b/src/main/java/org/redisson/RedissonSetMultimap.java index 35da45b53..3989bb762 100644 --- a/src/main/java/org/redisson/RedissonSetMultimap.java +++ b/src/main/java/org/redisson/RedissonSetMultimap.java @@ -77,7 +77,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SCARD_VALUE, setName); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -98,7 +98,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return 0; ", Arrays.asList(getName()), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -111,7 +111,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SISMEMBER_VALUE, setName, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -127,7 +127,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return redis.call('sadd', KEYS[2], ARGV[3]); ", Arrays.asList(getName(), setName), keyState, keyHash, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -146,7 +146,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements + "return res; ", Arrays.asList(getName(), setName), keyState, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -168,7 +168,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); ", Arrays.asList(getName(), setName), params.toArray()); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -181,7 +181,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements return new RedissonSet(codec, commandExecutor, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -198,7 +198,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements return commandExecutor.readAsync(getName(), codec, RedisCommands.SMEMBERS, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -220,7 +220,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return members; ", Arrays.asList(getName(), setName), keyState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -268,7 +268,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return members; ", Arrays.asList(getName(), setName), params.toArray()); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/redisson/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java index be9fab67c..215c82cd4 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapValues.java +++ b/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -42,7 +42,7 @@ import org.redisson.core.RSet; import io.netty.util.concurrent.Future; /** - * Distributed and concurrent implementation of {@link java.util.Set} + * Set based Multimap Cache values holder * * @author Nikita Koksharov * From a941a7f3a174a3bd2e67d5d73cd3bfa579c928ef Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 15:26:14 +0300 Subject: [PATCH 23/47] Config params validation added. #431 --- src/main/java/org/redisson/Redisson.java | 22 +++++++++++++ src/test/java/org/redisson/RedissonTest.java | 34 +++++++------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 2a0d66edb..648beb275 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -93,15 +93,21 @@ public class Redisson implements RedissonClient { Redisson(Config config) { this.config = config; Config configCopy = new Config(config); + if (configCopy.getMasterSlaveServersConfig() != null) { + validate(configCopy.getMasterSlaveServersConfig()); connectionManager = new MasterSlaveConnectionManager(configCopy.getMasterSlaveServersConfig(), configCopy); } else if (configCopy.getSingleServerConfig() != null) { + validate(configCopy.getSingleServerConfig()); connectionManager = new SingleConnectionManager(configCopy.getSingleServerConfig(), configCopy); } else if (configCopy.getSentinelServersConfig() != null) { + validate(configCopy.getSentinelServersConfig()); connectionManager = new SentinelConnectionManager(configCopy.getSentinelServersConfig(), configCopy); } else if (configCopy.getClusterServersConfig() != null) { + validate(configCopy.getClusterServersConfig()); connectionManager = new ClusterConnectionManager(configCopy.getClusterServersConfig(), configCopy); } else if (configCopy.getElasticacheServersConfig() != null) { + validate(configCopy.getElasticacheServersConfig()); connectionManager = new ElasticacheConnectionManager(configCopy.getElasticacheServersConfig(), configCopy); } else { throw new IllegalArgumentException("server(s) address(es) not defined!"); @@ -110,7 +116,23 @@ public class Redisson implements RedissonClient { evictionScheduler = new EvictionScheduler(commandExecutor); } + private void validate(SingleServerConfig config) { + if (config.getConnectionPoolSize() < config.getConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("connectionPoolSize can't be lower than connectionMinimumIdleSize"); + } + } + private void validate(BaseMasterSlaveServersConfig config) { + if (config.getSlaveConnectionPoolSize() < config.getSlaveConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("slaveConnectionPoolSize can't be lower than slaveConnectionMinimumIdleSize"); + } + if (config.getMasterConnectionPoolSize() < config.getMasterConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("masterConnectionPoolSize can't be lower than masterConnectionMinimumIdleSize"); + } + if (config.getSlaveSubscriptionConnectionPoolSize() < config.getSlaveSubscriptionConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("slaveSubscriptionConnectionMinimumIdleSize can't be lower than slaveSubscriptionConnectionPoolSize"); + } + } /** * Create sync/async Redisson instance with default config diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 74aeef7d5..ee032a4d9 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -1,6 +1,7 @@ package org.redisson; -import static org.assertj.core.api.Assertions.*; +import static com.jayway.awaitility.Awaitility.await; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.net.InetSocketAddress; @@ -12,36 +13,15 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.Test; -import org.redisson.client.RedisConnection; +import org.redisson.RedisRunner.RedisProcess; import org.redisson.client.RedisConnectionException; import org.redisson.client.RedisOutOfMemoryException; import org.redisson.client.WriteRedisConnectionException; -import org.redisson.client.codec.StringCodec; -import org.redisson.client.handler.CommandDecoder; -import org.redisson.client.handler.CommandEncoder; -import org.redisson.client.handler.CommandsListEncoder; -import org.redisson.client.handler.CommandsQueue; -import org.redisson.client.handler.ConnectionWatchdog; import org.redisson.codec.SerializationCodec; import org.redisson.connection.ConnectionListener; import org.redisson.core.ClusterNode; import org.redisson.core.Node; import org.redisson.core.NodesGroup; -import org.redisson.core.RBatch; -import org.redisson.core.RMap; - -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.util.concurrent.GenericFutureListener; - -import static com.jayway.awaitility.Awaitility.*; -import org.redisson.RedisRunner.RedisProcess; public class RedissonTest { @@ -94,6 +74,14 @@ public class RedissonTest { } } + @Test(expected = IllegalArgumentException.class) + public void testConfigValidation() { + Config redissonConfig = new Config(); + redissonConfig.useSingleServer() + .setAddress("127.0.0.1:6379") + .setConnectionPoolSize(2); + Redisson.create(redissonConfig); + } @Test public void testConnectionListener() throws IOException, InterruptedException, TimeoutException { From bc55b700e63d11742bcad27bf71cef0db676a2e5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 16:20:19 +0300 Subject: [PATCH 24/47] ByteBuffer switched to netty ByteBuffer --- src/main/java/org/redisson/RedissonSetCache.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 42726bafb..8a9083c75 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -39,6 +39,10 @@ import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RSetCache; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.base64.Base64; +import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import net.openhft.hashing.LongHashFunction; @@ -114,10 +118,12 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< long h1 = LongHashFunction.farmUo().hashBytes(objectState); long h2 = LongHashFunction.xx_r39().hashBytes(objectState); - return ByteBuffer.allocate((2 * Long.SIZE) / Byte.SIZE) - .putLong(h1) - .putLong(h2) - .array(); + ByteBuf buf = Unpooled.buffer((2 * Long.SIZE) / Byte.SIZE).writeLong(h1).writeLong(h2); + try { + return buf.array(); + } finally { + buf.release(); + } } String getTimeoutSetName() { From 3720a71c55df209ff74185bf9045bef9a6b5b6e1 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 19:41:42 +0300 Subject: [PATCH 25/47] few fixes --- src/test/java/org/redisson/RedissonTopicTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/redisson/RedissonTopicTest.java b/src/test/java/org/redisson/RedissonTopicTest.java index 5d1d09ef4..7373a5365 100644 --- a/src/test/java/org/redisson/RedissonTopicTest.java +++ b/src/test/java/org/redisson/RedissonTopicTest.java @@ -105,7 +105,8 @@ public class RedissonTopicTest { }); topic1.removeListener(listenerId); topic1.removeListener(listenerId2); - l.await(); + + Assert.assertTrue(l.await(5, TimeUnit.SECONDS)); } @Test @@ -166,7 +167,7 @@ public class RedissonTopicTest { }); topic2.publish(new Message("123")); - messageRecieved.await(); + Assert.assertTrue(messageRecieved.await(5, TimeUnit.SECONDS)); redisson1.shutdown(); redisson2.shutdown(); @@ -231,7 +232,7 @@ public class RedissonTopicTest { } }); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < 5000; i++) { topic2.publish(new Message("123")); } @@ -239,7 +240,7 @@ public class RedissonTopicTest { Thread.sleep(1000); - Assert.assertEquals(500, counter); + Assert.assertEquals(5000, counter); redisson1.shutdown(); redisson2.shutdown(); From dc9d1776a44f92ae076a7a37c7179eeee5d5b27d Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 19:51:12 +0300 Subject: [PATCH 26/47] test enhansments --- src/test/java/org/redisson/RedissonTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index ee032a4d9..92b551c11 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -49,6 +49,7 @@ public class RedissonTest { try { RedissonClient r = Redisson.create(config); + r.getKeys().flushall(); for (int i = 0; i < 10000; i++) { r.getMap("test").put("" + i, "" + i); } @@ -66,6 +67,7 @@ public class RedissonTest { try { RedissonClient r = Redisson.create(config); + r.getKeys().flushall(); for (int i = 0; i < 10000; i++) { r.getMap("test").fastPut("" + i, "" + i); } From 950308956a3b135c718ff4842659e4087e1178a5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 19:51:25 +0300 Subject: [PATCH 27/47] small refactoring --- .../client/handler/CommandDecoder.java | 24 +++++++++++-------- .../org/redisson/client/handler/State.java | 5 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index 4ef080b03..f7b150e9e 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -100,8 +100,10 @@ public class CommandDecoder extends ReplayingDecoder { try { // if (state().getSize() > 0) { // List respParts = new ArrayList(); -// respParts.addAll(state().getRespParts()); -// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts); +// if (state().getRespParts() != null) { +// respParts = state().getRespParts(); +// } +// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts, true); // } else { decode(in, cmd, null, ctx.channel(), currentDecoder); // } @@ -149,7 +151,6 @@ public class CommandDecoder extends ReplayingDecoder { } } else { if (!promise.trySuccess(null) && promise.cause() instanceof RedisTimeoutException) { - // TODO try increase timeout log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data); } } @@ -214,21 +215,27 @@ public class CommandDecoder extends ReplayingDecoder { handleResult(data, parts, result, false, channel); } else if (code == '*') { long size = readLong(in); -// state().setSizeOnce(size); - List respParts = new ArrayList(); + boolean top = false; +// if (state().trySetSize(size)) { +// state().setRespParts(respParts); +// top = true; +// } - decodeMulti(in, data, parts, channel, currentDecoder, size, respParts); + decodeMulti(in, data, parts, channel, currentDecoder, size, respParts, top); } else { throw new IllegalStateException("Can't decode replay " + (char)code); } } private void decodeMulti(ByteBuf in, CommandData data, List parts, - Channel channel, Decoder currentDecoder, long size, List respParts) + Channel channel, Decoder currentDecoder, long size, List respParts, boolean top) throws IOException { for (int i = respParts.size(); i < size; i++) { decode(in, data, respParts, channel, currentDecoder); +// if (top) { +// checkpoint(); +// } } MultiDecoder decoder = messageDecoder(data, respParts, channel); @@ -250,9 +257,6 @@ public class CommandDecoder extends ReplayingDecoder { } } else { handleMultiResult(data, parts, channel, result); -// if (parts != null && !decoder.isApplicable(parts.size(), state())) { -// state().setRespParts(parts); -// } } } diff --git a/src/main/java/org/redisson/client/handler/State.java b/src/main/java/org/redisson/client/handler/State.java index 8eed56b08..d9d1ba579 100644 --- a/src/main/java/org/redisson/client/handler/State.java +++ b/src/main/java/org/redisson/client/handler/State.java @@ -29,11 +29,12 @@ public class State { super(); } - public void setSizeOnce(long size) { + public boolean trySetSize(long size) { if (this.size != 0) { - return; + return false; } this.size = size; + return true; } public long getSize() { return size; From 9c33b274f98d358e5be81417dddf5e65ec310678 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 13:55:56 +0300 Subject: [PATCH 28/47] Ability to cancel BRPOP and BLPOP async command execution. #446 --- .../org/redisson/client/RedisConnection.java | 4 +- .../redisson/command/CommandAsyncService.java | 12 +++++ .../redisson/RedissonBlockingQueueTest.java | 50 ++++++++++++++++--- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/redisson/client/RedisConnection.java b/src/main/java/org/redisson/client/RedisConnection.java index 85ff35041..53f1c9383 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -174,8 +174,8 @@ public class RedisConnection implements RedisCommands { return closed; } - public void forceReconnect() { - channel.close(); + public ChannelFuture forceReconnectAsync() { + return channel.close(); } /** diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index d8c44d2fa..f2e63379a 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -462,6 +462,18 @@ public class CommandAsyncService implements CommandAsyncExecutor { int timeoutTime = connectionManager.getConfig().getTimeout(); if (skipTimeout.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); + details.getMainPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isCancelled()) { + return; + } + // cancel handling for commands from skipTimeout collection + if (details.getAttemptPromise().cancel(true)) { + connection.forceReconnectAsync(); + } + } + }); if (popTimeout == 0) { return; } diff --git a/src/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index d28a5d3da..5fab2294d 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -1,9 +1,6 @@ package org.redisson; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.HashSet; @@ -22,8 +19,47 @@ import org.junit.Assert; import org.junit.Test; import org.redisson.core.RBlockingQueue; +import io.netty.util.concurrent.Future; + public class RedissonBlockingQueueTest extends BaseTest { + @Test + public void testTakeAsyncCancel() { + Config config = createConfig(); + config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); + + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + for (int i = 0; i < 10; i++) { + Future f = queue1.takeAsync(); + f.cancel(true); + } + assertThat(queue1.add(1)).isTrue(); + assertThat(queue1.add(2)).isTrue(); + assertThat(queue1.size()).isEqualTo(2); + + redisson.shutdown(); + } + + @Test + public void testPollAsyncCancel() { + Config config = createConfig(); + config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); + + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + for (int i = 0; i < 10; i++) { + Future f = queue1.pollAsync(1, TimeUnit.SECONDS); + f.cancel(true); + } + assertThat(queue1.add(1)).isTrue(); + assertThat(queue1.add(2)).isTrue(); + assertThat(queue1.size()).isEqualTo(2); + + redisson.shutdown(); + } + + @Test public void testPollFromAny() throws InterruptedException { final RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); @@ -225,14 +261,14 @@ public class RedissonBlockingQueueTest extends BaseTest { try { // blocking int item = queue.take(); - assertTrue(item > 0 && item <= total); + assertThat(item > 0 && item <= total).isTrue(); } catch (InterruptedException exception) { - fail(); + Assert.fail(); } count++; } - assertThat(counter.get(), equalTo(total)); + assertThat(counter.get()).isEqualTo(total); queue.delete(); } From 73452d9974566963442c41f3dfeba3bdda8369c1 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:23:33 +0300 Subject: [PATCH 29/47] RemoteService implemented. #434 --- src/main/java/org/redisson/Redisson.java | 9 +- .../java/org/redisson/RedissonClient.java | 8 + .../org/redisson/RedissonRemoteService.java | 152 ++++++++++++++++++ .../java/org/redisson/RemoteServiceKey.java | 68 ++++++++ .../org/redisson/RemoteServiceMethod.java | 39 +++++ .../org/redisson/RemoteServiceRequest.java | 54 +++++++ .../org/redisson/RemoteServiceResponse.java | 42 +++++ .../client/handler/CommandDecoder.java | 8 +- .../org/redisson/codec/JsonJacksonCodec.java | 15 ++ .../org/redisson/core/RRemoteService.java | 36 +++++ .../redisson/RedissonRemoteServiceTest.java | 83 ++++++++++ 11 files changed, 509 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonRemoteService.java create mode 100644 src/main/java/org/redisson/RemoteServiceKey.java create mode 100644 src/main/java/org/redisson/RemoteServiceMethod.java create mode 100644 src/main/java/org/redisson/RemoteServiceRequest.java create mode 100644 src/main/java/org/redisson/RemoteServiceResponse.java create mode 100644 src/main/java/org/redisson/core/RRemoteService.java create mode 100644 src/test/java/org/redisson/RedissonRemoteServiceTest.java diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 648beb275..096483e1d 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -58,10 +58,10 @@ import org.redisson.core.RListMultimapCache; import org.redisson.core.RLock; import org.redisson.core.RMap; import org.redisson.core.RMapCache; -import org.redisson.core.RMultimapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; +import org.redisson.core.RRemoteService; import org.redisson.core.RScoredSortedSet; import org.redisson.core.RScript; import org.redisson.core.RSemaphore; @@ -87,6 +87,7 @@ public class Redisson implements RedissonClient { private final CommandExecutor commandExecutor; private final ConnectionManager connectionManager; private final Config config; + private final RedissonRemoteService remoteService; private final UUID id = UUID.randomUUID(); @@ -114,6 +115,7 @@ public class Redisson implements RedissonClient { } commandExecutor = new CommandSyncService(connectionManager); evictionScheduler = new EvictionScheduler(commandExecutor); + remoteService = new RedissonRemoteService(this); } private void validate(SingleServerConfig config) { @@ -369,6 +371,10 @@ public class Redisson implements RedissonClient { return new RedissonScript(commandExecutor); } + public RRemoteService getRemoteSerivce() { + return remoteService; + } + @Override public RSortedSet getSortedSet(String name) { return new RedissonSortedSet(commandExecutor, name); @@ -501,6 +507,7 @@ public class Redisson implements RedissonClient { @Override public void shutdown() { + remoteService.shutdown(); connectionManager.shutdown(); } diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index 270877e9f..edb10dced 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -45,6 +45,7 @@ import org.redisson.core.RMapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; +import org.redisson.core.RRemoteService; import org.redisson.core.RScoredSortedSet; import org.redisson.core.RScript; import org.redisson.core.RSemaphore; @@ -585,6 +586,13 @@ public interface RedissonClient { */ RScript getScript(); + /** + * Returns object for remote operations + * + * @return + */ + RRemoteService getRemoteSerivce(); + /** * Return batch object which executes group of * command in pipeline. diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java new file mode 100644 index 000000000..d3dac19b7 --- /dev/null +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -0,0 +1,152 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import org.redisson.core.MessageListener; +import org.redisson.core.RBlockingQueue; +import org.redisson.core.RRemoteService; +import org.redisson.core.RTopic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.buffer.ByteBufUtil; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.ThreadLocalRandom; + +public class RedissonRemoteService implements RRemoteService { + + private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); + + private final Map beans = PlatformDependent.newConcurrentHashMap(); + private final Queue> futures = new ConcurrentLinkedQueue>(); + + private final Redisson redisson; + + public RedissonRemoteService(Redisson redisson) { + this.redisson = redisson; + } + + @Override + public void register(Class serviceInterface, T object) { + for (Method method : serviceInterface.getMethods()) { + RemoteServiceMethod value = new RemoteServiceMethod(method, object); + RemoteServiceKey key = new RemoteServiceKey(serviceInterface, method.getName()); + if (beans.put(key, value) != null) { + return; + } + } + + String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + subscribe(serviceInterface, requestQueue); + } + + private void subscribe(final Class serviceInterface, final RBlockingQueue requestQueue) { + Future take = requestQueue.takeAsync(); + futures.add(take); + take.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + return; + } + + RemoteServiceRequest request = future.getNow(); + RemoteServiceMethod method = beans.get(new RemoteServiceKey(serviceInterface, request.getMethodName())); + String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + request.getRequestId(); + RTopic topic = redisson.getTopic(responseName); + RemoteServiceResponse response; + try { + Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); + response = new RemoteServiceResponse(result); + } catch (Exception e) { + e.getCause().printStackTrace(); + response = new RemoteServiceResponse(e.getCause()); + log.error("Can't execute: " + method.getMethod().getName() + " with args: " + request.getArgs(), e); + } + + long clients = topic.publish(response); + if (clients == 0) { + log.error("None of clients has not received a response for request {}", request); + } + + futures.remove(future); + subscribe(serviceInterface, requestQueue); + } + }); + } + + @Override + public T get(final Class serviceInterface) { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String requestId = generateRequestId(); + System.out.println(requestId); + + String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + requestQueue.add(new RemoteServiceRequest(requestId, method.getName(), args)); + + String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + requestId; + final RTopic topic = redisson.getTopic(responseName); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference response = new AtomicReference(); + int listenerId = topic.addListener(new MessageListener() { + @Override + public void onMessage(String channel, RemoteServiceResponse msg) { + response.set(msg); + latch.countDown(); + } + }); + + latch.await(); + topic.removeListener(listenerId); + RemoteServiceResponse msg = response.get(); + if (msg.getError() != null) { + throw msg.getError(); + } + return msg.getResult(); + } + }; + return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class[] {serviceInterface}, handler); + } + + private String generateRequestId() { + byte[] id = new byte[16]; + // TODO JDK UPGRADE replace to native ThreadLocalRandom + ThreadLocalRandom.current().nextBytes(id); + return ByteBufUtil.hexDump(id); + } + + public void shutdown() { + for (Future future : futures) { + future.cancel(true); + } + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceKey.java b/src/main/java/org/redisson/RemoteServiceKey.java new file mode 100644 index 000000000..5cadb3a5a --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceKey.java @@ -0,0 +1,68 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +public class RemoteServiceKey { + + private final Class serviceInterface; + private final String methodName; + + public RemoteServiceKey(Class serviceInterface, String methodName) { + super(); + this.serviceInterface = serviceInterface; + this.methodName = methodName; + } + + public String getMethodName() { + return methodName; + } + + public Class getServiceInterface() { + return serviceInterface; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((methodName == null) ? 0 : methodName.hashCode()); + result = prime * result + ((serviceInterface == null) ? 0 : serviceInterface.getName().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RemoteServiceKey other = (RemoteServiceKey) obj; + if (methodName == null) { + if (other.methodName != null) + return false; + } else if (!methodName.equals(other.methodName)) + return false; + if (serviceInterface == null) { + if (other.serviceInterface != null) + return false; + } else if (!serviceInterface.equals(other.serviceInterface)) + return false; + return true; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceMethod.java b/src/main/java/org/redisson/RemoteServiceMethod.java new file mode 100644 index 000000000..153c82d19 --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceMethod.java @@ -0,0 +1,39 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.lang.reflect.Method; + +public class RemoteServiceMethod { + + private final Object bean; + private final Method method; + + public RemoteServiceMethod(Method method, Object bean) { + super(); + this.method = method; + this.bean = bean; + } + + public Object getBean() { + return bean; + } + + public Method getMethod() { + return method; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceRequest.java b/src/main/java/org/redisson/RemoteServiceRequest.java new file mode 100644 index 000000000..3934a78b7 --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceRequest.java @@ -0,0 +1,54 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.util.Arrays; + +public class RemoteServiceRequest { + + private String requestId; + private String methodName; + private Object[] args; + + public RemoteServiceRequest() { + } + + public RemoteServiceRequest(String requestId, String methodName, Object[] args) { + super(); + this.requestId = requestId; + this.methodName = methodName; + this.args = args; + } + + public String getRequestId() { + return requestId; + } + + public Object[] getArgs() { + return args; + } + + public String getMethodName() { + return methodName; + } + + @Override + public String toString() { + return "[requestId=" + requestId + ", methodName=" + methodName + ", args=" + + Arrays.toString(args) + "]"; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceResponse.java b/src/main/java/org/redisson/RemoteServiceResponse.java new file mode 100644 index 000000000..dd14bf06c --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceResponse.java @@ -0,0 +1,42 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +public class RemoteServiceResponse { + + private Object result; + private Throwable error; + + public RemoteServiceResponse() { + } + + public RemoteServiceResponse(Object result) { + this.result = result; + } + + public RemoteServiceResponse(Throwable error) { + this.error = error; + } + + public Throwable getError() { + return error; + } + + public Object getResult() { + return result; + } + +} diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index f7b150e9e..ac868aafd 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -84,12 +84,12 @@ public class CommandDecoder extends ReplayingDecoder { currentDecoder = StringCodec.INSTANCE.getValueDecoder(); } - if (log.isTraceEnabled()) { - log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8)); - } - if (state() == null) { state(new State()); + + if (log.isTraceEnabled()) { + log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8)); + } } state().setDecoderState(null); diff --git a/src/main/java/org/redisson/codec/JsonJacksonCodec.java b/src/main/java/org/redisson/codec/JsonJacksonCodec.java index 711e1156f..ab518b0ed 100755 --- a/src/main/java/org/redisson/codec/JsonJacksonCodec.java +++ b/src/main/java/org/redisson/codec/JsonJacksonCodec.java @@ -23,8 +23,15 @@ import org.redisson.client.protocol.Decoder; import org.redisson.client.protocol.Encoder; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonIdentityReference; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.MapperFeature; @@ -32,6 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import io.netty.buffer.ByteBuf; @@ -49,6 +57,12 @@ public class JsonJacksonCodec implements Codec { public static final JsonJacksonCodec INSTANCE = new JsonJacksonCodec(); + @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id") + @JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.PUBLIC_ONLY, setterVisibility = Visibility.PUBLIC_ONLY, isGetterVisibility = Visibility.PUBLIC_ONLY) + public static class ThrowableMixIn { + + } + private final ObjectMapper mapObjectMapper = initObjectMapper(); protected ObjectMapper initObjectMapper() { @@ -111,6 +125,7 @@ public class JsonJacksonCodec implements Codec { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true); objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class); } @Override diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java new file mode 100644 index 000000000..f7066dc9d --- /dev/null +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -0,0 +1,36 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public interface RRemoteService { + + /** + * Register object as remote service + * + * @param remoteInterface + * @param object + */ + void register(Class remoteInterface, T object); + + /** + * Get remote service object for remote invocations + * + * @param remoteInterface + * @return + */ + T get(Class remoteInterface); + +} diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java new file mode 100644 index 000000000..b6cede708 --- /dev/null +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -0,0 +1,83 @@ +package org.redisson; + +import org.junit.Assert; +import org.junit.Test; +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; + +public class RedissonRemoteServiceTest extends BaseTest { + + public interface RemoteInterface { + + void voidMethod(String name, Long param); + + Long resultMethod(Long value); + + void errorMethod() throws IOException; + + void errorMethodWithCause(); + + } + + public class RemoteImpl implements RemoteInterface { + + @Override + public void voidMethod(String name, Long param) { + System.out.println(name + " " + param); + } + + @Override + public Long resultMethod(Long value) { + return value*2; + } + + @Override + public void errorMethod() throws IOException { + throw new IOException("Checking error throw"); + } + + @Override + public void errorMethodWithCause() { + try { + int s = 2 / 0; + } catch (Exception e) { + throw new RuntimeException("Checking error throw", e); + } + } + + + } + + @Test + public void testInvocations() { + RedissonClient r1 = Redisson.create(); + r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + RedissonClient r2 = Redisson.create(); + RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class); + + ri.voidMethod("someName", 100L); + assertThat(ri.resultMethod(100L)).isEqualTo(200); + + try { + ri.errorMethod(); + Assert.fail(); + } catch (IOException e) { + assertThat(e.getMessage()).isEqualTo("Checking error throw"); + } + + try { + ri.errorMethodWithCause(); + Assert.fail(); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(ArithmeticException.class); + assertThat(e.getCause().getMessage()).isEqualTo("/ by zero"); + } + + + r1.shutdown(); + r2.shutdown(); + } + +} From f152c23d57004296e87ef356590492dabbe4f0d1 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:31:12 +0300 Subject: [PATCH 30/47] sysout removed --- src/main/java/org/redisson/RedissonRemoteService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index d3dac19b7..176897b90 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -106,7 +106,6 @@ public class RedissonRemoteService implements RRemoteService { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String requestId = generateRequestId(); - System.out.println(requestId); String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); From 43e632bfdf93036e1747b1436b0fedaa3a3c8a11 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:39:39 +0300 Subject: [PATCH 31/47] minor RemoteService improvements --- src/main/java/org/redisson/RedissonRemoteService.java | 5 ++--- src/main/java/org/redisson/RemoteServiceRequest.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 176897b90..0beebb71f 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -84,14 +84,13 @@ public class RedissonRemoteService implements RRemoteService { Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); response = new RemoteServiceResponse(result); } catch (Exception e) { - e.getCause().printStackTrace(); response = new RemoteServiceResponse(e.getCause()); - log.error("Can't execute: " + method.getMethod().getName() + " with args: " + request.getArgs(), e); + log.error("Can't execute: " + request, e); } long clients = topic.publish(response); if (clients == 0) { - log.error("None of clients has not received a response for request {}", request); + log.error("None of clients has not received a response for: {}", request); } futures.remove(future); diff --git a/src/main/java/org/redisson/RemoteServiceRequest.java b/src/main/java/org/redisson/RemoteServiceRequest.java index 3934a78b7..434784456 100644 --- a/src/main/java/org/redisson/RemoteServiceRequest.java +++ b/src/main/java/org/redisson/RemoteServiceRequest.java @@ -47,7 +47,7 @@ public class RemoteServiceRequest { @Override public String toString() { - return "[requestId=" + requestId + ", methodName=" + methodName + ", args=" + return "RemoteServiceRequest[requestId=" + requestId + ", methodName=" + methodName + ", args=" + Arrays.toString(args) + "]"; } From 58472c6cf90de1d2b210ad3f75e12290b88a4ffc Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:50:37 +0300 Subject: [PATCH 32/47] RRemoteService.register method with custom executors amount added. #434 --- .../org/redisson/RedissonRemoteService.java | 40 ++++++++++++------- .../org/redisson/core/RRemoteService.java | 11 ++++- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 0beebb71f..55455db33 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -51,21 +51,31 @@ public class RedissonRemoteService implements RRemoteService { } @Override - public void register(Class serviceInterface, T object) { - for (Method method : serviceInterface.getMethods()) { + public void register(Class remoteInterface, T object) { + register(remoteInterface, object, 1); + } + + @Override + public void register(Class remoteInterface, T object, int executorsAmount) { + if (executorsAmount < 1) { + throw new IllegalArgumentException("executorsAmount can't be lower than 1"); + } + for (Method method : remoteInterface.getMethods()) { RemoteServiceMethod value = new RemoteServiceMethod(method, object); - RemoteServiceKey key = new RemoteServiceKey(serviceInterface, method.getName()); + RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName()); if (beans.put(key, value) != null) { return; } } - - String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; - RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); - subscribe(serviceInterface, requestQueue); + + for (int i = 0; i < executorsAmount; i++) { + String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + subscribe(remoteInterface, requestQueue); + } } - private void subscribe(final Class serviceInterface, final RBlockingQueue requestQueue) { + private void subscribe(final Class remoteInterface, final RBlockingQueue requestQueue) { Future take = requestQueue.takeAsync(); futures.add(take); take.addListener(new FutureListener() { @@ -76,8 +86,8 @@ public class RedissonRemoteService implements RRemoteService { } RemoteServiceRequest request = future.getNow(); - RemoteServiceMethod method = beans.get(new RemoteServiceKey(serviceInterface, request.getMethodName())); - String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + request.getRequestId(); + RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); + String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId(); RTopic topic = redisson.getTopic(responseName); RemoteServiceResponse response; try { @@ -94,23 +104,23 @@ public class RedissonRemoteService implements RRemoteService { } futures.remove(future); - subscribe(serviceInterface, requestQueue); + subscribe(remoteInterface, requestQueue); } }); } @Override - public T get(final Class serviceInterface) { + public T get(final Class remoteInterface) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String requestId = generateRequestId(); - String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; + String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); requestQueue.add(new RemoteServiceRequest(requestId, method.getName(), args)); - String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + requestId; + String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; final RTopic topic = redisson.getTopic(responseName); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference response = new AtomicReference(); @@ -131,7 +141,7 @@ public class RedissonRemoteService implements RRemoteService { return msg.getResult(); } }; - return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class[] {serviceInterface}, handler); + return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler); } private String generateRequestId() { diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index f7066dc9d..c322c512b 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -18,13 +18,22 @@ package org.redisson.core; public interface RRemoteService { /** - * Register object as remote service + * Register remote service with single executor * * @param remoteInterface * @param object */ void register(Class remoteInterface, T object); + /** + * Register remote service with custom executors amount + * + * @param remoteInterface + * @param object + * @param executorsAmount + */ + void register(Class remoteInterface, T object, int executorsAmount); + /** * Get remote service object for remote invocations * From 7fc81160668fd2165b49a0bb35c4ad5ab487e4d4 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 17:02:43 +0300 Subject: [PATCH 33/47] RemoteService.get method with timeout invocation added. #434 --- .../java/org/redisson/RedissonRemoteService.java | 14 ++++++++++++-- .../java/org/redisson/core/RRemoteService.java | 13 +++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 55455db33..4126a1d96 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.redisson.core.MessageListener; @@ -110,7 +111,12 @@ public class RedissonRemoteService implements RRemoteService { } @Override - public T get(final Class remoteInterface) { + public T get(Class remoteInterface) { + return get(remoteInterface, -1, null); + } + + @Override + public T get(final Class remoteInterface, final int timeout, final TimeUnit timeUnit) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { @@ -132,7 +138,11 @@ public class RedissonRemoteService implements RRemoteService { } }); - latch.await(); + if (timeout == -1) { + latch.await(); + } else { + latch.await(timeout, timeUnit); + } topic.removeListener(listenerId); RemoteServiceResponse msg = response.get(); if (msg.getError() != null) { diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index c322c512b..f8f27bd1a 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -15,6 +15,8 @@ */ package org.redisson.core; +import java.util.concurrent.TimeUnit; + public interface RRemoteService { /** @@ -42,4 +44,15 @@ public interface RRemoteService { */ T get(Class remoteInterface); + /** + * Get remote service object for remote invocations + * with specified timeout invocation + * + * @param remoteInterface + * @param timeout - timeout invocation + * @param timeUnit + * @return + */ + T get(Class remoteInterface, int timeout, TimeUnit timeUnit); + } From 7884c07b095b8b4bfee69444615239ceeaf6b22a Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 17:12:41 +0300 Subject: [PATCH 34/47] comment fixed --- src/main/java/org/redisson/core/RRemoteService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index f8f27bd1a..1b75c5dc3 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -46,10 +46,10 @@ public interface RRemoteService { /** * Get remote service object for remote invocations - * with specified timeout invocation + * with specified invocation timeout * * @param remoteInterface - * @param timeout - timeout invocation + * @param timeout - invocation timeout * @param timeUnit * @return */ From 5e2cbe6a2fbccf1f1b918d49168347d8ced699c6 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 18:27:13 +0300 Subject: [PATCH 35/47] RemoteSerivce shutdown process optimization. #446 --- src/main/java/org/redisson/Redisson.java | 5 +-- .../org/redisson/RedissonRemoteService.java | 11 ------ .../redisson/command/CommandAsyncService.java | 36 ++++++++++++------- .../connection/ConnectionManager.java | 2 ++ .../MasterSlaveConnectionManager.java | 9 +++++ 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 096483e1d..f1fdf2953 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -87,7 +87,6 @@ public class Redisson implements RedissonClient { private final CommandExecutor commandExecutor; private final ConnectionManager connectionManager; private final Config config; - private final RedissonRemoteService remoteService; private final UUID id = UUID.randomUUID(); @@ -115,7 +114,6 @@ public class Redisson implements RedissonClient { } commandExecutor = new CommandSyncService(connectionManager); evictionScheduler = new EvictionScheduler(commandExecutor); - remoteService = new RedissonRemoteService(this); } private void validate(SingleServerConfig config) { @@ -372,7 +370,7 @@ public class Redisson implements RedissonClient { } public RRemoteService getRemoteSerivce() { - return remoteService; + return new RedissonRemoteService(this); } @Override @@ -507,7 +505,6 @@ public class Redisson implements RedissonClient { @Override public void shutdown() { - remoteService.shutdown(); connectionManager.shutdown(); } diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 4126a1d96..09a9c8d18 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -19,8 +19,6 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -43,7 +41,6 @@ public class RedissonRemoteService implements RRemoteService { private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); private final Map beans = PlatformDependent.newConcurrentHashMap(); - private final Queue> futures = new ConcurrentLinkedQueue>(); private final Redisson redisson; @@ -78,7 +75,6 @@ public class RedissonRemoteService implements RRemoteService { private void subscribe(final Class remoteInterface, final RBlockingQueue requestQueue) { Future take = requestQueue.takeAsync(); - futures.add(take); take.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { @@ -104,7 +100,6 @@ public class RedissonRemoteService implements RRemoteService { log.error("None of clients has not received a response for: {}", request); } - futures.remove(future); subscribe(remoteInterface, requestQueue); } }); @@ -161,10 +156,4 @@ public class RedissonRemoteService implements RRemoteService { return ByteBufUtil.hexDump(id); } - public void shutdown() { - for (Future future : futures) { - future.cancel(true); - } - } - } diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index f2e63379a..d7f0c6126 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -462,18 +462,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { int timeoutTime = connectionManager.getConfig().getTimeout(); if (skipTimeout.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); - details.getMainPromise().addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - if (!future.isCancelled()) { - return; - } - // cancel handling for commands from skipTimeout collection - if (details.getAttemptPromise().cancel(true)) { - connection.forceReconnectAsync(); - } - } - }); + handleBlockingOperations(details, connection); if (popTimeout == 0) { return; } @@ -494,6 +483,29 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.setTimeout(timeout); } + private void handleBlockingOperations(final AsyncDetails details, final RedisConnection connection) { + final FutureListener listener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + details.getMainPromise().cancel(true); + } + }; + details.getMainPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isCancelled()) { + connectionManager.getShutdownPromise().removeListener(listener); + return; + } + // cancel handling for commands from skipTimeout collection + if (details.getAttemptPromise().cancel(true)) { + connection.forceReconnectAsync(); + } + } + }); + connectionManager.getShutdownPromise().addListener(listener); + } + private void checkConnectionFuture(final NodeSource source, final AsyncDetails details) { if (details.getAttemptPromise().isDone() || details.getMainPromise().isCancelled() || details.getConnectionFuture().isCancelled()) { diff --git a/src/main/java/org/redisson/connection/ConnectionManager.java b/src/main/java/org/redisson/connection/ConnectionManager.java index 48b2e2c98..21a275ddb 100644 --- a/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/src/main/java/org/redisson/connection/ConnectionManager.java @@ -107,5 +107,7 @@ public interface ConnectionManager { Timeout newTimeout(TimerTask task, long delay, TimeUnit unit); InfinitySemaphoreLatch getShutdownLatch(); + + Future getShutdownPromise(); } diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 1fb69f386..065e66f75 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -122,6 +122,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager { protected final Map entries = PlatformDependent.newConcurrentHashMap(); + private final Promise shutdownPromise; + private final InfinitySemaphoreLatch shutdownLatch = new InfinitySemaphoreLatch(); private final Set clients = Collections.newSetFromMap(PlatformDependent.newConcurrentHashMap()); @@ -156,6 +158,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { this.socketChannelClass = NioSocketChannel.class; } this.codec = cfg.getCodec(); + this.shutdownPromise = group.next().newPromise(); this.isClusterMode = cfg.isClusterConfig(); } @@ -674,6 +677,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdown() { + shutdownPromise.trySuccess(true); shutdownLatch.closeAndAwaitUninterruptibly(); for (MasterSlaveEntry entry : entries.values()) { entry.shutdown(); @@ -731,6 +735,11 @@ public class MasterSlaveConnectionManager implements ConnectionManager { public InfinitySemaphoreLatch getShutdownLatch() { return shutdownLatch; } + + @Override + public Future getShutdownPromise() { + return shutdownPromise; + } @Override public ConnectionEventsHub getConnectionEventsHub() { From ac3278d94b87a1a71c0aa13b0d7bc7bcf8f7e438 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:10:56 +0300 Subject: [PATCH 36/47] RemoteService timeout handling fixed. #434 --- .../org/redisson/RedissonRemoteService.java | 12 ++++++--- .../org/redisson/RemoteServiceResponse.java | 5 ++++ .../redisson/command/CommandAsyncService.java | 2 +- .../redisson/RedissonRemoteServiceTest.java | 27 ++++++++++++++++++- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 09a9c8d18..b80124260 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -23,6 +23,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import org.redisson.client.RedisException; +import org.redisson.client.RedisTimeoutException; import org.redisson.core.MessageListener; import org.redisson.core.RBlockingQueue; import org.redisson.core.RRemoteService; @@ -97,7 +99,7 @@ public class RedissonRemoteService implements RRemoteService { long clients = topic.publish(response); if (clients == 0) { - log.error("None of clients has not received a response for: {}", request); + log.error("None of clients has not received a response: {} for request: {}", response, request); } subscribe(remoteInterface, requestQueue); @@ -119,7 +121,8 @@ public class RedissonRemoteService implements RRemoteService { String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); - requestQueue.add(new RemoteServiceRequest(requestId, method.getName(), args)); + RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args); + requestQueue.add(request); String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; final RTopic topic = redisson.getTopic(responseName); @@ -136,7 +139,10 @@ public class RedissonRemoteService implements RRemoteService { if (timeout == -1) { latch.await(); } else { - latch.await(timeout, timeUnit); + if (!latch.await(timeout, timeUnit)) { + topic.removeListener(listenerId); + throw new RedisTimeoutException("No response after " + timeUnit.toMillis(timeout) + "ms for request: " + request); + } } topic.removeListener(listenerId); RemoteServiceResponse msg = response.get(); diff --git a/src/main/java/org/redisson/RemoteServiceResponse.java b/src/main/java/org/redisson/RemoteServiceResponse.java index dd14bf06c..09ebc5999 100644 --- a/src/main/java/org/redisson/RemoteServiceResponse.java +++ b/src/main/java/org/redisson/RemoteServiceResponse.java @@ -38,5 +38,10 @@ public class RemoteServiceResponse { public Object getResult() { return result; } + + @Override + public String toString() { + return "RemoteServiceResponse [result=" + result + ", error=" + error + "]"; + } } diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index d7f0c6126..faba6d1fc 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -493,8 +493,8 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.getMainPromise().addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { + connectionManager.getShutdownPromise().removeListener(listener); if (!future.isCancelled()) { - connectionManager.getShutdownPromise().removeListener(listener); return; } // cancel handling for commands from skipTimeout collection diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java index b6cede708..c8daedd90 100644 --- a/src/test/java/org/redisson/RedissonRemoteServiceTest.java +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -2,9 +2,12 @@ package org.redisson; import org.junit.Assert; import org.junit.Test; +import org.redisson.client.RedisTimeoutException; + import static org.assertj.core.api.Assertions.*; import java.io.IOException; +import java.util.concurrent.TimeUnit; public class RedissonRemoteServiceTest extends BaseTest { @@ -18,6 +21,8 @@ public class RedissonRemoteServiceTest extends BaseTest { void errorMethodWithCause(); + void timeoutMethod() throws InterruptedException; + } public class RemoteImpl implements RemoteInterface { @@ -46,8 +51,29 @@ public class RedissonRemoteServiceTest extends BaseTest { } } + @Override + public void timeoutMethod() throws InterruptedException { + Thread.sleep(2000); + } + } + + @Test(expected = RedisTimeoutException.class) + public void testTimeout() throws InterruptedException { + RedissonClient r1 = Redisson.create(); + r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + RedissonClient r2 = Redisson.create(); + RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class, 1, TimeUnit.SECONDS); + + try { + ri.timeoutMethod(); + } finally { + r1.shutdown(); + r2.shutdown(); + } + } @Test public void testInvocations() { @@ -74,7 +100,6 @@ public class RedissonRemoteServiceTest extends BaseTest { assertThat(e.getCause()).isInstanceOf(ArithmeticException.class); assertThat(e.getCause().getMessage()).isEqualTo("/ by zero"); } - r1.shutdown(); r2.shutdown(); From 40d5ad5237cb9d5200248852cfcd05b0dad943dd Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:43:52 +0300 Subject: [PATCH 37/47] Delete and expire Multimap methods fix. #447 --- .../java/org/redisson/RedissonMultimap.java | 71 +++++++++++++++++++ .../org/redisson/RedissonSetMultimapTest.java | 55 +++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/RedissonMultimap.java b/src/main/java/org/redisson/RedissonMultimap.java index 475660b82..53f4b1944 100644 --- a/src/main/java/org/redisson/RedissonMultimap.java +++ b/src/main/java/org/redisson/RedissonMultimap.java @@ -20,15 +20,18 @@ import java.net.InetSocketAddress; import java.util.AbstractCollection; import java.util.AbstractSet; 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 java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import java.util.Set; import org.redisson.client.codec.Codec; +import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.ScanCodec; import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.RedisCommands; @@ -152,10 +155,12 @@ public abstract class RedissonMultimap extends RedissonExpirable implement return new EntrySet(); } + @Override public long fastRemove(K ... keys) { return get(fastRemoveAsync(keys)); } + @Override public Future fastRemoveAsync(K ... keys) { if (keys == null || keys.length == 0) { return newSucceededFuture(0L); @@ -184,6 +189,72 @@ public abstract class RedissonMultimap extends RedissonExpirable implement throw new RuntimeException(e); } } + + @Override + public Future deleteAsync() { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {KEYS[1]}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end;" + + "end; " + + + "local n = 0 " + + "for i=1, #keys,5000 do " + + "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + + "end; " + + "return n;", + Arrays.asList(getName())); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpire', name, ARGV[1]); " + + "end;" + + "end; " + + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", + Arrays.asList(getName()), timeUnit.toMillis(timeToLive)); + } + + @Override + public Future expireAtAsync(long timestamp) { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpireat', name, ARGV[1]); " + + "end;" + + "end; " + + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", + Arrays.asList(getName()), timestamp); + } + + @Override + public Future clearExpireAsync() { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('persist', name); " + + "end;" + + "end; " + + "return redis.call('persist', KEYS[1]); ", + Arrays.asList(getName())); + } + MapScanResult scanIterator(InetSocketAddress client, long startPos) { Future> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec, StringCodec.INSTANCE), RedisCommands.HSCAN, getName(), startPos); diff --git a/src/test/java/org/redisson/RedissonSetMultimapTest.java b/src/test/java/org/redisson/RedissonSetMultimapTest.java index ef47fd980..c30256c53 100644 --- a/src/test/java/org/redisson/RedissonSetMultimapTest.java +++ b/src/test/java/org/redisson/RedissonSetMultimapTest.java @@ -1,15 +1,17 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.redisson.core.RSetMultimap; -import static org.assertj.core.api.Assertions.*; public class RedissonSetMultimapTest extends BaseTest { @@ -273,5 +275,56 @@ public class RedissonSetMultimapTest extends BaseTest { assertThat(allValues).containsOnlyElementsOf(values); } + @Test + public void testExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expire(100, TimeUnit.MILLISECONDS); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testExpireAt() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testClearExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + map.clearExpire(); + + Thread.sleep(500); + + assertThat(map.size()).isEqualTo(2); + } + + @Test + public void testDelete() { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + assertThat(map.delete()).isTrue(); + + RSetMultimap map2 = redisson.getSetMultimap("simple1"); + assertThat(map2.delete()).isFalse(); + } } From dead46a732846e7f45209f5496e03025c1d7c63d Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:45:31 +0300 Subject: [PATCH 38/47] minor changes --- src/main/java/org/redisson/RedissonMultimap.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/redisson/RedissonMultimap.java b/src/main/java/org/redisson/RedissonMultimap.java index 53f4b1944..a072520a5 100644 --- a/src/main/java/org/redisson/RedissonMultimap.java +++ b/src/main/java/org/redisson/RedissonMultimap.java @@ -214,7 +214,6 @@ public abstract class RedissonMultimap extends RedissonExpirable implement public Future expireAsync(long timeToLive, TimeUnit timeUnit) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local entries = redis.call('hgetall', KEYS[1]); " + - "local keys = {}; " + "for i, v in ipairs(entries) do " + "if i % 2 == 0 then " + "local name = '{' .. KEYS[1] .. '}:' .. v; " + @@ -229,7 +228,6 @@ public abstract class RedissonMultimap extends RedissonExpirable implement public Future expireAtAsync(long timestamp) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local entries = redis.call('hgetall', KEYS[1]); " + - "local keys = {}; " + "for i, v in ipairs(entries) do " + "if i % 2 == 0 then " + "local name = '{' .. KEYS[1] .. '}:' .. v; " + @@ -244,7 +242,6 @@ public abstract class RedissonMultimap extends RedissonExpirable implement public Future clearExpireAsync() { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local entries = redis.call('hgetall', KEYS[1]); " + - "local keys = {}; " + "for i, v in ipairs(entries) do " + "if i % 2 == 0 then " + "local name = '{' .. KEYS[1] .. '}:' .. v; " + From 29c30a307e18f86b1bcbc75da80b94ebf618e8e9 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:59:57 +0300 Subject: [PATCH 39/47] Delete method fixed --- src/main/java/org/redisson/RedissonBloomFilter.java | 2 +- src/main/java/org/redisson/RedissonMapCache.java | 2 +- src/main/java/org/redisson/RedissonSetCache.java | 2 +- .../java/org/redisson/client/protocol/RedisCommands.java | 1 + .../org/redisson/reactive/RedissonMapCacheReactive.java | 2 +- .../org/redisson/reactive/RedissonSetCacheReactive.java | 2 +- src/test/java/org/redisson/RedissonSetCacheTest.java | 9 +++++++++ 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/redisson/RedissonBloomFilter.java b/src/main/java/org/redisson/RedissonBloomFilter.java index 6b6913039..06fb7b6db 100644 --- a/src/main/java/org/redisson/RedissonBloomFilter.java +++ b/src/main/java/org/redisson/RedissonBloomFilter.java @@ -200,7 +200,7 @@ public class RedissonBloomFilter extends RedissonExpirable implements RBloomF @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getConfigName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getConfigName()); } private void readConfig() { diff --git a/src/main/java/org/redisson/RedissonMapCache.java b/src/main/java/org/redisson/RedissonMapCache.java index 8900fd119..d529c2b03 100644 --- a/src/main/java/org/redisson/RedissonMapCache.java +++ b/src/main/java/org/redisson/RedissonMapCache.java @@ -655,7 +655,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 8a9083c75..97f8526a7 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -474,7 +474,7 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/client/protocol/RedisCommands.java b/src/main/java/org/redisson/client/protocol/RedisCommands.java index 7d62ca62e..9528685ce 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -208,6 +208,7 @@ public interface RedisCommands { RedisStrictCommand DEL = new RedisStrictCommand("DEL"); RedisStrictCommand DBSIZE = new RedisStrictCommand("DBSIZE"); RedisStrictCommand DEL_BOOL = new RedisStrictCommand("DEL", new BooleanReplayConvertor()); + RedisStrictCommand DEL_OBJECTS = new RedisStrictCommand("DEL", new BooleanAmountReplayConvertor()); RedisStrictCommand DEL_VOID = new RedisStrictCommand("DEL", new VoidReplayConvertor()); RedisCommand GET = new RedisCommand("GET"); diff --git a/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java b/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java index 183f9e460..85acad239 100644 --- a/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java @@ -361,7 +361,7 @@ public class RedissonMapCacheReactive extends RedissonMapReactive im @Override public Publisher delete() { - return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java index 3bb427930..bce238a28 100644 --- a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java @@ -305,7 +305,7 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple @Override public Publisher delete() { - return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index a864425fd..9d7c71188 100644 --- a/src/test/java/org/redisson/RedissonSetCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheTest.java @@ -31,6 +31,15 @@ public class RedissonSetCacheTest extends BaseTest { } } + + @Test + public void testDelete() { + RSetCache set = redisson.getSetCache("set"); + assertThat(set.delete()).isFalse(); + set.add(1, 1, TimeUnit.SECONDS); + assertThat(set.delete()).isTrue(); + assertThat(set.delete()).isFalse(); + } @Test public void testEmptyReadAll() { From 928898dc2d8418a6b3d53437c71daa73035301e3 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:00:06 +0300 Subject: [PATCH 40/47] RedissonKeysTest refactoring --- src/test/java/org/redisson/RedissonKeysTest.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/redisson/RedissonKeysTest.java b/src/test/java/org/redisson/RedissonKeysTest.java index 26f22b751..4380d26df 100644 --- a/src/test/java/org/redisson/RedissonKeysTest.java +++ b/src/test/java/org/redisson/RedissonKeysTest.java @@ -1,14 +1,12 @@ package org.redisson; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RBucket; @@ -26,7 +24,7 @@ public class RedissonKeysTest extends BaseTest { Iterator iterator = redisson.getKeys().getKeysByPattern("test?").iterator(); for (; iterator.hasNext();) { String key = iterator.next(); - MatcherAssert.assertThat(key, Matchers.isOneOf("test1", "test2")); + assertThat(key).isIn("test1", "test2"); } } @@ -57,7 +55,7 @@ public class RedissonKeysTest extends BaseTest { RBucket bucket2 = redisson.getBucket("test2"); bucket2.set("someValue2"); - MatcherAssert.assertThat(redisson.getKeys().randomKey(), Matchers.isOneOf("test1", "test2")); + assertThat(redisson.getKeys().randomKey()).isIn("test1", "test2"); redisson.getKeys().delete("test1"); Assert.assertEquals(redisson.getKeys().randomKey(), "test2"); redisson.flushdb(); @@ -95,10 +93,10 @@ public class RedissonKeysTest extends BaseTest { map.fastPut("1", "2"); Collection keys = redisson.getKeys().findKeysByPattern("test?"); - MatcherAssert.assertThat(keys, Matchers.containsInAnyOrder("test1", "test2")); + assertThat(keys).containsOnly("test1", "test2"); Collection keys2 = redisson.getKeys().findKeysByPattern("test"); - MatcherAssert.assertThat(keys2, Matchers.empty()); + assertThat(keys2).isEmpty(); } @Test From 36487763c040f4581a3c3e4596ecf8732e9d57df Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:49:14 +0300 Subject: [PATCH 41/47] Delete and expire MultimapCache methods fixed. #447 --- .../redisson/RedissonListMultimapCache.java | 42 ++++--- .../org/redisson/RedissonMultimapCache.java | 114 ++++++++++++++++++ .../redisson/RedissonSetMultimapCache.java | 42 ++++--- .../RedissonSetMultimapCacheTest.java | 57 ++++++++- 4 files changed, 215 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonMultimapCache.java diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java index 62a4800ec..2dfce5dd0 100644 --- a/src/main/java/org/redisson/RedissonListMultimapCache.java +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -22,10 +22,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; -import org.redisson.client.protocol.RedisCommand; -import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommands; -import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RListMultimapCache; @@ -39,16 +36,18 @@ import io.netty.util.concurrent.Future; */ public class RedissonListMultimapCache extends RedissonListMultimap implements RListMultimapCache { - private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + private final RedissonMultimapCache baseCache; RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { @@ -207,20 +206,27 @@ public class RedissonListMultimapCache extends RedissonListMultimap @Override public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { - long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); - - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_EXPIRE_KEY, - "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " - + "if tonumber(ARGV[1]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + - "else " + - "redis.call('zrem', KEYS[2], ARGV[2]); " - + "end; " - + "return 1; " - + "else " - + "return 0; " - + "end", - Arrays.asList(getName(), getTimeoutSetName()), ttlTimeout, key); + return baseCache.expireKeyAsync(key, timeToLive, timeUnit); + } + + @Override + public Future deleteAsync() { + return baseCache.deleteAsync(); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return baseCache.expireAsync(timeToLive, timeUnit); + } + + @Override + public Future expireAtAsync(long timestamp) { + return baseCache.expireAtAsync(timestamp); + } + + @Override + public Future clearExpireAsync() { + return baseCache.clearExpireAsync(); } } diff --git a/src/main/java/org/redisson/RedissonMultimapCache.java b/src/main/java/org/redisson/RedissonMultimapCache.java new file mode 100644 index 000000000..a33ce15f5 --- /dev/null +++ b/src/main/java/org/redisson/RedissonMultimapCache.java @@ -0,0 +1,114 @@ +package org.redisson; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.LongCodec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.command.CommandAsyncExecutor; + +import io.netty.util.concurrent.Future; + +public class RedissonMultimapCache { + + private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + + private final CommandAsyncExecutor commandExecutor; + private final String name; + private final Codec codec; + private final String timeoutSetName; + + public RedissonMultimapCache(CommandAsyncExecutor commandExecutor, String name, Codec codec, String timeoutSetName) { + this.commandExecutor = commandExecutor; + this.name = name; + this.codec = codec; + this.timeoutSetName = timeoutSetName; + } + + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); + + return commandExecutor.evalWriteAsync(name, codec, EVAL_EXPIRE_KEY, + "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " + + "if tonumber(ARGV[1]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "end; " + + "return 1; " + + "else " + + "return 0; " + + "end", + Arrays.asList(name, timeoutSetName), ttlTimeout, key); + } + + public Future deleteAsync() { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {KEYS[1], KEYS[2]}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end;" + + "end; " + + + "local n = 0 " + + "for i=1, #keys,5000 do " + + "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + + "end; " + + "return n;", + Arrays.asList(name, timeoutSetName)); + } + + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag'); " + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpire', name, ARGV[1]); " + + "end;" + + "end; " + + "redis.call('pexpire', KEYS[2], ARGV[1]); " + + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", + Arrays.asList(name, timeoutSetName), timeUnit.toMillis(timeToLive)); + } + + public Future expireAtAsync(long timestamp) { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpireat', name, ARGV[1]); " + + "end;" + + "end; " + + "redis.call('pexpireat', KEYS[2], ARGV[1]); " + + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", + Arrays.asList(name, timeoutSetName), timestamp); + } + + public Future clearExpireAsync() { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('persist', name); " + + "end;" + + "end; " + + "redis.call('persist', KEYS[2]); " + + "return redis.call('persist', KEYS[1]); ", + Arrays.asList(name, timeoutSetName)); + } + + +} diff --git a/src/main/java/org/redisson/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java index 9b7dee226..ea4b4192a 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapCache.java +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -22,10 +22,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; -import org.redisson.client.protocol.RedisCommand; -import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommands; -import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RSetMultimapCache; @@ -39,16 +36,18 @@ import io.netty.util.concurrent.Future; */ public class RedissonSetMultimapCache extends RedissonSetMultimap implements RSetMultimapCache { - private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + private final RedissonMultimapCache baseCache; RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } RedissonSetMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { @@ -199,20 +198,27 @@ public class RedissonSetMultimapCache extends RedissonSetMultimap im @Override public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { - long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); - - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_EXPIRE_KEY, - "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " - + "if tonumber(ARGV[1]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + - "else " + - "redis.call('zrem', KEYS[2], ARGV[2]); " - + "end; " - + "return 1; " - + "else " - + "return 0; " - + "end", - Arrays.asList(getName(), getTimeoutSetName()), ttlTimeout, key); + return baseCache.expireKeyAsync(key, timeToLive, timeUnit); + } + + @Override + public Future deleteAsync() { + return baseCache.deleteAsync(); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return baseCache.expireAsync(timeToLive, timeUnit); + } + + @Override + public Future expireAtAsync(long timestamp) { + return baseCache.expireAtAsync(timestamp); + } + + @Override + public Future clearExpireAsync() { + return baseCache.clearExpireAsync(); } } diff --git a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java index 7fe68f557..442855b8c 100644 --- a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java @@ -1,15 +1,13 @@ package org.redisson; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; import java.util.concurrent.TimeUnit; -import org.junit.Assert; import org.junit.Test; -import org.redisson.codec.MsgPackJacksonCodec; import org.redisson.core.RMultimapCache; -import org.redisson.core.RSetCache; +import org.redisson.core.RSetMultimap; public class RedissonSetMultimapCacheTest extends BaseTest { @@ -137,5 +135,56 @@ public class RedissonSetMultimapCacheTest extends BaseTest { } + @Test + public void testExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expire(100, TimeUnit.MILLISECONDS); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testExpireAt() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testClearExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + map.clearExpire(); + + Thread.sleep(500); + + assertThat(map.size()).isEqualTo(2); + } + + @Test + public void testDelete() { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + assertThat(map.delete()).isTrue(); + + RSetMultimap map2 = redisson.getSetMultimapCache("simple1"); + assertThat(map2.delete()).isFalse(); + } } From f66db14ff7ab4a427f6e3f51957339af2defa557 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:49:41 +0300 Subject: [PATCH 42/47] license added --- .../java/org/redisson/RedissonMultimapCache.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/redisson/RedissonMultimapCache.java b/src/main/java/org/redisson/RedissonMultimapCache.java index a33ce15f5..765ab8021 100644 --- a/src/main/java/org/redisson/RedissonMultimapCache.java +++ b/src/main/java/org/redisson/RedissonMultimapCache.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.redisson; import java.util.Arrays; From cdd50516ac3fe64dad4c64a45729e13e85af70f0 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:50:55 +0300 Subject: [PATCH 43/47] compilation fixed --- src/main/java/org/redisson/RedissonListMultimapCache.java | 4 ++-- src/main/java/org/redisson/RedissonSetMultimapCache.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java index 2dfce5dd0..2ba95e527 100644 --- a/src/main/java/org/redisson/RedissonListMultimapCache.java +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -41,13 +41,13 @@ public class RedissonListMultimapCache extends RedissonListMultimap RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { diff --git a/src/main/java/org/redisson/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java index ea4b4192a..4254d3ed6 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapCache.java +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -41,13 +41,13 @@ public class RedissonSetMultimapCache extends RedissonSetMultimap im RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } RedissonSetMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { From 75ba40227df09878a709f5c814fdc2d6931e3e26 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:03:56 +0300 Subject: [PATCH 44/47] MOVED, ASK handling in cluster mode using RBatch. #448 --- .../java/org/redisson/client/handler/CommandDecoder.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index ac868aafd..d8ce3d733 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -139,7 +139,11 @@ public class CommandDecoder extends ReplayingDecoder { cmd.getPromise().tryFailure(e); } if (!cmd.getPromise().isSuccess()) { - error = (RedisException) cmd.getPromise().cause(); + if (!(cmd.getPromise().cause() instanceof RedisMovedException + || cmd.getPromise().cause() instanceof RedisAskException + || cmd.getPromise().cause() instanceof RedisLoadingException)) { + error = (RedisException) cmd.getPromise().cause(); + } } } From fb68ca68113a4b9a91036058ff6a0d31d0e1e583 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:04:04 +0300 Subject: [PATCH 45/47] test fixes --- src/test/java/org/redisson/BaseTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/redisson/BaseTest.java b/src/test/java/org/redisson/BaseTest.java index e1190e384..48c549c92 100644 --- a/src/test/java/org/redisson/BaseTest.java +++ b/src/test/java/org/redisson/BaseTest.java @@ -1,10 +1,8 @@ package org.redisson; -import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; -import org.redisson.client.codec.StringCodec; -import org.redisson.codec.MsgPackJacksonCodec; public abstract class BaseTest { @@ -43,9 +41,9 @@ public abstract class BaseTest { return Redisson.create(config); } - @After - public void after() { - redisson.flushdb(); + @Before + public void before() { + redisson.getKeys().flushall(); } } From fa4bb81023ba2ec6c7bfce8ea3de8a01076ed631 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:45:16 +0300 Subject: [PATCH 46/47] [maven-release-plugin] prepare release redisson-2.2.10 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8907f379a..f553c4252 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.10-SNAPSHOT + 2.2.10 bundle Redisson @@ -15,7 +15,7 @@ scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git - HEAD + redisson-2.2.10 From e909110798d0e0cd69d17a72803c6f692879ce8b Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:45:23 +0300 Subject: [PATCH 47/47] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f553c4252..106c3a909 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.10 + 2.2.11-SNAPSHOT bundle Redisson @@ -15,7 +15,7 @@ scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git - redisson-2.2.10 + HEAD