From e6892e96c907e818adf8cf256b33effdc2b3b407 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Fri, 18 Mar 2016 21:42:39 +0000 Subject: [PATCH 01/88] added travis config --- .travis.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..55e7e10ba --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +# 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. + +language: java +jdk: + - oraclejdk8 +services: + - redis-server +cache: + directories: + - $HOME/.m2 +script: mvn -DargLine="-DredisBinary=`which redis-server`" -Punit-test -Ptravis clean verify From 94bf56043718cf5f1845ffbbfc64d6483378bb3a Mon Sep 17 00:00:00 2001 From: jackygurui Date: Fri, 18 Mar 2016 22:29:36 +0000 Subject: [PATCH 02/88] use the latest version of redis compiled from source --- .travis.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 55e7e10ba..f685894fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,9 +15,22 @@ language: java jdk: - oraclejdk8 -services: - - redis-server +env: + - REDIS_VERSION=3.0.7 cache: directories: - $HOME/.m2 -script: mvn -DargLine="-DredisBinary=`which redis-server`" -Punit-test -Ptravis clean verify +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" -Punit-test -Ptravis clean verify From 3fea33c76314c64e9eb6389f8d7155ee8dd57fe1 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Fri, 18 Mar 2016 22:31:58 +0000 Subject: [PATCH 03/88] fixed travis sytax issue --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f685894fa..ce9484f40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,13 +22,11 @@ cache: - $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 + - 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 + - $REDIS_BIN/redis-server --daemonize yes - sleep 3 - $REDIS_BIN/redis-cli PING - export REDIS_VERSION="$(redis-cli INFO SERVER | sed -n 2p)" From dbc0149da2c840b6fb1553297515f7a998761d19 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 19 Mar 2016 13:55:56 +0300 Subject: [PATCH 04/88] RSetMultimapCache added. #428 --- src/main/java/org/redisson/Redisson.java | 10 + .../java/org/redisson/RedissonClient.java | 36 +- .../redisson/RedissonSetMultimapCache.java | 216 +++++++++ .../redisson/RedissonSetMultimapValues.java | 446 ++++++++++++++++++ .../org/redisson/core/RMultimapCache.java | 24 + .../redisson/core/RMultimapCacheAsync.java | 26 + .../org/redisson/core/RSetMultimapCache.java | 20 + .../RedissonSetMultimapCacheTest.java | 115 +++++ 8 files changed, 887 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonSetMultimapCache.java create mode 100644 src/main/java/org/redisson/RedissonSetMultimapValues.java create mode 100644 src/main/java/org/redisson/core/RMultimapCache.java create mode 100644 src/main/java/org/redisson/core/RMultimapCacheAsync.java create mode 100644 src/main/java/org/redisson/core/RSetMultimapCache.java create mode 100644 src/test/java/org/redisson/RedissonSetMultimapCacheTest.java diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index f02e14c0d..060192064 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -57,6 +57,7 @@ import org.redisson.core.RListMultimap; import org.redisson.core.RLock; import org.redisson.core.RMap; import org.redisson.core.RMapCache; +import org.redisson.core.RMultimapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; @@ -66,6 +67,7 @@ import org.redisson.core.RSemaphore; import org.redisson.core.RSet; import org.redisson.core.RSetCache; import org.redisson.core.RSetMultimap; +import org.redisson.core.RSetMultimapCache; import org.redisson.core.RSortedSet; import org.redisson.core.RTopic; @@ -265,6 +267,14 @@ public class Redisson implements RedissonClient { public RSetMultimap getSetMultimap(String name) { return new RedissonSetMultimap(commandExecutor, name); } + + public RSetMultimapCache getSetMultimapCache(String name) { + return new RedissonSetMultimapCache(commandExecutor, name); + } + + public RSetMultimapCache getSetMultimapCache(String name, Codec codec) { + return new RedissonSetMultimapCache(codec, commandExecutor, name); + } @Override public RSetMultimap getSetMultimap(String name, Codec codec) { diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index 66fe5b49f..0c1502d4a 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -31,7 +31,6 @@ import org.redisson.core.RBlockingDeque; import org.redisson.core.RBlockingQueue; import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; -import org.redisson.core.RMapCache; import org.redisson.core.RCountDownLatch; import org.redisson.core.RDeque; import org.redisson.core.RHyperLogLog; @@ -41,6 +40,7 @@ import org.redisson.core.RList; import org.redisson.core.RListMultimap; import org.redisson.core.RLock; import org.redisson.core.RMap; +import org.redisson.core.RMapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; @@ -50,6 +50,7 @@ import org.redisson.core.RSemaphore; import org.redisson.core.RSet; import org.redisson.core.RSetCache; import org.redisson.core.RSetMultimap; +import org.redisson.core.RSetMultimapCache; import org.redisson.core.RSortedSet; import org.redisson.core.RTopic; @@ -218,7 +219,7 @@ public interface RedissonClient { RList getList(String name, Codec codec); /** - * Returns List based MultiMap instance by name. + * Returns List based Multimap instance by name. * * @param name * @return @@ -226,7 +227,7 @@ public interface RedissonClient { RListMultimap getListMultimap(String name); /** - * Returns List based MultiMap instance by name + * Returns List based Multimap instance by name * using provided codec for both map keys and values. * * @param name @@ -254,15 +255,15 @@ public interface RedissonClient { RMap getMap(String name, Codec codec); /** - * Returns Set based MultiMap instance by name. + * Returns Set based Multimap instance by name. * * @param name * @return */ RSetMultimap getSetMultimap(String name); - + /** - * Returns Set based MultiMap instance by name + * Returns Set based Multimap instance by name * using provided codec for both map keys and values. * * @param name @@ -271,6 +272,29 @@ public interface RedissonClient { */ RSetMultimap getSetMultimap(String name, Codec codec); + /** + * Returns Set based Multimap instance by name. + * Supports key-entry eviction with a given TTL value. + * + *

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

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

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

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

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

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

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

+ * + * @param name + * @return + */ + RListMultimapCache getListMultimapCache(String name, Codec codec); + /** * Returns map instance by name. * diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java new file mode 100644 index 000000000..62a4800ec --- /dev/null +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -0,0 +1,226 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RListMultimapCache; + +import io.netty.util.concurrent.Future; + +/** + * @author Nikita Koksharov + * + * @param key + * @param value + */ +public class RedissonListMultimapCache extends RedissonListMultimap implements RListMultimapCache { + + private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + + RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + } + + RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + } + + public Future containsKeyAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String valuesName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value ~= false then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('llen', ARGV[3]) > 0 and 1 or 0;" + + "end;" + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), keyState, valuesName); + + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + String getTimeoutSetName() { + return "redisson_list_multimap_ttl{" + getName() + "}"; + } + + + public Future containsValueAsync(Object value) { + try { + byte[] valueState = codec.getMapValueEncoder().encode(value); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local keys = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(keys) do " + + "if i % 2 == 0 then " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], keys[i-1]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[2]) then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + + "local items = redis.call('lrange', name, 0, -1) " + + "for i=1,#items do " + + "if items[i] == ARGV[1] then " + + "return 1; " + + "end; " + + "end; " + + + "end; " + + "end;" + + "end; " + + "return 0; ", + Arrays.asList(getName(), getTimeoutSetName()), valueState, System.currentTimeMillis()); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public Future containsEntryAsync(Object key, Object value) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + byte[] valueState = codec.getMapValueEncoder().encode(value); + + String valuesName = getValuesName(keyHash); + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "local items = redis.call('lrange', KEYS[1], 0, -1); " + + "for i=0, #items do " + + "if items[i] == ARGV[3] then " + + "return 1; " + + "end; " + + "end; " + + "end; " + + "return 0; ", + Arrays.asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState, valueState); + + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public List get(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String valuesName = getValuesName(keyHash); + + return new RedissonListMultimapValues(codec, commandExecutor, valuesName, getTimeoutSetName(), key); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + public Future> getAllAsync(K key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + String valuesName = getValuesName(keyHash); + + return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LIST, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "return redis.call('lrange', KEYS[1], 0, -1); " + + "end; " + + "return {}; ", + Arrays.asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + + } + + public Future> removeAllAsync(Object key) { + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + String keyHash = hash(keyState); + + String valuesName = getValuesName(keyHash); + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET, + "redis.call('hdel', KEYS[1], ARGV[1]); " + + "local members = redis.call('lrange', KEYS[2], 0, -1); " + + "redis.call('del', KEYS[2]); " + + "redis.call('zrem', KEYS[3], ARGV[1]); " + + "return members; ", + Arrays.asList(getName(), valuesName, getTimeoutSetName()), keyState); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public boolean expireKey(K key, long timeToLive, TimeUnit timeUnit) { + return get(expireKeyAsync(key, timeToLive, timeUnit)); + } + + @Override + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_EXPIRE_KEY, + "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " + + "if tonumber(ARGV[1]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "end; " + + "return 1; " + + "else " + + "return 0; " + + "end", + Arrays.asList(getName(), getTimeoutSetName()), ttlTimeout, key); + } + +} diff --git a/src/main/java/org/redisson/RedissonListMultimapValues.java b/src/main/java/org/redisson/RedissonListMultimapValues.java new file mode 100644 index 000000000..1c6ed6d5f --- /dev/null +++ b/src/main/java/org/redisson/RedissonListMultimapValues.java @@ -0,0 +1,688 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import static org.redisson.client.protocol.RedisCommands.EVAL_OBJECT; +import static org.redisson.client.protocol.RedisCommands.LPOP; +import static org.redisson.client.protocol.RedisCommands.LPUSH_BOOLEAN; +import static org.redisson.client.protocol.RedisCommands.RPUSH_BOOLEAN; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanNumberReplayConvertor; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.client.protocol.convertor.Convertor; +import org.redisson.client.protocol.convertor.IntegerReplayConvertor; +import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.core.RList; + +import io.netty.util.concurrent.Future; + +/** + * List based Multimap Cache values holder + * + * @author Nikita Koksharov + * + * @param the type of elements held in this collection + */ +public class RedissonListMultimapValues extends RedissonExpirable implements RList { + + private static final RedisCommand LAST_INDEX = new RedisCommand("EVAL", new IntegerReplayConvertor(), 4, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_SIZE = new RedisCommand("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_GET = new RedisCommand("EVAL", 7, ValueType.MAP_KEY); + private static final RedisCommand> EVAL_READALL = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), 6, ValueType.MAP_KEY); + private static final RedisCommand EVAL_CONTAINS_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); + private static final RedisCommand EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS); + + + public static final RedisCommand EVAL_BOOLEAN_ARGS2 = new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS); + + private final Object key; + private final String timeoutSetName; + + public RedissonListMultimapValues(Codec codec, CommandAsyncExecutor commandExecutor, String name, String timeoutSetName, Object key) { + super(codec, commandExecutor, name); + this.timeoutSetName = timeoutSetName; + this.key = key; + } + + @Override + public int size() { + return get(sizeAsync()); + } + + public Future sizeAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_SIZE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('llen', KEYS[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + return get(containsAsync(o)); + } + + @Override + public Iterator iterator() { + return listIterator(); + } + + @Override + public Object[] toArray() { + List list = readAll(); + return list.toArray(); + } + + @Override + public List readAll() { + return get(readAllAsync()); + } + + @Override + public Future> readAllAsync() { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_READALL, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return {};" + + "end; " + + "return redis.call('lrange', KEYS[2], 0, -1);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key); + } + + @Override + public T[] toArray(T[] a) { + List list = readAll(); + return list.toArray(a); + } + + @Override + public boolean add(V e) { + return get(addAsync(e)); + } + + @Override + public Future addAsync(V e) { + return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, getName(), e); + } + + @Override + public boolean remove(Object o) { + return get(removeAsync(o)); + } + + @Override + public Future removeAsync(Object o) { + return removeAsync(o, 1); + } + + protected Future removeAsync(Object o, int count) { + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "return redis.call('lrem', KEYS[2], ARGV[2], ARGV[4]) > 0 and 1 or 0;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), count, key, o); + } + + protected boolean remove(Object o, int count) { + return get(removeAsync(o, count)); + } + + @Override + public Future containsAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "local items = redis.call('lrange', KEYS[2], 0, -1);" + + "for i = 0, #items, 1 do " + + "for j = 2, table.getn(ARGV), 1 do " + + "if ARGV[j] == items[i] " + + "then table.remove(ARGV, j) end " + + "end; " + + "end;" + + "return table.getn(ARGV) == 2 and 1 or 0; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + + } + + @Override + public boolean containsAll(Collection c) { + return get(containsAllAsync(c)); + } + + @Override + public boolean addAll(Collection c) { + return get(addAllAsync(c)); + } + + @Override + public Future addAllAsync(final Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(false); + } + + List args = new ArrayList(c.size() + 1); + args.add(getName()); + args.addAll(c); + return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, args.toArray()); + } + + public Future addAllAsync(int index, Collection coll) { + if (index < 0) { + throw new IndexOutOfBoundsException("index: " + index); + } + + if (coll.isEmpty()) { + return newSucceededFuture(false); + } + + if (index == 0) { // prepend elements to list + List elements = new ArrayList(coll); + Collections.reverse(elements); + elements.add(0, getName()); + + return commandExecutor.writeAsync(getName(), codec, LPUSH_BOOLEAN, elements.toArray()); + } + + List args = new ArrayList(coll.size() + 1); + args.add(index); + args.addAll(coll); + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_BOOLEAN_ARGS2, + "local ind = table.remove(ARGV, 1); " + // index is the first parameter + "local size = redis.call('llen', KEYS[1]); " + + "assert(tonumber(ind) <= size, 'index: ' .. ind .. ' but current size: ' .. size); " + + "local tail = redis.call('lrange', KEYS[1], ind, -1); " + + "redis.call('ltrim', KEYS[1], 0, ind - 1); " + + "for i=1, #ARGV, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); " + + "end " + + "if #tail > 0 then " + + "for i=1, #tail, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); " + + "end " + + "end;" + + "return 1;", + Collections.singletonList(getName()), args.toArray()); + } + + @Override + public boolean addAll(int index, Collection coll) { + return get(addAllAsync(index, coll)); + } + + @Override + public Future removeAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local v = 0 " + + "for i = 2, #ARGV, 1 do " + + "if redis.call('lrem', KEYS[2], 0, ARGV[i]) == 1 " + + "then v = 1 end " + +"end " + + "return v ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + @Override + public boolean removeAll(Collection c) { + return get(removeAllAsync(c)); + } + + @Override + public boolean retainAll(Collection c) { + return get(retainAllAsync(c)); + } + + @Override + public Future retainAllAsync(Collection c) { + List args = new ArrayList(c.size() + 2); + try { + byte[] keyState = codec.getMapKeyEncoder().encode(key); + args.add(System.currentTimeMillis()); + args.add(keyState); + args.addAll(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + + "local changed = 0; " + + "local s = redis.call('lrange', KEYS[2], 0, -1); " + + "local i = 0; " + + "while i <= #s do " + + "local element = s[i]; " + + "local isInAgrs = false; " + + "for j = 2, #ARGV, 1 do " + + "if ARGV[j] == element then " + + "isInAgrs = true; " + + "break; " + + "end; " + + "end; " + + "if isInAgrs == false then " + + "redis.call('lrem', KEYS[2], 0, element); " + + "changed = 1; " + + "end; " + + "i = i + 1; " + + "end; " + + "return changed; ", + Arrays.asList(timeoutSetName, getName()), args.toArray()); + } + + + @Override + public void clear() { + delete(); + } + + @Override + public Future getAsync(int index) { + return commandExecutor.evalReadAsync(getName(), codec, EVAL_GET, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return nil;" + + "end; " + + "return redis.call('lindex', KEYS[2], ARGV[2]);", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), index, key); + } + + @Override + public V get(int index) { + checkIndex(index); + return getValue(index); + } + + V getValue(int index) { + return get(getAsync(index)); + } + + private void checkIndex(int index) { + int size = size(); + if (!isInRange(index, size)) + throw new IndexOutOfBoundsException("index: " + index + " but current size: "+ size); + } + + private boolean isInRange(int index, int size) { + return index >= 0 && index < size; + } + + @Override + public V set(int index, V element) { + checkIndex(index); + return get(setAsync(index, element)); + } + + @Override + public Future setAsync(int index, V element) { + return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", 5), + "local v = redis.call('lindex', KEYS[1], ARGV[1]); " + + "redis.call('lset', KEYS[1], ARGV[1], ARGV[2]); " + + "return v", + Collections.singletonList(getName()), index, element); + } + + @Override + public void fastSet(int index, V element) { + get(fastSetAsync(index, element)); + } + + @Override + public Future fastSetAsync(int index, V element) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LSET, getName(), index, element); + } + + @Override + public void add(int index, V element) { + addAll(index, Collections.singleton(element)); + } + + @Override + public V remove(int index) { + checkIndex(index); + + if (index == 0) { + Future f = commandExecutor.writeAsync(getName(), codec, LPOP, getName()); + return get(f); + } + + Future f = commandExecutor.evalWriteAsync(getName(), codec, EVAL_OBJECT, + "local v = redis.call('lindex', KEYS[1], ARGV[1]); " + + "local tail = redis.call('lrange', KEYS[1], ARGV[1] + 1, -1);" + + "redis.call('ltrim', KEYS[1], 0, ARGV[1] - 1);" + + "if #tail > 0 then " + + "for i=1, #tail, 5000 do " + + "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); " + + "end " + + "end;" + + "return v", + Collections.singletonList(getName()), index); + return get(f); + } + + @Override + public int indexOf(Object o) { + return get(indexOfAsync(o)); + } + + @Override + public Future containsAsync(Object o) { + return indexOfAsync(o, new BooleanNumberReplayConvertor(-1L)); + } + + private Future indexOfAsync(Object o, Convertor convertor) { + return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", convertor, 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)), + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return -1;" + + "end; " + + + "local items = redis.call('lrange', KEYS[2], 0, -1); " + + "for i=1,#items do " + + "if items[i] == ARGV[3] then " + + "return i - 1; " + + "end; " + + "end; " + + "return -1;", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public Future indexOfAsync(Object o) { + return indexOfAsync(o, new IntegerReplayConvertor()); + } + + @Override + public int lastIndexOf(Object o) { + return get(lastIndexOfAsync(o)); + } + + @Override + public Future lastIndexOfAsync(Object o) { + return commandExecutor.evalReadAsync(getName(), codec, LAST_INDEX, + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore); " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return -1;" + + "end; " + + + "local items = redis.call('lrange', KEYS[1], 0, -1) " + + "for i = #items, 0, -1 do " + + "if items[i] == ARGV[1] then " + + "return i - 1 " + + "end " + + "end " + + "return -1", + Arrays.asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o); + } + + @Override + public void trim(int fromIndex, int toIndex) { + get(trimAsync(fromIndex, toIndex)); + } + + @Override + public Future trimAsync(int fromIndex, int toIndex) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LTRIM, getName(), fromIndex, toIndex); + } + + @Override + public ListIterator listIterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator(final int ind) { + return new ListIterator() { + + private V prevCurrentValue; + private V nextCurrentValue; + private V currentValueHasRead; + private int currentIndex = ind - 1; + private boolean hasBeenModified = true; + + @Override + public boolean hasNext() { + V val = RedissonListMultimapValues.this.getValue(currentIndex+1); + if (val != null) { + nextCurrentValue = val; + } + return val != null; + } + + @Override + public V next() { + if (nextCurrentValue == null && !hasNext()) { + throw new NoSuchElementException("No such element at index " + currentIndex); + } + currentIndex++; + currentValueHasRead = nextCurrentValue; + nextCurrentValue = null; + hasBeenModified = false; + return currentValueHasRead; + } + + @Override + public void remove() { + if (currentValueHasRead == null) { + throw new IllegalStateException("Neither next nor previous have been called"); + } + if (hasBeenModified) { + throw new IllegalStateException("Element been already deleted"); + } + RedissonListMultimapValues.this.remove(currentIndex); + currentIndex--; + hasBeenModified = true; + currentValueHasRead = null; + } + + @Override + public boolean hasPrevious() { + if (currentIndex < 0) { + return false; + } + V val = RedissonListMultimapValues.this.getValue(currentIndex); + if (val != null) { + prevCurrentValue = val; + } + return val != null; + } + + @Override + public V previous() { + if (prevCurrentValue == null && !hasPrevious()) { + throw new NoSuchElementException("No such element at index " + currentIndex); + } + currentIndex--; + hasBeenModified = false; + currentValueHasRead = prevCurrentValue; + prevCurrentValue = null; + return currentValueHasRead; + } + + @Override + public int nextIndex() { + return currentIndex + 1; + } + + @Override + public int previousIndex() { + return currentIndex; + } + + @Override + public void set(V e) { + if (hasBeenModified) { + throw new IllegalStateException(); + } + + RedissonListMultimapValues.this.fastSet(currentIndex, e); + } + + @Override + public void add(V e) { + RedissonListMultimapValues.this.add(currentIndex+1, e); + currentIndex++; + hasBeenModified = true; + } + }; + } + + @Override + public RList subList(int fromIndex, int toIndex) { + int size = size(); + if (fromIndex < 0 || toIndex > size) { + throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " toIndex: " + toIndex + " size: " + size); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex: " + fromIndex + " toIndex: " + toIndex); + } + + return new RedissonSubList(codec, commandExecutor, getName(), fromIndex, toIndex); + } + + public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + V e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof List)) + return false; + + Iterator e1 = iterator(); + Iterator e2 = ((List) o).iterator(); + while (e1.hasNext() && e2.hasNext()) { + V o1 = e1.next(); + Object o2 = e2.next(); + if (!(o1==null ? o2==null : o1.equals(o2))) + return false; + } + return !(e1.hasNext() || e2.hasNext()); + } + + @Override + public int hashCode() { + int hashCode = 1; + for (V e : this) { + hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); + } + return hashCode; + } + +} diff --git a/src/main/java/org/redisson/core/RListMultimapCache.java b/src/main/java/org/redisson/core/RListMultimapCache.java new file mode 100644 index 000000000..01d97baa5 --- /dev/null +++ b/src/main/java/org/redisson/core/RListMultimapCache.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public interface RListMultimapCache extends RListMultimap, RMultimapCache { + +} diff --git a/src/test/java/org/redisson/RedissonListMultimapCacheTest.java b/src/test/java/org/redisson/RedissonListMultimapCacheTest.java new file mode 100644 index 000000000..5f5e500e4 --- /dev/null +++ b/src/test/java/org/redisson/RedissonListMultimapCacheTest.java @@ -0,0 +1,139 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.redisson.core.RMultimapCache; + +public class RedissonListMultimapCacheTest extends BaseTest { + + @Test + public void testContains() { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.containsKey("1")).isTrue(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isTrue(); + assertThat(multimap.containsValue("3")).isTrue(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isTrue(); + assertThat(multimap.containsEntry("1", "3")).isTrue(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testContainsExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.containsKey("1")).isFalse(); + assertThat(multimap.containsKey("2")).isFalse(); + + assertThat(multimap.containsValue("1")).isFalse(); + assertThat(multimap.containsValue("3")).isFalse(); + assertThat(multimap.containsValue("4")).isFalse(); + + assertThat(multimap.containsEntry("1", "1")).isFalse(); + assertThat(multimap.containsEntry("1", "3")).isFalse(); + assertThat(multimap.containsEntry("1", "4")).isFalse(); + } + + @Test + public void testGetAll() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + + assertThat(multimap.getAll("1")).containsOnlyOnce("1", "2", "3"); + } + + @Test + public void testGetAllExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.getAll("1")).isEmpty(); + } + + @Test + public void testValues() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.put("1", "3"); + + assertThat(multimap.get("1").size()).isEqualTo(4); + assertThat(multimap.get("1")).containsExactly("1", "2", "3", "3"); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").remove("3")).isTrue(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").contains("2")).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1", "2"))).isTrue(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isTrue(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isTrue(); + } + + @Test + public void testValuesExpired() throws InterruptedException { + RMultimapCache multimap = redisson.getListMultimapCache("test"); + multimap.put("1", "1"); + multimap.put("1", "2"); + multimap.put("1", "3"); + multimap.expireKey("1", 1, TimeUnit.SECONDS); + + Thread.sleep(1000); + + assertThat(multimap.get("1").size()).isZero(); + assertThat(multimap.get("1")).contains(); + assertThat(multimap.get("1").remove("3")).isFalse(); + assertThat(multimap.get("1").contains("3")).isFalse(); + assertThat(multimap.get("1").retainAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").containsAll(Arrays.asList("1"))).isFalse(); + assertThat(multimap.get("1").removeAll(Arrays.asList("1"))).isFalse(); + } + + @Test + public void testScheduler() throws InterruptedException { + RMultimapCache cache = redisson.getListMultimapCache("simple33"); + assertThat(cache.put("1", "1")).isTrue(); + assertThat(cache.put("1", "2")).isTrue(); + assertThat(cache.put("1", "3")).isTrue(); + assertThat(cache.put("2", "1")).isTrue(); + assertThat(cache.put("2", "2")).isTrue(); + assertThat(cache.put("2", "3")).isTrue(); + + assertThat(cache.expireKey("1", 2, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("2", 3, TimeUnit.SECONDS)).isTrue(); + assertThat(cache.expireKey("3", 3, TimeUnit.SECONDS)).isFalse(); + + assertThat(cache.size()).isEqualTo(6); + + Thread.sleep(10000); + + assertThat(cache.size()).isZero(); + + } + + +} From 3207687300a6d5be6c375f6fca881ea08c0a8746 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 15:03:37 +0300 Subject: [PATCH 10/88] RuntimeExeception replaced with IllegalArgumentException --- .../org/redisson/RedissonListMultimap.java | 20 +++++++++---------- .../org/redisson/RedissonSetMultimap.java | 20 +++++++++---------- .../redisson/RedissonSetMultimapValues.java | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/redisson/RedissonListMultimap.java b/src/main/java/org/redisson/RedissonListMultimap.java index 3eb98f9ea..d2e9db44e 100644 --- a/src/main/java/org/redisson/RedissonListMultimap.java +++ b/src/main/java/org/redisson/RedissonListMultimap.java @@ -74,7 +74,7 @@ public class RedissonListMultimap extends RedissonMultimap implement String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, LLEN_VALUE, setName); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -99,7 +99,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 0; ", Arrays.asList(getName()), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -126,7 +126,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 0; ", Collections.singletonList(setName), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -148,7 +148,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return 1; ", Arrays.asList(getName(), setName), keyState, keyHash, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -167,7 +167,7 @@ public class RedissonListMultimap extends RedissonMultimap implement + "return res; ", Arrays.asList(getName(), setName), keyState, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -189,7 +189,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return redis.call('rpush', KEYS[2], unpack(ARGV, 3, #ARGV)); ", Arrays.asList(getName(), setName), params.toArray()); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -203,7 +203,7 @@ public class RedissonListMultimap extends RedissonMultimap implement return new RedissonList(codec, commandExecutor, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -220,7 +220,7 @@ public class RedissonListMultimap extends RedissonMultimap implement return commandExecutor.readAsync(getName(), codec, RedisCommands.LRANGE, setName, 0, -1); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -242,7 +242,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return members; ", Arrays.asList(getName(), setName), keyState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -272,7 +272,7 @@ public class RedissonListMultimap extends RedissonMultimap implement "return members; ", Arrays.asList(getName(), setName), params.toArray()); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/redisson/RedissonSetMultimap.java b/src/main/java/org/redisson/RedissonSetMultimap.java index 35da45b53..3989bb762 100644 --- a/src/main/java/org/redisson/RedissonSetMultimap.java +++ b/src/main/java/org/redisson/RedissonSetMultimap.java @@ -77,7 +77,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SCARD_VALUE, setName); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -98,7 +98,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return 0; ", Arrays.asList(getName()), valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -111,7 +111,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements String setName = getValuesName(keyHash); return commandExecutor.readAsync(getName(), codec, SISMEMBER_VALUE, setName, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -127,7 +127,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return redis.call('sadd', KEYS[2], ARGV[3]); ", Arrays.asList(getName(), setName), keyState, keyHash, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -146,7 +146,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements + "return res; ", Arrays.asList(getName(), setName), keyState, valueState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -168,7 +168,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); ", Arrays.asList(getName(), setName), params.toArray()); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -181,7 +181,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements return new RedissonSet(codec, commandExecutor, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -198,7 +198,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements return commandExecutor.readAsync(getName(), codec, RedisCommands.SMEMBERS, setName); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -220,7 +220,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return members; ", Arrays.asList(getName(), setName), keyState); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } @@ -268,7 +268,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements "return members; ", Arrays.asList(getName(), setName), params.toArray()); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } diff --git a/src/main/java/org/redisson/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java index be9fab67c..215c82cd4 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapValues.java +++ b/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -42,7 +42,7 @@ import org.redisson.core.RSet; import io.netty.util.concurrent.Future; /** - * Distributed and concurrent implementation of {@link java.util.Set} + * Set based Multimap Cache values holder * * @author Nikita Koksharov * From a941a7f3a174a3bd2e67d5d73cd3bfa579c928ef Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 15:26:14 +0300 Subject: [PATCH 11/88] Config params validation added. #431 --- src/main/java/org/redisson/Redisson.java | 22 +++++++++++++ src/test/java/org/redisson/RedissonTest.java | 34 +++++++------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 2a0d66edb..648beb275 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -93,15 +93,21 @@ public class Redisson implements RedissonClient { Redisson(Config config) { this.config = config; Config configCopy = new Config(config); + if (configCopy.getMasterSlaveServersConfig() != null) { + validate(configCopy.getMasterSlaveServersConfig()); connectionManager = new MasterSlaveConnectionManager(configCopy.getMasterSlaveServersConfig(), configCopy); } else if (configCopy.getSingleServerConfig() != null) { + validate(configCopy.getSingleServerConfig()); connectionManager = new SingleConnectionManager(configCopy.getSingleServerConfig(), configCopy); } else if (configCopy.getSentinelServersConfig() != null) { + validate(configCopy.getSentinelServersConfig()); connectionManager = new SentinelConnectionManager(configCopy.getSentinelServersConfig(), configCopy); } else if (configCopy.getClusterServersConfig() != null) { + validate(configCopy.getClusterServersConfig()); connectionManager = new ClusterConnectionManager(configCopy.getClusterServersConfig(), configCopy); } else if (configCopy.getElasticacheServersConfig() != null) { + validate(configCopy.getElasticacheServersConfig()); connectionManager = new ElasticacheConnectionManager(configCopy.getElasticacheServersConfig(), configCopy); } else { throw new IllegalArgumentException("server(s) address(es) not defined!"); @@ -110,7 +116,23 @@ public class Redisson implements RedissonClient { evictionScheduler = new EvictionScheduler(commandExecutor); } + private void validate(SingleServerConfig config) { + if (config.getConnectionPoolSize() < config.getConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("connectionPoolSize can't be lower than connectionMinimumIdleSize"); + } + } + private void validate(BaseMasterSlaveServersConfig config) { + if (config.getSlaveConnectionPoolSize() < config.getSlaveConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("slaveConnectionPoolSize can't be lower than slaveConnectionMinimumIdleSize"); + } + if (config.getMasterConnectionPoolSize() < config.getMasterConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("masterConnectionPoolSize can't be lower than masterConnectionMinimumIdleSize"); + } + if (config.getSlaveSubscriptionConnectionPoolSize() < config.getSlaveSubscriptionConnectionMinimumIdleSize()) { + throw new IllegalArgumentException("slaveSubscriptionConnectionMinimumIdleSize can't be lower than slaveSubscriptionConnectionPoolSize"); + } + } /** * Create sync/async Redisson instance with default config diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 74aeef7d5..ee032a4d9 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -1,6 +1,7 @@ package org.redisson; -import static org.assertj.core.api.Assertions.*; +import static com.jayway.awaitility.Awaitility.await; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.net.InetSocketAddress; @@ -12,36 +13,15 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.Test; -import org.redisson.client.RedisConnection; +import org.redisson.RedisRunner.RedisProcess; import org.redisson.client.RedisConnectionException; import org.redisson.client.RedisOutOfMemoryException; import org.redisson.client.WriteRedisConnectionException; -import org.redisson.client.codec.StringCodec; -import org.redisson.client.handler.CommandDecoder; -import org.redisson.client.handler.CommandEncoder; -import org.redisson.client.handler.CommandsListEncoder; -import org.redisson.client.handler.CommandsQueue; -import org.redisson.client.handler.ConnectionWatchdog; import org.redisson.codec.SerializationCodec; import org.redisson.connection.ConnectionListener; import org.redisson.core.ClusterNode; import org.redisson.core.Node; import org.redisson.core.NodesGroup; -import org.redisson.core.RBatch; -import org.redisson.core.RMap; - -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.util.concurrent.GenericFutureListener; - -import static com.jayway.awaitility.Awaitility.*; -import org.redisson.RedisRunner.RedisProcess; public class RedissonTest { @@ -94,6 +74,14 @@ public class RedissonTest { } } + @Test(expected = IllegalArgumentException.class) + public void testConfigValidation() { + Config redissonConfig = new Config(); + redissonConfig.useSingleServer() + .setAddress("127.0.0.1:6379") + .setConnectionPoolSize(2); + Redisson.create(redissonConfig); + } @Test public void testConnectionListener() throws IOException, InterruptedException, TimeoutException { From bc55b700e63d11742bcad27bf71cef0db676a2e5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 16:20:19 +0300 Subject: [PATCH 12/88] ByteBuffer switched to netty ByteBuffer --- src/main/java/org/redisson/RedissonSetCache.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 42726bafb..8a9083c75 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -39,6 +39,10 @@ import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RSetCache; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.base64.Base64; +import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import net.openhft.hashing.LongHashFunction; @@ -114,10 +118,12 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< long h1 = LongHashFunction.farmUo().hashBytes(objectState); long h2 = LongHashFunction.xx_r39().hashBytes(objectState); - return ByteBuffer.allocate((2 * Long.SIZE) / Byte.SIZE) - .putLong(h1) - .putLong(h2) - .array(); + ByteBuf buf = Unpooled.buffer((2 * Long.SIZE) / Byte.SIZE).writeLong(h1).writeLong(h2); + try { + return buf.array(); + } finally { + buf.release(); + } } String getTimeoutSetName() { From 3720a71c55df209ff74185bf9045bef9a6b5b6e1 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 19:41:42 +0300 Subject: [PATCH 13/88] few fixes --- src/test/java/org/redisson/RedissonTopicTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/redisson/RedissonTopicTest.java b/src/test/java/org/redisson/RedissonTopicTest.java index 5d1d09ef4..7373a5365 100644 --- a/src/test/java/org/redisson/RedissonTopicTest.java +++ b/src/test/java/org/redisson/RedissonTopicTest.java @@ -105,7 +105,8 @@ public class RedissonTopicTest { }); topic1.removeListener(listenerId); topic1.removeListener(listenerId2); - l.await(); + + Assert.assertTrue(l.await(5, TimeUnit.SECONDS)); } @Test @@ -166,7 +167,7 @@ public class RedissonTopicTest { }); topic2.publish(new Message("123")); - messageRecieved.await(); + Assert.assertTrue(messageRecieved.await(5, TimeUnit.SECONDS)); redisson1.shutdown(); redisson2.shutdown(); @@ -231,7 +232,7 @@ public class RedissonTopicTest { } }); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < 5000; i++) { topic2.publish(new Message("123")); } @@ -239,7 +240,7 @@ public class RedissonTopicTest { Thread.sleep(1000); - Assert.assertEquals(500, counter); + Assert.assertEquals(5000, counter); redisson1.shutdown(); redisson2.shutdown(); From dc9d1776a44f92ae076a7a37c7179eeee5d5b27d Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 19:51:12 +0300 Subject: [PATCH 14/88] test enhansments --- src/test/java/org/redisson/RedissonTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index ee032a4d9..92b551c11 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -49,6 +49,7 @@ public class RedissonTest { try { RedissonClient r = Redisson.create(config); + r.getKeys().flushall(); for (int i = 0; i < 10000; i++) { r.getMap("test").put("" + i, "" + i); } @@ -66,6 +67,7 @@ public class RedissonTest { try { RedissonClient r = Redisson.create(config); + r.getKeys().flushall(); for (int i = 0; i < 10000; i++) { r.getMap("test").fastPut("" + i, "" + i); } From 950308956a3b135c718ff4842659e4087e1178a5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 21 Mar 2016 19:51:25 +0300 Subject: [PATCH 15/88] small refactoring --- .../client/handler/CommandDecoder.java | 24 +++++++++++-------- .../org/redisson/client/handler/State.java | 5 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index 4ef080b03..f7b150e9e 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -100,8 +100,10 @@ public class CommandDecoder extends ReplayingDecoder { try { // if (state().getSize() > 0) { // List respParts = new ArrayList(); -// respParts.addAll(state().getRespParts()); -// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts); +// if (state().getRespParts() != null) { +// respParts = state().getRespParts(); +// } +// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts, true); // } else { decode(in, cmd, null, ctx.channel(), currentDecoder); // } @@ -149,7 +151,6 @@ public class CommandDecoder extends ReplayingDecoder { } } else { if (!promise.trySuccess(null) && promise.cause() instanceof RedisTimeoutException) { - // TODO try increase timeout log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data); } } @@ -214,21 +215,27 @@ public class CommandDecoder extends ReplayingDecoder { handleResult(data, parts, result, false, channel); } else if (code == '*') { long size = readLong(in); -// state().setSizeOnce(size); - List respParts = new ArrayList(); + boolean top = false; +// if (state().trySetSize(size)) { +// state().setRespParts(respParts); +// top = true; +// } - decodeMulti(in, data, parts, channel, currentDecoder, size, respParts); + decodeMulti(in, data, parts, channel, currentDecoder, size, respParts, top); } else { throw new IllegalStateException("Can't decode replay " + (char)code); } } private void decodeMulti(ByteBuf in, CommandData data, List parts, - Channel channel, Decoder currentDecoder, long size, List respParts) + Channel channel, Decoder currentDecoder, long size, List respParts, boolean top) throws IOException { for (int i = respParts.size(); i < size; i++) { decode(in, data, respParts, channel, currentDecoder); +// if (top) { +// checkpoint(); +// } } MultiDecoder decoder = messageDecoder(data, respParts, channel); @@ -250,9 +257,6 @@ public class CommandDecoder extends ReplayingDecoder { } } else { handleMultiResult(data, parts, channel, result); -// if (parts != null && !decoder.isApplicable(parts.size(), state())) { -// state().setRespParts(parts); -// } } } diff --git a/src/main/java/org/redisson/client/handler/State.java b/src/main/java/org/redisson/client/handler/State.java index 8eed56b08..d9d1ba579 100644 --- a/src/main/java/org/redisson/client/handler/State.java +++ b/src/main/java/org/redisson/client/handler/State.java @@ -29,11 +29,12 @@ public class State { super(); } - public void setSizeOnce(long size) { + public boolean trySetSize(long size) { if (this.size != 0) { - return; + return false; } this.size = size; + return true; } public long getSize() { return size; From e8a054708a39c8fdd891a600b8fef2bce15ca732 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Mon, 21 Mar 2016 22:56:57 +0000 Subject: [PATCH 16/88] added sudo false We are going to the container --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ce9484f40..13aeda99b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +sudo: false + language: java jdk: - oraclejdk8 From 9913a98f4f2f48d4326cb58f5586c0a5f4a95019 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Mon, 21 Mar 2016 22:58:49 +0000 Subject: [PATCH 17/88] make redis binary executable by all --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 13aeda99b..d0dc4f540 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ install: - 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 + - chmod +x $REDIS_BIN/* before_script: - $REDIS_BIN/redis-server --daemonize yes - sleep 3 From 23e188895eab523e2fb319b0cd6365332ee78184 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Mon, 21 Mar 2016 23:01:10 +0000 Subject: [PATCH 18/88] fixed redisBinary issue fixed redisBinary been set to the directory instead of the binary --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d0dc4f540..0153205e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,10 @@ install: - 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 - - chmod +x $REDIS_BIN/* 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" -Punit-test -Ptravis clean verify +script: mvn -DargLine="-DredisBinary=$REDIS_BIN/redis-server" -Punit-test -Ptravis clean verify From 0b26f2160d8753cf0a3404a6944ce06074d1b7e9 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 22 Mar 2016 00:06:09 +0000 Subject: [PATCH 19/88] add lines to change system settings increase the operating system limit to the maximum number of file descriptors per process --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0153205e2..9e243b571 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,8 @@ install: - tar -xvf redis-${REDIS_VERSION}.tar.gz - make -C redis-${REDIS_VERSION} PREFIX=$HOME/redis/${REDIS_VERSION} install before_script: + - sysctl -w fs.file-max=100000 + - ulimit -Sn 100000 - $REDIS_BIN/redis-server --daemonize yes - sleep 3 - $REDIS_BIN/redis-cli PING From 8fe49a02286de2f7d6de87c1f753b6bfa7928af9 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 22 Mar 2016 00:10:56 +0000 Subject: [PATCH 20/88] revert the change both sysctl and ulimit command was unsuccessful --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e243b571..0153205e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,6 @@ install: - tar -xvf redis-${REDIS_VERSION}.tar.gz - make -C redis-${REDIS_VERSION} PREFIX=$HOME/redis/${REDIS_VERSION} install before_script: - - sysctl -w fs.file-max=100000 - - ulimit -Sn 100000 - $REDIS_BIN/redis-server --daemonize yes - sleep 3 - $REDIS_BIN/redis-cli PING From 9c33b274f98d358e5be81417dddf5e65ec310678 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 13:55:56 +0300 Subject: [PATCH 21/88] Ability to cancel BRPOP and BLPOP async command execution. #446 --- .../org/redisson/client/RedisConnection.java | 4 +- .../redisson/command/CommandAsyncService.java | 12 +++++ .../redisson/RedissonBlockingQueueTest.java | 50 ++++++++++++++++--- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/redisson/client/RedisConnection.java b/src/main/java/org/redisson/client/RedisConnection.java index 85ff35041..53f1c9383 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -174,8 +174,8 @@ public class RedisConnection implements RedisCommands { return closed; } - public void forceReconnect() { - channel.close(); + public ChannelFuture forceReconnectAsync() { + return channel.close(); } /** diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index d8c44d2fa..f2e63379a 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -462,6 +462,18 @@ public class CommandAsyncService implements CommandAsyncExecutor { int timeoutTime = connectionManager.getConfig().getTimeout(); if (skipTimeout.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); + details.getMainPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isCancelled()) { + return; + } + // cancel handling for commands from skipTimeout collection + if (details.getAttemptPromise().cancel(true)) { + connection.forceReconnectAsync(); + } + } + }); if (popTimeout == 0) { return; } diff --git a/src/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index d28a5d3da..5fab2294d 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -1,9 +1,6 @@ package org.redisson; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.HashSet; @@ -22,8 +19,47 @@ import org.junit.Assert; import org.junit.Test; import org.redisson.core.RBlockingQueue; +import io.netty.util.concurrent.Future; + public class RedissonBlockingQueueTest extends BaseTest { + @Test + public void testTakeAsyncCancel() { + Config config = createConfig(); + config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); + + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + for (int i = 0; i < 10; i++) { + Future f = queue1.takeAsync(); + f.cancel(true); + } + assertThat(queue1.add(1)).isTrue(); + assertThat(queue1.add(2)).isTrue(); + assertThat(queue1.size()).isEqualTo(2); + + redisson.shutdown(); + } + + @Test + public void testPollAsyncCancel() { + Config config = createConfig(); + config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); + + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + for (int i = 0; i < 10; i++) { + Future f = queue1.pollAsync(1, TimeUnit.SECONDS); + f.cancel(true); + } + assertThat(queue1.add(1)).isTrue(); + assertThat(queue1.add(2)).isTrue(); + assertThat(queue1.size()).isEqualTo(2); + + redisson.shutdown(); + } + + @Test public void testPollFromAny() throws InterruptedException { final RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); @@ -225,14 +261,14 @@ public class RedissonBlockingQueueTest extends BaseTest { try { // blocking int item = queue.take(); - assertTrue(item > 0 && item <= total); + assertThat(item > 0 && item <= total).isTrue(); } catch (InterruptedException exception) { - fail(); + Assert.fail(); } count++; } - assertThat(counter.get(), equalTo(total)); + assertThat(counter.get()).isEqualTo(total); queue.delete(); } From 73452d9974566963442c41f3dfeba3bdda8369c1 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:23:33 +0300 Subject: [PATCH 22/88] RemoteService implemented. #434 --- src/main/java/org/redisson/Redisson.java | 9 +- .../java/org/redisson/RedissonClient.java | 8 + .../org/redisson/RedissonRemoteService.java | 152 ++++++++++++++++++ .../java/org/redisson/RemoteServiceKey.java | 68 ++++++++ .../org/redisson/RemoteServiceMethod.java | 39 +++++ .../org/redisson/RemoteServiceRequest.java | 54 +++++++ .../org/redisson/RemoteServiceResponse.java | 42 +++++ .../client/handler/CommandDecoder.java | 8 +- .../org/redisson/codec/JsonJacksonCodec.java | 15 ++ .../org/redisson/core/RRemoteService.java | 36 +++++ .../redisson/RedissonRemoteServiceTest.java | 83 ++++++++++ 11 files changed, 509 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonRemoteService.java create mode 100644 src/main/java/org/redisson/RemoteServiceKey.java create mode 100644 src/main/java/org/redisson/RemoteServiceMethod.java create mode 100644 src/main/java/org/redisson/RemoteServiceRequest.java create mode 100644 src/main/java/org/redisson/RemoteServiceResponse.java create mode 100644 src/main/java/org/redisson/core/RRemoteService.java create mode 100644 src/test/java/org/redisson/RedissonRemoteServiceTest.java diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 648beb275..096483e1d 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -58,10 +58,10 @@ import org.redisson.core.RListMultimapCache; import org.redisson.core.RLock; import org.redisson.core.RMap; import org.redisson.core.RMapCache; -import org.redisson.core.RMultimapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; +import org.redisson.core.RRemoteService; import org.redisson.core.RScoredSortedSet; import org.redisson.core.RScript; import org.redisson.core.RSemaphore; @@ -87,6 +87,7 @@ public class Redisson implements RedissonClient { private final CommandExecutor commandExecutor; private final ConnectionManager connectionManager; private final Config config; + private final RedissonRemoteService remoteService; private final UUID id = UUID.randomUUID(); @@ -114,6 +115,7 @@ public class Redisson implements RedissonClient { } commandExecutor = new CommandSyncService(connectionManager); evictionScheduler = new EvictionScheduler(commandExecutor); + remoteService = new RedissonRemoteService(this); } private void validate(SingleServerConfig config) { @@ -369,6 +371,10 @@ public class Redisson implements RedissonClient { return new RedissonScript(commandExecutor); } + public RRemoteService getRemoteSerivce() { + return remoteService; + } + @Override public RSortedSet getSortedSet(String name) { return new RedissonSortedSet(commandExecutor, name); @@ -501,6 +507,7 @@ public class Redisson implements RedissonClient { @Override public void shutdown() { + remoteService.shutdown(); connectionManager.shutdown(); } diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index 270877e9f..edb10dced 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -45,6 +45,7 @@ import org.redisson.core.RMapCache; import org.redisson.core.RPatternTopic; import org.redisson.core.RQueue; import org.redisson.core.RReadWriteLock; +import org.redisson.core.RRemoteService; import org.redisson.core.RScoredSortedSet; import org.redisson.core.RScript; import org.redisson.core.RSemaphore; @@ -585,6 +586,13 @@ public interface RedissonClient { */ RScript getScript(); + /** + * Returns object for remote operations + * + * @return + */ + RRemoteService getRemoteSerivce(); + /** * Return batch object which executes group of * command in pipeline. diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java new file mode 100644 index 000000000..d3dac19b7 --- /dev/null +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -0,0 +1,152 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import org.redisson.core.MessageListener; +import org.redisson.core.RBlockingQueue; +import org.redisson.core.RRemoteService; +import org.redisson.core.RTopic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.buffer.ByteBufUtil; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.ThreadLocalRandom; + +public class RedissonRemoteService implements RRemoteService { + + private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); + + private final Map beans = PlatformDependent.newConcurrentHashMap(); + private final Queue> futures = new ConcurrentLinkedQueue>(); + + private final Redisson redisson; + + public RedissonRemoteService(Redisson redisson) { + this.redisson = redisson; + } + + @Override + public void register(Class serviceInterface, T object) { + for (Method method : serviceInterface.getMethods()) { + RemoteServiceMethod value = new RemoteServiceMethod(method, object); + RemoteServiceKey key = new RemoteServiceKey(serviceInterface, method.getName()); + if (beans.put(key, value) != null) { + return; + } + } + + String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + subscribe(serviceInterface, requestQueue); + } + + private void subscribe(final Class serviceInterface, final RBlockingQueue requestQueue) { + Future take = requestQueue.takeAsync(); + futures.add(take); + take.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + return; + } + + RemoteServiceRequest request = future.getNow(); + RemoteServiceMethod method = beans.get(new RemoteServiceKey(serviceInterface, request.getMethodName())); + String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + request.getRequestId(); + RTopic topic = redisson.getTopic(responseName); + RemoteServiceResponse response; + try { + Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); + response = new RemoteServiceResponse(result); + } catch (Exception e) { + e.getCause().printStackTrace(); + response = new RemoteServiceResponse(e.getCause()); + log.error("Can't execute: " + method.getMethod().getName() + " with args: " + request.getArgs(), e); + } + + long clients = topic.publish(response); + if (clients == 0) { + log.error("None of clients has not received a response for request {}", request); + } + + futures.remove(future); + subscribe(serviceInterface, requestQueue); + } + }); + } + + @Override + public T get(final Class serviceInterface) { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String requestId = generateRequestId(); + System.out.println(requestId); + + String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + requestQueue.add(new RemoteServiceRequest(requestId, method.getName(), args)); + + String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + requestId; + final RTopic topic = redisson.getTopic(responseName); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference response = new AtomicReference(); + int listenerId = topic.addListener(new MessageListener() { + @Override + public void onMessage(String channel, RemoteServiceResponse msg) { + response.set(msg); + latch.countDown(); + } + }); + + latch.await(); + topic.removeListener(listenerId); + RemoteServiceResponse msg = response.get(); + if (msg.getError() != null) { + throw msg.getError(); + } + return msg.getResult(); + } + }; + return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class[] {serviceInterface}, handler); + } + + private String generateRequestId() { + byte[] id = new byte[16]; + // TODO JDK UPGRADE replace to native ThreadLocalRandom + ThreadLocalRandom.current().nextBytes(id); + return ByteBufUtil.hexDump(id); + } + + public void shutdown() { + for (Future future : futures) { + future.cancel(true); + } + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceKey.java b/src/main/java/org/redisson/RemoteServiceKey.java new file mode 100644 index 000000000..5cadb3a5a --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceKey.java @@ -0,0 +1,68 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +public class RemoteServiceKey { + + private final Class serviceInterface; + private final String methodName; + + public RemoteServiceKey(Class serviceInterface, String methodName) { + super(); + this.serviceInterface = serviceInterface; + this.methodName = methodName; + } + + public String getMethodName() { + return methodName; + } + + public Class getServiceInterface() { + return serviceInterface; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((methodName == null) ? 0 : methodName.hashCode()); + result = prime * result + ((serviceInterface == null) ? 0 : serviceInterface.getName().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RemoteServiceKey other = (RemoteServiceKey) obj; + if (methodName == null) { + if (other.methodName != null) + return false; + } else if (!methodName.equals(other.methodName)) + return false; + if (serviceInterface == null) { + if (other.serviceInterface != null) + return false; + } else if (!serviceInterface.equals(other.serviceInterface)) + return false; + return true; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceMethod.java b/src/main/java/org/redisson/RemoteServiceMethod.java new file mode 100644 index 000000000..153c82d19 --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceMethod.java @@ -0,0 +1,39 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.lang.reflect.Method; + +public class RemoteServiceMethod { + + private final Object bean; + private final Method method; + + public RemoteServiceMethod(Method method, Object bean) { + super(); + this.method = method; + this.bean = bean; + } + + public Object getBean() { + return bean; + } + + public Method getMethod() { + return method; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceRequest.java b/src/main/java/org/redisson/RemoteServiceRequest.java new file mode 100644 index 000000000..3934a78b7 --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceRequest.java @@ -0,0 +1,54 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.util.Arrays; + +public class RemoteServiceRequest { + + private String requestId; + private String methodName; + private Object[] args; + + public RemoteServiceRequest() { + } + + public RemoteServiceRequest(String requestId, String methodName, Object[] args) { + super(); + this.requestId = requestId; + this.methodName = methodName; + this.args = args; + } + + public String getRequestId() { + return requestId; + } + + public Object[] getArgs() { + return args; + } + + public String getMethodName() { + return methodName; + } + + @Override + public String toString() { + return "[requestId=" + requestId + ", methodName=" + methodName + ", args=" + + Arrays.toString(args) + "]"; + } + +} diff --git a/src/main/java/org/redisson/RemoteServiceResponse.java b/src/main/java/org/redisson/RemoteServiceResponse.java new file mode 100644 index 000000000..dd14bf06c --- /dev/null +++ b/src/main/java/org/redisson/RemoteServiceResponse.java @@ -0,0 +1,42 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +public class RemoteServiceResponse { + + private Object result; + private Throwable error; + + public RemoteServiceResponse() { + } + + public RemoteServiceResponse(Object result) { + this.result = result; + } + + public RemoteServiceResponse(Throwable error) { + this.error = error; + } + + public Throwable getError() { + return error; + } + + public Object getResult() { + return result; + } + +} diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index f7b150e9e..ac868aafd 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -84,12 +84,12 @@ public class CommandDecoder extends ReplayingDecoder { currentDecoder = StringCodec.INSTANCE.getValueDecoder(); } - if (log.isTraceEnabled()) { - log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8)); - } - if (state() == null) { state(new State()); + + if (log.isTraceEnabled()) { + log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8)); + } } state().setDecoderState(null); diff --git a/src/main/java/org/redisson/codec/JsonJacksonCodec.java b/src/main/java/org/redisson/codec/JsonJacksonCodec.java index 711e1156f..ab518b0ed 100755 --- a/src/main/java/org/redisson/codec/JsonJacksonCodec.java +++ b/src/main/java/org/redisson/codec/JsonJacksonCodec.java @@ -23,8 +23,15 @@ import org.redisson.client.protocol.Decoder; import org.redisson.client.protocol.Encoder; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonIdentityReference; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.MapperFeature; @@ -32,6 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import io.netty.buffer.ByteBuf; @@ -49,6 +57,12 @@ public class JsonJacksonCodec implements Codec { public static final JsonJacksonCodec INSTANCE = new JsonJacksonCodec(); + @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id") + @JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.PUBLIC_ONLY, setterVisibility = Visibility.PUBLIC_ONLY, isGetterVisibility = Visibility.PUBLIC_ONLY) + public static class ThrowableMixIn { + + } + private final ObjectMapper mapObjectMapper = initObjectMapper(); protected ObjectMapper initObjectMapper() { @@ -111,6 +125,7 @@ public class JsonJacksonCodec implements Codec { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true); objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); + objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class); } @Override diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java new file mode 100644 index 000000000..f7066dc9d --- /dev/null +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -0,0 +1,36 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public interface RRemoteService { + + /** + * Register object as remote service + * + * @param remoteInterface + * @param object + */ + void register(Class remoteInterface, T object); + + /** + * Get remote service object for remote invocations + * + * @param remoteInterface + * @return + */ + T get(Class remoteInterface); + +} diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java new file mode 100644 index 000000000..b6cede708 --- /dev/null +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -0,0 +1,83 @@ +package org.redisson; + +import org.junit.Assert; +import org.junit.Test; +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; + +public class RedissonRemoteServiceTest extends BaseTest { + + public interface RemoteInterface { + + void voidMethod(String name, Long param); + + Long resultMethod(Long value); + + void errorMethod() throws IOException; + + void errorMethodWithCause(); + + } + + public class RemoteImpl implements RemoteInterface { + + @Override + public void voidMethod(String name, Long param) { + System.out.println(name + " " + param); + } + + @Override + public Long resultMethod(Long value) { + return value*2; + } + + @Override + public void errorMethod() throws IOException { + throw new IOException("Checking error throw"); + } + + @Override + public void errorMethodWithCause() { + try { + int s = 2 / 0; + } catch (Exception e) { + throw new RuntimeException("Checking error throw", e); + } + } + + + } + + @Test + public void testInvocations() { + RedissonClient r1 = Redisson.create(); + r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + RedissonClient r2 = Redisson.create(); + RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class); + + ri.voidMethod("someName", 100L); + assertThat(ri.resultMethod(100L)).isEqualTo(200); + + try { + ri.errorMethod(); + Assert.fail(); + } catch (IOException e) { + assertThat(e.getMessage()).isEqualTo("Checking error throw"); + } + + try { + ri.errorMethodWithCause(); + Assert.fail(); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(ArithmeticException.class); + assertThat(e.getCause().getMessage()).isEqualTo("/ by zero"); + } + + + r1.shutdown(); + r2.shutdown(); + } + +} From f152c23d57004296e87ef356590492dabbe4f0d1 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:31:12 +0300 Subject: [PATCH 23/88] sysout removed --- src/main/java/org/redisson/RedissonRemoteService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index d3dac19b7..176897b90 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -106,7 +106,6 @@ public class RedissonRemoteService implements RRemoteService { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String requestId = generateRequestId(); - System.out.println(requestId); String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); From 43e632bfdf93036e1747b1436b0fedaa3a3c8a11 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:39:39 +0300 Subject: [PATCH 24/88] minor RemoteService improvements --- src/main/java/org/redisson/RedissonRemoteService.java | 5 ++--- src/main/java/org/redisson/RemoteServiceRequest.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 176897b90..0beebb71f 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -84,14 +84,13 @@ public class RedissonRemoteService implements RRemoteService { Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); response = new RemoteServiceResponse(result); } catch (Exception e) { - e.getCause().printStackTrace(); response = new RemoteServiceResponse(e.getCause()); - log.error("Can't execute: " + method.getMethod().getName() + " with args: " + request.getArgs(), e); + log.error("Can't execute: " + request, e); } long clients = topic.publish(response); if (clients == 0) { - log.error("None of clients has not received a response for request {}", request); + log.error("None of clients has not received a response for: {}", request); } futures.remove(future); diff --git a/src/main/java/org/redisson/RemoteServiceRequest.java b/src/main/java/org/redisson/RemoteServiceRequest.java index 3934a78b7..434784456 100644 --- a/src/main/java/org/redisson/RemoteServiceRequest.java +++ b/src/main/java/org/redisson/RemoteServiceRequest.java @@ -47,7 +47,7 @@ public class RemoteServiceRequest { @Override public String toString() { - return "[requestId=" + requestId + ", methodName=" + methodName + ", args=" + return "RemoteServiceRequest[requestId=" + requestId + ", methodName=" + methodName + ", args=" + Arrays.toString(args) + "]"; } From 58472c6cf90de1d2b210ad3f75e12290b88a4ffc Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 16:50:37 +0300 Subject: [PATCH 25/88] RRemoteService.register method with custom executors amount added. #434 --- .../org/redisson/RedissonRemoteService.java | 40 ++++++++++++------- .../org/redisson/core/RRemoteService.java | 11 ++++- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 0beebb71f..55455db33 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -51,21 +51,31 @@ public class RedissonRemoteService implements RRemoteService { } @Override - public void register(Class serviceInterface, T object) { - for (Method method : serviceInterface.getMethods()) { + public void register(Class remoteInterface, T object) { + register(remoteInterface, object, 1); + } + + @Override + public void register(Class remoteInterface, T object, int executorsAmount) { + if (executorsAmount < 1) { + throw new IllegalArgumentException("executorsAmount can't be lower than 1"); + } + for (Method method : remoteInterface.getMethods()) { RemoteServiceMethod value = new RemoteServiceMethod(method, object); - RemoteServiceKey key = new RemoteServiceKey(serviceInterface, method.getName()); + RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName()); if (beans.put(key, value) != null) { return; } } - - String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; - RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); - subscribe(serviceInterface, requestQueue); + + for (int i = 0; i < executorsAmount; i++) { + String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); + subscribe(remoteInterface, requestQueue); + } } - private void subscribe(final Class serviceInterface, final RBlockingQueue requestQueue) { + private void subscribe(final Class remoteInterface, final RBlockingQueue requestQueue) { Future take = requestQueue.takeAsync(); futures.add(take); take.addListener(new FutureListener() { @@ -76,8 +86,8 @@ public class RedissonRemoteService implements RRemoteService { } RemoteServiceRequest request = future.getNow(); - RemoteServiceMethod method = beans.get(new RemoteServiceKey(serviceInterface, request.getMethodName())); - String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + request.getRequestId(); + RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); + String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId(); RTopic topic = redisson.getTopic(responseName); RemoteServiceResponse response; try { @@ -94,23 +104,23 @@ public class RedissonRemoteService implements RRemoteService { } futures.remove(future); - subscribe(serviceInterface, requestQueue); + subscribe(remoteInterface, requestQueue); } }); } @Override - public T get(final Class serviceInterface) { + public T get(final Class remoteInterface) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String requestId = generateRequestId(); - String requestQueueName = "redisson_remote_service:{" + serviceInterface.getName() + "}"; + String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); requestQueue.add(new RemoteServiceRequest(requestId, method.getName(), args)); - String responseName = "redisson_remote_service:{" + serviceInterface.getName() + "}:" + requestId; + String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; final RTopic topic = redisson.getTopic(responseName); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference response = new AtomicReference(); @@ -131,7 +141,7 @@ public class RedissonRemoteService implements RRemoteService { return msg.getResult(); } }; - return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class[] {serviceInterface}, handler); + return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler); } private String generateRequestId() { diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index f7066dc9d..c322c512b 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -18,13 +18,22 @@ package org.redisson.core; public interface RRemoteService { /** - * Register object as remote service + * Register remote service with single executor * * @param remoteInterface * @param object */ void register(Class remoteInterface, T object); + /** + * Register remote service with custom executors amount + * + * @param remoteInterface + * @param object + * @param executorsAmount + */ + void register(Class remoteInterface, T object, int executorsAmount); + /** * Get remote service object for remote invocations * From 7fc81160668fd2165b49a0bb35c4ad5ab487e4d4 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 17:02:43 +0300 Subject: [PATCH 26/88] RemoteService.get method with timeout invocation added. #434 --- .../java/org/redisson/RedissonRemoteService.java | 14 ++++++++++++-- .../java/org/redisson/core/RRemoteService.java | 13 +++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 55455db33..4126a1d96 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.redisson.core.MessageListener; @@ -110,7 +111,12 @@ public class RedissonRemoteService implements RRemoteService { } @Override - public T get(final Class remoteInterface) { + public T get(Class remoteInterface) { + return get(remoteInterface, -1, null); + } + + @Override + public T get(final Class remoteInterface, final int timeout, final TimeUnit timeUnit) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { @@ -132,7 +138,11 @@ public class RedissonRemoteService implements RRemoteService { } }); - latch.await(); + if (timeout == -1) { + latch.await(); + } else { + latch.await(timeout, timeUnit); + } topic.removeListener(listenerId); RemoteServiceResponse msg = response.get(); if (msg.getError() != null) { diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index c322c512b..f8f27bd1a 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -15,6 +15,8 @@ */ package org.redisson.core; +import java.util.concurrent.TimeUnit; + public interface RRemoteService { /** @@ -42,4 +44,15 @@ public interface RRemoteService { */ T get(Class remoteInterface); + /** + * Get remote service object for remote invocations + * with specified timeout invocation + * + * @param remoteInterface + * @param timeout - timeout invocation + * @param timeUnit + * @return + */ + T get(Class remoteInterface, int timeout, TimeUnit timeUnit); + } From 7884c07b095b8b4bfee69444615239ceeaf6b22a Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 17:12:41 +0300 Subject: [PATCH 27/88] comment fixed --- src/main/java/org/redisson/core/RRemoteService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index f8f27bd1a..1b75c5dc3 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -46,10 +46,10 @@ public interface RRemoteService { /** * Get remote service object for remote invocations - * with specified timeout invocation + * with specified invocation timeout * * @param remoteInterface - * @param timeout - timeout invocation + * @param timeout - invocation timeout * @param timeUnit * @return */ From 5e2cbe6a2fbccf1f1b918d49168347d8ced699c6 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 22 Mar 2016 18:27:13 +0300 Subject: [PATCH 28/88] RemoteSerivce shutdown process optimization. #446 --- src/main/java/org/redisson/Redisson.java | 5 +-- .../org/redisson/RedissonRemoteService.java | 11 ------ .../redisson/command/CommandAsyncService.java | 36 ++++++++++++------- .../connection/ConnectionManager.java | 2 ++ .../MasterSlaveConnectionManager.java | 9 +++++ 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 096483e1d..f1fdf2953 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -87,7 +87,6 @@ public class Redisson implements RedissonClient { private final CommandExecutor commandExecutor; private final ConnectionManager connectionManager; private final Config config; - private final RedissonRemoteService remoteService; private final UUID id = UUID.randomUUID(); @@ -115,7 +114,6 @@ public class Redisson implements RedissonClient { } commandExecutor = new CommandSyncService(connectionManager); evictionScheduler = new EvictionScheduler(commandExecutor); - remoteService = new RedissonRemoteService(this); } private void validate(SingleServerConfig config) { @@ -372,7 +370,7 @@ public class Redisson implements RedissonClient { } public RRemoteService getRemoteSerivce() { - return remoteService; + return new RedissonRemoteService(this); } @Override @@ -507,7 +505,6 @@ public class Redisson implements RedissonClient { @Override public void shutdown() { - remoteService.shutdown(); connectionManager.shutdown(); } diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 4126a1d96..09a9c8d18 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -19,8 +19,6 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -43,7 +41,6 @@ public class RedissonRemoteService implements RRemoteService { private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); private final Map beans = PlatformDependent.newConcurrentHashMap(); - private final Queue> futures = new ConcurrentLinkedQueue>(); private final Redisson redisson; @@ -78,7 +75,6 @@ public class RedissonRemoteService implements RRemoteService { private void subscribe(final Class remoteInterface, final RBlockingQueue requestQueue) { Future take = requestQueue.takeAsync(); - futures.add(take); take.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { @@ -104,7 +100,6 @@ public class RedissonRemoteService implements RRemoteService { log.error("None of clients has not received a response for: {}", request); } - futures.remove(future); subscribe(remoteInterface, requestQueue); } }); @@ -161,10 +156,4 @@ public class RedissonRemoteService implements RRemoteService { return ByteBufUtil.hexDump(id); } - public void shutdown() { - for (Future future : futures) { - future.cancel(true); - } - } - } diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index f2e63379a..d7f0c6126 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -462,18 +462,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { int timeoutTime = connectionManager.getConfig().getTimeout(); if (skipTimeout.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); - details.getMainPromise().addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - if (!future.isCancelled()) { - return; - } - // cancel handling for commands from skipTimeout collection - if (details.getAttemptPromise().cancel(true)) { - connection.forceReconnectAsync(); - } - } - }); + handleBlockingOperations(details, connection); if (popTimeout == 0) { return; } @@ -494,6 +483,29 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.setTimeout(timeout); } + private void handleBlockingOperations(final AsyncDetails details, final RedisConnection connection) { + final FutureListener listener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + details.getMainPromise().cancel(true); + } + }; + details.getMainPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isCancelled()) { + connectionManager.getShutdownPromise().removeListener(listener); + return; + } + // cancel handling for commands from skipTimeout collection + if (details.getAttemptPromise().cancel(true)) { + connection.forceReconnectAsync(); + } + } + }); + connectionManager.getShutdownPromise().addListener(listener); + } + private void checkConnectionFuture(final NodeSource source, final AsyncDetails details) { if (details.getAttemptPromise().isDone() || details.getMainPromise().isCancelled() || details.getConnectionFuture().isCancelled()) { diff --git a/src/main/java/org/redisson/connection/ConnectionManager.java b/src/main/java/org/redisson/connection/ConnectionManager.java index 48b2e2c98..21a275ddb 100644 --- a/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/src/main/java/org/redisson/connection/ConnectionManager.java @@ -107,5 +107,7 @@ public interface ConnectionManager { Timeout newTimeout(TimerTask task, long delay, TimeUnit unit); InfinitySemaphoreLatch getShutdownLatch(); + + Future getShutdownPromise(); } diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 1fb69f386..065e66f75 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -122,6 +122,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager { protected final Map entries = PlatformDependent.newConcurrentHashMap(); + private final Promise shutdownPromise; + private final InfinitySemaphoreLatch shutdownLatch = new InfinitySemaphoreLatch(); private final Set clients = Collections.newSetFromMap(PlatformDependent.newConcurrentHashMap()); @@ -156,6 +158,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { this.socketChannelClass = NioSocketChannel.class; } this.codec = cfg.getCodec(); + this.shutdownPromise = group.next().newPromise(); this.isClusterMode = cfg.isClusterConfig(); } @@ -674,6 +677,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdown() { + shutdownPromise.trySuccess(true); shutdownLatch.closeAndAwaitUninterruptibly(); for (MasterSlaveEntry entry : entries.values()) { entry.shutdown(); @@ -731,6 +735,11 @@ public class MasterSlaveConnectionManager implements ConnectionManager { public InfinitySemaphoreLatch getShutdownLatch() { return shutdownLatch; } + + @Override + public Future getShutdownPromise() { + return shutdownPromise; + } @Override public ConnectionEventsHub getConnectionEventsHub() { From 516202c95fcc1f4862a15629f8d924c5c1eded5f Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 22 Mar 2016 15:27:56 +0000 Subject: [PATCH 29/88] change the test redis version to 2.8.24. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0153205e2..1ccd879c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ language: java jdk: - oraclejdk8 env: - - REDIS_VERSION=3.0.7 + - REDIS_VERSION=2.8.24 cache: directories: - $HOME/.m2 From 3ada401c04764dfdbe409bd014f8a6eb6c80ab67 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 22 Mar 2016 21:21:59 +0000 Subject: [PATCH 30/88] Some tweaks to try Trying to use vm instead of container to see if the problem with inconsistent result is getting better. Switching back to use redis v3.0.7 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ccd879c5..2a21106c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -sudo: false +sudo: true language: java jdk: - oraclejdk8 env: - - REDIS_VERSION=2.8.24 + - REDIS_VERSION=3.0.7 cache: directories: - $HOME/.m2 From 49ceb09b60da41971353dbb5bc9fc8e2aa352f95 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 22 Mar 2016 21:57:55 +0000 Subject: [PATCH 31/88] fixes and tweaks fixed sudo value should been required instead of true changed to use trusty distribution to see if inconsistent issue getting better --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2a21106c1..3bfa75efc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -sudo: true +sudo: required +dist: trusty language: java jdk: From 86fd0362123c4d0870bfde50b97adf5f3ee2f0d4 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 22 Mar 2016 22:48:22 +0000 Subject: [PATCH 32/88] disable sudo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3bfa75efc..276b6f248 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -sudo: required +sudo: false dist: trusty language: java From 255db08d835f319a4365478240a4d76132b66ffd Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 22 Mar 2016 23:30:39 +0000 Subject: [PATCH 33/88] change testManyConnections connection size to 2000 --- src/test/java/org/redisson/RedissonTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 92b551c11..7eb2da834 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -264,8 +264,8 @@ public class RedissonTest { public void testManyConnections() { Config redisConfig = new Config(); redisConfig.useSingleServer() - .setConnectionMinimumIdleSize(10000) - .setConnectionPoolSize(10000) + .setConnectionMinimumIdleSize(2000) + .setConnectionPoolSize(2000) .setAddress("localhost:6379"); RedissonClient r = Redisson.create(redisConfig); r.shutdown(); From ac3278d94b87a1a71c0aa13b0d7bc7bcf8f7e438 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:10:56 +0300 Subject: [PATCH 34/88] RemoteService timeout handling fixed. #434 --- .../org/redisson/RedissonRemoteService.java | 12 ++++++--- .../org/redisson/RemoteServiceResponse.java | 5 ++++ .../redisson/command/CommandAsyncService.java | 2 +- .../redisson/RedissonRemoteServiceTest.java | 27 ++++++++++++++++++- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 09a9c8d18..b80124260 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -23,6 +23,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import org.redisson.client.RedisException; +import org.redisson.client.RedisTimeoutException; import org.redisson.core.MessageListener; import org.redisson.core.RBlockingQueue; import org.redisson.core.RRemoteService; @@ -97,7 +99,7 @@ public class RedissonRemoteService implements RRemoteService { long clients = topic.publish(response); if (clients == 0) { - log.error("None of clients has not received a response for: {}", request); + log.error("None of clients has not received a response: {} for request: {}", response, request); } subscribe(remoteInterface, requestQueue); @@ -119,7 +121,8 @@ public class RedissonRemoteService implements RRemoteService { String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); - requestQueue.add(new RemoteServiceRequest(requestId, method.getName(), args)); + RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args); + requestQueue.add(request); String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; final RTopic topic = redisson.getTopic(responseName); @@ -136,7 +139,10 @@ public class RedissonRemoteService implements RRemoteService { if (timeout == -1) { latch.await(); } else { - latch.await(timeout, timeUnit); + if (!latch.await(timeout, timeUnit)) { + topic.removeListener(listenerId); + throw new RedisTimeoutException("No response after " + timeUnit.toMillis(timeout) + "ms for request: " + request); + } } topic.removeListener(listenerId); RemoteServiceResponse msg = response.get(); diff --git a/src/main/java/org/redisson/RemoteServiceResponse.java b/src/main/java/org/redisson/RemoteServiceResponse.java index dd14bf06c..09ebc5999 100644 --- a/src/main/java/org/redisson/RemoteServiceResponse.java +++ b/src/main/java/org/redisson/RemoteServiceResponse.java @@ -38,5 +38,10 @@ public class RemoteServiceResponse { public Object getResult() { return result; } + + @Override + public String toString() { + return "RemoteServiceResponse [result=" + result + ", error=" + error + "]"; + } } diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index d7f0c6126..faba6d1fc 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -493,8 +493,8 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.getMainPromise().addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { + connectionManager.getShutdownPromise().removeListener(listener); if (!future.isCancelled()) { - connectionManager.getShutdownPromise().removeListener(listener); return; } // cancel handling for commands from skipTimeout collection diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java index b6cede708..c8daedd90 100644 --- a/src/test/java/org/redisson/RedissonRemoteServiceTest.java +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -2,9 +2,12 @@ package org.redisson; import org.junit.Assert; import org.junit.Test; +import org.redisson.client.RedisTimeoutException; + import static org.assertj.core.api.Assertions.*; import java.io.IOException; +import java.util.concurrent.TimeUnit; public class RedissonRemoteServiceTest extends BaseTest { @@ -18,6 +21,8 @@ public class RedissonRemoteServiceTest extends BaseTest { void errorMethodWithCause(); + void timeoutMethod() throws InterruptedException; + } public class RemoteImpl implements RemoteInterface { @@ -46,8 +51,29 @@ public class RedissonRemoteServiceTest extends BaseTest { } } + @Override + public void timeoutMethod() throws InterruptedException { + Thread.sleep(2000); + } + } + + @Test(expected = RedisTimeoutException.class) + public void testTimeout() throws InterruptedException { + RedissonClient r1 = Redisson.create(); + r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + RedissonClient r2 = Redisson.create(); + RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class, 1, TimeUnit.SECONDS); + + try { + ri.timeoutMethod(); + } finally { + r1.shutdown(); + r2.shutdown(); + } + } @Test public void testInvocations() { @@ -74,7 +100,6 @@ public class RedissonRemoteServiceTest extends BaseTest { assertThat(e.getCause()).isInstanceOf(ArithmeticException.class); assertThat(e.getCause().getMessage()).isEqualTo("/ by zero"); } - r1.shutdown(); r2.shutdown(); From 40d5ad5237cb9d5200248852cfcd05b0dad943dd Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:43:52 +0300 Subject: [PATCH 35/88] Delete and expire Multimap methods fix. #447 --- .../java/org/redisson/RedissonMultimap.java | 71 +++++++++++++++++++ .../org/redisson/RedissonSetMultimapTest.java | 55 +++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/RedissonMultimap.java b/src/main/java/org/redisson/RedissonMultimap.java index 475660b82..53f4b1944 100644 --- a/src/main/java/org/redisson/RedissonMultimap.java +++ b/src/main/java/org/redisson/RedissonMultimap.java @@ -20,15 +20,18 @@ import java.net.InetSocketAddress; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import java.util.Set; import org.redisson.client.codec.Codec; +import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.ScanCodec; import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.RedisCommands; @@ -152,10 +155,12 @@ public abstract class RedissonMultimap extends RedissonExpirable implement return new EntrySet(); } + @Override public long fastRemove(K ... keys) { return get(fastRemoveAsync(keys)); } + @Override public Future fastRemoveAsync(K ... keys) { if (keys == null || keys.length == 0) { return newSucceededFuture(0L); @@ -184,6 +189,72 @@ public abstract class RedissonMultimap extends RedissonExpirable implement throw new RuntimeException(e); } } + + @Override + public Future deleteAsync() { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {KEYS[1]}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end;" + + "end; " + + + "local n = 0 " + + "for i=1, #keys,5000 do " + + "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + + "end; " + + "return n;", + Arrays.asList(getName())); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpire', name, ARGV[1]); " + + "end;" + + "end; " + + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", + Arrays.asList(getName()), timeUnit.toMillis(timeToLive)); + } + + @Override + public Future expireAtAsync(long timestamp) { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpireat', name, ARGV[1]); " + + "end;" + + "end; " + + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", + Arrays.asList(getName()), timestamp); + } + + @Override + public Future clearExpireAsync() { + return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('persist', name); " + + "end;" + + "end; " + + "return redis.call('persist', KEYS[1]); ", + Arrays.asList(getName())); + } + MapScanResult scanIterator(InetSocketAddress client, long startPos) { Future> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec, StringCodec.INSTANCE), RedisCommands.HSCAN, getName(), startPos); diff --git a/src/test/java/org/redisson/RedissonSetMultimapTest.java b/src/test/java/org/redisson/RedissonSetMultimapTest.java index ef47fd980..c30256c53 100644 --- a/src/test/java/org/redisson/RedissonSetMultimapTest.java +++ b/src/test/java/org/redisson/RedissonSetMultimapTest.java @@ -1,15 +1,17 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.redisson.core.RSetMultimap; -import static org.assertj.core.api.Assertions.*; public class RedissonSetMultimapTest extends BaseTest { @@ -273,5 +275,56 @@ public class RedissonSetMultimapTest extends BaseTest { assertThat(allValues).containsOnlyElementsOf(values); } + @Test + public void testExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expire(100, TimeUnit.MILLISECONDS); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testExpireAt() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testClearExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + map.clearExpire(); + + Thread.sleep(500); + + assertThat(map.size()).isEqualTo(2); + } + + @Test + public void testDelete() { + RSetMultimap map = redisson.getSetMultimap("simple"); + map.put("1", "2"); + map.put("2", "3"); + assertThat(map.delete()).isTrue(); + + RSetMultimap map2 = redisson.getSetMultimap("simple1"); + assertThat(map2.delete()).isFalse(); + } } From dead46a732846e7f45209f5496e03025c1d7c63d Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:45:31 +0300 Subject: [PATCH 36/88] minor changes --- src/main/java/org/redisson/RedissonMultimap.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/redisson/RedissonMultimap.java b/src/main/java/org/redisson/RedissonMultimap.java index 53f4b1944..a072520a5 100644 --- a/src/main/java/org/redisson/RedissonMultimap.java +++ b/src/main/java/org/redisson/RedissonMultimap.java @@ -214,7 +214,6 @@ public abstract class RedissonMultimap extends RedissonExpirable implement public Future expireAsync(long timeToLive, TimeUnit timeUnit) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local entries = redis.call('hgetall', KEYS[1]); " + - "local keys = {}; " + "for i, v in ipairs(entries) do " + "if i % 2 == 0 then " + "local name = '{' .. KEYS[1] .. '}:' .. v; " + @@ -229,7 +228,6 @@ public abstract class RedissonMultimap extends RedissonExpirable implement public Future expireAtAsync(long timestamp) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local entries = redis.call('hgetall', KEYS[1]); " + - "local keys = {}; " + "for i, v in ipairs(entries) do " + "if i % 2 == 0 then " + "local name = '{' .. KEYS[1] .. '}:' .. v; " + @@ -244,7 +242,6 @@ public abstract class RedissonMultimap extends RedissonExpirable implement public Future clearExpireAsync() { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local entries = redis.call('hgetall', KEYS[1]); " + - "local keys = {}; " + "for i, v in ipairs(entries) do " + "if i % 2 == 0 then " + "local name = '{' .. KEYS[1] .. '}:' .. v; " + From 29c30a307e18f86b1bcbc75da80b94ebf618e8e9 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 10:59:57 +0300 Subject: [PATCH 37/88] Delete method fixed --- src/main/java/org/redisson/RedissonBloomFilter.java | 2 +- src/main/java/org/redisson/RedissonMapCache.java | 2 +- src/main/java/org/redisson/RedissonSetCache.java | 2 +- .../java/org/redisson/client/protocol/RedisCommands.java | 1 + .../org/redisson/reactive/RedissonMapCacheReactive.java | 2 +- .../org/redisson/reactive/RedissonSetCacheReactive.java | 2 +- src/test/java/org/redisson/RedissonSetCacheTest.java | 9 +++++++++ 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/redisson/RedissonBloomFilter.java b/src/main/java/org/redisson/RedissonBloomFilter.java index 6b6913039..06fb7b6db 100644 --- a/src/main/java/org/redisson/RedissonBloomFilter.java +++ b/src/main/java/org/redisson/RedissonBloomFilter.java @@ -200,7 +200,7 @@ public class RedissonBloomFilter extends RedissonExpirable implements RBloomF @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getConfigName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getConfigName()); } private void readConfig() { diff --git a/src/main/java/org/redisson/RedissonMapCache.java b/src/main/java/org/redisson/RedissonMapCache.java index 8900fd119..d529c2b03 100644 --- a/src/main/java/org/redisson/RedissonMapCache.java +++ b/src/main/java/org/redisson/RedissonMapCache.java @@ -655,7 +655,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 8a9083c75..97f8526a7 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -474,7 +474,7 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @Override public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/client/protocol/RedisCommands.java b/src/main/java/org/redisson/client/protocol/RedisCommands.java index 7d62ca62e..9528685ce 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -208,6 +208,7 @@ public interface RedisCommands { RedisStrictCommand DEL = new RedisStrictCommand("DEL"); RedisStrictCommand DBSIZE = new RedisStrictCommand("DBSIZE"); RedisStrictCommand DEL_BOOL = new RedisStrictCommand("DEL", new BooleanReplayConvertor()); + RedisStrictCommand DEL_OBJECTS = new RedisStrictCommand("DEL", new BooleanAmountReplayConvertor()); RedisStrictCommand DEL_VOID = new RedisStrictCommand("DEL", new VoidReplayConvertor()); RedisCommand GET = new RedisCommand("GET"); diff --git a/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java b/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java index 183f9e460..85acad239 100644 --- a/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java @@ -361,7 +361,7 @@ public class RedissonMapCacheReactive extends RedissonMapReactive im @Override public Publisher delete() { - return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java index 3bb427930..bce238a28 100644 --- a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java @@ -305,7 +305,7 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple @Override public Publisher delete() { - return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName()); + return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); } @Override diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index a864425fd..9d7c71188 100644 --- a/src/test/java/org/redisson/RedissonSetCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheTest.java @@ -31,6 +31,15 @@ public class RedissonSetCacheTest extends BaseTest { } } + + @Test + public void testDelete() { + RSetCache set = redisson.getSetCache("set"); + assertThat(set.delete()).isFalse(); + set.add(1, 1, TimeUnit.SECONDS); + assertThat(set.delete()).isTrue(); + assertThat(set.delete()).isFalse(); + } @Test public void testEmptyReadAll() { From 928898dc2d8418a6b3d53437c71daa73035301e3 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:00:06 +0300 Subject: [PATCH 38/88] RedissonKeysTest refactoring --- src/test/java/org/redisson/RedissonKeysTest.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/redisson/RedissonKeysTest.java b/src/test/java/org/redisson/RedissonKeysTest.java index 26f22b751..4380d26df 100644 --- a/src/test/java/org/redisson/RedissonKeysTest.java +++ b/src/test/java/org/redisson/RedissonKeysTest.java @@ -1,14 +1,12 @@ package org.redisson; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RBucket; @@ -26,7 +24,7 @@ public class RedissonKeysTest extends BaseTest { Iterator iterator = redisson.getKeys().getKeysByPattern("test?").iterator(); for (; iterator.hasNext();) { String key = iterator.next(); - MatcherAssert.assertThat(key, Matchers.isOneOf("test1", "test2")); + assertThat(key).isIn("test1", "test2"); } } @@ -57,7 +55,7 @@ public class RedissonKeysTest extends BaseTest { RBucket bucket2 = redisson.getBucket("test2"); bucket2.set("someValue2"); - MatcherAssert.assertThat(redisson.getKeys().randomKey(), Matchers.isOneOf("test1", "test2")); + assertThat(redisson.getKeys().randomKey()).isIn("test1", "test2"); redisson.getKeys().delete("test1"); Assert.assertEquals(redisson.getKeys().randomKey(), "test2"); redisson.flushdb(); @@ -95,10 +93,10 @@ public class RedissonKeysTest extends BaseTest { map.fastPut("1", "2"); Collection keys = redisson.getKeys().findKeysByPattern("test?"); - MatcherAssert.assertThat(keys, Matchers.containsInAnyOrder("test1", "test2")); + assertThat(keys).containsOnly("test1", "test2"); Collection keys2 = redisson.getKeys().findKeysByPattern("test"); - MatcherAssert.assertThat(keys2, Matchers.empty()); + assertThat(keys2).isEmpty(); } @Test From 36487763c040f4581a3c3e4596ecf8732e9d57df Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:49:14 +0300 Subject: [PATCH 39/88] Delete and expire MultimapCache methods fixed. #447 --- .../redisson/RedissonListMultimapCache.java | 42 ++++--- .../org/redisson/RedissonMultimapCache.java | 114 ++++++++++++++++++ .../redisson/RedissonSetMultimapCache.java | 42 ++++--- .../RedissonSetMultimapCacheTest.java | 57 ++++++++- 4 files changed, 215 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonMultimapCache.java diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java index 62a4800ec..2dfce5dd0 100644 --- a/src/main/java/org/redisson/RedissonListMultimapCache.java +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -22,10 +22,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; -import org.redisson.client.protocol.RedisCommand; -import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommands; -import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RListMultimapCache; @@ -39,16 +36,18 @@ import io.netty.util.concurrent.Future; */ public class RedissonListMultimapCache extends RedissonListMultimap implements RListMultimapCache { - private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + private final RedissonMultimapCache baseCache; RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { @@ -207,20 +206,27 @@ public class RedissonListMultimapCache extends RedissonListMultimap @Override public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { - long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); - - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_EXPIRE_KEY, - "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " - + "if tonumber(ARGV[1]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + - "else " + - "redis.call('zrem', KEYS[2], ARGV[2]); " - + "end; " - + "return 1; " - + "else " - + "return 0; " - + "end", - Arrays.asList(getName(), getTimeoutSetName()), ttlTimeout, key); + return baseCache.expireKeyAsync(key, timeToLive, timeUnit); + } + + @Override + public Future deleteAsync() { + return baseCache.deleteAsync(); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return baseCache.expireAsync(timeToLive, timeUnit); + } + + @Override + public Future expireAtAsync(long timestamp) { + return baseCache.expireAtAsync(timestamp); + } + + @Override + public Future clearExpireAsync() { + return baseCache.clearExpireAsync(); } } diff --git a/src/main/java/org/redisson/RedissonMultimapCache.java b/src/main/java/org/redisson/RedissonMultimapCache.java new file mode 100644 index 000000000..a33ce15f5 --- /dev/null +++ b/src/main/java/org/redisson/RedissonMultimapCache.java @@ -0,0 +1,114 @@ +package org.redisson; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.LongCodec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.convertor.BooleanReplayConvertor; +import org.redisson.command.CommandAsyncExecutor; + +import io.netty.util.concurrent.Future; + +public class RedissonMultimapCache { + + private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + + private final CommandAsyncExecutor commandExecutor; + private final String name; + private final Codec codec; + private final String timeoutSetName; + + public RedissonMultimapCache(CommandAsyncExecutor commandExecutor, String name, Codec codec, String timeoutSetName) { + this.commandExecutor = commandExecutor; + this.name = name; + this.codec = codec; + this.timeoutSetName = timeoutSetName; + } + + public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { + long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); + + return commandExecutor.evalWriteAsync(name, codec, EVAL_EXPIRE_KEY, + "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " + + "if tonumber(ARGV[1]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "end; " + + "return 1; " + + "else " + + "return 0; " + + "end", + Arrays.asList(name, timeoutSetName), ttlTimeout, key); + } + + public Future deleteAsync() { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT, + "local entries = redis.call('hgetall', KEYS[1]); " + + "local keys = {KEYS[1], KEYS[2]}; " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "table.insert(keys, name); " + + "end;" + + "end; " + + + "local n = 0 " + + "for i=1, #keys,5000 do " + + "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + + "end; " + + "return n;", + Arrays.asList(name, timeoutSetName)); + } + + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag'); " + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpire', name, ARGV[1]); " + + "end;" + + "end; " + + "redis.call('pexpire', KEYS[2], ARGV[1]); " + + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", + Arrays.asList(name, timeoutSetName), timeUnit.toMillis(timeToLive)); + } + + public Future expireAtAsync(long timestamp) { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('pexpireat', name, ARGV[1]); " + + "end;" + + "end; " + + "redis.call('pexpireat', KEYS[2], ARGV[1]); " + + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", + Arrays.asList(name, timeoutSetName), timestamp); + } + + public Future clearExpireAsync() { + return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, + "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + + "local entries = redis.call('hgetall', KEYS[1]); " + + "for i, v in ipairs(entries) do " + + "if i % 2 == 0 then " + + "local name = '{' .. KEYS[1] .. '}:' .. v; " + + "redis.call('persist', name); " + + "end;" + + "end; " + + "redis.call('persist', KEYS[2]); " + + "return redis.call('persist', KEYS[1]); ", + Arrays.asList(name, timeoutSetName)); + } + + +} diff --git a/src/main/java/org/redisson/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java index 9b7dee226..ea4b4192a 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapCache.java +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -22,10 +22,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; -import org.redisson.client.protocol.RedisCommand; -import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommands; -import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RSetMultimapCache; @@ -39,16 +36,18 @@ import io.netty.util.concurrent.Future; */ public class RedissonSetMultimapCache extends RedissonSetMultimap implements RSetMultimapCache { - private static final RedisCommand EVAL_EXPIRE_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY); + private final RedissonMultimapCache baseCache; RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } RedissonSetMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); + baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { @@ -199,20 +198,27 @@ public class RedissonSetMultimapCache extends RedissonSetMultimap im @Override public Future expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) { - long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive); - - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_EXPIRE_KEY, - "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " - + "if tonumber(ARGV[1]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " + - "else " + - "redis.call('zrem', KEYS[2], ARGV[2]); " - + "end; " - + "return 1; " - + "else " - + "return 0; " - + "end", - Arrays.asList(getName(), getTimeoutSetName()), ttlTimeout, key); + return baseCache.expireKeyAsync(key, timeToLive, timeUnit); + } + + @Override + public Future deleteAsync() { + return baseCache.deleteAsync(); + } + + @Override + public Future expireAsync(long timeToLive, TimeUnit timeUnit) { + return baseCache.expireAsync(timeToLive, timeUnit); + } + + @Override + public Future expireAtAsync(long timestamp) { + return baseCache.expireAtAsync(timestamp); + } + + @Override + public Future clearExpireAsync() { + return baseCache.clearExpireAsync(); } } diff --git a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java index 7fe68f557..442855b8c 100644 --- a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java @@ -1,15 +1,13 @@ package org.redisson; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; import java.util.concurrent.TimeUnit; -import org.junit.Assert; import org.junit.Test; -import org.redisson.codec.MsgPackJacksonCodec; import org.redisson.core.RMultimapCache; -import org.redisson.core.RSetCache; +import org.redisson.core.RSetMultimap; public class RedissonSetMultimapCacheTest extends BaseTest { @@ -137,5 +135,56 @@ public class RedissonSetMultimapCacheTest extends BaseTest { } + @Test + public void testExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expire(100, TimeUnit.MILLISECONDS); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testExpireAt() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + Thread.sleep(500); + + assertThat(map.size()).isZero(); + } + + @Test + public void testClearExpire() throws InterruptedException { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + + map.expireAt(System.currentTimeMillis() + 100); + + map.clearExpire(); + + Thread.sleep(500); + + assertThat(map.size()).isEqualTo(2); + } + + @Test + public void testDelete() { + RSetMultimap map = redisson.getSetMultimapCache("simple"); + map.put("1", "2"); + map.put("2", "3"); + assertThat(map.delete()).isTrue(); + + RSetMultimap map2 = redisson.getSetMultimapCache("simple1"); + assertThat(map2.delete()).isFalse(); + } } From f66db14ff7ab4a427f6e3f51957339af2defa557 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:49:41 +0300 Subject: [PATCH 40/88] license added --- .../java/org/redisson/RedissonMultimapCache.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/redisson/RedissonMultimapCache.java b/src/main/java/org/redisson/RedissonMultimapCache.java index a33ce15f5..765ab8021 100644 --- a/src/main/java/org/redisson/RedissonMultimapCache.java +++ b/src/main/java/org/redisson/RedissonMultimapCache.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.redisson; import java.util.Arrays; From cdd50516ac3fe64dad4c64a45729e13e85af70f0 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 11:50:55 +0300 Subject: [PATCH 41/88] compilation fixed --- src/main/java/org/redisson/RedissonListMultimapCache.java | 4 ++-- src/main/java/org/redisson/RedissonSetMultimapCache.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java index 2dfce5dd0..2ba95e527 100644 --- a/src/main/java/org/redisson/RedissonListMultimapCache.java +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -41,13 +41,13 @@ public class RedissonListMultimapCache extends RedissonListMultimap RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { diff --git a/src/main/java/org/redisson/RedissonSetMultimapCache.java b/src/main/java/org/redisson/RedissonSetMultimapCache.java index ea4b4192a..4254d3ed6 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapCache.java +++ b/src/main/java/org/redisson/RedissonSetMultimapCache.java @@ -41,13 +41,13 @@ public class RedissonSetMultimapCache extends RedissonSetMultimap im RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) { super(connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } RedissonSetMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) { super(codec, connectionManager, name); evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName()); - baseCache = new RedissonMultimapCache<>(connectionManager, name, codec, getTimeoutSetName()); + baseCache = new RedissonMultimapCache(connectionManager, name, codec, getTimeoutSetName()); } public Future containsKeyAsync(Object key) { From 75ba40227df09878a709f5c814fdc2d6931e3e26 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:03:56 +0300 Subject: [PATCH 42/88] MOVED, ASK handling in cluster mode using RBatch. #448 --- .../java/org/redisson/client/handler/CommandDecoder.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index ac868aafd..d8ce3d733 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -139,7 +139,11 @@ public class CommandDecoder extends ReplayingDecoder { cmd.getPromise().tryFailure(e); } if (!cmd.getPromise().isSuccess()) { - error = (RedisException) cmd.getPromise().cause(); + if (!(cmd.getPromise().cause() instanceof RedisMovedException + || cmd.getPromise().cause() instanceof RedisAskException + || cmd.getPromise().cause() instanceof RedisLoadingException)) { + error = (RedisException) cmd.getPromise().cause(); + } } } From fb68ca68113a4b9a91036058ff6a0d31d0e1e583 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:04:04 +0300 Subject: [PATCH 43/88] test fixes --- src/test/java/org/redisson/BaseTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/redisson/BaseTest.java b/src/test/java/org/redisson/BaseTest.java index e1190e384..48c549c92 100644 --- a/src/test/java/org/redisson/BaseTest.java +++ b/src/test/java/org/redisson/BaseTest.java @@ -1,10 +1,8 @@ package org.redisson; -import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; -import org.redisson.client.codec.StringCodec; -import org.redisson.codec.MsgPackJacksonCodec; public abstract class BaseTest { @@ -43,9 +41,9 @@ public abstract class BaseTest { return Redisson.create(config); } - @After - public void after() { - redisson.flushdb(); + @Before + public void before() { + redisson.getKeys().flushall(); } } From fa4bb81023ba2ec6c7bfce8ea3de8a01076ed631 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:45:16 +0300 Subject: [PATCH 44/88] [maven-release-plugin] prepare release redisson-2.2.10 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8907f379a..f553c4252 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.10-SNAPSHOT + 2.2.10 bundle Redisson @@ -15,7 +15,7 @@ scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git - HEAD + redisson-2.2.10 From e909110798d0e0cd69d17a72803c6f692879ce8b Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 23 Mar 2016 13:45:23 +0300 Subject: [PATCH 45/88] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f553c4252..106c3a909 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.10 + 2.2.11-SNAPSHOT bundle Redisson @@ -15,7 +15,7 @@ scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git - redisson-2.2.10 + HEAD From 709a3f1c31657560408bf540259ca9839b55cda2 Mon Sep 17 00:00:00 2001 From: Rui Gu Date: Wed, 23 Mar 2016 11:56:18 +0000 Subject: [PATCH 46/88] Revert "change testManyConnections connection size to 2000" This reverts commit 255db08d835f319a4365478240a4d76132b66ffd. --- src/test/java/org/redisson/RedissonTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 7eb2da834..92b551c11 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -264,8 +264,8 @@ public class RedissonTest { public void testManyConnections() { Config redisConfig = new Config(); redisConfig.useSingleServer() - .setConnectionMinimumIdleSize(2000) - .setConnectionPoolSize(2000) + .setConnectionMinimumIdleSize(10000) + .setConnectionPoolSize(10000) .setAddress("localhost:6379"); RedissonClient r = Redisson.create(redisConfig); r.shutdown(); From 449e6e3df476b079ac6d113e20bbdbfa41d38cfb Mon Sep 17 00:00:00 2001 From: Rui Gu Date: Wed, 23 Mar 2016 12:11:57 +0000 Subject: [PATCH 47/88] added travisEnv property used to ignore some tests ignore testManyConnections when travisEnv property is set to true --- .travis.yml | 2 +- src/test/java/org/redisson/RedissonTest.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 276b6f248..e2347a31c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,4 +34,4 @@ before_script: - $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" -Punit-test -Ptravis clean verify +script: mvn -DargLine="-DredisBinary=$REDIS_BIN/redis-server -DtravisEnv=true" -Punit-test -Ptravis clean verify diff --git a/src/test/java/org/redisson/RedissonTest.java b/src/test/java/org/redisson/RedissonTest.java index 92b551c11..75f5519bf 100644 --- a/src/test/java/org/redisson/RedissonTest.java +++ b/src/test/java/org/redisson/RedissonTest.java @@ -12,6 +12,7 @@ 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.RedisRunner.RedisProcess; import org.redisson.client.RedisConnectionException; @@ -262,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) From cf3a53888ac51f798b5ef1a580a27daf4bdeaa33 Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Thu, 24 Mar 2016 12:25:42 +0300 Subject: [PATCH 48/88] Update CHANGELOG.md --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9295791..4ffd4f0fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ Redisson Releases History ================================ ####Please Note: trunk is current development branch. +####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` From a523da1a9ce4e9095f0ea8d1a6d0e81601cb9577 Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Thu, 24 Mar 2016 13:54:29 +0300 Subject: [PATCH 49/88] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3bf37cb73..2d94762a1 100644 --- a/README.md +++ b/README.md @@ -81,12 +81,12 @@ Include the following to your dependency list: org.redisson redisson - 2.2.9 + 2.2.10 ### Gradle - compile 'org.redisson:redisson:2.2.9' + compile 'org.redisson:redisson:2.2.10' ### Supported by From bb7a14c4ba91666c31f50db76c6752e9b5a4a715 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 24 Mar 2016 18:02:42 +0300 Subject: [PATCH 50/88] RemoteService ack support added --- .../org/redisson/RedissonRemoteService.java | 119 +++++++++++++----- .../redisson/RedissonShutdownException.java | 11 ++ .../org/redisson/codec/JsonJacksonCodec.java | 1 + .../redisson/command/CommandAsyncService.java | 3 +- .../MasterSlaveConnectionManager.java | 4 +- .../org/redisson/core/RRemoteService.java | 64 +++++++++- .../redisson/misc/InfinitySemaphoreLatch.java | 7 +- .../remote/RRemoteServiceResponse.java | 5 + .../org/redisson/remote/RemoteServiceAck.java | 11 ++ .../RemoteServiceAckTimeoutException.java | 21 ++++ .../{ => remote}/RemoteServiceKey.java | 2 +- .../{ => remote}/RemoteServiceMethod.java | 2 +- .../{ => remote}/RemoteServiceRequest.java | 20 ++- .../{ => remote}/RemoteServiceResponse.java | 4 +- .../remote/RemoteServiceTimeoutException.java | 17 +++ .../redisson/RedissonRemoteServiceTest.java | 12 +- 16 files changed, 251 insertions(+), 52 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonShutdownException.java create mode 100644 src/main/java/org/redisson/remote/RRemoteServiceResponse.java create mode 100644 src/main/java/org/redisson/remote/RemoteServiceAck.java create mode 100644 src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java rename src/main/java/org/redisson/{ => remote}/RemoteServiceKey.java (98%) rename src/main/java/org/redisson/{ => remote}/RemoteServiceMethod.java (97%) rename src/main/java/org/redisson/{ => remote}/RemoteServiceRequest.java (70%) rename src/main/java/org/redisson/{ => remote}/RemoteServiceResponse.java (92%) create mode 100644 src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index b80124260..ba99b01a4 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -23,12 +23,18 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.redisson.client.RedisException; -import org.redisson.client.RedisTimeoutException; import org.redisson.core.MessageListener; import org.redisson.core.RBlockingQueue; import org.redisson.core.RRemoteService; import org.redisson.core.RTopic; +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; @@ -81,71 +87,126 @@ public class RedissonRemoteService implements RRemoteService { @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { + if (future.cause() instanceof RedissonShutdownException) { + return; + } + subscribe(remoteInterface, requestQueue); return; } + subscribe(remoteInterface, requestQueue); RemoteServiceRequest request = future.getNow(); + if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) { + log.debug("request: {} has been skipped due to ackTimeout"); + return; + } + RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId(); - RTopic topic = redisson.getTopic(responseName); - RemoteServiceResponse response; - try { - Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); - response = new RemoteServiceResponse(result); - } catch (Exception e) { - response = new RemoteServiceResponse(e.getCause()); - log.error("Can't execute: " + request, e); + RTopic topic = redisson.getTopic(responseName); + Future ackClientsFuture = topic.publishAsync(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; + } + if (future.getNow() == 0) { + log.error("Client has not received ack for request: {}", request); + return; + } + + invokeMethod(request, method, topic); + } + }); + } + + }); + } + + private void invokeMethod(RemoteServiceRequest request, RemoteServiceMethod method, + RTopic topic) { + 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 = topic.publishAsync(responseHolder.get()); + clientsFuture.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + return; } - long clients = topic.publish(response); - if (clients == 0) { - log.error("None of clients has not received a response: {} for request: {}", response, request); + if (future.getNow() == 0) { + log.error("None of clients has not received a response: {} for request: {}", responseHolder.get(), request); } - - subscribe(remoteInterface, requestQueue); } }); } - + @Override public T get(Class remoteInterface) { return get(remoteInterface, -1, null); } @Override - public T get(final Class remoteInterface, final int timeout, final TimeUnit timeUnit) { + 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); + RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args, ackTimeUnit.toMillis(ackTimeout), System.currentTimeMillis()); requestQueue.add(request); String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; - final RTopic topic = redisson.getTopic(responseName); + final CountDownLatch ackLatch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference response = new AtomicReference(); - int listenerId = topic.addListener(new MessageListener() { + final AtomicReference response = new AtomicReference(); + final RTopic topic = redisson.getTopic(responseName); + int listenerId = topic.addListener(new MessageListener() { @Override - public void onMessage(String channel, RemoteServiceResponse msg) { - response.set(msg); - latch.countDown(); + public void onMessage(String channel, RRemoteServiceResponse msg) { + if (msg instanceof RemoteServiceResponse) { + response.set(msg); + latch.countDown(); + } else { + ackLatch.countDown(); + } } }); - if (timeout == -1) { + if (!ackLatch.await(ackTimeout, ackTimeUnit)) { + topic.removeListener(listenerId); + throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request); + } + + if (executionTimeout == -1) { latch.await(); } else { - if (!latch.await(timeout, timeUnit)) { + if (!latch.await(executionTimeout, executionTimeUnit)) { topic.removeListener(listenerId); - throw new RedisTimeoutException("No response after " + timeUnit.toMillis(timeout) + "ms for request: " + request); + throw new RemoteServiceTimeoutException("No response after " + executionTimeUnit.toMillis(executionTimeout) + "ms for request: " + request); } } topic.removeListener(listenerId); - RemoteServiceResponse msg = response.get(); + RemoteServiceResponse msg = (RemoteServiceResponse) response.get(); if (msg.getError() != null) { throw msg.getError(); } diff --git a/src/main/java/org/redisson/RedissonShutdownException.java b/src/main/java/org/redisson/RedissonShutdownException.java new file mode 100644 index 000000000..629255e0e --- /dev/null +++ b/src/main/java/org/redisson/RedissonShutdownException.java @@ -0,0 +1,11 @@ +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/codec/JsonJacksonCodec.java b/src/main/java/org/redisson/codec/JsonJacksonCodec.java index ab518b0ed..e90c5cef2 100755 --- a/src/main/java/org/redisson/codec/JsonJacksonCodec.java +++ b/src/main/java/org/redisson/codec/JsonJacksonCodec.java @@ -124,6 +124,7 @@ 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); } diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index faba6d1fc..2abe97b96 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.redisson.RedisClientResult; +import org.redisson.RedissonShutdownException; import org.redisson.SlotCallback; import org.redisson.client.RedisAskException; import org.redisson.client.RedisConnection; @@ -354,7 +355,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; } diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 065e66f75..fe2a22872 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -677,8 +677,10 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdown() { + shutdownLatch.close(); shutdownPromise.trySuccess(true); - shutdownLatch.closeAndAwaitUninterruptibly(); + shutdownLatch.awaitUninterruptibly(); + for (MasterSlaveEntry entry : entries.values()) { entry.shutdown(); } diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index 1b75c5dc3..0461188ed 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -17,6 +17,46 @@ package org.redisson.core; import java.util.concurrent.TimeUnit; +import org.redisson.remote.RemoteServiceAckTimeoutException; + +/** + * 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 { /** @@ -37,7 +77,8 @@ public interface RRemoteService { void register(Class remoteInterface, T object, int executorsAmount); /** - * Get remote service object for remote invocations + * Get remote service object for remote invocations. + * Uses ack timeout = 1000 ms by default * * @param remoteInterface * @return @@ -46,13 +87,26 @@ public interface RRemoteService { /** * Get remote service object for remote invocations - * with specified invocation timeout + * with specified invocation timeout. Uses 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 timeout - invocation timeout - * @param timeUnit + * @param executionTimeout - invocation timeout + * @param executionTimeUnit + * @param ackTimeout - ack timeout + * @param ackTimeUnit * @return */ - T get(Class remoteInterface, int timeout, TimeUnit timeUnit); + T get(Class remoteInterface, long executionTimeout, TimeUnit executionTimeUnit, long ackTimeout, TimeUnit ackTimeUnit); } 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/remote/RRemoteServiceResponse.java b/src/main/java/org/redisson/remote/RRemoteServiceResponse.java new file mode 100644 index 000000000..de563757e --- /dev/null +++ b/src/main/java/org/redisson/remote/RRemoteServiceResponse.java @@ -0,0 +1,5 @@ +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..497fe1e7b --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceAck.java @@ -0,0 +1,11 @@ +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..76d4b979e --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java @@ -0,0 +1,21 @@ +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/RemoteServiceKey.java b/src/main/java/org/redisson/remote/RemoteServiceKey.java similarity index 98% rename from src/main/java/org/redisson/RemoteServiceKey.java rename to src/main/java/org/redisson/remote/RemoteServiceKey.java index 5cadb3a5a..20f7ea9ff 100644 --- a/src/main/java/org/redisson/RemoteServiceKey.java +++ b/src/main/java/org/redisson/remote/RemoteServiceKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.redisson; +package org.redisson.remote; public class RemoteServiceKey { diff --git a/src/main/java/org/redisson/RemoteServiceMethod.java b/src/main/java/org/redisson/remote/RemoteServiceMethod.java similarity index 97% rename from src/main/java/org/redisson/RemoteServiceMethod.java rename to src/main/java/org/redisson/remote/RemoteServiceMethod.java index 153c82d19..26998214f 100644 --- a/src/main/java/org/redisson/RemoteServiceMethod.java +++ b/src/main/java/org/redisson/remote/RemoteServiceMethod.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.redisson; +package org.redisson.remote; import java.lang.reflect.Method; diff --git a/src/main/java/org/redisson/RemoteServiceRequest.java b/src/main/java/org/redisson/remote/RemoteServiceRequest.java similarity index 70% rename from src/main/java/org/redisson/RemoteServiceRequest.java rename to src/main/java/org/redisson/remote/RemoteServiceRequest.java index 434784456..f47324f10 100644 --- a/src/main/java/org/redisson/RemoteServiceRequest.java +++ b/src/main/java/org/redisson/remote/RemoteServiceRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.redisson; +package org.redisson.remote; import java.util.Arrays; @@ -22,15 +22,27 @@ public class RemoteServiceRequest { private String requestId; private String methodName; private Object[] args; + private long ackTimeout; + private long date; public RemoteServiceRequest() { } - public RemoteServiceRequest(String requestId, String methodName, Object[] args) { + public RemoteServiceRequest(String requestId, String methodName, Object[] args, long ackTimeout, long date) { super(); this.requestId = requestId; this.methodName = methodName; this.args = args; + this.ackTimeout = ackTimeout; + this.date = date; + } + + public long getDate() { + return date; + } + + public long getAckTimeout() { + return ackTimeout; } public String getRequestId() { @@ -47,8 +59,8 @@ public class RemoteServiceRequest { @Override public String toString() { - return "RemoteServiceRequest[requestId=" + requestId + ", methodName=" + methodName + ", args=" - + Arrays.toString(args) + "]"; + return "RemoteServiceRequest [requestId=" + requestId + ", methodName=" + methodName + ", args=" + + Arrays.toString(args) + ", ackTimeout=" + ackTimeout + ", date=" + date + "]"; } } diff --git a/src/main/java/org/redisson/RemoteServiceResponse.java b/src/main/java/org/redisson/remote/RemoteServiceResponse.java similarity index 92% rename from src/main/java/org/redisson/RemoteServiceResponse.java rename to src/main/java/org/redisson/remote/RemoteServiceResponse.java index 09ebc5999..55d074870 100644 --- a/src/main/java/org/redisson/RemoteServiceResponse.java +++ b/src/main/java/org/redisson/remote/RemoteServiceResponse.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.redisson; +package org.redisson.remote; -public class RemoteServiceResponse { +public class RemoteServiceResponse implements RRemoteServiceResponse { private Object result; private Throwable error; diff --git a/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java new file mode 100644 index 000000000..27316abf6 --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java @@ -0,0 +1,17 @@ +package org.redisson.remote; + +/** + * Rises when invocation timeout has been occurred + * + * @author Nikita Koksharov + * + */ +public class RemoteServiceTimeoutException extends RuntimeException { + + private static final long serialVersionUID = -1749266931994840256L; + + public RemoteServiceTimeoutException(String message) { + super(message); + } + +} diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java index c8daedd90..5739bc7bc 100644 --- a/src/test/java/org/redisson/RedissonRemoteServiceTest.java +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -1,14 +1,14 @@ package org.redisson; -import org.junit.Assert; -import org.junit.Test; -import org.redisson.client.RedisTimeoutException; - -import static org.assertj.core.api.Assertions.*; +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 { @@ -59,7 +59,7 @@ public class RedissonRemoteServiceTest extends BaseTest { } - @Test(expected = RedisTimeoutException.class) + @Test(expected = RemoteServiceTimeoutException.class) public void testTimeout() throws InterruptedException { RedissonClient r1 = Redisson.create(); r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); From c2414c3864f49956bb3b93b2f8aa22a7da088a17 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 24 Mar 2016 18:03:00 +0300 Subject: [PATCH 51/88] licenses added --- .../org/redisson/RedissonShutdownException.java | 15 +++++++++++++++ .../redisson/remote/RRemoteServiceResponse.java | 15 +++++++++++++++ .../org/redisson/remote/RemoteServiceAck.java | 15 +++++++++++++++ .../remote/RemoteServiceAckTimeoutException.java | 15 +++++++++++++++ .../remote/RemoteServiceTimeoutException.java | 15 +++++++++++++++ 5 files changed, 75 insertions(+) diff --git a/src/main/java/org/redisson/RedissonShutdownException.java b/src/main/java/org/redisson/RedissonShutdownException.java index 629255e0e..ddfcb1aa1 100644 --- a/src/main/java/org/redisson/RedissonShutdownException.java +++ b/src/main/java/org/redisson/RedissonShutdownException.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.redisson; public class RedissonShutdownException extends RuntimeException { diff --git a/src/main/java/org/redisson/remote/RRemoteServiceResponse.java b/src/main/java/org/redisson/remote/RRemoteServiceResponse.java index de563757e..67305dd00 100644 --- a/src/main/java/org/redisson/remote/RRemoteServiceResponse.java +++ b/src/main/java/org/redisson/remote/RRemoteServiceResponse.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.redisson.remote; public interface RRemoteServiceResponse { diff --git a/src/main/java/org/redisson/remote/RemoteServiceAck.java b/src/main/java/org/redisson/remote/RemoteServiceAck.java index 497fe1e7b..2d34c4804 100644 --- a/src/main/java/org/redisson/remote/RemoteServiceAck.java +++ b/src/main/java/org/redisson/remote/RemoteServiceAck.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.redisson.remote; /** diff --git a/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java b/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java index 76d4b979e..566fc61b9 100644 --- a/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java +++ b/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.redisson.remote; /** diff --git a/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java index 27316abf6..b5d5720c0 100644 --- a/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java +++ b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.redisson.remote; /** From c503d445987d2a27f89eeafe98b41897cbf41091 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 24 Mar 2016 19:47:00 +0300 Subject: [PATCH 52/88] Compilation fixed --- src/main/java/org/redisson/RedissonRemoteService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index ba99b01a4..d424128a3 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -95,15 +95,15 @@ public class RedissonRemoteService implements RRemoteService { } subscribe(remoteInterface, requestQueue); - RemoteServiceRequest request = future.getNow(); + final RemoteServiceRequest request = future.getNow(); if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) { log.debug("request: {} has been skipped due to ackTimeout"); return; } - RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); + final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId(); - RTopic topic = redisson.getTopic(responseName); + final RTopic topic = redisson.getTopic(responseName); Future ackClientsFuture = topic.publishAsync(new RemoteServiceAck()); ackClientsFuture.addListener(new FutureListener() { @Override @@ -125,7 +125,7 @@ public class RedissonRemoteService implements RRemoteService { }); } - private void invokeMethod(RemoteServiceRequest request, RemoteServiceMethod method, + private void invokeMethod(final RemoteServiceRequest request, RemoteServiceMethod method, RTopic topic) { final AtomicReference responseHolder = new AtomicReference(); try { From af01f891b713b8b01f6c453a79f5ca40a5fb6777 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 24 Mar 2016 19:47:22 +0300 Subject: [PATCH 53/88] libs updated --- pom.xml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 106c3a909..111ee57f8 100644 --- a/pom.xml +++ b/pom.xml @@ -100,33 +100,33 @@ io.netty netty-transport-native-epoll - 4.0.34.Final + 4.0.35.Final provided io.netty netty-common - 4.0.34.Final + 4.0.35.Final io.netty netty-codec - 4.0.34.Final + 4.0.35.Final io.netty netty-buffer - 4.0.34.Final + 4.0.35.Final io.netty netty-transport - 4.0.34.Final + 4.0.35.Final io.netty netty-handler - 4.0.34.Final + 4.0.35.Final @@ -145,6 +145,7 @@ com.jayway.awaitility awaitility 1.7.0 + test junit From faf8dd5975b3c7aeb18881a3c391b291004cb3cb Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Thu, 24 Mar 2016 19:49:13 +0300 Subject: [PATCH 54/88] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2d94762a1..2753486b5 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 From 166735b5a1b55903486c7b252e27d585bfade846 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 25 Mar 2016 12:20:19 +0300 Subject: [PATCH 55/88] Redisson shutdown handling when queue.take or poll methods were invoked --- src/main/java/org/redisson/command/CommandAsyncService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index 2abe97b96..b6c753077 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -488,7 +488,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { final FutureListener listener = new FutureListener() { @Override public void operationComplete(Future future) throws Exception { - details.getMainPromise().cancel(true); + details.getMainPromise().tryFailure(new RedissonShutdownException("Redisson is shutdown")); } }; details.getMainPromise().addListener(new FutureListener() { @@ -496,6 +496,9 @@ public class CommandAsyncService implements CommandAsyncExecutor { public void operationComplete(Future future) throws Exception { connectionManager.getShutdownPromise().removeListener(listener); if (!future.isCancelled()) { + if (future.cause() instanceof RedissonShutdownException) { + details.getAttemptPromise().cancel(true); + } return; } // cancel handling for commands from skipTimeout collection From 4f138fde01a9c9dcd2c341dcfff80c567a7bf970 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 25 Mar 2016 14:19:29 +0300 Subject: [PATCH 56/88] refactoring --- .../client/handler/CommandDecoder.java | 28 +++--- .../client/handler/CommandsQueue.java | 2 +- .../MasterSlaveConnectionManager.java | 93 ++++++++++--------- 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index d8ce3d733..f0b5ce652 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 @@ -271,14 +271,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); } } @@ -314,17 +314,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); } } @@ -335,11 +335,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/CommandsQueue.java b/src/main/java/org/redisson/client/handler/CommandsQueue.java index 985ad2656..ff65b9d4c 100644 --- a/src/main/java/org/redisson/client/handler/CommandsQueue.java +++ b/src/main/java/org/redisson/client/handler/CommandsQueue.java @@ -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/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index fe2a22872..c0a11c4aa 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -551,7 +551,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { for (Entry mapEntry : name2PubSubConnection.entrySet()) { for (RedisPubSubConnection redisPubSubConnection : allPubSubConnections) { PubSubConnectionEntry pubSubEntry = mapEntry.getValue(); - final String channelName = mapEntry.getKey(); + String channelName = mapEntry.getKey(); if (!pubSubEntry.getConnection().equals(redisPubSubConnection)) { continue; @@ -560,55 +560,64 @@ public class MasterSlaveConnectionManager implements ConnectionManager { synchronized (pubSubEntry) { pubSubEntry.close(); - final Collection listeners = pubSubEntry.getListeners(channelName); + 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); - } - }); - } + reattachPatternPubSubListeners(channelName, listeners); } 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); - } - }); - } + reattachPubSubListeners(channelName, listeners); } } } } } + private void reattachPubSubListeners(final String channelName, final Collection listeners) { + 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); + } + }); + } + } + + private void reattachPatternPubSubListeners(final String channelName, + final Collection listeners) { + 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); + } + }); + } + } + protected void slaveDown(ClusterSlotRange slotRange, String host, int port, FreezeReason freezeReason) { MasterSlaveEntry entry = getEntry(slotRange); slaveDown(entry, host, port, freezeReason); From 1a2ea4db43f5e7d3075aa02621894c940f5a8a6a Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 25 Mar 2016 15:33:34 +0300 Subject: [PATCH 57/88] Long BlockingQueue\Deque commands reattaching. #449 --- .../org/redisson/client/RedisConnection.java | 10 +++++ .../client/handler/ConnectionWatchdog.java | 33 ++++++++++++--- .../client/protocol/QueueCommand.java | 3 ++ .../redisson/command/CommandAsyncService.java | 7 +--- .../redisson/RedissonBlockingQueueTest.java | 40 +++++++++++++++++++ 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/redisson/client/RedisConnection.java b/src/main/java/org/redisson/client/RedisConnection.java index 53f1c9383..9db33c5c0 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -18,8 +18,10 @@ package org.redisson.client; 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; @@ -59,6 +61,14 @@ public class RedisConnection implements RedisCommands { return (C) channel.attr(RedisConnection.CONNECTION).get(); } + 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; } diff --git a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java index 81a030ce6..1afce25e6 100644 --- a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java +++ b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java @@ -22,6 +22,8 @@ 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.redisson.client.protocol.QueueCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,18 +123,16 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { @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,27 @@ 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 + && QueueCommand.TIMEOUTLESS_COMMANDS.contains(commandData.getCommand().getName())) { + 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/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/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index b6c753077..1abdce13d 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -20,7 +20,6 @@ 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.TimeUnit; @@ -39,6 +38,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; @@ -68,9 +68,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; } @@ -461,7 +458,7 @@ 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); if (popTimeout == 0) { diff --git a/src/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index 5fab2294d..e4c5938be 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -1,7 +1,9 @@ package org.redisson; +import static com.jayway.awaitility.Awaitility.await; import static org.assertj.core.api.Assertions.*; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; @@ -11,18 +13,56 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; 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; +import io.netty.util.concurrent.FutureListener; public class RedissonBlockingQueueTest extends BaseTest { + @Test + public void testTakeReattach() throws InterruptedException, IOException { + 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:pollany"); + Future f = queue1.takeAsync(); + + final AtomicBoolean executed = new AtomicBoolean(); + f.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + assertThat(future.get()).isEqualTo(123); + executed.set(true); + } + }); + + 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); + + await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(executed.get()).isTrue()); + runner.stop(); + } + @Test public void testTakeAsyncCancel() { Config config = createConfig(); From f0f97a44d1c7d64615e12641c2eedbf94fd3d4f6 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 25 Mar 2016 16:08:16 +0300 Subject: [PATCH 58/88] Few enhancements. #449 --- .../org/redisson/client/RedisConnection.java | 4 +++ .../redisson/command/CommandAsyncService.java | 15 ++++++-- .../redisson/RedissonBlockingQueueTest.java | 36 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/redisson/client/RedisConnection.java b/src/main/java/org/redisson/client/RedisConnection.java index 9db33c5c0..9be80dec3 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -61,6 +61,10 @@ 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) { diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index 1abdce13d..f53274f2b 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -488,13 +488,14 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.getMainPromise().tryFailure(new RedissonShutdownException("Redisson is shutdown")); } }; + details.getMainPromise().addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { connectionManager.getShutdownPromise().removeListener(listener); if (!future.isCancelled()) { if (future.cause() instanceof RedissonShutdownException) { - details.getAttemptPromise().cancel(true); + details.getAttemptPromise().tryFailure(future.cause()); } return; } @@ -504,6 +505,16 @@ public class CommandAsyncService implements CommandAsyncExecutor { } } }); + + details.getAttemptPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (future.isCancelled()) { + connection.removeCurrentCommand(); + } + } + }); + connectionManager.getShutdownPromise().addListener(listener); } @@ -626,7 +637,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/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index e4c5938be..e7c006ef9 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -28,6 +28,42 @@ import io.netty.util.concurrent.FutureListener; public class RedissonBlockingQueueTest extends BaseTest { + @Test + public void testPollReattach() throws InterruptedException, IOException { + 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:pollany"); + Future f = queue1.pollAsync(10, TimeUnit.SECONDS); + + final AtomicBoolean executed = new AtomicBoolean(); + f.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + assertThat(future.get()).isEqualTo(123); + executed.set(true); + } + }); + + 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); + + await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(executed.get()).isTrue()); + runner.stop(); + } + + @Test public void testTakeReattach() throws InterruptedException, IOException { RedisProcess runner = new RedisRunner().port(6319).run(); From 38c906fadb7ab588db26a2e6c48a26d9bcdd4e34 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 25 Mar 2016 17:32:15 +0300 Subject: [PATCH 59/88] Lost connection case handling for BlockingQueue poll method --- .../redisson/command/CommandAsyncService.java | 44 +++++++++++--- .../redisson/RedissonBlockingQueueTest.java | 58 +++++++++---------- 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index f53274f2b..3a46fdedf 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.redisson.RedisClientResult; @@ -56,6 +57,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; /** * @@ -460,7 +462,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { int timeoutTime = connectionManager.getConfig().getTimeout(); if (QueueCommand.TIMEOUTLESS_COMMANDS.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); - handleBlockingOperations(details, connection); + handleBlockingOperations(details, connection, popTimeout); if (popTimeout == 0) { return; } @@ -481,7 +483,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.setTimeout(timeout); } - private void handleBlockingOperations(final AsyncDetails details, final RedisConnection connection) { + private void handleBlockingOperations(final AsyncDetails details, final RedisConnection connection, Integer popTimeout) { final FutureListener listener = new FutureListener() { @Override public void operationComplete(Future future) throws Exception { @@ -489,19 +491,41 @@ public class CommandAsyncService implements CommandAsyncExecutor { } }; + final AtomicBoolean canceledByScheduler = new AtomicBoolean(); + final ScheduledFuture scheduledFuture; + if (popTimeout != 0) { + // to handle cases when connection has been lost + scheduledFuture = connectionManager.getGroup().schedule(new Runnable() { + @Override + public void run() { + if (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); - if (!future.isCancelled()) { - if (future.cause() instanceof RedissonShutdownException) { - details.getAttemptPromise().tryFailure(future.cause()); - } + // handling cancel operation for commands from skipTimeout collection + if ((future.isCancelled() && details.getAttemptPromise().cancel(true)) + || canceledByScheduler.get()) { + connection.forceReconnectAsync(); return; } - // cancel handling for commands from skipTimeout collection - if (details.getAttemptPromise().cancel(true)) { - connection.forceReconnectAsync(); + + if (future.cause() instanceof RedissonShutdownException) { + details.getAttemptPromise().tryFailure(future.cause()); } } }); @@ -510,6 +534,8 @@ public class CommandAsyncService implements CommandAsyncExecutor { @Override public void operationComplete(Future future) throws Exception { if (future.isCancelled()) { + // command should be removed due to + // ConnectionWatchdog blockingQueue reconnection logic connection.removeCurrentCommand(); } } diff --git a/src/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index e7c006ef9..2542ab5ac 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -1,7 +1,6 @@ package org.redisson; -import static com.jayway.awaitility.Awaitility.await; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.util.ArrayList; @@ -10,10 +9,11 @@ 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.atomic.AtomicBoolean; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.hamcrest.MatcherAssert; @@ -24,29 +24,35 @@ import org.redisson.RedisRunner.RedisProcess; import org.redisson.core.RBlockingQueue; import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; public class RedissonBlockingQueueTest extends BaseTest { @Test - public void testPollReattach() throws InterruptedException, IOException { + 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:pollany"); - Future f = queue1.pollAsync(10, TimeUnit.SECONDS); + final RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollTimeout"); + Future f = queue1.pollAsync(5, TimeUnit.SECONDS); - final AtomicBoolean executed = new AtomicBoolean(); - f.addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - assertThat(future.get()).isEqualTo(123); - executed.set(true); - } - }); + 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(); @@ -59,30 +65,21 @@ public class RedissonBlockingQueueTest extends BaseTest { } assertThat(queue1.size()).isEqualTo(10); - await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(executed.get()).isTrue()); + Integer result = f.get(1, TimeUnit.SECONDS); + assertThat(result).isEqualTo(123); runner.stop(); } @Test - public void testTakeReattach() throws InterruptedException, IOException { + 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); - final RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + RBlockingQueue queue1 = redisson.getBlockingQueue("testTakeReattach"); Future f = queue1.takeAsync(); - - final AtomicBoolean executed = new AtomicBoolean(); - f.addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - assertThat(future.get()).isEqualTo(123); - executed.set(true); - } - }); - f.await(1, TimeUnit.SECONDS); runner.stop(); @@ -95,7 +92,8 @@ public class RedissonBlockingQueueTest extends BaseTest { } assertThat(queue1.size()).isEqualTo(10); - await().atMost(1, TimeUnit.SECONDS).until(() -> assertThat(executed.get()).isTrue()); + Integer result = f.get(1, TimeUnit.SECONDS); + assertThat(result).isEqualTo(123); runner.stop(); } @@ -105,7 +103,7 @@ public class RedissonBlockingQueueTest extends BaseTest { config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); RedissonClient redisson = Redisson.create(config); - RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + RBlockingQueue queue1 = redisson.getBlockingQueue("testTakeAsyncCancel"); for (int i = 0; i < 10; i++) { Future f = queue1.takeAsync(); f.cancel(true); From 898fff317b1dcea66965742bb0ccd1b01c949a5b Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 25 Mar 2016 17:49:46 +0300 Subject: [PATCH 60/88] reconnection checking during poll operation state scheduled check --- .../java/org/redisson/command/CommandAsyncService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/redisson/command/CommandAsyncService.java b/src/main/java/org/redisson/command/CommandAsyncService.java index 3a46fdedf..ab0b9e18b 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -49,6 +49,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; @@ -494,11 +495,15 @@ public class CommandAsyncService implements CommandAsyncExecutor { final AtomicBoolean canceledByScheduler = new AtomicBoolean(); final ScheduledFuture scheduledFuture; if (popTimeout != 0) { - // to handle cases when connection has been lost + // to handle cases when connection has been lost + final Channel orignalChannel = connection.getChannel(); scheduledFuture = connectionManager.getGroup().schedule(new Runnable() { @Override public void run() { - if (connection.isActive()) { + // there is no re-connection was made + // and connection is still active + if (orignalChannel == connection.getChannel() + && connection.isActive()) { return; } From 5cc2fb862a76a89ea633d65f3dd09e4afb63da26 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 29 Mar 2016 12:30:51 +0300 Subject: [PATCH 61/88] RTopic listener execution bug fixed. Performance boost up to 43%! #453 --- .../java/org/redisson/RedissonObject.java | 4 +- .../java/org/redisson/client/RedisClient.java | 5 +- .../org/redisson/client/RedisConnection.java | 10 +-- .../client/handler/ConnectionWatchdog.java | 3 +- .../connection/ClientConnectionsEntry.java | 5 +- .../connection/FastCompleteFuture.java | 75 ------------------- .../redisson/connection/FastFailedFuture.java | 65 ---------------- .../connection/FastSuccessFuture.java | 47 ------------ .../MasterSlaveConnectionManager.java | 11 +-- .../java/org/redisson/RedissonTopicTest.java | 24 ++++++ 10 files changed, 45 insertions(+), 204 deletions(-) delete mode 100644 src/main/java/org/redisson/connection/FastCompleteFuture.java delete mode 100644 src/main/java/org/redisson/connection/FastFailedFuture.java delete mode 100644 src/main/java/org/redisson/connection/FastSuccessFuture.java diff --git a/src/main/java/org/redisson/RedissonObject.java b/src/main/java/org/redisson/RedissonObject.java index a49ffa2d1..7e12d8ac8 100644 --- a/src/main/java/org/redisson/RedissonObject.java +++ b/src/main/java/org/redisson/RedissonObject.java @@ -50,11 +50,11 @@ abstract class RedissonObject implements RObject { } 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/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 9be80dec3..4f6c49dc4 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -25,13 +25,13 @@ 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; @@ -47,7 +47,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(); @@ -151,13 +151,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() { @@ -176,7 +176,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); } diff --git a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java index 1afce25e6..ac410070c 100644 --- a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java +++ b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java @@ -37,6 +37,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 { @@ -117,7 +118,7 @@ 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 diff --git a/src/main/java/org/redisson/connection/ClientConnectionsEntry.java b/src/main/java/org/redisson/connection/ClientConnectionsEntry.java index 72a1adaeb..abaf8fd4c 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 @@ -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 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/FastSuccessFuture.java b/src/main/java/org/redisson/connection/FastSuccessFuture.java deleted file mode 100644 index 08eab015b..000000000 --- a/src/main/java/org/redisson/connection/FastSuccessFuture.java +++ /dev/null @@ -1,47 +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; - -/** - * - * @author Nikita Koksharov - * - * @param - */ -public class FastSuccessFuture extends FastCompleteFuture { - - private final V result; - - public FastSuccessFuture(V result) { - this.result = result; - } - - @Override - public Throwable cause() { - return null; - } - - @Override - public boolean isSuccess() { - return true; - } - - @Override - public V getNow() { - return result; - } - -} diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index c0a11c4aa..125df525d 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; @@ -136,7 +137,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { this(config); init(cfg); } - + public MasterSlaveConnectionManager(Config cfg) { Version.logVersion(); @@ -158,7 +159,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { this.socketChannelClass = NioSocketChannel.class; } this.codec = cfg.getCodec(); - this.shutdownPromise = group.next().newPromise(); + this.shutdownPromise = newPromise(); this.isClusterMode = cfg.isClusterConfig(); } @@ -714,17 +715,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 diff --git a/src/test/java/org/redisson/RedissonTopicTest.java b/src/test/java/org/redisson/RedissonTopicTest.java index 7373a5365..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 { From f2d82a8981df087e12e1ad8b71465000f3fb4831 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 29 Mar 2016 13:49:13 +0300 Subject: [PATCH 62/88] RSetCache.add methods result fixed. #454 --- .../java/org/redisson/RedissonSetCache.java | 43 ++++++++++++------- .../org/redisson/RedissonSetCacheTest.java | 43 ++++++++++++++----- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 97f8526a7..34bc65af4 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -17,7 +17,6 @@ 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; @@ -41,8 +40,6 @@ import org.redisson.core.RSetCache; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.handler.codec.base64.Base64; -import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import net.openhft.hashing.LongHashFunction; @@ -320,13 +317,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); } @@ -342,12 +346,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); } diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index 9d7c71188..61e489e29 100644 --- a/src/test/java/org/redisson/RedissonSetCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheTest.java @@ -59,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 From 6adbd9387f20f9959d9078bf8a10b4cd58751905 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 29 Mar 2016 13:49:56 +0300 Subject: [PATCH 63/88] hamcrest usage reduced --- .../java/org/redisson/RedissonDequeTest.java | 45 +++------ .../java/org/redisson/RedissonListTest.java | 99 +++++++------------ .../java/org/redisson/RedissonQueueTest.java | 16 +-- 3 files changed, 57 insertions(+), 103 deletions(-) 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/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/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(); From a60d3f491e4c8d5d4d74db05278024f540af983f Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 29 Mar 2016 15:30:53 +0300 Subject: [PATCH 64/88] refactoring --- .../cluster/ClusterConnectionManager.java | 10 +++++---- .../connection/ConnectionManager.java | 6 ++--- .../MasterSlaveConnectionManager.java | 12 +++------- .../redisson/connection/MasterSlaveEntry.java | 14 +++++++----- .../balancer/LoadBalancerManager.java | 3 +-- .../balancer/LoadBalancerManagerImpl.java | 22 +++++++++---------- .../connection/pool/ConnectionPool.java | 2 +- 7 files changed, 32 insertions(+), 37 deletions(-) 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/connection/ConnectionManager.java b/src/main/java/org/redisson/connection/ConnectionManager.java index 21a275ddb..4bd1a720c 100644 --- a/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/src/main/java/org/redisson/connection/ConnectionManager.java @@ -23,11 +23,11 @@ import java.util.concurrent.TimeUnit; import org.redisson.MasterSlaveServersConfig; 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.RedisCommand; import org.redisson.cluster.ClusterSlotRange; -import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.core.NodeType; import org.redisson.misc.InfinitySemaphoreLatch; @@ -44,6 +44,8 @@ import io.netty.util.concurrent.Promise; */ public interface ConnectionManager { + void reattachPubSub(Collection allPubSubConnections); + boolean isClusterMode(); Future newSucceededFuture(R value); @@ -62,8 +64,6 @@ public interface ConnectionManager { Future newFailedFuture(Throwable cause); - void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason); - Collection getClients(); void shutdownAsync(RedisClient client); diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 125df525d..2bf246e3f 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -542,13 +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 + @Override + public void reattachPubSub(Collection allPubSubConnections) { for (Entry mapEntry : name2PubSubConnection.entrySet()) { for (RedisPubSubConnection redisPubSubConnection : allPubSubConnections) { PubSubConnectionEntry pubSubEntry = mapEntry.getValue(); @@ -620,8 +615,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { } 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) { diff --git a/src/main/java/org/redisson/connection/MasterSlaveEntry.java b/src/main/java/org/redisson/connection/MasterSlaveEntry.java index 7582e98fc..139030165 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveEntry.java +++ b/src/main/java/org/redisson/connection/MasterSlaveEntry.java @@ -90,9 +90,11 @@ 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) { + if (!slaveBalancer.freeze(host, port, freezeReason)) { + 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 +102,7 @@ public class MasterSlaveEntry { log.info("master {}:{} used as slave", addr.getHostName(), addr.getPort()); } } - return conns; + return true; } public Future addSlave(String host, int port) { @@ -134,7 +136,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 +157,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..2b28c343f 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); + boolean 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..8c73c4d8d 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 boolean 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 false; } synchronized (connectionEntry) { - log.debug("{} freezed", addr); + if (connectionEntry.isFreezed()) { + return false; + } + connectionEntry.setFreezed(true); + // only RECONNECT freeze reason could be replaced if (connectionEntry.getFreezeReason() == null || connectionEntry.getFreezeReason() == FreezeReason.RECONNECT) { @@ -134,11 +134,9 @@ public class LoadBalancerManagerImpl implements LoadBalancerManager { connection.closeAsync(); } - synchronized (connectionEntry) { - List list = new ArrayList(connectionEntry.getAllSubscribeConnections()); - connectionEntry.getAllSubscribeConnections().clear(); - return list; - } + connectionManager.reattachPubSub(connectionEntry.getAllSubscribeConnections()); + connectionEntry.getAllSubscribeConnections().clear(); + return true; } public Future nextPubSubConnection() { diff --git a/src/main/java/org/redisson/connection/pool/ConnectionPool.java b/src/main/java/org/redisson/connection/pool/ConnectionPool.java index 58998a2dd..00c1c6886 100644 --- a/src/main/java/org/redisson/connection/pool/ConnectionPool.java +++ b/src/main/java/org/redisson/connection/pool/ConnectionPool.java @@ -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); From 901be4439e6d9643d25d99648c7c637a92fba625 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 29 Mar 2016 17:46:01 +0300 Subject: [PATCH 65/88] Long BlockingQueue methods re-subscription when slave is down --- .../client/handler/ConnectionWatchdog.java | 29 ++-- .../redisson/client/protocol/CommandData.java | 4 + .../connection/ClientConnectionsEntry.java | 12 +- .../connection/ConnectionManager.java | 3 - .../MasterSlaveConnectionManager.java | 72 -------- .../redisson/connection/MasterSlaveEntry.java | 155 +++++++++++++++++- .../balancer/LoadBalancerManager.java | 2 +- .../balancer/LoadBalancerManagerImpl.java | 28 +--- 8 files changed, 185 insertions(+), 120 deletions(-) diff --git a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java index ac410070c..54270890d 100644 --- a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java +++ b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java @@ -23,7 +23,6 @@ import org.redisson.client.RedisException; import org.redisson.client.RedisPubSubConnection; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.CommandData; -import org.redisson.client.protocol.QueueCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -151,26 +150,28 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { } private void refresh(RedisConnection connection, Channel channel) { - CommandData commandData = connection.getCurrentCommand(); + CommandData commandData = connection.getCurrentCommand(); connection.updateChannel(channel); reattachBlockingQueue(connection, commandData); reattachPubSub(connection); } - private void reattachBlockingQueue(RedisConnection connection, final CommandData commandData) { - if (commandData != null - && QueueCommand.TIMEOUTLESS_COMMANDS.contains(commandData.getCommand().getName())) { - 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); - } - } - }); + 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/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/connection/ClientConnectionsEntry.java b/src/main/java/org/redisson/connection/ClientConnectionsEntry.java index abaf8fd4c..1d9c3d6da 100644 --- a/src/main/java/org/redisson/connection/ClientConnectionsEntry.java +++ b/src/main/java/org/redisson/connection/ClientConnectionsEntry.java @@ -158,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; @@ -196,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 4bd1a720c..dd3ad00f2 100644 --- a/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/src/main/java/org/redisson/connection/ConnectionManager.java @@ -23,7 +23,6 @@ import java.util.concurrent.TimeUnit; import org.redisson.MasterSlaveServersConfig; 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.RedisCommand; @@ -44,8 +43,6 @@ import io.netty.util.concurrent.Promise; */ public interface ConnectionManager { - void reattachPubSub(Collection allPubSubConnections); - boolean isClusterMode(); Future newSucceededFuture(R value); diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 2bf246e3f..63f8e175f 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -542,78 +542,6 @@ public class MasterSlaveConnectionManager implements ConnectionManager { return null; } - @Override - public void reattachPubSub(Collection allPubSubConnections) { - for (Entry mapEntry : name2PubSubConnection.entrySet()) { - for (RedisPubSubConnection redisPubSubConnection : allPubSubConnections) { - PubSubConnectionEntry pubSubEntry = mapEntry.getValue(); - String channelName = mapEntry.getKey(); - - if (!pubSubEntry.getConnection().equals(redisPubSubConnection)) { - continue; - } - - synchronized (pubSubEntry) { - pubSubEntry.close(); - - Collection listeners = pubSubEntry.getListeners(channelName); - if (pubSubEntry.getConnection().getPatternChannels().get(channelName) != null) { - reattachPatternPubSubListeners(channelName, listeners); - } else { - reattachPubSubListeners(channelName, listeners); - } - } - } - } - } - - private void reattachPubSubListeners(final String channelName, final Collection listeners) { - 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); - } - }); - } - } - - private void reattachPatternPubSubListeners(final String channelName, - final Collection listeners) { - 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); - } - }); - } - } - protected void slaveDown(ClusterSlotRange slotRange, String host, int port, FreezeReason freezeReason) { getEntry(slotRange).slaveDown(host, port, freezeReason); } diff --git a/src/main/java/org/redisson/connection/MasterSlaveEntry.java b/src/main/java/org/redisson/connection/MasterSlaveEntry.java index 139030165..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; /** * @@ -91,7 +97,8 @@ public class MasterSlaveEntry { } public boolean slaveDown(String host, int port, FreezeReason freezeReason) { - if (!slaveBalancer.freeze(host, port, freezeReason)) { + ClientConnectionsEntry entry = slaveBalancer.freeze(host, port, freezeReason); + if (entry == null) { return false; } @@ -102,8 +109,154 @@ public class MasterSlaveEntry { log.info("master {}:{} used as slave", addr.getHostName(), addr.getPort()); } } + + // 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) { return addSlave(host, port, true, NodeType.SLAVE); diff --git a/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java b/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java index 2b28c343f..edef7d2bb 100644 --- a/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java +++ b/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java @@ -36,7 +36,7 @@ public interface LoadBalancerManager { boolean unfreeze(String host, int port, FreezeReason freezeReason); - boolean 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 8c73c4d8d..38135c62d 100644 --- a/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java +++ b/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java @@ -95,16 +95,16 @@ public class LoadBalancerManagerImpl implements LoadBalancerManager { return false; } - public boolean 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 false; + return null; } synchronized (connectionEntry) { if (connectionEntry.isFreezed()) { - return false; + return null; } connectionEntry.setFreezed(true); @@ -116,27 +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(); - } - - connectionManager.reattachPubSub(connectionEntry.getAllSubscribeConnections()); - connectionEntry.getAllSubscribeConnections().clear(); - return true; + return connectionEntry; } public Future nextPubSubConnection() { From 0f542267cbce01621e38d2b64118c0985f2f17d3 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 29 Mar 2016 18:01:55 +0300 Subject: [PATCH 66/88] RedissonMultiLock refactoring --- .../org/redisson/core/RedissonMultiLock.java | 47 ++----------------- 1 file changed, 4 insertions(+), 43 deletions(-) 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 { From 8495095697104ea4ef395606d5eac9199d860dbd Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 29 Mar 2016 20:04:46 +0300 Subject: [PATCH 67/88] RemoteService uses queue for responses --- .../org/redisson/RedissonRemoteService.java | 92 ++++++++----------- .../org/redisson/core/RRemoteService.java | 11 ++- .../redisson/remote/RemoteServiceRequest.java | 9 +- 3 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index d424128a3..5e2c813df 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -18,15 +18,15 @@ 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.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.redisson.core.MessageListener; +import org.redisson.core.RBatch; import org.redisson.core.RBlockingQueue; +import org.redisson.core.RBlockingQueueAsync; import org.redisson.core.RRemoteService; -import org.redisson.core.RTopic; import org.redisson.remote.RRemoteServiceResponse; import org.redisson.remote.RemoteServiceAck; import org.redisson.remote.RemoteServiceAckTimeoutException; @@ -44,6 +44,11 @@ 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); @@ -102,22 +107,18 @@ public class RedissonRemoteService implements RRemoteService { } final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); - String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId(); - final RTopic topic = redisson.getTopic(responseName); - Future ackClientsFuture = topic.publishAsync(new RemoteServiceAck()); - ackClientsFuture.addListener(new FutureListener() { + 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 { + public void operationComplete(Future> future) throws Exception { if (!future.isSuccess()) { log.error("Can't send ack for request: " + request, future.cause()); return; } - if (future.getNow() == 0) { - log.error("Client has not received ack for request: {}", request); - return; - } - invokeMethod(request, method, topic); + invokeMethod(request, method, responseName); } }); } @@ -125,8 +126,7 @@ public class RedissonRemoteService implements RRemoteService { }); } - private void invokeMethod(final RemoteServiceRequest request, RemoteServiceMethod method, - RTopic topic) { + 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()); @@ -138,24 +138,21 @@ public class RedissonRemoteService implements RRemoteService { log.error("Can't execute: " + request, e); } - Future clientsFuture = topic.publishAsync(responseHolder.get()); - clientsFuture.addListener(new FutureListener() { + Future> clientsFuture = send(request.getResponseTimeout(), responseName, responseHolder.get()); + clientsFuture.addListener(new FutureListener>() { @Override - public void operationComplete(Future future) throws Exception { + public void operationComplete(Future> future) throws Exception { if (!future.isSuccess()) { + log.error("Can't send response: " + responseHolder.get() + " for request: " + request, future.cause()); return; } - - if (future.getNow() == 0) { - log.error("None of clients has not received a response: {} for request: {}", responseHolder.get(), request); - } } }); } @Override public T get(Class remoteInterface) { - return get(remoteInterface, -1, null); + return get(remoteInterface, 30, TimeUnit.SECONDS); } @Override @@ -172,45 +169,26 @@ public class RedissonRemoteService implements RRemoteService { String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); - RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args, ackTimeUnit.toMillis(ackTimeout), System.currentTimeMillis()); + 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; - final CountDownLatch ackLatch = new CountDownLatch(1); - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference response = new AtomicReference(); - final RTopic topic = redisson.getTopic(responseName); - int listenerId = topic.addListener(new MessageListener() { - @Override - public void onMessage(String channel, RRemoteServiceResponse msg) { - if (msg instanceof RemoteServiceResponse) { - response.set(msg); - latch.countDown(); - } else { - ackLatch.countDown(); - } - } - }); + RBlockingQueue responseQueue = redisson.getBlockingQueue(responseName); - if (!ackLatch.await(ackTimeout, ackTimeUnit)) { - topic.removeListener(listenerId); + RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(ackTimeout, ackTimeUnit); + if (ack == null) { throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request); } - if (executionTimeout == -1) { - latch.await(); - } else { - if (!latch.await(executionTimeout, executionTimeUnit)) { - topic.removeListener(listenerId); - throw new RemoteServiceTimeoutException("No response after " + executionTimeUnit.toMillis(executionTimeout) + "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); } - topic.removeListener(listenerId); - RemoteServiceResponse msg = (RemoteServiceResponse) response.get(); - if (msg.getError() != null) { - throw msg.getError(); + if (response.getError() != null) { + throw response.getError(); } - return msg.getResult(); + return response.getResult(); } }; return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler); @@ -222,5 +200,13 @@ public class RedissonRemoteService implements RRemoteService { 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/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index 0461188ed..f5449b664 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -17,8 +17,6 @@ package org.redisson.core; import java.util.concurrent.TimeUnit; -import org.redisson.remote.RemoteServiceAckTimeoutException; - /** * Allows to execute object methods remotely between Redisson instances (Server side and Client side instances in terms of remote invocation). *

@@ -78,7 +76,10 @@ public interface RRemoteService { /** * Get remote service object for remote invocations. - * Uses ack timeout = 1000 ms by default + *

+ * Ack timeout = 1000 ms by default + *

+ * Execution timeout = 30 sec by default * * @param remoteInterface * @return @@ -87,7 +88,9 @@ public interface RRemoteService { /** * Get remote service object for remote invocations - * with specified invocation timeout. Uses ack timeout = 1000 ms by default + * with specified invocation timeout. + *

+ * Ack timeout = 1000 ms by default * * @param remoteInterface * @param executionTimeout - invocation timeout diff --git a/src/main/java/org/redisson/remote/RemoteServiceRequest.java b/src/main/java/org/redisson/remote/RemoteServiceRequest.java index f47324f10..3980ac4f0 100644 --- a/src/main/java/org/redisson/remote/RemoteServiceRequest.java +++ b/src/main/java/org/redisson/remote/RemoteServiceRequest.java @@ -23,20 +23,27 @@ public class RemoteServiceRequest { 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 date) { + 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; } From 66024192aa02ac77d7e82de8714246dc1279b606 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Tue, 29 Mar 2016 22:44:25 +0100 Subject: [PATCH 68/88] travis matrix added matrix environment tags to test against three redis versions. --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0153205e2..32d003cc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,10 @@ language: java jdk: - oraclejdk8 env: - - REDIS_VERSION=3.0.7 + matrix: + - REDIS_VERSION=3.0.7 + - REDIS_VERSION=2.8.24 + - REDIS_VERSION=3.2.0-rc3 cache: directories: - $HOME/.m2 From 382f50eb983ea8b8b147f2affdf70e2868aa8f91 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 30 Mar 2016 10:47:47 +0300 Subject: [PATCH 69/88] RScoredSortedSet.removeAllAsync & removeAll methods optimization --- .../java/org/redisson/RedissonScoredSortedSet.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/redisson/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index e4755f679..ee6724f9a 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -330,12 +330,11 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @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 ", + "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()); } From 49f10ee0273e1a3abf8b146b1e81f620e9e17878 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 30 Mar 2016 17:47:00 +0300 Subject: [PATCH 70/88] refactoring --- src/main/java/org/redisson/RedissonScoredSortedSet.java | 9 +++------ src/test/java/org/redisson/RedissonSetCacheTest.java | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/redisson/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index ee6724f9a..62c493436 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -30,11 +30,8 @@ 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; @@ -315,7 +312,7 @@ 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 " @@ -329,7 +326,7 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @Override public Future removeAllAsync(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 v = 0;" + "for i=1, #ARGV, 5000 do " + "v = v + redis.call('zrem', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); " @@ -350,7 +347,7 @@ 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 " diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index 61e489e29..64e4ddc23 100644 --- a/src/test/java/org/redisson/RedissonSetCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheTest.java @@ -203,7 +203,7 @@ public class RedissonSetCacheTest extends BaseTest { 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))); From d854ce0f3a0d13e16e84a6cdc0d3c0215b40c251 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 31 Mar 2016 20:34:31 +0300 Subject: [PATCH 71/88] RGeo implemented. #295 --- src/main/java/org/redisson/Redisson.java | 11 + .../java/org/redisson/RedissonClient.java | 19 ++ src/main/java/org/redisson/RedissonGeo.java | 194 ++++++++++++++++++ .../redisson/client/codec/GeoEntryCodec.java | 45 ++++ .../redisson/client/codec/ScoredCodec.java | 2 +- .../client/handler/CommandEncoder.java | 8 +- .../client/protocol/RedisCommand.java | 2 +- .../client/protocol/RedisCommands.java | 13 +- .../convertor/DoubleReplayConvertor.java | 2 +- .../decoder/FlatNestedMultiDecoder.java | 44 ++++ .../protocol/decoder/GeoDistanceDecoder.java | 58 ++++++ .../decoder/GeoDistanceMapDecoder.java | 68 ++++++ .../protocol/decoder/GeoMapReplayDecoder.java | 48 +++++ .../protocol/decoder/GeoPositionDecoder.java | 50 +++++ .../decoder/GeoPositionMapDecoder.java | 62 ++++++ .../protocol/decoder/NestedMultiDecoder.java | 108 +++++++--- .../protocol/decoder/NestedMultiDecoder2.java | 86 -------- .../connection/decoder/MapGetAllDecoder.java | 4 + src/main/java/org/redisson/core/GeoEntry.java | 43 ++++ .../java/org/redisson/core/GeoPosition.java | 70 +++++++ src/main/java/org/redisson/core/GeoUnit.java | 48 +++++ src/main/java/org/redisson/core/RGeo.java | 51 +++++ .../java/org/redisson/core/RGeoAsync.java | 53 +++++ 23 files changed, 972 insertions(+), 117 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonGeo.java create mode 100644 src/main/java/org/redisson/client/codec/GeoEntryCodec.java create mode 100644 src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java create mode 100644 src/main/java/org/redisson/client/protocol/decoder/GeoDistanceDecoder.java create mode 100644 src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java create mode 100644 src/main/java/org/redisson/client/protocol/decoder/GeoMapReplayDecoder.java create mode 100644 src/main/java/org/redisson/client/protocol/decoder/GeoPositionDecoder.java create mode 100644 src/main/java/org/redisson/client/protocol/decoder/GeoPositionMapDecoder.java delete mode 100644 src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder2.java create mode 100644 src/main/java/org/redisson/core/GeoEntry.java create mode 100644 src/main/java/org/redisson/core/GeoPosition.java create mode 100644 src/main/java/org/redisson/core/GeoUnit.java create mode 100644 src/main/java/org/redisson/core/RGeo.java create mode 100644 src/main/java/org/redisson/core/RGeoAsync.java diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index f1fdf2953..a6c88ea08 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -49,6 +49,7 @@ import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; 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; @@ -180,6 +181,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) { diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index edb10dced..a83750c7d 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -33,6 +33,7 @@ import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; 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; @@ -65,6 +66,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. diff --git a/src/main/java/org/redisson/RedissonGeo.java b/src/main/java/org/redisson/RedissonGeo.java new file mode 100644 index 000000000..dd36d3764 --- /dev/null +++ b/src/main/java/org/redisson/RedissonGeo.java @@ -0,0 +1,194 @@ +/** + * 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 { + + public RedissonGeo(CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + } + + public RedissonGeo(Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + } + + @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), 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)); + 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) { + MultiDecoder> decoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); + RedisCommand> command = new RedisCommand>("GEORADIUS", decoder); + 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) { + MultiDecoder> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); + RedisCommand> command = new RedisCommand>("GEORADIUS", decoder); + 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) { + MultiDecoder> decoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); + RedisCommand command = new RedisCommand("GEORADIUSBYMEMBER", decoder, 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) { + MultiDecoder> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); + RedisCommand> command = new RedisCommand>("GEORADIUSBYMEMBER", decoder, 2); + return commandExecutor.readAsync(getName(), codec, command, getName(), member, radius, geoUnit, "WITHCOORD"); + } +} 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/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 { - 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 9528685ce..d56eae403 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()); @@ -238,7 +245,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..413694b33 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java @@ -0,0 +1,44 @@ +/** + * 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); + } + + @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..ac4f98393 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.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.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; +import io.netty.util.CharsetUtil; + +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 { + System.out.println("1 " + buf.toString(CharsetUtil.UTF_8)); + 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) { + System.out.println(parts); + 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..c2a5f5fdd 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,94 @@ 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; public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { this.firstDecoder = firstDecoder; this.secondDecoder = secondDecoder; } + + public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, MultiDecoder thirdDecoder) { + this.firstDecoder = firstDecoder; + this.secondDecoder = secondDecoder; + this.thirdDecoder = thirdDecoder; + } + @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(); + } + // 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 ds.getFlipDecoders().peek().isApplicable(paramNum, state); + + 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 +121,32 @@ public class NestedMultiDecoder implements MultiDecoder { @Override public Object decode(List parts, State state) { + if (parts.isEmpty() && state.getDecoderState() == null) { + 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/connection/decoder/MapGetAllDecoder.java b/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java index 3cf057aaa..35aa171df 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; @@ -45,6 +46,9 @@ 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++) { Object value = parts.get(index); 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/RGeo.java b/src/main/java/org/redisson/core/RGeo.java new file mode 100644 index 000000000..c6c32dafc --- /dev/null +++ b/src/main/java/org/redisson/core/RGeo.java @@ -0,0 +1,51 @@ +/** + * 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; + +/** + * + * @author Nikita Koksharov + * + * @param + */ +public interface RGeo extends RExpirable, RGeoAsync { + + long add(double longitude, double latitude, V member); + + long add(GeoEntry... entries); + + Double dist(V firstMember, V secondMember, GeoUnit geoUnit); + + Map hash(V... members); + + Map pos(V... members); + + List radius(double longitude, double latitude, double radius, GeoUnit geoUnit); + + Map radiusWithDistance(double longitude, double latitude, double radius, GeoUnit geoUnit); + + Map radiusWithPosition(double longitude, double latitude, double radius, GeoUnit geoUnit); + + List radius(V member, double radius, GeoUnit geoUnit); + + Map radiusWithDistance(V member, double radius, GeoUnit geoUnit); + + 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..e2917d432 --- /dev/null +++ b/src/main/java/org/redisson/core/RGeoAsync.java @@ -0,0 +1,53 @@ +/** + * 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 { + + Future addAsync(double longitude, double latitude, V member); + + Future addAsync(GeoEntry... entries); + + Future distAsync(V firstMember, V secondMember, GeoUnit geoUnit); + + Future> hashAsync(V... members); + + Future> posAsync(V... members); + + Future> radiusAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + Future> radiusWithDistanceAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + Future> radiusWithPositionAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + Future> radiusAsync(V member, double radius, GeoUnit geoUnit); + + Future> radiusWithDistanceAsync(V member, double radius, GeoUnit geoUnit); + + Future> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit); + +} From 818a3055674655083b3d965eecf1fba7cc7efcd5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 1 Apr 2016 12:56:10 +0300 Subject: [PATCH 72/88] RLexSortedSet methods renamed. #459 --- .../org/redisson/RedissonLexSortedSet.java | 161 ++++++++++++++++-- .../org/redisson/RedissonScoredSortedSet.java | 3 + .../java/org/redisson/core/RLexSortedSet.java | 78 +++++++++ .../org/redisson/core/RLexSortedSetAsync.java | 78 +++++++++ .../redisson/RedissonLexSortedSetTest.java | 39 +++-- 5 files changed, 327 insertions(+), 32 deletions(-) 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/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index 62c493436..0ffb1122d 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -121,6 +121,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 { 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/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); } } From a95a08330a3f7a91785114a5079d2f7fdaf52504 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 1 Apr 2016 13:05:49 +0300 Subject: [PATCH 73/88] some deprecated methods removed --- src/main/java/org/redisson/Redisson.java | 10 ---------- src/main/java/org/redisson/RedissonClient.java | 12 ------------ src/test/java/org/redisson/RedissonBucketTest.java | 2 +- .../java/org/redisson/RedissonConcurrentMapTest.java | 2 +- src/test/java/org/redisson/RedissonKeysTest.java | 2 +- .../org/redisson/RedissonSetMultimapCacheTest.java | 2 +- 6 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index a6c88ea08..cfd3de6d5 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -537,16 +537,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/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index a83750c7d..72c2c31bb 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -658,18 +658,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/test/java/org/redisson/RedissonBucketTest.java b/src/test/java/org/redisson/RedissonBucketTest.java index 7ed1bba45..ff88c0173 100755 --- a/src/test/java/org/redisson/RedissonBucketTest.java +++ b/src/test/java/org/redisson/RedissonBucketTest.java @@ -59,7 +59,7 @@ 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(); } 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/RedissonKeysTest.java b/src/test/java/org/redisson/RedissonKeysTest.java index 4380d26df..ef6a4d4b9 100644 --- a/src/test/java/org/redisson/RedissonKeysTest.java +++ b/src/test/java/org/redisson/RedissonKeysTest.java @@ -58,7 +58,7 @@ public class RedissonKeysTest extends BaseTest { 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()); } diff --git a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java index 442855b8c..a4da5c6dd 100644 --- a/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetMultimapCacheTest.java @@ -102,7 +102,7 @@ public class RedissonSetMultimapCacheTest extends BaseTest { multimap.put("1", "3"); multimap.expireKey("1", 1, TimeUnit.SECONDS); - Thread.sleep(1000); + Thread.sleep(1500); assertThat(multimap.get("1").size()).isZero(); assertThat(multimap.get("1")).contains(); From 22a3dd2a09c731f54484ad47da2292aa3cc4d6d3 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 1 Apr 2016 14:11:41 +0300 Subject: [PATCH 74/88] RBuckets object added. #460, #430 --- src/main/java/org/redisson/Redisson.java | 16 +- .../java/org/redisson/RedissonBuckets.java | 118 +++++++++++ .../java/org/redisson/RedissonClient.java | 53 ++--- src/main/java/org/redisson/RedissonGeo.java | 2 +- src/main/java/org/redisson/RedissonMap.java | 2 +- .../java/org/redisson/RedissonMapCache.java | 4 +- .../client/codec/DelegateDecoderCodec.java | 34 ++++ .../client/protocol/RedisCommands.java | 1 + .../decoder/GeoDistanceMapDecoder.java | 3 - .../connection/decoder/MapGetAllDecoder.java | 8 +- src/main/java/org/redisson/core/RBuckets.java | 64 ++++++ .../java/org/redisson/RedissonBucketTest.java | 45 ----- .../org/redisson/RedissonBucketsTest.java | 91 +++++++++ .../java/org/redisson/RedissonGeoTest.java | 183 ++++++++++++++++++ 14 files changed, 531 insertions(+), 93 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonBuckets.java create mode 100644 src/main/java/org/redisson/client/codec/DelegateDecoderCodec.java create mode 100644 src/main/java/org/redisson/core/RBuckets.java create mode 100644 src/test/java/org/redisson/RedissonBucketsTest.java create mode 100644 src/test/java/org/redisson/RedissonGeoTest.java diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index cfd3de6d5..fd9e63201 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -47,6 +47,7 @@ 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; @@ -202,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)); @@ -260,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); diff --git a/src/main/java/org/redisson/RedissonBuckets.java b/src/main/java/org/redisson/RedissonBuckets.java new file mode 100644 index 000000000..43a118771 --- /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 72c2c31bb..fffc0e54b 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -31,6 +31,7 @@ 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; @@ -151,57 +152,43 @@ public interface RedissonClient { RBucket getBucket(String name, Codec codec); /** - *

Returns a list of object holder instances by a key pattern. + * Returns interface for mass operations with Bucket objects. * - *

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

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 #findBuckets(String)} + * Use {@link RBuckets#get(String...)} */ @Deprecated - List> getBuckets(String pattern); + Map loadBucketValues(String ... keys); + + /** + * Use {@link RBuckets#set(Map)} + */ + @Deprecated + void saveBuckets(Map buckets); /** * Returns HyperLogLog instance by name. diff --git a/src/main/java/org/redisson/RedissonGeo.java b/src/main/java/org/redisson/RedissonGeo.java index dd36d3764..7a62e165c 100644 --- a/src/main/java/org/redisson/RedissonGeo.java +++ b/src/main/java/org/redisson/RedissonGeo.java @@ -104,7 +104,7 @@ public class RedissonGeo extends RedissonExpirable implements RGeo { List params = new ArrayList(members.length + 1); params.add(getName()); params.addAll(Arrays.asList(members)); - RedisCommand> command = new RedisCommand>("GEOHASH", new MapGetAllDecoder(params), 2, ValueType.OBJECTS); + RedisCommand> command = new RedisCommand>("GEOHASH", new MapGetAllDecoder(params, 1), 2, ValueType.OBJECTS); return commandExecutor.readAsync(getName(), new ScoredCodec(codec), command, params.toArray()); } diff --git a/src/main/java/org/redisson/RedissonMap.java b/src/main/java/org/redisson/RedissonMap.java index 245e6a7cd..cb8c8b54a 100644 --- a/src/main/java/org/redisson/RedissonMap.java +++ b/src/main/java/org/redisson/RedissonMap.java @@ -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 d529c2b03..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 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/protocol/RedisCommands.java b/src/main/java/org/redisson/client/protocol/RedisCommands.java index d56eae403..e3737d259 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -196,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); diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java index ac4f98393..9e9d526e5 100644 --- a/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java @@ -25,7 +25,6 @@ import org.redisson.client.codec.DoubleCodec; import org.redisson.client.handler.State; import io.netty.buffer.ByteBuf; -import io.netty.util.CharsetUtil; public class GeoDistanceMapDecoder implements MultiDecoder> { @@ -40,7 +39,6 @@ public class GeoDistanceMapDecoder implements MultiDecoder> @Override public Object decode(ByteBuf buf, State state) throws IOException { - System.out.println("1 " + buf.toString(CharsetUtil.UTF_8)); if (pos.get() % 2 == 0) { return codec.getValueDecoder().decode(buf, state); } @@ -55,7 +53,6 @@ public class GeoDistanceMapDecoder implements MultiDecoder> @Override public Map decode(List parts, State state) { - System.out.println(parts); Map result = new HashMap(parts.size()/2); for (int i = 0; i < parts.size(); i++) { if (i % 2 != 0) { diff --git a/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java b/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java index 35aa171df..7ce90ee4f 100644 --- a/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java +++ b/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java @@ -28,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 @@ -50,12 +52,12 @@ public class MapGetAllDecoder implements MultiDecoder> { 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/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/test/java/org/redisson/RedissonBucketTest.java b/src/test/java/org/redisson/RedissonBucketTest.java index ff88c0173..0688f55b3 100755 --- a/src/test/java/org/redisson/RedissonBucketTest.java +++ b/src/test/java/org/redisson/RedissonBucketTest.java @@ -64,35 +64,6 @@ public class RedissonBucketTest extends BaseTest { 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/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(); + } + +} From 563fb79574c7d95618aec35105cad485b6388159 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Fri, 1 Apr 2016 13:53:16 +0100 Subject: [PATCH 75/88] Fixes a compilation issue. --- src/main/java/org/redisson/RedissonBuckets.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/RedissonBuckets.java b/src/main/java/org/redisson/RedissonBuckets.java index 43a118771..e9958461c 100644 --- a/src/main/java/org/redisson/RedissonBuckets.java +++ b/src/main/java/org/redisson/RedissonBuckets.java @@ -72,7 +72,7 @@ public class RedissonBuckets implements RBuckets { return Collections.emptyMap(); } - RedisCommand> command = new RedisCommand>("MGET", new MapGetAllDecoder(Arrays.asList(keys), 0), ValueType.OBJECTS); + 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); } From 7c4deafc7484bb8e84b4e69c04aef842814e7049 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 4 Apr 2016 11:56:10 +0300 Subject: [PATCH 76/88] SCAN commands result handling fixed. #463 --- .../org/redisson/RedissonBaseIterator.java | 96 +++++++++++++++++++ .../org/redisson/RedissonBaseMapIterator.java | 58 +++++++---- src/main/java/org/redisson/RedissonKeys.java | 51 ++-------- .../org/redisson/RedissonMapIterator.java | 2 +- .../RedissonMultiMapKeysIterator.java | 2 +- .../org/redisson/RedissonScoredSortedSet.java | 47 +-------- src/main/java/org/redisson/RedissonSet.java | 58 +---------- .../java/org/redisson/RedissonSetCache.java | 58 +---------- .../redisson/RedissonSetMultimapValues.java | 59 +----------- .../java/org/redisson/RedissonMapTest.java | 21 ++++ 10 files changed, 185 insertions(+), 267 deletions(-) create mode 100644 src/main/java/org/redisson/RedissonBaseIterator.java 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..66cf04465 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 (newValues.equals(firstValues)) { + 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/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/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/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index 0ffb1122d..188460143 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -25,7 +25,6 @@ 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; @@ -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; + return new RedissonBaseIterator() { @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(); + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return scanIterator(client, nextIterPos); } @Override - public V next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element at index"); - } - - value = iter.next(); - removeExecuted = false; - return value; - } - - @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; } - + }; } diff --git a/src/main/java/org/redisson/RedissonSet.java b/src/main/java/org/redisson/RedissonSet.java index 9904bcc30..2afeeb563 100644 --- a/src/main/java/org/redisson/RedissonSet.java +++ b/src/main/java/org/redisson/RedissonSet.java @@ -82,66 +82,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; } - + }; } diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 34bc65af4..124fbfbc9 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -161,66 +161,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; } - + }; } diff --git a/src/main/java/org/redisson/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java index 215c82cd4..36d0fefee 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapValues.java +++ b/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.Set; import org.redisson.client.codec.Codec; @@ -128,66 +127,18 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R @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) { RedissonSetMultimapValues.this.remove(value); - currentElementRemoved = true; - removeExecuted = true; } - + }; } 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"); From 5170ba8cc766b5822b22e28b4c10ce0cd84e1cb8 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 4 Apr 2016 15:12:32 +0300 Subject: [PATCH 77/88] ReactiveSet iterator fixed. #463 --- .../org/redisson/reactive/PublisherAdder.java | 18 ++++++----- .../reactive/SetReactiveIterator.java | 31 +++---------------- 2 files changed, 15 insertions(+), 34 deletions(-) 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/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 From 641a7224125159cb7606196907b4a094c7602d60 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 4 Apr 2016 15:58:59 +0300 Subject: [PATCH 78/88] [maven-release-plugin] prepare release redisson-2.2.11 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 111ee57f8..b7ebf6ff9 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.11-SNAPSHOT + 2.2.11 bundle Redisson @@ -15,7 +15,7 @@ scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git - HEAD + redisson-2.2.11 From ed9dcfa0ac10ec3d6ac3d7a8208967bd50319e26 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 4 Apr 2016 15:59:06 +0300 Subject: [PATCH 79/88] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b7ebf6ff9..a2d439b28 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.11 + 2.2.12-SNAPSHOT bundle Redisson @@ -15,7 +15,7 @@ scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git scm:git:git@github.com:mrniko/redisson.git - redisson-2.2.11 + HEAD From e065c8c9c1383db07da49a51486b5fc77aed05c8 Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Mon, 4 Apr 2016 16:18:45 +0300 Subject: [PATCH 80/88] Update CHANGELOG.md --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ffd4f0fc..54c412b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ 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` From 51f4c88a26f65a4d1bb663962e5435eb0432f99a Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Tue, 5 Apr 2016 13:21:15 +0300 Subject: [PATCH 81/88] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2753486b5..9c21b0127 100644 --- a/README.md +++ b/README.md @@ -82,12 +82,12 @@ Include the following to your dependency list: org.redisson redisson - 2.2.10 + 2.2.11 ### Gradle - compile 'org.redisson:redisson:2.2.10' + compile 'org.redisson:redisson:2.2.11' ### Supported by From 43bde3e0c042e3fd7ec15605384956c0768c6da4 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 5 Apr 2016 13:43:34 +0300 Subject: [PATCH 82/88] RGeo interface covered by comments. --- src/main/java/org/redisson/core/RGeo.java | 127 +++++++++++++++++- .../java/org/redisson/core/RGeoAsync.java | 114 ++++++++++++++++ 2 files changed, 235 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/redisson/core/RGeo.java b/src/main/java/org/redisson/core/RGeo.java index c6c32dafc..e07c3c02c 100644 --- a/src/main/java/org/redisson/core/RGeo.java +++ b/src/main/java/org/redisson/core/RGeo.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; /** + * Geospatial items holder * * @author Nikita Koksharov * @@ -26,26 +27,140 @@ import java.util.Map; */ 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 index e2917d432..15420b385 100644 --- a/src/main/java/org/redisson/core/RGeoAsync.java +++ b/src/main/java/org/redisson/core/RGeoAsync.java @@ -28,26 +28,140 @@ import io.netty.util.concurrent.Future; */ 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); } From 33748b86509ff627be18ef0b62123190cdd202b4 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 5 Apr 2016 15:13:21 +0300 Subject: [PATCH 83/88] lib versions updated --- pom.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index a2d439b28..c8e527aae 100644 --- a/pom.xml +++ b/pom.xml @@ -100,33 +100,33 @@ io.netty netty-transport-native-epoll - 4.0.35.Final + 4.0.36.Final provided io.netty netty-common - 4.0.35.Final + 4.0.36.Final io.netty netty-codec - 4.0.35.Final + 4.0.36.Final io.netty netty-buffer - 4.0.35.Final + 4.0.36.Final io.netty netty-transport - 4.0.35.Final + 4.0.36.Final io.netty netty-handler - 4.0.35.Final + 4.0.36.Final @@ -261,7 +261,7 @@ org.apache.maven.plugins maven-eclipse-plugin - 2.9 + 2.10 true true @@ -310,7 +310,7 @@ maven-compiler-plugin - 3.3 + 3.5.1 ${source.version} ${source.version} @@ -336,7 +336,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.0 attach-sources @@ -351,7 +351,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.16 + 2.19.1 From d03e0de847f0d5cac23cf3b916164cf74d77b51c Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 8 Apr 2016 12:02:19 +0300 Subject: [PATCH 84/88] Fixed probably thread blocking issues #455 --- .../org/redisson/RedissonCountDownLatch.java | 4 +- src/main/java/org/redisson/RedissonLock.java | 4 +- .../java/org/redisson/RedissonObject.java | 6 +++ .../java/org/redisson/RedissonSemaphore.java | 5 ++- .../org/redisson/client/RedisConnection.java | 40 +++++++++++++------ .../client/handler/CommandDecoder.java | 7 +--- .../client/handler/CommandsQueue.java | 4 +- .../command/CommandAsyncExecutor.java | 3 ++ .../redisson/command/CommandAsyncService.java | 29 +++++++++++++- .../connection/pool/ConnectionPool.java | 2 +- 10 files changed, 76 insertions(+), 28 deletions(-) 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/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/RedissonObject.java b/src/main/java/org/redisson/RedissonObject.java index 7e12d8ac8..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,6 +47,10 @@ 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); } 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/client/RedisConnection.java b/src/main/java/org/redisson/client/RedisConnection.java index 4f6c49dc4..fc98647fd 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -15,6 +15,7 @@ */ package org.redisson.client; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; @@ -111,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) { diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index f0b5ce652..6e4849a00 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -1,5 +1,4 @@ /** - * 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. @@ -173,7 +172,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); @@ -206,9 +205,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); diff --git a/src/main/java/org/redisson/client/handler/CommandsQueue.java b/src/main/java/org/redisson/client/handler/CommandsQueue.java index ff65b9d4c..96446c90e 100644 --- a/src/main/java/org/redisson/client/handler/CommandsQueue.java +++ b/src/main/java/org/redisson/client/handler/CommandsQueue.java @@ -23,10 +23,10 @@ import org.redisson.client.protocol.QueueCommand; import org.redisson.client.protocol.QueueCommandHolder; import io.netty.channel.Channel; -import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; import io.netty.util.AttributeKey; import io.netty.util.internal.PlatformDependent; @@ -37,7 +37,7 @@ import io.netty.util.internal.PlatformDependent; * @author Nikita Koksharov * */ -public class CommandsQueue extends ChannelDuplexHandler { +public class CommandsQueue extends ChannelOutboundHandlerAdapter { public static final AttributeKey CURRENT_COMMAND = AttributeKey.valueOf("promise"); 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 ab0b9e18b..cf1a506a9 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -22,9 +22,11 @@ import java.util.Collection; import java.util.Collections; 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; @@ -82,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(); diff --git a/src/main/java/org/redisson/connection/pool/ConnectionPool.java b/src/main/java/org/redisson/connection/pool/ConnectionPool.java index 00c1c6886..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); // } From 9bd6a0387bf633b3cc25ceac4f5a8a48e6f96bc2 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 11 Apr 2016 14:22:48 +0300 Subject: [PATCH 85/88] RMap iterator fixed. #468 --- .../org/redisson/RedissonBaseMapIterator.java | 2 +- .../org/redisson/RedissonMapCacheTest.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/redisson/RedissonBaseMapIterator.java b/src/main/java/org/redisson/RedissonBaseMapIterator.java index 66cf04465..d493efb6d 100644 --- a/src/main/java/org/redisson/RedissonBaseMapIterator.java +++ b/src/main/java/org/redisson/RedissonBaseMapIterator.java @@ -64,7 +64,7 @@ abstract class RedissonBaseMapIterator implements Iterator { firstValues = convert(res.getMap()); } else { Map newValues = convert(res.getMap()); - if (newValues.equals(firstValues)) { + if (firstValues.entrySet().containsAll(newValues.entrySet())) { finished = true; free(firstValues); free(newValues); 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"); From 89312c230f2e10b4236b55143dbb17862ddbc4d9 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 12 Apr 2016 10:49:35 +0300 Subject: [PATCH 86/88] LUA arrays start index fixed --- src/main/java/org/redisson/RedissonList.java | 13 ++++++------- .../org/redisson/RedissonListMultimapValues.java | 10 +++++----- src/main/java/org/redisson/RedissonMap.java | 2 +- .../org/redisson/RedissonScoredSortedSet.java | 12 ++++++------ src/main/java/org/redisson/RedissonSet.java | 15 +++++++-------- src/main/java/org/redisson/RedissonSetCache.java | 7 +++---- .../org/redisson/RedissonSetMultimapValues.java | 14 +++++++------- src/main/java/org/redisson/RedissonSubList.java | 9 ++++----- 8 files changed, 39 insertions(+), 43 deletions(-) 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/RedissonListMultimapValues.java b/src/main/java/org/redisson/RedissonListMultimapValues.java index 1c6ed6d5f..abffc6e61 100644 --- a/src/main/java/org/redisson/RedissonListMultimapValues.java +++ b/src/main/java/org/redisson/RedissonListMultimapValues.java @@ -199,13 +199,13 @@ public class RedissonListMultimapValues extends RedissonExpirable implements + "return 0;" + "end; " + "local items = redis.call('lrange', KEYS[2], 0, -1);" + - "for i = 0, #items, 1 do " + - "for j = 2, table.getn(ARGV), 1 do " + "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 table.getn(ARGV) == 2 and 1 or 0; ", + + "return #ARGV == 2 and 1 or 0; ", Arrays.asList(timeoutSetName, getName()), args.toArray()); } @@ -340,7 +340,7 @@ public class RedissonListMultimapValues extends RedissonExpirable implements "local changed = 0; " + "local s = redis.call('lrange', KEYS[2], 0, -1); " - + "local i = 0; " + + "local i = 1; " + "while i <= #s do " + "local element = s[i]; " + "local isInAgrs = false; " @@ -508,7 +508,7 @@ public class RedissonListMultimapValues extends RedissonExpirable implements + "end; " + "local items = redis.call('lrange', KEYS[1], 0, -1) " + - "for i = #items, 0, -1 do " + + "for i = #items, 1, -1 do " + "if items[i] == ARGV[1] then " + "return i - 1 " + "end " + diff --git a/src/main/java/org/redisson/RedissonMap.java b/src/main/java/org/redisson/RedissonMap.java index cb8c8b54a..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 " diff --git a/src/main/java/org/redisson/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index 188460143..01875ae2b 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -280,13 +280,13 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc public Future containsAllAsync(Collection c) { 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()); } @@ -316,11 +316,11 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc 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/RedissonSet.java b/src/main/java/org/redisson/RedissonSet.java index 2afeeb563..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; @@ -168,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()); } @@ -205,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 " @@ -229,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 124fbfbc9..a4c1e13b6 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -23,7 +23,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 java.util.concurrent.TimeUnit; @@ -336,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()); } diff --git a/src/main/java/org/redisson/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java index 36d0fefee..c3546e05e 100644 --- a/src/main/java/org/redisson/RedissonSetMultimapValues.java +++ b/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -251,13 +251,13 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R + "return 0;" + "end; " + "local s = redis.call('smembers', KEYS[2]);" + - "for i = 0, table.getn(s), 1 do " + - "for j = 2, table.getn(ARGV), 1 do " + "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 table.getn(ARGV) == 2 and 1 or 0; ", + + "return #ARGV == 2 and 1 or 0; ", Arrays.asList(timeoutSetName, getName()), args.toArray()); } @@ -307,11 +307,11 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R "local changed = 0 " + "local s = redis.call('smembers', KEYS[2]) " - + "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 = 2, table.getn(ARGV), 1 do " + + "for j = 2, #ARGV, 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " @@ -350,7 +350,7 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R + "end; " + "local v = 0 " + - "for i = 2, table.getn(ARGV), 1 do " + "for i = 2, #ARGV, 1 do " + "if redis.call('srem', KEYS[2], ARGV[i]) == 1 " + "then v = 1 end " +"end " 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 " From e45d370404cbd000e932f26c701bae40c963eabf Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 12 Apr 2016 14:05:11 +0300 Subject: [PATCH 87/88] MultiDecoder empty result handling fixed --- src/main/java/org/redisson/RedissonGeo.java | 21 +++++++++++-------- .../decoder/FlatNestedMultiDecoder.java | 4 ++++ .../protocol/decoder/NestedMultiDecoder.java | 16 ++++++++++---- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/redisson/RedissonGeo.java b/src/main/java/org/redisson/RedissonGeo.java index 7a62e165c..527892d25 100644 --- a/src/main/java/org/redisson/RedissonGeo.java +++ b/src/main/java/org/redisson/RedissonGeo.java @@ -45,12 +45,19 @@ 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 @@ -119,7 +126,7 @@ public class RedissonGeo extends RedissonExpirable implements RGeo { params.add(getName()); params.addAll(Arrays.asList(members)); - MultiDecoder> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoPositionMapDecoder(params)); + 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()); } @@ -141,8 +148,7 @@ public class RedissonGeo extends RedissonExpirable implements RGeo { @Override public Future> radiusWithDistanceAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { - MultiDecoder> decoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); - RedisCommand> command = new RedisCommand>("GEORADIUS", decoder); + RedisCommand> command = new RedisCommand>("GEORADIUS", distanceDecoder); return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHDIST"); } @@ -153,8 +159,7 @@ public class RedissonGeo extends RedissonExpirable implements RGeo { @Override public Future> radiusWithPositionAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { - MultiDecoder> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); - RedisCommand> command = new RedisCommand>("GEORADIUS", decoder); + RedisCommand> command = new RedisCommand>("GEORADIUS", postitionDecoder); return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHCOORD"); } @@ -175,8 +180,7 @@ public class RedissonGeo extends RedissonExpirable implements RGeo { @Override public Future> radiusWithDistanceAsync(V member, double radius, GeoUnit geoUnit) { - MultiDecoder> decoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); - RedisCommand command = new RedisCommand("GEORADIUSBYMEMBER", decoder, 2); + RedisCommand command = new RedisCommand("GEORADIUSBYMEMBER", distanceDecoder, 2); return commandExecutor.readAsync(getName(), codec, command, getName(), member, radius, geoUnit, "WITHDIST"); } @@ -187,8 +191,7 @@ public class RedissonGeo extends RedissonExpirable implements RGeo { @Override public Future> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit) { - MultiDecoder> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder()); - RedisCommand> command = new RedisCommand>("GEORADIUSBYMEMBER", decoder, 2); + 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/client/protocol/decoder/FlatNestedMultiDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java index 413694b33..4bd04c8ee 100644 --- a/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java +++ b/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java @@ -27,6 +27,10 @@ public class FlatNestedMultiDecoder extends NestedMultiDecoder { 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); 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 c2a5f5fdd..43b5266ac 100644 --- a/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder.java +++ b/src/main/java/org/redisson/client/protocol/decoder/NestedMultiDecoder.java @@ -58,19 +58,27 @@ public class NestedMultiDecoder implements MultiDecoder { protected final MultiDecoder firstDecoder; protected final MultiDecoder secondDecoder; private MultiDecoder thirdDecoder; + private boolean handleEmpty; public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { - this.firstDecoder = firstDecoder; - this.secondDecoder = 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); @@ -121,7 +129,7 @@ public class NestedMultiDecoder implements MultiDecoder { @Override public Object decode(List parts, State state) { - if (parts.isEmpty() && state.getDecoderState() == null) { + if (parts.isEmpty() && state.getDecoderState() == null && handleEmpty) { MultiDecoder decoder = secondDecoder; if (thirdDecoder != null) { decoder = thirdDecoder; From 11eaf5d62e7996de8a26d1326c2fbb166d75c19f Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 12 Apr 2016 14:12:13 +0300 Subject: [PATCH 88/88] License header fixed --- src/main/java/org/redisson/client/handler/CommandDecoder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index 6e4849a00..64113ad5d 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -1,4 +1,5 @@ /** + * 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.