diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c6dad00aa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +# 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. + +sudo: false +dist: trusty + +language: java +jdk: + - oraclejdk8 +env: + matrix: + - REDIS_VERSION=3.0.7 + - REDIS_VERSION=2.8.24 + - REDIS_VERSION=3.2.0-rc3 +cache: + directories: + - $HOME/.m2 +install: + - export REDIS_BIN=$HOME/redis/${REDIS_VERSION}/bin + - wget -c https://github.com/antirez/redis/archive/${REDIS_VERSION}.tar.gz -O redis-${REDIS_VERSION}.tar.gz + - tar -xvf redis-${REDIS_VERSION}.tar.gz + - make -C redis-${REDIS_VERSION} PREFIX=$HOME/redis/${REDIS_VERSION} install +before_script: + - $REDIS_BIN/redis-server --daemonize yes + - sleep 3 + - $REDIS_BIN/redis-cli PING + - export REDIS_VERSION="$(redis-cli INFO SERVER | sed -n 2p)" + - echo $REDIS_VERSION +script: mvn -DargLine="-DredisBinary=$REDIS_BIN/redis-server -DtravisEnv=true" -Punit-test -Ptravis clean verify diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9295791..54c412b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,36 @@ Redisson Releases History ================================ ####Please Note: trunk is current development branch. +####04-Apr-2016 - version 2.2.11 released + +Since this version Redisson has __perfomance boost up to 43%__ + +Feature - __new object added__ `RGeo` +Feature - __new object added__ `RBuckets` +Feature - travis-ci integration (thanks to jackygurui) +Improvement - `RScoredSortedSet.removeAllAsync` & `removeAll` methods optimization +Improvement - `RemoteService` reliability tuned up +Improvement - Reattaching RBlockingQueue\Deque blocking commands (poll, take ...) after Redis failover process or channel reconnection +Fixed - iterator objects may skip results in some cases +Fixed - RTopic listeners hangs during synchronous commands execution inside it +Fixed - Redisson hangs during shutdown if `RBlockingQueue\Deque.take` or `RBlockingQueue\Deque.poll` methods were invoked + + +####23-Mar-2016 - version 2.2.10 released + +Feature - __new object added__ `RRemoteService` +Feature - __new object added__ `RSetMultimapCache` +Feature - __new object added__ `RListMultimapCache` +Improvement - ability to cancel BRPOP and BLPOP async command execution +Improvement - Config params validation +Improvement - test RedisRunner improvements (thanks to jackygurui) +Improvement - `Double.NEGATIVE_INFINITY` and `Double.POSITIVE_INFINITY` handling for ScoredSortedSet (thanks to jackygurui) +Fixed - MOVED, ASK handling in cluster mode using RBatch +Fixed - delete and expire logic for Multimap objects +Fixed - `RLock.tryLockAsync` NPE +Fixed - possible NPE during Redisson version logging +Fixed - Netty threads shutdown after connection error + ####04-Mar-2016 - version 2.2.9 released Feature - __new object added__ `RSetMultimap` diff --git a/README.md b/README.md index 3bf37cb73..9c21b0127 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Features * [Spring cache](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html) integration * Supports [Reactive Streams](http://www.reactive-streams.org) * Supports [Redis pipelining](http://redis.io/topics/pipelining) (command batches) +* Supports [Remote services](https://github.com/mrniko/redisson/wiki/5.-distributed-objects#513-remote-service) * Supports Android platform * Supports auto-reconnect * Supports failed to send command auto-retry @@ -81,12 +82,12 @@ Include the following to your dependency list: org.redisson redisson - 2.2.9 + 2.2.11 ### Gradle - compile 'org.redisson:redisson:2.2.9' + compile 'org.redisson:redisson:2.2.11' ### Supported by diff --git a/pom.xml b/pom.xml index 8907f379a..c8e527aae 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.10-SNAPSHOT + 2.2.12-SNAPSHOT bundle Redisson @@ -100,33 +100,33 @@ io.netty netty-transport-native-epoll - 4.0.34.Final + 4.0.36.Final provided io.netty netty-common - 4.0.34.Final + 4.0.36.Final io.netty netty-codec - 4.0.34.Final + 4.0.36.Final io.netty netty-buffer - 4.0.34.Final + 4.0.36.Final io.netty netty-transport - 4.0.34.Final + 4.0.36.Final io.netty netty-handler - 4.0.34.Final + 4.0.36.Final @@ -145,6 +145,7 @@ com.jayway.awaitility awaitility 1.7.0 + test junit @@ -260,7 +261,7 @@ org.apache.maven.plugins maven-eclipse-plugin - 2.9 + 2.10 true true @@ -309,7 +310,7 @@ maven-compiler-plugin - 3.3 + 3.5.1 ${source.version} ${source.version} @@ -335,7 +336,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.0 attach-sources @@ -350,7 +351,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.16 + 2.19.1 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 f02e14c0d..fd9e63201 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -47,25 +47,30 @@ import org.redisson.core.RBlockingDeque; import org.redisson.core.RBlockingQueue; import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; +import org.redisson.core.RBuckets; import org.redisson.core.RCountDownLatch; import org.redisson.core.RDeque; +import org.redisson.core.RGeo; import org.redisson.core.RHyperLogLog; 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; 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; 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; @@ -90,15 +95,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!"); @@ -107,7 +118,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 @@ -155,6 +182,16 @@ public class Redisson implements RedissonClient { public static RedissonReactiveClient createReactive(Config config) { return new RedissonReactive(config); } + + @Override + public RGeo getGeo(String name) { + return new RedissonGeo(commandExecutor, name); + } + + @Override + public RGeo getGeo(String name, Codec codec) { + return new RedissonGeo(codec, commandExecutor, name); + } @Override public RBucket getBucket(String name) { @@ -166,6 +203,16 @@ public class Redisson implements RedissonClient { return new RedissonBucket(codec, commandExecutor, name); } + @Override + public RBuckets getBuckets() { + return new RedissonBuckets(this, commandExecutor); + } + + @Override + public RBuckets getBuckets(Codec codec) { + return new RedissonBuckets(this, codec, commandExecutor); + } + @Override public List> findBuckets(String pattern) { Collection keys = commandExecutor.get(commandExecutor., String>readAllAsync(RedisCommands.KEYS, pattern)); @@ -179,10 +226,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(); @@ -203,6 +252,7 @@ public class Redisson implements RedissonClient { return result; } + @Override public void saveBuckets(Map buckets) { if (buckets.isEmpty()) { return; @@ -221,11 +271,6 @@ public class Redisson implements RedissonClient { commandExecutor.write(params.get(0).toString(), RedisCommands.MSET, params.toArray()); } - @Override - public List> getBuckets(String pattern) { - return findBuckets(pattern); - } - @Override public RHyperLogLog getHyperLogLog(String name) { return new RedissonHyperLogLog(commandExecutor, name); @@ -265,6 +310,26 @@ public class Redisson implements RedissonClient { public RSetMultimap getSetMultimap(String name) { 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) { @@ -321,6 +386,10 @@ public class Redisson implements RedissonClient { return new RedissonScript(commandExecutor); } + public RRemoteService getRemoteSerivce() { + return new RedissonRemoteService(this); + } + @Override public RSortedSet getSortedSet(String name) { return new RedissonSortedSet(commandExecutor, name); @@ -461,10 +530,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!"); @@ -472,16 +543,6 @@ public class Redisson implements RedissonClient { return new RedisNodes(connectionManager); } - @Override - public void flushdb() { - commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHDB)); - } - - @Override - public void flushall() { - commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHALL)); - } - @Override public boolean isShutdown() { return connectionManager.isShutdown(); diff --git a/src/main/java/org/redisson/RedissonBaseIterator.java b/src/main/java/org/redisson/RedissonBaseIterator.java new file mode 100644 index 000000000..ab1eaa044 --- /dev/null +++ b/src/main/java/org/redisson/RedissonBaseIterator.java @@ -0,0 +1,96 @@ +/** + * 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.net.InetSocketAddress; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.redisson.client.protocol.decoder.ListScanResult; + +abstract class RedissonBaseIterator implements Iterator { + + private List firstValues; + private Iterator iter; + private InetSocketAddress client; + private long nextIterPos; + private long startPos = -1; + + 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; + do { + prevIterPos = nextIterPos; + ListScanResult res = iterator(client, nextIterPos); + client = res.getRedisClient(); + if (startPos == -1) { + startPos = res.getPos(); + } + if (nextIterPos == 0 && firstValues == null) { + firstValues = res.getValues(); + } else if (res.getValues().equals(firstValues) && res.getPos() == startPos) { + return false; + } + iter = res.getValues().iterator(); + nextIterPos = res.getPos(); + } while (!iter.hasNext() && nextIterPos != prevIterPos); + if (prevIterPos == nextIterPos && !removeExecuted) { + nextIterPos = -1; + } + } + return iter.hasNext(); + } + + abstract ListScanResult iterator(InetSocketAddress client, long nextIterPos); + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException("No such element"); + } + + 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(); + remove(value); + currentElementRemoved = true; + removeExecuted = true; + } + + abstract void remove(V value); + +} diff --git a/src/main/java/org/redisson/RedissonBaseMapIterator.java b/src/main/java/org/redisson/RedissonBaseMapIterator.java index 9c2b9c073..d493efb6d 100644 --- a/src/main/java/org/redisson/RedissonBaseMapIterator.java +++ b/src/main/java/org/redisson/RedissonBaseMapIterator.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; +import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.client.protocol.decoder.MapScanResult; import org.redisson.client.protocol.decoder.ScanObjectEntry; @@ -32,10 +33,12 @@ abstract class RedissonBaseMapIterator implements Iterator { private Map firstValues; private Iterator> iter; - protected long iterPos = 0; + protected long nextIterPos; + protected long startPos = -1; protected InetSocketAddress client; private boolean finished; + private boolean currentElementRemoved; private boolean removeExecuted; protected Map.Entry entry; @@ -44,26 +47,41 @@ abstract class RedissonBaseMapIterator implements Iterator { if (finished) { return false; } + if (iter == null || !iter.hasNext()) { - MapScanResult res = iterator(); - client = res.getRedisClient(); - if (iterPos == 0 && firstValues == null) { - firstValues = convert(res.getMap()); - } else { - Map newValues = convert(res.getMap()); - if (newValues.equals(firstValues)) { - finished = true; - free(firstValues); + if (nextIterPos == -1) { + return false; + } + long prevIterPos; + do { + prevIterPos = nextIterPos; + MapScanResult res = iterator(); + client = res.getRedisClient(); + if (startPos == -1) { + startPos = res.getPos(); + } + if (nextIterPos == 0 && firstValues == null) { + firstValues = convert(res.getMap()); + } else { + Map newValues = convert(res.getMap()); + if (firstValues.entrySet().containsAll(newValues.entrySet())) { + finished = true; + free(firstValues); + free(newValues); + firstValues = null; + return false; + } free(newValues); - firstValues = null; - return false; } - free(newValues); + iter = res.getMap().entrySet().iterator(); + nextIterPos = res.getPos(); + } while (!iter.hasNext() && nextIterPos != prevIterPos); + if (prevIterPos == nextIterPos && !removeExecuted) { + nextIterPos = -1; } - iter = res.getMap().entrySet().iterator(); - iterPos = res.getPos(); } return iter.hasNext(); + } protected abstract MapScanResult iterator(); @@ -90,7 +108,7 @@ abstract class RedissonBaseMapIterator implements Iterator { } entry = iter.next(); - removeExecuted = false; + currentElementRemoved = false; return getValue(entry); } @@ -108,14 +126,16 @@ abstract class RedissonBaseMapIterator implements Iterator { @Override public void remove() { - if (removeExecuted) { + if (currentElementRemoved) { throw new IllegalStateException("Element been already deleted"); } + if (iter == null) { + throw new IllegalStateException(); + } - // lazy init iterator - hasNext(); iter.remove(); removeKey(); + currentElementRemoved = true; removeExecuted = true; } 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/RedissonBuckets.java b/src/main/java/org/redisson/RedissonBuckets.java new file mode 100644 index 000000000..e9958461c --- /dev/null +++ b/src/main/java/org/redisson/RedissonBuckets.java @@ -0,0 +1,118 @@ +/** + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.DelegateDecoderCodec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.command.CommandExecutor; +import org.redisson.connection.decoder.MapGetAllDecoder; +import org.redisson.core.RBucket; +import org.redisson.core.RBuckets; + +import io.netty.util.concurrent.Future; + +public class RedissonBuckets implements RBuckets { + + private final Codec codec; + private final CommandExecutor commandExecutor; + private final Redisson redisson; + + public RedissonBuckets(Redisson redisson, CommandExecutor commandExecutor) { + this(redisson, commandExecutor.getConnectionManager().getCodec(), commandExecutor); + } + + public RedissonBuckets(Redisson redisson, Codec codec, CommandExecutor commandExecutor) { + super(); + this.codec = codec; + this.commandExecutor = commandExecutor; + this.redisson = redisson; + } + + @Override + public List> find(String pattern) { + Collection keys = commandExecutor.get(commandExecutor., String>readAllAsync(RedisCommands.KEYS, pattern)); + List> buckets = new ArrayList>(keys.size()); + for (String key : keys) { + if(key == null) { + continue; + } + buckets.add(redisson.getBucket(key, codec)); + } + return buckets; + } + + @Override + public Map get(String... keys) { + if (keys.length == 0) { + return Collections.emptyMap(); + } + + RedisCommand> command = new RedisCommand>("MGET", new MapGetAllDecoder(Arrays.asList(keys), 0), ValueType.OBJECTS); + Future> future = commandExecutor.readAsync(keys[0], new DelegateDecoderCodec(codec), command, keys); + return commandExecutor.get(future); + } + + @Override + public boolean trySet(Map buckets) { + if (buckets.isEmpty()) { + return false; + } + + List params = new ArrayList(buckets.size()); + for (Entry entry : buckets.entrySet()) { + params.add(entry.getKey()); + try { + params.add(codec.getValueEncoder().encode(entry.getValue())); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + return commandExecutor.write(params.get(0).toString(), RedisCommands.MSETNX, params.toArray()); + } + + @Override + public void set(Map buckets) { + if (buckets.isEmpty()) { + return; + } + + List params = new ArrayList(buckets.size()); + for (Entry entry : buckets.entrySet()) { + params.add(entry.getKey()); + try { + params.add(codec.getValueEncoder().encode(entry.getValue())); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + commandExecutor.write(params.get(0).toString(), RedisCommands.MSET, params.toArray()); + } + +} diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index 66fe5b49f..fffc0e54b 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -31,25 +31,30 @@ 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.RBuckets; import org.redisson.core.RCountDownLatch; import org.redisson.core.RDeque; +import org.redisson.core.RGeo; import org.redisson.core.RHyperLogLog; 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; 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; 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; @@ -62,6 +67,24 @@ import org.redisson.core.RTopic; */ public interface RedissonClient { + /** + * Returns geospatial items holder instance by name. + * + * @param name + * @return + */ + RGeo getGeo(String name); + + /** + * Returns geospatial items holder instance by name + * using provided codec for geospatial members. + * + * @param name + * @param geospatial member codec + * @return + */ + RGeo getGeo(String name, Codec codec); + /** * Returns set-based cache instance by name. * Supports value eviction with a given TTL value. @@ -129,57 +152,43 @@ public interface RedissonClient { RBucket getBucket(String name, Codec codec); /** - *

Returns a list of object holder instances by a key pattern. - * - *

Supported glob-style patterns:
-     *    h?llo subscribes to hello, hallo and hxllo
-     *    h*llo subscribes to hllo and heeeello
-     *    h[ae]llo subscribes to hello and hallo, but not hillo
-     *    h[^e]llo matches hallo, hbllo, ... but not hello
-     *    h[a-b]llo matches hallo and hbllo
- *

Use \ to escape special characters if you want to match them verbatim. + * Returns interface for mass operations with Bucket objects. * - *

Uses KEYS Redis command. - * - * @param pattern * @return */ - List> findBuckets(String pattern); + RBuckets getBuckets(); /** - *

Returns Redis object mapped by key. Result Map is not contains - * key-value entry for null values. - * - *

Uses MGET Redis command. + * Returns interface for mass operations with Bucket objects + * using provided codec for object. * - * @param keys * @return */ - Map loadBucketValues(Collection keys); + RBuckets getBuckets(Codec codec); /** - *

Returns Redis object mapped by key. Result Map is not contains - * key-value entry for null values. - * - *

Uses MGET Redis command. - * - * @param keys - * @return + * Use {@link RBuckets#find(String)} */ - Map loadBucketValues(String ... keys); + @Deprecated + List> findBuckets(String pattern); /** - * Saves Redis object mapped by key. - * - * @param buckets + * Use {@link RBuckets#get(String...)} */ - void saveBuckets(Map buckets); + @Deprecated + Map loadBucketValues(Collection keys); + + /** + * Use {@link RBuckets#get(String...)} + */ + @Deprecated + Map loadBucketValues(String ... keys); /** - * Use {@link #findBuckets(String)} + * Use {@link RBuckets#set(Map)} */ @Deprecated - List> getBuckets(String pattern); + void saveBuckets(Map buckets); /** * Returns HyperLogLog instance by name. @@ -218,7 +227,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 +235,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 @@ -235,6 +244,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. * @@ -254,15 +286,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 +303,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 * @@ -537,6 +592,13 @@ public interface RedissonClient { */ RScript getScript(); + /** + * Returns object for remote operations + * + * @return + */ + RRemoteService getRemoteSerivce(); + /** * Return batch object which executes group of * command in pipeline. @@ -583,18 +645,6 @@ public interface RedissonClient { */ NodesGroup getClusterNodesGroup(); - /** - * Use {@link RKeys#flushdb()} - */ - @Deprecated - void flushdb(); - - /** - * Use {@link RKeys#flushall()} - */ - @Deprecated - void flushall(); - /** * Returns {@code true} if this Redisson instance has been shut down. * diff --git a/src/main/java/org/redisson/RedissonCountDownLatch.java b/src/main/java/org/redisson/RedissonCountDownLatch.java index 8f3eeef1f..5a4d660a6 100644 --- a/src/main/java/org/redisson/RedissonCountDownLatch.java +++ b/src/main/java/org/redisson/RedissonCountDownLatch.java @@ -53,7 +53,7 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown public void await() throws InterruptedException { Future promise = subscribe(); try { - promise.await(); + get(promise); while (getCount() > 0) { // waiting for open state @@ -71,7 +71,7 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown public boolean await(long time, TimeUnit unit) throws InterruptedException { Future promise = subscribe(); try { - if (!promise.await(time, unit)) { + if (!await(promise, time, unit)) { return false; } diff --git a/src/main/java/org/redisson/RedissonGeo.java b/src/main/java/org/redisson/RedissonGeo.java new file mode 100644 index 000000000..527892d25 --- /dev/null +++ b/src/main/java/org/redisson/RedissonGeo.java @@ -0,0 +1,197 @@ +/** + * 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.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.GeoEntryCodec; +import org.redisson.client.codec.ScoredCodec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.decoder.GeoDistanceDecoder; +import org.redisson.client.protocol.decoder.GeoMapReplayDecoder; +import org.redisson.client.protocol.decoder.GeoPositionDecoder; +import org.redisson.client.protocol.decoder.GeoPositionMapDecoder; +import org.redisson.client.protocol.decoder.MultiDecoder; +import org.redisson.client.protocol.decoder.NestedMultiDecoder; +import org.redisson.client.protocol.decoder.FlatNestedMultiDecoder; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.connection.decoder.MapGetAllDecoder; +import org.redisson.core.GeoEntry; +import org.redisson.core.GeoPosition; +import org.redisson.core.GeoUnit; +import org.redisson.core.RGeo; + +import io.netty.util.concurrent.Future; + +public class RedissonGeo extends RedissonExpirable implements RGeo { + + MultiDecoder> postitionDecoder; + MultiDecoder> distanceDecoder; + + public RedissonGeo(CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + postitionDecoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + distanceDecoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + } + + public RedissonGeo(Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + postitionDecoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + distanceDecoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + } + + @Override + public Future addAsync(double longitude, double latitude, V member) { + return commandExecutor.writeAsync(getName(), RedisCommands.GEOADD, getName(), convert(longitude), convert(latitude), member); + } + + private String convert(double longitude) { + return BigDecimal.valueOf(longitude).toPlainString(); + } + + @Override + public long add(double longitude, double latitude, V member) { + return get(addAsync(longitude, latitude, member)); + } + + @Override + public long add(GeoEntry... entries) { + return get(addAsync(entries)); + } + + @Override + public Future addAsync(GeoEntry... entries) { + List params = new ArrayList(entries.length + 1); + params.add(getName()); + for (GeoEntry entry : entries) { + params.add(entry.getLongitude()); + params.add(entry.getLatitude()); + params.add(entry.getMember()); + } + return commandExecutor.writeAsync(getName(), new GeoEntryCodec(codec), RedisCommands.GEOADD_ENTRIES, params.toArray()); + } + + @Override + public Double dist(V firstMember, V secondMember, GeoUnit geoUnit) { + return get(distAsync(firstMember, secondMember, geoUnit)); + } + + @Override + public Future distAsync(V firstMember, V secondMember, GeoUnit geoUnit) { + return commandExecutor.readAsync(getName(), new ScoredCodec(codec), RedisCommands.GEODIST, getName(), firstMember, secondMember, geoUnit); + } + + @Override + public Map hash(V... members) { + return get(hashAsync(members)); + } + + @Override + public Future> hashAsync(V... members) { + List params = new ArrayList(members.length + 1); + params.add(getName()); + params.addAll(Arrays.asList(members)); + RedisCommand> command = new RedisCommand>("GEOHASH", new MapGetAllDecoder(params, 1), 2, ValueType.OBJECTS); + return commandExecutor.readAsync(getName(), new ScoredCodec(codec), command, params.toArray()); + } + + @Override + public Map pos(V... members) { + return get(posAsync(members)); + } + + @Override + public Future> posAsync(V... members) { + List params = new ArrayList(members.length + 1); + params.add(getName()); + params.addAll(Arrays.asList(members)); + + MultiDecoder> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoPositionMapDecoder(params), true); + RedisCommand> command = new RedisCommand>("GEOPOS", decoder, 2, ValueType.OBJECTS); + return commandExecutor.readAsync(getName(), new ScoredCodec(codec), command, params.toArray()); + } + + @Override + public List radius(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return get(radiusAsync(longitude, latitude, radius, geoUnit)); + } + + @Override + public Future> radiusAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return commandExecutor.readAsync(getName(), codec, RedisCommands.GEORADIUS, getName(), convert(longitude), convert(latitude), radius, geoUnit); + } + + @Override + public Map radiusWithDistance(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return get(radiusWithDistanceAsync(longitude, latitude, radius, geoUnit)); + } + + @Override + public Future> radiusWithDistanceAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { + RedisCommand> command = new RedisCommand>("GEORADIUS", distanceDecoder); + return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHDIST"); + } + + @Override + public Map radiusWithPosition(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return get(radiusWithPositionAsync(longitude, latitude, radius, geoUnit)); + } + + @Override + public Future> radiusWithPositionAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { + RedisCommand> command = new RedisCommand>("GEORADIUS", postitionDecoder); + return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHCOORD"); + } + + @Override + public List radius(V member, double radius, GeoUnit geoUnit) { + return get(radiusAsync(member, radius, geoUnit)); + } + + @Override + public Future> radiusAsync(V member, double radius, GeoUnit geoUnit) { + return commandExecutor.readAsync(getName(), codec, RedisCommands.GEORADIUSBYMEMBER, getName(), member, radius, geoUnit); + } + + @Override + public Map radiusWithDistance(V member, double radius, GeoUnit geoUnit) { + return get(radiusWithDistanceAsync(member, radius, geoUnit)); + } + + @Override + public Future> radiusWithDistanceAsync(V member, double radius, GeoUnit geoUnit) { + RedisCommand command = new RedisCommand("GEORADIUSBYMEMBER", distanceDecoder, 2); + return commandExecutor.readAsync(getName(), codec, command, getName(), member, radius, geoUnit, "WITHDIST"); + } + + @Override + public Map radiusWithPosition(V member, double radius, GeoUnit geoUnit) { + return get(radiusWithPositionAsync(member, radius, geoUnit)); + } + + @Override + public Future> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit) { + RedisCommand> command = new RedisCommand>("GEORADIUSBYMEMBER", postitionDecoder, 2); + return commandExecutor.readAsync(getName(), codec, command, getName(), member, radius, geoUnit, "WITHCOORD"); + } +} diff --git a/src/main/java/org/redisson/RedissonKeys.java b/src/main/java/org/redisson/RedissonKeys.java index e553bb169..afab001b7 100644 --- a/src/main/java/org/redisson/RedissonKeys.java +++ b/src/main/java/org/redisson/RedissonKeys.java @@ -15,6 +15,7 @@ */ package org.redisson; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -23,7 +24,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -90,55 +90,18 @@ public class RedissonKeys implements RKeys { } private Iterator createKeysIterator(final int slot, final String pattern) { - return new Iterator() { - - private List firstValues; - private Iterator iter; - private long iterPos; - - private boolean removeExecuted; - private String value; + return new RedissonBaseIterator() { @Override - public boolean hasNext() { - if (iter == null || !iter.hasNext()) { - ListScanResult res = scanIterator(slot, iterPos, pattern); - if (iterPos == 0 && firstValues == null) { - firstValues = res.getValues(); - } else if (res.getValues().equals(firstValues)) { - return false; - } - iter = res.getValues().iterator(); - iterPos = res.getPos(); - } - return iter.hasNext(); + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return RedissonKeys.this.scanIterator(slot, nextIterPos, pattern); } @Override - public String next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element"); - } - - value = iter.next(); - removeExecuted = false; - return value; + void remove(String value) { + RedissonKeys.this.delete(value); } - - @Override - public void remove() { - if (removeExecuted) { - throw new IllegalStateException("Element been already deleted"); - } - if (iter == null) { - throw new IllegalStateException(); - } - - iter.remove(); - delete(value); - removeExecuted = true; - } - + }; } diff --git a/src/main/java/org/redisson/RedissonLexSortedSet.java b/src/main/java/org/redisson/RedissonLexSortedSet.java index 2616d064e..35eb8c17a 100644 --- a/src/main/java/org/redisson/RedissonLexSortedSet.java +++ b/src/main/java/org/redisson/RedissonLexSortedSet.java @@ -32,33 +32,64 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem super(StringCodec.INSTANCE, commandExecutor, name); } + @Override + public int removeRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { + return removeRangeByLex(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public int removeRangeByLex(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { - return get(removeRangeByLexAsync(fromElement, fromInclusive, toElement, toInclusive)); + return get(removeRangeAsync(fromElement, fromInclusive, toElement, toInclusive)); } + @Override + public int removeRangeHead(String toElement, boolean toInclusive) { + return removeRangeHeadByLex(toElement, toInclusive); + } + @Override public int removeRangeHeadByLex(String toElement, boolean toInclusive) { - return get(removeRangeHeadByLexAsync(toElement, toInclusive)); + return get(removeRangeHeadAsync(toElement, toInclusive)); } + @Override + public Future removeRangeHeadAsync(String toElement, boolean toInclusive) { + return removeRangeHeadByLexAsync(toElement, toInclusive); + } + @Override public Future removeRangeHeadByLexAsync(String toElement, boolean toInclusive) { String toValue = value(toElement, toInclusive); return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), "-", toValue); } + + @Override + public int removeRangeTail(String fromElement, boolean fromInclusive) { + return removeRangeTailByLex(fromElement, fromInclusive); + } @Override public int removeRangeTailByLex(String fromElement, boolean fromInclusive) { - return get(removeRangeTailByLexAsync(fromElement, fromInclusive)); + return get(removeRangeTailAsync(fromElement, fromInclusive)); } + @Override + public Future removeRangeTailAsync(String fromElement, boolean fromInclusive) { + return removeRangeTailByLexAsync(fromElement, fromInclusive); + } + @Override public Future removeRangeTailByLexAsync(String fromElement, boolean fromInclusive) { String fromValue = value(fromElement, fromInclusive); return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, "+"); } + @Override + public Future removeRangeAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive) { + return removeRangeByLexAsync(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public Future removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { String fromValue = value(fromElement, fromInclusive); @@ -67,33 +98,63 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, toValue); } + @Override + public Collection range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { + return lexRange(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public Collection lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { - return get(lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive)); + return get(rangeAsync(fromElement, fromInclusive, toElement, toInclusive)); } + @Override + public Collection rangeHead(String toElement, boolean toInclusive) { + return lexRangeHead(toElement, toInclusive); + } + @Override public Collection lexRangeHead(String toElement, boolean toInclusive) { - return get(lexRangeHeadAsync(toElement, toInclusive)); + return get(rangeHeadAsync(toElement, toInclusive)); } + @Override + public Future> rangeHeadAsync(String toElement, boolean toInclusive) { + return lexRangeHeadAsync(toElement, toInclusive); + } + @Override public Future> lexRangeHeadAsync(String toElement, boolean toInclusive) { String toValue = value(toElement, toInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue); } + + @Override + public Collection rangeTail(String fromElement, boolean fromInclusive) { + return lexRangeTail(fromElement, fromInclusive); + } @Override public Collection lexRangeTail(String fromElement, boolean fromInclusive) { - return get(lexRangeTailAsync(fromElement, fromInclusive)); + return get(rangeTailAsync(fromElement, fromInclusive)); } + @Override + public Future> rangeTailAsync(String fromElement, boolean fromInclusive) { + return lexRangeTailAsync(fromElement, fromInclusive); + } + @Override public Future> lexRangeTailAsync(String fromElement, boolean fromInclusive) { String fromValue = value(fromElement, fromInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+"); } + @Override + public Future> rangeAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive) { + return lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive); + } @Override public Future> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { @@ -103,33 +164,65 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, toValue); } + @Override + public Collection range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, + int offset, int count) { + return lexRange(fromElement, fromInclusive, toElement, toInclusive, offset, count); + } + @Override public Collection lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) { - return get(lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive, offset, count)); + return get(rangeAsync(fromElement, fromInclusive, toElement, toInclusive, offset, count)); } + @Override + public Collection rangeHead(String toElement, boolean toInclusive, int offset, int count) { + return lexRangeHead(toElement, toInclusive, offset, count); + } + @Override public Collection lexRangeHead(String toElement, boolean toInclusive, int offset, int count) { - return get(lexRangeHeadAsync(toElement, toInclusive, offset, count)); + return get(rangeHeadAsync(toElement, toInclusive, offset, count)); } + @Override + public Future> rangeHeadAsync(String toElement, boolean toInclusive, int offset, int count) { + return lexRangeHeadAsync(toElement, toInclusive, offset, count); + } + @Override public Future> lexRangeHeadAsync(String toElement, boolean toInclusive, int offset, int count) { String toValue = value(toElement, toInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue, "LIMIT", offset, count); } + @Override + public Collection rangeTail(String fromElement, boolean fromInclusive, int offset, int count) { + return lexRangeTail(fromElement, fromInclusive, offset, count); + } + @Override public Collection lexRangeTail(String fromElement, boolean fromInclusive, int offset, int count) { - return get(lexRangeTailAsync(fromElement, fromInclusive, offset, count)); + return get(rangeTailAsync(fromElement, fromInclusive, offset, count)); } + @Override + public Future> rangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count) { + return lexRangeTailAsync(fromElement, fromInclusive, offset, count); + } + @Override public Future> lexRangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count) { String fromValue = value(fromElement, fromInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+", "LIMIT", offset, count); } + @Override + public Future> rangeAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive, int offset, int count) { + return lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive, offset, count); + } + @Override public Future> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) { String fromValue = value(fromElement, fromInclusive); @@ -137,10 +230,20 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, toValue, "LIMIT", offset, count); } + + @Override + public int countTail(String fromElement, boolean fromInclusive) { + return lexCountTail(fromElement, fromInclusive); + } @Override public int lexCountTail(String fromElement, boolean fromInclusive) { - return get(lexCountTailAsync(fromElement, fromInclusive)); + return get(countTailAsync(fromElement, fromInclusive)); + } + + @Override + public Future countTailAsync(String fromElement, boolean fromInclusive) { + return lexCountTailAsync(fromElement, fromInclusive); } @Override @@ -149,10 +252,20 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), fromValue, "+"); } + + @Override + public int countHead(String toElement, boolean toInclusive) { + return lexCountHead(toElement, toInclusive); + } @Override public int lexCountHead(String toElement, boolean toInclusive) { - return get(lexCountHeadAsync(toElement, toInclusive)); + return get(countHeadAsync(toElement, toInclusive)); + } + + @Override + public Future countHeadAsync(String toElement, boolean toInclusive) { + return lexCountHeadAsync(toElement, toInclusive); } @Override @@ -162,11 +275,22 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), "-", toValue); } + @Override + public int count(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { + return lexCount(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public int lexCount(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { - return get(lexCountAsync(fromElement, fromInclusive, toElement, toInclusive)); + return get(countAsync(fromElement, fromInclusive, toElement, toInclusive)); } + @Override + public Future countAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive) { + return lexCountAsync(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public Future lexCountAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { String fromValue = value(fromElement, fromInclusive); @@ -192,6 +316,9 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem @Override public Future addAllAsync(Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(false); + } List params = new ArrayList(2*c.size()); for (Object param : c) { params.add(0); @@ -210,4 +337,14 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return get(addAllAsync(c)); } + @Override + public Collection range(int startIndex, int endIndex) { + return valueRange(startIndex, endIndex); + } + + @Override + public Future> rangeAsync(int startIndex, int endIndex) { + return valueRangeAsync(startIndex, endIndex); + } + } diff --git a/src/main/java/org/redisson/RedissonList.java b/src/main/java/org/redisson/RedissonList.java index 734ec97ad..8b2d33039 100644 --- a/src/main/java/org/redisson/RedissonList.java +++ b/src/main/java/org/redisson/RedissonList.java @@ -143,13 +143,13 @@ public class RedissonList extends RedissonExpirable implements RList { return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local items = redis.call('lrange', KEYS[1], 0, -1) " + "for i=1, #items do " + - "for j = 0, table.getn(ARGV), 1 do " + + "for j = 1, #ARGV, 1 do " + "if items[i] == ARGV[j] then " + "table.remove(ARGV, j) " + "end " + "end " + "end " + - "return table.getn(ARGV) == 0 and 1 or 0", + "return #ARGV == 0 and 1 or 0", Collections.singletonList(getName()), c.toArray()); } @@ -222,7 +222,7 @@ public class RedissonList extends RedissonExpirable implements RList { public Future removeAllAsync(Collection c) { return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local v = 0 " + - "for i = 0, table.getn(ARGV), 1 do " + "for i = 1, #ARGV, 1 do " + "if redis.call('lrem', KEYS[1], 0, ARGV[i]) == 1 " + "then v = 1 end " +"end " @@ -246,11 +246,10 @@ public class RedissonList extends RedissonExpirable implements RList { "local changed = 0 " + "local items = redis.call('lrange', KEYS[1], 0, -1) " + "local i = 1 " - + "local s = table.getn(items) " - + "while i <= s do " + + "while i <= #items do " + "local element = items[i] " + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " @@ -390,7 +389,7 @@ public class RedissonList extends RedissonExpirable implements RList { "local key = KEYS[1] " + "local obj = ARGV[1] " + "local items = redis.call('lrange', key, 0, -1) " + - "for i = #items, 0, -1 do " + + "for i = #items, 1, -1 do " + "if items[i] == obj then " + "return i - 1 " + "end " + 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/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java new file mode 100644 index 000000000..2ba95e527 --- /dev/null +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -0,0 +1,232 @@ +/** + * 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.RedisCommands; +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 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) { + 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) { + 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/RedissonListMultimapValues.java b/src/main/java/org/redisson/RedissonListMultimapValues.java new file mode 100644 index 000000000..abffc6e61 --- /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 = 1, #items, 1 do " + + "for j = 2, #ARGV, 1 do " + + "if ARGV[j] == items[i] " + + "then table.remove(ARGV, j) end " + + "end; " + + "end;" + + "return #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 = 1; " + + "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, 1, -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/RedissonLock.java b/src/main/java/org/redisson/RedissonLock.java index 3bc14bf9b..eaf060496 100644 --- a/src/main/java/org/redisson/RedissonLock.java +++ b/src/main/java/org/redisson/RedissonLock.java @@ -110,7 +110,7 @@ public class RedissonLock extends RedissonExpirable implements RLock { } Future future = subscribe(); - future.sync(); + get(future); try { while (true) { @@ -229,7 +229,7 @@ public class RedissonLock extends RedissonExpirable implements RLock { } Future future = subscribe(); - if (!future.await(time, TimeUnit.MILLISECONDS)) { + if (!await(future, time, TimeUnit.MILLISECONDS)) { future.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { diff --git a/src/main/java/org/redisson/RedissonMap.java b/src/main/java/org/redisson/RedissonMap.java index 245e6a7cd..e72d4b074 100644 --- a/src/main/java/org/redisson/RedissonMap.java +++ b/src/main/java/org/redisson/RedissonMap.java @@ -107,7 +107,7 @@ public class RedissonMap extends RedissonExpirable implements RMap { public Future containsValueAsync(Object value) { return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4), "local s = redis.call('hvals', KEYS[1]);" + - "for i = 0, table.getn(s), 1 do " + "for i = 1, #s, 1 do " + "if ARGV[1] == s[i] then " + "return 1 " + "end " @@ -130,7 +130,7 @@ public class RedissonMap extends RedissonExpirable implements RMap { List args = new ArrayList(keys.size() + 1); args.add(getName()); args.addAll(keys); - return commandExecutor.readAsync(getName(), codec, new RedisCommand>("HMGET", new MapGetAllDecoder(args), 2, ValueType.MAP_KEY, ValueType.MAP_VALUE), args.toArray()); + return commandExecutor.readAsync(getName(), codec, new RedisCommand>("HMGET", new MapGetAllDecoder(args, 1), 2, ValueType.MAP_KEY, ValueType.MAP_VALUE), args.toArray()); } @Override diff --git a/src/main/java/org/redisson/RedissonMapCache.java b/src/main/java/org/redisson/RedissonMapCache.java index 8900fd119..0167015c8 100644 --- a/src/main/java/org/redisson/RedissonMapCache.java +++ b/src/main/java/org/redisson/RedissonMapCache.java @@ -162,11 +162,11 @@ public class RedissonMapCache extends RedissonMap implements RMapCac return newSucceededFuture(Collections.emptyMap()); } - List args = new ArrayList(keys.size() + 2); + List args = new ArrayList(keys.size() + 1); args.add(System.currentTimeMillis()); args.addAll(keys); - return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), + return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + "local expireIdleHead = redis.call('zrange', KEYS[3], 0, 0, 'withscores');" + "local maxDate = table.remove(ARGV, 1); " // index is the first parameter @@ -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/RedissonMapIterator.java b/src/main/java/org/redisson/RedissonMapIterator.java index 4290e58e4..a16cfa3f5 100644 --- a/src/main/java/org/redisson/RedissonMapIterator.java +++ b/src/main/java/org/redisson/RedissonMapIterator.java @@ -29,7 +29,7 @@ public class RedissonMapIterator extends RedissonBaseMapIterator iterator() { - return map.scanIterator(client, iterPos); + return map.scanIterator(client, nextIterPos); } protected void removeKey() { diff --git a/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java b/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java index 03918ee60..375349e95 100644 --- a/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java +++ b/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java @@ -29,7 +29,7 @@ public class RedissonMultiMapKeysIterator extends RedissonBaseMapIterat } protected MapScanResult iterator() { - return map.scanIterator(client, iterPos); + return map.scanIterator(client, nextIterPos); } protected void removeKey() { diff --git a/src/main/java/org/redisson/RedissonMultimap.java b/src/main/java/org/redisson/RedissonMultimap.java index 475660b82..a072520a5 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,69 @@ 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]); " + + "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]); " + + "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]); " + + "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/main/java/org/redisson/RedissonMultimapCache.java b/src/main/java/org/redisson/RedissonMultimapCache.java new file mode 100644 index 000000000..765ab8021 --- /dev/null +++ b/src/main/java/org/redisson/RedissonMultimapCache.java @@ -0,0 +1,129 @@ +/** + * 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; +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/RedissonObject.java b/src/main/java/org/redisson/RedissonObject.java index a49ffa2d1..6cc3d6000 100644 --- a/src/main/java/org/redisson/RedissonObject.java +++ b/src/main/java/org/redisson/RedissonObject.java @@ -15,6 +15,8 @@ */ package org.redisson; +import java.util.concurrent.TimeUnit; + import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandAsyncExecutor; @@ -45,16 +47,20 @@ abstract class RedissonObject implements RObject { this(commandExecutor.getConnectionManager().getCodec(), commandExecutor, name); } + protected boolean await(Future future, long timeout, TimeUnit timeoutUnit) throws InterruptedException { + return commandExecutor.await(future, timeout, timeoutUnit); + } + protected V get(Future future) { return commandExecutor.get(future); } protected Promise newPromise() { - return commandExecutor.getConnectionManager().getGroup().next().newPromise(); + return commandExecutor.getConnectionManager().newPromise(); } protected Future newSucceededFuture(V result) { - return commandExecutor.getConnectionManager().newSucceededFuture(result); + return commandExecutor.getConnectionManager().newSucceededFuture(result); } @Override diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java new file mode 100644 index 000000000..5e2c813df --- /dev/null +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -0,0 +1,212 @@ +/** + * 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.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.redisson.core.RBatch; +import org.redisson.core.RBlockingQueue; +import org.redisson.core.RBlockingQueueAsync; +import org.redisson.core.RRemoteService; +import org.redisson.remote.RRemoteServiceResponse; +import org.redisson.remote.RemoteServiceAck; +import org.redisson.remote.RemoteServiceAckTimeoutException; +import org.redisson.remote.RemoteServiceKey; +import org.redisson.remote.RemoteServiceMethod; +import org.redisson.remote.RemoteServiceRequest; +import org.redisson.remote.RemoteServiceResponse; +import org.redisson.remote.RemoteServiceTimeoutException; +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; + +/** + * + * @author Nikita Koksharov + * + */ +public class RedissonRemoteService implements RRemoteService { + + private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); + + private final Map beans = PlatformDependent.newConcurrentHashMap(); + + private final Redisson redisson; + + public RedissonRemoteService(Redisson redisson) { + this.redisson = redisson; + } + + @Override + 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(remoteInterface, method.getName()); + if (beans.put(key, value) != null) { + return; + } + } + + 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 remoteInterface, final RBlockingQueue requestQueue) { + Future take = requestQueue.takeAsync(); + take.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + if (future.cause() instanceof RedissonShutdownException) { + return; + } + subscribe(remoteInterface, requestQueue); + return; + } + subscribe(remoteInterface, requestQueue); + + final RemoteServiceRequest request = future.getNow(); + if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) { + log.debug("request: {} has been skipped due to ackTimeout"); + return; + } + + final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); + final String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId(); + + Future> ackClientsFuture = send(request.getAckTimeout(), responseName, new RemoteServiceAck()); + ackClientsFuture.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't send ack for request: " + request, future.cause()); + return; + } + + invokeMethod(request, method, responseName); + } + }); + } + + }); + } + + private void invokeMethod(final RemoteServiceRequest request, RemoteServiceMethod method, String responseName) { + final AtomicReference responseHolder = new AtomicReference(); + try { + Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); + RemoteServiceResponse response = new RemoteServiceResponse(result); + responseHolder.set(response); + } catch (Exception e) { + RemoteServiceResponse response = new RemoteServiceResponse(e.getCause()); + responseHolder.set(response); + log.error("Can't execute: " + request, e); + } + + Future> clientsFuture = send(request.getResponseTimeout(), responseName, responseHolder.get()); + clientsFuture.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't send response: " + responseHolder.get() + " for request: " + request, future.cause()); + return; + } + } + }); + } + + @Override + public T get(Class remoteInterface) { + return get(remoteInterface, 30, TimeUnit.SECONDS); + } + + @Override + public T get(final Class remoteInterface, final long executionTimeout, final TimeUnit executionTimeUnit) { + return get(remoteInterface, executionTimeout, executionTimeUnit, 1, TimeUnit.SECONDS); + } + + public T get(final Class remoteInterface, final long executionTimeout, final TimeUnit executionTimeUnit, + final long ackTimeout, final TimeUnit ackTimeUnit) { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String requestId = generateRequestId(); + + String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args, + ackTimeUnit.toMillis(ackTimeout), executionTimeUnit.toMillis(executionTimeout), System.currentTimeMillis()); + requestQueue.add(request); + + String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; + RBlockingQueue responseQueue = redisson.getBlockingQueue(responseName); + + RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(ackTimeout, ackTimeUnit); + if (ack == null) { + throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request); + } + + RemoteServiceResponse response = (RemoteServiceResponse) responseQueue.poll(executionTimeout, executionTimeUnit); + if (response == null) { + throw new RemoteServiceTimeoutException("No response after " + executionTimeUnit.toMillis(executionTimeout) + "ms for request: " + request); + } + if (response.getError() != null) { + throw response.getError(); + } + return response.getResult(); + } + }; + return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler); + } + + private String generateRequestId() { + byte[] id = new byte[16]; + // TODO JDK UPGRADE replace to native ThreadLocalRandom + ThreadLocalRandom.current().nextBytes(id); + return ByteBufUtil.hexDump(id); + } + + private Future> send(long timeout, String responseName, T response) { + RBatch batch = redisson.createBatch(); + RBlockingQueueAsync queue = batch.getBlockingQueue(responseName); + queue.putAsync(response); + queue.expireAsync(timeout, TimeUnit.MILLISECONDS); + return batch.executeAsync(); + } + +} diff --git a/src/main/java/org/redisson/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index e4755f679..01875ae2b 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -25,16 +25,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.NoSuchElementException; import org.redisson.client.codec.Codec; import org.redisson.client.codec.ScoredCodec; import org.redisson.client.codec.StringCodec; -import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.ScoredEntry; -import org.redisson.client.protocol.RedisCommand.ValueType; -import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RScoredSortedSet; @@ -124,6 +120,9 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @Override public Future addAllAsync(Map objects) { + if (objects.isEmpty()) { + return newSucceededFuture(0L); + } List params = new ArrayList(objects.size()*2+1); params.add(getName()); try { @@ -245,54 +244,18 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @Override public Iterator iterator() { - return new Iterator() { - - private List firstValues; - private Iterator iter; - private InetSocketAddress client; - private long iterPos; - - private boolean removeExecuted; - private V value; - - @Override - public boolean hasNext() { - if (iter == null || !iter.hasNext()) { - ListScanResult res = scanIterator(client, iterPos); - client = res.getRedisClient(); - if (iterPos == 0 && firstValues == null) { - firstValues = res.getValues(); - } else if (res.getValues().equals(firstValues)) { - return false; - } - iter = res.getValues().iterator(); - iterPos = res.getPos(); - } - return iter.hasNext(); - } + return new RedissonBaseIterator() { @Override - public V next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element at index"); - } - - value = iter.next(); - removeExecuted = false; - return value; + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return scanIterator(client, nextIterPos); } @Override - public void remove() { - if (removeExecuted) { - throw new IllegalStateException("Element been already deleted"); - } - - iter.remove(); + void remove(V value) { RedissonScoredSortedSet.this.remove(value); - removeExecuted = true; } - + }; } @@ -315,27 +278,26 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @Override public Future containsAllAsync(Collection c) { - return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local s = redis.call('zrange', KEYS[1], 0, -1);" + - "for i = 0, table.getn(s), 1 do " + - "for j = 0, table.getn(ARGV), 1 do " + "for i = 1, #s, 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == s[i] " + "then table.remove(ARGV, j) end " + "end; " + "end;" - + "return table.getn(ARGV) == 0 and 1 or 0; ", + + "return #ARGV == 0 and 1 or 0; ", Collections.singletonList(getName()), c.toArray()); } @Override public Future removeAllAsync(Collection c) { - return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), - "local v = 0 " + - "for i = 0, table.getn(ARGV), 1 do " - + "if redis.call('zrem', KEYS[1], ARGV[i]) == 1 " - + "then v = 1 end " - +"end " - + "return v ", + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, + "local v = 0;" + + "for i=1, #ARGV, 5000 do " + + "v = v + redis.call('zrem', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); " + + "end " + + "return v > 0;", Collections.singletonList(getName()), c.toArray()); } @@ -351,14 +313,14 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @Override public Future retainAllAsync(Collection c) { - return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local changed = 0 " + "local s = redis.call('zrange', KEYS[1], 0, -1) " - + "local i = 0 " - + "while i <= table.getn(s) do " + + "local i = 1 " + + "while i <= #s do " + "local element = s[i] " + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " diff --git a/src/main/java/org/redisson/RedissonSemaphore.java b/src/main/java/org/redisson/RedissonSemaphore.java index e29a4e0a0..a23cd966b 100644 --- a/src/main/java/org/redisson/RedissonSemaphore.java +++ b/src/main/java/org/redisson/RedissonSemaphore.java @@ -70,7 +70,8 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore { return; } - Future future = subscribe().sync(); + Future future = subscribe(); + get(future); try { while (true) { if (tryAcquire(permits)) { @@ -113,7 +114,7 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore { long time = unit.toMillis(waitTime); Future future = subscribe(); - if (!future.await(time, TimeUnit.MILLISECONDS)) { + if (!await(future, time, TimeUnit.MILLISECONDS)) { return false; } diff --git a/src/main/java/org/redisson/RedissonSet.java b/src/main/java/org/redisson/RedissonSet.java index 9904bcc30..d2e728195 100644 --- a/src/main/java/org/redisson/RedissonSet.java +++ b/src/main/java/org/redisson/RedissonSet.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.Set; import org.redisson.client.codec.Codec; @@ -82,66 +81,18 @@ public class RedissonSet extends RedissonExpirable implements RSet { @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(); - } + return new RedissonBaseIterator() { @Override - public V next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element at index"); - } - - value = iter.next(); - currentElementRemoved = false; - return value; + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return scanIterator(client, nextIterPos); } @Override - public void remove() { - if (currentElementRemoved) { - throw new IllegalStateException("Element been already deleted"); - } - if (iter == null) { - throw new IllegalStateException(); - } - - iter.remove(); + void remove(V value) { RedissonSet.this.remove(value); - currentElementRemoved = true; - removeExecuted = true; } - + }; } @@ -216,13 +167,13 @@ public class RedissonSet extends RedissonExpirable implements RSet { public Future containsAllAsync(Collection c) { return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local s = redis.call('smembers', KEYS[1]);" + - "for i = 0, table.getn(s), 1 do " + - "for j = 0, table.getn(ARGV), 1 do " + "for i = 1, #s, 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == s[i] " + "then table.remove(ARGV, j) end " + "end; " + "end;" - + "return table.getn(ARGV) == 0 and 1 or 0; ", + + "return #ARGV == 0 and 1 or 0; ", Collections.singletonList(getName()), c.toArray()); } @@ -253,11 +204,11 @@ public class RedissonSet extends RedissonExpirable implements RSet { return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local changed = 0 " + "local s = redis.call('smembers', KEYS[1]) " - + "local i = 0 " - + "while i <= table.getn(s) do " + + "local i = 1 " + + "while i <= #s do " + "local element = s[i] " + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " @@ -277,7 +228,7 @@ public class RedissonSet extends RedissonExpirable implements RSet { public Future removeAllAsync(Collection c) { return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local v = 0 " + - "for i = 0, table.getn(ARGV), 1 do " + "for i = 1, #ARGV, 1 do " + "if redis.call('srem', KEYS[1], ARGV[i]) == 1 " + "then v = 1 end " +"end " diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 42726bafb..a4c1e13b6 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -17,14 +17,12 @@ package org.redisson; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.ByteBuffer; 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.NoSuchElementException; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -39,6 +37,8 @@ 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.util.concurrent.Future; import net.openhft.hashing.LongHashFunction; @@ -114,10 +114,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() { @@ -158,66 +160,18 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @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(); - } + return new RedissonBaseIterator() { @Override - public V next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element at index"); - } - - value = iter.next(); - currentElementRemoved = false; - return value; + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return scanIterator(client, nextIterPos); } @Override - public void remove() { - if (currentElementRemoved) { - throw new IllegalStateException("Element been already deleted"); - } - if (iter == null) { - throw new IllegalStateException(); - } - - iter.remove(); + void remove(V value) { RedissonSetCache.this.remove(value); - currentElementRemoved = true; - removeExecuted = true; } - + }; } @@ -314,13 +268,20 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< long timeoutDate = System.currentTimeMillis() + unit.toMillis(ttl); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, - "redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); " + - "if redis.call('hexists', KEYS[1], ARGV[3]) == 0 then " + - "redis.call('hset', KEYS[1], ARGV[3], ARGV[2]); " + - "return 1; " + - "end;" + - "return 0; ", - Arrays.asList(getName(), getTimeoutSetName()), timeoutDate, objectState, key); + "local value = redis.call('hexists', KEYS[1], ARGV[3]); " + + "if value == 1 then " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); " + + "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[2]); " + + "return 1;" + + "else " + + "return 0;" + + "end; " + + "end;" + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[3]); " + + "redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); " + + "return 1; ", + Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), timeoutDate, key, objectState); } catch (IOException e) { throw new RuntimeException(e); } @@ -336,12 +297,19 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< byte[] objectState = encode(value); byte[] key = hash(objectState); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, - "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " + - "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + - "return 1; " + - "end; " + - "return 0; ", - Arrays.asList(getName()), key, objectState); + "local value = redis.call('hexists', KEYS[1], ARGV[2]); " + + "if value == 1 then " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "return 1;" + + "else " + + "return 0;" + + "end; " + + "end;" + + "redis.call('hset', KEYS[1], ARGV[2], ARGV[3]); " + + "return 1; ", + Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), key, objectState); } catch (IOException e) { throw new RuntimeException(e); } @@ -367,14 +335,14 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< public Future containsAllAsync(Collection c) { return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, "local s = redis.call('hvals', KEYS[1]);" + - "for i = 0, table.getn(s), 1 do " + - "for j = 0, table.getn(ARGV), 1 do " + "for i = 1, #s, 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == s[i] then " + "table.remove(ARGV, j) " + "end " + "end; " + "end;" - + "return table.getn(ARGV) == 0 and 1 or 0; ", + + "return #ARGV == 0 and 1 or 0; ", Collections.singletonList(getName()), c.toArray()); } @@ -468,7 +436,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/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/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java new file mode 100644 index 000000000..4254d3ed6 --- /dev/null +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -0,0 +1,224 @@ +/** + * 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.RedisCommands; +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 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) { + 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) { + 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/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java new file mode 100644 index 000000000..c3546e05e --- /dev/null +++ b/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -0,0 +1,397 @@ +/** + * 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.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; + +/** + * Set based Multimap Cache values holder + * + * @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 RedissonBaseIterator() { + + @Override + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return scanIterator(client, nextIterPos); + } + + @Override + void remove(V value) { + RedissonSetMultimapValues.this.remove(value); + } + + }; + } + + @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 = 1, #s, 1 do " + + "for j = 2, #ARGV, 1 do " + + "if ARGV[j] == s[i] " + + "then table.remove(ARGV, j) end " + + "end; " + + "end;" + + "return #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 = 1 " + + "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('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, #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/RedissonShutdownException.java b/src/main/java/org/redisson/RedissonShutdownException.java new file mode 100644 index 000000000..ddfcb1aa1 --- /dev/null +++ b/src/main/java/org/redisson/RedissonShutdownException.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; + +public class RedissonShutdownException extends RuntimeException { + + private static final long serialVersionUID = -2694051226420789395L; + + public RedissonShutdownException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/redisson/RedissonSubList.java b/src/main/java/org/redisson/RedissonSubList.java index 3306d83c3..759200eef 100644 --- a/src/main/java/org/redisson/RedissonSubList.java +++ b/src/main/java/org/redisson/RedissonSubList.java @@ -104,13 +104,13 @@ public class RedissonSubList extends RedissonList implements RList { "local toIndex = table.remove(ARGV, 2);" + "local items = redis.call('lrange', KEYS[1], tonumber(fromIndex), tonumber(toIndex)) " + "for i=1, #items do " + - "for j = 0, #ARGV, 1 do " + + "for j = 1, #ARGV, 1 do " + "if items[i] == ARGV[j] then " + "table.remove(ARGV, j) " + "end " + "end " + "end " + - "return table.getn(ARGV) == 0 and 1 or 0", + "return #ARGV == 0 and 1 or 0", Collections.singletonList(getName()), params.toArray()); } @@ -203,11 +203,10 @@ public class RedissonSubList extends RedissonList implements RList { "local toIndex = table.remove(ARGV, 2);" + "local items = redis.call('lrange', KEYS[1], fromIndex, toIndex) " + "local i = 1 " - + "local s = table.getn(items) " - + "while i <= s do " + + "while i <= #items do " + "local element = items[i] " + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " diff --git a/src/main/java/org/redisson/client/RedisClient.java b/src/main/java/org/redisson/client/RedisClient.java index e5dd7ad5b..2aa645755 100644 --- a/src/main/java/org/redisson/client/RedisClient.java +++ b/src/main/java/org/redisson/client/RedisClient.java @@ -38,6 +38,7 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; public class RedisClient { @@ -95,7 +96,7 @@ public class RedisClient { } public Future connectAsync() { - final Promise f = bootstrap.group().next().newPromise(); + final Promise f = ImmediateEventExecutor.INSTANCE.newPromise(); ChannelFuture channelFuture = bootstrap.connect(); channelFuture.addListener(new ChannelFutureListener() { @Override @@ -122,7 +123,7 @@ public class RedisClient { } public Future connectPubSubAsync() { - final Promise f = bootstrap.group().next().newPromise(); + final Promise f = ImmediateEventExecutor.INSTANCE.newPromise(); ChannelFuture channelFuture = bootstrap.connect(); channelFuture.addListener(new ChannelFutureListener() { @Override diff --git a/src/main/java/org/redisson/client/RedisConnection.java b/src/main/java/org/redisson/client/RedisConnection.java index 85ff35041..fc98647fd 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -15,21 +15,24 @@ */ package org.redisson.client; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; +import org.redisson.client.handler.CommandsQueue; import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandsData; +import org.redisson.client.protocol.QueueCommand; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.RedisStrictCommand; -import org.redisson.connection.FastSuccessFuture; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.ScheduledFuture; @@ -45,7 +48,7 @@ public class RedisConnection implements RedisCommands { private ReconnectListener reconnectListener; private long lastUsageTime; - private final Future acquireFuture = new FastSuccessFuture(this); + private final Future acquireFuture = ImmediateEventExecutor.INSTANCE.newSucceededFuture(this); public RedisConnection(RedisClient redisClient, Channel channel) { super(); @@ -59,6 +62,18 @@ public class RedisConnection implements RedisCommands { return (C) channel.attr(RedisConnection.CONNECTION).get(); } + public void removeCurrentCommand() { + channel.attr(CommandsQueue.CURRENT_COMMAND).remove(); + } + + public CommandData getCurrentCommand() { + QueueCommand command = channel.attr(CommandsQueue.CURRENT_COMMAND).get(); + if (command instanceof CommandData) { + return (CommandData)command; + } + return null; + } + public long getLastUsageTime() { return lastUsageTime; } @@ -97,21 +112,34 @@ public class RedisConnection implements RedisCommands { return redisClient; } - public R await(Future cmd) { - // TODO change connectTimeout to timeout - if (!cmd.awaitUninterruptibly(redisClient.getTimeout(), TimeUnit.MILLISECONDS)) { - Promise promise = (Promise)cmd; - RedisTimeoutException ex = new RedisTimeoutException("Command execution timeout for " + redisClient.getAddr()); - promise.setFailure(ex); - throw ex; - } - if (!cmd.isSuccess()) { - if (cmd.cause() instanceof RedisException) { - throw (RedisException) cmd.cause(); + public R await(Future future) { + final CountDownLatch l = new CountDownLatch(1); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + l.countDown(); + } + }); + + try { + // TODO change connectTimeout to timeout + if (!l.await(redisClient.getTimeout(), TimeUnit.MILLISECONDS)) { + Promise promise = (Promise)future; + RedisTimeoutException ex = new RedisTimeoutException("Command execution timeout for " + redisClient.getAddr()); + promise.setFailure(ex); + throw ex; + } + if (!future.isSuccess()) { + if (future.cause() instanceof RedisException) { + throw (RedisException) future.cause(); + } + throw new RedisException("Unexpected exception while processing command", future.cause()); } - throw new RedisException("Unexpected exception while processing command", cmd.cause()); + return future.getNow(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; } - return cmd.getNow(); } public T sync(RedisStrictCommand command, Object ... params) { @@ -137,13 +165,13 @@ public class RedisConnection implements RedisCommands { } public Future async(Codec encoder, RedisCommand command, Object ... params) { - Promise promise = redisClient.getBootstrap().group().next().newPromise(); + Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); send(new CommandData(promise, encoder, command, params)); return promise; } public Future asyncWithTimeout(Codec encoder, RedisCommand command, Object ... params) { - final Promise promise = redisClient.getBootstrap().group().next().newPromise(); + final Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); final ScheduledFuture scheduledFuture = redisClient.getBootstrap().group().next().schedule(new Runnable() { @Override public void run() { @@ -162,7 +190,7 @@ public class RedisConnection implements RedisCommands { } public CommandData create(Codec encoder, RedisCommand command, Object ... params) { - Promise promise = redisClient.getBootstrap().group().next().newPromise(); + Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); return new CommandData(promise, encoder, command, params); } @@ -174,8 +202,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/client/codec/DelegateDecoderCodec.java b/src/main/java/org/redisson/client/codec/DelegateDecoderCodec.java new file mode 100644 index 000000000..407cc5b93 --- /dev/null +++ b/src/main/java/org/redisson/client/codec/DelegateDecoderCodec.java @@ -0,0 +1,34 @@ +/** + * 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.client.codec; + +import org.redisson.client.protocol.Decoder; + +public class DelegateDecoderCodec extends StringCodec { + + private final Codec delegate; + + public DelegateDecoderCodec(Codec delegate) { + super(); + this.delegate = delegate; + } + + @Override + public Decoder getValueDecoder() { + return delegate.getValueDecoder(); + } + +} diff --git a/src/main/java/org/redisson/client/codec/GeoEntryCodec.java b/src/main/java/org/redisson/client/codec/GeoEntryCodec.java new file mode 100644 index 000000000..71a985948 --- /dev/null +++ b/src/main/java/org/redisson/client/codec/GeoEntryCodec.java @@ -0,0 +1,45 @@ +/** + * 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.client.codec; + +import org.redisson.client.protocol.Encoder; + +public class GeoEntryCodec extends StringCodec { + + private final ThreadLocal pos = new ThreadLocal() { + protected Integer initialValue() { + return 0; + }; + }; + + private final Codec delegate; + + public GeoEntryCodec(Codec delegate) { + super(); + this.delegate = delegate; + } + + @Override + public Encoder getValueEncoder() { + Integer p = pos.get() + 1; + pos.set(p); + if (p % 3 == 0) { + return delegate.getValueEncoder(); + } + return super.getValueEncoder(); + } + +} diff --git a/src/main/java/org/redisson/client/codec/ScoredCodec.java b/src/main/java/org/redisson/client/codec/ScoredCodec.java index 12378bc2f..f4bf3656e 100644 --- a/src/main/java/org/redisson/client/codec/ScoredCodec.java +++ b/src/main/java/org/redisson/client/codec/ScoredCodec.java @@ -19,7 +19,7 @@ import org.redisson.client.protocol.Encoder; public class ScoredCodec extends StringCodec { - public final Codec delegate; + private final Codec delegate; public ScoredCodec(Codec delegate) { super(); diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index 4ef080b03..64113ad5d 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -68,11 +68,11 @@ public class CommandDecoder extends ReplayingDecoder { private static final char ZERO = '0'; // It is not needed to use concurrent map because responses are coming consecutive - private final Map> messageDecoders = new HashMap>(); - private final Map> channels = PlatformDependent.newConcurrentHashMap(); + private final Map> pubSubMessageDecoders = new HashMap>(); + private final Map> pubSubChannels = PlatformDependent.newConcurrentHashMap(); - public void addChannel(String channel, CommandData data) { - channels.put(channel, data); + public void addPubSubCommand(String channel, CommandData data) { + pubSubChannels.put(channel, data); } @Override @@ -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); @@ -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); // } @@ -137,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(); + } } } @@ -149,7 +155,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); } } @@ -168,7 +173,7 @@ public class CommandDecoder extends ReplayingDecoder { if (code == '+') { String result = in.readBytes(in.bytesBefore((byte) '\r')).toString(CharsetUtil.UTF_8); in.skipBytes(2); - + handleResult(data, parts, result, false, channel); } else if (code == '-') { String error = in.readBytes(in.bytesBefore((byte) '\r')).toString(CharsetUtil.UTF_8); @@ -201,9 +206,7 @@ public class CommandDecoder extends ReplayingDecoder { } } } else if (code == ':') { - String status = in.readBytes(in.bytesBefore((byte) '\r')).toString(CharsetUtil.UTF_8); - in.skipBytes(2); - Object result = Long.valueOf(status); + Long result = readLong(in); handleResult(data, parts, result, false, channel); } else if (code == '$') { ByteBuf buf = readBytes(in); @@ -214,21 +217,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 +259,6 @@ public class CommandDecoder extends ReplayingDecoder { } } else { handleMultiResult(data, parts, channel, result); -// if (parts != null && !decoder.isApplicable(parts.size(), state())) { -// state().setRespParts(parts); -// } } } @@ -263,14 +269,14 @@ public class CommandDecoder extends ReplayingDecoder { } else { if (result instanceof PubSubStatusMessage) { String channelName = ((PubSubStatusMessage) result).getChannel(); - CommandData d = channels.get(channelName); + CommandData d = pubSubChannels.get(channelName); if (Arrays.asList("PSUBSCRIBE", "SUBSCRIBE").contains(d.getCommand().getName())) { - channels.remove(channelName); - messageDecoders.put(channelName, d.getMessageDecoder()); + pubSubChannels.remove(channelName); + pubSubMessageDecoders.put(channelName, d.getMessageDecoder()); } if (Arrays.asList("PUNSUBSCRIBE", "UNSUBSCRIBE").contains(d.getCommand().getName())) { - channels.remove(channelName); - messageDecoders.remove(channelName); + pubSubChannels.remove(channelName); + pubSubMessageDecoders.remove(channelName); } } @@ -306,17 +312,17 @@ public class CommandDecoder extends ReplayingDecoder { if (data == null) { if (Arrays.asList("subscribe", "psubscribe", "punsubscribe", "unsubscribe").contains(parts.get(0))) { String channelName = (String) parts.get(1); - CommandData commandData = channels.get(channelName); + CommandData commandData = pubSubChannels.get(channelName); if (commandData == null) { return null; } return commandData.getCommand().getReplayMultiDecoder(); } else if (parts.get(0).equals("message")) { String channelName = (String) parts.get(1); - return messageDecoders.get(channelName); + return pubSubMessageDecoders.get(channelName); } else if (parts.get(0).equals("pmessage")) { String patternName = (String) parts.get(1); - return messageDecoders.get(patternName); + return pubSubMessageDecoders.get(patternName); } } @@ -327,11 +333,11 @@ public class CommandDecoder extends ReplayingDecoder { if (data == null) { if (parts.size() == 2 && parts.get(0).equals("message")) { String channelName = (String) parts.get(1); - return messageDecoders.get(channelName); + return pubSubMessageDecoders.get(channelName); } if (parts.size() == 3 && parts.get(0).equals("pmessage")) { String patternName = (String) parts.get(1); - return messageDecoders.get(patternName); + return pubSubMessageDecoders.get(patternName); } return currentDecoder; } diff --git a/src/main/java/org/redisson/client/handler/CommandEncoder.java b/src/main/java/org/redisson/client/handler/CommandEncoder.java index 8c7560908..2a8b0af2c 100644 --- a/src/main/java/org/redisson/client/handler/CommandEncoder.java +++ b/src/main/java/org/redisson/client/handler/CommandEncoder.java @@ -18,6 +18,7 @@ package org.redisson.client.handler; import java.util.List; import org.redisson.client.codec.ByteArrayCodec; +import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.Encoder; import org.redisson.client.protocol.DefaultParamsEncoder; @@ -112,8 +113,11 @@ public class CommandEncoder extends MessageToByteEncoder CURRENT_COMMAND = AttributeKey.valueOf("promise"); @@ -82,7 +82,7 @@ public class CommandsQueue extends ChannelDuplexHandler { if (!pubSubOps.isEmpty()) { for (CommandData cd : pubSubOps) { for (Object channel : cd.getParams()) { - ch.pipeline().get(CommandDecoder.class).addChannel(channel.toString(), cd); + ch.pipeline().get(CommandDecoder.class).addPubSubCommand(channel.toString(), cd); } } } else { diff --git a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java index 81a030ce6..54270890d 100644 --- a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java +++ b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java @@ -22,6 +22,7 @@ import org.redisson.client.RedisConnection; import org.redisson.client.RedisException; import org.redisson.client.RedisPubSubConnection; import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.CommandData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +36,7 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.group.ChannelGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { @@ -115,24 +117,22 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { if (connection.getReconnectListener() != null) { // new connection used only for channel init RedisConnection rc = new RedisConnection(connection.getRedisClient(), channel); - Promise connectionFuture = bootstrap.group().next().newPromise(); + Promise connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise(); connection.getReconnectListener().onReconnect(rc, connectionFuture); connectionFuture.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { if (future.isSuccess()) { - connection.updateChannel(channel); - resubscribe(connection); + refresh(connection, channel); } } }); } else { - connection.updateChannel(channel); - resubscribe(connection); + refresh(connection, channel); } } - private void resubscribe(RedisConnection connection) { + private void reattachPubSub(RedisConnection connection) { if (connection instanceof RedisPubSubConnection) { RedisPubSubConnection conn = (RedisPubSubConnection) connection; for (Entry entry : conn.getChannels().entrySet()) { @@ -149,4 +149,29 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { ctx.channel().close(); } + private void refresh(RedisConnection connection, Channel channel) { + CommandData commandData = connection.getCurrentCommand(); + connection.updateChannel(channel); + + reattachBlockingQueue(connection, commandData); + reattachPubSub(connection); + } + + private void reattachBlockingQueue(RedisConnection connection, final CommandData commandData) { + if (commandData == null + || !commandData.isBlockingCommand()) { + return; + } + + ChannelFuture future = connection.send(commandData); + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't reconnect blocking queue to new connection. {}", commandData); + } + } + }); + } + } 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; diff --git a/src/main/java/org/redisson/client/protocol/CommandData.java b/src/main/java/org/redisson/client/protocol/CommandData.java index 6cb93ad77..a47227c9e 100644 --- a/src/main/java/org/redisson/client/protocol/CommandData.java +++ b/src/main/java/org/redisson/client/protocol/CommandData.java @@ -77,5 +77,9 @@ public class CommandData implements QueueCommand { } return Collections.emptyList(); } + + public boolean isBlockingCommand() { + return QueueCommand.TIMEOUTLESS_COMMANDS.contains(command.getName()) && !promise.isDone(); + } } diff --git a/src/main/java/org/redisson/client/protocol/QueueCommand.java b/src/main/java/org/redisson/client/protocol/QueueCommand.java index 028810a34..33582c982 100644 --- a/src/main/java/org/redisson/client/protocol/QueueCommand.java +++ b/src/main/java/org/redisson/client/protocol/QueueCommand.java @@ -23,6 +23,9 @@ import java.util.Set; public interface QueueCommand { Set PUBSUB_COMMANDS = new HashSet(Arrays.asList("PSUBSCRIBE", "SUBSCRIBE", "PUNSUBSCRIBE", "UNSUBSCRIBE")); + + Set TIMEOUTLESS_COMMANDS = new HashSet(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(), + RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName())); List> getPubSubOperations(); diff --git a/src/main/java/org/redisson/client/protocol/RedisCommand.java b/src/main/java/org/redisson/client/protocol/RedisCommand.java index 5111dbbb6..2ff072329 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommand.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommand.java @@ -24,7 +24,7 @@ import org.redisson.client.protocol.decoder.MultiDecoder; public class RedisCommand { - public enum ValueType {OBJECT, OBJECTS, MAP_VALUE, MAP_KEY, MAP, BINARY} + public enum ValueType {OBJECT, OBJECTS, MAP_VALUE, MAP_KEY, MAP, BINARY, STRING} private ValueType outParamType = ValueType.OBJECT; private List inParamType = Arrays.asList(ValueType.OBJECT); diff --git a/src/main/java/org/redisson/client/protocol/RedisCommands.java b/src/main/java/org/redisson/client/protocol/RedisCommands.java index 7d62ca62e..e3737d259 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -15,10 +15,11 @@ */ package org.redisson.client.protocol; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.convertor.BitSetReplayConvertor; @@ -40,7 +41,7 @@ import org.redisson.client.protocol.decoder.ListScanResultReplayDecoder; import org.redisson.client.protocol.decoder.MapScanResult; import org.redisson.client.protocol.decoder.MapScanResultReplayDecoder; import org.redisson.client.protocol.decoder.NestedMultiDecoder; -import org.redisson.client.protocol.decoder.NestedMultiDecoder2; +import org.redisson.client.protocol.decoder.FlatNestedMultiDecoder; import org.redisson.client.protocol.decoder.ObjectFirstResultReplayDecoder; import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; import org.redisson.client.protocol.decoder.ObjectMapEntryReplayDecoder; @@ -57,6 +58,12 @@ import org.redisson.client.protocol.pubsub.PubSubStatusDecoder; public interface RedisCommands { + RedisStrictCommand GEOADD = new RedisStrictCommand("GEOADD", 4); + RedisStrictCommand GEOADD_ENTRIES = new RedisStrictCommand("GEOADD", 2, ValueType.OBJECTS); + RedisCommand GEODIST = new RedisCommand("GEODIST", new DoubleReplayConvertor(), 2, Arrays.asList(ValueType.OBJECT, ValueType.OBJECT, ValueType.STRING)); + RedisCommand> GEORADIUS = new RedisCommand>("GEORADIUS", new ObjectListReplayDecoder()); + RedisCommand> GEORADIUSBYMEMBER = new RedisCommand>("GEORADIUSBYMEMBER", new ObjectListReplayDecoder(), 2); + RedisStrictCommand KEYSLOT = new RedisStrictCommand("CLUSTER", "KEYSLOT", new IntegerReplayConvertor()); RedisStrictCommand GETBIT = new RedisStrictCommand("GETBIT", new BooleanReplayConvertor()); @@ -189,6 +196,7 @@ public interface RedisCommands { RedisStrictCommand> KEYS = new RedisStrictCommand>("KEYS", new StringListReplayDecoder()); RedisCommand> MGET = new RedisCommand>("MGET", new ObjectListReplayDecoder()); RedisStrictCommand MSET = new RedisStrictCommand("MSET", new VoidReplayConvertor()); + RedisStrictCommand MSETNX = new RedisStrictCommand("MSETNX", new BooleanReplayConvertor()); RedisCommand HSETNX = new RedisCommand("HSETNX", new BooleanReplayConvertor(), 2, ValueType.MAP); RedisCommand HSET = new RedisCommand("HSET", new BooleanReplayConvertor(), 2, ValueType.MAP); @@ -208,6 +216,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"); @@ -237,7 +246,7 @@ public interface RedisCommands { RedisStrictCommand> SENTINEL_GET_MASTER_ADDR_BY_NAME = new RedisStrictCommand>("SENTINEL", "GET-MASTER-ADDR-BY-NAME", new StringListReplayDecoder()); RedisCommand>> SENTINEL_SLAVES = new RedisCommand>>("SENTINEL", "SLAVES", - new NestedMultiDecoder2(new ObjectMapReplayDecoder(), new ListResultReplayDecoder()), ValueType.OBJECT + new FlatNestedMultiDecoder(new ObjectMapReplayDecoder(), new ListResultReplayDecoder()), ValueType.OBJECT ); RedisStrictCommand INFO_REPLICATION = new RedisStrictCommand("INFO", "replication", new StringDataDecoder()); diff --git a/src/main/java/org/redisson/client/protocol/convertor/DoubleReplayConvertor.java b/src/main/java/org/redisson/client/protocol/convertor/DoubleReplayConvertor.java index 4bef92164..dd1a51eca 100644 --- a/src/main/java/org/redisson/client/protocol/convertor/DoubleReplayConvertor.java +++ b/src/main/java/org/redisson/client/protocol/convertor/DoubleReplayConvertor.java @@ -19,7 +19,7 @@ public class DoubleReplayConvertor extends SingleConvertor { @Override public Double convert(Object obj) { - if (obj == null) { + if (obj == null || obj.toString().isEmpty()) { return null; } return Double.valueOf(obj.toString()); diff --git a/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java new file mode 100644 index 000000000..4bd04c8ee --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java @@ -0,0 +1,48 @@ +/** + * 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.client.protocol.decoder; + +import java.io.IOException; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class FlatNestedMultiDecoder extends NestedMultiDecoder { + + public FlatNestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { + super(firstDecoder, secondDecoder); + } + + public FlatNestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, boolean handleEmpty) { + super(firstDecoder, secondDecoder, handleEmpty); + } + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + return firstDecoder.decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + DecoderState ds = getDecoder(state); + if (paramNum == 0) { + ds.resetDecoderIndex(); + } + return firstDecoder.isApplicable(paramNum, state); + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceDecoder.java new file mode 100644 index 000000000..4464f27b5 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceDecoder.java @@ -0,0 +1,58 @@ +/** + * 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.client.protocol.decoder; + +import java.io.IOException; +import java.util.List; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.DoubleCodec; +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; + +public class GeoDistanceDecoder implements MultiDecoder> { + + private final ThreadLocal pos = new ThreadLocal(); + + private final Codec codec; + + public GeoDistanceDecoder(Codec codec) { + super(); + this.codec = codec; + } + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + if (pos.get() % 2 == 0) { + return codec.getValueDecoder().decode(buf, state); + } + return DoubleCodec.INSTANCE.getValueDecoder().decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + pos.set(paramNum); + return true; + } + + @Override + public List decode(List parts, State state) { + return parts; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java new file mode 100644 index 000000000..9e9d526e5 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java @@ -0,0 +1,65 @@ +/** + * 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.client.protocol.decoder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.DoubleCodec; +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class GeoDistanceMapDecoder implements MultiDecoder> { + + private final ThreadLocal pos = new ThreadLocal(); + + private final Codec codec; + + public GeoDistanceMapDecoder(Codec codec) { + super(); + this.codec = codec; + } + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + if (pos.get() % 2 == 0) { + return codec.getValueDecoder().decode(buf, state); + } + return DoubleCodec.INSTANCE.getValueDecoder().decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + pos.set(paramNum); + return true; + } + + @Override + public Map decode(List parts, State state) { + Map result = new HashMap(parts.size()/2); + for (int i = 0; i < parts.size(); i++) { + if (i % 2 != 0) { + result.put(parts.get(i-1), parts.get(i)); + } + } + return result; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoMapReplayDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoMapReplayDecoder.java new file mode 100644 index 000000000..94c043a47 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoMapReplayDecoder.java @@ -0,0 +1,48 @@ +/** + * 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.client.protocol.decoder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class GeoMapReplayDecoder implements MultiDecoder> { + + @Override + public Object decode(ByteBuf buf, State state) { + throw new UnsupportedOperationException(); + } + + @Override + public Map decode(List parts, State state) { + Map result = new HashMap(parts.size()); + for (Object object : parts) { + List vals = ((List) object); + result.put(vals.get(0), vals.get(1)); + } + return result; + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return false; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoPositionDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionDecoder.java new file mode 100644 index 000000000..3b3fea8f0 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionDecoder.java @@ -0,0 +1,50 @@ +/** + * 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.client.protocol.decoder; + +import java.io.IOException; +import java.util.List; + +import org.redisson.client.codec.DoubleCodec; +import org.redisson.client.handler.State; +import org.redisson.core.GeoPosition; + +import io.netty.buffer.ByteBuf; + +public class GeoPositionDecoder implements MultiDecoder { + + @Override + public Double decode(ByteBuf buf, State state) throws IOException { + return (Double) DoubleCodec.INSTANCE.getValueDecoder().decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return true; + } + + @Override + public GeoPosition decode(List parts, State state) { + if (parts.isEmpty()) { + return null; + } + + Double longitude = Double.valueOf(parts.get(0).toString()); + Double latitude = Double.valueOf(parts.get(1).toString()); + return new GeoPosition(longitude, latitude); + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoPositionMapDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionMapDecoder.java new file mode 100644 index 000000000..07ac3662f --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionMapDecoder.java @@ -0,0 +1,62 @@ +/** + * 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.client.protocol.decoder; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class GeoPositionMapDecoder implements MultiDecoder> { + + private final List args; + + public GeoPositionMapDecoder(List args) { + this.args = args; + } + + @Override + public Double decode(ByteBuf buf, State state) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return false; + } + + @Override + public Map decode(List parts, State state) { + if (parts.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap(parts.size()); + for (int index = 0; index < args.size()-1; index++) { + Object value = parts.get(index); + if (value == null || value == Collections.emptyMap()) { + continue; + } + result.put(args.get(index+1), value); + } + return result; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder.java index f8ac24ff1..43b5266ac 100644 --- a/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder.java +++ b/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder.java @@ -16,9 +16,6 @@ package org.redisson.client.protocol.decoder; import java.io.IOException; -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Deque; import java.util.List; import org.redisson.client.handler.State; @@ -29,53 +26,102 @@ public class NestedMultiDecoder implements MultiDecoder { public static class DecoderState { - Deque> decoders; + int decoderIndex; + + int flipDecoderIndex; - Deque> flipDecoders; - - public DecoderState(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { - super(); - this.decoders = new ArrayDeque>(Arrays.asList(firstDecoder, secondDecoder)); - this.flipDecoders = new ArrayDeque>(Arrays.asList(firstDecoder, secondDecoder, firstDecoder)); + public DecoderState() { } - public Deque> getDecoders() { - return decoders; + public int getDecoderIndex() { + return decoderIndex; } - - public Deque> getFlipDecoders() { - return flipDecoders; + public void resetDecoderIndex() { + decoderIndex = 0; + } + public void incDecoderIndex() { + decoderIndex++; + } + + public int getFlipDecoderIndex() { + return flipDecoderIndex; + } + public void resetFlipDecoderIndex() { + flipDecoderIndex = 0; + } + public void incFlipDecoderIndex() { + flipDecoderIndex++; } } - private final MultiDecoder firstDecoder; - private final MultiDecoder secondDecoder; + protected final MultiDecoder firstDecoder; + protected final MultiDecoder secondDecoder; + private MultiDecoder thirdDecoder; + private boolean handleEmpty; public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { + this(firstDecoder, secondDecoder, false); + } + + public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, boolean handleEmpty) { + this(firstDecoder, secondDecoder, null, handleEmpty); + } + + public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, MultiDecoder thirdDecoder) { + this(firstDecoder, secondDecoder, thirdDecoder, false); + } + + public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, MultiDecoder thirdDecoder, boolean handleEmpty) { this.firstDecoder = firstDecoder; this.secondDecoder = secondDecoder; + this.thirdDecoder = thirdDecoder; + this.handleEmpty = handleEmpty; } @Override public Object decode(ByteBuf buf, State state) throws IOException { DecoderState ds = getDecoder(state); - return ds.getFlipDecoders().peek().decode(buf, state); + + MultiDecoder decoder = null; + if (ds.getFlipDecoderIndex() == 2) { + decoder = firstDecoder; + } + if (ds.getFlipDecoderIndex() == 1) { + decoder = secondDecoder; + } + + return decoder.decode(buf, state); } @Override public boolean isApplicable(int paramNum, State state) { DecoderState ds = getDecoder(state); if (paramNum == 0) { - ds.getFlipDecoders().poll(); + ds.incFlipDecoderIndex(); + ds.resetDecoderIndex(); } - return ds.getFlipDecoders().peek().isApplicable(paramNum, state); + // used only with thirdDecoder + if (ds.getFlipDecoderIndex() == 3) { + ds.resetFlipDecoderIndex(); + ds.incFlipDecoderIndex(); + } + + MultiDecoder decoder = null; + if (ds.getFlipDecoderIndex() == 2) { + decoder = firstDecoder; + } + if (ds.getFlipDecoderIndex() == 1) { + decoder = secondDecoder; + } + + return decoder.isApplicable(paramNum, state); } - private DecoderState getDecoder(State state) { + protected final DecoderState getDecoder(State state) { DecoderState ds = state.getDecoderState(); if (ds == null) { - ds = new DecoderState(firstDecoder, secondDecoder); + ds = new DecoderState(); state.setDecoderState(ds); } return ds; @@ -83,8 +129,32 @@ public class NestedMultiDecoder implements MultiDecoder { @Override public Object decode(List parts, State state) { + if (parts.isEmpty() && state.getDecoderState() == null && handleEmpty) { + MultiDecoder decoder = secondDecoder; + if (thirdDecoder != null) { + decoder = thirdDecoder; + } + return decoder.decode(parts, state); + } + DecoderState ds = getDecoder(state); - return ds.getDecoders().poll().decode(parts, state); + if (parts.isEmpty()) { + ds.resetDecoderIndex(); + } + + ds.incDecoderIndex(); + MultiDecoder decoder = null; + if (ds.getDecoderIndex() == 1) { + decoder = firstDecoder; + } + if (ds.getDecoderIndex() == 2) { + decoder = secondDecoder; + } + if (ds.getDecoderIndex() == 3) { + decoder = thirdDecoder; + } + + return decoder.decode(parts, state); } } diff --git a/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder2.java b/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder2.java deleted file mode 100644 index 36ebdac71..000000000 --- a/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder2.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * 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.client.protocol.decoder; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.redisson.client.handler.State; - -import io.netty.buffer.ByteBuf; - -public class NestedMultiDecoder2 implements MultiDecoder { - - private final MultiDecoder firstDecoder; - private final MultiDecoder secondDecoder; - - public NestedMultiDecoder2(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { - this.firstDecoder = firstDecoder; - this.secondDecoder = secondDecoder; - } - - @Override - public Object decode(ByteBuf buf, State state) throws IOException { - return firstDecoder.decode(buf, state); - } - - @Override - public boolean isApplicable(int paramNum, State state) { - if (paramNum == 0) { - setCounter(state, 0); - } - return firstDecoder.isApplicable(paramNum, state); - } - - private Integer getCounter(State state) { - Integer value = state.getDecoderState(); - if (value == null) { - return 0; - } - return value; - } - - private void setCounter(State state, Integer value) { - state.setDecoderState(value); - } - - - @Override - public Object decode(List parts, State state) { - // handle empty result - if (parts.isEmpty() && state.getDecoderState() == null) { - return secondDecoder.decode(parts, state); - } - - int counter = getCounter(state); - if (counter == 2) { - counter = 0; - } - counter++; - setCounter(state, counter); - MultiDecoder decoder = null; - if (counter == 1) { - decoder = firstDecoder; - } - if (counter == 2) { - decoder = secondDecoder; - } - return decoder.decode(parts, state); - } - -} diff --git a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java index d0bc0cf36..7ac9a601e 100644 --- a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java +++ b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java @@ -346,8 +346,9 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { failedSlaves.removeAll(currentPart.getFailedSlaveAddresses()); for (URI uri : failedSlaves) { currentPart.addFailedSlaveAddress(uri); - slaveDown(entry, uri.getHost(), uri.getPort(), FreezeReason.MANAGER); - log.warn("slave: {} has down for slot ranges: {}", uri, currentPart.getSlotRanges()); + if (entry.slaveDown(uri.getHost(), uri.getPort(), FreezeReason.MANAGER)) { + log.warn("slave: {} has down for slot ranges: {}", uri, currentPart.getSlotRanges()); + } } } @@ -358,8 +359,9 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { for (URI uri : removedSlaves) { currentPart.removeSlaveAddress(uri); - slaveDown(entry, uri.getHost(), uri.getPort(), FreezeReason.MANAGER); - log.info("slave {} removed for slot ranges: {}", uri, currentPart.getSlotRanges()); + if (entry.slaveDown(uri.getHost(), uri.getPort(), FreezeReason.MANAGER)) { + log.info("slave {} removed for slot ranges: {}", uri, currentPart.getSlotRanges()); + } } Set addedSlaves = new HashSet(newPart.getSlaveAddresses()); diff --git a/src/main/java/org/redisson/codec/JsonJacksonCodec.java b/src/main/java/org/redisson/codec/JsonJacksonCodec.java index 711e1156f..e90c5cef2 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() { @@ -110,7 +124,9 @@ public class JsonJacksonCodec implements Codec { .withCreatorVisibility(JsonAutoDetect.Visibility.NONE)); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true); + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class); } @Override diff --git a/src/main/java/org/redisson/command/CommandAsyncExecutor.java b/src/main/java/org/redisson/command/CommandAsyncExecutor.java index cddeb2a8b..a218b8f19 100644 --- a/src/main/java/org/redisson/command/CommandAsyncExecutor.java +++ b/src/main/java/org/redisson/command/CommandAsyncExecutor.java @@ -18,6 +18,7 @@ package org.redisson.command; import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; +import java.util.concurrent.TimeUnit; import org.redisson.SlotCallback; import org.redisson.client.RedisException; @@ -38,6 +39,8 @@ public interface CommandAsyncExecutor { RedisException convertException(Future future); + boolean await(Future future, long timeout, TimeUnit timeoutUnit) throws InterruptedException; + V get(Future future); Future writeAsync(Integer slot, Codec codec, RedisCommand command, Object ... params); diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index d8c44d2fa..cf1a506a9 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -20,13 +20,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.redisson.RedisClientResult; +import org.redisson.RedissonShutdownException; import org.redisson.SlotCallback; import org.redisson.client.RedisAskException; import org.redisson.client.RedisConnection; @@ -38,6 +41,7 @@ import org.redisson.client.WriteRedisConnectionException; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandsData; +import org.redisson.client.protocol.QueueCommand; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommands; import org.redisson.cluster.ClusterSlotRange; @@ -47,6 +51,7 @@ import org.redisson.connection.NodeSource.Redirect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.util.Timeout; @@ -55,6 +60,7 @@ import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.Promise; +import io.netty.util.concurrent.ScheduledFuture; /** * @@ -67,9 +73,6 @@ public class CommandAsyncService implements CommandAsyncExecutor { final ConnectionManager connectionManager; - private final Set skipTimeout = new HashSet(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(), - RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName())); - public CommandAsyncService(ConnectionManager connectionManager) { this.connectionManager = connectionManager; } @@ -81,13 +84,38 @@ public class CommandAsyncService implements CommandAsyncExecutor { @Override public V get(Future future) { - future.awaitUninterruptibly(); + final CountDownLatch l = new CountDownLatch(1); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + l.countDown(); + } + }); + try { + l.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + // commented out due to blocking issues up to 200 ms per minute for each thread + // future.awaitUninterruptibly(); if (future.isSuccess()) { return future.getNow(); } throw convertException(future); } + @Override + public boolean await(Future future, long timeout, TimeUnit timeoutUnit) throws InterruptedException { + final CountDownLatch l = new CountDownLatch(1); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + l.countDown(); + } + }); + return l.await(timeout, timeoutUnit); + } + @Override public Future readAsync(InetSocketAddress client, String key, Codec codec, RedisCommand command, Object ... params) { Promise mainPromise = connectionManager.newPromise(); @@ -354,7 +382,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { } if (!connectionManager.getShutdownLatch().acquire()) { - mainPromise.setFailure(new IllegalStateException("Redisson is shutdown")); + mainPromise.setFailure(new RedissonShutdownException("Redisson is shutdown")); return; } @@ -460,8 +488,9 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.getTimeout().cancel(); int timeoutTime = connectionManager.getConfig().getTimeout(); - if (skipTimeout.contains(details.getCommand().getName())) { + if (QueueCommand.TIMEOUTLESS_COMMANDS.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); + handleBlockingOperations(details, connection, popTimeout); if (popTimeout == 0) { return; } @@ -482,6 +511,71 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.setTimeout(timeout); } + private void handleBlockingOperations(final AsyncDetails details, final RedisConnection connection, Integer popTimeout) { + final FutureListener listener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + details.getMainPromise().tryFailure(new RedissonShutdownException("Redisson is shutdown")); + } + }; + + final AtomicBoolean canceledByScheduler = new AtomicBoolean(); + final ScheduledFuture scheduledFuture; + if (popTimeout != 0) { + // to handle cases when connection has been lost + final Channel orignalChannel = connection.getChannel(); + scheduledFuture = connectionManager.getGroup().schedule(new Runnable() { + @Override + public void run() { + // there is no re-connection was made + // and connection is still active + if (orignalChannel == connection.getChannel() + && connection.isActive()) { + return; + } + + canceledByScheduler.set(true); + details.getAttemptPromise().trySuccess(null); + } + }, popTimeout, TimeUnit.SECONDS); + } else { + scheduledFuture = null; + } + + details.getMainPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (scheduledFuture != null) { + scheduledFuture.cancel(false); + } + connectionManager.getShutdownPromise().removeListener(listener); + // handling cancel operation for commands from skipTimeout collection + if ((future.isCancelled() && details.getAttemptPromise().cancel(true)) + || canceledByScheduler.get()) { + connection.forceReconnectAsync(); + return; + } + + if (future.cause() instanceof RedissonShutdownException) { + details.getAttemptPromise().tryFailure(future.cause()); + } + } + }); + + details.getAttemptPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (future.isCancelled()) { + // command should be removed due to + // ConnectionWatchdog blockingQueue reconnection logic + connection.removeCurrentCommand(); + } + } + }); + + connectionManager.getShutdownPromise().addListener(listener); + } + private void checkConnectionFuture(final NodeSource source, final AsyncDetails details) { if (details.getAttemptPromise().isDone() || details.getMainPromise().isCancelled() || details.getConnectionFuture().isCancelled()) { @@ -601,7 +695,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { } details.getMainPromise().setSuccess(res); } else { - details.getMainPromise().setFailure(future.cause()); + details.getMainPromise().tryFailure(future.cause()); } AsyncDetails.release(details); } diff --git a/src/main/java/org/redisson/connection/ClientConnectionsEntry.java b/src/main/java/org/redisson/connection/ClientConnectionsEntry.java index 72a1adaeb..1d9c3d6da 100644 --- a/src/main/java/org/redisson/connection/ClientConnectionsEntry.java +++ b/src/main/java/org/redisson/connection/ClientConnectionsEntry.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; public class ClientConnectionsEntry { @@ -137,7 +138,7 @@ public class ClientConnectionsEntry { } public Future connect() { - final Promise connectionFuture = client.getBootstrap().group().next().newPromise(); + final Promise connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise(); Future future = client.connectAsync(); future.addListener(new FutureListener() { @Override @@ -157,19 +158,19 @@ public class ClientConnectionsEntry { } private void addReconnectListener(Promise connectionFuture, T conn) { - connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); - addFireEventListener(connectionFuture); + addFireEventListener(conn, connectionFuture); conn.setReconnectListener(new ReconnectListener() { @Override public void onReconnect(RedisConnection conn, Promise connectionFuture) { - connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); - addFireEventListener(connectionFuture); + addFireEventListener(conn, connectionFuture); } }); } - private void addFireEventListener(Promise connectionFuture) { + private void addFireEventListener(T conn, Promise connectionFuture) { + connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); + if (connectionFuture.isSuccess()) { connectionManager.getConnectionEventsHub().fireConnect(connectionFuture.getNow().getRedisClient().getAddr()); return; @@ -186,7 +187,7 @@ public class ClientConnectionsEntry { } public Future connectPubSub() { - final Promise connectionFuture = client.getBootstrap().group().next().newPromise(); + final Promise connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise(); Future future = client.connectPubSubAsync(); future.addListener(new FutureListener() { @Override @@ -195,10 +196,12 @@ public class ClientConnectionsEntry { connectionFuture.tryFailure(future.cause()); return; } + RedisPubSubConnection conn = future.getNow(); log.debug("new pubsub connection created: {}", conn); addReconnectListener(connectionFuture, conn); + allSubscribeConnections.add(conn); } diff --git a/src/main/java/org/redisson/connection/ConnectionManager.java b/src/main/java/org/redisson/connection/ConnectionManager.java index 48b2e2c98..dd3ad00f2 100644 --- a/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/src/main/java/org/redisson/connection/ConnectionManager.java @@ -27,7 +27,6 @@ import org.redisson.client.RedisPubSubListener; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommand; import org.redisson.cluster.ClusterSlotRange; -import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.core.NodeType; import org.redisson.misc.InfinitySemaphoreLatch; @@ -62,8 +61,6 @@ public interface ConnectionManager { Future newFailedFuture(Throwable cause); - void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason); - Collection getClients(); void shutdownAsync(RedisClient client); @@ -107,5 +104,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/FastCompleteFuture.java b/src/main/java/org/redisson/connection/FastCompleteFuture.java deleted file mode 100644 index c11645c47..000000000 --- a/src/main/java/org/redisson/connection/FastCompleteFuture.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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.connection; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.util.concurrent.CompleteFuture; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - -/** - * Invokes Future listeners in the same thread unlike {@code SucceededFuture} does. - * - * @author Nikita Koksharov - * - * @param - */ -public abstract class FastCompleteFuture extends CompleteFuture { - - private static final Logger logger = LoggerFactory.getLogger(FastCompleteFuture.class); - - protected FastCompleteFuture() { - super(null); - } - - @Override - public Future addListener(GenericFutureListener> listener) { - if (listener == null) { - throw new NullPointerException("listener"); - } - - notify(listener); - return this; - } - - private void notify(GenericFutureListener> listener) { - try { - ((GenericFutureListener)listener).operationComplete(this); - } catch (Throwable t) { - if (logger.isWarnEnabled()) { - logger.warn("An exception was thrown by " + listener.getClass().getName() + ".operationComplete()", t); - } - } - } - - @Override - public Future addListeners(GenericFutureListener>... listeners) { - if (listeners == null) { - throw new NullPointerException("listeners"); - } - for (GenericFutureListener> l: listeners) { - if (l == null) { - break; - } - notify(l); - } - return this; - } - - -} diff --git a/src/main/java/org/redisson/connection/FastFailedFuture.java b/src/main/java/org/redisson/connection/FastFailedFuture.java deleted file mode 100644 index 00a61d234..000000000 --- a/src/main/java/org/redisson/connection/FastFailedFuture.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * 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.connection; - -import io.netty.util.concurrent.Future; -import io.netty.util.internal.PlatformDependent; - -/** - * - * @author Nikita Koksharov - * - * @param - */ -public class FastFailedFuture extends FastCompleteFuture { - - private final Throwable cause; - - protected FastFailedFuture(Throwable cause) { - if (cause == null) { - throw new NullPointerException("cause"); - } - this.cause = cause; - } - - @Override - public Throwable cause() { - return cause; - } - - @Override - public boolean isSuccess() { - return false; - } - - @Override - public Future sync() { - PlatformDependent.throwException(cause); - return this; - } - - @Override - public Future syncUninterruptibly() { - PlatformDependent.throwException(cause); - return this; - } - - @Override - public V getNow() { - return null; - } - -} diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 1fb69f386..63f8e175f 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -61,6 +61,7 @@ import io.netty.util.Timer; import io.netty.util.TimerTask; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; import io.netty.util.internal.PlatformDependent; @@ -122,6 +123,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()); @@ -134,7 +137,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { this(config); init(cfg); } - + public MasterSlaveConnectionManager(Config cfg) { Version.logVersion(); @@ -156,6 +159,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { this.socketChannelClass = NioSocketChannel.class; } this.codec = cfg.getCodec(); + this.shutdownPromise = newPromise(); this.isClusterMode = cfg.isClusterConfig(); } @@ -538,77 +542,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager { return null; } - public void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason) { - Collection allPubSubConnections = entry.slaveDown(host, port, freezeReason); - if (allPubSubConnections.isEmpty()) { - return; - } - - // reattach listeners to other channels - for (Entry mapEntry : name2PubSubConnection.entrySet()) { - for (RedisPubSubConnection redisPubSubConnection : allPubSubConnections) { - PubSubConnectionEntry pubSubEntry = mapEntry.getValue(); - final String channelName = mapEntry.getKey(); - - if (!pubSubEntry.getConnection().equals(redisPubSubConnection)) { - continue; - } - - synchronized (pubSubEntry) { - pubSubEntry.close(); - - final Collection listeners = pubSubEntry.getListeners(channelName); - if (pubSubEntry.getConnection().getPatternChannels().get(channelName) != null) { - Codec subscribeCodec = punsubscribe(channelName); - if (!listeners.isEmpty()) { - Future future = psubscribe(channelName, subscribeCodec); - future.addListener(new FutureListener() { - @Override - public void operationComplete(Future future) - throws Exception { - if (!future.isSuccess()) { - log.error("Can't resubscribe topic channel: " + channelName); - return; - } - - PubSubConnectionEntry newEntry = future.getNow(); - for (RedisPubSubListener redisPubSubListener : listeners) { - newEntry.addListener(channelName, redisPubSubListener); - } - log.debug("resubscribed listeners for '{}' channel-pattern", channelName); - } - }); - } - } else { - Codec subscribeCodec = unsubscribe(channelName); - if (!listeners.isEmpty()) { - Future future = subscribe(subscribeCodec, channelName, null); - future.addListener(new FutureListener() { - - @Override - public void operationComplete(Future future) - throws Exception { - if (!future.isSuccess()) { - log.error("Can't resubscribe topic channel: " + channelName); - return; - } - PubSubConnectionEntry newEntry = future.getNow(); - for (RedisPubSubListener redisPubSubListener : listeners) { - newEntry.addListener(channelName, redisPubSubListener); - } - log.debug("resubscribed listeners for '{}' channel", channelName); - } - }); - } - } - } - } - } - } - protected void slaveDown(ClusterSlotRange slotRange, String host, int port, FreezeReason freezeReason) { - MasterSlaveEntry entry = getEntry(slotRange); - slaveDown(entry, host, port, freezeReason); + getEntry(slotRange).slaveDown(host, port, freezeReason); } protected void changeMaster(ClusterSlotRange slotRange, String host, int port) { @@ -674,7 +609,10 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdown() { - shutdownLatch.closeAndAwaitUninterruptibly(); + shutdownLatch.close(); + shutdownPromise.trySuccess(true); + shutdownLatch.awaitUninterruptibly(); + for (MasterSlaveEntry entry : entries.values()) { entry.shutdown(); } @@ -699,17 +637,17 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public Promise newPromise() { - return group.next().newPromise(); + return ImmediateEventExecutor.INSTANCE.newPromise(); } @Override public Future newSucceededFuture(R value) { - return new FastSuccessFuture(value); + return ImmediateEventExecutor.INSTANCE.newSucceededFuture(value); } @Override public Future newFailedFuture(Throwable cause) { - return new FastFailedFuture(cause); + return ImmediateEventExecutor.INSTANCE.newFailedFuture(cause); } @Override @@ -731,6 +669,11 @@ public class MasterSlaveConnectionManager implements ConnectionManager { public InfinitySemaphoreLatch getShutdownLatch() { return shutdownLatch; } + + @Override + public Future getShutdownPromise() { + return shutdownPromise; + } @Override public ConnectionEventsHub getConnectionEventsHub() { diff --git a/src/main/java/org/redisson/connection/MasterSlaveEntry.java b/src/main/java/org/redisson/connection/MasterSlaveEntry.java index 7582e98fc..ce8bea5c6 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveEntry.java +++ b/src/main/java/org/redisson/connection/MasterSlaveEntry.java @@ -28,6 +28,9 @@ import org.redisson.ReadMode; import org.redisson.client.RedisClient; import org.redisson.client.RedisConnection; import org.redisson.client.RedisPubSubConnection; +import org.redisson.client.RedisPubSubListener; +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.CommandData; import org.redisson.cluster.ClusterSlotRange; import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.connection.balancer.LoadBalancerManager; @@ -37,7 +40,10 @@ import org.redisson.core.NodeType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; /** * @@ -90,9 +96,12 @@ public class MasterSlaveEntry { return writeConnectionHolder.add(masterEntry); } - public Collection slaveDown(String host, int port, FreezeReason freezeReason) { - Collection conns = slaveBalancer.freeze(host, port, freezeReason); - + public boolean slaveDown(String host, int port, FreezeReason freezeReason) { + ClientConnectionsEntry entry = slaveBalancer.freeze(host, port, freezeReason); + if (entry == null) { + return false; + } + // add master as slave if no more slaves available if (config.getReadMode() == ReadMode.SLAVE && slaveBalancer.getAvailableClients() == 0) { InetSocketAddress addr = masterEntry.getClient().getAddr(); @@ -100,7 +109,153 @@ public class MasterSlaveEntry { log.info("master {}:{} used as slave", addr.getHostName(), addr.getPort()); } } - return conns; + + // close all connections + while (true) { + final RedisConnection connection = entry.pollConnection(); + if (connection == null) { + break; + } + + connection.closeAsync().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + reattachBlockingQueue(connection); + } + }); + } + + // close all pub/sub connections + while (true) { + RedisPubSubConnection connection = entry.pollSubscribeConnection(); + if (connection == null) { + break; + } + connection.closeAsync(); + } + + for (RedisPubSubConnection connection : entry.getAllSubscribeConnections()) { + reattachPubSub(connection); + } + entry.getAllSubscribeConnections().clear(); + + return true; + } + + private void reattachPubSub(RedisPubSubConnection redisPubSubConnection) { + for (String channelName : redisPubSubConnection.getChannels().keySet()) { + PubSubConnectionEntry pubSubEntry = connectionManager.getPubSubEntry(channelName); + + synchronized (pubSubEntry) { + pubSubEntry.close(); + + Collection listeners = pubSubEntry.getListeners(channelName); + reattachPubSubListeners(channelName, listeners); + } + } + + for (String channelName : redisPubSubConnection.getPatternChannels().keySet()) { + PubSubConnectionEntry pubSubEntry = connectionManager.getPubSubEntry(channelName); + + synchronized (pubSubEntry) { + pubSubEntry.close(); + + Collection listeners = pubSubEntry.getListeners(channelName); + reattachPatternPubSubListeners(channelName, listeners); + } + } + } + + private void reattachPubSubListeners(final String channelName, final Collection listeners) { + Codec subscribeCodec = connectionManager.unsubscribe(channelName); + if (!listeners.isEmpty()) { + Future future = connectionManager.subscribe(subscribeCodec, channelName, null); + future.addListener(new FutureListener() { + + @Override + public void operationComplete(Future future) + throws Exception { + if (!future.isSuccess()) { + log.error("Can't resubscribe topic channel: " + channelName); + return; + } + PubSubConnectionEntry newEntry = future.getNow(); + for (RedisPubSubListener redisPubSubListener : listeners) { + newEntry.addListener(channelName, redisPubSubListener); + } + log.debug("resubscribed listeners for '{}' channel", channelName); + } + }); + } + } + + private void reattachPatternPubSubListeners(final String channelName, + final Collection listeners) { + Codec subscribeCodec = connectionManager.punsubscribe(channelName); + if (!listeners.isEmpty()) { + Future future = connectionManager.psubscribe(channelName, subscribeCodec); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) + throws Exception { + if (!future.isSuccess()) { + log.error("Can't resubscribe topic channel: " + channelName); + return; + } + + PubSubConnectionEntry newEntry = future.getNow(); + for (RedisPubSubListener redisPubSubListener : listeners) { + newEntry.addListener(channelName, redisPubSubListener); + } + log.debug("resubscribed listeners for '{}' channel-pattern", channelName); + } + }); + } + } + + private void reattachBlockingQueue(RedisConnection connection) { + final CommandData commandData = connection.getCurrentCommand(); + + if (commandData == null + || !commandData.isBlockingCommand()) { + return; + } + + Future newConnection = connectionReadOp(); + newConnection.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't resubscribe blocking queue {}", commandData); + return; + } + + final RedisConnection newConnection = future.getNow(); + + final FutureListener listener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + releaseRead(newConnection); + } + }; + commandData.getPromise().addListener(listener); + if (commandData.getPromise().isDone()) { + return; + } + ChannelFuture channelFuture = newConnection.send(commandData); + channelFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + listener.operationComplete(null); + commandData.getPromise().removeListener(listener); + releaseRead(newConnection); + log.error("Can't resubscribe blocking queue {}", commandData); + } + } + }); + } + }); } public Future addSlave(String host, int port) { @@ -134,7 +289,7 @@ public class MasterSlaveEntry { // exclude master from slaves if (config.getReadMode() == ReadMode.SLAVE && (!addr.getHostName().equals(host) || port != addr.getPort())) { - connectionManager.slaveDown(this, addr.getHostName(), addr.getPort(), FreezeReason.SYSTEM); + slaveDown(addr.getHostName(), addr.getPort(), FreezeReason.SYSTEM); log.info("master {}:{} excluded from slaves", addr.getHostName(), addr.getPort()); } return true; @@ -155,7 +310,7 @@ public class MasterSlaveEntry { // more than one slave available, so master can be removed from slaves if (config.getReadMode() == ReadMode.SLAVE && slaveBalancer.getAvailableClients() > 1) { - connectionManager.slaveDown(this, host, port, FreezeReason.SYSTEM); + slaveDown(host, port, FreezeReason.SYSTEM); } connectionManager.shutdownAsync(oldMaster.getClient()); } diff --git a/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java b/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java index 56a372b98..edef7d2bb 100644 --- a/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java +++ b/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java @@ -16,7 +16,6 @@ package org.redisson.connection.balancer; import java.net.InetSocketAddress; -import java.util.Collection; import org.redisson.client.RedisConnection; import org.redisson.client.RedisPubSubConnection; @@ -37,7 +36,7 @@ public interface LoadBalancerManager { boolean unfreeze(String host, int port, FreezeReason freezeReason); - Collection freeze(String host, int port, FreezeReason freezeReason); + ClientConnectionsEntry freeze(String host, int port, FreezeReason freezeReason); Future add(ClientConnectionsEntry entry); diff --git a/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java b/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java index 45f55ecc0..38135c62d 100644 --- a/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java +++ b/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java @@ -16,10 +16,6 @@ package org.redisson.connection.balancer; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; import java.util.Map; import org.redisson.MasterSlaveServersConfig; @@ -99,16 +95,20 @@ public class LoadBalancerManagerImpl implements LoadBalancerManager { return false; } - public Collection freeze(String host, int port, FreezeReason freezeReason) { + public ClientConnectionsEntry freeze(String host, int port, FreezeReason freezeReason) { InetSocketAddress addr = new InetSocketAddress(host, port); ClientConnectionsEntry connectionEntry = addr2Entry.get(addr); if (connectionEntry == null) { - return Collections.emptyList(); + return null; } synchronized (connectionEntry) { - log.debug("{} freezed", addr); + if (connectionEntry.isFreezed()) { + return null; + } + connectionEntry.setFreezed(true); + // only RECONNECT freeze reason could be replaced if (connectionEntry.getFreezeReason() == null || connectionEntry.getFreezeReason() == FreezeReason.RECONNECT) { @@ -116,29 +116,7 @@ public class LoadBalancerManagerImpl implements LoadBalancerManager { } } - // close all connections - while (true) { - RedisConnection connection = connectionEntry.pollConnection(); - if (connection == null) { - break; - } - connection.closeAsync(); - } - - // close all pub/sub connections - while (true) { - RedisPubSubConnection connection = connectionEntry.pollSubscribeConnection(); - if (connection == null) { - break; - } - connection.closeAsync(); - } - - synchronized (connectionEntry) { - List list = new ArrayList(connectionEntry.getAllSubscribeConnections()); - connectionEntry.getAllSubscribeConnections().clear(); - return list; - } + return connectionEntry; } public Future nextPubSubConnection() { diff --git a/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java b/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java index 3cf057aaa..7ce90ee4f 100644 --- a/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java +++ b/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java @@ -16,6 +16,7 @@ package org.redisson.connection.decoder; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,10 +28,12 @@ import io.netty.buffer.ByteBuf; public class MapGetAllDecoder implements MultiDecoder> { + private final int shiftIndex; private final List args; - public MapGetAllDecoder(List args) { + public MapGetAllDecoder(List args, int shiftIndex) { this.args = args; + this.shiftIndex = shiftIndex; } @Override @@ -45,13 +48,16 @@ public class MapGetAllDecoder implements MultiDecoder> { @Override public Map decode(List parts, State state) { + if (parts.isEmpty()) { + return Collections.emptyMap(); + } Map result = new HashMap(parts.size()); - for (int index = 0; index < args.size()-1; index++) { + for (int index = 0; index < args.size()-shiftIndex; index++) { Object value = parts.get(index); if (value == null) { continue; } - result.put(args.get(index+1), value); + result.put(args.get(index+shiftIndex), value); } return result; } diff --git a/src/main/java/org/redisson/connection/pool/ConnectionPool.java b/src/main/java/org/redisson/connection/pool/ConnectionPool.java index 58998a2dd..87dc8a594 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("Publish/Subscribe connection pool exhausted! All connections are busy. Try to increase Publish/Subscribe connection pool size."); + StringBuilder errorMsg = new StringBuilder("Connection pool exhausted! All connections are busy. Increase connection pool size."); // if (!freezed.isEmpty()) { // errorMsg.append(" Disconnected hosts: " + freezed); // } @@ -277,7 +277,7 @@ abstract class ConnectionPool { private void checkForReconnect(ClientConnectionsEntry entry) { if (entry.getNodeType() == NodeType.SLAVE) { - connectionManager.slaveDown(masterSlaveEntry, entry.getClient().getAddr().getHostName(), + masterSlaveEntry.slaveDown(entry.getClient().getAddr().getHostName(), entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT); log.warn("slave {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts()); scheduleCheck(entry); diff --git a/src/main/java/org/redisson/core/GeoEntry.java b/src/main/java/org/redisson/core/GeoEntry.java new file mode 100644 index 000000000..7476b619f --- /dev/null +++ b/src/main/java/org/redisson/core/GeoEntry.java @@ -0,0 +1,43 @@ +/** + * 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 class GeoEntry { + + private final double longitude; + private final double latitude; + private final Object member; + + public GeoEntry(double longitude, double latitude, Object member) { + super(); + this.longitude = longitude; + this.latitude = latitude; + this.member = member; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + public Object getMember() { + return member; + } + +} diff --git a/src/main/java/org/redisson/core/GeoPosition.java b/src/main/java/org/redisson/core/GeoPosition.java new file mode 100644 index 000000000..545749273 --- /dev/null +++ b/src/main/java/org/redisson/core/GeoPosition.java @@ -0,0 +1,70 @@ +/** + * 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 class GeoPosition { + + private final double longitude; + private final double latitude; + + public GeoPosition(double longitude, double latitude) { + super(); + this.longitude = longitude; + this.latitude = latitude; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(latitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(longitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GeoPosition other = (GeoPosition) obj; + if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) + return false; + if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) + return false; + return true; + } + + @Override + public String toString() { + return "GeoPosition [longitude=" + longitude + ", latitude=" + latitude + "]"; + } + +} diff --git a/src/main/java/org/redisson/core/GeoUnit.java b/src/main/java/org/redisson/core/GeoUnit.java new file mode 100644 index 000000000..c4610ff93 --- /dev/null +++ b/src/main/java/org/redisson/core/GeoUnit.java @@ -0,0 +1,48 @@ +/** + * 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 enum GeoUnit { + + METERS { + @Override + public String toString() { + return "m"; + } + }, + + KILOMETERS { + @Override + public String toString() { + return "km"; + } + }, + + MILES { + @Override + public String toString() { + return "mi"; + } + }, + + FEET { + @Override + public String toString() { + return "ft"; + } + } + +} diff --git a/src/main/java/org/redisson/core/RBuckets.java b/src/main/java/org/redisson/core/RBuckets.java new file mode 100644 index 000000000..036d7f88f --- /dev/null +++ b/src/main/java/org/redisson/core/RBuckets.java @@ -0,0 +1,64 @@ +/** + * 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.List; +import java.util.Map; + +public interface RBuckets { + + /** + *

Returns a list of object holder instances by a key pattern. + * + *

Supported glob-style patterns:
+     *    h?llo subscribes to hello, hallo and hxllo
+     *    h*llo subscribes to hllo and heeeello
+     *    h[ae]llo subscribes to hello and hallo, but not hillo
+     *    h[^e]llo matches hallo, hbllo, ... but not hello
+     *    h[a-b]llo matches hallo and hbllo
+ *

Use \ to escape special characters if you want to match them verbatim. + * + * @param pattern + * @return + */ + List> find(String pattern); + + /** + * Returns Redis object mapped by key. Result Map is not contains + * key-value entry for null values. + * + * @param keys + * @return + */ + Map get(String ... keys); + + /** + * Try to save objects mapped by Redis key. + * If at least one of them is already exist then + * don't set none of them. + * + * @param buckets + */ + boolean trySet(Map buckets); + + /** + * Saves objects mapped by Redis key. + * + * @param buckets + */ + void set(Map buckets); + +} diff --git a/src/main/java/org/redisson/core/RGeo.java b/src/main/java/org/redisson/core/RGeo.java new file mode 100644 index 000000000..e07c3c02c --- /dev/null +++ b/src/main/java/org/redisson/core/RGeo.java @@ -0,0 +1,166 @@ +/** + * 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.List; +import java.util.Map; + +/** + * Geospatial items holder + * + * @author Nikita Koksharov + * + * @param + */ +public interface RGeo extends RExpirable, RGeoAsync { + + /** + * Adds geospatial member. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + long add(double longitude, double latitude, V member); + + /** + * Adds geospatial members. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + long add(GeoEntry... entries); + + /** + * Returns distance between members in GeoUnit units. + * + * @see {@link GeoUnit} + * + * @param firstMember + * @param secondMember + * @param geoUnit + * @return + */ + Double dist(V firstMember, V secondMember, GeoUnit geoUnit); + + /** + * Returns 11 characters Geohash string mapped by defined member. + * + * @param members + * @return + */ + Map hash(V... members); + + /** + * Returns geo-position mapped by defined member. + * + * @param members + * @return + */ + Map pos(V... members); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + List radius(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the location. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithDistance(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithPosition(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + List radius(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the defined member location. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithDistance(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithPosition(V member, double radius, GeoUnit geoUnit); + +} diff --git a/src/main/java/org/redisson/core/RGeoAsync.java b/src/main/java/org/redisson/core/RGeoAsync.java new file mode 100644 index 000000000..15420b385 --- /dev/null +++ b/src/main/java/org/redisson/core/RGeoAsync.java @@ -0,0 +1,167 @@ +/** + * 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.List; +import java.util.Map; + +import io.netty.util.concurrent.Future; + +/** + * + * @author Nikita Koksharov + * + * @param + */ +public interface RGeoAsync extends RExpirableAsync { + + /** + * Adds geospatial member. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + Future addAsync(double longitude, double latitude, V member); + + /** + * Adds geospatial members. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + Future addAsync(GeoEntry... entries); + + /** + * Returns distance between members in GeoUnit units. + * + * @see {@link GeoUnit} + * + * @param firstMember + * @param secondMember + * @param geoUnit + * @return + */ + Future distAsync(V firstMember, V secondMember, GeoUnit geoUnit); + + /** + * Returns 11 characters Geohash string mapped by defined member. + * + * @param members + * @return + */ + Future> hashAsync(V... members); + + /** + * Returns geo-position mapped by defined member. + * + * @param members + * @return + */ + Future> posAsync(V... members); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the location. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithDistanceAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithPositionAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusAsync(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the defined member location. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithDistanceAsync(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit); + +} diff --git a/src/main/java/org/redisson/core/RLexSortedSet.java b/src/main/java/org/redisson/core/RLexSortedSet.java index 1f68f6180..598df157f 100644 --- a/src/main/java/org/redisson/core/RLexSortedSet.java +++ b/src/main/java/org/redisson/core/RLexSortedSet.java @@ -20,32 +20,110 @@ import java.util.Set; public interface RLexSortedSet extends RLexSortedSetAsync, Set, RExpirable { + int removeRangeTail(String fromElement, boolean fromInclusive); + + /** + * Use {@link RLexSortedSet#removeRangeTail(String, boolean)} + */ + @Deprecated int removeRangeTailByLex(String fromElement, boolean fromInclusive); + int removeRangeHead(String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSet#removeRangeHead(String, boolean)} + */ + @Deprecated int removeRangeHeadByLex(String toElement, boolean toInclusive); + int removeRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSet#removeRange(String, boolean)} + */ + @Deprecated int removeRangeByLex(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + int countTail(String fromElement, boolean fromInclusive); + + /** + * Use {@link RLexSortedSet#countTail(String, boolean)} + */ + @Deprecated int lexCountTail(String fromElement, boolean fromInclusive); + int countHead(String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSet#countHead(String, boolean)} + */ + @Deprecated int lexCountHead(String toElement, boolean toInclusive); + Collection rangeTail(String fromElement, boolean fromInclusive); + + /** + * Use {@link RLexSortedSet#rangeTail(String, boolean)} + */ + @Deprecated Collection lexRangeTail(String fromElement, boolean fromInclusive); + Collection rangeHead(String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSet#rangeHead(String, boolean)} + */ + @Deprecated Collection lexRangeHead(String toElement, boolean toInclusive); + Collection range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSet#range(String, boolean, String, boolean)} + */ + @Deprecated Collection lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + Collection rangeTail(String fromElement, boolean fromInclusive, int offset, int count); + + /** + * Use {@link RLexSortedSet#rangeTail(String, boolean, int, int)} + */ + @Deprecated Collection lexRangeTail(String fromElement, boolean fromInclusive, int offset, int count); + Collection rangeHead(String toElement, boolean toInclusive, int offset, int count); + + /** + * Use {@link RLexSortedSet#rangeHead(String, boolean, int, int)} + */ + @Deprecated Collection lexRangeHead(String toElement, boolean toInclusive, int offset, int count); + Collection range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count); + + /** + * Use {@link RLexSortedSet#range(String, boolean, String, boolean, int, int)} + */ + @Deprecated Collection lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count); + int count(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSet#count(String, boolean, String, boolean)} + */ + @Deprecated int lexCount(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); int rank(String o); + Collection range(int startIndex, int endIndex); + + /** + * Use {@link RLexSortedSet#range(int, int)} + */ + @Deprecated Collection valueRange(int startIndex, int endIndex); } diff --git a/src/main/java/org/redisson/core/RLexSortedSetAsync.java b/src/main/java/org/redisson/core/RLexSortedSetAsync.java index abdb18cb4..6616e25e5 100644 --- a/src/main/java/org/redisson/core/RLexSortedSetAsync.java +++ b/src/main/java/org/redisson/core/RLexSortedSetAsync.java @@ -21,32 +21,110 @@ import io.netty.util.concurrent.Future; public interface RLexSortedSetAsync extends RCollectionAsync { + Future removeRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSetAsync#removeRangeAsync(String, boolean, String, boolean)} + */ + @Deprecated Future removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + Future removeRangeTailAsync(String fromElement, boolean fromInclusive); + + /** + * Use {@link RLexSortedSetAsync#removeRangeTailAsync(String, boolean, String, boolean)} + */ + @Deprecated Future removeRangeTailByLexAsync(String fromElement, boolean fromInclusive); + Future removeRangeHeadAsync(String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSetAsync#removeRangeHeadAsync(String, boolean)} + */ + @Deprecated Future removeRangeHeadByLexAsync(String toElement, boolean toInclusive); + Future countTailAsync(String fromElement, boolean fromInclusive); + + /** + * Use {@link RLexSortedSetAsync#countTailAsync(String, boolean)} + */ + @Deprecated Future lexCountTailAsync(String fromElement, boolean fromInclusive); + Future countHeadAsync(String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSetAsync#countHeadAsync(String, boolean)} + */ + @Deprecated Future lexCountHeadAsync(String toElement, boolean toInclusive); + Future> rangeTailAsync(String fromElement, boolean fromInclusive); + + /** + * Use {@link RLexSortedSetAsync#rangeTailAsync(String, boolean)} + */ + @Deprecated Future> lexRangeTailAsync(String fromElement, boolean fromInclusive); + Future> rangeHeadAsync(String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSetAsync#rangeHeadAsync(String, boolean)} + */ + @Deprecated Future> lexRangeHeadAsync(String toElement, boolean toInclusive); + Future> rangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSetAsync#rangeAsync(String, boolean, String, boolean)} + */ + @Deprecated Future> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + Future> rangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count); + + /** + * Use {@link RLexSortedSetAsync#rangeTailAsync(String, boolean, int, int)} + */ + @Deprecated Future> lexRangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count); + Future> rangeHeadAsync(String toElement, boolean toInclusive, int offset, int count); + + /** + * Use {@link RLexSortedSetAsync#rangeHeadAsync(String, boolean, int, int)} + */ + @Deprecated Future> lexRangeHeadAsync(String toElement, boolean toInclusive, int offset, int count); + Future> rangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count); + + /** + * Use {@link RLexSortedSetAsync#rangeAsync(String, boolean, String, boolean, int, int)} + */ + @Deprecated Future> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count); + Future countAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); + + /** + * Use {@link RLexSortedSetAsync#countAsync(String, boolean, String, boolean)} + */ + @Deprecated Future lexCountAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); Future rankAsync(String o); + Future> rangeAsync(int startIndex, int endIndex); + + /** + * Use {@link RLexSortedSetAsync#rangeAsync(int, int)} + */ + @Deprecated Future> valueRangeAsync(int startIndex, int endIndex); } 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/main/java/org/redisson/core/RMultimapCache.java b/src/main/java/org/redisson/core/RMultimapCache.java new file mode 100644 index 000000000..7fc06a11a --- /dev/null +++ b/src/main/java/org/redisson/core/RMultimapCache.java @@ -0,0 +1,33 @@ +/** + * 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 { + + /** + * 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 new file mode 100644 index 000000000..e910a7405 --- /dev/null +++ b/src/main/java/org/redisson/core/RMultimapCacheAsync.java @@ -0,0 +1,35 @@ +/** + * 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 { + + /** + * 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); + +} 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..f5449b664 --- /dev/null +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -0,0 +1,115 @@ +/** + * 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; + +/** + * Allows to execute object methods remotely between Redisson instances (Server side and Client side instances in terms of remote invocation). + *

+ * 1. Server side instance (worker instance). Register object with RRemoteService instance. + *

+ * + * RRemoteService remoteService = redisson.getRemoteService();
+ *
+ * // register remote service before any remote invocation
+ * remoteService.register(SomeServiceInterface.class, someServiceImpl); + *
+ *

+ * 2. Client side instance. Invokes method remotely. + *

+ * + * RRemoteService remoteService = redisson.getRemoteService();
+ * SomeServiceInterface service = remoteService.get(SomeServiceInterface.class);
+ *
+ * String result = service.doSomeStuff(1L, "secondParam", new AnyParam()); + *
+ *

+ *

+ * There are two timeouts during execution: + *

+ * Acknowledge (Ack) timeout.Client side instance waits for acknowledge message from Server side instance. + *

+ * If acknowledge has not been received by Client side instance then RemoteServiceAckTimeoutException will be thrown. + * And next invocation attempt can be made. + *

+ * If acknowledge has not been received Client side instance but Server side instance has received invocation message already. + * In this case invocation will be skipped, due to ack timeout checking by Server side instance. + *

+ * Execution timeout. Client side instance received acknowledge message. If it hasn't received any result or error + * from server side during execution timeout then RemoteServiceTimeoutException will be thrown. + * + * @author Nikita Koksharov + * + */ +public interface RRemoteService { + + /** + * 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. + *

+ * Ack timeout = 1000 ms by default + *

+ * Execution timeout = 30 sec by default + * + * @param remoteInterface + * @return + */ + T get(Class remoteInterface); + + /** + * Get remote service object for remote invocations + * with specified invocation timeout. + *

+ * Ack timeout = 1000 ms by default + * + * @param remoteInterface + * @param executionTimeout - invocation timeout + * @param executionTimeUnit + * @return + */ + T get(Class remoteInterface, long executionTimeout, TimeUnit executionTimeUnit); + + /** + * Get remote service object for remote invocations + * with specified invocation and ack timeouts + * + * @param remoteInterface + * @param executionTimeout - invocation timeout + * @param executionTimeUnit + * @param ackTimeout - ack timeout + * @param ackTimeUnit + * @return + */ + T get(Class remoteInterface, long executionTimeout, TimeUnit executionTimeUnit, long ackTimeout, TimeUnit ackTimeUnit); + +} 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/main/java/org/redisson/core/RedissonMultiLock.java b/src/main/java/org/redisson/core/RedissonMultiLock.java index 3d5633aac..83ee1a785 100644 --- a/src/main/java/org/redisson/core/RedissonMultiLock.java +++ b/src/main/java/org/redisson/core/RedissonMultiLock.java @@ -20,19 +20,16 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; -import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; -import io.netty.util.internal.PlatformDependent; /** * Groups multiple independent locks and handles them as one lock. @@ -75,52 +72,16 @@ public class RedissonMultiLock implements Lock { } public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference result = new AtomicReference(); - Promise promise = new DefaultPromise() { - public Promise setSuccess(Void result) { - latch.countDown(); - return this; - }; - - public Promise setFailure(Throwable cause) { - result.set(cause); - latch.countDown(); - return this; - }; - }; + Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); lock(promise, 0, leaseTime, unit); - latch.await(); - if (result.get() instanceof Throwable) { - PlatformDependent.throwException((Throwable)result.get()); - } + promise.sync(); } @Override public void lockInterruptibly() throws InterruptedException { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference result = new AtomicReference(); - Promise promise = new DefaultPromise() { - public Promise setSuccess(Void result) { - latch.countDown(); - return this; - }; - - public Promise setFailure(Throwable cause) { - result.set(cause); - latch.countDown(); - return this; - }; - }; - - lock(promise, 0, -1, null); - - latch.await(); - if (result.get() instanceof Throwable) { - PlatformDependent.throwException((Throwable)result.get()); - } + lockInterruptibly(-1, null); } private void lock(final Promise promise, final long waitTime, final long leaseTime, final TimeUnit unit) throws InterruptedException { diff --git a/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java b/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java index 1242ce571..e737cd711 100644 --- a/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java +++ b/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java @@ -83,9 +83,12 @@ public class InfinitySemaphoreLatch extends AbstractQueuedSynchronizer { return closed; } + public void close() { + closed = true; + } + // waiting for an open state - public final boolean closeAndAwaitUninterruptibly() { - closed = true; + public final boolean awaitUninterruptibly() { try { return await(15, TimeUnit.SECONDS); } catch (InterruptedException e) { diff --git a/src/main/java/org/redisson/reactive/PublisherAdder.java b/src/main/java/org/redisson/reactive/PublisherAdder.java index 2d2ec7c80..a7ca9b907 100644 --- a/src/main/java/org/redisson/reactive/PublisherAdder.java +++ b/src/main/java/org/redisson/reactive/PublisherAdder.java @@ -15,6 +15,8 @@ */ package org.redisson.reactive; +import java.util.concurrent.atomic.AtomicLong; + import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; import org.redisson.api.RCollectionReactive; @@ -40,9 +42,10 @@ public class PublisherAdder { c.subscribe(new DefaultSubscriber() { + volatile boolean completed; + AtomicLong values = new AtomicLong(); Subscription s; Long lastSize = 0L; - V lastValue; @Override public void onSubscribe(Subscription s) { @@ -52,7 +55,7 @@ public class PublisherAdder { @Override public void onNext(V o) { - lastValue = o; + values.getAndIncrement(); destination.add(o).subscribe(new DefaultSubscriber() { @Override @@ -68,19 +71,18 @@ public class PublisherAdder { @Override public void onNext(Long o) { lastSize = sum(lastSize, o); - } - - @Override - public void onComplete() { - lastValue = null; s.request(1); + if (values.decrementAndGet() == 0 && completed) { + promise.onNext(lastSize); + } } }); } @Override public void onComplete() { - if (lastValue == null) { + completed = true; + if (values.get() == 0) { promise.onNext(lastSize); } } 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/main/java/org/redisson/reactive/SetReactiveIterator.java b/src/main/java/org/redisson/reactive/SetReactiveIterator.java index b4855f1b3..a5d78573d 100644 --- a/src/main/java/org/redisson/reactive/SetReactiveIterator.java +++ b/src/main/java/org/redisson/reactive/SetReactiveIterator.java @@ -16,7 +16,6 @@ package org.redisson.reactive; import java.net.InetSocketAddress; -import java.util.ArrayList; import java.util.List; import org.reactivestreams.Publisher; @@ -24,56 +23,36 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.redisson.client.protocol.decoder.ListScanResult; -import reactor.core.reactivestreams.SubscriberBarrier; import reactor.rx.Stream; +import reactor.rx.subscription.ReactiveSubscription; public abstract class SetReactiveIterator extends Stream { @Override public void subscribe(final Subscriber t) { - t.onSubscribe(new SubscriberBarrier(t) { + t.onSubscribe(new ReactiveSubscription(this, t) { private List firstValues; private long nextIterPos; private InetSocketAddress client; private long currentIndex; - private List prevValues = new ArrayList(); @Override - protected void doRequest(long n) { + protected void onRequest(long n) { currentIndex = n; - if (!prevValues.isEmpty()) { - List vals = new ArrayList(prevValues); - prevValues.clear(); - - handle(vals); - - if (currentIndex == 0) { - return; - } - } - nextValues(); } private void handle(List vals) { for (V val : vals) { - if (currentIndex > 0) { - onNext(val); - } else { - prevValues.add(val); - } - currentIndex--; - if (currentIndex == 0) { - onComplete(); - } + onNext(val); } } protected void nextValues() { - final SubscriberBarrier m = this; + final ReactiveSubscription m = this; scanIteratorReactive(client, nextIterPos).subscribe(new Subscriber>() { @Override diff --git a/src/main/java/org/redisson/remote/RRemoteServiceResponse.java b/src/main/java/org/redisson/remote/RRemoteServiceResponse.java new file mode 100644 index 000000000..67305dd00 --- /dev/null +++ b/src/main/java/org/redisson/remote/RRemoteServiceResponse.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.remote; + +public interface RRemoteServiceResponse { + +} diff --git a/src/main/java/org/redisson/remote/RemoteServiceAck.java b/src/main/java/org/redisson/remote/RemoteServiceAck.java new file mode 100644 index 000000000..2d34c4804 --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceAck.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.remote; + +/** + * Worker sends this message when it has received a {@link RemoteServiceRequest}. + * + * @author Nikita Koksharov + * + */ +public class RemoteServiceAck implements RRemoteServiceResponse { + +} diff --git a/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java b/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java new file mode 100644 index 000000000..566fc61b9 --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.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.remote; + +/** + * Rises when remote method executor has not answered + * within Ack timeout. + *

+ * Method invocation has not been started in this case. + * So a new invocation attempt can be made. + * + * @author Nikita Koksharov + * + */ +public class RemoteServiceAckTimeoutException extends RuntimeException { + + private static final long serialVersionUID = 1820133675653636587L; + + public RemoteServiceAckTimeoutException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/redisson/remote/RemoteServiceKey.java b/src/main/java/org/redisson/remote/RemoteServiceKey.java new file mode 100644 index 000000000..20f7ea9ff --- /dev/null +++ b/src/main/java/org/redisson/remote/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.remote; + +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/remote/RemoteServiceMethod.java b/src/main/java/org/redisson/remote/RemoteServiceMethod.java new file mode 100644 index 000000000..26998214f --- /dev/null +++ b/src/main/java/org/redisson/remote/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.remote; + +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/remote/RemoteServiceRequest.java b/src/main/java/org/redisson/remote/RemoteServiceRequest.java new file mode 100644 index 000000000..3980ac4f0 --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceRequest.java @@ -0,0 +1,73 @@ +/** + * 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.remote; + +import java.util.Arrays; + +public class RemoteServiceRequest { + + private String requestId; + private String methodName; + private Object[] args; + private long ackTimeout; + private long responseTimeout; + private long date; + + + public RemoteServiceRequest() { + } + + public RemoteServiceRequest(String requestId, String methodName, Object[] args, long ackTimeout, long responseTimeout, long date) { + super(); + this.requestId = requestId; + this.methodName = methodName; + this.args = args; + this.ackTimeout = ackTimeout; + this.responseTimeout = responseTimeout; + this.date = date; + } + + public long getResponseTimeout() { + return responseTimeout; + } + + public long getDate() { + return date; + } + + public long getAckTimeout() { + return ackTimeout; + } + + public String getRequestId() { + return requestId; + } + + public Object[] getArgs() { + return args; + } + + public String getMethodName() { + return methodName; + } + + @Override + public String toString() { + return "RemoteServiceRequest [requestId=" + requestId + ", methodName=" + methodName + ", args=" + + Arrays.toString(args) + ", ackTimeout=" + ackTimeout + ", date=" + date + "]"; + } + +} diff --git a/src/main/java/org/redisson/remote/RemoteServiceResponse.java b/src/main/java/org/redisson/remote/RemoteServiceResponse.java new file mode 100644 index 000000000..55d074870 --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceResponse.java @@ -0,0 +1,47 @@ +/** + * 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.remote; + +public class RemoteServiceResponse implements RRemoteServiceResponse { + + 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; + } + + @Override + public String toString() { + return "RemoteServiceResponse [result=" + result + ", error=" + error + "]"; + } + +} diff --git a/src/main/java/org/redisson/connection/FastSuccessFuture.java b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java similarity index 60% rename from src/main/java/org/redisson/connection/FastSuccessFuture.java rename to src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java index 08eab015b..b5d5720c0 100644 --- a/src/main/java/org/redisson/connection/FastSuccessFuture.java +++ b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java @@ -13,35 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.redisson.connection; +package org.redisson.remote; /** - * + * Rises when invocation timeout has been occurred + * * @author Nikita Koksharov * - * @param */ -public class FastSuccessFuture extends FastCompleteFuture { - - private final V result; - - public FastSuccessFuture(V result) { - this.result = result; - } +public class RemoteServiceTimeoutException extends RuntimeException { - @Override - public Throwable cause() { - return null; - } - - @Override - public boolean isSuccess() { - return true; - } + private static final long serialVersionUID = -1749266931994840256L; - @Override - public V getNow() { - return result; + public RemoteServiceTimeoutException(String message) { + super(message); } - + } 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(); } } diff --git a/src/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index d28a5d3da..2542ab5ac 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -1,29 +1,139 @@ 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.assertThat; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; +import org.redisson.RedisRunner.RedisProcess; import org.redisson.core.RBlockingQueue; +import io.netty.util.concurrent.Future; + public class RedissonBlockingQueueTest extends BaseTest { + @Test + public void testPollWithBrokenConnection() throws IOException, InterruptedException, ExecutionException { + RedisProcess runner = new RedisRunner().port(6319).run(); + + Config config = new Config(); + config.useSingleServer().setAddress("127.0.0.1:6319"); + RedissonClient redisson = Redisson.create(config); + final RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollTimeout"); + Future f = queue1.pollAsync(5, TimeUnit.SECONDS); + + f.await(1, TimeUnit.SECONDS); + runner.stop(); + + assertThat(f.get()).isNull(); + } + + @Test + public void testPollReattach() throws InterruptedException, IOException, ExecutionException, TimeoutException { + RedisProcess runner = new RedisRunner().port(6319).run(); + + Config config = new Config(); + config.useSingleServer().setAddress("127.0.0.1:6319"); + RedissonClient redisson = Redisson.create(config); + + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + Future f = queue1.pollAsync(10, TimeUnit.SECONDS); + f.await(1, TimeUnit.SECONDS); + runner.stop(); + + runner = new RedisRunner().port(6319).run(); + queue1.put(123); + + // check connection rotation + for (int i = 0; i < 10; i++) { + queue1.put(i); + } + assertThat(queue1.size()).isEqualTo(10); + + Integer result = f.get(1, TimeUnit.SECONDS); + assertThat(result).isEqualTo(123); + runner.stop(); + } + + + @Test + public void testTakeReattach() throws InterruptedException, IOException, ExecutionException, TimeoutException { + RedisProcess runner = new RedisRunner().port(6319).run(); + + Config config = new Config(); + config.useSingleServer().setAddress("127.0.0.1:6319"); + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("testTakeReattach"); + Future f = queue1.takeAsync(); + f.await(1, TimeUnit.SECONDS); + runner.stop(); + + runner = new RedisRunner().port(6319).run(); + queue1.put(123); + + // check connection rotation + for (int i = 0; i < 10; i++) { + queue1.put(i); + } + assertThat(queue1.size()).isEqualTo(10); + + Integer result = f.get(1, TimeUnit.SECONDS); + assertThat(result).isEqualTo(123); + runner.stop(); + } + + @Test + public void testTakeAsyncCancel() { + Config config = createConfig(); + config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); + + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("testTakeAsyncCancel"); + 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 +335,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(); } diff --git a/src/test/java/org/redisson/RedissonBucketTest.java b/src/test/java/org/redisson/RedissonBucketTest.java index 7ed1bba45..0688f55b3 100755 --- a/src/test/java/org/redisson/RedissonBucketTest.java +++ b/src/test/java/org/redisson/RedissonBucketTest.java @@ -59,40 +59,11 @@ public class RedissonBucketTest extends BaseTest { assertThat(r1.trySet("4", 500, TimeUnit.MILLISECONDS)).isFalse(); assertThat(r1.get()).isEqualTo("3"); - Thread.sleep(500); + Thread.sleep(1000); assertThat(r1.get()).isNull(); } - @Test - public void testSaveBuckets() { - Map buckets = new HashMap(); - buckets.put("12", 1); - buckets.put("41", 2); - redisson.saveBuckets(buckets); - - RBucket r1 = redisson.getBucket("12"); - assertThat(r1.get()).isEqualTo(1); - - RBucket r2 = redisson.getBucket("41"); - assertThat(r2.get()).isEqualTo(2); - } - - @Test - public void testLoadBucketValues() { - RBucket bucket1 = redisson.getBucket("test1"); - bucket1.set("someValue1"); - RBucket bucket3 = redisson.getBucket("test3"); - bucket3.set("someValue3"); - - Map result = redisson.loadBucketValues("test1", "test2", "test3", "test4"); - Map expected = new HashMap(); - expected.put("test1", "someValue1"); - expected.put("test3", "someValue3"); - - Assert.assertEquals(expected, result); - } - @Test public void testExpire() throws InterruptedException { RBucket bucket = redisson.getBucket("test1"); @@ -175,20 +146,4 @@ public class RedissonBucketTest extends BaseTest { Assert.assertFalse(bucket.isExists()); } - @Test - public void testGetPattern() { - Collection names = Arrays.asList("test:testGetPattern:one", "test:testGetPattern:two"); - Collection vals = Arrays.asList("one-val", "two-val"); - redisson.getBucket("test:testGetPattern:one").set("one-val"); - redisson.getBucket("test:testGetPattern:two").set("two-val"); - List> buckets = redisson.getBuckets("test:testGetPattern:*"); - Assert.assertEquals(2, buckets.size()); - Assert.assertTrue(names.contains(buckets.get(0).getName())); - Assert.assertTrue(names.contains(buckets.get(1).getName())); - Assert.assertTrue(vals.contains(buckets.get(0).get())); - Assert.assertTrue(vals.contains(buckets.get(1).get())); - for (RBucket bucket : buckets) { - bucket.delete(); - } - } } diff --git a/src/test/java/org/redisson/RedissonBucketsTest.java b/src/test/java/org/redisson/RedissonBucketsTest.java new file mode 100644 index 000000000..02d8a29da --- /dev/null +++ b/src/test/java/org/redisson/RedissonBucketsTest.java @@ -0,0 +1,91 @@ +package org.redisson; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Assert; +import org.junit.Test; +import org.redisson.core.RBucket; + +public class RedissonBucketsTest extends BaseTest { + + @Test + public void testGet() { + RBucket bucket1 = redisson.getBucket("test1"); + bucket1.set("someValue1"); + RBucket bucket3 = redisson.getBucket("test3"); + bucket3.set("someValue3"); + + Map result = redisson.getBuckets().get("test1", "test2", "test3", "test4"); + Map expected = new HashMap(); + expected.put("test1", "someValue1"); + expected.put("test3", "someValue3"); + + Assert.assertEquals(expected, result); + } + + @Test + public void testFind() { + Collection names = Arrays.asList("test:testGetPattern:one", "test:testGetPattern:two"); + Collection vals = Arrays.asList("one-val", "two-val"); + + redisson.getBucket("test:testGetPattern:one").set("one-val"); + redisson.getBucket("test:testGetPattern:two").set("two-val"); + + List> buckets = redisson.getBuckets().find("test:testGetPattern:*"); + Assert.assertEquals(2, buckets.size()); + Assert.assertTrue(names.contains(buckets.get(0).getName())); + Assert.assertTrue(names.contains(buckets.get(1).getName())); + Assert.assertTrue(vals.contains(buckets.get(0).get())); + Assert.assertTrue(vals.contains(buckets.get(1).get())); + for (RBucket bucket : buckets) { + bucket.delete(); + } + } + + + @Test + public void testSet() { + Map buckets = new HashMap(); + buckets.put("12", 1); + buckets.put("41", 2); + redisson.getBuckets().set(buckets); + + RBucket r1 = redisson.getBucket("12"); + assertThat(r1.get()).isEqualTo(1); + + RBucket r2 = redisson.getBucket("41"); + assertThat(r2.get()).isEqualTo(2); + } + + @Test + public void testTrySet() { + redisson.getBucket("12").set("341"); + + Map buckets = new HashMap(); + buckets.put("12", 1); + buckets.put("41", 2); + assertThat(redisson.getBuckets().trySet(buckets)).isFalse(); + + RBucket r2 = redisson.getBucket("41"); + assertThat(r2.get()).isNull(); + + Map buckets2 = new HashMap(); + buckets2.put("61", 1); + buckets2.put("41", 2); + assertThat(redisson.getBuckets().trySet(buckets2)).isTrue(); + + RBucket r1 = redisson.getBucket("61"); + assertThat(r1.get()).isEqualTo(1); + + RBucket r3 = redisson.getBucket("41"); + assertThat(r3.get()).isEqualTo(2); + } + + +} diff --git a/src/test/java/org/redisson/RedissonConcurrentMapTest.java b/src/test/java/org/redisson/RedissonConcurrentMapTest.java index ed3903744..adaa1f142 100644 --- a/src/test/java/org/redisson/RedissonConcurrentMapTest.java +++ b/src/test/java/org/redisson/RedissonConcurrentMapTest.java @@ -94,7 +94,7 @@ public class RedissonConcurrentMapTest extends BaseConcurrentTest { } assertMapSize(5, name); - redisson.flushdb(); + redisson.getKeys().flushdb(); redisson.shutdown(); } diff --git a/src/test/java/org/redisson/RedissonDequeTest.java b/src/test/java/org/redisson/RedissonDequeTest.java index 9ccb56362..784810abf 100644 --- a/src/test/java/org/redisson/RedissonDequeTest.java +++ b/src/test/java/org/redisson/RedissonDequeTest.java @@ -1,12 +1,11 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; -import java.util.Iterator; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RDeque; @@ -23,7 +22,7 @@ public class RedissonDequeTest extends BaseTest { queue1.removeLastOccurrence(3); - MatcherAssert.assertThat(queue1, Matchers.containsInAnyOrder(3, 2, 1)); + assertThat(queue1).containsExactly(3, 2, 1); } @Test @@ -36,7 +35,7 @@ public class RedissonDequeTest extends BaseTest { queue1.removeFirstOccurrence(3); - MatcherAssert.assertThat(queue1, Matchers.containsInAnyOrder(2, 1, 3)); + assertThat(queue1).containsExactly(2, 1, 3); } @Test @@ -86,7 +85,7 @@ public class RedissonDequeTest extends BaseTest { queue2.addFirst(4); queue1.pollLastAndOfferFirstTo(queue2); - MatcherAssert.assertThat(queue2, Matchers.contains(3, 4, 5, 6)); + assertThat(queue2).containsExactly(3, 4, 5, 6); } @Test @@ -96,8 +95,8 @@ public class RedissonDequeTest extends BaseTest { queue.addFirst(2); queue.addFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); - } + assertThat(queue).containsExactly(3, 2, 1); + } @Test public void testAddFirst() { @@ -106,7 +105,7 @@ public class RedissonDequeTest extends BaseTest { queue.addFirst(2); queue.addFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); + assertThat(queue).containsExactly(3, 2, 1); } @Test @@ -116,7 +115,7 @@ public class RedissonDequeTest extends BaseTest { queue.addLast(2); queue.addLast(3); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3)); + assertThat(queue).containsExactly(1, 2, 3); } @Test @@ -126,7 +125,7 @@ public class RedissonDequeTest extends BaseTest { queue.addLast(2); queue.addLast(3); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3)); + assertThat(queue).containsExactly(1, 2, 3); } @Test @@ -136,7 +135,7 @@ public class RedissonDequeTest extends BaseTest { queue.offerFirst(2); queue.offerFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); + assertThat(queue).containsExactly(3, 2, 1); } @Test @@ -146,7 +145,7 @@ public class RedissonDequeTest extends BaseTest { queue.offerFirst(2); queue.offerFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); + assertThat(queue).containsExactly(3, 2, 1); } @Test @@ -156,7 +155,7 @@ public class RedissonDequeTest extends BaseTest { queue.offerLast(2); queue.offerLast(3); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3)); + assertThat(queue).containsExactly(1, 2, 3); Assert.assertEquals((Integer)1, queue.poll()); } @@ -166,14 +165,7 @@ public class RedissonDequeTest extends BaseTest { final Deque queue = new ArrayDeque(); queue.addAll(Arrays.asList(1, 2, 3)); - MatcherAssert.assertThat(new Iterable() { - - @Override - public Iterator iterator() { - return queue.descendingIterator(); - } - - }, Matchers.contains(3, 2, 1)); + assertThat(queue.descendingIterator()).containsExactly(3, 2, 1); } @Test @@ -181,14 +173,7 @@ public class RedissonDequeTest extends BaseTest { final RDeque queue = redisson.getDeque("deque"); queue.addAll(Arrays.asList(1, 2, 3)); - MatcherAssert.assertThat(new Iterable() { - - @Override - public Iterator iterator() { - return queue.descendingIterator(); - } - - }, Matchers.contains(3, 2, 1)); + assertThat(queue.descendingIterator()).containsExactly(3, 2, 1); } } diff --git a/src/test/java/org/redisson/RedissonGeoTest.java b/src/test/java/org/redisson/RedissonGeoTest.java new file mode 100644 index 000000000..fc689e006 --- /dev/null +++ b/src/test/java/org/redisson/RedissonGeoTest.java @@ -0,0 +1,183 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; +import org.redisson.core.GeoEntry; +import org.redisson.core.GeoPosition; +import org.redisson.core.GeoUnit; +import org.redisson.core.RGeo; + +public class RedissonGeoTest extends BaseTest { + + @Test + public void testAdd() { + RGeo geo = redisson.getGeo("test"); + assertThat(geo.add(2.51, 3.12, "city1")).isEqualTo(1); + } + + @Test + public void testAddEntries() { + RGeo geo = redisson.getGeo("test"); + assertThat(geo.add(new GeoEntry(3.11, 9.10321, "city1"), new GeoEntry(81.1231, 38.65478, "city2"))).isEqualTo(2); + } + + @Test + public void testDist() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + assertThat(geo.dist("Palermo", "Catania", GeoUnit.METERS)).isEqualTo(166274.15156960033D); + } + + @Test + public void testDistEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.dist("Palermo", "Catania", GeoUnit.METERS)).isNull(); + } + + @Test + public void testHash() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new LinkedHashMap(); + expected.put("Palermo", "sqc8b49rny0"); + expected.put("Catania", "sqdtr74hyu0"); + assertThat(geo.hash("Palermo", "Catania")).isEqualTo(expected); + } + + @Test + public void testHashEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.hash("Palermo", "Catania")).isEmpty(); + } + + + @Test + public void testPos() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new LinkedHashMap(); + expected.put("Palermo", new GeoPosition(13.361389338970184, 38.115556395496299)); + expected.put("Catania", new GeoPosition(15.087267458438873, 37.50266842333162)); + assertThat(geo.pos("test2", "Palermo", "test3", "Catania", "test1")).isEqualTo(expected); + } + + @Test + public void testPosEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.pos("test2", "Palermo", "test3", "Catania", "test1")).isEmpty(); + } + + @Test + public void testRadius() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + assertThat(geo.radius(15, 37, 200, GeoUnit.KILOMETERS)).containsExactly("Palermo", "Catania"); + } + + @Test + public void testRadiusEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radius(15, 37, 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusWithDistance() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new HashMap(); + expected.put("Palermo", 190.4424); + expected.put("Catania", 56.4413); + assertThat(geo.radiusWithDistance(15, 37, 200, GeoUnit.KILOMETERS)).isEqualTo(expected); + } + + @Test + public void testRadiusWithDistanceEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithDistance(15, 37, 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusWithPosition() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new HashMap(); + expected.put("Palermo", new GeoPosition(13.361389338970184, 38.115556395496299)); + expected.put("Catania", new GeoPosition(15.087267458438873, 37.50266842333162)); + assertThat(geo.radiusWithPosition(15, 37, 200, GeoUnit.KILOMETERS)).isEqualTo(expected); + } + + @Test + public void testRadiusWithPositionEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithPosition(15, 37, 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusMember() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + assertThat(geo.radius("Palermo", 200, GeoUnit.KILOMETERS)).containsExactly("Palermo", "Catania"); + } + + @Test + public void testRadiusMemberEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radius("Palermo", 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusMemberWithDistance() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new HashMap(); + expected.put("Palermo", 0.0); + expected.put("Catania", 166.2742); + assertThat(geo.radiusWithDistance("Palermo", 200, GeoUnit.KILOMETERS)).isEqualTo(expected); + } + + @Test + public void testRadiusMemberWithDistanceEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithDistance("Palermo", 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusMemberWithPosition() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new HashMap(); + expected.put("Palermo", new GeoPosition(13.361389338970184, 38.115556395496299)); + expected.put("Catania", new GeoPosition(15.087267458438873, 37.50266842333162)); + assertThat(geo.radiusWithPosition("Palermo", 200, GeoUnit.KILOMETERS)).isEqualTo(expected); + } + + @Test + public void testRadiusMemberWithPositionEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithPosition("Palermo", 200, GeoUnit.KILOMETERS)).isEmpty(); + } + +} diff --git a/src/test/java/org/redisson/RedissonKeysTest.java b/src/test/java/org/redisson/RedissonKeysTest.java index 26f22b751..ef6a4d4b9 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,10 +55,10 @@ 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(); + redisson.getKeys().flushdb(); Assert.assertNull(redisson.getKeys().randomKey()); } @@ -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 diff --git a/src/test/java/org/redisson/RedissonLexSortedSetTest.java b/src/test/java/org/redisson/RedissonLexSortedSetTest.java index b048b0176..e6ce62d4c 100644 --- a/src/test/java/org/redisson/RedissonLexSortedSetTest.java +++ b/src/test/java/org/redisson/RedissonLexSortedSetTest.java @@ -1,7 +1,6 @@ package org.redisson; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; +import static org.assertj.core.api.Assertions.*; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RLexSortedSet; @@ -20,12 +19,12 @@ public class RedissonLexSortedSetTest extends BaseTest { Assert.assertTrue(set.add("f")); Assert.assertTrue(set.add("g")); - Assert.assertEquals(0, (int)set.removeRangeTailByLex("z", false)); + Assert.assertEquals(0, (int)set.removeRangeTail("z", false)); - Assert.assertEquals(4, (int)set.removeRangeTailByLex("c", false)); - MatcherAssert.assertThat(set, Matchers.contains("a", "b", "c")); - Assert.assertEquals(1, (int)set.removeRangeTailByLex("c", true)); - MatcherAssert.assertThat(set, Matchers.contains("a", "b")); + Assert.assertEquals(4, (int)set.removeRangeTail("c", false)); + assertThat(set).containsExactly("a", "b", "c"); + Assert.assertEquals(1, (int)set.removeRangeTail("c", true)); + assertThat(set).containsExactly("a", "b"); } @@ -40,10 +39,10 @@ public class RedissonLexSortedSetTest extends BaseTest { set.add("f"); set.add("g"); - Assert.assertEquals(2, (int)set.removeRangeHeadByLex("c", false)); - MatcherAssert.assertThat(set, Matchers.contains("c", "d", "e", "f", "g")); - Assert.assertEquals(1, (int)set.removeRangeHeadByLex("c", true)); - MatcherAssert.assertThat(set, Matchers.contains("d", "e", "f", "g")); + Assert.assertEquals(2, (int)set.removeRangeHead("c", false)); + assertThat(set).containsExactly("c", "d", "e", "f", "g"); + Assert.assertEquals(1, (int)set.removeRangeHead("c", true)); + assertThat(set).containsExactly("d", "e", "f", "g"); } @Test @@ -57,8 +56,8 @@ public class RedissonLexSortedSetTest extends BaseTest { set.add("f"); set.add("g"); - Assert.assertEquals(5, set.removeRangeByLex("aaa", true, "g", false)); - MatcherAssert.assertThat(set, Matchers.contains("a", "g")); + Assert.assertEquals(5, set.removeRange("aaa", true, "g", false)); + assertThat(set).containsExactly("a", "g"); } @@ -74,8 +73,8 @@ public class RedissonLexSortedSetTest extends BaseTest { Assert.assertTrue(set.add("f")); Assert.assertTrue(set.add("g")); - MatcherAssert.assertThat(set.lexRangeTail("c", false), Matchers.contains("d", "e", "f", "g")); - MatcherAssert.assertThat(set.lexRangeTail("c", true), Matchers.contains("c", "d", "e", "f", "g")); + assertThat(set.rangeTail("c", false)).containsExactly("d", "e", "f", "g"); + assertThat(set.rangeTail("c", true)).containsExactly("c", "d", "e", "f", "g"); } @@ -90,8 +89,8 @@ public class RedissonLexSortedSetTest extends BaseTest { set.add("f"); set.add("g"); - MatcherAssert.assertThat(set.lexRangeHead("c", false), Matchers.contains("a", "b")); - MatcherAssert.assertThat(set.lexRangeHead("c", true), Matchers.contains("a", "b", "c")); + assertThat(set.rangeHead("c", false)).containsExactly("a", "b"); + assertThat(set.rangeHead("c", true)).containsExactly("a", "b", "c"); } @@ -106,7 +105,7 @@ public class RedissonLexSortedSetTest extends BaseTest { set.add("f"); set.add("g"); - MatcherAssert.assertThat(set.lexRange("aaa", true, "g", false), Matchers.contains("b", "c", "d", "e", "f")); + assertThat(set.range("aaa", true, "g", false)).containsExactly("b", "c", "d", "e", "f"); } @Test @@ -120,8 +119,8 @@ public class RedissonLexSortedSetTest extends BaseTest { set.add("f"); set.add("g"); - Assert.assertEquals(5, (int)set.lexCount("b", true, "f", true)); - Assert.assertEquals(3, (int)set.lexCount("b", false, "f", false)); + assertThat(set.count("b", true, "f", true)).isEqualTo(5); + assertThat(set.count("b", false, "f", false)).isEqualTo(3); } } 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(); + + } + + +} diff --git a/src/test/java/org/redisson/RedissonListTest.java b/src/test/java/org/redisson/RedissonListTest.java index 9341d939b..173a5dec7 100644 --- a/src/test/java/org/redisson/RedissonListTest.java +++ b/src/test/java/org/redisson/RedissonListTest.java @@ -1,5 +1,7 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -8,17 +10,11 @@ import java.util.LinkedList; import java.util.List; import java.util.ListIterator; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.client.RedisException; import org.redisson.core.RList; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; -import static org.assertj.core.api.Assertions.*; - public class RedissonListTest extends BaseTest { @Test @@ -84,37 +80,7 @@ public class RedissonListTest extends BaseTest { test2.add("foo"); test2.add(0, "bar"); - MatcherAssert.assertThat(test2, Matchers.contains("bar", "foo")); - } - - @Test - public void testAddAllAsync() throws InterruptedException { - final RList list = redisson.getList("list"); - list.addAllAsync(Arrays.asList(1L, 2L, 3L)).addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - list.addAllAsync(Arrays.asList(1L, 24L, 3L)); - } - }).awaitUninterruptibly(); - - Thread.sleep(1000); - - Assert.assertThat(list, Matchers.contains(1L, 2L, 3L, 1L, 24L, 3L)); - } - - @Test - public void testAddAsync() throws InterruptedException { - final RList list = redisson.getList("list"); - list.addAsync(1L).addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - list.addAsync(2L); - } - }).awaitUninterruptibly(); - - Thread.sleep(1000); - - Assert.assertThat(list, Matchers.contains(1L, 2L)); + assertThat(test2).containsExactly("bar", "foo"); } @Test @@ -123,7 +89,7 @@ public class RedissonListTest extends BaseTest { list.add(1L); list.add(2L); - Assert.assertThat(list, Matchers.contains(1L, 2L)); + assertThat(list).containsExactly(1L, 2L); } @Test(expected = IllegalStateException.class) @@ -186,17 +152,17 @@ public class RedissonListTest extends BaseTest { Assert.assertFalse(iterator.hasPrevious()); Assert.assertTrue(1 == iterator.next()); iterator.set(3); - Assert.assertThat(list, Matchers.contains(3, 2, 3, 4)); + assertThat(list).containsExactly(3, 2, 3, 4); Assert.assertTrue(2 == iterator.next()); iterator.add(31); - Assert.assertThat(list, Matchers.contains(3, 2, 31, 3, 4)); + assertThat(list).containsExactly(3, 2, 31, 3, 4); Assert.assertTrue(3 == iterator.next()); Assert.assertTrue(4 == iterator.next()); Assert.assertFalse(iterator.hasNext()); iterator.add(71); - Assert.assertThat(list, Matchers.contains(3, 2, 31, 3, 4, 71)); + assertThat(list).containsExactly(3, 2, 31, 3, 4, 71); iterator.add(8); - Assert.assertThat(list, Matchers.contains(3, 2, 31, 3, 4, 71, 8)); + assertThat(list).containsExactly(3, 2, 31, 3, 4, 71, 8); } @Test @@ -212,17 +178,17 @@ public class RedissonListTest extends BaseTest { Assert.assertFalse(iterator.hasPrevious()); Assert.assertTrue(1 == iterator.next()); iterator.set(3); - Assert.assertThat(list, Matchers.contains(3, 2, 3, 4)); + assertThat(list).containsExactly(3, 2, 3, 4); Assert.assertTrue(2 == iterator.next()); iterator.add(31); - Assert.assertThat(list, Matchers.contains(3, 2, 31, 3, 4)); + assertThat(list).containsExactly(3, 2, 31, 3, 4); Assert.assertTrue(3 == iterator.next()); Assert.assertTrue(4 == iterator.next()); Assert.assertFalse(iterator.hasNext()); iterator.add(71); - Assert.assertThat(list, Matchers.contains(3, 2, 31, 3, 4, 71)); + assertThat(list).containsExactly(3, 2, 31, 3, 4, 71); iterator.add(8); - Assert.assertThat(list, Matchers.contains(3, 2, 31, 3, 4, 71, 8)); + assertThat(list).containsExactly(3, 2, 31, 3, 4, 71, 8); } @Test @@ -606,7 +572,7 @@ public class RedissonListTest extends BaseTest { list.set(4, 6); - Assert.assertThat(list, Matchers.contains(1, 2, 3, 4, 6)); + assertThat(list).containsExactly(1, 2, 3, 4, 6); } @Test(expected = IndexOutOfBoundsException.class) @@ -632,7 +598,7 @@ public class RedissonListTest extends BaseTest { list.set(4, 6); - Assert.assertThat(list, Matchers.contains(1, 2, 3, 4, 6)); + assertThat(list).containsExactly(1, 2, 3, 4, 6); } @@ -661,11 +627,11 @@ public class RedissonListTest extends BaseTest { Assert.assertFalse(list.removeAll(Collections.emptyList())); Assert.assertTrue(list.removeAll(Arrays.asList(3, 2, 10, 6))); - Assert.assertThat(list, Matchers.contains(1, 4, 5)); + assertThat(list).containsExactly(1, 4, 5); Assert.assertTrue(list.removeAll(Arrays.asList(4))); - Assert.assertThat(list, Matchers.contains(1, 5)); + assertThat(list).containsExactly(1, 5); Assert.assertTrue(list.removeAll(Arrays.asList(1, 5, 1, 5))); @@ -683,7 +649,7 @@ public class RedissonListTest extends BaseTest { Assert.assertTrue(list.retainAll(Arrays.asList(3, 2, 10, 6))); - Assert.assertThat(list, Matchers.contains(2, 3)); + assertThat(list).containsExactly(2, 3); Assert.assertEquals(2, list.size()); } @@ -717,7 +683,7 @@ public class RedissonListTest extends BaseTest { list.add(2); Assert.assertFalse(list.retainAll(Arrays.asList(1, 2))); // nothing changed - Assert.assertThat(list, Matchers.contains(1, 2)); + assertThat(list).containsExactly(1, 2); } @Test(expected = RedisException.class) @@ -737,19 +703,19 @@ public class RedissonListTest extends BaseTest { list.addAll(2, Arrays.asList(7, 8, 9)); - Assert.assertThat(list, Matchers.contains(1, 2, 7, 8, 9, 3, 4, 5)); + assertThat(list).containsExactly(1, 2, 7, 8, 9, 3, 4, 5); list.addAll(list.size()-1, Arrays.asList(9, 1, 9)); - Assert.assertThat(list, Matchers.contains(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5)); + assertThat(list).containsExactly(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5); list.addAll(list.size(), Arrays.asList(0, 5)); - Assert.assertThat(list, Matchers.contains(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5)); + assertThat(list).containsExactly(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5); list.addAll(0, Arrays.asList(6, 7)); - Assert.assertThat(list, Matchers.contains(6,7,1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5)); + assertThat(list).containsExactly(6,7,1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5); } @Test @@ -765,15 +731,15 @@ public class RedissonListTest extends BaseTest { list.addAll(list.size()-1, Arrays.asList(9, 1, 9)); - Assert.assertThat(list, Matchers.contains(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5)); + assertThat(list).containsExactly(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5); list.addAll(list.size(), Arrays.asList(0, 5)); - Assert.assertThat(list, Matchers.contains(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5)); + assertThat(list).containsExactly(1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5); list.addAll(0, Arrays.asList(6,7)); - Assert.assertThat(list, Matchers.contains(6,7,1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5)); + assertThat(list).containsExactly(6,7,1, 2, 7, 8, 9, 3, 4, 9, 1, 9, 5, 0, 5); } @@ -790,7 +756,7 @@ public class RedissonListTest extends BaseTest { Assert.assertTrue(list.addAll(Arrays.asList(9, 1, 9))); - Assert.assertThat(list, Matchers.contains(1, 2, 3, 4, 5, 7, 8, 9, 9, 1, 9)); + assertThat(list).containsExactly(1, 2, 3, 4, 5, 7, 8, 9, 9, 1, 9); } @Test @@ -855,7 +821,7 @@ public class RedissonListTest extends BaseTest { } } - Assert.assertThat(list, Matchers.contains("1", "4", "5", "3")); + assertThat(list).containsExactly("1", "4", "5", "3"); int iteration = 0; for (Iterator iterator = list.iterator(); iterator.hasNext();) { @@ -957,13 +923,16 @@ public class RedissonListTest extends BaseTest { list.add("4"); list.add("5"); list.add("6"); - Assert.assertThat(list, Matchers.contains("1", "2", "3", "4", "5", "6")); + + assertThat(list).containsExactly("1", "2", "3", "4", "5", "6"); list.remove("2"); - Assert.assertThat(list, Matchers.contains("1", "3", "4", "5", "6")); + + assertThat(list).containsExactly("1", "3", "4", "5", "6"); list.remove("4"); - Assert.assertThat(list, Matchers.contains("1", "3", "5", "6")); + + assertThat(list).containsExactly("1", "3", "5", "6"); } @Test @@ -974,6 +943,6 @@ public class RedissonListTest extends BaseTest { list.add("3"); list.add("e"); - Assert.assertThat(list, Matchers.contains(1, 2L, "3", "e")); + assertThat(list).containsExactly(1, 2L, "3", "e"); } } diff --git a/src/test/java/org/redisson/RedissonMapCacheTest.java b/src/test/java/org/redisson/RedissonMapCacheTest.java index 725ae166e..865bf7852 100644 --- a/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -6,9 +6,12 @@ import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import org.hamcrest.MatcherAssert; @@ -200,6 +203,47 @@ public class RedissonMapCacheTest extends BaseTest { Assert.assertEquals(0, cache.size()); } + @Test + public void testIteratorRemoveHighVolume() throws InterruptedException { + RMapCache map = redisson.getMapCache("simpleMap"); + for (int i = 0; i < 10000; i++) { + map.put(i, i*10); + } + + int cnt = 0; + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + iterator.remove(); + cnt++; + } + Assert.assertEquals(10000, cnt); + assertThat(map).isEmpty(); + Assert.assertEquals(0, map.size()); + } + + @Test + public void testIteratorRandomRemoveHighVolume() throws InterruptedException { + RMapCache map = redisson.getMapCache("simpleMap"); + for (int i = 0; i < 10000; i++) { + map.put(i, i*10); + } + + int cnt = 0; + int removed = 0; + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (ThreadLocalRandom.current().nextBoolean()) { + iterator.remove(); + removed++; + } + cnt++; + } + Assert.assertEquals(10000, cnt); + assertThat(map.size()).isEqualTo(cnt - removed); + } + @Test public void testClearExpire() throws InterruptedException { RMapCache cache = redisson.getMapCache("simple"); diff --git a/src/test/java/org/redisson/RedissonMapTest.java b/src/test/java/org/redisson/RedissonMapTest.java index 968f0e5d3..0a46a6558 100644 --- a/src/test/java/org/redisson/RedissonMapTest.java +++ b/src/test/java/org/redisson/RedissonMapTest.java @@ -23,6 +23,7 @@ import org.redisson.client.codec.StringCodec; import org.redisson.codec.JsonJacksonCodec; import org.redisson.core.Predicate; import org.redisson.core.RMap; +import org.redisson.core.RSet; import io.netty.util.concurrent.Future; @@ -257,6 +258,26 @@ public class RedissonMapTest extends BaseTest { assertThat(val2).isEqualTo(4); } + @Test + public void testIteratorRemoveHighVolume() throws InterruptedException { + RMap map = redisson.getMap("simpleMap"); + for (int i = 0; i < 10000; i++) { + map.put(i, i*10); + } + + int cnt = 0; + Iterator iterator = map.keySet().iterator(); + while (iterator.hasNext()) { + Integer integer = iterator.next(); + iterator.remove(); + cnt++; + } + Assert.assertEquals(10000, cnt); + assertThat(map).isEmpty(); + Assert.assertEquals(0, map.size()); + } + + @Test public void testIterator() { RMap rMap = redisson.getMap("123"); diff --git a/src/test/java/org/redisson/RedissonQueueTest.java b/src/test/java/org/redisson/RedissonQueueTest.java index cf4df47bb..6834f00e9 100644 --- a/src/test/java/org/redisson/RedissonQueueTest.java +++ b/src/test/java/org/redisson/RedissonQueueTest.java @@ -1,11 +1,11 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Queue; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RQueue; @@ -20,9 +20,9 @@ public class RedissonQueueTest extends BaseTest { queue.add(3); queue.offer(4); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3, 4)); + assertThat(queue).containsExactly(1, 2, 3, 4); Assert.assertEquals((Integer)1, queue.poll()); - MatcherAssert.assertThat(queue, Matchers.contains(2, 3, 4)); + assertThat(queue).containsExactly(2, 3, 4); Assert.assertEquals((Integer)2, queue.element()); } @@ -34,9 +34,9 @@ public class RedissonQueueTest extends BaseTest { queue.add(3); queue.offer(4); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3, 4)); + assertThat(queue).containsExactly(1, 2, 3, 4); Assert.assertEquals((Integer)1, queue.poll()); - MatcherAssert.assertThat(queue, Matchers.contains(2, 3, 4)); + assertThat(queue).containsExactly(2, 3, 4); Assert.assertEquals((Integer)2, queue.element()); } @@ -51,7 +51,7 @@ public class RedissonQueueTest extends BaseTest { queue.remove(); queue.remove(); - MatcherAssert.assertThat(queue, Matchers.contains(3, 4)); + assertThat(queue).containsExactly(3, 4); queue.remove(); queue.remove(); @@ -69,7 +69,7 @@ public class RedissonQueueTest extends BaseTest { queue.remove(); queue.remove(); - MatcherAssert.assertThat(queue, Matchers.contains(3, 4)); + assertThat(queue).containsExactly(3, 4); queue.remove(); queue.remove(); diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java new file mode 100644 index 000000000..5739bc7bc --- /dev/null +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -0,0 +1,108 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; +import org.redisson.remote.RemoteServiceTimeoutException; + +public class RedissonRemoteServiceTest extends BaseTest { + + public interface RemoteInterface { + + void voidMethod(String name, Long param); + + Long resultMethod(Long value); + + void errorMethod() throws IOException; + + void errorMethodWithCause(); + + void timeoutMethod() throws InterruptedException; + + } + + 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); + } + } + + @Override + public void timeoutMethod() throws InterruptedException { + Thread.sleep(2000); + } + + + } + + @Test(expected = RemoteServiceTimeoutException.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() { + 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(); + } + +} diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index 69acd3519..64e4ddc23 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() { @@ -50,43 +59,64 @@ public class RedissonSetCacheTest extends BaseTest { @Test public void testAddExpire() throws InterruptedException, ExecutionException { RSetCache set = redisson.getSetCache("simple3"); - set.add("123", 1, TimeUnit.SECONDS); + assertThat(set.add("123", 500, TimeUnit.MILLISECONDS)).isTrue(); assertThat(set).contains("123"); - Thread.sleep(1000); - + Thread.sleep(500); + + assertThat(set.size()).isEqualTo(1); assertThat(set).doesNotContain("123"); + + assertThat(set.add("123", 1, TimeUnit.SECONDS)).isTrue(); + } @Test public void testAddExpireTwise() throws InterruptedException, ExecutionException { RSetCache set = redisson.getSetCache("simple31"); - set.add("123", 1, TimeUnit.SECONDS); + assertThat(set.add("123", 1, TimeUnit.SECONDS)).isTrue(); Thread.sleep(1000); Assert.assertFalse(set.contains("123")); - set.add("4341", 1, TimeUnit.SECONDS); + assertThat(set.add("4341", 1, TimeUnit.SECONDS)).isTrue(); Thread.sleep(1000); Assert.assertFalse(set.contains("4341")); } + + @Test + public void testAddExpireThenAdd() throws InterruptedException, ExecutionException { + RSetCache set = redisson.getSetCache("simple31"); + assertThat(set.add("123", 500, TimeUnit.MILLISECONDS)).isTrue(); + + Thread.sleep(500); + + assertThat(set.size()).isEqualTo(1); + assertThat(set.contains("123")).isFalse(); + + assertThat(set.add("123")).isTrue(); + Thread.sleep(1000); + + assertThat(set.contains("123")).isTrue(); + } + @Test public void testExpireOverwrite() throws InterruptedException, ExecutionException { RSetCache set = redisson.getSetCache("simple"); - set.add("123", 1, TimeUnit.SECONDS); + assertThat(set.add("123", 1, TimeUnit.SECONDS)).isTrue(); Thread.sleep(800); - set.add("123", 1, TimeUnit.SECONDS); + assertThat(set.add("123", 1, TimeUnit.SECONDS)).isFalse(); - Thread.sleep(800); - Assert.assertTrue(set.contains("123")); + Thread.sleep(100); + assertThat(set.contains("123")).isTrue(); - Thread.sleep(200); + Thread.sleep(100); - Assert.assertFalse(set.contains("123")); + assertThat(set.contains("123")).isFalse(); } @Test @@ -169,14 +199,15 @@ 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); - set.add(i*10, 10, TimeUnit.SECONDS); + set.add(i*10, 15, TimeUnit.SECONDS); } Assert.assertTrue(set.retainAll(Arrays.asList(1, 2))); + Thread.sleep(500); assertThat(set).containsOnly(1, 2); Assert.assertEquals(2, set.size()); } diff --git a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java new file mode 100644 index 000000000..a4da5c6dd --- /dev/null +++ b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java @@ -0,0 +1,190 @@ +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; +import org.redisson.core.RSetMultimap; + +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(1500); + + 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.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(); + + } + + @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(); + } + +} 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(); + } } diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 1cbeff418..75f5519bf 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; @@ -11,37 +12,17 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; +import org.junit.Assume; 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 { @@ -69,6 +50,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); } @@ -86,6 +68,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); } @@ -94,6 +77,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 { @@ -211,7 +202,7 @@ public class RedissonTest { assertThat(c.toJSON()).isEqualTo(t); } - @Test +// @Test public void testCluster() { NodesGroup nodes = redisson.getClusterNodesGroup(); Assert.assertEquals(2, nodes.getNodes().size()); @@ -272,6 +263,7 @@ public class RedissonTest { @Test public void testManyConnections() { + Assume.assumeFalse(Boolean.valueOf(System.getProperty("travisEnv"))); Config redisConfig = new Config(); redisConfig.useSingleServer() .setConnectionMinimumIdleSize(10000) diff --git a/src/test/java/org/redisson/RedissonTopicTest.java b/src/test/java/org/redisson/RedissonTopicTest.java index 5d1d09ef4..dd237b6e1 100644 --- a/src/test/java/org/redisson/RedissonTopicTest.java +++ b/src/test/java/org/redisson/RedissonTopicTest.java @@ -8,6 +8,7 @@ import org.junit.Assert; import org.junit.Test; import org.redisson.core.BaseStatusListener; import org.redisson.core.MessageListener; +import org.redisson.core.RSet; import org.redisson.core.RTopic; public class RedissonTopicTest { @@ -46,6 +47,29 @@ public class RedissonTopicTest { } + @Test + public void testSyncCommands() throws InterruptedException { + RedissonClient redisson = BaseTest.createInstance(); + RTopic topic = redisson.getTopic("system_bus"); + RSet redissonSet = redisson.getSet("set1"); + CountDownLatch latch = new CountDownLatch(1); + topic.addListener(new MessageListener() { + + @Override + public void onMessage(String channel, String msg) { + for (int j = 0; j < 1000; j++) { + redissonSet.contains("" + j); + } + latch.countDown(); + } + }); + + topic.publish("sometext"); + + latch.await(); + redisson.shutdown(); + } + @Test public void testInnerPublish() throws InterruptedException { @@ -105,7 +129,8 @@ public class RedissonTopicTest { }); topic1.removeListener(listenerId); topic1.removeListener(listenerId2); - l.await(); + + Assert.assertTrue(l.await(5, TimeUnit.SECONDS)); } @Test @@ -166,7 +191,7 @@ public class RedissonTopicTest { }); topic2.publish(new Message("123")); - messageRecieved.await(); + Assert.assertTrue(messageRecieved.await(5, TimeUnit.SECONDS)); redisson1.shutdown(); redisson2.shutdown(); @@ -231,7 +256,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 +264,7 @@ public class RedissonTopicTest { Thread.sleep(1000); - Assert.assertEquals(500, counter); + Assert.assertEquals(5000, counter); redisson1.shutdown(); redisson2.shutdown();