diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c6dad00aa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +# Copyright 2014 Nikita Koksharov, Nickolay Borbit +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +sudo: false +dist: trusty + +language: java +jdk: + - oraclejdk8 +env: + matrix: + - REDIS_VERSION=3.0.7 + - REDIS_VERSION=2.8.24 + - REDIS_VERSION=3.2.0-rc3 +cache: + directories: + - $HOME/.m2 +install: + - export REDIS_BIN=$HOME/redis/${REDIS_VERSION}/bin + - wget -c https://github.com/antirez/redis/archive/${REDIS_VERSION}.tar.gz -O redis-${REDIS_VERSION}.tar.gz + - tar -xvf redis-${REDIS_VERSION}.tar.gz + - make -C redis-${REDIS_VERSION} PREFIX=$HOME/redis/${REDIS_VERSION} install +before_script: + - $REDIS_BIN/redis-server --daemonize yes + - sleep 3 + - $REDIS_BIN/redis-cli PING + - export REDIS_VERSION="$(redis-cli INFO SERVER | sed -n 2p)" + - echo $REDIS_VERSION +script: mvn -DargLine="-DredisBinary=$REDIS_BIN/redis-server -DtravisEnv=true" -Punit-test -Ptravis clean verify diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9295791..6e4cfb306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,63 @@ Redisson Releases History ================================ ####Please Note: trunk is current development branch. +####30-Apr-2016 - version 2.2.13 released + +Feature - `RSet.diff` and `RSet.intersection` methods added +Imporovement - `RScoredSortedSet`.`containsAll`, `removeAll` and `retainAll` methods speed optimization +Imporovement - `RSetCache` memory and speed optimization +Imporovement - `RSet`.`retainAll`, `containsAll`, `removeAll` methods speed optimized up to 100x +Fixed - possible infinity `RLock` expiration renewal process +Fixed - error during `RSetCache.readAll` invocation. +Fixed - expiration override wasn't work in `RSetCache.add` + +####22-Apr-2016 - version 2.2.12 released + +Imporovement - Replaying phase handling in CommandDecoder +Fixed - cluster state update manager can't try next node if current node has failed to response +Fixed - cluster initialization +Fixed - items removing during `RMap` iteration +Fixed - `RGeo.addAsync` codec definition +Fixed - `RMapCache` iterator and readAll methods +Fixed - unnecessary slots migration in cluster mode +Fixed - Command batches redirect in cluster mode +Fixed - cluster mode compatibility for `RedissonMultimap.fastRemove` method +Fixed - `RedissonMultiLock` deadlock +Fixed - MultiDecoder empty result handling +Fixed - array start index in LUA scripts +Fixed - RMap iterator +Fixed - probably thread blocking issues + +####04-Apr-2016 - version 2.2.11 released + +Since this version Redisson has __perfomance boost up to 43%__ + +Feature - __new object added__ `RGeo` +Feature - __new object added__ `RBuckets` +Feature - travis-ci integration (thanks to jackygurui) +Improvement - `RScoredSortedSet.removeAllAsync` & `removeAll` methods optimization +Improvement - `RemoteService` reliability tuned up +Improvement - Reattaching RBlockingQueue\Deque blocking commands (poll, take ...) after Redis failover process or channel reconnection +Fixed - iterator objects may skip results in some cases +Fixed - RTopic listeners hangs during synchronous commands execution inside it +Fixed - Redisson hangs during shutdown if `RBlockingQueue\Deque.take` or `RBlockingQueue\Deque.poll` methods were invoked + + +####23-Mar-2016 - version 2.2.10 released + +Feature - __new object added__ `RRemoteService` +Feature - __new object added__ `RSetMultimapCache` +Feature - __new object added__ `RListMultimapCache` +Improvement - ability to cancel BRPOP and BLPOP async command execution +Improvement - Config params validation +Improvement - test RedisRunner improvements (thanks to jackygurui) +Improvement - `Double.NEGATIVE_INFINITY` and `Double.POSITIVE_INFINITY` handling for ScoredSortedSet (thanks to jackygurui) +Fixed - MOVED, ASK handling in cluster mode using RBatch +Fixed - delete and expire logic for Multimap objects +Fixed - `RLock.tryLockAsync` NPE +Fixed - possible NPE during Redisson version logging +Fixed - Netty threads shutdown after connection error + ####04-Mar-2016 - version 2.2.9 released Feature - __new object added__ `RSetMultimap` diff --git a/README.md b/README.md index 3bf37cb73..9d87ecd05 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Redisson - distributed and scalable Java objects powered by Redis. Advanced Java Redis client +Redis based In-Memory Data Grid for Java. Redisson. ==== [![Maven Central](https://img.shields.io/maven-central/v/org.redisson/redisson.svg?style=flat-square)](https://maven-badges.herokuapp.com/maven-central/org.redisson/redisson/) @@ -63,6 +63,7 @@ Features * [Spring cache](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html) integration * Supports [Reactive Streams](http://www.reactive-streams.org) * Supports [Redis pipelining](http://redis.io/topics/pipelining) (command batches) +* Supports [Remote services](https://github.com/mrniko/redisson/wiki/5.-distributed-objects#513-remote-service) * Supports Android platform * Supports auto-reconnect * Supports failed to send command auto-retry @@ -81,12 +82,12 @@ Include the following to your dependency list: org.redisson redisson - 2.2.9 + 2.2.13 ### Gradle - compile 'org.redisson:redisson:2.2.9' + compile 'org.redisson:redisson:2.2.13' ### Supported by diff --git a/pom.xml b/pom.xml index 106c3a909..974c60071 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson - 2.2.11-SNAPSHOT + 2.2.14-SNAPSHOT bundle Redisson @@ -100,33 +100,33 @@ io.netty netty-transport-native-epoll - 4.0.34.Final + 4.0.36.Final provided io.netty netty-common - 4.0.34.Final + 4.0.36.Final io.netty netty-codec - 4.0.34.Final + 4.0.36.Final io.netty netty-buffer - 4.0.34.Final + 4.0.36.Final io.netty netty-transport - 4.0.34.Final + 4.0.36.Final io.netty netty-handler - 4.0.34.Final + 4.0.36.Final @@ -145,6 +145,7 @@ com.jayway.awaitility awaitility 1.7.0 + test junit @@ -260,7 +261,7 @@ org.apache.maven.plugins maven-eclipse-plugin - 2.9 + 2.10 true true @@ -309,7 +310,7 @@ maven-compiler-plugin - 3.3 + 3.5.1 ${source.version} ${source.version} @@ -335,7 +336,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.0 attach-sources @@ -350,7 +351,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.16 + 2.19.1 diff --git a/src/main/java/org/redisson/EvictionScheduler.java b/src/main/java/org/redisson/EvictionScheduler.java index d73f861cc..245374cf6 100644 --- a/src/main/java/org/redisson/EvictionScheduler.java +++ b/src/main/java/org/redisson/EvictionScheduler.java @@ -141,6 +141,9 @@ public class EvictionScheduler { } } + public void schedule(String name) { + schedule(name, null); + } public void schedule(String name, String timeoutSetName, String maxIdleSetName) { RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName, false); @@ -225,6 +228,10 @@ public class EvictionScheduler { Arrays.asList(name, timeoutSetName, maxIdleSetName), System.currentTimeMillis(), keysLimit); } + if (timeoutSetName == null) { + return executor.writeAsync(name, LongCodec.INSTANCE, RedisCommands.ZREMRANGEBYSCORE, name, 0, System.currentTimeMillis()); + } + 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 f1fdf2953..9105c74df 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -47,8 +47,10 @@ import org.redisson.core.RBlockingDeque; import org.redisson.core.RBlockingQueue; import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; +import org.redisson.core.RBuckets; import org.redisson.core.RCountDownLatch; import org.redisson.core.RDeque; +import org.redisson.core.RGeo; import org.redisson.core.RHyperLogLog; import org.redisson.core.RKeys; import org.redisson.core.RLexSortedSet; @@ -73,6 +75,7 @@ import org.redisson.core.RSortedSet; import org.redisson.core.RTopic; import io.netty.util.concurrent.Future; +import java.util.concurrent.TimeUnit; /** * Main infrastructure class allows to get access @@ -180,6 +183,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) { @@ -191,6 +204,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)); @@ -249,11 +272,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); @@ -372,7 +390,12 @@ public class Redisson implements RedissonClient { public RRemoteService getRemoteSerivce() { return new RedissonRemoteService(this); } - + + @Override + public RRemoteService getRemoteSerivce(String name) { + return new RedissonRemoteService(this, name); + } + @Override public RSortedSet getSortedSet(String name) { return new RedissonSortedSet(commandExecutor, name); @@ -507,6 +530,12 @@ public class Redisson implements RedissonClient { public void shutdown() { connectionManager.shutdown(); } + + + @Override + public void shutdown(long quietPeriod, long timeout, TimeUnit unit) { + connectionManager.shutdown(quietPeriod, timeout, unit); + } @Override public Config getConfig() { @@ -526,16 +555,6 @@ public class Redisson implements RedissonClient { return new RedisNodes(connectionManager); } - @Override - public void flushdb() { - commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHDB)); - } - - @Override - public void flushall() { - commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHALL)); - } - @Override public boolean isShutdown() { return connectionManager.isShutdown(); diff --git a/src/main/java/org/redisson/RedissonBaseIterator.java b/src/main/java/org/redisson/RedissonBaseIterator.java new file mode 100644 index 000000000..3f6c62c7a --- /dev/null +++ b/src/main/java/org/redisson/RedissonBaseIterator.java @@ -0,0 +1,143 @@ +/** + * 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.ArrayList; +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 List lastValues; + private Iterator lastIter; + protected long nextIterPos; + protected InetSocketAddress client; + + private boolean finished; + private boolean currentElementRemoved; + private boolean removeExecuted; + private V value; + + @Override + public boolean hasNext() { + if (lastIter == null || !lastIter.hasNext()) { + if (finished) { + + currentElementRemoved = false; + removeExecuted = false; + client = null; + firstValues = null; + lastValues = null; + nextIterPos = 0; + + if (!tryAgain()) { + return false; + } + finished = false; + } + long prevIterPos; + do { + prevIterPos = nextIterPos; + ListScanResult res = iterator(client, nextIterPos); + lastValues = new ArrayList(res.getValues()); + client = res.getRedisClient(); + + if (nextIterPos == 0 && firstValues == null) { + firstValues = lastValues; + lastValues = null; + if (firstValues.isEmpty() && tryAgain()) { + client = null; + firstValues = null; + nextIterPos = 0; + prevIterPos = -1; + } + } else { + if (firstValues.isEmpty()) { + firstValues = lastValues; + lastValues = null; + if (firstValues.isEmpty() && tryAgain()) { + client = null; + firstValues = null; + nextIterPos = 0; + prevIterPos = -1; + continue; + } + } else if (lastValues.removeAll(firstValues)) { + currentElementRemoved = false; + removeExecuted = false; + client = null; + firstValues = null; + lastValues = null; + nextIterPos = 0; + prevIterPos = -1; + if (tryAgain()) { + continue; + } + finished = true; + return false; + } + } + lastIter = res.getValues().iterator(); + nextIterPos = res.getPos(); + } while (!lastIter.hasNext() && nextIterPos != prevIterPos); + if (prevIterPos == nextIterPos && !removeExecuted) { + finished = true; + } + } + return lastIter.hasNext(); + } + + protected boolean tryAgain() { + return false; + } + + abstract ListScanResult iterator(InetSocketAddress client, long nextIterPos); + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException("No such element"); + } + + value = lastIter.next(); + currentElementRemoved = false; + return value; + } + + @Override + public void remove() { + if (currentElementRemoved) { + throw new IllegalStateException("Element been already deleted"); + } + if (lastIter == null) { + throw new IllegalStateException(); + } + + firstValues.remove(value); + lastIter.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..b31246795 100644 --- a/src/main/java/org/redisson/RedissonBaseMapIterator.java +++ b/src/main/java/org/redisson/RedissonBaseMapIterator.java @@ -31,44 +31,103 @@ import io.netty.buffer.ByteBuf; abstract class RedissonBaseMapIterator implements Iterator { private Map firstValues; - private Iterator> iter; - protected long iterPos = 0; + private Map lastValues; + private Iterator> lastIter; + protected long nextIterPos; protected InetSocketAddress client; private boolean finished; + private boolean currentElementRemoved; private boolean removeExecuted; protected Map.Entry entry; @Override public boolean hasNext() { - 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); - free(newValues); - firstValues = null; + if (lastIter == null || !lastIter.hasNext()) { + if (finished) { + free(firstValues); + free(lastValues); + + currentElementRemoved = false; + removeExecuted = false; + client = null; + firstValues = null; + lastValues = null; + nextIterPos = 0; + + if (!tryAgain()) { return false; } - free(newValues); + finished = false; + } + long prevIterPos; + do { + prevIterPos = nextIterPos; + MapScanResult res = iterator(); + if (lastValues != null) { + free(lastValues); + } + lastValues = convert(res.getMap()); + client = res.getRedisClient(); + if (nextIterPos == 0 && firstValues == null) { + firstValues = lastValues; + lastValues = null; + if (firstValues.isEmpty() && tryAgain()) { + client = null; + firstValues = null; + nextIterPos = 0; + prevIterPos = -1; + } + } else { + if (firstValues.isEmpty()) { + firstValues = lastValues; + lastValues = null; + if (firstValues.isEmpty() && tryAgain()) { + client = null; + firstValues = null; + nextIterPos = 0; + prevIterPos = -1; + continue; + } + } else if (lastValues.keySet().removeAll(firstValues.keySet())) { + free(firstValues); + free(lastValues); + + currentElementRemoved = false; + removeExecuted = false; + client = null; + firstValues = null; + lastValues = null; + nextIterPos = 0; + prevIterPos = -1; + if (tryAgain()) { + continue; + } + finished = true; + return false; + } + } + lastIter = res.getMap().entrySet().iterator(); + nextIterPos = res.getPos(); + } while (!lastIter.hasNext() && nextIterPos != prevIterPos); + if (prevIterPos == nextIterPos && !removeExecuted) { + finished = true; } - iter = res.getMap().entrySet().iterator(); - iterPos = res.getPos(); } - return iter.hasNext(); + return lastIter.hasNext(); + + } + + protected boolean tryAgain() { + return false; } protected abstract MapScanResult iterator(); private void free(Map map) { + if (map == null) { + return; + } for (Entry entry : map.entrySet()) { entry.getKey().release(); entry.getValue().release(); @@ -89,8 +148,8 @@ abstract class RedissonBaseMapIterator implements Iterator { throw new NoSuchElementException("No such element at index"); } - entry = iter.next(); - removeExecuted = false; + entry = lastIter.next(); + currentElementRemoved = false; return getValue(entry); } @@ -108,14 +167,17 @@ abstract class RedissonBaseMapIterator implements Iterator { @Override public void remove() { - if (removeExecuted) { + if (currentElementRemoved) { throw new IllegalStateException("Element been already deleted"); } + if (lastIter == null) { + throw new IllegalStateException(); + } - // lazy init iterator - hasNext(); - iter.remove(); + firstValues.remove(entry.getKey().getBuf()); + lastIter.remove(); removeKey(); + currentElementRemoved = true; removeExecuted = true; } diff --git a/src/main/java/org/redisson/RedissonBuckets.java b/src/main/java/org/redisson/RedissonBuckets.java new file mode 100644 index 000000000..e9958461c --- /dev/null +++ b/src/main/java/org/redisson/RedissonBuckets.java @@ -0,0 +1,118 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.DelegateDecoderCodec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.command.CommandExecutor; +import org.redisson.connection.decoder.MapGetAllDecoder; +import org.redisson.core.RBucket; +import org.redisson.core.RBuckets; + +import io.netty.util.concurrent.Future; + +public class RedissonBuckets implements RBuckets { + + private final Codec codec; + private final CommandExecutor commandExecutor; + private final Redisson redisson; + + public RedissonBuckets(Redisson redisson, CommandExecutor commandExecutor) { + this(redisson, commandExecutor.getConnectionManager().getCodec(), commandExecutor); + } + + public RedissonBuckets(Redisson redisson, Codec codec, CommandExecutor commandExecutor) { + super(); + this.codec = codec; + this.commandExecutor = commandExecutor; + this.redisson = redisson; + } + + @Override + public List> find(String pattern) { + Collection keys = commandExecutor.get(commandExecutor., String>readAllAsync(RedisCommands.KEYS, pattern)); + List> buckets = new ArrayList>(keys.size()); + for (String key : keys) { + if(key == null) { + continue; + } + buckets.add(redisson.getBucket(key, codec)); + } + return buckets; + } + + @Override + public Map get(String... keys) { + if (keys.length == 0) { + return Collections.emptyMap(); + } + + RedisCommand> command = new RedisCommand>("MGET", new MapGetAllDecoder(Arrays.asList(keys), 0), ValueType.OBJECTS); + Future> future = commandExecutor.readAsync(keys[0], new DelegateDecoderCodec(codec), command, keys); + return commandExecutor.get(future); + } + + @Override + public boolean trySet(Map buckets) { + if (buckets.isEmpty()) { + return false; + } + + List params = new ArrayList(buckets.size()); + for (Entry entry : buckets.entrySet()) { + params.add(entry.getKey()); + try { + params.add(codec.getValueEncoder().encode(entry.getValue())); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + return commandExecutor.write(params.get(0).toString(), RedisCommands.MSETNX, params.toArray()); + } + + @Override + public void set(Map buckets) { + if (buckets.isEmpty()) { + return; + } + + List params = new ArrayList(buckets.size()); + for (Entry entry : buckets.entrySet()) { + params.add(entry.getKey()); + try { + params.add(codec.getValueEncoder().encode(entry.getValue())); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + commandExecutor.write(params.get(0).toString(), RedisCommands.MSET, params.toArray()); + } + +} diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index edb10dced..fa7ea0bb8 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -18,6 +18,7 @@ package org.redisson; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; import org.redisson.core.ClusterNode; @@ -31,8 +32,10 @@ import org.redisson.core.RBlockingDeque; import org.redisson.core.RBlockingQueue; import org.redisson.core.RBloomFilter; import org.redisson.core.RBucket; +import org.redisson.core.RBuckets; import org.redisson.core.RCountDownLatch; import org.redisson.core.RDeque; +import org.redisson.core.RGeo; import org.redisson.core.RHyperLogLog; import org.redisson.core.RKeys; import org.redisson.core.RLexSortedSet; @@ -65,6 +68,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. @@ -132,57 +153,43 @@ public interface RedissonClient { RBucket getBucket(String name, Codec codec); /** - *

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

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

Use \ to escape special characters if you want to match them verbatim. - * - *

Uses KEYS Redis command. + * Returns interface for mass operations with Bucket objects. * - * @param pattern * @return */ - List> findBuckets(String pattern); + RBuckets getBuckets(); /** - *

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

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

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

Uses MGET Redis command. - * - * @param keys - * @return + * Use {@link RBuckets#find(String)} */ - Map loadBucketValues(String ... keys); + @Deprecated + List> findBuckets(String pattern); /** - * Saves Redis object mapped by key. - * - * @param buckets + * Use {@link RBuckets#get(String...)} */ - void saveBuckets(Map buckets); + @Deprecated + Map loadBucketValues(Collection keys); + + /** + * Use {@link RBuckets#get(String...)} + */ + @Deprecated + Map loadBucketValues(String ... keys); /** - * Use {@link #findBuckets(String)} + * Use {@link RBuckets#set(Map)} */ @Deprecated - List> getBuckets(String pattern); + void saveBuckets(Map buckets); /** * Returns HyperLogLog instance by name. @@ -587,12 +594,20 @@ public interface RedissonClient { RScript getScript(); /** - * Returns object for remote operations + * Returns object for remote operations prefixed with the default name (redisson_remote_service) * * @return */ RRemoteService getRemoteSerivce(); - + + /** + * Returns object for remote operations prefixed with the specified name + * + * @param name The name used as the Redis key prefix for the services + * @return + */ + RRemoteService getRemoteSerivce(String name); + /** * Return batch object which executes group of * command in pipeline. @@ -613,8 +628,24 @@ public interface RedissonClient { /** * Shuts down Redisson instance NOT Redis server + * + * This equates to invoke shutdown(2, 15, TimeUnit.SECONDS); */ void shutdown(); + + /** + * Shuts down Redisson instance NOT Redis server + * + * Shutdown ensures that no tasks are submitted for 'the quiet period' + * (usually a couple seconds) before it shuts itself down. If a task is submitted during the quiet period, + * it is guaranteed to be accepted and the quiet period will start over. + * + * @param quietPeriod the quiet period as described in the documentation + * @param timeout the maximum amount of time to wait until the executor is {@linkplain #shutdown()} + * regardless if a task was submitted during the quiet period + * @param unit the unit of {@code quietPeriod} and {@code timeout} + */ + void shutdown(long quietPeriod, long timeout, TimeUnit unit); /** * Allows to get configuration provided @@ -639,18 +670,6 @@ public interface RedissonClient { */ NodesGroup getClusterNodesGroup(); - /** - * Use {@link RKeys#flushdb()} - */ - @Deprecated - void flushdb(); - - /** - * Use {@link RKeys#flushall()} - */ - @Deprecated - void flushall(); - /** * Returns {@code true} if this Redisson instance has been shut down. * diff --git a/src/main/java/org/redisson/RedissonCountDownLatch.java b/src/main/java/org/redisson/RedissonCountDownLatch.java index 8f3eeef1f..5a4d660a6 100644 --- a/src/main/java/org/redisson/RedissonCountDownLatch.java +++ b/src/main/java/org/redisson/RedissonCountDownLatch.java @@ -53,7 +53,7 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown public void await() throws InterruptedException { Future promise = subscribe(); try { - promise.await(); + get(promise); while (getCount() > 0) { // waiting for open state @@ -71,7 +71,7 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown public boolean await(long time, TimeUnit unit) throws InterruptedException { Future promise = subscribe(); try { - if (!promise.await(time, unit)) { + if (!await(promise, time, unit)) { return false; } diff --git a/src/main/java/org/redisson/RedissonExpirable.java b/src/main/java/org/redisson/RedissonExpirable.java index 9bebf502e..87a3d4b37 100644 --- a/src/main/java/org/redisson/RedissonExpirable.java +++ b/src/main/java/org/redisson/RedissonExpirable.java @@ -58,12 +58,12 @@ abstract class RedissonExpirable extends RedissonObject implements RExpirable { @Override public boolean expireAt(Date timestamp) { - return expireAt(timestamp.getTime() / 1000); + return expireAt(timestamp.getTime()); } @Override public Future expireAtAsync(Date timestamp) { - return expireAtAsync(timestamp.getTime() / 1000); + return expireAtAsync(timestamp.getTime()); } @Override diff --git a/src/main/java/org/redisson/RedissonGeo.java b/src/main/java/org/redisson/RedissonGeo.java new file mode 100644 index 000000000..20ca9a3d1 --- /dev/null +++ b/src/main/java/org/redisson/RedissonGeo.java @@ -0,0 +1,197 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.GeoEntryCodec; +import org.redisson.client.codec.ScoredCodec; +import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.decoder.GeoDistanceDecoder; +import org.redisson.client.protocol.decoder.GeoMapReplayDecoder; +import org.redisson.client.protocol.decoder.GeoPositionDecoder; +import org.redisson.client.protocol.decoder.GeoPositionMapDecoder; +import org.redisson.client.protocol.decoder.MultiDecoder; +import org.redisson.client.protocol.decoder.NestedMultiDecoder; +import org.redisson.client.protocol.decoder.FlatNestedMultiDecoder; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.connection.decoder.MapGetAllDecoder; +import org.redisson.core.GeoEntry; +import org.redisson.core.GeoPosition; +import org.redisson.core.GeoUnit; +import org.redisson.core.RGeo; + +import io.netty.util.concurrent.Future; + +public class RedissonGeo extends RedissonExpirable implements RGeo { + + MultiDecoder> postitionDecoder; + MultiDecoder> distanceDecoder; + + public RedissonGeo(CommandAsyncExecutor connectionManager, String name) { + super(connectionManager, name); + postitionDecoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + distanceDecoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + } + + public RedissonGeo(Codec codec, CommandAsyncExecutor connectionManager, String name) { + super(codec, connectionManager, name); + postitionDecoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + distanceDecoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); + } + + @Override + public Future addAsync(double longitude, double latitude, V member) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.GEOADD, getName(), convert(longitude), convert(latitude), member); + } + + private String convert(double longitude) { + return BigDecimal.valueOf(longitude).toPlainString(); + } + + @Override + public long add(double longitude, double latitude, V member) { + return get(addAsync(longitude, latitude, member)); + } + + @Override + public long add(GeoEntry... entries) { + return get(addAsync(entries)); + } + + @Override + public Future addAsync(GeoEntry... entries) { + List params = new ArrayList(entries.length + 1); + params.add(getName()); + for (GeoEntry entry : entries) { + params.add(entry.getLongitude()); + params.add(entry.getLatitude()); + params.add(entry.getMember()); + } + return commandExecutor.writeAsync(getName(), new GeoEntryCodec(codec), RedisCommands.GEOADD_ENTRIES, params.toArray()); + } + + @Override + public Double dist(V firstMember, V secondMember, GeoUnit geoUnit) { + return get(distAsync(firstMember, secondMember, geoUnit)); + } + + @Override + public Future distAsync(V firstMember, V secondMember, GeoUnit geoUnit) { + return commandExecutor.readAsync(getName(), new ScoredCodec(codec), RedisCommands.GEODIST, getName(), firstMember, secondMember, geoUnit); + } + + @Override + public Map hash(V... members) { + return get(hashAsync(members)); + } + + @Override + public Future> hashAsync(V... members) { + List params = new ArrayList(members.length + 1); + params.add(getName()); + params.addAll(Arrays.asList(members)); + RedisCommand> command = new RedisCommand>("GEOHASH", new MapGetAllDecoder(params, 1), 2, ValueType.OBJECTS); + return commandExecutor.readAsync(getName(), new ScoredCodec(codec), command, params.toArray()); + } + + @Override + public Map pos(V... members) { + return get(posAsync(members)); + } + + @Override + public Future> posAsync(V... members) { + List params = new ArrayList(members.length + 1); + params.add(getName()); + params.addAll(Arrays.asList(members)); + + MultiDecoder> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoPositionMapDecoder(params), true); + RedisCommand> command = new RedisCommand>("GEOPOS", decoder, 2, ValueType.OBJECTS); + return commandExecutor.readAsync(getName(), new ScoredCodec(codec), command, params.toArray()); + } + + @Override + public List radius(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return get(radiusAsync(longitude, latitude, radius, geoUnit)); + } + + @Override + public Future> radiusAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return commandExecutor.readAsync(getName(), codec, RedisCommands.GEORADIUS, getName(), convert(longitude), convert(latitude), radius, geoUnit); + } + + @Override + public Map radiusWithDistance(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return get(radiusWithDistanceAsync(longitude, latitude, radius, geoUnit)); + } + + @Override + public Future> radiusWithDistanceAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { + RedisCommand> command = new RedisCommand>("GEORADIUS", distanceDecoder); + return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHDIST"); + } + + @Override + public Map radiusWithPosition(double longitude, double latitude, double radius, GeoUnit geoUnit) { + return get(radiusWithPositionAsync(longitude, latitude, radius, geoUnit)); + } + + @Override + public Future> radiusWithPositionAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) { + RedisCommand> command = new RedisCommand>("GEORADIUS", postitionDecoder); + return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHCOORD"); + } + + @Override + public List radius(V member, double radius, GeoUnit geoUnit) { + return get(radiusAsync(member, radius, geoUnit)); + } + + @Override + public Future> radiusAsync(V member, double radius, GeoUnit geoUnit) { + return commandExecutor.readAsync(getName(), codec, RedisCommands.GEORADIUSBYMEMBER, getName(), member, radius, geoUnit); + } + + @Override + public Map radiusWithDistance(V member, double radius, GeoUnit geoUnit) { + return get(radiusWithDistanceAsync(member, radius, geoUnit)); + } + + @Override + public Future> radiusWithDistanceAsync(V member, double radius, GeoUnit geoUnit) { + RedisCommand command = new RedisCommand("GEORADIUSBYMEMBER", distanceDecoder, 2); + return commandExecutor.readAsync(getName(), codec, command, getName(), member, radius, geoUnit, "WITHDIST"); + } + + @Override + public Map radiusWithPosition(V member, double radius, GeoUnit geoUnit) { + return get(radiusWithPositionAsync(member, radius, geoUnit)); + } + + @Override + public Future> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit) { + RedisCommand> command = new RedisCommand>("GEORADIUSBYMEMBER", postitionDecoder, 2); + return commandExecutor.readAsync(getName(), codec, command, getName(), member, radius, geoUnit, "WITHCOORD"); + } +} diff --git a/src/main/java/org/redisson/RedissonKeys.java b/src/main/java/org/redisson/RedissonKeys.java index e553bb169..afab001b7 100644 --- a/src/main/java/org/redisson/RedissonKeys.java +++ b/src/main/java/org/redisson/RedissonKeys.java @@ -15,6 +15,7 @@ */ package org.redisson; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -23,7 +24,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -90,55 +90,18 @@ public class RedissonKeys implements RKeys { } private Iterator createKeysIterator(final int slot, final String pattern) { - return new Iterator() { - - private List firstValues; - private Iterator iter; - private long iterPos; - - private boolean removeExecuted; - private String value; + return new RedissonBaseIterator() { @Override - public boolean hasNext() { - if (iter == null || !iter.hasNext()) { - ListScanResult res = scanIterator(slot, iterPos, pattern); - if (iterPos == 0 && firstValues == null) { - firstValues = res.getValues(); - } else if (res.getValues().equals(firstValues)) { - return false; - } - iter = res.getValues().iterator(); - iterPos = res.getPos(); - } - return iter.hasNext(); + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return RedissonKeys.this.scanIterator(slot, nextIterPos, pattern); } @Override - public String next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element"); - } - - value = iter.next(); - removeExecuted = false; - return value; + void remove(String value) { + RedissonKeys.this.delete(value); } - - @Override - public void remove() { - if (removeExecuted) { - throw new IllegalStateException("Element been already deleted"); - } - if (iter == null) { - throw new IllegalStateException(); - } - - iter.remove(); - delete(value); - removeExecuted = true; - } - + }; } diff --git a/src/main/java/org/redisson/RedissonLexSortedSet.java b/src/main/java/org/redisson/RedissonLexSortedSet.java index 2616d064e..35eb8c17a 100644 --- a/src/main/java/org/redisson/RedissonLexSortedSet.java +++ b/src/main/java/org/redisson/RedissonLexSortedSet.java @@ -32,33 +32,64 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem super(StringCodec.INSTANCE, commandExecutor, name); } + @Override + public int removeRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { + return removeRangeByLex(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public int removeRangeByLex(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { - return get(removeRangeByLexAsync(fromElement, fromInclusive, toElement, toInclusive)); + return get(removeRangeAsync(fromElement, fromInclusive, toElement, toInclusive)); } + @Override + public int removeRangeHead(String toElement, boolean toInclusive) { + return removeRangeHeadByLex(toElement, toInclusive); + } + @Override public int removeRangeHeadByLex(String toElement, boolean toInclusive) { - return get(removeRangeHeadByLexAsync(toElement, toInclusive)); + return get(removeRangeHeadAsync(toElement, toInclusive)); } + @Override + public Future removeRangeHeadAsync(String toElement, boolean toInclusive) { + return removeRangeHeadByLexAsync(toElement, toInclusive); + } + @Override public Future removeRangeHeadByLexAsync(String toElement, boolean toInclusive) { String toValue = value(toElement, toInclusive); return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), "-", toValue); } + + @Override + public int removeRangeTail(String fromElement, boolean fromInclusive) { + return removeRangeTailByLex(fromElement, fromInclusive); + } @Override public int removeRangeTailByLex(String fromElement, boolean fromInclusive) { - return get(removeRangeTailByLexAsync(fromElement, fromInclusive)); + return get(removeRangeTailAsync(fromElement, fromInclusive)); } + @Override + public Future removeRangeTailAsync(String fromElement, boolean fromInclusive) { + return removeRangeTailByLexAsync(fromElement, fromInclusive); + } + @Override public Future removeRangeTailByLexAsync(String fromElement, boolean fromInclusive) { String fromValue = value(fromElement, fromInclusive); return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, "+"); } + @Override + public Future removeRangeAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive) { + return removeRangeByLexAsync(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public Future removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { String fromValue = value(fromElement, fromInclusive); @@ -67,33 +98,63 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, toValue); } + @Override + public Collection range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { + return lexRange(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public Collection lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { - return get(lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive)); + return get(rangeAsync(fromElement, fromInclusive, toElement, toInclusive)); } + @Override + public Collection rangeHead(String toElement, boolean toInclusive) { + return lexRangeHead(toElement, toInclusive); + } + @Override public Collection lexRangeHead(String toElement, boolean toInclusive) { - return get(lexRangeHeadAsync(toElement, toInclusive)); + return get(rangeHeadAsync(toElement, toInclusive)); } + @Override + public Future> rangeHeadAsync(String toElement, boolean toInclusive) { + return lexRangeHeadAsync(toElement, toInclusive); + } + @Override public Future> lexRangeHeadAsync(String toElement, boolean toInclusive) { String toValue = value(toElement, toInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue); } + + @Override + public Collection rangeTail(String fromElement, boolean fromInclusive) { + return lexRangeTail(fromElement, fromInclusive); + } @Override public Collection lexRangeTail(String fromElement, boolean fromInclusive) { - return get(lexRangeTailAsync(fromElement, fromInclusive)); + return get(rangeTailAsync(fromElement, fromInclusive)); } + @Override + public Future> rangeTailAsync(String fromElement, boolean fromInclusive) { + return lexRangeTailAsync(fromElement, fromInclusive); + } + @Override public Future> lexRangeTailAsync(String fromElement, boolean fromInclusive) { String fromValue = value(fromElement, fromInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+"); } + @Override + public Future> rangeAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive) { + return lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive); + } @Override public Future> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { @@ -103,33 +164,65 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, toValue); } + @Override + public Collection range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, + int offset, int count) { + return lexRange(fromElement, fromInclusive, toElement, toInclusive, offset, count); + } + @Override public Collection lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) { - return get(lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive, offset, count)); + return get(rangeAsync(fromElement, fromInclusive, toElement, toInclusive, offset, count)); } + @Override + public Collection rangeHead(String toElement, boolean toInclusive, int offset, int count) { + return lexRangeHead(toElement, toInclusive, offset, count); + } + @Override public Collection lexRangeHead(String toElement, boolean toInclusive, int offset, int count) { - return get(lexRangeHeadAsync(toElement, toInclusive, offset, count)); + return get(rangeHeadAsync(toElement, toInclusive, offset, count)); } + @Override + public Future> rangeHeadAsync(String toElement, boolean toInclusive, int offset, int count) { + return lexRangeHeadAsync(toElement, toInclusive, offset, count); + } + @Override public Future> lexRangeHeadAsync(String toElement, boolean toInclusive, int offset, int count) { String toValue = value(toElement, toInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue, "LIMIT", offset, count); } + @Override + public Collection rangeTail(String fromElement, boolean fromInclusive, int offset, int count) { + return lexRangeTail(fromElement, fromInclusive, offset, count); + } + @Override public Collection lexRangeTail(String fromElement, boolean fromInclusive, int offset, int count) { - return get(lexRangeTailAsync(fromElement, fromInclusive, offset, count)); + return get(rangeTailAsync(fromElement, fromInclusive, offset, count)); } + @Override + public Future> rangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count) { + return lexRangeTailAsync(fromElement, fromInclusive, offset, count); + } + @Override public Future> lexRangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count) { String fromValue = value(fromElement, fromInclusive); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+", "LIMIT", offset, count); } + @Override + public Future> rangeAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive, int offset, int count) { + return lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive, offset, count); + } + @Override public Future> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) { String fromValue = value(fromElement, fromInclusive); @@ -137,10 +230,20 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, toValue, "LIMIT", offset, count); } + + @Override + public int countTail(String fromElement, boolean fromInclusive) { + return lexCountTail(fromElement, fromInclusive); + } @Override public int lexCountTail(String fromElement, boolean fromInclusive) { - return get(lexCountTailAsync(fromElement, fromInclusive)); + return get(countTailAsync(fromElement, fromInclusive)); + } + + @Override + public Future countTailAsync(String fromElement, boolean fromInclusive) { + return lexCountTailAsync(fromElement, fromInclusive); } @Override @@ -149,10 +252,20 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), fromValue, "+"); } + + @Override + public int countHead(String toElement, boolean toInclusive) { + return lexCountHead(toElement, toInclusive); + } @Override public int lexCountHead(String toElement, boolean toInclusive) { - return get(lexCountHeadAsync(toElement, toInclusive)); + return get(countHeadAsync(toElement, toInclusive)); + } + + @Override + public Future countHeadAsync(String toElement, boolean toInclusive) { + return lexCountHeadAsync(toElement, toInclusive); } @Override @@ -162,11 +275,22 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), "-", toValue); } + @Override + public int count(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { + return lexCount(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public int lexCount(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { - return get(lexCountAsync(fromElement, fromInclusive, toElement, toInclusive)); + return get(countAsync(fromElement, fromInclusive, toElement, toInclusive)); } + @Override + public Future countAsync(String fromElement, boolean fromInclusive, String toElement, + boolean toInclusive) { + return lexCountAsync(fromElement, fromInclusive, toElement, toInclusive); + } + @Override public Future lexCountAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { String fromValue = value(fromElement, fromInclusive); @@ -192,6 +316,9 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem @Override public Future addAllAsync(Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(false); + } List params = new ArrayList(2*c.size()); for (Object param : c) { params.add(0); @@ -210,4 +337,14 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet implem return get(addAllAsync(c)); } + @Override + public Collection range(int startIndex, int endIndex) { + return valueRange(startIndex, endIndex); + } + + @Override + public Future> rangeAsync(int startIndex, int endIndex) { + return valueRangeAsync(startIndex, endIndex); + } + } diff --git a/src/main/java/org/redisson/RedissonList.java b/src/main/java/org/redisson/RedissonList.java index 734ec97ad..5678e9d98 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 " + @@ -567,4 +566,24 @@ public class RedissonList extends RedissonExpirable implements RList { return hashCode; } + @Override + public Future addAfterAsync(V elementToFind, V element) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "AFTER", elementToFind, element); + } + + @Override + public Future addBeforeAsync(V elementToFind, V element) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "BEFORE", elementToFind, element); + } + + @Override + public Integer addAfter(V elementToFind, V element) { + return get(addAfterAsync(elementToFind, element)); + } + + @Override + public Integer addBefore(V elementToFind, V element) { + return get(addBeforeAsync(elementToFind, element)); + } + } diff --git a/src/main/java/org/redisson/RedissonListMultimapCache.java b/src/main/java/org/redisson/RedissonListMultimapCache.java index 2ba95e527..828ff6e14 100644 --- a/src/main/java/org/redisson/RedissonListMultimapCache.java +++ b/src/main/java/org/redisson/RedissonListMultimapCache.java @@ -131,7 +131,7 @@ public class RedissonListMultimapCache extends RedissonListMultimap + "end; " + "if expireDate > tonumber(ARGV[1]) then " + "local items = redis.call('lrange', KEYS[1], 0, -1); " + - "for i=0, #items do " + + "for i = 1, #items do " + "if items[i] == ARGV[3] then " + "return 1; " + "end; " + diff --git a/src/main/java/org/redisson/RedissonListMultimapValues.java b/src/main/java/org/redisson/RedissonListMultimapValues.java index 1c6ed6d5f..3cf698a8e 100644 --- a/src/main/java/org/redisson/RedissonListMultimapValues.java +++ b/src/main/java/org/redisson/RedissonListMultimapValues.java @@ -57,7 +57,7 @@ public class RedissonListMultimapValues extends RedissonExpirable implements 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_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); @@ -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 " + @@ -685,4 +685,24 @@ public class RedissonListMultimapValues extends RedissonExpirable implements return hashCode; } + @Override + public Future addAfterAsync(V elementToFind, V element) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "AFTER", elementToFind, element); + } + + @Override + public Future addBeforeAsync(V elementToFind, V element) { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "BEFORE", elementToFind, element); + } + + @Override + public Integer addAfter(V elementToFind, V element) { + return get(addAfterAsync(elementToFind, element)); + } + + @Override + public Integer addBefore(V elementToFind, V element) { + return get(addBeforeAsync(elementToFind, element)); + } + } diff --git a/src/main/java/org/redisson/RedissonLock.java b/src/main/java/org/redisson/RedissonLock.java index 3bc14bf9b..6f26229da 100644 --- a/src/main/java/org/redisson/RedissonLock.java +++ b/src/main/java/org/redisson/RedissonLock.java @@ -30,6 +30,8 @@ import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandExecutor; import org.redisson.core.RLock; import org.redisson.pubsub.LockPubSub; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.netty.util.Timeout; import io.netty.util.TimerTask; @@ -49,6 +51,8 @@ import io.netty.util.internal.PlatformDependent; */ public class RedissonLock extends RedissonExpirable implements RLock { + private final Logger log = LoggerFactory.getLogger(RedissonLock.class); + public static final long LOCK_EXPIRATION_INTERVAL_SECONDS = 30; private static final ConcurrentMap expirationRenewalMap = PlatformDependent.newConcurrentHashMap(); protected long internalLockLeaseTime = TimeUnit.SECONDS.toMillis(LOCK_EXPIRATION_INTERVAL_SECONDS); @@ -110,7 +114,7 @@ public class RedissonLock extends RedissonExpirable implements RLock { } Future future = subscribe(); - future.sync(); + get(future); try { while (true) { @@ -155,7 +159,7 @@ public class RedissonLock extends RedissonExpirable implements RLock { } - private Future tryLockInnerAsync(long threadId) { + private Future tryLockInnerAsync(final long threadId) { Future ttlRemaining = tryLockInnerAsync(LOCK_EXPIRATION_INTERVAL_SECONDS, TimeUnit.SECONDS, threadId); ttlRemaining.addListener(new FutureListener() { @Override @@ -175,26 +179,39 @@ public class RedissonLock extends RedissonExpirable implements RLock { } private void scheduleExpirationRenewal() { - if (expirationRenewalMap.containsKey(getName())) { + if (expirationRenewalMap.containsKey(getEntryName())) { return; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { - expireAsync(internalLockLeaseTime, TimeUnit.MILLISECONDS); - expirationRenewalMap.remove(getName()); - scheduleExpirationRenewal(); // reschedule itself + Future future = expireAsync(internalLockLeaseTime, TimeUnit.MILLISECONDS); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + expirationRenewalMap.remove(getEntryName()); + if (!future.isSuccess()) { + log.error("Can't update lock " + getName() + " expiration", future.cause()); + return; + } + + if (future.getNow()) { + // reschedule itself + scheduleExpirationRenewal(); + } + } + }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); - if (expirationRenewalMap.putIfAbsent(getName(), task) != null) { + if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) { task.cancel(); } } void cancelExpirationRenewal() { - Timeout task = expirationRenewalMap.remove(getName()); + Timeout task = expirationRenewalMap.remove(getEntryName()); if (task != null) { task.cancel(); } @@ -229,7 +246,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 { @@ -377,6 +394,11 @@ public class RedissonLock extends RedissonExpirable implements RLock { } public Future unlockAsync() { + long threadId = Thread.currentThread().getId(); + return unlockAsync(threadId); + } + + public Future unlockAsync(final long threadId) { final Promise result = newPromise(); Future future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('exists', KEYS[1]) == 0) then " + @@ -396,7 +418,7 @@ public class RedissonLock extends RedissonExpirable implements RLock { "return 1; "+ "end; " + "return nil;", - Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId())); + Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId)); future.addListener(new FutureListener() { @Override @@ -409,7 +431,7 @@ public class RedissonLock extends RedissonExpirable implements RLock { Boolean opStatus = future.getNow(); if (opStatus == null) { IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " - + id + " thread-id: " + Thread.currentThread().getId()); + + id + " thread-id: " + threadId); result.setFailure(cause); return; } @@ -428,8 +450,12 @@ public class RedissonLock extends RedissonExpirable implements RLock { } public Future lockAsync(final long leaseTime, final TimeUnit unit) { - final Promise result = newPromise(); final long currentThreadId = Thread.currentThread().getId(); + return lockAsync(leaseTime, unit, currentThreadId); + } + + public Future lockAsync(final long leaseTime, final TimeUnit unit, final long currentThreadId) { + final Promise result = newPromise(); Future ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); ttlFuture.addListener(new FutureListener() { @Override @@ -525,8 +551,13 @@ public class RedissonLock extends RedissonExpirable implements RLock { } public Future tryLockAsync() { + long threadId = Thread.currentThread().getId(); + return tryLockAsync(threadId); + } + + public Future tryLockAsync(long threadId) { final Promise result = newPromise(); - Future future = tryLockInnerAsync(Thread.currentThread().getId()); + Future future = tryLockInnerAsync(threadId); future.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { @@ -546,10 +577,15 @@ public class RedissonLock extends RedissonExpirable implements RLock { } public Future tryLockAsync(final long waitTime, final long leaseTime, final TimeUnit unit) { + final long currentThreadId = Thread.currentThread().getId(); + return tryLockAsync(waitTime, leaseTime, unit, currentThreadId); + } + + public Future tryLockAsync(final long waitTime, final long leaseTime, final TimeUnit unit, + final long currentThreadId) { final Promise result = newPromise(); final AtomicLong time = new AtomicLong(unit.toMillis(waitTime)); - final long currentThreadId = Thread.currentThread().getId(); Future ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); ttlFuture.addListener(new FutureListener() { @Override diff --git a/src/main/java/org/redisson/RedissonMap.java b/src/main/java/org/redisson/RedissonMap.java index 245e6a7cd..0480dc52f 100644 --- a/src/main/java/org/redisson/RedissonMap.java +++ b/src/main/java/org/redisson/RedissonMap.java @@ -95,7 +95,7 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public Future containsKeyAsync(Object key) { - return commandExecutor.readAsync(getName(), codec, RedisCommands.HEXISTS, getName(), key); + return commandExecutor.readAsync(getName(key), codec, RedisCommands.HEXISTS, getName(key), key); } @Override @@ -107,7 +107,7 @@ public class RedissonMap extends RedissonExpirable implements RMap { public Future containsValueAsync(Object value) { return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4), "local s = redis.call('hvals', KEYS[1]);" + - "for i = 0, table.getn(s), 1 do " + "for i = 1, #s, 1 do " + "if ARGV[1] == s[i] then " + "return 1 " + "end " @@ -130,7 +130,7 @@ public class RedissonMap extends RedissonExpirable implements RMap { List args = new ArrayList(keys.size() + 1); args.add(getName()); args.addAll(keys); - return commandExecutor.readAsync(getName(), codec, new RedisCommand>("HMGET", new MapGetAllDecoder(args), 2, ValueType.MAP_KEY, ValueType.MAP_VALUE), args.toArray()); + return commandExecutor.readAsync(getName(), codec, new RedisCommand>("HMGET", new MapGetAllDecoder(args, 1), 2, ValueType.MAP_KEY, ValueType.MAP_VALUE), args.toArray()); } @Override @@ -226,13 +226,13 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public Future putIfAbsentAsync(K key, V value) { - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT, + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT, "if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then " + "return nil " + "else " + "return redis.call('hget', KEYS[1], ARGV[1]) " + "end", - Collections.singletonList(getName()), key, value); + Collections.singletonList(getName(key)), key, value); } @Override @@ -242,7 +242,7 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public Future fastPutIfAbsentAsync(K key, V value) { - return commandExecutor.writeAsync(getName(), codec, RedisCommands.HSETNX, getName(), key, value); + return commandExecutor.writeAsync(getName(key), codec, RedisCommands.HSETNX, getName(key), key, value); } @Override @@ -252,13 +252,13 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public Future removeAsync(Object key, Object value) { - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REMOVE_VALUE, + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE_VALUE, "if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then " + "return redis.call('hdel', KEYS[1], ARGV[1]) " + "else " + "return 0 " + "end", - Collections.singletonList(getName()), key, value); + Collections.singletonList(getName(key)), key, value); } @Override @@ -268,14 +268,14 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public Future replaceAsync(K key, V oldValue, V newValue) { - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REPLACE_VALUE, + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE_VALUE, "if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then " + "redis.call('hset', KEYS[1], ARGV[1], ARGV[3]); " + "return 1; " + "else " + "return 0; " + "end", - Collections.singletonList(getName()), key, oldValue, newValue); + Collections.singletonList(getName(key)), key, oldValue, newValue); } @Override @@ -285,7 +285,7 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public Future replaceAsync(K key, V value) { - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REPLACE, + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE, "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " + "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " @@ -293,36 +293,40 @@ public class RedissonMap extends RedissonExpirable implements RMap { + "else " + "return nil; " + "end", - Collections.singletonList(getName()), key, value); + Collections.singletonList(getName(key)), key, value); } @Override public Future getAsync(K key) { - return commandExecutor.readAsync(getName(), codec, RedisCommands.HGET, getName(), key); + return commandExecutor.readAsync(getName(key), codec, RedisCommands.HGET, getName(key), key); } - + + protected String getName(Object key) { + return getName(); + } + @Override public Future putAsync(K key, V value) { - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT, + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT, "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + "return v", - Collections.singletonList(getName()), key, value); + Collections.singletonList(getName(key)), key, value); } @Override public Future removeAsync(K key) { - return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REMOVE, + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE, "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "redis.call('hdel', KEYS[1], ARGV[1]); " + "return v", - Collections.singletonList(getName()), key); + Collections.singletonList(getName(key)), key); } @Override public Future fastPutAsync(K key, V value) { - return commandExecutor.writeAsync(getName(), codec, RedisCommands.HSET, getName(), key, value); + return commandExecutor.writeAsync(getName(key), codec, RedisCommands.HSET, getName(key), key, value); } @Override @@ -347,8 +351,9 @@ public class RedissonMap extends RedissonExpirable implements RMap { return get(fastRemoveAsync(keys)); } - MapScanResult scanIterator(InetSocketAddress client, long startPos) { - Future> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec), RedisCommands.HSCAN, getName(), startPos); + MapScanResult scanIterator(String name, InetSocketAddress client, long startPos) { + Future> f + = commandExecutor.readAsync(client, name, new ScanCodec(codec), RedisCommands.HSCAN, name, startPos); return get(f); } @@ -423,9 +428,9 @@ public class RedissonMap extends RedissonExpirable implements RMap { public Future addAndGetAsync(K key, Number value) { try { byte[] keyState = codec.getMapKeyEncoder().encode(key); - return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, + return commandExecutor.writeAsync(getName(key), StringCodec.INSTANCE, new RedisCommand("HINCRBYFLOAT", new NumberConvertor(value.getClass())), - getName(), keyState, new BigDecimal(value.toString()).toPlainString()); + getName(key), keyState, new BigDecimal(value.toString()).toPlainString()); } catch (IOException e) { throw new IllegalArgumentException(e); } diff --git a/src/main/java/org/redisson/RedissonMapCache.java b/src/main/java/org/redisson/RedissonMapCache.java index d529c2b03..5cb409ef0 100644 --- a/src/main/java/org/redisson/RedissonMapCache.java +++ b/src/main/java/org/redisson/RedissonMapCache.java @@ -18,6 +18,7 @@ package org.redisson; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -33,9 +34,14 @@ import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.client.protocol.convertor.LongReplayConvertor; import org.redisson.client.protocol.convertor.VoidReplayConvertor; +import org.redisson.client.protocol.decoder.ListMultiDecoder; +import org.redisson.client.protocol.decoder.LongMultiDecoder; +import org.redisson.client.protocol.decoder.MapCacheScanResult; +import org.redisson.client.protocol.decoder.MapCacheScanResultReplayDecoder; 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.ObjectListDecoder; +import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; +import org.redisson.client.protocol.decoder.ObjectMapDecoder; import org.redisson.client.protocol.decoder.ObjectMapReplayDecoder; import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.command.CommandAsyncExecutor; @@ -43,6 +49,7 @@ import org.redisson.connection.decoder.MapGetAllDecoder; import org.redisson.core.RMapCache; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; /** *

Map-based cache with ability to set TTL for each entry via @@ -69,7 +76,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac static final RedisCommand EVAL_REPLACE = new RedisCommand("EVAL", 6, ValueType.MAP, ValueType.MAP_VALUE); static final RedisCommand EVAL_REPLACE_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE, ValueType.MAP_VALUE)); private static final RedisCommand EVAL_HMSET = new RedisCommand("EVAL", new VoidReplayConvertor(), 4, ValueType.MAP); - private static final RedisCommand> EVAL_HSCAN = new RedisCommand>("EVAL", new NestedMultiDecoder(new ObjectMapReplayDecoder(), new MapScanResultReplayDecoder()), ValueType.MAP); + private static final RedisCommand> EVAL_HSCAN = new RedisCommand>("EVAL", new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapReplayDecoder(), new ObjectListReplayDecoder()), ValueType.MAP); private static final RedisCommand EVAL_REMOVE = new RedisCommand("EVAL", 4, ValueType.MAP_KEY, ValueType.MAP_VALUE); private static final RedisCommand EVAL_REMOVE_VALUE = new RedisCommand("EVAL", new LongReplayConvertor(), 5, ValueType.MAP); private static final RedisCommand EVAL_PUT_TTL = new RedisCommand("EVAL", 9, ValueType.MAP, ValueType.MAP_VALUE); @@ -162,16 +169,14 @@ 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 - + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= tonumber(maxDate); " - + "local hasExpireIdle = #expireIdleHead == 2 and tonumber(expireIdleHead[2]) <= tonumber(maxDate); " + "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter + + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime; " + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); " + "for i = #map, 1, -1 do " + "local value = map[i]; " @@ -182,18 +187,18 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "if hasExpire then " + "local expireDate = redis.call('zscore', KEYS[2], key); " - + "if expireDate ~= false and tonumber(expireDate) <= tonumber(maxDate) then " + + "if expireDate ~= false and tonumber(expireDate) <= currentTime then " + "map[i] = false; " + "end; " + "end; " - + "if hasExpireIdle and t ~= 0 then " + + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); " + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "if tonumber(expireIdle) > currentTime then " + "local value = struct.pack('dLc0', t, string.len(val), val); " + "redis.call('hset', KEYS[1], key, value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "redis.call('zadd', KEYS[3], t + currentTime, key); " + "else " + "map[i] = false; " + "end; " @@ -525,10 +530,14 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } @Override - MapScanResult scanIterator(InetSocketAddress client, long startPos) { - Future> f = commandExecutor.evalReadAsync(client, getName(), new ScanCodec(codec), EVAL_HSCAN, + MapScanResult scanIterator(String name, InetSocketAddress client, long startPos) { + RedisCommand> EVAL_HSCAN = new RedisCommand>("EVAL", + new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new ScanCodec(codec)), new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()), ValueType.MAP); + Future> f = commandExecutor.evalReadAsync(client, getName(), codec, EVAL_HSCAN, "local result = {}; " + + "local idleKeys = {}; " + "local res = redis.call('hscan', KEYS[1], ARGV[2]); " + + "local currentTime = tonumber(ARGV[1]); " + "for i, value in ipairs(res[2]) do " + "if i % 2 == 0 then " + "local key = res[2][i-1]; " + @@ -542,22 +551,61 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); " + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], key, value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "if tonumber(expireIdle) > currentTime and expireDate > currentTime then " + + "table.insert(idleKeys, key); " + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " - + "if expireDate > tonumber(ARGV[1]) then " + + "if expireDate > currentTime then " + "table.insert(result, key); " + "table.insert(result, val); " + "end; " + "end; " + "end;" - + "return {res[1], result};", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), startPos); + + "return {res[1], result, idleKeys};", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), startPos); + + f.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) + throws Exception { + if (future.isSuccess()) { + MapCacheScanResult res = future.getNow(); + if (res.getIdleKeys().isEmpty()) { + return; + } + + List args = new ArrayList(res.getIdleKeys().size() + 1); + args.add(System.currentTimeMillis()); + args.addAll(res.getIdleKeys()); + + commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), + "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter + + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); " + + "for i = #map, 1, -1 do " + + "local value = map[i]; " + + "if value ~= false then " + + "local key = ARGV[i]; " + + "local t, val = struct.unpack('dLc0', value); " + + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[2], key); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > currentTime then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], key, value); " + + "redis.call('zadd', KEYS[2], t + currentTime, key); " + + "end; " + + "end; " + + "end; " + + "end; " + + "end; ", + Arrays.asList(getName(), getIdleSetName()), args.toArray()); + + } + } + }); return get(f); } @@ -691,4 +739,73 @@ public class RedissonMapCache extends RedissonMap implements RMapCac Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName())); } + @Override + public Future>> readAllEntrySetAsync() { + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_ENTRY, + "local s = redis.call('hgetall', KEYS[1]); " + + "local result = {}; " + + "for i, v in ipairs(s) do " + + "if i % 2 == 0 then " + + "local t, val = struct.unpack('dLc0', v); " + + "local key = s[i-1];" + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], key); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], key, value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "table.insert(result, key); " + + "table.insert(result, val); " + + "end; " + + "end; " + + "end;" + + "return result;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis()); + } + + @Override + public Future> readAllValuesAsync() { + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_VALUE_LIST, + "local s = redis.call('hgetall', KEYS[1]); " + + "local result = {}; " + + "for i, v in ipairs(s) do " + + "if i % 2 == 0 then " + + "local t, val = struct.unpack('dLc0', v); " + + "local key = s[i-1];" + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], key); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], key, value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "table.insert(result, val); " + + "end; " + + "end; " + + "end;" + + "return result;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis()); + } + } diff --git a/src/main/java/org/redisson/RedissonMapIterator.java b/src/main/java/org/redisson/RedissonMapIterator.java index 4290e58e4..5b3c84326 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(map.getName(), client, nextIterPos); } protected void removeKey() { diff --git a/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java b/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java index 03918ee60..375349e95 100644 --- a/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java +++ b/src/main/java/org/redisson/RedissonMultiMapKeysIterator.java @@ -29,7 +29,7 @@ public class RedissonMultiMapKeysIterator extends RedissonBaseMapIterat } protected MapScanResult iterator() { - return map.scanIterator(client, iterPos); + return map.scanIterator(client, nextIterPos); } protected void removeKey() { diff --git a/src/main/java/org/redisson/RedissonMultimap.java b/src/main/java/org/redisson/RedissonMultimap.java index a072520a5..1075b2e33 100644 --- a/src/main/java/org/redisson/RedissonMultimap.java +++ b/src/main/java/org/redisson/RedissonMultimap.java @@ -22,13 +22,12 @@ 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 java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; @@ -167,24 +166,24 @@ public abstract class RedissonMultimap extends RedissonExpirable implement } try { - List args = new ArrayList(keys.length*2); - List hashes = new ArrayList(); + List mapKeys = new ArrayList(keys.length); + List listKeys = new ArrayList(keys.length + 1); + listKeys.add(getName()); for (K key : keys) { byte[] keyState = codec.getMapKeyEncoder().encode(key); - args.add(keyState); + mapKeys.add(keyState); String keyHash = hash(keyState); String name = getValuesName(keyHash); - hashes.add(name); + listKeys.add(name); } - args.addAll(hashes); return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG, - "local res = redis.call('hdel', KEYS[1], unpack(ARGV, 1, #ARGV/2)); " + + "local res = redis.call('hdel', KEYS[1], unpack(ARGV)); " + "if res > 0 then " + - "redis.call('del', unpack(ARGV, #ARGV/2, #ARGV)); " + + "redis.call('del', unpack(KEYS, 2, #KEYS)); " + "end; " + "return res; ", - Collections.singletonList(getName()), args.toArray()); + listKeys, mapKeys.toArray()); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/redisson/RedissonObject.java b/src/main/java/org/redisson/RedissonObject.java index a49ffa2d1..6cc3d6000 100644 --- a/src/main/java/org/redisson/RedissonObject.java +++ b/src/main/java/org/redisson/RedissonObject.java @@ -15,6 +15,8 @@ */ package org.redisson; +import java.util.concurrent.TimeUnit; + import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandAsyncExecutor; @@ -45,16 +47,20 @@ abstract class RedissonObject implements RObject { this(commandExecutor.getConnectionManager().getCodec(), commandExecutor, name); } + protected boolean await(Future future, long timeout, TimeUnit timeoutUnit) throws InterruptedException { + return commandExecutor.await(future, timeout, timeoutUnit); + } + protected V get(Future future) { return commandExecutor.get(future); } protected Promise newPromise() { - return commandExecutor.getConnectionManager().getGroup().next().newPromise(); + return commandExecutor.getConnectionManager().newPromise(); } protected Future newSucceededFuture(V result) { - return commandExecutor.getConnectionManager().newSucceededFuture(result); + return commandExecutor.getConnectionManager().newSucceededFuture(result); } @Override diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index b80124260..c3431a3b8 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -18,17 +18,23 @@ 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.client.RedisException; -import org.redisson.client.RedisTimeoutException; -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; +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; @@ -38,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); @@ -45,9 +56,15 @@ public class RedissonRemoteService implements RRemoteService { private final Map beans = PlatformDependent.newConcurrentHashMap(); private final Redisson redisson; + private final String name; public RedissonRemoteService(Redisson redisson) { + this(redisson, "redisson_remote_service"); + } + + public RedissonRemoteService(Redisson redisson, String name) { this.redisson = redisson; + this.name = name; } @Override @@ -69,7 +86,7 @@ public class RedissonRemoteService implements RRemoteService { } for (int i = 0; i < executorsAmount; i++) { - String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; + String requestQueueName = name + ":{" + remoteInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); subscribe(remoteInterface, requestQueue); } @@ -81,75 +98,117 @@ public class RedissonRemoteService implements RRemoteService { @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { + if (future.cause() instanceof RedissonShutdownException) { + return; + } + // re-subscribe after a failed takeAsync + subscribe(remoteInterface, requestQueue); return; } + + // do not subscribe now, see https://github.com/mrniko/redisson/issues/493 + // subscribe(remoteInterface, requestQueue); - RemoteServiceRequest request = future.getNow(); - 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); + final RemoteServiceRequest request = future.getNow(); + if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) { + log.debug("request: {} has been skipped due to ackTimeout"); + // re-subscribe after a skipped ackTimeout + subscribe(remoteInterface, requestQueue); + return; } - long clients = topic.publish(response); - if (clients == 0) { - log.error("None of clients has not received a response: {} for request: {}", response, request); - } + final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); + final String responseName = name + ":{" + remoteInterface.getName() + "}:" + request.getRequestId(); - subscribe(remoteInterface, requestQueue); + Future> ackClientsFuture = send(request.getAckTimeout(), responseName, new RemoteServiceAck()); + ackClientsFuture.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't send ack for request: " + request, future.cause()); + if (future.cause() instanceof RedissonShutdownException) { + return; + } + // re-subscribe after a failed send (ack) + subscribe(remoteInterface, requestQueue); + return; + } + + invokeMethod(remoteInterface, requestQueue, request, method, responseName); + } + }); } + }); } + private void invokeMethod(final Class remoteInterface, final RBlockingQueue requestQueue, final RemoteServiceRequest request, RemoteServiceMethod method, String responseName) { + final AtomicReference responseHolder = new AtomicReference(); + try { + Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); + RemoteServiceResponse response = new RemoteServiceResponse(result); + responseHolder.set(response); + } catch (Exception e) { + RemoteServiceResponse response = new RemoteServiceResponse(e.getCause()); + responseHolder.set(response); + log.error("Can't execute: " + request, e); + } + + Future> clientsFuture = send(request.getResponseTimeout(), responseName, responseHolder.get()); + clientsFuture.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't send response: " + responseHolder.get() + " for request: " + request, future.cause()); + if (future.cause() instanceof RedissonShutdownException) { + return; + } + } + // re-subscribe anyways (fail or success) after the send (response) + subscribe(remoteInterface, requestQueue); + } + }); + } + @Override public T get(Class remoteInterface) { - return get(remoteInterface, -1, null); + return get(remoteInterface, 30, TimeUnit.SECONDS); } @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() + "}"; + + String requestQueueName = name + ":{" + 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), executionTimeUnit.toMillis(executionTimeout), System.currentTimeMillis()); requestQueue.add(request); - 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(); - int listenerId = topic.addListener(new MessageListener() { - @Override - public void onMessage(String channel, RemoteServiceResponse msg) { - response.set(msg); - latch.countDown(); - } - }); + String responseName = name + ":{" + remoteInterface.getName() + "}:" + requestId; + RBlockingQueue responseQueue = redisson.getBlockingQueue(responseName); - if (timeout == -1) { - latch.await(); - } else { - if (!latch.await(timeout, timeUnit)) { - topic.removeListener(listenerId); - throw new RedisTimeoutException("No response after " + timeUnit.toMillis(timeout) + "ms for request: " + request); - } + RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(ackTimeout, ackTimeUnit); + if (ack == null) { + throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request); } - topic.removeListener(listenerId); - RemoteServiceResponse msg = response.get(); - if (msg.getError() != null) { - throw msg.getError(); + + RemoteServiceResponse response = (RemoteServiceResponse) responseQueue.poll(executionTimeout, executionTimeUnit); + if (response == null) { + throw new RemoteServiceTimeoutException("No response after " + executionTimeUnit.toMillis(executionTimeout) + "ms for request: " + request); + } + if (response.getError() != null) { + throw response.getError(); } - return msg.getResult(); + return response.getResult(); } }; return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler); @@ -161,5 +220,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/RedissonScoredSortedSet.java b/src/main/java/org/redisson/RedissonScoredSortedSet.java index e4755f679..042c44ebf 100644 --- a/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -19,13 +19,13 @@ import java.io.IOException; import java.math.BigDecimal; import java.net.InetSocketAddress; 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.NoSuchElementException; import org.redisson.client.codec.Codec; import org.redisson.client.codec.ScoredCodec; @@ -51,6 +51,16 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc super(codec, commandExecutor, name); } + @Override + public Collection readAll() { + return get(readAllAsync()); + } + + @Override + public Future> readAllAsync() { + return valueRangeAsync(0, -1); + } + @Override public V pollFirst() { return get(pollFirstAsync()); @@ -124,6 +134,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 { @@ -229,7 +242,7 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc } @Override - public int rank(V o) { + public Integer rank(V o) { return get(rankAsync(o)); } @@ -245,54 +258,18 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @Override public Iterator iterator() { - return new Iterator() { - - private List firstValues; - private Iterator iter; - private InetSocketAddress client; - private long iterPos; - - private boolean removeExecuted; - private V value; - - @Override - public boolean hasNext() { - if (iter == null || !iter.hasNext()) { - ListScanResult res = scanIterator(client, iterPos); - client = res.getRedisClient(); - if (iterPos == 0 && firstValues == null) { - firstValues = res.getValues(); - } else if (res.getValues().equals(firstValues)) { - return false; - } - iter = res.getValues().iterator(); - iterPos = res.getPos(); - } - return iter.hasNext(); - } + return new RedissonBaseIterator() { @Override - public V next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element at index"); - } - - value = iter.next(); - removeExecuted = false; - return value; + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return scanIterator(client, nextIterPos); } @Override - public void remove() { - if (removeExecuted) { - throw new IllegalStateException("Element been already deleted"); - } - - iter.remove(); + void remove(V value) { RedissonScoredSortedSet.this.remove(value); - removeExecuted = true; } - + }; } @@ -315,28 +292,32 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc @Override public Future containsAllAsync(Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(true); + } + return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), - "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 " - + "if ARGV[j] == s[i] " - + "then table.remove(ARGV, j) end " + "for j = 1, #ARGV, 1 do " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[j]) " + + "if expireDateScore == false then " + + "return 0;" + + "end; " + "end; " - + "end;" - + "return table.getn(ARGV) == 0 and 1 or 0; ", + + "return 1; ", Collections.singletonList(getName()), c.toArray()); } @Override public Future removeAllAsync(Collection c) { - return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), - "local v = 0 " + - "for i = 0, table.getn(ARGV), 1 do " - + "if redis.call('zrem', KEYS[1], ARGV[i]) == 1 " - + "then v = 1 end " - +"end " - + "return v ", - Collections.singletonList(getName()), c.toArray()); + if (c.isEmpty()) { + return newSucceededFuture(false); + } + + List params = new ArrayList(c.size()+1); + params.add(getName()); + params.addAll(c); + + return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZREM, params.toArray()); } @Override @@ -348,30 +329,34 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc public boolean retainAll(Collection c) { return get(retainAllAsync(c)); } + + private byte[] encode(V value) { + try { + return codec.getValueEncoder().encode(value); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } @Override public Future retainAllAsync(Collection c) { - return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), - "local changed = 0 " + - "local s = redis.call('zrange', KEYS[1], 0, -1) " - + "local i = 0 " - + "while i <= table.getn(s) do " - + "local element = s[i] " - + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " - + "if ARGV[j] == element then " - + "isInAgrs = true " - + "break " - + "end " - + "end " - + "if isInAgrs == false then " - + "redis.call('zrem', KEYS[1], element) " - + "changed = 1 " - + "end " - + "i = i + 1 " - + "end " - + "return changed ", - Collections.singletonList(getName()), c.toArray()); + if (c.isEmpty()) { + return deleteAsync(); + } + + List params = new ArrayList(c.size()*2); + for (Object object : c) { + params.add(0); + params.add(encode((V)object)); + } + + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "redis.call('zadd', KEYS[2], unpack(ARGV)); " + + "local prevSize = redis.call('zcard', KEYS[1]); " + + "local size = redis.call('zinterstore', KEYS[1], 2, KEYS[1], KEYS[2], 'aggregate', 'sum');" + + "redis.call('del', KEYS[2]); " + + "return size ~= prevSize and 1 or 0; ", + Arrays.asList(getName(), "redisson_temp__{" + getName() + "}"), params.toArray()); } @Override @@ -414,7 +399,7 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc public Future> valueRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { String startValue = value(startScore, startScoreInclusive); String endValue = value(endScore, endScoreInclusive); - return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE, getName(), startValue, endValue); + return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE_LIST, getName(), startValue, endValue); } @Override @@ -453,7 +438,7 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc public Future> valueRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { String startValue = value(startScore, startScoreInclusive); String endValue = value(endScore, endScoreInclusive); - return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE, getName(), startValue, endValue, "LIMIT", offset, count); + return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE_LIST, getName(), startValue, endValue, "LIMIT", offset, count); } @Override @@ -473,6 +458,11 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc return get(entryRangeAsync(startScore, startScoreInclusive, endScore, endScoreInclusive, offset, count)); } + @Override + public Collection> entryRangeReversed(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { + return get(entryRangeReversedAsync(startScore, startScoreInclusive, endScore, endScoreInclusive, offset, count)); + } + @Override public Future>> entryRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { String startValue = value(startScore, startScoreInclusive); @@ -480,14 +470,31 @@ public class RedissonScoredSortedSet extends RedissonExpirable implements RSc return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE_ENTRY, getName(), startValue, endValue, "WITHSCORES", "LIMIT", offset, count); } + @Override + public Future>> entryRangeReversedAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { + String startValue = value(startScore, startScoreInclusive); + String endValue = value(endScore, endScoreInclusive); + return commandExecutor.readAsync(getName(), codec, RedisCommands.ZREVRANGEBYSCORE_ENTRY, getName(), endValue, startValue, "WITHSCORES", "LIMIT", offset, count); + } + @Override public Future revRankAsync(V o) { return commandExecutor.readAsync(getName(), codec, RedisCommands.ZREVRANK_INT, getName(), o); } @Override - public int revRank(V o) { + public Integer revRank(V o) { return get(revRankAsync(o)); } + public Long count(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { + return get(countAsync(startScore, startScoreInclusive, endScore, endScoreInclusive)); + } + + public Future countAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { + String startValue = value(startScore, startScoreInclusive); + String endValue = value(endScore, endScoreInclusive); + return commandExecutor.readAsync(getName(), codec, RedisCommands.ZCOUNT, getName(), startValue, endValue); + } + } diff --git a/src/main/java/org/redisson/RedissonSemaphore.java b/src/main/java/org/redisson/RedissonSemaphore.java index e29a4e0a0..a23cd966b 100644 --- a/src/main/java/org/redisson/RedissonSemaphore.java +++ b/src/main/java/org/redisson/RedissonSemaphore.java @@ -70,7 +70,8 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore { return; } - Future future = subscribe().sync(); + Future future = subscribe(); + get(future); try { while (true) { if (tryAcquire(permits)) { @@ -113,7 +114,7 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore { long time = unit.toMillis(waitTime); Future future = subscribe(); - if (!future.await(time, TimeUnit.MILLISECONDS)) { + if (!await(future, time, TimeUnit.MILLISECONDS)) { return false; } diff --git a/src/main/java/org/redisson/RedissonSet.java b/src/main/java/org/redisson/RedissonSet.java index 9904bcc30..7303a68cc 100644 --- a/src/main/java/org/redisson/RedissonSet.java +++ b/src/main/java/org/redisson/RedissonSet.java @@ -19,14 +19,15 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.Set; import 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.decoder.ListScanResult; import org.redisson.command.CommandAsyncExecutor; import org.redisson.core.RSet; @@ -72,76 +73,32 @@ public class RedissonSet extends RedissonExpirable implements RSet { @Override public Future containsAsync(Object o) { - return commandExecutor.readAsync(getName(), codec, RedisCommands.SISMEMBER, getName(), o); + return commandExecutor.readAsync(getName(o), codec, RedisCommands.SISMEMBER, getName(o), o); } - private ListScanResult scanIterator(InetSocketAddress client, long startPos) { - Future> f = commandExecutor.readAsync(client, getName(), codec, RedisCommands.SSCAN, getName(), startPos); + protected String getName(Object o) { + return getName(); + } + + ListScanResult scanIterator(String name, InetSocketAddress client, long startPos) { + Future> f = commandExecutor.readAsync(client, name, codec, RedisCommands.SSCAN, name, startPos); 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; + return new RedissonBaseIterator() { @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(); + ListScanResult iterator(InetSocketAddress client, long nextIterPos) { + return scanIterator(getName(), client, nextIterPos); } @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(); + void remove(V value) { RedissonSet.this.remove(value); - currentElementRemoved = true; - removeExecuted = true; } - + }; } @@ -174,7 +131,7 @@ public class RedissonSet extends RedissonExpirable implements RSet { @Override public Future addAsync(V e) { - return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_SINGLE, getName(), e); + return commandExecutor.writeAsync(getName(e), codec, RedisCommands.SADD_SINGLE, getName(e), e); } @Override @@ -189,7 +146,7 @@ public class RedissonSet extends RedissonExpirable implements RSet { @Override public Future removeAsync(Object o) { - return commandExecutor.writeAsync(getName(), codec, RedisCommands.SREM_SINGLE, getName(), o); + return commandExecutor.writeAsync(getName(o), codec, RedisCommands.SREM_SINGLE, getName(o), o); } @Override @@ -199,7 +156,7 @@ public class RedissonSet extends RedissonExpirable implements RSet { @Override public Future moveAsync(String destination, V member) { - return commandExecutor.writeAsync(getName(), codec, RedisCommands.SMOVE, getName(), destination, member); + return commandExecutor.writeAsync(getName(member), codec, RedisCommands.SMOVE, getName(member), destination, member); } @Override @@ -214,29 +171,29 @@ public class RedissonSet extends RedissonExpirable implements RSet { @Override 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 " - + "if ARGV[j] == s[i] " - + "then table.remove(ARGV, j) end " - + "end; " - + "end;" - + "return table.getn(ARGV) == 0 and 1 or 0; ", - Collections.singletonList(getName()), c.toArray()); + if (c.isEmpty()) { + return newSucceededFuture(true); + } + + return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS), + "redis.call('sadd', KEYS[2], unpack(ARGV)); " + + "local size = redis.call('sdiff', KEYS[2], KEYS[1]);" + + "redis.call('del', KEYS[2]); " + + "return #size == 0 and 1 or 0; ", + Arrays.asList(getName(), "redisson_temp__{" + getName() + "}"), c.toArray()); } @Override public boolean addAll(Collection c) { - if (c.isEmpty()) { - return false; - } - return get(addAllAsync(c)); } @Override public Future addAllAsync(Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(false); + } + List args = new ArrayList(c.size() + 1); args.add(getName()); args.addAll(c); @@ -250,39 +207,29 @@ public class RedissonSet extends RedissonExpirable implements RSet { @Override public Future retainAllAsync(Collection c) { - 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 element = s[i] " - + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " - + "if ARGV[j] == element then " - + "isInAgrs = true " - + "break " - + "end " - + "end " - + "if isInAgrs == false then " - + "redis.call('SREM', KEYS[1], element) " - + "changed = 1 " - + "end " - + "i = i + 1 " - + "end " - + "return changed ", - Collections.singletonList(getName()), c.toArray()); + if (c.isEmpty()) { + return deleteAsync(); + } + return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS), + "redis.call('sadd', KEYS[2], unpack(ARGV)); " + + "local prevSize = redis.call('scard', KEYS[1]); " + + "local size = redis.call('sinterstore', KEYS[1], KEYS[1], KEYS[2]);" + + "redis.call('del', KEYS[2]); " + + "return size ~= prevSize and 1 or 0; ", + Arrays.asList(getName(), "redisson_temp__{" + getName() + "}"), c.toArray()); } @Override 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 " - + "if redis.call('srem', KEYS[1], ARGV[i]) == 1 " - + "then v = 1 end " - +"end " - + "return v ", - Collections.singletonList(getName()), c.toArray()); + if (c.isEmpty()) { + return newSucceededFuture(false); + } + + List args = new ArrayList(c.size() + 1); + args.add(getName()); + args.addAll(c); + + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SREM_SINGLE, args.toArray()); } @Override @@ -316,9 +263,78 @@ public class RedissonSet extends RedissonExpirable implements RSet { return commandExecutor.writeAsync(getName(), codec, RedisCommands.SUNION, args.toArray()); } + @Override + public int diff(String... names) { + return get(diffAsync(names)); + } + + @Override + public Future diffAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SDIFFSTORE_INT, args.toArray()); + } + + @Override + public Set readDiff(String... names) { + return get(readDiffAsync(names)); + } + + @Override + public Future> readDiffAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SDIFF, args.toArray()); + } + + @Override + public int intersection(String... names) { + return get(intersectionAsync(names)); + } + + @Override + public Future intersectionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTERSTORE_INT, args.toArray()); + } + + @Override + public Set readIntersection(String... names) { + return get(readIntersectionAsync(names)); + } + + @Override + public Future> readIntersectionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTER, args.toArray()); + } + @Override public void clear() { delete(); } + @Override + 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(' '); + } + } + } diff --git a/src/main/java/org/redisson/RedissonSetCache.java b/src/main/java/org/redisson/RedissonSetCache.java index 97f8526a7..3704be77a 100644 --- a/src/main/java/org/redisson/RedissonSetCache.java +++ b/src/main/java/org/redisson/RedissonSetCache.java @@ -17,40 +17,30 @@ package org.redisson; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; -import org.redisson.client.codec.LongCodec; import org.redisson.client.protocol.RedisCommand; +import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.RedisStrictCommand; import org.redisson.client.protocol.convertor.BooleanReplayConvertor; -import org.redisson.client.protocol.convertor.VoidReplayConvertor; 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; /** *

Set-based cache with ability to set TTL for each entry via * {@link #put(Object, Object, long, TimeUnit)} method. - * And therefore has an complex lua-scripts inside. - * Uses map(value_hash, value) to tie with sorted set which contains expiration record for every value with TTL. *

* *

Current Redis implementation doesn't have set entry eviction functionality. @@ -69,17 +59,14 @@ import net.openhft.hashing.LongHashFunction; */ public class RedissonSetCache extends RedissonExpirable implements RSetCache { - private static final RedisCommand ADD_ALL = new RedisCommand("HMSET", new VoidReplayConvertor()); - private static final RedisStrictCommand HDEL = new RedisStrictCommand("HDEL", new BooleanReplayConvertor()); - - protected RedissonSetCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { + public RedissonSetCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { super(commandExecutor, name); - evictionScheduler.schedule(getName(), getTimeoutSetName()); + evictionScheduler.schedule(getName()); } - protected RedissonSetCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { + public RedissonSetCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { super(codec, commandExecutor, name); - evictionScheduler.schedule(getName(), getTimeoutSetName()); + evictionScheduler.schedule(getName()); } @Override @@ -89,7 +76,7 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @Override public Future sizeAsync() { - return commandExecutor.readAsync(getName(), codec, RedisCommands.HLEN, getName()); + return commandExecutor.readAsync(getName(), codec, RedisCommands.ZCARD_INT, getName()); } @Override @@ -102,128 +89,56 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< return get(containsAsync(o)); } - private byte[] hash(Object o) { - if (o == null) { - throw new NullPointerException("Value can't be null"); - } - try { - byte[] objectState = codec.getValueEncoder().encode(o); - return hash(objectState); - } catch (IOException e) { - throw new IllegalArgumentException(e); - } - } - - private byte[] hash(byte[] objectState) { - long h1 = LongHashFunction.farmUo().hashBytes(objectState); - long h2 = LongHashFunction.xx_r39().hashBytes(objectState); - - ByteBuf buf = Unpooled.buffer((2 * Long.SIZE) / Byte.SIZE).writeLong(h1).writeLong(h2); - try { - return buf.array(); - } finally { - buf.release(); - } - } - - String getTimeoutSetName() { - return "redisson__timeout__set__{" + getName() + "}"; - } - @Override public Future containsAsync(Object o) { - byte[] key = hash(o); - return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, - "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 " - + "return 0;" - + "end; " + - "end;" + - "return value; ", - Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), key); + return commandExecutor.evalReadAsync(getName(), codec, new RedisStrictCommand("EVAL", new BooleanReplayConvertor(), 5), + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " + + "if expireDateScore ~= false then " + + "if tonumber(expireDateScore) <= tonumber(ARGV[1]) then " + + "return 0;" + + "else " + + "return 1;" + + "end;" + + "else " + + "return 0;" + + "end; ", + Arrays.asList(getName()), System.currentTimeMillis(), o); } ListScanResult scanIterator(InetSocketAddress client, long startPos) { - Future> f = commandExecutor.evalReadAsync(client, getName(), codec, RedisCommands.EVAL_SSCAN, + Future> f = scanIteratorAsync(client, startPos); + return get(f); + } + + public Future> scanIteratorAsync(InetSocketAddress client, long startPos) { + return commandExecutor.evalReadAsync(client, getName(), codec, RedisCommands.EVAL_ZSCAN, "local result = {}; " - + "local res = redis.call('hscan', KEYS[1], ARGV[1]); " + + "local res = redis.call('zscan', KEYS[1], ARGV[1]); " + "for i, value in ipairs(res[2]) do " + "if i % 2 == 0 then " - + "local key = res[2][i-1]; " - + "local expireDate = redis.call('zscore', KEYS[2], key); " - + "if (expireDate == false) or (expireDate ~= false and tonumber(expireDate) > tonumber(ARGV[2])) then " - + "table.insert(result, value); " + + "local expireDate = value; " + + "if tonumber(expireDate) > tonumber(ARGV[2]) then " + + "table.insert(result, res[2][i-1]); " + "end; " - + "end; " + + "end;" + "end;" - + "return {res[1], result};", Arrays.asList(getName(), getTimeoutSetName()), startPos, System.currentTimeMillis()); - return get(f); + + "return {res[1], result};", Arrays.asList(getName()), startPos, System.currentTimeMillis()); } @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; + return new RedissonBaseIterator() { @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(); + 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(); - 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(); + void remove(V value) { RedissonSetCache.this.remove(value); - currentElementRemoved = true; - removeExecuted = true; } - + }; } @@ -234,49 +149,16 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @Override public Future> readAllAsync() { - return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_SET, - "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + - "local keys = redis.call('hkeys', KEYS[1]); " - + "if #keys == 0 then " - + "return {}; " - + "end; " + - "local maxDate = ARGV[1]; " + - "local minExpireDate = 92233720368547758;" + - "if #expireHead == 2 and tonumber(expireHead[2]) <= tonumber(maxDate) then " + - "for i = #keys, 1, -1 do " + - "local key = keys[i]; " + - "local expireDate = redis.call('zscore', KEYS[2], key); " + - "if expireDate ~= false and tonumber(expireDate) <= tonumber(maxDate) then " + - "minExpireDate = math.min(tonumber(expireDate), minExpireDate); " + - "table.remove(keys, i); " + - "end;" + - "end;" + - "end; " + - "return redis.call('hmget', KEYS[1], unpack(keys));", - Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis()); + return (Future>)readAllAsync(RedisCommands.ZRANGEBYSCORE); } + private Future readAllAsync(RedisCommand> command) { + return commandExecutor.readAsync(getName(), codec, command, getName(), System.currentTimeMillis(), 92233720368547758L); + } + + private Future> readAllasListAsync() { - return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LIST, - "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + - "local keys = redis.call('hkeys', KEYS[1]); " + - "if #keys == 0 then " - + "return {}; " + - "end; " + - "local maxDate = ARGV[1]; " + - "local minExpireDate = 92233720368547758;" + - "if #expireHead == 2 and tonumber(expireHead[2]) <= tonumber(maxDate) then " + - "for i = #keys, 1, -1 do " + - "local key = keys[i]; " + - "local expireDate = redis.call('zscore', KEYS[2], key); " + - "if expireDate ~= false and tonumber(expireDate) <= tonumber(maxDate) then " + - "minExpireDate = math.min(tonumber(expireDate), minExpireDate); " + - "table.remove(keys, i); " + - "end;" + - "end;" + - "end; " + - "return redis.call('hmget', KEYS[1], unpack(keys));", - Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis()); + return (Future>)readAllAsync(RedisCommands.ZRANGEBYSCORE_LIST); } @Override @@ -314,49 +196,35 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< throw new NullPointerException("TimeUnit param can't be null"); } + byte[] objectState = encode(value); + + long timeoutDate = System.currentTimeMillis() + unit.toMillis(ttl); + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false and tonumber(expireDateScore) > tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "redis.call('zadd', KEYS[1], ARGV[2], ARGV[3]); " + + "return 1; ", + Arrays.asList(getName()), System.currentTimeMillis(), timeoutDate, objectState); + } + + private byte[] encode(V value) { try { - byte[] objectState = encode(value); - byte[] key = hash(objectState); - - 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); + return codec.getValueEncoder().encode(value); } catch (IOException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } - private byte[] encode(V value) throws IOException { - return codec.getValueEncoder().encode(value); - } - @Override public Future addAsync(V value) { - try { - 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); - } catch (IOException e) { - throw new RuntimeException(e); - } + return addAsync(value, 92233720368547758L - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public Future removeAsync(Object o) { - byte[] key = hash(o); - return commandExecutor.writeAsync(getName(), codec, HDEL, getName(), key); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZREM, getName(), o); } @Override @@ -371,17 +239,27 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @Override 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 " - + "if ARGV[j] == s[i] then " - + "table.remove(ARGV, j) " - + "end " + if (c.isEmpty()) { + return newSucceededFuture(true); + } + + List params = new ArrayList(c.size() + 1); + params.add(System.currentTimeMillis()); + params.addAll(c); + + return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS), + "for j = 2, #ARGV, 1 do " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[j]) " + + "if expireDateScore ~= false then " + + "if tonumber(expireDateScore) <= tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "else " + + "return 0;" + + "end; " + "end; " - + "end;" - + "return table.getn(ARGV) == 0 and 1 or 0; ", - Collections.singletonList(getName()), c.toArray()); + + "return 1; ", + Collections.singletonList(getName()), params.toArray()); } @Override @@ -395,20 +273,16 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< return newSucceededFuture(false); } + long score = 92233720368547758L - System.currentTimeMillis(); List params = new ArrayList(c.size()*2 + 1); params.add(getName()); - try { - for (V value : c) { - byte[] objectState = encode(value); - byte[] key = hash(objectState); - params.add(key); - params.add(objectState); - } - } catch (IOException e) { - throw new RuntimeException(e); + for (V value : c) { + byte[] objectState = encode(value); + params.add(score); + params.add(objectState); } - return commandExecutor.writeAsync(getName(), codec, ADD_ALL, params.toArray()); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZADD_BOOL_RAW, params.toArray()); } @Override @@ -418,48 +292,37 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< @Override public Future retainAllAsync(Collection c) { - List params = new ArrayList(c.size()); + if (c.isEmpty()) { + return deleteAsync(); + } + + long score = 92233720368547758L - System.currentTimeMillis(); + List params = new ArrayList(c.size()*2); for (Object object : c) { - params.add(hash(object)); + params.add(score); + params.add(encode((V)object)); } + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, - "local keys = redis.call('hkeys', KEYS[1]); " + - "local i=1;" + - "while i <= #keys do " - + "local changed = false;" - + "local element = keys[i];" - + "for j, argElement in pairs(ARGV) do " - + "if argElement == element then " - + "changed = true;" - + "table.remove(keys, i); " - + "table.remove(ARGV, j); " - + "break; " - + "end; " - + "end; " + - "if changed == false then " + - "i = i + 1 " + - "end " + - "end " + - "if #keys > 0 then " - + "for i=1, #keys,5000 do " - + "redis.call('hdel', KEYS[1], unpack(keys, i, math.min(i+4999, #keys))); " - + "redis.call('zrem', KEYS[2], unpack(keys, i, math.min(i+4999, #keys))); " - + "end " - + "return 1;" - + "end; " - + "return 0; ", - Arrays.asList(getName(), getTimeoutSetName()), params.toArray()); + "redis.call('zadd', KEYS[2], unpack(ARGV)); " + + "local prevSize = redis.call('zcard', KEYS[1]); " + + "local size = redis.call('zinterstore', KEYS[1], #ARGV/2, KEYS[1], KEYS[2], 'aggregate', 'min');" + + "redis.call('del', KEYS[2]); " + + "return size ~= prevSize and 1 or 0; ", + Arrays.asList(getName(), "redisson_temp__{" + getName() + "}"), params.toArray()); } @Override public Future removeAllAsync(Collection c) { + if (c.isEmpty()) { + return newSucceededFuture(false); + } + List params = new ArrayList(c.size()+1); params.add(getName()); - for (Object object : c) { - params.add(hash(object)); - } + params.addAll(c); - return commandExecutor.writeAsync(getName(), codec, HDEL, params.toArray()); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZREM, params.toArray()); } @Override @@ -472,36 +335,4 @@ public class RedissonSetCache extends RedissonExpirable implements RSetCache< delete(); } - @Override - public Future deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); - } - - @Override - public Future expireAsync(long timeToLive, TimeUnit timeUnit) { - return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpire', KEYS[2], ARGV[1]); " - + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", - Arrays.asList(getName(), getTimeoutSetName()), timeUnit.toMillis(timeToLive)); - } - - @Override - public Future expireAtAsync(long timestamp) { - return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpireat', KEYS[2], ARGV[1]); " - + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", - Arrays.asList(getName(), getTimeoutSetName()), timestamp); - } - - @Override - public Future clearExpireAsync() { - return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + - "redis.call('persist', KEYS[2]); " - + "return redis.call('persist', KEYS[1]); ", - Arrays.asList(getName(), getTimeoutSetName())); - } - } diff --git a/src/main/java/org/redisson/RedissonSetMultimapValues.java b/src/main/java/org/redisson/RedissonSetMultimapValues.java index 215c82cd4..629cbd111 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; @@ -52,7 +51,7 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R 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_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); @@ -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; } - + }; } @@ -300,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()); } @@ -356,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 " @@ -399,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 " @@ -443,4 +394,56 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R delete(); } + @Override + public int diff(String... names) { + return get(diffAsync(names)); + } + + @Override + public Future diffAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SDIFFSTORE_INT, args.toArray()); + } + + @Override + public Set readDiff(String... names) { + return get(readDiffAsync(names)); + } + + @Override + public Future> readDiffAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SDIFF, args.toArray()); + } + + @Override + public int intersection(String... names) { + return get(intersectionAsync(names)); + } + + @Override + public Future intersectionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTERSTORE_INT, args.toArray()); + } + + @Override + public Set readIntersection(String... names) { + return get(readIntersectionAsync(names)); + } + + @Override + public Future> readIntersectionAsync(String... names) { + List args = new ArrayList(names.length + 1); + args.add(getName()); + args.addAll(Arrays.asList(names)); + return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTER, args.toArray()); + } + } diff --git a/src/main/java/org/redisson/RedissonShutdownException.java b/src/main/java/org/redisson/RedissonShutdownException.java new file mode 100644 index 000000000..ddfcb1aa1 --- /dev/null +++ b/src/main/java/org/redisson/RedissonShutdownException.java @@ -0,0 +1,26 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson; + +public class RedissonShutdownException extends RuntimeException { + + private static final long serialVersionUID = -2694051226420789395L; + + public RedissonShutdownException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/redisson/RedissonSubList.java b/src/main/java/org/redisson/RedissonSubList.java index 3306d83c3..4e474bb7d 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()); } @@ -179,7 +179,7 @@ public class RedissonSubList extends RedissonList implements RList { "local items = redis.call('lrange', KEYS[1], fromIndex, toIndex); " + "for i=1, #items do " + - "for j = 0, #ARGV, 1 do " + + "for j = 1, #ARGV, 1 do " + "if items[i] == ARGV[j] then " + "redis.call('lrem', KEYS[1], count, ARGV[i]); " + "v = 1; " + @@ -203,11 +203,10 @@ public class RedissonSubList extends RedissonList implements RList { "local toIndex = table.remove(ARGV, 2);" + "local items = redis.call('lrange', KEYS[1], fromIndex, toIndex) " + "local i = 1 " - + "local s = table.getn(items) " - + "while i <= s do " + + "while i <= #items do " + "local element = items[i] " + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " + + "for j = 1, #ARGV, 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " diff --git a/src/main/java/org/redisson/client/RedisClient.java b/src/main/java/org/redisson/client/RedisClient.java index e5dd7ad5b..32e5445aa 100644 --- a/src/main/java/org/redisson/client/RedisClient.java +++ b/src/main/java/org/redisson/client/RedisClient.java @@ -19,7 +19,7 @@ import java.net.InetSocketAddress; import org.redisson.client.handler.CommandDecoder; import org.redisson.client.handler.CommandEncoder; -import org.redisson.client.handler.CommandsListEncoder; +import org.redisson.client.handler.CommandBatchEncoder; import org.redisson.client.handler.CommandsQueue; import org.redisson.client.handler.ConnectionWatchdog; @@ -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 { @@ -61,8 +62,8 @@ public class RedisClient { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addFirst(new ConnectionWatchdog(bootstrap, channels), - new CommandEncoder(), - new CommandsListEncoder(), + CommandEncoder.INSTANCE, + CommandBatchEncoder.INSTANCE, new CommandsQueue(), new CommandDecoder()); } @@ -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 53f1c9383..72158ff59 100644 --- a/src/main/java/org/redisson/client/RedisConnection.java +++ b/src/main/java/org/redisson/client/RedisConnection.java @@ -15,21 +15,24 @@ */ package org.redisson.client; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; +import org.redisson.client.handler.CommandsQueue; import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandsData; +import org.redisson.client.protocol.QueueCommand; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.RedisStrictCommand; -import org.redisson.connection.FastSuccessFuture; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.ScheduledFuture; @@ -45,7 +48,7 @@ public class RedisConnection implements RedisCommands { private ReconnectListener reconnectListener; private long lastUsageTime; - private final Future acquireFuture = new FastSuccessFuture(this); + private final Future acquireFuture = ImmediateEventExecutor.INSTANCE.newSucceededFuture(this); public RedisConnection(RedisClient redisClient, Channel channel) { super(); @@ -59,6 +62,18 @@ public class RedisConnection implements RedisCommands { return (C) channel.attr(RedisConnection.CONNECTION).get(); } + public void removeCurrentCommand() { + channel.attr(CommandsQueue.CURRENT_COMMAND).remove(); + } + + public CommandData getCurrentCommand() { + QueueCommand command = channel.attr(CommandsQueue.CURRENT_COMMAND).get(); + if (command instanceof CommandData) { + return (CommandData)command; + } + return null; + } + public long getLastUsageTime() { return lastUsageTime; } @@ -97,21 +112,34 @@ public class RedisConnection implements RedisCommands { return redisClient; } - public R await(Future cmd) { - // TODO change connectTimeout to timeout - if (!cmd.awaitUninterruptibly(redisClient.getTimeout(), TimeUnit.MILLISECONDS)) { - Promise promise = (Promise)cmd; - RedisTimeoutException ex = new RedisTimeoutException("Command execution timeout for " + redisClient.getAddr()); - promise.setFailure(ex); - throw ex; - } - if (!cmd.isSuccess()) { - if (cmd.cause() instanceof RedisException) { - throw (RedisException) cmd.cause(); + public R await(Future future) { + final CountDownLatch l = new CountDownLatch(1); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + l.countDown(); + } + }); + + try { + // TODO change connectTimeout to timeout + if (!l.await(redisClient.getTimeout(), TimeUnit.MILLISECONDS)) { + Promise promise = (Promise)future; + RedisTimeoutException ex = new RedisTimeoutException("Command execution timeout for " + redisClient.getAddr()); + promise.setFailure(ex); + throw ex; + } + if (!future.isSuccess()) { + if (future.cause() instanceof RedisException) { + throw (RedisException) future.cause(); + } + throw new RedisException("Unexpected exception while processing command", future.cause()); } - throw new RedisException("Unexpected exception while processing command", cmd.cause()); + return future.getNow(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; } - return cmd.getNow(); } public T sync(RedisStrictCommand command, Object ... params) { @@ -137,13 +165,13 @@ public class RedisConnection implements RedisCommands { } public Future async(Codec encoder, RedisCommand command, Object ... params) { - Promise promise = redisClient.getBootstrap().group().next().newPromise(); + Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); send(new CommandData(promise, encoder, command, params)); return promise; } public Future asyncWithTimeout(Codec encoder, RedisCommand command, Object ... params) { - final Promise promise = redisClient.getBootstrap().group().next().newPromise(); + final Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); final ScheduledFuture scheduledFuture = redisClient.getBootstrap().group().next().schedule(new Runnable() { @Override public void run() { @@ -162,7 +190,7 @@ public class RedisConnection implements RedisCommands { } public CommandData create(Codec encoder, RedisCommand command, Object ... params) { - Promise promise = redisClient.getBootstrap().group().next().newPromise(); + Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); return new CommandData(promise, encoder, command, params); } @@ -194,7 +222,7 @@ public class RedisConnection implements RedisCommands { @Override public String toString() { - return getClass().getSimpleName() + " [redisClient=" + redisClient + ", channel=" + channel + "]"; + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + " [redisClient=" + redisClient + ", channel=" + channel + "]"; } public Future getAcquireFuture() { diff --git a/src/main/java/org/redisson/client/RedisRedirectException.java b/src/main/java/org/redisson/client/RedisRedirectException.java index e2c30b761..9c15b87e5 100644 --- a/src/main/java/org/redisson/client/RedisRedirectException.java +++ b/src/main/java/org/redisson/client/RedisRedirectException.java @@ -18,7 +18,7 @@ package org.redisson.client; import java.net.InetSocketAddress; import java.net.URI; -class RedisRedirectException extends RedisException { +public class RedisRedirectException extends RedisException { private static final long serialVersionUID = 181505625075250011L; diff --git a/src/main/java/org/redisson/client/codec/DelegateDecoderCodec.java b/src/main/java/org/redisson/client/codec/DelegateDecoderCodec.java new file mode 100644 index 000000000..407cc5b93 --- /dev/null +++ b/src/main/java/org/redisson/client/codec/DelegateDecoderCodec.java @@ -0,0 +1,34 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.codec; + +import org.redisson.client.protocol.Decoder; + +public class DelegateDecoderCodec extends StringCodec { + + private final Codec delegate; + + public DelegateDecoderCodec(Codec delegate) { + super(); + this.delegate = delegate; + } + + @Override + public Decoder getValueDecoder() { + return delegate.getValueDecoder(); + } + +} diff --git a/src/main/java/org/redisson/client/codec/GeoEntryCodec.java b/src/main/java/org/redisson/client/codec/GeoEntryCodec.java new file mode 100644 index 000000000..71a985948 --- /dev/null +++ b/src/main/java/org/redisson/client/codec/GeoEntryCodec.java @@ -0,0 +1,45 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.codec; + +import org.redisson.client.protocol.Encoder; + +public class GeoEntryCodec extends StringCodec { + + private final ThreadLocal pos = new ThreadLocal() { + protected Integer initialValue() { + return 0; + }; + }; + + private final Codec delegate; + + public GeoEntryCodec(Codec delegate) { + super(); + this.delegate = delegate; + } + + @Override + public Encoder getValueEncoder() { + Integer p = pos.get() + 1; + pos.set(p); + if (p % 3 == 0) { + return delegate.getValueEncoder(); + } + return super.getValueEncoder(); + } + +} diff --git a/src/main/java/org/redisson/client/codec/ScoredCodec.java b/src/main/java/org/redisson/client/codec/ScoredCodec.java index 12378bc2f..f4bf3656e 100644 --- a/src/main/java/org/redisson/client/codec/ScoredCodec.java +++ b/src/main/java/org/redisson/client/codec/ScoredCodec.java @@ -19,7 +19,7 @@ import org.redisson.client.protocol.Encoder; public class ScoredCodec extends StringCodec { - public final Codec delegate; + private final Codec delegate; public ScoredCodec(Codec delegate) { super(); diff --git a/src/main/java/org/redisson/client/handler/CommandsListEncoder.java b/src/main/java/org/redisson/client/handler/CommandBatchEncoder.java similarity index 77% rename from src/main/java/org/redisson/client/handler/CommandsListEncoder.java rename to src/main/java/org/redisson/client/handler/CommandBatchEncoder.java index a4c4ffef9..9412b5ab4 100644 --- a/src/main/java/org/redisson/client/handler/CommandsListEncoder.java +++ b/src/main/java/org/redisson/client/handler/CommandBatchEncoder.java @@ -20,6 +20,7 @@ import org.redisson.client.protocol.CommandsData; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelHandler.Sharable; import io.netty.handler.codec.MessageToByteEncoder; /** @@ -27,12 +28,16 @@ import io.netty.handler.codec.MessageToByteEncoder; * @author Nikita Koksharov * */ -public class CommandsListEncoder extends MessageToByteEncoder { +@Sharable +public class CommandBatchEncoder extends MessageToByteEncoder { + public static final CommandBatchEncoder INSTANCE = new CommandBatchEncoder(); + @Override protected void encode(ChannelHandlerContext ctx, CommandsData msg, ByteBuf out) throws Exception { + CommandEncoder encoder = ctx.pipeline().get(CommandEncoder.class); for (CommandData commandData : msg.getCommands()) { - ctx.pipeline().get(CommandEncoder.class).encode(ctx, (CommandData)commandData, out); + encoder.encode(ctx, commandData, out); } } diff --git a/src/main/java/org/redisson/client/handler/CommandDecoder.java b/src/main/java/org/redisson/client/handler/CommandDecoder.java index d8ce3d733..9e07d9818 100644 --- a/src/main/java/org/redisson/client/handler/CommandDecoder.java +++ b/src/main/java/org/redisson/client/handler/CommandDecoder.java @@ -35,7 +35,9 @@ import org.redisson.client.protocol.CommandsData; import org.redisson.client.protocol.Decoder; import org.redisson.client.protocol.QueueCommand; import org.redisson.client.protocol.RedisCommand.ValueType; +import org.redisson.client.protocol.decoder.ListMultiDecoder; import org.redisson.client.protocol.decoder.MultiDecoder; +import org.redisson.client.protocol.decoder.NestedMultiDecoder; import org.redisson.client.protocol.pubsub.Message; import org.redisson.client.protocol.pubsub.PubSubMessage; import org.redisson.client.protocol.pubsub.PubSubPatternMessage; @@ -68,87 +70,123 @@ 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 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { QueueCommand data = ctx.channel().attr(CommandsQueue.CURRENT_COMMAND).get(); - Decoder currentDecoder = null; - if (data == null) { - 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)); + boolean makeCheckpoint = data != null; + if (data != null) { + if (data instanceof CommandsData) { + makeCheckpoint = false; + } else { + CommandData cmd = (CommandData)data; + if (cmd.getCommand().getReplayMultiDecoder() != null + && (NestedMultiDecoder.class.isAssignableFrom(cmd.getCommand().getReplayMultiDecoder().getClass()) + || ListMultiDecoder.class.isAssignableFrom(cmd.getCommand().getReplayMultiDecoder().getClass()))) { + makeCheckpoint = false; + } + } } + state(new State(makeCheckpoint)); } + state().setDecoderState(null); if (data == null) { - decode(in, null, null, ctx.channel(), currentDecoder); + decode(in, null, null, ctx.channel()); } else if (data instanceof CommandData) { CommandData cmd = (CommandData)data; try { -// if (state().getSize() > 0) { -// List respParts = new ArrayList(); -// 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); -// } + if (state().getLevels().size() > 0) { + decodeFromCheckpoint(ctx, in, data, cmd); + } else { + decode(in, cmd, null, ctx.channel()); + } } catch (IOException e) { - cmd.getPromise().tryFailure(e); + cmd.tryFailure(e); } } else if (data instanceof CommandsData) { CommandsData commands = (CommandsData)data; - handleCommandsDataResponse(ctx, in, data, currentDecoder, commands); + decodeCommandBatch(ctx, in, data, commands); return; } - + ctx.pipeline().get(CommandsQueue.class).sendNextCommand(ctx.channel()); state(null); } - private void handleCommandsDataResponse(ChannelHandlerContext ctx, ByteBuf in, QueueCommand data, - Decoder currentDecoder, CommandsData commands) { - int i = state().getIndex(); + private void decodeFromCheckpoint(ChannelHandlerContext ctx, ByteBuf in, QueueCommand data, + CommandData cmd) throws IOException { + if (state().getLevels().size() == 2) { + StateLevel secondLevel = state().getLevels().get(1); + + if (secondLevel.getParts().isEmpty()) { + state().getLevels().remove(1); + } + } + + if (state().getLevels().size() == 2) { + StateLevel firstLevel = state().getLevels().get(0); + StateLevel secondLevel = state().getLevels().get(1); + + decodeList(in, cmd, firstLevel.getParts(), ctx.channel(), secondLevel.getSize(), secondLevel.getParts()); + + Channel channel = ctx.channel(); + MultiDecoder decoder = messageDecoder(cmd, firstLevel.getParts(), channel); + if (decoder != null) { + Object result = decoder.decode(firstLevel.getParts(), state()); + if (data != null) { + handleResult(cmd, null, result, true, channel); + } + } + } + if (state().getLevels().size() == 1) { + StateLevel firstLevel = state().getLevels().get(0); + if (firstLevel.getParts().isEmpty()) { + state().resetLevel(); + decode(in, cmd, null, ctx.channel()); + } else { + decodeList(in, cmd, null, ctx.channel(), firstLevel.getSize(), firstLevel.getParts()); + } + } + } + + private void decodeCommandBatch(ChannelHandlerContext ctx, ByteBuf in, QueueCommand data, + CommandsData commandBatch) { + int i = state().getBatchIndex(); RedisException error = null; while (in.writerIndex() > in.readerIndex()) { CommandData cmd = null; try { checkpoint(); - state().setIndex(i); - cmd = (CommandData) commands.getCommands().get(i); - decode(in, cmd, null, ctx.channel(), currentDecoder); + state().setBatchIndex(i); + cmd = (CommandData) commandBatch.getCommands().get(i); + decode(in, cmd, null, ctx.channel()); i++; } catch (IOException e) { - cmd.getPromise().tryFailure(e); + cmd.tryFailure(e); } - if (!cmd.getPromise().isSuccess()) { - if (!(cmd.getPromise().cause() instanceof RedisMovedException - || cmd.getPromise().cause() instanceof RedisAskException - || cmd.getPromise().cause() instanceof RedisLoadingException)) { - error = (RedisException) cmd.getPromise().cause(); - } + if (!cmd.isSuccess()) { + error = (RedisException) cmd.cause(); } } - if (i == commands.getCommands().size()) { - Promise promise = commands.getPromise(); + if (i == commandBatch.getCommands().size()) { + Promise promise = commandBatch.getPromise(); if (error != null) { if (!promise.tryFailure(error) && promise.cause() instanceof RedisTimeoutException) { log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data); @@ -158,22 +196,22 @@ public class CommandDecoder extends ReplayingDecoder { log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data); } } - + ctx.pipeline().get(CommandsQueue.class).sendNextCommand(ctx.channel()); state(null); } else { checkpoint(); - state().setIndex(i); + state().setBatchIndex(i); } } - private void decode(ByteBuf in, CommandData data, List parts, Channel channel, Decoder currentDecoder) throws IOException { + private void decode(ByteBuf in, CommandData data, List parts, Channel channel) throws IOException { int code = in.readByte(); 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); @@ -183,63 +221,69 @@ public class CommandDecoder extends ReplayingDecoder { String[] errorParts = error.split(" "); int slot = Integer.valueOf(errorParts[1]); String addr = errorParts[2]; - data.getPromise().tryFailure(new RedisMovedException(slot, addr)); + data.tryFailure(new RedisMovedException(slot, addr)); } else if (error.startsWith("ASK")) { String[] errorParts = error.split(" "); int slot = Integer.valueOf(errorParts[1]); String addr = errorParts[2]; - data.getPromise().tryFailure(new RedisAskException(slot, addr)); + data.tryFailure(new RedisAskException(slot, addr)); } else if (error.startsWith("LOADING")) { - data.getPromise().tryFailure(new RedisLoadingException(error + data.tryFailure(new RedisLoadingException(error + ". channel: " + channel + " data: " + data)); } else if (error.startsWith("OOM")) { - data.getPromise().tryFailure(new RedisOutOfMemoryException(error.split("OOM ")[1] + data.tryFailure(new RedisOutOfMemoryException(error.split("OOM ")[1] + ". channel: " + channel + " data: " + data)); } else if (error.contains("-OOM ")) { - data.getPromise().tryFailure(new RedisOutOfMemoryException(error.split("-OOM ")[1] + data.tryFailure(new RedisOutOfMemoryException(error.split("-OOM ")[1] + ". channel: " + channel + " data: " + data)); } else { if (data != null) { - data.getPromise().tryFailure(new RedisException(error + ". channel: " + channel + " command: " + data)); + data.tryFailure(new RedisException(error + ". channel: " + channel + " command: " + data)); } else { log.error("Error: {} channel: {} data: {}", error, channel, data); } } } 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); Object result = null; if (buf != null) { - result = decoder(data, parts, currentDecoder).decode(buf, state()); + Decoder decoder = selectDecoder(data, parts); + result = decoder.decode(buf, state()); } handleResult(data, parts, result, false, channel); } else if (code == '*') { + int level = state().incLevel(); + long size = readLong(in); - List respParts = new ArrayList(); - boolean top = false; -// if (state().trySetSize(size)) { -// state().setRespParts(respParts); -// top = true; -// } - - decodeMulti(in, data, parts, channel, currentDecoder, size, respParts, top); + List respParts; + if (state().getLevels().size()-1 >= level) { + StateLevel stateLevel = state().getLevels().get(level); + respParts = stateLevel.getParts(); + size = stateLevel.getSize(); + } else { + respParts = new ArrayList(); + if (state().isMakeCheckpoint()) { + state().addLevel(new StateLevel(size, respParts)); + } + } + + decodeList(in, data, parts, channel, size, respParts); } 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, boolean top) + private void decodeList(ByteBuf in, CommandData data, List parts, + Channel channel, long size, List respParts) throws IOException { for (int i = respParts.size(); i < size; i++) { - decode(in, data, respParts, channel, currentDecoder); -// if (top) { -// checkpoint(); -// } + decode(in, data, respParts, channel); + if (state().isMakeCheckpoint()) { + checkpoint(); + } } MultiDecoder decoder = messageDecoder(data, respParts, channel); @@ -248,7 +292,10 @@ public class CommandDecoder extends ReplayingDecoder { } Object result = decoder.decode(respParts, state()); - + if (data != null) { + handleResult(data, parts, result, true, channel); + return; + } if (result instanceof Message) { // store current message index @@ -257,40 +304,34 @@ public class CommandDecoder extends ReplayingDecoder { handleMultiResult(data, null, channel, result); // has next messages? if (in.writerIndex() > in.readerIndex()) { - decode(in, data, null, channel, currentDecoder); + decode(in, data, null, channel); } - } else { - handleMultiResult(data, parts, channel, result); } } private void handleMultiResult(CommandData data, List parts, Channel channel, Object result) { - if (data != null) { - handleResult(data, parts, result, true, channel); - } else { - if (result instanceof PubSubStatusMessage) { - String channelName = ((PubSubStatusMessage) result).getChannel(); - CommandData d = channels.get(channelName); - if (Arrays.asList("PSUBSCRIBE", "SUBSCRIBE").contains(d.getCommand().getName())) { - channels.remove(channelName); - messageDecoders.put(channelName, d.getMessageDecoder()); - } - if (Arrays.asList("PUNSUBSCRIBE", "UNSUBSCRIBE").contains(d.getCommand().getName())) { - channels.remove(channelName); - messageDecoders.remove(channelName); - } + if (result instanceof PubSubStatusMessage) { + String channelName = ((PubSubStatusMessage) result).getChannel(); + CommandData d = pubSubChannels.get(channelName); + if (Arrays.asList("PSUBSCRIBE", "SUBSCRIBE").contains(d.getCommand().getName())) { + pubSubChannels.remove(channelName); + pubSubMessageDecoders.put(channelName, d.getMessageDecoder()); } - - RedisPubSubConnection pubSubConnection = RedisPubSubConnection.getFrom(channel); - if (result instanceof PubSubStatusMessage) { - pubSubConnection.onMessage((PubSubStatusMessage) result); - } else if (result instanceof PubSubMessage) { - pubSubConnection.onMessage((PubSubMessage) result); - } else { - pubSubConnection.onMessage((PubSubPatternMessage) result); + if (Arrays.asList("PUNSUBSCRIBE", "UNSUBSCRIBE").contains(d.getCommand().getName())) { + pubSubChannels.remove(channelName); + pubSubMessageDecoders.remove(channelName); } } + + RedisPubSubConnection pubSubConnection = RedisPubSubConnection.getFrom(channel); + if (result instanceof PubSubStatusMessage) { + pubSubConnection.onMessage((PubSubStatusMessage) result); + } else if (result instanceof PubSubMessage) { + pubSubConnection.onMessage((PubSubMessage) result); + } else { + pubSubConnection.onMessage((PubSubPatternMessage) result); + } } private void handleResult(CommandData data, List parts, Object result, boolean multiResult, Channel channel) { @@ -304,7 +345,7 @@ public class CommandDecoder extends ReplayingDecoder { if (parts != null) { parts.add(result); } else { - if (!data.getPromise().trySuccess(result) && data.getPromise().cause() instanceof RedisTimeoutException) { + if (!data.getPromise().trySuccess(result) && data.cause() instanceof RedisTimeoutException) { log.warn("response has been skipped due to timeout! channel: {}, command: {}, result: {}", channel, data, result); } } @@ -314,34 +355,34 @@ 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); } } return data.getCommand().getReplayMultiDecoder(); } - private Decoder decoder(CommandData data, List parts, Decoder currentDecoder) { + private Decoder selectDecoder(CommandData data, List parts) { 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; + return StringCodec.INSTANCE.getValueDecoder(); } Decoder decoder = data.getCommand().getReplayDecoder(); diff --git a/src/main/java/org/redisson/client/handler/CommandEncoder.java b/src/main/java/org/redisson/client/handler/CommandEncoder.java index 8c7560908..54bd57e52 100644 --- a/src/main/java/org/redisson/client/handler/CommandEncoder.java +++ b/src/main/java/org/redisson/client/handler/CommandEncoder.java @@ -15,18 +15,21 @@ */ package org.redisson.client.handler; +import java.util.HashMap; import java.util.List; +import java.util.Map; -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; +import org.redisson.client.protocol.Encoder; +import org.redisson.client.protocol.RedisCommand.ValueType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.redisson.client.protocol.RedisCommand.ValueType; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelHandler.Sharable; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.util.CharsetUtil; @@ -38,8 +41,11 @@ import io.netty.util.CharsetUtil; * @author Nikita Koksharov * */ -public class CommandEncoder extends MessageToByteEncoder> { +@Sharable +public class CommandEncoder extends MessageToByteEncoder> { + public static final CommandEncoder INSTANCE = new CommandEncoder(); + private final Logger log = LoggerFactory.getLogger(getClass()); private final Encoder paramsEncoder = new DefaultParamsEncoder(); @@ -48,14 +54,16 @@ public class CommandEncoder extends MessageToByteEncoder longCache = new HashMap(); + @Override - protected void encode(ChannelHandlerContext ctx, CommandData msg, ByteBuf out) throws Exception { + protected void encode(ChannelHandlerContext ctx, CommandData msg, ByteBuf out) throws Exception { out.writeByte(ARGS_PREFIX); int len = 1 + msg.getParams().length; if (msg.getCommand().getSubName() != null) { len++; } - out.writeBytes(toChars(len)); + out.writeBytes(convert(len)); out.writeBytes(CRLF); writeArgument(out, msg.getCommand().getName().getBytes("UTF-8")); @@ -90,7 +98,7 @@ public class CommandEncoder extends MessageToByteEncoder msg, int param) { + private Encoder selectEncoder(CommandData msg, int param) { int typeIndex = 0; List inParamType = msg.getCommand().getInParamType(); if (inParamType.size() > 1) { @@ -112,15 +120,18 @@ public class CommandEncoder extends MessageToByteEncoder= 0 && i <= 255) { + return longCache.get(i); + } + return toChars(i); + } + public static byte[] toChars(long i) { int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); byte[] buf = new byte[size]; @@ -192,4 +210,12 @@ public class CommandEncoder extends MessageToByteEncoder CURRENT_COMMAND = AttributeKey.valueOf("promise"); @@ -82,7 +82,7 @@ public class CommandsQueue extends ChannelDuplexHandler { if (!pubSubOps.isEmpty()) { for (CommandData cd : pubSubOps) { for (Object channel : cd.getParams()) { - ch.pipeline().get(CommandDecoder.class).addChannel(channel.toString(), cd); + ch.pipeline().get(CommandDecoder.class).addPubSubCommand(channel.toString(), cd); } } } else { diff --git a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java index 81a030ce6..54270890d 100644 --- a/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java +++ b/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java @@ -22,6 +22,7 @@ import org.redisson.client.RedisConnection; import org.redisson.client.RedisException; import org.redisson.client.RedisPubSubConnection; import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.CommandData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +36,7 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.group.ChannelGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { @@ -115,24 +117,22 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { if (connection.getReconnectListener() != null) { // new connection used only for channel init RedisConnection rc = new RedisConnection(connection.getRedisClient(), channel); - Promise connectionFuture = bootstrap.group().next().newPromise(); + Promise connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise(); connection.getReconnectListener().onReconnect(rc, connectionFuture); connectionFuture.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { if (future.isSuccess()) { - connection.updateChannel(channel); - resubscribe(connection); + refresh(connection, channel); } } }); } else { - connection.updateChannel(channel); - resubscribe(connection); + refresh(connection, channel); } } - private void resubscribe(RedisConnection connection) { + private void reattachPubSub(RedisConnection connection) { if (connection instanceof RedisPubSubConnection) { RedisPubSubConnection conn = (RedisPubSubConnection) connection; for (Entry entry : conn.getChannels().entrySet()) { @@ -149,4 +149,29 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { ctx.channel().close(); } + private void refresh(RedisConnection connection, Channel channel) { + CommandData commandData = connection.getCurrentCommand(); + connection.updateChannel(channel); + + reattachBlockingQueue(connection, commandData); + reattachPubSub(connection); + } + + private void reattachBlockingQueue(RedisConnection connection, final CommandData commandData) { + if (commandData == null + || !commandData.isBlockingCommand()) { + return; + } + + ChannelFuture future = connection.send(commandData); + future.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't reconnect blocking queue to new connection. {}", commandData); + } + } + }); + } + } diff --git a/src/main/java/org/redisson/client/handler/State.java b/src/main/java/org/redisson/client/handler/State.java index d9d1ba579..0c8f35206 100644 --- a/src/main/java/org/redisson/client/handler/State.java +++ b/src/main/java/org/redisson/client/handler/State.java @@ -15,50 +15,80 @@ */ package org.redisson.client.handler; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import org.redisson.client.protocol.decoder.DecoderState; + public class State { - private int index; - private Object decoderState; + private int batchIndex; + private DecoderState decoderState; + + private int level = -1; + private List levels; + private DecoderState decoderStateCopy; + private final boolean makeCheckpoint; - private long size; - private List respParts; + public State(boolean makeCheckpoint) { + this.makeCheckpoint = makeCheckpoint; + } - public State() { - super(); + public boolean isMakeCheckpoint() { + return makeCheckpoint; } - public boolean trySetSize(long size) { - if (this.size != 0) { - return false; + public void resetLevel() { + level = -1; + } + public int decLevel() { + return --level; + } + public int incLevel() { + return ++level; + } + + public void addLevel(StateLevel stateLevel) { + if (levels == null) { + levels = new ArrayList(2); } - this.size = size; - return true; + levels.add(stateLevel); } - public long getSize() { - return size; + public List getLevels() { + if (levels == null) { + return Collections.emptyList(); + } + return levels; } - public void setRespParts(List respParts) { - this.respParts = respParts; + public void setBatchIndex(int index) { + this.batchIndex = index; } - public List getRespParts() { - return respParts; + public int getBatchIndex() { + return batchIndex; } - public void setIndex(int index) { - this.index = index; + public T getDecoderState() { + return (T) decoderState; } - public int getIndex() { - return index; + public void setDecoderState(DecoderState decoderState) { + this.decoderState = decoderState; } - public T getDecoderState() { - return (T)decoderState; + public DecoderState getDecoderStateCopy() { + return decoderStateCopy; } - public void setDecoderState(Object decoderState) { - this.decoderState = decoderState; + public void setDecoderStateCopy(DecoderState decoderStateCopy) { + this.decoderStateCopy = decoderStateCopy; + } + + @Override + public String toString() { + return "State [batchIndex=" + batchIndex + ", decoderState=" + decoderState + ", level=" + level + ", levels=" + + levels + ", decoderStateCopy=" + decoderStateCopy + "]"; } + + } diff --git a/src/main/java/org/redisson/client/handler/StateLevel.java b/src/main/java/org/redisson/client/handler/StateLevel.java new file mode 100644 index 000000000..64b0d9186 --- /dev/null +++ b/src/main/java/org/redisson/client/handler/StateLevel.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.handler; + +import java.util.List; + +public class StateLevel { + + private long size; + private List parts; + + public StateLevel(long size, List parts) { + super(); + this.size = size; + this.parts = parts; + } + + public long getSize() { + return size; + } + + public List getParts() { + return parts; + } + + @Override + public String toString() { + return "StateLevel [size=" + size + ", parts=" + parts + "]"; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/BatchCommandData.java b/src/main/java/org/redisson/client/protocol/BatchCommandData.java new file mode 100644 index 000000000..70ce32f84 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/BatchCommandData.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; + +import java.util.concurrent.atomic.AtomicReference; + +import org.redisson.client.RedisRedirectException; +import org.redisson.client.codec.Codec; + +import io.netty.util.concurrent.Promise; + +public class BatchCommandData extends CommandData { + + private final AtomicReference redirectError = new AtomicReference(); + + public BatchCommandData(Promise promise, Codec codec, RedisCommand command, Object[] params) { + super(promise, codec, command, params); + } + + @Override + public boolean tryFailure(Throwable cause) { + if (redirectError.get() != null) { + return false; + } + if (cause instanceof RedisRedirectException) { + return redirectError.compareAndSet(null, (RedisRedirectException) cause); + } + + return super.tryFailure(cause); + } + + @Override + public boolean isSuccess() { + return redirectError.get() == null && super.isSuccess(); + } + + @Override + public Throwable cause() { + if (redirectError.get() != null) { + return redirectError.get(); + } + return super.cause(); + } + + public void clearError() { + redirectError.set(null); + } + +} diff --git a/src/main/java/org/redisson/client/protocol/CommandData.java b/src/main/java/org/redisson/client/protocol/CommandData.java index 6cb93ad77..7cafdedab 100644 --- a/src/main/java/org/redisson/client/protocol/CommandData.java +++ b/src/main/java/org/redisson/client/protocol/CommandData.java @@ -59,6 +59,18 @@ public class CommandData implements QueueCommand { public Promise getPromise() { return promise; } + + public Throwable cause() { + return promise.cause(); + } + + public boolean isSuccess() { + return promise.isSuccess(); + } + + public boolean tryFailure(Throwable cause) { + return promise.tryFailure(cause); + } public Codec getCodec() { return codec; @@ -77,5 +89,9 @@ public class CommandData implements QueueCommand { } return Collections.emptyList(); } + + public boolean isBlockingCommand() { + return QueueCommand.TIMEOUTLESS_COMMANDS.contains(command.getName()) && !promise.isDone(); + } } diff --git a/src/main/java/org/redisson/client/protocol/QueueCommand.java b/src/main/java/org/redisson/client/protocol/QueueCommand.java index 028810a34..33582c982 100644 --- a/src/main/java/org/redisson/client/protocol/QueueCommand.java +++ b/src/main/java/org/redisson/client/protocol/QueueCommand.java @@ -23,6 +23,9 @@ import java.util.Set; public interface QueueCommand { Set PUBSUB_COMMANDS = new HashSet(Arrays.asList("PSUBSCRIBE", "SUBSCRIBE", "PUNSUBSCRIBE", "UNSUBSCRIBE")); + + Set TIMEOUTLESS_COMMANDS = new HashSet(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(), + RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName())); List> getPubSubOperations(); diff --git a/src/main/java/org/redisson/client/protocol/RedisCommand.java b/src/main/java/org/redisson/client/protocol/RedisCommand.java index 5111dbbb6..2ff072329 100644 --- a/src/main/java/org/redisson/client/protocol/RedisCommand.java +++ b/src/main/java/org/redisson/client/protocol/RedisCommand.java @@ -24,7 +24,7 @@ import org.redisson.client.protocol.decoder.MultiDecoder; public class RedisCommand { - public enum ValueType {OBJECT, OBJECTS, MAP_VALUE, MAP_KEY, MAP, BINARY} + public enum ValueType {OBJECT, OBJECTS, MAP_VALUE, MAP_KEY, MAP, BINARY, STRING} private ValueType outParamType = ValueType.OBJECT; private List inParamType = Arrays.asList(ValueType.OBJECT); diff --git a/src/main/java/org/redisson/client/protocol/RedisCommands.java b/src/main/java/org/redisson/client/protocol/RedisCommands.java index 9528685ce..edd4b8424 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()); @@ -76,9 +83,10 @@ public interface RedisCommands { RedisCommand ZADD_BOOL_RAW = new RedisCommand("ZADD", new BooleanAmountReplayConvertor()); RedisCommand ZADD_RAW = new RedisCommand("ZADD"); RedisCommand ZADD = new RedisCommand("ZADD"); - RedisCommand ZREM = new RedisCommand("ZREM", new BooleanAmountReplayConvertor(), 2); + RedisCommand ZREM = new RedisCommand("ZREM", new BooleanAmountReplayConvertor(), 2, ValueType.OBJECTS); RedisStrictCommand ZCARD_INT = new RedisStrictCommand("ZCARD", new IntegerReplayConvertor()); RedisStrictCommand ZCARD = new RedisStrictCommand("ZCARD"); + RedisStrictCommand ZCOUNT = new RedisStrictCommand("ZCOUNT"); RedisStrictCommand ZLEXCOUNT = new RedisStrictCommand("ZLEXCOUNT", new IntegerReplayConvertor()); RedisCommand ZSCORE_CONTAINS = new RedisCommand("ZSCORE", new BooleanNotNullReplayConvertor(), 2); RedisStrictCommand ZSCORE = new RedisStrictCommand("ZSCORE", new DoubleReplayConvertor(), 2); @@ -91,8 +99,10 @@ public interface RedisCommands { RedisStrictCommand ZREMRANGEBYSCORE = new RedisStrictCommand("ZREMRANGEBYSCORE", new IntegerReplayConvertor()); RedisStrictCommand ZREMRANGEBYLEX = new RedisStrictCommand("ZREMRANGEBYLEX", new IntegerReplayConvertor()); RedisCommand> ZRANGEBYLEX = new RedisCommand>("ZRANGEBYLEX", new ObjectListReplayDecoder()); - RedisCommand> ZRANGEBYSCORE = new RedisCommand>("ZRANGEBYSCORE", new ObjectListReplayDecoder()); + RedisCommand> ZRANGEBYSCORE = new RedisCommand>("ZRANGEBYSCORE", new ObjectSetReplayDecoder()); + RedisCommand> ZRANGEBYSCORE_LIST = new RedisCommand>("ZRANGEBYSCORE", new ObjectListReplayDecoder()); RedisCommand> ZREVRANGEBYSCORE = new RedisCommand>("ZREVRANGEBYSCORE", new ObjectListReplayDecoder()); + RedisCommand>> ZREVRANGEBYSCORE_ENTRY = new RedisCommand>>("ZREVRANGEBYSCORE", new ScoredSortedSetReplayDecoder()); RedisCommand>> ZRANGE_ENTRY = new RedisCommand>>("ZRANGE", new ScoredSortedSetReplayDecoder()); RedisCommand>> ZRANGEBYSCORE_ENTRY = new RedisCommand>>("ZRANGEBYSCORE", new ScoredSortedSetReplayDecoder()); RedisCommand> ZSCAN = new RedisCommand>("ZSCAN", new NestedMultiDecoder(new ScoredSortedSetScanDecoder(), new ScoredSortedSetScanReplayDecoder()), ValueType.OBJECT); @@ -107,29 +117,33 @@ public interface RedisCommands { RedisStrictCommand MULTI = new RedisStrictCommand("MULTI", new VoidReplayConvertor()); RedisCommand> EXEC = new RedisCommand>("EXEC", new ObjectListReplayDecoder()); - RedisCommand SREM = new RedisCommand("SREM", 2, ValueType.OBJECTS); RedisCommand SADD_BOOL = new RedisCommand("SADD", new BooleanAmountReplayConvertor(), 2, ValueType.OBJECTS); RedisStrictCommand SADD = new RedisStrictCommand("SADD", 2, ValueType.OBJECTS); RedisCommand SPOP_SINGLE = new RedisCommand("SPOP"); RedisCommand SADD_SINGLE = new RedisCommand("SADD", new BooleanReplayConvertor(), 2); - RedisCommand SREM_SINGLE = new RedisCommand("SREM", new BooleanReplayConvertor(), 2); + RedisCommand SREM_SINGLE = new RedisCommand("SREM", new BooleanAmountReplayConvertor(), 2, ValueType.OBJECTS); RedisCommand SMOVE = new RedisCommand("SMOVE", new BooleanReplayConvertor(), 3); - RedisCommand> SMEMBERS = new RedisCommand>("SMEMBERS", new ObjectSetReplayDecoder()); + RedisCommand> SMEMBERS = new RedisCommand>("SMEMBERS", new ObjectSetReplayDecoder()); RedisCommand> SSCAN = new RedisCommand>("SSCAN", new NestedMultiDecoder(new ObjectListReplayDecoder(), new ListScanResultReplayDecoder()), ValueType.OBJECT); RedisCommand> EVAL_SSCAN = new RedisCommand>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder(), new ListScanResultReplayDecoder()), ValueType.OBJECT); + RedisCommand> EVAL_ZSCAN = new RedisCommand>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder(), new ListScanResultReplayDecoder()), ValueType.OBJECT); RedisCommand SISMEMBER = new RedisCommand("SISMEMBER", new BooleanReplayConvertor(), 2); RedisStrictCommand SCARD_INT = new RedisStrictCommand("SCARD", new IntegerReplayConvertor()); RedisStrictCommand SCARD = new RedisStrictCommand("SCARD"); RedisStrictCommand SUNIONSTORE_INT = new RedisStrictCommand("SUNIONSTORE", new IntegerReplayConvertor()); + RedisStrictCommand SDIFFSTORE_INT = new RedisStrictCommand("SDIFFSTORE", new IntegerReplayConvertor()); + RedisStrictCommand SINTERSTORE_INT = new RedisStrictCommand("SINTERSTORE", new IntegerReplayConvertor()); RedisStrictCommand SUNIONSTORE = new RedisStrictCommand("SUNIONSTORE"); - RedisCommand> SUNION = new RedisCommand>("SUNION", new ObjectSetReplayDecoder()); + RedisCommand> SUNION = new RedisCommand>("SUNION", new ObjectSetReplayDecoder()); + RedisCommand> SDIFF = new RedisCommand>("SDIFF", new ObjectSetReplayDecoder()); + RedisCommand> SINTER = new RedisCommand>("SINTER", new ObjectSetReplayDecoder()); RedisCommand LSET = new RedisCommand("LSET", new VoidReplayConvertor(), 3); RedisCommand LPOP = new RedisCommand("LPOP"); RedisCommand LREM_SINGLE = new RedisCommand("LREM", new BooleanReplayConvertor(), 3); RedisStrictCommand LREM = new RedisStrictCommand("LREM", 3); RedisCommand LINDEX = new RedisCommand("LINDEX"); - RedisCommand LINSERT = new RedisCommand("LINSERT", 3, ValueType.OBJECTS); + RedisCommand LINSERT = new RedisCommand("LINSERT", new IntegerReplayConvertor(), 3, ValueType.OBJECTS); RedisStrictCommand LLEN_INT = new RedisStrictCommand("LLEN", new IntegerReplayConvertor()); RedisStrictCommand LLEN = new RedisStrictCommand("LLEN"); RedisStrictCommand LTRIM = new RedisStrictCommand("LTRIM", new VoidReplayConvertor()); @@ -169,9 +183,10 @@ public interface RedisCommands { RedisStrictCommand EVAL_LONG = new RedisStrictCommand("EVAL"); RedisStrictCommand EVAL_VOID = new RedisStrictCommand("EVAL", new VoidReplayConvertor()); RedisCommand> EVAL_LIST = new RedisCommand>("EVAL", new ObjectListReplayDecoder()); - RedisCommand> EVAL_SET = new RedisCommand>("EVAL", new ObjectSetReplayDecoder()); + RedisCommand> EVAL_SET = new RedisCommand>("EVAL", new ObjectSetReplayDecoder()); RedisCommand EVAL_OBJECT = new RedisCommand("EVAL"); RedisCommand EVAL_MAP_VALUE = new RedisCommand("EVAL", ValueType.MAP_VALUE); + RedisCommand>> EVAL_MAP_ENTRY = new RedisCommand>>("EVAL", new ObjectMapEntryReplayDecoder(), ValueType.MAP); RedisCommand> EVAL_MAP_VALUE_LIST = new RedisCommand>("EVAL", new ObjectListReplayDecoder(), ValueType.MAP_VALUE); RedisStrictCommand INCR = new RedisStrictCommand("INCR"); @@ -189,6 +204,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); @@ -238,7 +254,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/DecoderState.java b/src/main/java/org/redisson/client/protocol/decoder/DecoderState.java new file mode 100644 index 000000000..1c9c78afa --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/DecoderState.java @@ -0,0 +1,22 @@ +/** + * 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; + +public interface DecoderState { + + DecoderState copy(); + +} 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..72f5cfc1c --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/FlatNestedMultiDecoder.java @@ -0,0 +1,48 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.protocol.decoder; + +import java.io.IOException; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class FlatNestedMultiDecoder extends NestedMultiDecoder { + + public FlatNestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { + super(firstDecoder, secondDecoder); + } + + public FlatNestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, boolean handleEmpty) { + super(firstDecoder, secondDecoder, handleEmpty); + } + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + return firstDecoder.decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + NestedDecoderState ds = getDecoder(state); + if (paramNum == 0) { + ds.resetDecoderIndex(); + } + return firstDecoder.isApplicable(paramNum, state); + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceDecoder.java new file mode 100644 index 000000000..4464f27b5 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceDecoder.java @@ -0,0 +1,58 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.protocol.decoder; + +import java.io.IOException; +import java.util.List; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.DoubleCodec; +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; + +public class GeoDistanceDecoder implements MultiDecoder> { + + private final ThreadLocal pos = new ThreadLocal(); + + private final Codec codec; + + public GeoDistanceDecoder(Codec codec) { + super(); + this.codec = codec; + } + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + if (pos.get() % 2 == 0) { + return codec.getValueDecoder().decode(buf, state); + } + return DoubleCodec.INSTANCE.getValueDecoder().decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + pos.set(paramNum); + return true; + } + + @Override + public List decode(List parts, State state) { + return parts; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java new file mode 100644 index 000000000..9e9d526e5 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoDistanceMapDecoder.java @@ -0,0 +1,65 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.protocol.decoder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.redisson.client.codec.Codec; +import org.redisson.client.codec.DoubleCodec; +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class GeoDistanceMapDecoder implements MultiDecoder> { + + private final ThreadLocal pos = new ThreadLocal(); + + private final Codec codec; + + public GeoDistanceMapDecoder(Codec codec) { + super(); + this.codec = codec; + } + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + if (pos.get() % 2 == 0) { + return codec.getValueDecoder().decode(buf, state); + } + return DoubleCodec.INSTANCE.getValueDecoder().decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + pos.set(paramNum); + return true; + } + + @Override + public Map decode(List parts, State state) { + Map result = new HashMap(parts.size()/2); + for (int i = 0; i < parts.size(); i++) { + if (i % 2 != 0) { + result.put(parts.get(i-1), parts.get(i)); + } + } + return result; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoMapReplayDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoMapReplayDecoder.java new file mode 100644 index 000000000..94c043a47 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoMapReplayDecoder.java @@ -0,0 +1,48 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.protocol.decoder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class GeoMapReplayDecoder implements MultiDecoder> { + + @Override + public Object decode(ByteBuf buf, State state) { + throw new UnsupportedOperationException(); + } + + @Override + public Map decode(List parts, State state) { + Map result = new HashMap(parts.size()); + for (Object object : parts) { + List vals = ((List) object); + result.put(vals.get(0), vals.get(1)); + } + return result; + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return false; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoPositionDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionDecoder.java new file mode 100644 index 000000000..3b3fea8f0 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionDecoder.java @@ -0,0 +1,50 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.protocol.decoder; + +import java.io.IOException; +import java.util.List; + +import org.redisson.client.codec.DoubleCodec; +import org.redisson.client.handler.State; +import org.redisson.core.GeoPosition; + +import io.netty.buffer.ByteBuf; + +public class GeoPositionDecoder implements MultiDecoder { + + @Override + public Double decode(ByteBuf buf, State state) throws IOException { + return (Double) DoubleCodec.INSTANCE.getValueDecoder().decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return true; + } + + @Override + public GeoPosition decode(List parts, State state) { + if (parts.isEmpty()) { + return null; + } + + Double longitude = Double.valueOf(parts.get(0).toString()); + Double latitude = Double.valueOf(parts.get(1).toString()); + return new GeoPosition(longitude, latitude); + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/GeoPositionMapDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionMapDecoder.java new file mode 100644 index 000000000..07ac3662f --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/GeoPositionMapDecoder.java @@ -0,0 +1,62 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.protocol.decoder; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class GeoPositionMapDecoder implements MultiDecoder> { + + private final List args; + + public GeoPositionMapDecoder(List args) { + this.args = args; + } + + @Override + public Double decode(ByteBuf buf, State state) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return false; + } + + @Override + public Map decode(List parts, State state) { + if (parts.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap(parts.size()); + for (int index = 0; index < args.size()-1; index++) { + Object value = parts.get(index); + if (value == null || value == Collections.emptyMap()) { + continue; + } + result.put(args.get(index+1), value); + } + return result; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/KeyValueObjectDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/KeyValueObjectDecoder.java index 614e9b8df..73f238c23 100644 --- a/src/main/java/org/redisson/client/protocol/decoder/KeyValueObjectDecoder.java +++ b/src/main/java/org/redisson/client/protocol/decoder/KeyValueObjectDecoder.java @@ -27,7 +27,7 @@ public class KeyValueObjectDecoder implements MultiDecoder { @Override public Object decode(ByteBuf buf, State state) { String status = buf.toString(CharsetUtil.UTF_8); - buf.skipBytes(2); + buf.skipBytes(1); return status; } diff --git a/src/main/java/org/redisson/client/protocol/decoder/ListMultiDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/ListMultiDecoder.java new file mode 100644 index 000000000..3b9df4013 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/ListMultiDecoder.java @@ -0,0 +1,115 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.client.protocol.decoder; + +import java.io.IOException; +import java.util.List; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class ListMultiDecoder implements MultiDecoder { + + private final MultiDecoder[] decoders; + + public static class NestedDecoderState implements DecoderState { + + int index = -1; + int partsIndex = -1; + + public NestedDecoderState() { + } + + public NestedDecoderState(int index) { + super(); + this.index = index; + } + + public void resetPartsIndex() { + partsIndex = -1; + } + + public int incPartsIndex() { + return ++partsIndex; + } + + public int getPartsIndex() { + return partsIndex; + } + + public int incIndex() { + return ++index; + } + + public int getIndex() { + return index; + } + + @Override + public DecoderState copy() { + return new NestedDecoderState(index); + } + + @Override + public String toString() { + return "NestedDecoderState [index=" + index + "]"; + } + + } + + protected final NestedDecoderState getDecoder(State state) { + NestedDecoderState ds = state.getDecoderState(); + if (ds == null) { + ds = new NestedDecoderState(); + state.setDecoderState(ds); + } + return ds; + } + + public ListMultiDecoder(MultiDecoder ... decoders) { + this.decoders = decoders; + } + + public Object decode(ByteBuf buf, State state) throws IOException { + int index = getDecoder(state).getIndex(); + return decoders[index].decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + if (paramNum == 0) { + NestedDecoderState s = getDecoder(state); + s.incIndex(); + s.resetPartsIndex(); + } + return true; + } + + @Override + public Object decode(List parts, State state) { + NestedDecoderState s = getDecoder(state); + int index = s.getIndex(); + index += s.incPartsIndex(); + Object res = decoders[index].decode(parts, state); + if (res == null) { + index = s.incIndex() + s.getPartsIndex(); + return decoders[index].decode(parts, state); + } + return res; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/LongMultiDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/LongMultiDecoder.java new file mode 100644 index 000000000..30a2ee66d --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/LongMultiDecoder.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.client.protocol.decoder; + +import java.io.IOException; +import java.util.List; + +import org.redisson.client.codec.LongCodec; +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class LongMultiDecoder implements MultiDecoder { + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + return LongCodec.INSTANCE.getValueDecoder().decode(buf, state); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return false; + } + + @Override + public Object decode(List parts, State state) { + return null; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/MapCacheScanResult.java b/src/main/java/org/redisson/client/protocol/decoder/MapCacheScanResult.java new file mode 100644 index 000000000..0a6e9b2f2 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/MapCacheScanResult.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.protocol.decoder; + +import java.util.List; +import java.util.Map; + +public class MapCacheScanResult extends MapScanResult { + + private final List idleKeys; + + public MapCacheScanResult(Long pos, Map values, List idleKeys) { + super(pos, values); + this.idleKeys = idleKeys; + }; + + public List getIdleKeys() { + return idleKeys; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/MapCacheScanResultReplayDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/MapCacheScanResultReplayDecoder.java new file mode 100644 index 000000000..afed8cc14 --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/MapCacheScanResultReplayDecoder.java @@ -0,0 +1,46 @@ +/** + * 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 java.util.Map; + +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class MapCacheScanResultReplayDecoder implements MultiDecoder> { + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public MapCacheScanResult decode(List parts, State state) { + Long pos = (Long)parts.get(0); + Map values = (Map)parts.get(1); + List idleKeys = (List) parts.get(2); + return new MapCacheScanResult(pos, values, idleKeys); + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return false; + } + +} 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..2fcd87c4e 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; @@ -27,55 +24,120 @@ import io.netty.buffer.ByteBuf; public class NestedMultiDecoder implements MultiDecoder { - public static class DecoderState { + public static class NestedDecoderState implements DecoderState { - Deque> decoders; + int decoderIndex; + + int flipDecoderIndex; - Deque> flipDecoders; - - public DecoderState(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { + public NestedDecoderState() { + } + + public NestedDecoderState(int decoderIndex, int flipDecoderIndex) { super(); - this.decoders = new ArrayDeque>(Arrays.asList(firstDecoder, secondDecoder)); - this.flipDecoders = new ArrayDeque>(Arrays.asList(firstDecoder, secondDecoder, firstDecoder)); + this.decoderIndex = decoderIndex; + this.flipDecoderIndex = flipDecoderIndex; } - public Deque> getDecoders() { - return decoders; + public int getDecoderIndex() { + return decoderIndex; + } + public void resetDecoderIndex() { + decoderIndex = 0; + } + public void incDecoderIndex() { + decoderIndex++; + } + + public int getFlipDecoderIndex() { + return flipDecoderIndex; + } + public void resetFlipDecoderIndex() { + flipDecoderIndex = 0; + } + public void incFlipDecoderIndex() { + flipDecoderIndex++; } - public Deque> getFlipDecoders() { - return flipDecoders; + @Override + public DecoderState copy() { + return new NestedDecoderState(decoderIndex, flipDecoderIndex); } + @Override + public String toString() { + return "NestedDecoderState [decoderIndex=" + decoderIndex + ", flipDecoderIndex=" + flipDecoderIndex + "]"; + } + } - private final MultiDecoder firstDecoder; - private final MultiDecoder secondDecoder; + protected final MultiDecoder firstDecoder; + protected final MultiDecoder secondDecoder; + private MultiDecoder thirdDecoder; + private boolean handleEmpty; public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder) { + this(firstDecoder, secondDecoder, false); + } + + public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, boolean handleEmpty) { + this(firstDecoder, secondDecoder, null, handleEmpty); + } + + public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, MultiDecoder thirdDecoder) { + this(firstDecoder, secondDecoder, thirdDecoder, false); + } + + public NestedMultiDecoder(MultiDecoder firstDecoder, MultiDecoder secondDecoder, MultiDecoder thirdDecoder, boolean handleEmpty) { this.firstDecoder = firstDecoder; this.secondDecoder = secondDecoder; + this.thirdDecoder = thirdDecoder; + this.handleEmpty = handleEmpty; } @Override public Object decode(ByteBuf buf, State state) throws IOException { - DecoderState ds = getDecoder(state); - return ds.getFlipDecoders().peek().decode(buf, state); + NestedDecoderState ds = getDecoder(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); + NestedDecoderState 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) { - DecoderState ds = state.getDecoderState(); + protected final NestedDecoderState getDecoder(State state) { + NestedDecoderState ds = state.getDecoderState(); if (ds == null) { - ds = new DecoderState(firstDecoder, secondDecoder); + ds = new NestedDecoderState(); state.setDecoderState(ds); } return ds; @@ -83,8 +145,32 @@ public class NestedMultiDecoder implements MultiDecoder { @Override public Object decode(List parts, State state) { - DecoderState ds = getDecoder(state); - return ds.getDecoders().poll().decode(parts, state); + if (parts.isEmpty() && state.getDecoderState() == null && handleEmpty) { + MultiDecoder decoder = secondDecoder; + if (thirdDecoder != null) { + decoder = thirdDecoder; + } + return decoder.decode(parts, state); + } + + NestedDecoderState ds = getDecoder(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/client/protocol/decoder/ObjectListDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/ObjectListDecoder.java new file mode 100644 index 000000000..9b3ac26ee --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/ObjectListDecoder.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.Codec; +import org.redisson.client.handler.State; + +import io.netty.buffer.ByteBuf; + +public class ObjectListDecoder implements MultiDecoder> { + + private Codec codec; + + public ObjectListDecoder(Codec codec) { + super(); + this.codec = codec; + } + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + return codec.getMapKeyDecoder().decode(buf, state); + } + + @Override + public List decode(List parts, State state) { + return (List) parts; + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return false; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/ObjectMapDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/ObjectMapDecoder.java new file mode 100644 index 000000000..3b1c1c6bb --- /dev/null +++ b/src/main/java/org/redisson/client/protocol/decoder/ObjectMapDecoder.java @@ -0,0 +1,63 @@ +/** + * 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.handler.State; + +import io.netty.buffer.ByteBuf; + +public class ObjectMapDecoder implements MultiDecoder> { + + private Codec codec; + + public ObjectMapDecoder(Codec codec) { + super(); + this.codec = codec; + } + + private int pos; + + @Override + public Object decode(ByteBuf buf, State state) throws IOException { + if (pos++ % 2 == 0) { + return codec.getMapKeyDecoder().decode(buf, state); + } + return codec.getMapValueDecoder().decode(buf, state); + } + + @Override + public Map decode(List parts, State state) { + Map result = new HashMap(parts.size()/2); + for (int i = 0; i < parts.size(); i++) { + if (i % 2 != 0) { + result.put(parts.get(i-1), parts.get(i)); + } + } + return result; + } + + @Override + public boolean isApplicable(int paramNum, State state) { + return true; + } + +} diff --git a/src/main/java/org/redisson/client/protocol/decoder/ObjectSetReplayDecoder.java b/src/main/java/org/redisson/client/protocol/decoder/ObjectSetReplayDecoder.java index e6b17b62b..ac5df9c37 100644 --- a/src/main/java/org/redisson/client/protocol/decoder/ObjectSetReplayDecoder.java +++ b/src/main/java/org/redisson/client/protocol/decoder/ObjectSetReplayDecoder.java @@ -23,7 +23,7 @@ import org.redisson.client.handler.State; import io.netty.buffer.ByteBuf; -public class ObjectSetReplayDecoder implements MultiDecoder> { +public class ObjectSetReplayDecoder implements MultiDecoder> { @Override public Object decode(ByteBuf buf, State state) { @@ -31,8 +31,8 @@ public class ObjectSetReplayDecoder implements MultiDecoder> { } @Override - public Set decode(List parts, State state) { - return new HashSet(parts); + public Set decode(List parts, State state) { + return new HashSet(parts); } @Override diff --git a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java index d0bc0cf36..0fd5e56b8 100644 --- a/src/main/java/org/redisson/cluster/ClusterConnectionManager.java +++ b/src/main/java/org/redisson/cluster/ClusterConnectionManager.java @@ -74,18 +74,31 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { try { RedisConnection connection = connectionFuture.syncUninterruptibly().getNow(); String nodesValue = connection.sync(RedisCommands.CLUSTER_NODES); + + log.debug("cluster nodes state from {} during startup:\n{}", connection.getRedisClient().getAddr(), nodesValue); Collection partitions = parsePartitions(nodesValue); List>>> futures = new ArrayList>>>(); for (ClusterPartition partition : partitions) { + if (partition.isMasterFail()) { + continue; + } Future>> masterFuture = addMasterEntry(partition, cfg); futures.add(masterFuture); } for (Future>> masterFuture : futures) { - masterFuture.syncUninterruptibly(); + masterFuture.awaitUninterruptibly(); + if (!masterFuture.isSuccess()) { + log.error("Can't connect to master node.", masterFuture.cause()); + continue; + } for (Future future : masterFuture.getNow()) { - future.syncUninterruptibly(); + future.awaitUninterruptibly(); + if (!future.isSuccess()) { + log.error("Can't add nodes.", masterFuture.cause()); + continue; + } } } break; @@ -100,9 +113,15 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { throw new RedisConnectionException("Can't connect to servers!", lastException); } - scheduleClusterChangeCheck(cfg); + scheduleClusterChangeCheck(cfg, null); } - + + private void close(RedisConnection conn) { + if (nodeConnections.values().remove(conn)) { + conn.closeAsync(); + } + } + private Future connect(ClusterServersConfig cfg, final URI addr) { RedisConnection connection = nodeConnections.get(addr); if (connection != null) { @@ -175,13 +194,13 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { } final RedisConnection connection = future.getNow(); - Future> clusterFuture = connection.async(RedisCommands.CLUSTER_INFO); + Future> clusterFuture = connection.asyncWithTimeout(null, RedisCommands.CLUSTER_INFO); clusterFuture.addListener(new FutureListener>() { @Override public void operationComplete(Future> future) throws Exception { if (!future.isSuccess()) { - log.error("Can't execute CLUSTER_INFO with " + connection.getRedisClient().getAddr(), future.cause()); + log.error("Can't execute CLUSTER_INFO for " + connection.getRedisClient().getAddr(), future.cause()); result.setFailure(future.cause()); return; } @@ -243,26 +262,31 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { return result; } - private void scheduleClusterChangeCheck(final ClusterServersConfig cfg) { + private void scheduleClusterChangeCheck(final ClusterServersConfig cfg, final Iterator iterator) { monitorFuture = GlobalEventExecutor.INSTANCE.schedule(new Runnable() { @Override public void run() { - List nodes = new ArrayList(); - List slaves = new ArrayList(); AtomicReference lastException = new AtomicReference(); - for (ClusterPartition partition : lastPartitions.values()) { - if (!partition.isMasterFail()) { - nodes.add(partition.getMasterAddress()); + Iterator nodesIterator = iterator; + if (nodesIterator == null) { + List nodes = new ArrayList(); + List slaves = new ArrayList(); + for (ClusterPartition partition : lastPartitions.values()) { + if (!partition.isMasterFail()) { + nodes.add(partition.getMasterAddress()); + } + + Set partitionSlaves = new HashSet(partition.getSlaveAddresses()); + partitionSlaves.removeAll(partition.getFailedSlaveAddresses()); + slaves.addAll(partitionSlaves); } - - Set partitionSlaves = new HashSet(partition.getSlaveAddresses()); - partitionSlaves.removeAll(partition.getFailedSlaveAddresses()); - slaves.addAll(partitionSlaves); + // master nodes first + nodes.addAll(slaves); + + nodesIterator = nodes.iterator(); } - // master nodes first - nodes.addAll(slaves); - checkClusterState(cfg, nodes.iterator(), lastException); + checkClusterState(cfg, nodesIterator, lastException); } }, cfg.getScanInterval(), TimeUnit.MILLISECONDS); @@ -271,7 +295,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { private void checkClusterState(final ClusterServersConfig cfg, final Iterator iterator, final AtomicReference lastException) { if (!iterator.hasNext()) { log.error("Can't update cluster state", lastException.get()); - scheduleClusterChangeCheck(cfg); + scheduleClusterChangeCheck(cfg, null); return; } URI uri = iterator.next(); @@ -286,19 +310,20 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { } RedisConnection connection = future.getNow(); - updateClusterState(cfg, connection); + updateClusterState(cfg, connection, iterator); } }); } - private void updateClusterState(final ClusterServersConfig cfg, final RedisConnection connection) { - Future future = connection.async(RedisCommands.CLUSTER_NODES); + private void updateClusterState(final ClusterServersConfig cfg, final RedisConnection connection, final Iterator iterator) { + Future future = connection.asyncWithTimeout(null, RedisCommands.CLUSTER_NODES); future.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { log.error("Can't execute CLUSTER_NODES with " + connection.getRedisClient().getAddr(), future.cause()); - scheduleClusterChangeCheck(cfg); + close(connection); + scheduleClusterChangeCheck(cfg, iterator); return; } @@ -309,7 +334,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { checkMasterNodesChange(newPartitions); checkSlaveNodesChange(newPartitions); checkSlotsChange(cfg, newPartitions); - scheduleClusterChangeCheck(cfg); + scheduleClusterChangeCheck(cfg, null); } }); } @@ -346,8 +371,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 +384,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()); @@ -417,7 +444,6 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { URI oldUri = currentPart.getMasterAddress(); changeMaster(currentSlotRange, newUri.getHost(), newUri.getPort()); - slaveDown(currentSlotRange, oldUri.getHost(), oldUri.getPort(), FreezeReason.MANAGER); currentPart.setMasterAddress(newMasterPart.getMasterAddress()); } @@ -483,10 +509,12 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { List currentPartitions = new ArrayList(lastPartitions.values()); for (ClusterPartition currentPartition : currentPartitions) { for (ClusterPartition newPartition : newPartitions) { - if (!currentPartition.getNodeId().equals(newPartition.getNodeId())) { + if (!currentPartition.getNodeId().equals(newPartition.getNodeId()) + // skip master change case + || !currentPartition.getMasterAddr().equals(newPartition.getMasterAddr())) { continue; } - + Set addedSlots = new HashSet(newPartition.getSlotRanges()); addedSlots.removeAll(currentPartition.getSlotRanges()); MasterSlaveEntry entry = getEntry(currentPartition.getSlotRanges().iterator().next()); diff --git a/src/main/java/org/redisson/cluster/ClusterPartition.java b/src/main/java/org/redisson/cluster/ClusterPartition.java index f6486bd9f..ace54a42d 100644 --- a/src/main/java/org/redisson/cluster/ClusterPartition.java +++ b/src/main/java/org/redisson/cluster/ClusterPartition.java @@ -93,4 +93,11 @@ public class ClusterPartition { failedSlaves.remove(uri); } + @Override + public String toString() { + return "ClusterPartition [nodeId=" + nodeId + ", masterFail=" + masterFail + ", masterAddress=" + masterAddress + + ", slaveAddresses=" + slaveAddresses + ", failedSlaves=" + failedSlaves + ", slotRanges=" + slotRanges + + "]"; + } + } 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/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 faba6d1fc..cf1a506a9 100644 --- a/src/main/java/org/redisson/command/CommandAsyncService.java +++ b/src/main/java/org/redisson/command/CommandAsyncService.java @@ -20,13 +20,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.redisson.RedisClientResult; +import org.redisson.RedissonShutdownException; import org.redisson.SlotCallback; import org.redisson.client.RedisAskException; import org.redisson.client.RedisConnection; @@ -38,6 +41,7 @@ import org.redisson.client.WriteRedisConnectionException; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandsData; +import org.redisson.client.protocol.QueueCommand; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommands; import org.redisson.cluster.ClusterSlotRange; @@ -47,6 +51,7 @@ import org.redisson.connection.NodeSource.Redirect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.util.Timeout; @@ -55,6 +60,7 @@ import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.Promise; +import io.netty.util.concurrent.ScheduledFuture; /** * @@ -67,9 +73,6 @@ public class CommandAsyncService implements CommandAsyncExecutor { final ConnectionManager connectionManager; - private final Set skipTimeout = new HashSet(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(), - RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName())); - public CommandAsyncService(ConnectionManager connectionManager) { this.connectionManager = connectionManager; } @@ -81,13 +84,38 @@ public class CommandAsyncService implements CommandAsyncExecutor { @Override public V get(Future future) { - future.awaitUninterruptibly(); + final CountDownLatch l = new CountDownLatch(1); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + l.countDown(); + } + }); + try { + l.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + // commented out due to blocking issues up to 200 ms per minute for each thread + // future.awaitUninterruptibly(); if (future.isSuccess()) { return future.getNow(); } throw convertException(future); } + @Override + public boolean await(Future future, long timeout, TimeUnit timeoutUnit) throws InterruptedException { + final CountDownLatch l = new CountDownLatch(1); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + l.countDown(); + } + }); + return l.await(timeout, timeoutUnit); + } + @Override public Future readAsync(InetSocketAddress client, String key, Codec codec, RedisCommand command, Object ... params) { Promise mainPromise = connectionManager.newPromise(); @@ -354,7 +382,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { } if (!connectionManager.getShutdownLatch().acquire()) { - mainPromise.setFailure(new IllegalStateException("Redisson is shutdown")); + mainPromise.setFailure(new RedissonShutdownException("Redisson is shutdown")); return; } @@ -460,9 +488,9 @@ public class CommandAsyncService implements CommandAsyncExecutor { details.getTimeout().cancel(); int timeoutTime = connectionManager.getConfig().getTimeout(); - if (skipTimeout.contains(details.getCommand().getName())) { + if (QueueCommand.TIMEOUTLESS_COMMANDS.contains(details.getCommand().getName())) { Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString()); - handleBlockingOperations(details, connection); + handleBlockingOperations(details, connection, popTimeout); if (popTimeout == 0) { return; } @@ -483,26 +511,68 @@ 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 { - details.getMainPromise().cancel(true); + details.getMainPromise().tryFailure(new RedissonShutdownException("Redisson is shutdown")); } }; + + final AtomicBoolean canceledByScheduler = new AtomicBoolean(); + final ScheduledFuture scheduledFuture; + if (popTimeout != 0) { + // to handle cases when connection has been lost + final Channel orignalChannel = connection.getChannel(); + scheduledFuture = connectionManager.getGroup().schedule(new Runnable() { + @Override + public void run() { + // there is no re-connection was made + // and connection is still active + if (orignalChannel == connection.getChannel() + && connection.isActive()) { + return; + } + + canceledByScheduler.set(true); + details.getAttemptPromise().trySuccess(null); + } + }, popTimeout, TimeUnit.SECONDS); + } else { + scheduledFuture = null; + } + details.getMainPromise().addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { + if (scheduledFuture != null) { + scheduledFuture.cancel(false); + } connectionManager.getShutdownPromise().removeListener(listener); - if (!future.isCancelled()) { + // 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()); + } + } + }); + + details.getAttemptPromise().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (future.isCancelled()) { + // command should be removed due to + // ConnectionWatchdog blockingQueue reconnection logic + connection.removeCurrentCommand(); } } }); + connectionManager.getShutdownPromise().addListener(listener); } @@ -625,7 +695,7 @@ public class CommandAsyncService implements CommandAsyncExecutor { } details.getMainPromise().setSuccess(res); } else { - details.getMainPromise().setFailure(future.cause()); + details.getMainPromise().tryFailure(future.cause()); } AsyncDetails.release(details); } diff --git a/src/main/java/org/redisson/command/CommandBatchService.java b/src/main/java/org/redisson/command/CommandBatchService.java index 6bf4083cb..3939f1190 100644 --- a/src/main/java/org/redisson/command/CommandBatchService.java +++ b/src/main/java/org/redisson/command/CommandBatchService.java @@ -31,6 +31,7 @@ import org.redisson.client.RedisTimeoutException; import org.redisson.client.WriteRedisConnectionException; import org.redisson.client.codec.Codec; import org.redisson.client.codec.StringCodec; +import org.redisson.client.protocol.BatchCommandData; import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandsData; import org.redisson.client.protocol.RedisCommand; @@ -52,16 +53,16 @@ public class CommandBatchService extends CommandReactiveService { public static class CommandEntry implements Comparable { - final CommandData command; + final BatchCommandData command; final int index; - public CommandEntry(CommandData command, int index) { + public CommandEntry(BatchCommandData command, int index) { super(); this.command = command; this.index = index; } - public CommandData getCommand() { + public BatchCommandData getCommand() { return command; } @@ -89,6 +90,12 @@ public class CommandBatchService extends CommandReactiveService { public boolean isReadOnlyMode() { return readOnlyMode; } + + public void clearErrors() { + for (CommandEntry commandEntry : commands) { + commandEntry.getCommand().clearError(); + } + } } @@ -96,7 +103,7 @@ public class CommandBatchService extends CommandReactiveService { private ConcurrentMap commands = PlatformDependent.newConcurrentHashMap(); - private boolean executed; + private volatile boolean executed; public CommandBatchService(ConnectionManager connectionManager) { super(connectionManager); @@ -106,7 +113,7 @@ public class CommandBatchService extends CommandReactiveService { protected void async(boolean readOnlyMode, NodeSource nodeSource, Codec codec, RedisCommand command, Object[] params, Promise mainPromise, int attempt) { if (executed) { - throw new IllegalStateException("Batch already executed!"); + throw new IllegalStateException("Batch already has been executed!"); } Entry entry = commands.get(nodeSource.getSlot()); if (entry == null) { @@ -120,7 +127,9 @@ public class CommandBatchService extends CommandReactiveService { if (!readOnlyMode) { entry.setReadOnlyMode(false); } - entry.getCommands().add(new CommandEntry(new CommandData(mainPromise, codec, command, params), index.incrementAndGet())); + + BatchCommandData commandData = new BatchCommandData(mainPromise, codec, command, params); + entry.getCommands().add(new CommandEntry(commandData, index.incrementAndGet())); } public List execute() { @@ -278,15 +287,18 @@ public class CommandBatchService extends CommandReactiveService { if (future.cause() instanceof RedisMovedException) { RedisMovedException ex = (RedisMovedException)future.cause(); + entry.clearErrors(); execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.MOVED), mainPromise, slots, attempt); return; } if (future.cause() instanceof RedisAskException) { RedisAskException ex = (RedisAskException)future.cause(); + entry.clearErrors(); execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.ASK), mainPromise, slots, attempt); return; } if (future.cause() instanceof RedisLoadingException) { + entry.clearErrors(); execute(entry, source, mainPromise, slots, attempt); return; } diff --git a/src/main/java/org/redisson/connection/ClientConnectionsEntry.java b/src/main/java/org/redisson/connection/ClientConnectionsEntry.java index 72a1adaeb..1d9c3d6da 100644 --- a/src/main/java/org/redisson/connection/ClientConnectionsEntry.java +++ b/src/main/java/org/redisson/connection/ClientConnectionsEntry.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; public class ClientConnectionsEntry { @@ -137,7 +138,7 @@ public class ClientConnectionsEntry { } public Future connect() { - final Promise connectionFuture = client.getBootstrap().group().next().newPromise(); + final Promise connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise(); Future future = client.connectAsync(); future.addListener(new FutureListener() { @Override @@ -157,19 +158,19 @@ public class ClientConnectionsEntry { } private void addReconnectListener(Promise connectionFuture, T conn) { - connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); - addFireEventListener(connectionFuture); + addFireEventListener(conn, connectionFuture); conn.setReconnectListener(new ReconnectListener() { @Override public void onReconnect(RedisConnection conn, Promise connectionFuture) { - connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); - addFireEventListener(connectionFuture); + addFireEventListener(conn, connectionFuture); } }); } - private void addFireEventListener(Promise connectionFuture) { + private void addFireEventListener(T conn, Promise connectionFuture) { + connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); + if (connectionFuture.isSuccess()) { connectionManager.getConnectionEventsHub().fireConnect(connectionFuture.getNow().getRedisClient().getAddr()); return; @@ -186,7 +187,7 @@ public class ClientConnectionsEntry { } public Future connectPubSub() { - final Promise connectionFuture = client.getBootstrap().group().next().newPromise(); + final Promise connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise(); Future future = client.connectPubSubAsync(); future.addListener(new FutureListener() { @Override @@ -195,10 +196,12 @@ public class ClientConnectionsEntry { connectionFuture.tryFailure(future.cause()); return; } + RedisPubSubConnection conn = future.getNow(); log.debug("new pubsub connection created: {}", conn); addReconnectListener(connectionFuture, conn); + allSubscribeConnections.add(conn); } diff --git a/src/main/java/org/redisson/connection/ConnectionManager.java b/src/main/java/org/redisson/connection/ConnectionManager.java index 21a275ddb..d15d630be 100644 --- a/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/src/main/java/org/redisson/connection/ConnectionManager.java @@ -27,7 +27,6 @@ import org.redisson.client.RedisPubSubListener; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommand; import org.redisson.cluster.ClusterSlotRange; -import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.core.NodeType; import org.redisson.misc.InfinitySemaphoreLatch; @@ -62,8 +61,6 @@ public interface ConnectionManager { Future newFailedFuture(Throwable cause); - void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason); - Collection getClients(); void shutdownAsync(RedisClient client); @@ -102,6 +99,8 @@ public interface ConnectionManager { void shutdown(); + void shutdown(long quietPeriod, long timeout, TimeUnit unit); + EventLoopGroup getGroup(); Timeout newTimeout(TimerTask task, long delay, TimeUnit unit); diff --git a/src/main/java/org/redisson/connection/FastCompleteFuture.java b/src/main/java/org/redisson/connection/FastCompleteFuture.java deleted file mode 100644 index c11645c47..000000000 --- a/src/main/java/org/redisson/connection/FastCompleteFuture.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2014 Nikita Koksharov, Nickolay Borbit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.redisson.connection; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.util.concurrent.CompleteFuture; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - -/** - * Invokes Future listeners in the same thread unlike {@code SucceededFuture} does. - * - * @author Nikita Koksharov - * - * @param - */ -public abstract class FastCompleteFuture extends CompleteFuture { - - private static final Logger logger = LoggerFactory.getLogger(FastCompleteFuture.class); - - protected FastCompleteFuture() { - super(null); - } - - @Override - public Future addListener(GenericFutureListener> listener) { - if (listener == null) { - throw new NullPointerException("listener"); - } - - notify(listener); - return this; - } - - private void notify(GenericFutureListener> listener) { - try { - ((GenericFutureListener)listener).operationComplete(this); - } catch (Throwable t) { - if (logger.isWarnEnabled()) { - logger.warn("An exception was thrown by " + listener.getClass().getName() + ".operationComplete()", t); - } - } - } - - @Override - public Future addListeners(GenericFutureListener>... listeners) { - if (listeners == null) { - throw new NullPointerException("listeners"); - } - for (GenericFutureListener> l: listeners) { - if (l == null) { - break; - } - notify(l); - } - return this; - } - - -} diff --git a/src/main/java/org/redisson/connection/FastFailedFuture.java b/src/main/java/org/redisson/connection/FastFailedFuture.java deleted file mode 100644 index 00a61d234..000000000 --- a/src/main/java/org/redisson/connection/FastFailedFuture.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2014 Nikita Koksharov, Nickolay Borbit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.redisson.connection; - -import io.netty.util.concurrent.Future; -import io.netty.util.internal.PlatformDependent; - -/** - * - * @author Nikita Koksharov - * - * @param - */ -public class FastFailedFuture extends FastCompleteFuture { - - private final Throwable cause; - - protected FastFailedFuture(Throwable cause) { - if (cause == null) { - throw new NullPointerException("cause"); - } - this.cause = cause; - } - - @Override - public Throwable cause() { - return cause; - } - - @Override - public boolean isSuccess() { - return false; - } - - @Override - public Future sync() { - PlatformDependent.throwException(cause); - return this; - } - - @Override - public Future syncUninterruptibly() { - PlatformDependent.throwException(cause); - return this; - } - - @Override - public V getNow() { - return null; - } - -} diff --git a/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 065e66f75..f67e8c34b 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; @@ -98,7 +99,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { } }; - protected static final int MAX_SLOT = 16384; + public static final int MAX_SLOT = 16384; protected final ClusterSlotRange singleSlotRange = new ClusterSlotRange(0, MAX_SLOT); @@ -120,10 +121,10 @@ public class MasterSlaveConnectionManager implements ConnectionManager { protected boolean isClusterMode; - protected final Map entries = PlatformDependent.newConcurrentHashMap(); + private final Map entries = PlatformDependent.newConcurrentHashMap(); private final Promise shutdownPromise; - + private final InfinitySemaphoreLatch shutdownLatch = new InfinitySemaphoreLatch(); private final Set clients = Collections.newSetFromMap(PlatformDependent.newConcurrentHashMap()); @@ -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(); } @@ -188,7 +189,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { protected void init(MasterSlaveServersConfig config) { this.config = config; - int[] timeouts = new int[] {config.getRetryInterval(), config.getTimeout(), config.getReconnectionTimeout()}; + int[] timeouts = new int[]{config.getRetryInterval(), config.getTimeout(), config.getReconnectionTimeout()}; Arrays.sort(timeouts); int minTimeout = timeouts[0]; if (minTimeout % 100 != 0) { @@ -541,77 +542,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager { return null; } - public void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason) { - Collection allPubSubConnections = entry.slaveDown(host, port, freezeReason); - if (allPubSubConnections.isEmpty()) { - return; - } - - // reattach listeners to other channels - for (Entry mapEntry : name2PubSubConnection.entrySet()) { - for (RedisPubSubConnection redisPubSubConnection : allPubSubConnections) { - PubSubConnectionEntry pubSubEntry = mapEntry.getValue(); - final String channelName = mapEntry.getKey(); - - if (!pubSubEntry.getConnection().equals(redisPubSubConnection)) { - continue; - } - - synchronized (pubSubEntry) { - pubSubEntry.close(); - - final Collection listeners = pubSubEntry.getListeners(channelName); - if (pubSubEntry.getConnection().getPatternChannels().get(channelName) != null) { - Codec subscribeCodec = punsubscribe(channelName); - if (!listeners.isEmpty()) { - Future future = psubscribe(channelName, subscribeCodec); - future.addListener(new FutureListener() { - @Override - public void operationComplete(Future future) - throws Exception { - if (!future.isSuccess()) { - log.error("Can't resubscribe topic channel: " + channelName); - return; - } - - PubSubConnectionEntry newEntry = future.getNow(); - for (RedisPubSubListener redisPubSubListener : listeners) { - newEntry.addListener(channelName, redisPubSubListener); - } - log.debug("resubscribed listeners for '{}' channel-pattern", channelName); - } - }); - } - } else { - Codec subscribeCodec = unsubscribe(channelName); - if (!listeners.isEmpty()) { - Future future = subscribe(subscribeCodec, channelName, null); - future.addListener(new FutureListener() { - - @Override - public void operationComplete(Future future) - throws Exception { - if (!future.isSuccess()) { - log.error("Can't resubscribe topic channel: " + channelName); - return; - } - PubSubConnectionEntry newEntry = future.getNow(); - for (RedisPubSubListener redisPubSubListener : listeners) { - newEntry.addListener(channelName, redisPubSubListener); - } - log.debug("resubscribed listeners for '{}' channel", channelName); - } - }); - } - } - } - } - } - } - protected void slaveDown(ClusterSlotRange slotRange, String host, int port, FreezeReason freezeReason) { - MasterSlaveEntry entry = getEntry(slotRange); - slaveDown(entry, host, port, freezeReason); + getEntry(slotRange).slaveDown(host, port, freezeReason); } protected void changeMaster(ClusterSlotRange slotRange, String host, int port) { @@ -677,13 +609,20 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdown() { + shutdown(2, 15, TimeUnit.SECONDS);//default netty value + } + + @Override + public void shutdown(long quietPeriod, long timeout, TimeUnit unit) { + shutdownLatch.close(); shutdownPromise.trySuccess(true); - shutdownLatch.closeAndAwaitUninterruptibly(); + shutdownLatch.awaitUninterruptibly(); + for (MasterSlaveEntry entry : entries.values()) { entry.shutdown(); } timer.stop(); - group.shutdownGracefully().syncUninterruptibly(); + group.shutdownGracefully(quietPeriod, timeout, unit).syncUninterruptibly(); } @Override @@ -703,17 +642,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 @@ -735,7 +674,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { public InfinitySemaphoreLatch getShutdownLatch() { return shutdownLatch; } - + @Override public Future getShutdownPromise() { return shutdownPromise; diff --git a/src/main/java/org/redisson/connection/MasterSlaveEntry.java b/src/main/java/org/redisson/connection/MasterSlaveEntry.java index 7582e98fc..ff26955be 100644 --- a/src/main/java/org/redisson/connection/MasterSlaveEntry.java +++ b/src/main/java/org/redisson/connection/MasterSlaveEntry.java @@ -28,6 +28,9 @@ import org.redisson.ReadMode; import org.redisson.client.RedisClient; import org.redisson.client.RedisConnection; import org.redisson.client.RedisPubSubConnection; +import org.redisson.client.RedisPubSubListener; +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.CommandData; import org.redisson.cluster.ClusterSlotRange; import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.connection.balancer.LoadBalancerManager; @@ -37,7 +40,10 @@ import org.redisson.core.NodeType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; /** * @@ -90,9 +96,25 @@ public class MasterSlaveEntry { return writeConnectionHolder.add(masterEntry); } - public Collection slaveDown(String host, int port, FreezeReason freezeReason) { - Collection conns = slaveBalancer.freeze(host, port, freezeReason); + private boolean slaveDown(ClientConnectionsEntry entry, FreezeReason freezeReason) { + ClientConnectionsEntry e = slaveBalancer.freeze(entry, freezeReason); + if (e == null) { + return false; + } + + return slaveDown(e); + } + + public boolean slaveDown(String host, int port, FreezeReason freezeReason) { + ClientConnectionsEntry entry = slaveBalancer.freeze(host, port, freezeReason); + if (entry == null) { + return false; + } + + return slaveDown(entry); + } + private boolean slaveDown(ClientConnectionsEntry entry) { // add master as slave if no more slaves available if (config.getReadMode() == ReadMode.SLAVE && slaveBalancer.getAvailableClients() == 0) { InetSocketAddress addr = masterEntry.getClient().getAddr(); @@ -100,7 +122,153 @@ public class MasterSlaveEntry { log.info("master {}:{} used as slave", addr.getHostName(), addr.getPort()); } } - return conns; + + // close all connections + while (true) { + final RedisConnection connection = entry.pollConnection(); + if (connection == null) { + break; + } + + connection.closeAsync().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + reattachBlockingQueue(connection); + } + }); + } + + // close all pub/sub connections + while (true) { + RedisPubSubConnection connection = entry.pollSubscribeConnection(); + if (connection == null) { + break; + } + connection.closeAsync(); + } + + for (RedisPubSubConnection connection : entry.getAllSubscribeConnections()) { + reattachPubSub(connection); + } + entry.getAllSubscribeConnections().clear(); + + return true; + } + + private void reattachPubSub(RedisPubSubConnection redisPubSubConnection) { + for (String channelName : redisPubSubConnection.getChannels().keySet()) { + PubSubConnectionEntry pubSubEntry = connectionManager.getPubSubEntry(channelName); + + synchronized (pubSubEntry) { + pubSubEntry.close(); + + Collection listeners = pubSubEntry.getListeners(channelName); + reattachPubSubListeners(channelName, listeners); + } + } + + for (String channelName : redisPubSubConnection.getPatternChannels().keySet()) { + PubSubConnectionEntry pubSubEntry = connectionManager.getPubSubEntry(channelName); + + synchronized (pubSubEntry) { + pubSubEntry.close(); + + Collection listeners = pubSubEntry.getListeners(channelName); + reattachPatternPubSubListeners(channelName, listeners); + } + } + } + + private void reattachPubSubListeners(final String channelName, final Collection listeners) { + Codec subscribeCodec = connectionManager.unsubscribe(channelName); + if (!listeners.isEmpty()) { + Future future = connectionManager.subscribe(subscribeCodec, channelName, null); + future.addListener(new FutureListener() { + + @Override + public void operationComplete(Future future) + throws Exception { + if (!future.isSuccess()) { + log.error("Can't resubscribe topic channel: " + channelName); + return; + } + PubSubConnectionEntry newEntry = future.getNow(); + for (RedisPubSubListener redisPubSubListener : listeners) { + newEntry.addListener(channelName, redisPubSubListener); + } + log.debug("resubscribed listeners for '{}' channel", channelName); + } + }); + } + } + + private void reattachPatternPubSubListeners(final String channelName, + final Collection listeners) { + Codec subscribeCodec = connectionManager.punsubscribe(channelName); + if (!listeners.isEmpty()) { + Future future = connectionManager.psubscribe(channelName, subscribeCodec); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) + throws Exception { + if (!future.isSuccess()) { + log.error("Can't resubscribe topic channel: " + channelName); + return; + } + + PubSubConnectionEntry newEntry = future.getNow(); + for (RedisPubSubListener redisPubSubListener : listeners) { + newEntry.addListener(channelName, redisPubSubListener); + } + log.debug("resubscribed listeners for '{}' channel-pattern", channelName); + } + }); + } + } + + private void reattachBlockingQueue(RedisConnection connection) { + final CommandData commandData = connection.getCurrentCommand(); + + if (commandData == null + || !commandData.isBlockingCommand()) { + return; + } + + Future newConnection = connectionReadOp(); + newConnection.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't resubscribe blocking queue {}", commandData); + return; + } + + final RedisConnection newConnection = future.getNow(); + + final FutureListener listener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + releaseRead(newConnection); + } + }; + commandData.getPromise().addListener(listener); + if (commandData.getPromise().isDone()) { + return; + } + ChannelFuture channelFuture = newConnection.send(commandData); + channelFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + listener.operationComplete(null); + commandData.getPromise().removeListener(listener); + releaseRead(newConnection); + log.error("Can't resubscribe blocking queue {}", commandData); + } + } + }); + } + }); } public Future addSlave(String host, int port) { @@ -134,7 +302,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; @@ -150,12 +318,12 @@ public class MasterSlaveEntry { ClientConnectionsEntry oldMaster = masterEntry; setupMasterEntry(host, port); writeConnectionHolder.remove(oldMaster); - oldMaster.freezeMaster(FreezeReason.MANAGER); + slaveDown(oldMaster, FreezeReason.MANAGER); // 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..02d408f29 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,9 @@ public interface LoadBalancerManager { boolean unfreeze(String host, int port, FreezeReason freezeReason); - Collection freeze(String host, int port, FreezeReason freezeReason); + ClientConnectionsEntry freeze(ClientConnectionsEntry connectionEntry, FreezeReason freezeReason); + + ClientConnectionsEntry freeze(String host, int port, FreezeReason freezeReason); Future add(ClientConnectionsEntry entry); diff --git a/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java b/src/main/java/org/redisson/connection/balancer/LoadBalancerManagerImpl.java index 45f55ecc0..38bf4b781 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; @@ -98,47 +94,32 @@ public class LoadBalancerManagerImpl implements LoadBalancerManager { } return false; } - - public Collection freeze(String host, int port, FreezeReason freezeReason) { + + public ClientConnectionsEntry freeze(String host, int port, FreezeReason freezeReason) { InetSocketAddress addr = new InetSocketAddress(host, port); ClientConnectionsEntry connectionEntry = addr2Entry.get(addr); + return freeze(connectionEntry, freezeReason); + } + + public ClientConnectionsEntry freeze(ClientConnectionsEntry connectionEntry, FreezeReason freezeReason) { if (connectionEntry == null) { - return Collections.emptyList(); + return null; } synchronized (connectionEntry) { - log.debug("{} freezed", addr); - connectionEntry.setFreezed(true); // only RECONNECT freeze reason could be replaced if (connectionEntry.getFreezeReason() == null || connectionEntry.getFreezeReason() == FreezeReason.RECONNECT) { + connectionEntry.setFreezed(true); connectionEntry.setFreezeReason(freezeReason); + return connectionEntry; } - } - - // 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; + if (connectionEntry.isFreezed()) { + return null; } - connection.closeAsync(); } - synchronized (connectionEntry) { - List list = new ArrayList(connectionEntry.getAllSubscribeConnections()); - connectionEntry.getAllSubscribeConnections().clear(); - return list; - } + return connectionEntry; } public Future nextPubSubConnection() { diff --git a/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java b/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java index 8e5fe597b..0d14eff2d 100644 --- a/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java +++ b/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java @@ -21,11 +21,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.redisson.client.codec.LongCodec; import org.redisson.client.handler.State; import org.redisson.client.protocol.decoder.MultiDecoder; import io.netty.buffer.ByteBuf; -import io.netty.util.CharsetUtil; public class CacheGetAllDecoder implements MultiDecoder> { @@ -37,7 +37,7 @@ public class CacheGetAllDecoder implements MultiDecoder> { @Override public Object decode(ByteBuf buf, State state) throws IOException { - return Long.valueOf(buf.toString(CharsetUtil.UTF_8)); + return LongCodec.INSTANCE.getValueDecoder().decode(buf, state); } @Override diff --git a/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java b/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java index 3cf057aaa..7ce90ee4f 100644 --- a/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java +++ b/src/main/java/org/redisson/connection/decoder/MapGetAllDecoder.java @@ -16,6 +16,7 @@ package org.redisson.connection.decoder; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,10 +28,12 @@ import io.netty.buffer.ByteBuf; public class MapGetAllDecoder implements MultiDecoder> { + private final int shiftIndex; private final List args; - public MapGetAllDecoder(List args) { + public MapGetAllDecoder(List args, int shiftIndex) { this.args = args; + this.shiftIndex = shiftIndex; } @Override @@ -45,13 +48,16 @@ public class MapGetAllDecoder implements MultiDecoder> { @Override public Map decode(List parts, State state) { + if (parts.isEmpty()) { + return Collections.emptyMap(); + } Map result = new HashMap(parts.size()); - for (int index = 0; index < args.size()-1; index++) { + for (int index = 0; index < args.size()-shiftIndex; index++) { Object value = parts.get(index); if (value == null) { continue; } - result.put(args.get(index+1), value); + result.put(args.get(index+shiftIndex), value); } return result; } diff --git a/src/main/java/org/redisson/connection/pool/ConnectionPool.java b/src/main/java/org/redisson/connection/pool/ConnectionPool.java index 58998a2dd..3c63d632b 100644 --- a/src/main/java/org/redisson/connection/pool/ConnectionPool.java +++ b/src/main/java/org/redisson/connection/pool/ConnectionPool.java @@ -153,10 +153,15 @@ abstract class ConnectionPool { } } - StringBuilder errorMsg = new StringBuilder("Publish/Subscribe connection pool exhausted! All connections are busy. Try to increase Publish/Subscribe connection pool size."); -// if (!freezed.isEmpty()) { -// errorMsg.append(" Disconnected hosts: " + freezed); -// } + StringBuilder errorMsg; + if (connectionManager.isClusterMode()) { + errorMsg = new StringBuilder("Connection pool exhausted! for slots: " + masterSlaveEntry.getSlotRanges()); + } else { + errorMsg = new StringBuilder("Connection pool exhausted! "); + } + if (!freezed.isEmpty()) { + errorMsg.append(" Disconnected hosts: " + freezed); + } if (!zeroConnectionsAmount.isEmpty()) { errorMsg.append(" Hosts with fully busy connections: " + zeroConnectionsAmount); } @@ -277,7 +282,7 @@ abstract class ConnectionPool { private void checkForReconnect(ClientConnectionsEntry entry) { if (entry.getNodeType() == NodeType.SLAVE) { - connectionManager.slaveDown(masterSlaveEntry, entry.getClient().getAddr().getHostName(), + masterSlaveEntry.slaveDown(entry.getClient().getAddr().getHostName(), entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT); log.warn("slave {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts()); scheduleCheck(entry); diff --git a/src/main/java/org/redisson/core/GeoEntry.java b/src/main/java/org/redisson/core/GeoEntry.java new file mode 100644 index 000000000..7476b619f --- /dev/null +++ b/src/main/java/org/redisson/core/GeoEntry.java @@ -0,0 +1,43 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public class GeoEntry { + + private final double longitude; + private final double latitude; + private final Object member; + + public GeoEntry(double longitude, double latitude, Object member) { + super(); + this.longitude = longitude; + this.latitude = latitude; + this.member = member; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + public Object getMember() { + return member; + } + +} diff --git a/src/main/java/org/redisson/core/GeoPosition.java b/src/main/java/org/redisson/core/GeoPosition.java new file mode 100644 index 000000000..545749273 --- /dev/null +++ b/src/main/java/org/redisson/core/GeoPosition.java @@ -0,0 +1,70 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public class GeoPosition { + + private final double longitude; + private final double latitude; + + public GeoPosition(double longitude, double latitude) { + super(); + this.longitude = longitude; + this.latitude = latitude; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(latitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(longitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GeoPosition other = (GeoPosition) obj; + if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) + return false; + if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) + return false; + return true; + } + + @Override + public String toString() { + return "GeoPosition [longitude=" + longitude + ", latitude=" + latitude + "]"; + } + +} diff --git a/src/main/java/org/redisson/core/GeoUnit.java b/src/main/java/org/redisson/core/GeoUnit.java new file mode 100644 index 000000000..c4610ff93 --- /dev/null +++ b/src/main/java/org/redisson/core/GeoUnit.java @@ -0,0 +1,48 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +public enum GeoUnit { + + METERS { + @Override + public String toString() { + return "m"; + } + }, + + KILOMETERS { + @Override + public String toString() { + return "km"; + } + }, + + MILES { + @Override + public String toString() { + return "mi"; + } + }, + + FEET { + @Override + public String toString() { + return "ft"; + } + } + +} diff --git a/src/main/java/org/redisson/core/RBuckets.java b/src/main/java/org/redisson/core/RBuckets.java new file mode 100644 index 000000000..036d7f88f --- /dev/null +++ b/src/main/java/org/redisson/core/RBuckets.java @@ -0,0 +1,64 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +import java.util.List; +import java.util.Map; + +public interface RBuckets { + + /** + *

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

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

Use \ to escape special characters if you want to match them verbatim. + * + * @param pattern + * @return + */ + List> find(String pattern); + + /** + * Returns Redis object mapped by key. Result Map is not contains + * key-value entry for null values. + * + * @param keys + * @return + */ + Map get(String ... keys); + + /** + * Try to save objects mapped by Redis key. + * If at least one of them is already exist then + * don't set none of them. + * + * @param buckets + */ + boolean trySet(Map buckets); + + /** + * Saves objects mapped by Redis key. + * + * @param buckets + */ + void set(Map buckets); + +} diff --git a/src/main/java/org/redisson/core/RGeo.java b/src/main/java/org/redisson/core/RGeo.java new file mode 100644 index 000000000..e07c3c02c --- /dev/null +++ b/src/main/java/org/redisson/core/RGeo.java @@ -0,0 +1,166 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +import java.util.List; +import java.util.Map; + +/** + * Geospatial items holder + * + * @author Nikita Koksharov + * + * @param + */ +public interface RGeo extends RExpirable, RGeoAsync { + + /** + * Adds geospatial member. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + long add(double longitude, double latitude, V member); + + /** + * Adds geospatial members. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + long add(GeoEntry... entries); + + /** + * Returns distance between members in GeoUnit units. + * + * @see {@link GeoUnit} + * + * @param firstMember + * @param secondMember + * @param geoUnit + * @return + */ + Double dist(V firstMember, V secondMember, GeoUnit geoUnit); + + /** + * Returns 11 characters Geohash string mapped by defined member. + * + * @param members + * @return + */ + Map hash(V... members); + + /** + * Returns geo-position mapped by defined member. + * + * @param members + * @return + */ + Map pos(V... members); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + List radius(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the location. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithDistance(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithPosition(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + List radius(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the defined member location. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithDistance(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Map radiusWithPosition(V member, double radius, GeoUnit geoUnit); + +} diff --git a/src/main/java/org/redisson/core/RGeoAsync.java b/src/main/java/org/redisson/core/RGeoAsync.java new file mode 100644 index 000000000..15420b385 --- /dev/null +++ b/src/main/java/org/redisson/core/RGeoAsync.java @@ -0,0 +1,167 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +import java.util.List; +import java.util.Map; + +import io.netty.util.concurrent.Future; + +/** + * + * @author Nikita Koksharov + * + * @param + */ +public interface RGeoAsync extends RExpirableAsync { + + /** + * Adds geospatial member. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + Future addAsync(double longitude, double latitude, V member); + + /** + * Adds geospatial members. + * + * @param entries + * @return number of elements added to the sorted set, + * not including elements already existing for which + * the score was updated + */ + Future addAsync(GeoEntry... entries); + + /** + * Returns distance between members in GeoUnit units. + * + * @see {@link GeoUnit} + * + * @param firstMember + * @param secondMember + * @param geoUnit + * @return + */ + Future distAsync(V firstMember, V secondMember, GeoUnit geoUnit); + + /** + * Returns 11 characters Geohash string mapped by defined member. + * + * @param members + * @return + */ + Future> hashAsync(V... members); + + /** + * Returns geo-position mapped by defined member. + * + * @param members + * @return + */ + Future> posAsync(V... members); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the location. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithDistanceAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the center location + * and the maximum distance from the center (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithPositionAsync(double longitude, double latitude, double radius, GeoUnit geoUnit); + + /** + * Returns the members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusAsync(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the distance mapped by member, distance between member and the defined member location. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithDistanceAsync(V member, double radius, GeoUnit geoUnit); + + /** + * Returns the geo-position mapped by member. + * Members of a sorted set, which are within the + * borders of the area specified with the defined member location + * and the maximum distance from the defined member location (the radius) + * in GeoUnit units. + * + * @param longitude + * @param latitude + * @param radius + * @param geoUnit + * @return + */ + Future> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit); + +} diff --git a/src/main/java/org/redisson/core/RLexSortedSet.java b/src/main/java/org/redisson/core/RLexSortedSet.java index 1f68f6180..407c01650 100644 --- a/src/main/java/org/redisson/core/RLexSortedSet.java +++ b/src/main/java/org/redisson/core/RLexSortedSet.java @@ -20,32 +20,133 @@ import java.util.Set; public interface RLexSortedSet extends RLexSortedSetAsync, Set, RExpirable { + String pollFirst(); + + String pollLast(); + + String first(); + + String last(); + + /** + * Returns rank of value, with the scores ordered from high to low. + * + * @param o + * @return rank or null if value does not exist + */ + Integer revRank(String o); + + /** + * Read all values at once. + * + * @return + */ + Collection readAll(); + + 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); + Integer 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..42541bfa5 100644 --- a/src/main/java/org/redisson/core/RLexSortedSetAsync.java +++ b/src/main/java/org/redisson/core/RLexSortedSetAsync.java @@ -21,32 +21,133 @@ import io.netty.util.concurrent.Future; public interface RLexSortedSetAsync extends RCollectionAsync { + Future pollLastAsync(); + + Future pollFirstAsync(); + + Future firstAsync(); + + Future lastAsync(); + + /** + * Read all values at once. + * + * @return + */ + Future> readAllAsync(); + + 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); + + /** + * Returns rank of value, with the scores ordered from high to low. + * + * @param o + * @return rank or null if value does not exist + */ + Future revRankAsync(String o); } diff --git a/src/main/java/org/redisson/core/RList.java b/src/main/java/org/redisson/core/RList.java index efe8da169..02506780b 100644 --- a/src/main/java/org/redisson/core/RList.java +++ b/src/main/java/org/redisson/core/RList.java @@ -27,6 +27,32 @@ import java.util.RandomAccess; */ public interface RList extends List, RExpirable, RListAsync, RandomAccess { + /** + * Add element after elementToFind + * + * @param elementToFind + * @param element + * @return new list size + */ + Integer addAfter(V elementToFind, V element); + + /** + * Add element before elementToFind + * + * @param elementToFind + * @param element + * @return new list size + */ + Integer addBefore(V elementToFind, V element); + + /** + * Set element at index. + * Works faster than {@link #set(int, Object)} but + * doesn't return previous element. + * + * @param index + * @param element + */ void fastSet(int index, V element); RList subList(int fromIndex, int toIndex); diff --git a/src/main/java/org/redisson/core/RListAsync.java b/src/main/java/org/redisson/core/RListAsync.java index fec490fae..d4032587c 100644 --- a/src/main/java/org/redisson/core/RListAsync.java +++ b/src/main/java/org/redisson/core/RListAsync.java @@ -30,12 +30,38 @@ import io.netty.util.concurrent.Future; */ public interface RListAsync extends RCollectionAsync, RandomAccess { + /** + * Add element after elementToFind + * + * @param elementToFind + * @param element + * @return new list size + */ + Future addAfterAsync(V elementToFind, V element); + + /** + * Add element before elementToFind + * + * @param elementToFind + * @param element + * @return new list size + */ + Future addBeforeAsync(V elementToFind, V element); + Future addAllAsync(int index, Collection coll); Future lastIndexOfAsync(Object o); Future indexOfAsync(Object o); + /** + * Set element at index. + * Works faster than {@link #setAsync(int, Object)} but + * doesn't return previous element. + * + * @param index + * @param element + */ Future fastSetAsync(int index, V element); Future setAsync(int index, V element); diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index 1b75c5dc3..f5449b664 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -17,6 +17,44 @@ package org.redisson.core; import java.util.concurrent.TimeUnit; +/** + * Allows to execute object methods remotely between Redisson instances (Server side and Client side instances in terms of remote invocation). + *

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

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

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

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

+ *

+ * There are two timeouts during execution: + *

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

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

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

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

+ * Ack timeout = 1000 ms by default + *

+ * Execution timeout = 30 sec by default * * @param remoteInterface * @return @@ -46,13 +88,28 @@ public interface RRemoteService { /** * Get remote service object for remote invocations - * with specified invocation timeout + * with specified invocation timeout. + *

+ * Ack timeout = 1000 ms by default + * + * @param remoteInterface + * @param executionTimeout - invocation timeout + * @param executionTimeUnit + * @return + */ + T get(Class remoteInterface, long executionTimeout, TimeUnit executionTimeUnit); + + /** + * Get remote service object for remote invocations + * with specified invocation and ack timeouts * * @param remoteInterface - * @param 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/core/RScoredSortedSet.java b/src/main/java/org/redisson/core/RScoredSortedSet.java index ce4e6fccf..6898fe2be 100644 --- a/src/main/java/org/redisson/core/RScoredSortedSet.java +++ b/src/main/java/org/redisson/core/RScoredSortedSet.java @@ -36,9 +36,21 @@ public interface RScoredSortedSet extends RScoredSortedSetAsync, Iterable< int removeRangeByRank(int startIndex, int endIndex); - int rank(V o); + /** + * Returns rank of value, with the scores ordered from low to high. + * + * @param o + * @return rank or null if value does not exist + */ + Integer rank(V o); - int revRank(V o); + /** + * Returns rank of value, with the scores ordered from high to low. + * + * @param o + * @return rank or null if value does not exist + */ + Integer revRank(V o); Double getScore(V o); @@ -100,4 +112,24 @@ public interface RScoredSortedSet extends RScoredSortedSetAsync, Iterable< Collection> entryRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count); + Collection> entryRangeReversed(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count); + + /** + * Returns the number of elements with a score between startScore and endScore. + * + * @param startScore + * @param startScoreInclusive + * @param endScore + * @param endScoreInclusive + * @return + */ + Long count(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); + + /** + * Read all values at once. + * + * @return + */ + Collection readAll(); + } diff --git a/src/main/java/org/redisson/core/RScoredSortedSetAsync.java b/src/main/java/org/redisson/core/RScoredSortedSetAsync.java index 83e41798e..f2b1ffc10 100644 --- a/src/main/java/org/redisson/core/RScoredSortedSetAsync.java +++ b/src/main/java/org/redisson/core/RScoredSortedSetAsync.java @@ -94,4 +94,24 @@ public interface RScoredSortedSetAsync extends RExpirableAsync { Future>> entryRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count); + Future>> entryRangeReversedAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count); + + /** + * Returns the number of elements with a score between startScore and endScore. + * + * @param startScore + * @param startScoreInclusive + * @param endScore + * @param endScoreInclusive + * @return + */ + Future countAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); + + /** + * Read all values at once. + * + * @return + */ + Future> readAllAsync(); + } diff --git a/src/main/java/org/redisson/core/RSet.java b/src/main/java/org/redisson/core/RSet.java index d98db75f6..316a1c6ff 100644 --- a/src/main/java/org/redisson/core/RSet.java +++ b/src/main/java/org/redisson/core/RSet.java @@ -68,4 +68,40 @@ public interface RSet extends Set, RExpirable, RSetAsync { */ Set readUnion(String... names); + /** + * Diff sets specified by name and write to current set. + * If current set already exists, it is overwritten. + * + * @param names + * @return + */ + int diff(String... names); + + /** + * Diff sets specified by name with current set. + * Without current set state change. + * + * @param names + * @return + */ + + Set readDiff(String... names); + /** + * Intersection sets specified by name and write to current set. + * If current set already exists, it is overwritten. + * + * @param names + * @return + */ + int intersection(String... names); + + /** + * Intersection sets specified by name with current set. + * Without current set state change. + * + * @param names + * @return + */ + Set readIntersection(String... names); + } diff --git a/src/main/java/org/redisson/core/RSetAsync.java b/src/main/java/org/redisson/core/RSetAsync.java index f74512191..214bebc7d 100644 --- a/src/main/java/org/redisson/core/RSetAsync.java +++ b/src/main/java/org/redisson/core/RSetAsync.java @@ -71,4 +71,40 @@ public interface RSetAsync extends RCollectionAsync { */ Future> readUnionAsync(String... keys); + /** + * Diff sets specified by name and write to current set. + * If current set already exists, it is overwritten. + * + * @param names + * @return + */ + Future diffAsync(String... keys); + + /** + * Diff sets specified by name with current set. + * Without current set state change. + * + * @param names + * @return + */ + Future> readDiffAsync(String... keys); + + /** + * Intersection sets specified by name and write to current set. + * If current set already exists, it is overwritten. + * + * @param names + * @return + */ + Future intersectionAsync(String... keys); + + /** + * Intersection sets specified by name with current set. + * Without current set state change. + * + * @param names + * @return + */ + Future> readIntersectionAsync(String... keys); + } diff --git a/src/main/java/org/redisson/core/RSetCache.java b/src/main/java/org/redisson/core/RSetCache.java index a2db04c93..902cb3493 100644 --- a/src/main/java/org/redisson/core/RSetCache.java +++ b/src/main/java/org/redisson/core/RSetCache.java @@ -19,10 +19,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; /** - *

Set-based cache with ability to set TTL for each entry via - * {@link #put(Object, Object, long, TimeUnit)} method. - * And therefore has an complex lua-scripts inside. - * Uses map(value_hash, value) to tie with sorted set which contains expiration record for every value with TTL. + *

Set-based cache with ability to set TTL for each object. *

* *

Current Redis implementation doesn't have set entry eviction functionality. diff --git a/src/main/java/org/redisson/core/RedissonMultiLock.java b/src/main/java/org/redisson/core/RedissonMultiLock.java index 3d5633aac..ad1465dd8 100644 --- a/src/main/java/org/redisson/core/RedissonMultiLock.java +++ b/src/main/java/org/redisson/core/RedissonMultiLock.java @@ -20,19 +20,18 @@ 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 org.redisson.RedissonLock; + 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,98 +74,74 @@ 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); + long currentThreadId = Thread.currentThread().getId(); + lock(promise, 0, leaseTime, unit, locks, currentThreadId); - 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 { + private void lock(final Promise promise, final long waitTime, final long leaseTime, final TimeUnit unit, final List locks, final long currentThreadId) throws InterruptedException { final AtomicInteger tryLockRequestsAmount = new AtomicInteger(); final Map, RLock> tryLockFutures = new HashMap, RLock>(locks.size()); FutureListener listener = new FutureListener() { - AtomicBoolean unlock = new AtomicBoolean(); + AtomicReference lockedLockHolder = new AtomicReference(); + AtomicReference failed = new AtomicReference(); @Override - public void operationComplete(Future future) throws Exception { + public void operationComplete(final Future future) throws Exception { if (!future.isSuccess()) { - // unlock once - if (unlock.compareAndSet(false, true)) { - for (RLock lock : locks) { - lock.unlockAsync(); - } - - promise.setFailure(future.cause()); - } - return; + failed.compareAndSet(null, future.cause()); } Boolean res = future.getNow(); - // unlock once - if (!res && unlock.compareAndSet(false, true)) { - for (RLock lock : locks) { - lock.unlockAsync(); + if (res != null && !res) { + RLock lock = tryLockFutures.get(future); + lockedLockHolder.compareAndSet(null, lock); + } + + if (tryLockRequestsAmount.decrementAndGet() == 0) { + if (lockedLockHolder.get() == null && failed.get() == null) { + promise.setSuccess(null); + return; } - RLock lock = tryLockFutures.get(future); - lock.lockAsync().addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - if (!future.isSuccess()) { - promise.setFailure(future.cause()); - return; + tryLockRequestsAmount.set(tryLockFutures.size()); + for (RLock lock : tryLockFutures.values()) { + lock.unlockAsync().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (tryLockRequestsAmount.decrementAndGet() == 0) { + if (failed.get() != null) { + promise.setFailure(failed.get()); + } else if (lockedLockHolder.get() != null) { + final RedissonLock lockedLock = (RedissonLock) lockedLockHolder.get(); + lockedLock.lockAsync(leaseTime, unit, currentThreadId).addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + promise.setFailure(future.cause()); + return; + } + + List newLocks = new ArrayList(tryLockFutures.values()); + newLocks.remove(lockedLock); + lock(promise, waitTime, leaseTime, unit, newLocks, currentThreadId); + } + }); + } + } } - - lock(promise, waitTime, leaseTime, unit); - } - }); - } - if (!unlock.get() && tryLockRequestsAmount.decrementAndGet() == 0) { - promise.setSuccess(null); + }); + } } } }; @@ -177,11 +152,13 @@ public class RedissonMultiLock implements Lock { } tryLockRequestsAmount.incrementAndGet(); + Future future; if (waitTime > 0 || leaseTime > 0) { - tryLockFutures.put(lock.tryLockAsync(waitTime, leaseTime, unit), lock); + future = ((RedissonLock)lock).tryLockAsync(waitTime, leaseTime, unit, currentThreadId); } else { - tryLockFutures.put(lock.tryLockAsync(), lock); + future = ((RedissonLock)lock).tryLockAsync(currentThreadId); } + tryLockFutures.put(future, lock); } for (Future future : tryLockFutures.keySet()) { diff --git a/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java b/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java index 1242ce571..e737cd711 100644 --- a/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java +++ b/src/main/java/org/redisson/misc/InfinitySemaphoreLatch.java @@ -83,9 +83,12 @@ public class InfinitySemaphoreLatch extends AbstractQueuedSynchronizer { return closed; } + public void close() { + closed = true; + } + // waiting for an open state - public final boolean closeAndAwaitUninterruptibly() { - closed = true; + public final boolean awaitUninterruptibly() { try { return await(15, TimeUnit.SECONDS); } catch (InterruptedException e) { diff --git a/src/main/java/org/redisson/reactive/PublisherAdder.java b/src/main/java/org/redisson/reactive/PublisherAdder.java index 2d2ec7c80..a7ca9b907 100644 --- a/src/main/java/org/redisson/reactive/PublisherAdder.java +++ b/src/main/java/org/redisson/reactive/PublisherAdder.java @@ -15,6 +15,8 @@ */ package org.redisson.reactive; +import java.util.concurrent.atomic.AtomicLong; + import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; import org.redisson.api.RCollectionReactive; @@ -40,9 +42,10 @@ public class PublisherAdder { c.subscribe(new DefaultSubscriber() { + volatile boolean completed; + AtomicLong values = new AtomicLong(); Subscription s; Long lastSize = 0L; - V lastValue; @Override public void onSubscribe(Subscription s) { @@ -52,7 +55,7 @@ public class PublisherAdder { @Override public void onNext(V o) { - lastValue = o; + values.getAndIncrement(); destination.add(o).subscribe(new DefaultSubscriber() { @Override @@ -68,19 +71,18 @@ public class PublisherAdder { @Override public void onNext(Long o) { lastSize = sum(lastSize, o); - } - - @Override - public void onComplete() { - lastValue = null; s.request(1); + if (values.decrementAndGet() == 0 && completed) { + promise.onNext(lastSize); + } } }); } @Override public void onComplete() { - if (lastValue == null) { + completed = true; + if (values.get() == 0) { promise.onNext(lastSize); } } diff --git a/src/main/java/org/redisson/reactive/RedissonScoredSortedSetReactive.java b/src/main/java/org/redisson/reactive/RedissonScoredSortedSetReactive.java index 50af3b9a4..fec4044b4 100644 --- a/src/main/java/org/redisson/reactive/RedissonScoredSortedSetReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonScoredSortedSetReactive.java @@ -140,8 +140,8 @@ public class RedissonScoredSortedSetReactive extends RedissonExpirableReactiv public Publisher containsAll(Collection c) { return commandExecutor.evalReadReactive(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), "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, table.getn(s), 1 do " + + "for j = 1, table.getn(ARGV), 1 do " + "if ARGV[j] == s[i] " + "then table.remove(ARGV, j) end " + "end; " @@ -154,7 +154,7 @@ public class RedissonScoredSortedSetReactive extends RedissonExpirableReactiv public Publisher removeAll(Collection c) { return commandExecutor.evalWriteReactive(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), "local v = 0 " + - "for i = 0, table.getn(ARGV), 1 do " + "for i = 1, table.getn(ARGV), 1 do " + "if redis.call('zrem', KEYS[1], ARGV[i]) == 1 " + "then v = 1 end " +"end " @@ -167,11 +167,11 @@ public class RedissonScoredSortedSetReactive extends RedissonExpirableReactiv return commandExecutor.evalWriteReactive(getName(), codec, new RedisCommand("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), "local changed = 0 " + "local s = redis.call('zrange', KEYS[1], 0, -1) " - + "local i = 0 " + + "local i = 1 " + "while i <= table.getn(s) do " + "local element = s[i] " + "local isInAgrs = false " - + "for j = 0, table.getn(ARGV), 1 do " + + "for j = 1, table.getn(ARGV), 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " @@ -207,7 +207,7 @@ public class RedissonScoredSortedSetReactive extends RedissonExpirableReactiv public Publisher> valueRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { String startValue = value(BigDecimal.valueOf(startScore).toPlainString(), startScoreInclusive); String endValue = value(BigDecimal.valueOf(endScore).toPlainString(), endScoreInclusive); - return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE, getName(), startValue, endValue); + return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE_LIST, getName(), startValue, endValue); } @Override @@ -221,7 +221,7 @@ public class RedissonScoredSortedSetReactive extends RedissonExpirableReactiv public Publisher> valueRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { String startValue = value(BigDecimal.valueOf(startScore).toPlainString(), startScoreInclusive); String endValue = value(BigDecimal.valueOf(endScore).toPlainString(), endScoreInclusive); - return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE, getName(), startValue, endValue, "LIMIT", offset, count); + return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE_LIST, getName(), startValue, endValue, "LIMIT", offset, count); } @Override diff --git a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java index bce238a28..52c90959f 100644 --- a/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java +++ b/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java @@ -17,30 +17,21 @@ package org.redisson.reactive; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import org.reactivestreams.Publisher; import org.redisson.EvictionScheduler; +import org.redisson.RedissonSetCache; import org.redisson.api.RSetCacheReactive; 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.RedisStrictCommand; -import org.redisson.client.protocol.convertor.BooleanReplayConvertor; -import org.redisson.client.protocol.convertor.VoidReplayConvertor; import org.redisson.client.protocol.decoder.ListScanResult; -import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; import org.redisson.command.CommandReactiveExecutor; -import net.openhft.hashing.LongHashFunction; - /** *

Set-based cache with ability to set TTL for each entry via * {@link #put(Object, Object, long, TimeUnit)} method. @@ -64,80 +55,30 @@ import net.openhft.hashing.LongHashFunction; */ public class RedissonSetCacheReactive extends RedissonExpirableReactive implements RSetCacheReactive { - private static final RedisCommand ADD_ALL = new RedisCommand("HMSET", new VoidReplayConvertor()); - private static final RedisStrictCommand HDEL = new RedisStrictCommand("HDEL", new BooleanReplayConvertor()); - + private final RedissonSetCache instance; + public RedissonSetCacheReactive(EvictionScheduler evictionScheduler, CommandReactiveExecutor commandExecutor, String name) { super(commandExecutor, name); - evictionScheduler.schedule(getName(), getTimeoutSetName()); + instance = new RedissonSetCache(evictionScheduler, commandExecutor, name); } public RedissonSetCacheReactive(Codec codec, EvictionScheduler evictionScheduler, CommandReactiveExecutor commandExecutor, String name) { super(codec, commandExecutor, name); - evictionScheduler.schedule(getName(), getTimeoutSetName()); + instance = new RedissonSetCache(codec, evictionScheduler, commandExecutor, name); } @Override public Publisher size() { - return commandExecutor.readReactive(getName(), codec, RedisCommands.HLEN_LONG, getName()); - } - - private byte[] hash(Object o) { - if (o == null) { - throw new NullPointerException("Value can't be null"); - } - try { - byte[] objectState = codec.getValueEncoder().encode(o); - return hash(objectState); - } catch (IOException e) { - throw new IllegalArgumentException(e); - } - } - - private byte[] hash(byte[] objectState) { - 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(); - } - - String getTimeoutSetName() { - return "redisson__timeout__set__{" + getName() + "}"; + return commandExecutor.readReactive(getName(), codec, RedisCommands.ZCARD, getName()); } @Override public Publisher contains(Object o) { - byte[] key = hash(o); - - return commandExecutor.evalReadReactive(getName(), codec, RedisCommands.EVAL_BOOLEAN, - "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 " - + "return 0;" - + "end; " + - "end;" + - "return value; ", - Arrays.asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), key); + return reactive(instance.containsAsync(o)); } Publisher> scanIterator(InetSocketAddress client, long startPos) { - return commandExecutor.evalReadReactive(client, getName(), codec, RedisCommands.EVAL_SSCAN, - "local result = {}; " - + "local res = redis.call('hscan', KEYS[1], ARGV[1]); " - + "for i, value in ipairs(res[2]) do " - + "if i % 2 == 0 then " - + "local key = res[2][i-1]; " - + "local expireDate = redis.call('zscore', KEYS[2], key); " - + "if (expireDate == false) or (expireDate ~= false and tonumber(expireDate) > tonumber(ARGV[2])) then " - + "table.insert(result, value); " - + "end; " - + "end; " - + "end;" - + "return {res[1], result};", Arrays.asList(getName(), getTimeoutSetName()), startPos, System.currentTimeMillis()); + return reactive(instance.scanIteratorAsync(client, startPos)); } @Override @@ -152,45 +93,7 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple @Override public Publisher add(V value, long ttl, TimeUnit unit) { - if (ttl < 0) { - throw new IllegalArgumentException("TTL can't be negative"); - } - if (ttl == 0) { - try { - byte[] objectState = encode(value); - byte[] key = hash(objectState); - return commandExecutor.evalWriteReactive(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); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - if (unit == null) { - throw new NullPointerException("TimeUnit param can't be null"); - } - - try { - byte[] objectState = encode(value); - byte[] key = hash(objectState); - - long timeoutDate = System.currentTimeMillis() + unit.toMillis(ttl); - return commandExecutor.evalWriteReactive(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); - } catch (IOException e) { - throw new RuntimeException(e); - } + return reactive(instance.addAsync(value, ttl, unit)); } private byte[] encode(V value) throws IOException { @@ -201,14 +104,16 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple public Publisher add(V value) { try { byte[] objectState = encode(value); - byte[] key = hash(objectState); + + long timeoutDate = 92233720368547758L; return commandExecutor.evalWriteReactive(getName(), codec, RedisCommands.EVAL_LONG, - "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 expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " + + "if expireDateScore ~= false and tonumber(expireDateScore) > tonumber(ARGV[1]) then " + + "return 0;" + + "end; " + + "redis.call('zadd', KEYS[1], ARGV[2], ARGV[3]); " + + "return 1; ", + Arrays.asList(getName()), System.currentTimeMillis(), timeoutDate, objectState); } catch (IOException e) { throw new RuntimeException(e); } @@ -216,23 +121,12 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple @Override public Publisher remove(Object o) { - byte[] key = hash(o); - return commandExecutor.writeReactive(getName(), codec, HDEL, getName(), key); + return reactive(instance.removeAsync(o)); } @Override public Publisher containsAll(Collection c) { - return commandExecutor.evalReadReactive(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 " - + "if ARGV[j] == s[i] then " - + "table.remove(ARGV, j) " - + "end " - + "end; " - + "end;" - + "return table.getn(ARGV) == 0 and 1 or 0; ", - Collections.singletonList(getName()), c.toArray()); + return reactive(instance.containsAllAsync(c)); } @Override @@ -241,98 +135,30 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple return newSucceeded(0L); } + long score = 92233720368547758L - System.currentTimeMillis(); List params = new ArrayList(c.size()*2 + 1); params.add(getName()); try { for (V value : c) { byte[] objectState = encode(value); - byte[] key = hash(objectState); - params.add(key); + params.add(score); params.add(objectState); } } catch (IOException e) { throw new RuntimeException(e); } - return commandExecutor.writeReactive(getName(), codec, ADD_ALL, params.toArray()); + return commandExecutor.writeReactive(getName(), codec, RedisCommands.ZADD_RAW, params.toArray()); } @Override public Publisher retainAll(Collection c) { - List params = new ArrayList(c.size()); - for (Object object : c) { - params.add(hash(object)); - } - return commandExecutor.evalWriteReactive(getName(), codec, RedisCommands.EVAL_BOOLEAN, - "local keys = redis.call('hkeys', KEYS[1]); " + - "local i=1;" + - "while i <= #keys do " - + "local changed = false;" - + "local element = keys[i];" - + "for j, argElement in pairs(ARGV) do " - + "if argElement == element then " - + "changed = true;" - + "table.remove(keys, i); " - + "table.remove(ARGV, j); " - + "break; " - + "end; " - + "end; " + - "if changed == false then " + - "i = i + 1 " + - "end " + - "end " + - "if #keys > 0 then " - + "for i=1, #keys,5000 do " - + "redis.call('hdel', KEYS[1], unpack(keys, i, math.min(i+4999, #keys))); " - + "redis.call('zrem', KEYS[2], unpack(keys, i, math.min(i+4999, #keys))); " - + "end " - + "return 1;" - + "end; " - + "return 0; ", - Arrays.asList(getName(), getTimeoutSetName()), params.toArray()); + return reactive(instance.retainAllAsync(c)); } @Override public Publisher removeAll(Collection c) { - List params = new ArrayList(c.size()+1); - params.add(getName()); - for (Object object : c) { - params.add(hash(object)); - } - - return commandExecutor.writeReactive(getName(), codec, HDEL, params.toArray()); - } - - @Override - public Publisher delete() { - return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName()); - } - - @Override - public Publisher expire(long timeToLive, TimeUnit timeUnit) { - return commandExecutor.evalWriteReactive(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpire', KEYS[2], ARGV[1]); " - + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", - Arrays.asList(getName(), getTimeoutSetName()), timeUnit.toMillis(timeToLive)); - } - - @Override - public Publisher expireAt(long timestamp) { - return commandExecutor.evalWriteReactive(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpireat', KEYS[2], ARGV[1]); " - + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", - Arrays.asList(getName(), getTimeoutSetName()), timestamp); - } - - @Override - public Publisher clearExpire() { - return commandExecutor.evalWriteReactive(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + - "redis.call('persist', KEYS[2]); " - + "return redis.call('persist', KEYS[1]); ", - Arrays.asList(getName(), getTimeoutSetName())); + return reactive(instance.removeAllAsync(c)); } @Override diff --git a/src/main/java/org/redisson/reactive/SetReactiveIterator.java b/src/main/java/org/redisson/reactive/SetReactiveIterator.java index b4855f1b3..a5d78573d 100644 --- a/src/main/java/org/redisson/reactive/SetReactiveIterator.java +++ b/src/main/java/org/redisson/reactive/SetReactiveIterator.java @@ -16,7 +16,6 @@ package org.redisson.reactive; import java.net.InetSocketAddress; -import java.util.ArrayList; import java.util.List; import org.reactivestreams.Publisher; @@ -24,56 +23,36 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.redisson.client.protocol.decoder.ListScanResult; -import reactor.core.reactivestreams.SubscriberBarrier; import reactor.rx.Stream; +import reactor.rx.subscription.ReactiveSubscription; public abstract class SetReactiveIterator extends Stream { @Override public void subscribe(final Subscriber t) { - t.onSubscribe(new SubscriberBarrier(t) { + t.onSubscribe(new ReactiveSubscription(this, t) { private List firstValues; private long nextIterPos; private InetSocketAddress client; private long currentIndex; - private List prevValues = new ArrayList(); @Override - protected void doRequest(long n) { + protected void onRequest(long n) { currentIndex = n; - if (!prevValues.isEmpty()) { - List vals = new ArrayList(prevValues); - prevValues.clear(); - - handle(vals); - - if (currentIndex == 0) { - return; - } - } - nextValues(); } private void handle(List vals) { for (V val : vals) { - if (currentIndex > 0) { - onNext(val); - } else { - prevValues.add(val); - } - currentIndex--; - if (currentIndex == 0) { - onComplete(); - } + onNext(val); } } protected void nextValues() { - final SubscriberBarrier m = this; + final ReactiveSubscription m = this; scanIteratorReactive(client, nextIterPos).subscribe(new Subscriber>() { @Override diff --git a/src/main/java/org/redisson/remote/RRemoteServiceResponse.java b/src/main/java/org/redisson/remote/RRemoteServiceResponse.java new file mode 100644 index 000000000..67305dd00 --- /dev/null +++ b/src/main/java/org/redisson/remote/RRemoteServiceResponse.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.remote; + +public interface RRemoteServiceResponse { + +} diff --git a/src/main/java/org/redisson/remote/RemoteServiceAck.java b/src/main/java/org/redisson/remote/RemoteServiceAck.java new file mode 100644 index 000000000..2d34c4804 --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceAck.java @@ -0,0 +1,26 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.remote; + +/** + * Worker sends this message when it has received a {@link RemoteServiceRequest}. + * + * @author Nikita Koksharov + * + */ +public class RemoteServiceAck implements RRemoteServiceResponse { + +} diff --git a/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java b/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java new file mode 100644 index 000000000..566fc61b9 --- /dev/null +++ b/src/main/java/org/redisson/remote/RemoteServiceAckTimeoutException.java @@ -0,0 +1,36 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.remote; + +/** + * Rises when remote method executor has not answered + * within Ack timeout. + *

+ * Method invocation has not been started in this case. + * So a new invocation attempt can be made. + * + * @author Nikita Koksharov + * + */ +public class RemoteServiceAckTimeoutException extends RuntimeException { + + private static final long serialVersionUID = 1820133675653636587L; + + public RemoteServiceAckTimeoutException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/redisson/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 63% rename from src/main/java/org/redisson/RemoteServiceRequest.java rename to src/main/java/org/redisson/remote/RemoteServiceRequest.java index 434784456..3980ac4f0 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,34 @@ public class RemoteServiceRequest { private String requestId; private String methodName; private Object[] args; + private long ackTimeout; + private long responseTimeout; + private long date; + public RemoteServiceRequest() { } - public RemoteServiceRequest(String requestId, String methodName, Object[] args) { + public RemoteServiceRequest(String requestId, String methodName, Object[] args, long ackTimeout, long responseTimeout, long date) { super(); this.requestId = requestId; this.methodName = methodName; this.args = args; + this.ackTimeout = ackTimeout; + this.responseTimeout = responseTimeout; + this.date = date; + } + + public long getResponseTimeout() { + return responseTimeout; + } + + public long getDate() { + return date; + } + + public long getAckTimeout() { + return ackTimeout; } public String getRequestId() { @@ -47,8 +66,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/connection/FastSuccessFuture.java b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java similarity index 60% rename from src/main/java/org/redisson/connection/FastSuccessFuture.java rename to src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java index 08eab015b..b5d5720c0 100644 --- a/src/main/java/org/redisson/connection/FastSuccessFuture.java +++ b/src/main/java/org/redisson/remote/RemoteServiceTimeoutException.java @@ -13,35 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.redisson.connection; +package org.redisson.remote; /** - * + * Rises when invocation timeout has been occurred + * * @author Nikita Koksharov * - * @param */ -public class FastSuccessFuture extends FastCompleteFuture { - - private final V result; - - public FastSuccessFuture(V result) { - this.result = result; - } +public class RemoteServiceTimeoutException extends RuntimeException { - @Override - public Throwable cause() { - return null; - } - - @Override - public boolean isSuccess() { - return true; - } + private static final long serialVersionUID = -1749266931994840256L; - @Override - public V getNow() { - return result; + public RemoteServiceTimeoutException(String message) { + super(message); } - + } diff --git a/src/test/java/org/redisson/RedissonBlockingQueueTest.java b/src/test/java/org/redisson/RedissonBlockingQueueTest.java index 5fab2294d..265ca9953 100644 --- a/src/test/java/org/redisson/RedissonBlockingQueueTest.java +++ b/src/test/java/org/redisson/RedissonBlockingQueueTest.java @@ -1,35 +1,107 @@ package org.redisson; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; +import org.redisson.RedisRunner.RedisProcess; import org.redisson.core.RBlockingQueue; import io.netty.util.concurrent.Future; public class RedissonBlockingQueueTest extends BaseTest { + @Test + public void testPollWithBrokenConnection() throws IOException, InterruptedException, ExecutionException { + RedisProcess runner = new RedisRunner().port(6319).run(); + + Config config = new Config(); + config.useSingleServer().setAddress("127.0.0.1:6319"); + RedissonClient redisson = Redisson.create(config); + final RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollTimeout"); + Future f = queue1.pollAsync(5, TimeUnit.SECONDS); + + f.await(1, TimeUnit.SECONDS); + runner.stop(); + + assertThat(f.get()).isNull(); + } + + @Test + public void testPollReattach() throws InterruptedException, IOException, ExecutionException, TimeoutException { + RedisProcess runner = new RedisRunner().port(6319).run(); + + Config config = new Config(); + config.useSingleServer().setAddress("127.0.0.1:6319"); + RedissonClient redisson = Redisson.create(config); + + RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + Future f = queue1.pollAsync(10, TimeUnit.SECONDS); + f.await(1, TimeUnit.SECONDS); + runner.stop(); + + runner = new RedisRunner().port(6319).run(); + queue1.put(123); + + // check connection rotation + for (int i = 0; i < 10; i++) { + queue1.put(i); + } + assertThat(queue1.size()).isEqualTo(10); + + Integer result = f.get(1, TimeUnit.SECONDS); + assertThat(result).isEqualTo(123); + runner.stop(); + } + + + @Test + public void testTakeReattach() throws InterruptedException, IOException, ExecutionException, TimeoutException { + RedisProcess runner = new RedisRunner().port(6319).run(); + + Config config = new Config(); + config.useSingleServer().setAddress("127.0.0.1:6319"); + RedissonClient redisson = Redisson.create(config); + RBlockingQueue queue1 = redisson.getBlockingQueue("testTakeReattach"); + Future f = queue1.takeAsync(); + f.await(1, TimeUnit.SECONDS); + runner.stop(); + + runner = new RedisRunner().port(6319).run(); + queue1.put(123); + + // check connection rotation + for (int i = 0; i < 10; i++) { + queue1.put(i); + } + assertThat(queue1.size()).isEqualTo(10); + + Integer result = f.get(1, TimeUnit.SECONDS); + assertThat(result).isEqualTo(123); + runner.stop(); + } + @Test public void testTakeAsyncCancel() { Config config = createConfig(); config.useSingleServer().setConnectionMinimumIdleSize(1).setConnectionPoolSize(1); RedissonClient redisson = Redisson.create(config); - RBlockingQueue queue1 = redisson.getBlockingQueue("queue:pollany"); + RBlockingQueue queue1 = redisson.getBlockingQueue("testTakeAsyncCancel"); for (int i = 0; i < 10; i++) { Future f = queue1.takeAsync(); f.cancel(true); @@ -147,7 +219,7 @@ public class RedissonBlockingQueueTest extends BaseTest { queue2.put(6); queue1.pollLastAndOfferFirstTo(queue2.getName(), 10, TimeUnit.SECONDS); - MatcherAssert.assertThat(queue2, Matchers.contains(3, 4, 5, 6)); + assertThat(queue2).containsExactly(3, 4, 5, 6); } @Test @@ -158,9 +230,9 @@ public class RedissonBlockingQueueTest 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()); } @@ -172,9 +244,9 @@ public class RedissonBlockingQueueTest 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()); } @@ -189,7 +261,7 @@ public class RedissonBlockingQueueTest extends BaseTest { queue.remove(); queue.remove(); - MatcherAssert.assertThat(queue, Matchers.contains(3, 4)); + assertThat(queue).containsExactly(3, 4); queue.remove(); queue.remove(); @@ -207,7 +279,7 @@ public class RedissonBlockingQueueTest extends BaseTest { queue.remove(); queue.remove(); - MatcherAssert.assertThat(queue, Matchers.contains(3, 4)); + assertThat(queue).containsExactly(3, 4); queue.remove(); queue.remove(); @@ -281,7 +353,7 @@ public class RedissonBlockingQueueTest extends BaseTest { ArrayList dst = new ArrayList(); queue1.drainTo(dst); - MatcherAssert.assertThat(dst, Matchers.contains(1, 2L, "e")); + assertThat(dst).containsExactly(1, 2L, "e"); Assert.assertEquals(0, queue1.size()); } @@ -294,13 +366,42 @@ public class RedissonBlockingQueueTest extends BaseTest { ArrayList dst = new ArrayList(); queue1.drainTo(dst, 2); - MatcherAssert.assertThat(dst, Matchers.contains(1, 2L)); + assertThat(dst).containsExactly(1, 2L); Assert.assertEquals(1, queue1.size()); dst.clear(); queue1.drainTo(dst, 2); - MatcherAssert.assertThat(dst, Matchers.contains("e")); - - + assertThat(dst).containsExactly("e"); + } + + @Test + public void testSingleCharAsKeyName() { + String value = "Long Test Message;Long Test Message;Long Test Message;" + + "Long Test Message;Long Test Message;Long Test Message;Long " + + "Test Message;Long Test Message;Long Test Message;Long Test " + + "Message;Long Test Message;Long Test Message;Long Test Messa" + + "ge;Long Test Message;Long Test Message;Long Test Message;Lo" + + "ng Test Message;Long Test Message;Long Test Message;Long Te" + + "st Message;Long Test Message;Long Test Message;Long Test Me" + + "ssage;Long Test Message;Long Test Message;Long Test Message" + + ";Long Test Message;Long Test Message;Long Test Message;Long" + + " Test Message;Long Test Message;Long Test Message;Long Test" + + " Message;Long Test Message;Long Test Message;Long Test Mess" + + "age;"; + try { + for (int i = 0; i < 10; i++) { + System.out.println("Iteration: " + i); + RBlockingQueue q = redisson.getBlockingQueue(String.valueOf(i)); + q.add(value); + System.out.println("Message added to [" + i + "]"); + q.expire(1, TimeUnit.MINUTES); + System.out.println("Expiry set to [" + i + "]"); + String poll = q.poll(1, TimeUnit.SECONDS); + System.out.println("Message polled from [" + i + "]" + poll); + Assert.assertEquals(value, poll); + } + } catch (Exception e) { + Assert.fail(e.getLocalizedMessage()); + } } } diff --git a/src/test/java/org/redisson/RedissonBucketTest.java b/src/test/java/org/redisson/RedissonBucketTest.java index 7ed1bba45..0688f55b3 100755 --- a/src/test/java/org/redisson/RedissonBucketTest.java +++ b/src/test/java/org/redisson/RedissonBucketTest.java @@ -59,40 +59,11 @@ public class RedissonBucketTest extends BaseTest { assertThat(r1.trySet("4", 500, TimeUnit.MILLISECONDS)).isFalse(); assertThat(r1.get()).isEqualTo("3"); - Thread.sleep(500); + Thread.sleep(1000); assertThat(r1.get()).isNull(); } - @Test - public void testSaveBuckets() { - Map buckets = new HashMap(); - buckets.put("12", 1); - buckets.put("41", 2); - redisson.saveBuckets(buckets); - - RBucket r1 = redisson.getBucket("12"); - assertThat(r1.get()).isEqualTo(1); - - RBucket r2 = redisson.getBucket("41"); - assertThat(r2.get()).isEqualTo(2); - } - - @Test - public void testLoadBucketValues() { - RBucket bucket1 = redisson.getBucket("test1"); - bucket1.set("someValue1"); - RBucket bucket3 = redisson.getBucket("test3"); - bucket3.set("someValue3"); - - Map result = redisson.loadBucketValues("test1", "test2", "test3", "test4"); - Map expected = new HashMap(); - expected.put("test1", "someValue1"); - expected.put("test3", "someValue3"); - - Assert.assertEquals(expected, result); - } - @Test public void testExpire() throws InterruptedException { RBucket bucket = redisson.getBucket("test1"); @@ -175,20 +146,4 @@ public class RedissonBucketTest extends BaseTest { Assert.assertFalse(bucket.isExists()); } - @Test - public void testGetPattern() { - Collection names = Arrays.asList("test:testGetPattern:one", "test:testGetPattern:two"); - Collection vals = Arrays.asList("one-val", "two-val"); - redisson.getBucket("test:testGetPattern:one").set("one-val"); - redisson.getBucket("test:testGetPattern:two").set("two-val"); - List> buckets = redisson.getBuckets("test:testGetPattern:*"); - Assert.assertEquals(2, buckets.size()); - Assert.assertTrue(names.contains(buckets.get(0).getName())); - Assert.assertTrue(names.contains(buckets.get(1).getName())); - Assert.assertTrue(vals.contains(buckets.get(0).get())); - Assert.assertTrue(vals.contains(buckets.get(1).get())); - for (RBucket bucket : buckets) { - bucket.delete(); - } - } } diff --git a/src/test/java/org/redisson/RedissonBucketsTest.java b/src/test/java/org/redisson/RedissonBucketsTest.java new file mode 100644 index 000000000..02d8a29da --- /dev/null +++ b/src/test/java/org/redisson/RedissonBucketsTest.java @@ -0,0 +1,91 @@ +package org.redisson; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Assert; +import org.junit.Test; +import org.redisson.core.RBucket; + +public class RedissonBucketsTest extends BaseTest { + + @Test + public void testGet() { + RBucket bucket1 = redisson.getBucket("test1"); + bucket1.set("someValue1"); + RBucket bucket3 = redisson.getBucket("test3"); + bucket3.set("someValue3"); + + Map result = redisson.getBuckets().get("test1", "test2", "test3", "test4"); + Map expected = new HashMap(); + expected.put("test1", "someValue1"); + expected.put("test3", "someValue3"); + + Assert.assertEquals(expected, result); + } + + @Test + public void testFind() { + Collection names = Arrays.asList("test:testGetPattern:one", "test:testGetPattern:two"); + Collection vals = Arrays.asList("one-val", "two-val"); + + redisson.getBucket("test:testGetPattern:one").set("one-val"); + redisson.getBucket("test:testGetPattern:two").set("two-val"); + + List> buckets = redisson.getBuckets().find("test:testGetPattern:*"); + Assert.assertEquals(2, buckets.size()); + Assert.assertTrue(names.contains(buckets.get(0).getName())); + Assert.assertTrue(names.contains(buckets.get(1).getName())); + Assert.assertTrue(vals.contains(buckets.get(0).get())); + Assert.assertTrue(vals.contains(buckets.get(1).get())); + for (RBucket bucket : buckets) { + bucket.delete(); + } + } + + + @Test + public void testSet() { + Map buckets = new HashMap(); + buckets.put("12", 1); + buckets.put("41", 2); + redisson.getBuckets().set(buckets); + + RBucket r1 = redisson.getBucket("12"); + assertThat(r1.get()).isEqualTo(1); + + RBucket r2 = redisson.getBucket("41"); + assertThat(r2.get()).isEqualTo(2); + } + + @Test + public void testTrySet() { + redisson.getBucket("12").set("341"); + + Map buckets = new HashMap(); + buckets.put("12", 1); + buckets.put("41", 2); + assertThat(redisson.getBuckets().trySet(buckets)).isFalse(); + + RBucket r2 = redisson.getBucket("41"); + assertThat(r2.get()).isNull(); + + Map buckets2 = new HashMap(); + buckets2.put("61", 1); + buckets2.put("41", 2); + assertThat(redisson.getBuckets().trySet(buckets2)).isTrue(); + + RBucket r1 = redisson.getBucket("61"); + assertThat(r1.get()).isEqualTo(1); + + RBucket r3 = redisson.getBucket("41"); + assertThat(r3.get()).isEqualTo(2); + } + + +} diff --git a/src/test/java/org/redisson/RedissonConcurrentMapTest.java b/src/test/java/org/redisson/RedissonConcurrentMapTest.java index ed3903744..adaa1f142 100644 --- a/src/test/java/org/redisson/RedissonConcurrentMapTest.java +++ b/src/test/java/org/redisson/RedissonConcurrentMapTest.java @@ -94,7 +94,7 @@ public class RedissonConcurrentMapTest extends BaseConcurrentTest { } assertMapSize(5, name); - redisson.flushdb(); + redisson.getKeys().flushdb(); redisson.shutdown(); } diff --git a/src/test/java/org/redisson/RedissonDequeTest.java b/src/test/java/org/redisson/RedissonDequeTest.java index 9ccb56362..784810abf 100644 --- a/src/test/java/org/redisson/RedissonDequeTest.java +++ b/src/test/java/org/redisson/RedissonDequeTest.java @@ -1,12 +1,11 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; -import java.util.Iterator; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RDeque; @@ -23,7 +22,7 @@ public class RedissonDequeTest extends BaseTest { queue1.removeLastOccurrence(3); - MatcherAssert.assertThat(queue1, Matchers.containsInAnyOrder(3, 2, 1)); + assertThat(queue1).containsExactly(3, 2, 1); } @Test @@ -36,7 +35,7 @@ public class RedissonDequeTest extends BaseTest { queue1.removeFirstOccurrence(3); - MatcherAssert.assertThat(queue1, Matchers.containsInAnyOrder(2, 1, 3)); + assertThat(queue1).containsExactly(2, 1, 3); } @Test @@ -86,7 +85,7 @@ public class RedissonDequeTest extends BaseTest { queue2.addFirst(4); queue1.pollLastAndOfferFirstTo(queue2); - MatcherAssert.assertThat(queue2, Matchers.contains(3, 4, 5, 6)); + assertThat(queue2).containsExactly(3, 4, 5, 6); } @Test @@ -96,8 +95,8 @@ public class RedissonDequeTest extends BaseTest { queue.addFirst(2); queue.addFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); - } + assertThat(queue).containsExactly(3, 2, 1); + } @Test public void testAddFirst() { @@ -106,7 +105,7 @@ public class RedissonDequeTest extends BaseTest { queue.addFirst(2); queue.addFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); + assertThat(queue).containsExactly(3, 2, 1); } @Test @@ -116,7 +115,7 @@ public class RedissonDequeTest extends BaseTest { queue.addLast(2); queue.addLast(3); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3)); + assertThat(queue).containsExactly(1, 2, 3); } @Test @@ -126,7 +125,7 @@ public class RedissonDequeTest extends BaseTest { queue.addLast(2); queue.addLast(3); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3)); + assertThat(queue).containsExactly(1, 2, 3); } @Test @@ -136,7 +135,7 @@ public class RedissonDequeTest extends BaseTest { queue.offerFirst(2); queue.offerFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); + assertThat(queue).containsExactly(3, 2, 1); } @Test @@ -146,7 +145,7 @@ public class RedissonDequeTest extends BaseTest { queue.offerFirst(2); queue.offerFirst(3); - MatcherAssert.assertThat(queue, Matchers.contains(3, 2, 1)); + assertThat(queue).containsExactly(3, 2, 1); } @Test @@ -156,7 +155,7 @@ public class RedissonDequeTest extends BaseTest { queue.offerLast(2); queue.offerLast(3); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3)); + assertThat(queue).containsExactly(1, 2, 3); Assert.assertEquals((Integer)1, queue.poll()); } @@ -166,14 +165,7 @@ public class RedissonDequeTest extends BaseTest { final Deque queue = new ArrayDeque(); queue.addAll(Arrays.asList(1, 2, 3)); - MatcherAssert.assertThat(new Iterable() { - - @Override - public Iterator iterator() { - return queue.descendingIterator(); - } - - }, Matchers.contains(3, 2, 1)); + assertThat(queue.descendingIterator()).containsExactly(3, 2, 1); } @Test @@ -181,14 +173,7 @@ public class RedissonDequeTest extends BaseTest { final RDeque queue = redisson.getDeque("deque"); queue.addAll(Arrays.asList(1, 2, 3)); - MatcherAssert.assertThat(new Iterable() { - - @Override - public Iterator iterator() { - return queue.descendingIterator(); - } - - }, Matchers.contains(3, 2, 1)); + assertThat(queue.descendingIterator()).containsExactly(3, 2, 1); } } diff --git a/src/test/java/org/redisson/RedissonGeoTest.java b/src/test/java/org/redisson/RedissonGeoTest.java new file mode 100644 index 000000000..455ba317f --- /dev/null +++ b/src/test/java/org/redisson/RedissonGeoTest.java @@ -0,0 +1,239 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +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.1516D); + } + + @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 testRadiusWithDistanceHugeAmount() { + RGeo geo = redisson.getGeo("test"); + + for (int i = 0; i < 10000; i++) { + geo.add(10 + 0.000001*i, 11 + 0.000001*i, "" + i); + } + + Map res = geo.radiusWithDistance(10, 11, 200, GeoUnit.KILOMETERS); + assertThat(res).hasSize(10000); + } + + @Test + public void testRadiusWithPositionHugeAmount() { + RGeo geo = redisson.getGeo("test"); + + for (int i = 0; i < 10000; i++) { + geo.add(10 + 0.000001*i, 11 + 0.000001*i, "" + i); + } + + Map res = geo.radiusWithPosition(10, 11, 200, GeoUnit.KILOMETERS); + assertThat(res).hasSize(10000); + } + + + @Test + public void testRadiusWithDistanceBigObject() { + RGeo> geo = redisson.getGeo("test"); + + Map map = new HashMap(); + for (int i = 0; i < 150; i++) { + map.put("" + i, "" + i); + } + + geo.add(new GeoEntry(13.361389, 38.115556, map)); + + Map map1 = new HashMap(map); + map1.remove("100"); + geo.add(new GeoEntry(15.087269, 37.502669, map1)); + + Map map2 = new HashMap(map); + map2.remove("0"); + geo.add(new GeoEntry(15.081269, 37.502169, map2)); + + Map, Double> expected = new HashMap, Double>(); + expected.put(map, 190.4424); + expected.put(map1, 56.4413); + expected.put(map2, 56.3159); + + Map, Double> res = geo.radiusWithDistance(15, 37, 200, GeoUnit.KILOMETERS); + assertThat(res.keySet()).containsOnlyElementsOf(expected.keySet()); + assertThat(res.values()).containsOnlyElementsOf(expected.values()); + } + + + @Test + public void testRadiusWithDistanceEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithDistance(15, 37, 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusWithPosition() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new HashMap(); + expected.put("Palermo", new GeoPosition(13.361389338970184, 38.115556395496299)); + expected.put("Catania", new GeoPosition(15.087267458438873, 37.50266842333162)); + assertThat(geo.radiusWithPosition(15, 37, 200, GeoUnit.KILOMETERS)).isEqualTo(expected); + } + + @Test + public void testRadiusWithPositionEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithPosition(15, 37, 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusMember() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + assertThat(geo.radius("Palermo", 200, GeoUnit.KILOMETERS)).containsExactly("Palermo", "Catania"); + } + + @Test + public void testRadiusMemberEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radius("Palermo", 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusMemberWithDistance() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new HashMap(); + expected.put("Palermo", 0.0); + expected.put("Catania", 166.2742); + assertThat(geo.radiusWithDistance("Palermo", 200, GeoUnit.KILOMETERS)).isEqualTo(expected); + } + + @Test + public void testRadiusMemberWithDistanceEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithDistance("Palermo", 200, GeoUnit.KILOMETERS)).isEmpty(); + } + + @Test + public void testRadiusMemberWithPosition() { + RGeo geo = redisson.getGeo("test"); + geo.add(new GeoEntry(13.361389, 38.115556, "Palermo"), new GeoEntry(15.087269, 37.502669, "Catania")); + + Map expected = new HashMap(); + expected.put("Palermo", new GeoPosition(13.361389338970184, 38.115556395496299)); + expected.put("Catania", new GeoPosition(15.087267458438873, 37.50266842333162)); + assertThat(geo.radiusWithPosition("Palermo", 200, GeoUnit.KILOMETERS)).isEqualTo(expected); + } + + @Test + public void testRadiusMemberWithPositionEmpty() { + RGeo geo = redisson.getGeo("test"); + + assertThat(geo.radiusWithPosition("Palermo", 200, GeoUnit.KILOMETERS)).isEmpty(); + } + +} diff --git a/src/test/java/org/redisson/RedissonKeysTest.java b/src/test/java/org/redisson/RedissonKeysTest.java index 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/RedissonLexSortedSetTest.java b/src/test/java/org/redisson/RedissonLexSortedSetTest.java index b048b0176..d3d220cb4 100644 --- a/src/test/java/org/redisson/RedissonLexSortedSetTest.java +++ b/src/test/java/org/redisson/RedissonLexSortedSetTest.java @@ -1,5 +1,7 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; @@ -8,6 +10,44 @@ import org.redisson.core.RLexSortedSet; public class RedissonLexSortedSetTest extends BaseTest { + @Test + public void testPollLast() { + RLexSortedSet set = redisson.getLexSortedSet("simple"); + Assert.assertNull(set.pollLast()); + + set.add("a"); + set.add("b"); + set.add("c"); + + Assert.assertEquals("c", set.pollLast()); + MatcherAssert.assertThat(set, Matchers.contains("a", "b")); + } + + @Test + public void testPollFirst() { + RLexSortedSet set = redisson.getLexSortedSet("simple"); + Assert.assertNull(set.pollFirst()); + + set.add("a"); + set.add("b"); + set.add("c"); + + Assert.assertEquals("a", set.pollFirst()); + MatcherAssert.assertThat(set, Matchers.contains("b", "c")); + } + + @Test + public void testFirstLast() { + RLexSortedSet set = redisson.getLexSortedSet("simple"); + set.add("a"); + set.add("b"); + set.add("c"); + set.add("d"); + + Assert.assertEquals("a", set.first()); + Assert.assertEquals("d", set.last()); + } + @Test public void testRemoveLexRangeTail() { RLexSortedSet set = redisson.getLexSortedSet("simple"); @@ -20,12 +60,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 +80,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 +97,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 +114,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 +130,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 +146,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 +160,8 @@ public class RedissonLexSortedSetTest extends BaseTest { set.add("f"); set.add("g"); - Assert.assertEquals(5, (int)set.lexCount("b", true, "f", true)); - Assert.assertEquals(3, (int)set.lexCount("b", false, "f", false)); + assertThat(set.count("b", true, "f", true)).isEqualTo(5); + assertThat(set.count("b", false, "f", false)).isEqualTo(3); } } diff --git a/src/test/java/org/redisson/RedissonListTest.java b/src/test/java/org/redisson/RedissonListTest.java index 9341d939b..46623be44 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,19 +10,38 @@ 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 + public void testAddBefore() { + RList list = redisson.getList("list"); + list.add("1"); + list.add("2"); + list.add("3"); + + assertThat(list.addBefore("2", "0")).isEqualTo(4); + + assertThat(list).containsExactly("1", "0", "2", "3"); + } + + @Test + public void testAddAfter() { + RList list = redisson.getList("list"); + list.add("1"); + list.add("2"); + list.add("3"); + + assertThat(list.addAfter("2", "0")).isEqualTo(4); + + assertThat(list).containsExactly("1", "2", "0", "3"); + } + + @Test public void testTrim() { RList list = redisson.getList("list1"); @@ -84,37 +105,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 +114,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 +177,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 +203,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 +597,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 +623,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 +652,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 +674,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 +708,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 +728,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 +756,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 +781,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 +846,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 +948,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 +968,6 @@ public class RedissonListTest extends BaseTest { list.add("3"); list.add("e"); - Assert.assertThat(list, Matchers.contains(1, 2L, "3", "e")); + assertThat(list).containsExactly(1, 2L, "3", "e"); } } diff --git a/src/test/java/org/redisson/RedissonMapCacheTest.java b/src/test/java/org/redisson/RedissonMapCacheTest.java index 725ae166e..c98039f4f 100644 --- a/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -6,13 +6,14 @@ 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; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.codec.JsonJacksonCodec; @@ -124,13 +125,20 @@ public class RedissonMapCacheTest extends BaseTest { } } + + @Test + public void testCacheValues() { + final RMapCache map = redisson.getMapCache("testRMapCacheValues"); + map.put("1234", "5678", 0, TimeUnit.MINUTES, 60, TimeUnit.MINUTES); + assertThat(map.values()).containsOnly("5678"); + } @Test public void testGetAll() throws InterruptedException { RMapCache map = redisson.getMapCache("getAll"); map.put(1, 100); map.put(2, 200, 1, TimeUnit.SECONDS); - map.put(3, 300, 1, TimeUnit.SECONDS); + map.put(3, 300, 1, TimeUnit.SECONDS, 1, TimeUnit.SECONDS); map.put(4, 400); Map filtered = map.getAll(new HashSet(Arrays.asList(2, 3, 5))); @@ -200,6 +208,69 @@ 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 testIteratorRandomRemoveFirst() throws InterruptedException { + RMapCache map = redisson.getMapCache("simpleMap"); + for (int i = 0; i < 1000; 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 (cnt < 20) { + iterator.remove(); + removed++; + } + cnt++; + } + Assert.assertEquals(1000, cnt); + assertThat(map.size()).isEqualTo(cnt - removed); + } + + @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"); @@ -221,10 +292,12 @@ public class RedissonMapCacheTest extends BaseTest { map.put(2, "33", 1, TimeUnit.SECONDS); map.put(3, "43"); - Assert.assertEquals(3, map.entrySet().size()); - - MatcherAssert.assertThat(map, Matchers.hasEntry(Matchers.equalTo(1), Matchers.equalTo("12"))); - MatcherAssert.assertThat(map, Matchers.hasEntry(Matchers.equalTo(3), Matchers.equalTo("43"))); + Map expected = new HashMap<>(); + map.put(1, "12"); + map.put(3, "43"); + + assertThat(map.entrySet()).containsAll(expected.entrySet()); + assertThat(map).hasSize(3); } @Test @@ -253,7 +326,7 @@ public class RedissonMapCacheTest extends BaseTest { joinMap.put(6, "6"); map.putAll(joinMap); - MatcherAssert.assertThat(map.keySet(), Matchers.containsInAnyOrder(1, 2, 3, 4, 5, 6)); + assertThat(map.keySet()).containsOnly(1, 2, 3, 4, 5, 6); } @Test @@ -535,47 +608,6 @@ public class RedissonMapCacheTest extends BaseTest { Assert.assertEquals(1, map.size()); } -// @Test -// public void testKeyIterator() { -// RMap map = redisson.getCache("simple"); -// map.put(1, 0); -// map.put(3, 5); -// map.put(4, 6); -// map.put(7, 8); -// -// Collection keys = map.keySet(); -// MatcherAssert.assertThat(keys, Matchers.containsInAnyOrder(1, 3, 4, 7)); -// for (Iterator iterator = map.keyIterator(); iterator.hasNext();) { -// Integer value = iterator.next(); -// if (!keys.remove(value)) { -// Assert.fail(); -// } -// } -// -// Assert.assertEquals(0, keys.size()); -// } - -// @Test -// public void testValueIterator() { -// RCache map = redisson.getCache("simple"); -// map.put(1, 0); -// map.put(3, 5); -// map.put(4, 6); -// map.put(7, 8); -// -// Collection values = map.values(); -// MatcherAssert.assertThat(values, Matchers.containsInAnyOrder(0, 5, 6, 8)); -// for (Iterator iterator = map.valueIterator(); iterator.hasNext();) { -// Integer value = iterator.next(); -// if (!values.remove(value)) { -// Assert.fail(); -// } -// } -// -// Assert.assertEquals(0, values.size()); -// } - - @Test public void testFastPutIfAbsent() throws Exception { RMapCache map = redisson.getMapCache("simple"); @@ -722,6 +754,33 @@ public class RedissonMapCacheTest extends BaseTest { } } + + + @Test + public void testRMapCacheValues() { + final RMapCache map = redisson.getMapCache("testRMapCacheValues"); + map.put("1234", "5678", 1, TimeUnit.MINUTES, 60, TimeUnit.MINUTES); + assertThat(map.values()).containsOnly("5678"); + } + + @Test + public void testReadAllEntrySet() throws InterruptedException { + RMapCache map = redisson.getMapCache("simple12"); + map.put(1, "12"); + map.put(2, "33", 10, TimeUnit.MINUTES, 60, TimeUnit.MINUTES); + map.put(3, "43"); + + assertThat(map.readAllEntrySet()).isEqualTo(map.entrySet()); + } + + + @Test + public void testReadAllValues() { + final RMapCache map = redisson.getMapCache("testRMapCacheAllValues"); + map.put("1234", "5678", 1, TimeUnit.MINUTES, 60, TimeUnit.MINUTES); + assertThat(map.readAllValues()).containsOnly("5678"); + } + public static class SimpleObjectWithoutDefaultConstructor { private String testField; diff --git a/src/test/java/org/redisson/RedissonMapTest.java b/src/test/java/org/redisson/RedissonMapTest.java index 968f0e5d3..1c13f09aa 100644 --- a/src/test/java/org/redisson/RedissonMapTest.java +++ b/src/test/java/org/redisson/RedissonMapTest.java @@ -14,9 +14,6 @@ import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; -import org.assertj.core.data.MapEntry; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.client.codec.StringCodec; @@ -257,6 +254,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"); @@ -387,6 +404,18 @@ public class RedissonMapTest extends BaseTest { Map testMap = new HashMap<>(map); assertThat(map.readAllKeySet()).containsOnlyElementsOf(testMap.keySet()); } + + @Test + public void testReadAllKeySetHighAmount() { + RMap map = redisson.getMap("simple"); + for (int i = 0; i < 1000; i++) { + map.put(new SimpleKey("" + i), new SimpleValue("" + i)); + } + + assertThat(map.readAllKeySet().size()).isEqualTo(1000); + Map testMap = new HashMap<>(map); + assertThat(map.readAllKeySet()).containsOnlyElementsOf(testMap.keySet()); + } @Test public void testReadAllValues() { @@ -633,7 +662,7 @@ public class RedissonMapTest extends BaseTest { map.put(7, 8); Collection keys = map.keySet(); - MatcherAssert.assertThat(keys, Matchers.containsInAnyOrder(1, 3, 4, 7)); + assertThat(keys).containsOnly(1, 3, 4, 7); for (Iterator iterator = map.keyIterator(); iterator.hasNext();) { Integer value = iterator.next(); if (!keys.remove(value)) { @@ -653,7 +682,7 @@ public class RedissonMapTest extends BaseTest { map.put(7, 8); Collection values = map.values(); - MatcherAssert.assertThat(values, Matchers.containsInAnyOrder(0, 5, 6, 8)); + assertThat(values).containsOnly(0, 5, 6, 8); for (Iterator iterator = map.valueIterator(); iterator.hasNext();) { Integer value = iterator.next(); if (!values.remove(value)) { diff --git a/src/test/java/org/redisson/RedissonMultiLockTest.java b/src/test/java/org/redisson/RedissonMultiLockTest.java index 9b5446a80..9d8c1399a 100644 --- a/src/test/java/org/redisson/RedissonMultiLockTest.java +++ b/src/test/java/org/redisson/RedissonMultiLockTest.java @@ -16,6 +16,41 @@ import org.redisson.RedisRunner.RedisProcess; public class RedissonMultiLockTest { + @Test + public void testMultiThreads() throws IOException, InterruptedException { + RedisProcess redis1 = redisTestMultilockInstance1(); + + Config config1 = new Config(); + config1.useSingleServer().setAddress("127.0.0.1:6320"); + RedissonClient client = Redisson.create(config1); + + RLock lock1 = client.getLock("lock1"); + RLock lock2 = client.getLock("lock2"); + RLock lock3 = client.getLock("lock3"); + + Thread t = new Thread() { + public void run() { + RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); + lock.lock(); + + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + } + + lock.unlock(); + }; + }; + t.start(); + t.join(1000); + + RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); + lock.lock(); + lock.unlock(); + + assertThat(redis1.stop()).isEqualTo(0); + } + @Test public void test() throws IOException, InterruptedException { RedisProcess redis1 = redisTestMultilockInstance1(); diff --git a/src/test/java/org/redisson/RedissonQueueTest.java b/src/test/java/org/redisson/RedissonQueueTest.java index cf4df47bb..6834f00e9 100644 --- a/src/test/java/org/redisson/RedissonQueueTest.java +++ b/src/test/java/org/redisson/RedissonQueueTest.java @@ -1,11 +1,11 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Queue; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RQueue; @@ -20,9 +20,9 @@ public class RedissonQueueTest extends BaseTest { queue.add(3); queue.offer(4); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3, 4)); + assertThat(queue).containsExactly(1, 2, 3, 4); Assert.assertEquals((Integer)1, queue.poll()); - MatcherAssert.assertThat(queue, Matchers.contains(2, 3, 4)); + assertThat(queue).containsExactly(2, 3, 4); Assert.assertEquals((Integer)2, queue.element()); } @@ -34,9 +34,9 @@ public class RedissonQueueTest extends BaseTest { queue.add(3); queue.offer(4); - MatcherAssert.assertThat(queue, Matchers.contains(1, 2, 3, 4)); + assertThat(queue).containsExactly(1, 2, 3, 4); Assert.assertEquals((Integer)1, queue.poll()); - MatcherAssert.assertThat(queue, Matchers.contains(2, 3, 4)); + assertThat(queue).containsExactly(2, 3, 4); Assert.assertEquals((Integer)2, queue.element()); } @@ -51,7 +51,7 @@ public class RedissonQueueTest extends BaseTest { queue.remove(); queue.remove(); - MatcherAssert.assertThat(queue, Matchers.contains(3, 4)); + assertThat(queue).containsExactly(3, 4); queue.remove(); queue.remove(); @@ -69,7 +69,7 @@ public class RedissonQueueTest extends BaseTest { queue.remove(); queue.remove(); - MatcherAssert.assertThat(queue, Matchers.contains(3, 4)); + assertThat(queue).containsExactly(3, 4); queue.remove(); queue.remove(); diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java index c8daedd90..11ea1d5a5 100644 --- a/src/test/java/org/redisson/RedissonRemoteServiceTest.java +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -1,13 +1,15 @@ 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 java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Assert; +import org.junit.Test; +import org.redisson.remote.RemoteServiceTimeoutException; public class RedissonRemoteServiceTest extends BaseTest { @@ -59,7 +61,89 @@ public class RedissonRemoteServiceTest extends BaseTest { } - @Test(expected = RedisTimeoutException.class) + @Test + public void testExecutorsAmountConcurrency() throws InterruptedException { + + // Redisson server and client + final RedissonClient server = Redisson.create(); + final RedissonClient client = Redisson.create(); + + final int serverAmount = 1; + final int clientAmount = 10; + + // to store the current call concurrency count + final AtomicInteger concurrency = new AtomicInteger(0); + + // a flag to indicate the the allowed concurrency was exceeded + final AtomicBoolean concurrencyIsExceeded = new AtomicBoolean(false); + + // the server: register a service with an overrided timeoutMethod method that: + // - incr the concurrency + // - check if concurrency is greater than what was allowed, and if yes set the concurrencyOfOneIsExceeded flag + // - wait 2s + // - decr the concurrency + server.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl() { + @Override + public void timeoutMethod() throws InterruptedException { + try { + if (concurrency.incrementAndGet() > serverAmount) { + concurrencyIsExceeded.compareAndSet(false, true); + } + super.timeoutMethod(); + } finally { + concurrency.decrementAndGet(); + } + } + }, serverAmount); + + // a latch to force the client threads to execute simultaneously + // (as far as practicable, this is hard to predict) + final CountDownLatch readyLatch = new CountDownLatch(1); + + // the client: starts a couple of threads that will: + // - await for the ready latch + // - then call timeoutMethod + Future[] clientFutures = new Future[clientAmount]; + ExecutorService executor = Executors.newFixedThreadPool(clientAmount); + for (int i = 0; i < clientAmount; i++) { + clientFutures[i] = executor.submit(new Runnable() { + @Override + public void run() { + try { + RemoteInterface ri = client.getRemoteSerivce().get(RemoteInterface.class, clientAmount * 3, TimeUnit.SECONDS, clientAmount * 3, TimeUnit.SECONDS); + readyLatch.await(); + ri.timeoutMethod(); + } catch (InterruptedException e) { + // ignore + } + } + }); + } + + // open the latch to wake the threads + readyLatch.countDown(); + + // await for the client threads to terminate + for (Future clientFuture : clientFutures) { + try { + clientFuture.get(); + } catch (ExecutionException e) { + // ignore + } + } + executor.shutdown(); + executor.awaitTermination(clientAmount * 3, TimeUnit.SECONDS); + + // shutdown the server and the client + server.shutdown(); + client.shutdown(); + + // do the concurrencyIsExceeded flag was set ? + // if yes, that would indicate that the server exceeded its expected concurrency + assertThat(concurrencyIsExceeded.get()).isEqualTo(false); + } + + @Test(expected = RemoteServiceTimeoutException.class) public void testTimeout() throws InterruptedException { RedissonClient r1 = Redisson.create(); r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); @@ -104,5 +188,21 @@ public class RedissonRemoteServiceTest extends BaseTest { r1.shutdown(); r2.shutdown(); } - + + @Test + public void testInvocationWithServiceName() { + String name = "MyServiceName"; + + RedissonClient r1 = Redisson.create(); + r1.getRemoteSerivce(name).register(RemoteInterface.class, new RemoteImpl()); + + RedissonClient r2 = Redisson.create(); + RemoteInterface ri = r2.getRemoteSerivce(name).get(RemoteInterface.class); + + ri.voidMethod("someName", 100L); + assertThat(ri.resultMethod(100L)).isEqualTo(200); + + r1.shutdown(); + r2.shutdown(); + } } diff --git a/src/test/java/org/redisson/RedissonScoredSortedSetTest.java b/src/test/java/org/redisson/RedissonScoredSortedSetTest.java index 3aeab3d72..cdc445007 100644 --- a/src/test/java/org/redisson/RedissonScoredSortedSetTest.java +++ b/src/test/java/org/redisson/RedissonScoredSortedSetTest.java @@ -13,8 +13,6 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ExecutionException; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.redisson.client.codec.StringCodec; @@ -27,6 +25,30 @@ import io.netty.util.concurrent.Future; public class RedissonScoredSortedSetTest extends BaseTest { + @Test + public void testCount() { + RScoredSortedSet set = redisson.getScoredSortedSet("simple"); + set.add(0, "1"); + set.add(1, "4"); + set.add(2, "2"); + set.add(3, "5"); + set.add(4, "3"); + + assertThat(set.count(0, true, 3, false)).isEqualTo(3); + } + + @Test + public void testReadAll() { + RScoredSortedSet set = redisson.getScoredSortedSet("simple"); + set.add(0, "1"); + set.add(1, "4"); + set.add(2, "2"); + set.add(3, "5"); + set.add(4, "3"); + + assertThat(set.readAll()).containsOnly("1", "2", "4", "5", "3"); + } + @Test public void testAddAll() { RScoredSortedSet set = redisson.getScoredSortedSet("simple"); @@ -59,7 +81,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.3, "c"); Assert.assertEquals("c", set.pollLast()); - MatcherAssert.assertThat(set, Matchers.contains("a", "b")); + assertThat(set).containsExactly("a", "b"); } @Test @@ -72,7 +94,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.3, "c"); Assert.assertEquals("a", set.pollFirst()); - MatcherAssert.assertThat(set, Matchers.contains("b", "c")); + assertThat(set).containsExactly("b", "c"); } @Test @@ -100,7 +122,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.7, "g"); Assert.assertEquals(2, set.removeRangeByScore(0.1, false, 0.3, true)); - MatcherAssert.assertThat(set, Matchers.contains("a", "d", "e", "f", "g")); + assertThat(set).containsExactly("a", "d", "e", "f", "g"); } @Test @@ -115,7 +137,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.7, "g"); Assert.assertEquals(3, set.removeRangeByScore(Double.NEGATIVE_INFINITY, false, 0.3, true)); - MatcherAssert.assertThat(set, Matchers.contains("d", "e", "f", "g")); + assertThat(set).containsExactly("d", "e", "f", "g"); } @Test @@ -130,7 +152,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.7, "g"); Assert.assertEquals(3, set.removeRangeByScore(0.4, false, Double.POSITIVE_INFINITY, true)); - MatcherAssert.assertThat(set, Matchers.contains("a", "b", "c", "d")); + assertThat(set).containsExactly("a", "b", "c", "d"); } @Test @@ -145,7 +167,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.7, "g"); Assert.assertEquals(2, set.removeRangeByRank(0, 1)); - MatcherAssert.assertThat(set, Matchers.contains("c", "d", "e", "f", "g")); + assertThat(set).containsExactly("c", "d", "e", "f", "g"); } @Test @@ -159,7 +181,8 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.6, "f"); set.add(0.7, "g"); - Assert.assertEquals(3, (int)set.rank("d")); + assertThat(set.revRank("d")).isEqualTo(3); + assertThat(set.rank("abc")).isNull(); } @Test @@ -173,7 +196,8 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.6, "f"); set.add(0.7, "g"); - Assert.assertEquals(1, (int)set.revRank("f")); + assertThat(set.revRank("f")).isEqualTo(1); + assertThat(set.revRank("abc")).isNull(); } @@ -197,14 +221,14 @@ public class RedissonScoredSortedSetTest extends BaseTest { Assert.assertTrue(set.removeAsync(1).get()); Assert.assertFalse(set.contains(1)); - Assert.assertThat(set, Matchers.contains(3, 7)); + assertThat(set).containsExactly(3, 7); Assert.assertFalse(set.removeAsync(1).get()); - Assert.assertThat(set, Matchers.contains(3, 7)); + assertThat(set).containsExactly(3, 7); set.removeAsync(3).get(); Assert.assertFalse(set.contains(3)); - Assert.assertThat(set, Matchers.contains(7)); + assertThat(set).containsExactly(7); } @Test @@ -235,7 +259,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { } } - Assert.assertThat(set, Matchers.contains("1", "4", "5", "3")); + assertThat(set).containsExactly("1", "4", "5", "3"); int iteration = 0; for (Iterator iterator = set.iterator(); iterator.hasNext();) { @@ -284,8 +308,10 @@ public class RedissonScoredSortedSetTest extends BaseTest { } Assert.assertTrue(set.retainAll(Arrays.asList(1, 2))); - Assert.assertThat(set, Matchers.containsInAnyOrder(1, 2)); + assertThat(set).containsExactly(1, 2); Assert.assertEquals(2, set.size()); + assertThat(set.getScore(1)).isEqualTo(10); + assertThat(set.getScore(2)).isEqualTo(20); } @Test @@ -296,7 +322,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(0.3, 3); Assert.assertTrue(set.removeAll(Arrays.asList(1, 2))); - Assert.assertThat(set, Matchers.contains(3)); + assertThat(set).containsOnly(3); Assert.assertEquals(1, set.size()); } @@ -314,15 +340,15 @@ public class RedissonScoredSortedSetTest extends BaseTest { SortedSet hs = set.tailSet(3); hs.add(10); - MatcherAssert.assertThat(hs, Matchers.contains(3, 4, 5, 10)); + assertThat(hs).containsExactly(3, 4, 5, 10); set.remove(4); - MatcherAssert.assertThat(hs, Matchers.contains(3, 5, 10)); + assertThat(hs).containsExactly(3, 5, 10); set.remove(3); - MatcherAssert.assertThat(hs, Matchers.contains(5, 10)); + assertThat(hs).containsExactly(5, 10); hs.add(-1); } @@ -341,15 +367,15 @@ public class RedissonScoredSortedSetTest extends BaseTest { SortedSet hs = set.headSet(3); hs.add(0); - MatcherAssert.assertThat(hs, Matchers.contains(0, 1, 2)); + assertThat(hs).containsExactly(0, 1, 2); set.remove(2); - MatcherAssert.assertThat(hs, Matchers.contains(0, 1)); + assertThat(hs).containsExactly(0, 1); set.remove(3); - MatcherAssert.assertThat(hs, Matchers.contains(0, 1)); + assertThat(hs).containsExactly(0, 1); hs.add(7); } @@ -367,15 +393,15 @@ public class RedissonScoredSortedSetTest extends BaseTest { SortedSet hs = set.tailSet(3); hs.add(10); - MatcherAssert.assertThat(hs, Matchers.contains(3, 4, 5, 10)); + assertThat(hs).containsExactly(3, 4, 5, 10); set.remove(4); - MatcherAssert.assertThat(hs, Matchers.contains(3, 5, 10)); + assertThat(hs).containsExactly(3, 5, 10); set.remove(3); - MatcherAssert.assertThat(hs, Matchers.contains(5, 10)); + assertThat(hs).containsExactly(5, 10); hs.add(-1); } @@ -393,15 +419,15 @@ public class RedissonScoredSortedSetTest extends BaseTest { SortedSet hs = set.headSet(3); hs.add(0); - MatcherAssert.assertThat(hs, Matchers.contains(0, 1, 2)); + assertThat(hs).containsExactly(0, 1, 2); set.remove(2); - MatcherAssert.assertThat(hs, Matchers.contains(0, 1)); + assertThat(hs).containsExactly(0, 1); set.remove(3); - MatcherAssert.assertThat(hs, Matchers.contains(0, 1)); + assertThat(hs).containsExactly(0, 1); hs.add(7); } @@ -417,10 +443,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { Assert.assertTrue(set.add(1, -1)); Assert.assertTrue(set.add(2, 0)); - MatcherAssert.assertThat(set, Matchers.contains(-1, 0, 1, 2, 3, 4, 10)); - -// Assert.assertEquals(-1, (int)set.first()); -// Assert.assertEquals(10, (int)set.last()); + assertThat(set).containsExactly(-1, 0, 1, 2, 3, 4, 10); } @Test @@ -435,7 +458,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { Assert.assertFalse(set.remove(0)); Assert.assertTrue(set.remove(3)); - Assert.assertThat(set, Matchers.contains(1, 2, 4, 5)); + assertThat(set).containsExactly(1, 2, 4, 5); } @Test @@ -458,10 +481,10 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(3, "5"); set.add(4, "3"); - MatcherAssert.assertThat(Arrays.asList(set.toArray()), Matchers.containsInAnyOrder("1", "2", "4", "5", "3")); + assertThat(Arrays.asList(set.toArray())).containsExactly("1", "4", "2", "5", "3"); String[] strs = set.toArray(new String[0]); - MatcherAssert.assertThat(Arrays.asList(strs), Matchers.containsInAnyOrder("1", "4", "2", "5", "3")); + assertThat(Arrays.asList(strs)).containsExactly("1", "4", "2", "5", "3"); } @Test @@ -517,7 +540,7 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(4, 5); Collection vals = set.valueRange(0, -1); - MatcherAssert.assertThat(vals, Matchers.contains(1, 2, 3, 4, 5)); + assertThat(vals).containsExactly(1, 2, 3, 4, 5); } @Test @@ -530,11 +553,11 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(50, 5); Collection> vals = set.entryRange(0, -1); - MatcherAssert.assertThat(vals, Matchers.contains(new ScoredEntry(10D, 1), + assertThat(vals).containsExactly(new ScoredEntry(10D, 1), new ScoredEntry(20D, 2), new ScoredEntry(30D, 3), new ScoredEntry(40D, 4), - new ScoredEntry(50D, 5))); + new ScoredEntry(50D, 5)); } @Test @@ -649,12 +672,32 @@ public class RedissonScoredSortedSetTest extends BaseTest { set.add(4, "e"); Collection> r = set.entryRange(1, true, 4, false, 1, 2); + Assert.assertEquals(2, r.size()); ScoredEntry[] a = r.toArray(new ScoredEntry[0]); Assert.assertEquals(2d, a[0].getScore(), 0); Assert.assertEquals(3d, a[1].getScore(), 0); Assert.assertEquals("c", a[0].getValue()); Assert.assertEquals("d", a[1].getValue()); } + + @Test + public void testScoredSortedSetEntryRangeReversed() { + RScoredSortedSet set = redisson.getScoredSortedSet("simple"); + + set.add(0, "a"); + set.add(1, "b"); + set.add(2, "c"); + set.add(3, "d"); + set.add(4, "e"); + + Collection> r = set.entryRangeReversed(1, true, 4, false, 1, 2); + Assert.assertEquals(2, r.size()); + ScoredEntry[] a = r.toArray(new ScoredEntry[0]); + Assert.assertEquals(2d, a[0].getScore(), 0); + Assert.assertEquals(1d, a[1].getScore(), 0); + Assert.assertEquals("c", a[0].getValue()); + Assert.assertEquals("b", a[1].getValue()); + } @Test public void testScoredSortedSetEntryRangeNegativeInf() { diff --git a/src/test/java/org/redisson/RedissonSemaphoreTest.java b/src/test/java/org/redisson/RedissonSemaphoreTest.java index 4561a8709..9a81d26ff 100644 --- a/src/test/java/org/redisson/RedissonSemaphoreTest.java +++ b/src/test/java/org/redisson/RedissonSemaphoreTest.java @@ -6,6 +6,7 @@ import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; import org.redisson.core.RSemaphore; @@ -240,7 +241,8 @@ public class RedissonSemaphoreTest extends BaseConcurrentTest { RSemaphore s = redisson.getSemaphore("test"); s.setPermits(10); - final CyclicBarrier barrier = new CyclicBarrier(10); + final AtomicInteger checkPermits = new AtomicInteger(s.availablePermits()); + final CyclicBarrier barrier = new CyclicBarrier(s.availablePermits()); testMultiInstanceConcurrency(iterations, new RedissonRunnable() { @Override public void run(RedissonClient redisson) { @@ -250,10 +252,12 @@ public class RedissonSemaphoreTest extends BaseConcurrentTest { barrier.await(); - assertThat(s.availablePermits()).isEqualTo(0); - assertThat(s.tryAcquire()).isFalse(); - - Thread.sleep(50); + if (checkPermits.decrementAndGet() > 0) { + assertThat(s.availablePermits()).isEqualTo(0); + assertThat(s.tryAcquire()).isFalse(); + } else { + Thread.sleep(50); + } } catch (InterruptedException e) { // TODO Auto-generated catch block @@ -268,6 +272,8 @@ public class RedissonSemaphoreTest extends BaseConcurrentTest { } }); + System.out.println(lockedCounter.get()); + assertThat(lockedCounter.get()).isLessThan(iterations); } diff --git a/src/test/java/org/redisson/RedissonSetCacheReactiveTest.java b/src/test/java/org/redisson/RedissonSetCacheReactiveTest.java index 5ea9204d2..bbd1bb185 100644 --- a/src/test/java/org/redisson/RedissonSetCacheReactiveTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheReactiveTest.java @@ -1,5 +1,7 @@ package org.redisson; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.Serializable; import java.util.Arrays; import java.util.Collections; @@ -73,18 +75,18 @@ public class RedissonSetCacheReactiveTest extends BaseReactiveTest { @Test public void testExpireOverwrite() throws InterruptedException, ExecutionException { RSetCacheReactive set = redisson.getSetCache("simple"); - set.add("123", 1, TimeUnit.SECONDS); + assertThat(sync(set.add("123", 1, TimeUnit.SECONDS))).isTrue(); Thread.sleep(800); - set.add("123", 1, TimeUnit.SECONDS); + assertThat(sync(set.add("123", 1, TimeUnit.SECONDS))).isFalse(); - Thread.sleep(800); - Assert.assertTrue(sync(set.contains("123"))); + Thread.sleep(50); + assertThat(sync(set.contains("123"))).isTrue(); - Thread.sleep(200); + Thread.sleep(150); - Assert.assertFalse(sync(set.contains("123"))); + assertThat(sync(set.contains("123"))).isFalse(); } @Test @@ -238,7 +240,7 @@ public class RedissonSetCacheReactiveTest extends BaseReactiveTest { Thread.sleep(1000); - MatcherAssert.assertThat(sync(cache), Matchers.contains("0", "2", "3")); + assertThat(sync(cache)).contains("0", "2", "3"); } @Test diff --git a/src/test/java/org/redisson/RedissonSetCacheTest.java b/src/test/java/org/redisson/RedissonSetCacheTest.java index 9d7c71188..65fce35e6 100644 --- a/src/test/java/org/redisson/RedissonSetCacheTest.java +++ b/src/test/java/org/redisson/RedissonSetCacheTest.java @@ -5,8 +5,10 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -32,6 +34,18 @@ public class RedissonSetCacheTest extends BaseTest { } + @Test + public void testRemoveAll() { + RSetCache set = redisson.getSetCache("set"); + set.add(1); + set.add(2, 10, TimeUnit.SECONDS); + set.add(3); + + assertThat(set.removeAll(Arrays.asList(1, 3))).isTrue(); + assertThat(set.removeAll(Arrays.asList(1, 3))).isFalse(); + assertThat(set).containsOnly(2); + } + @Test public void testDelete() { RSetCache set = redisson.getSetCache("set"); @@ -46,56 +60,100 @@ public class RedissonSetCacheTest extends BaseTest { RSetCache set = redisson.getSetCache("set"); assertThat(set.readAll()).isEmpty(); } - + + @Test + public void testAddBigBean() { + RSetCache> set = redisson.getSetCache("simple"); + Map map = new HashMap(); + for (int i = 0; i < 150; i++) { + map.put(i, i); + } + set.add(map); + map.remove(0); + set.add(map); + set.iterator().next(); + } + @Test public void testAddBean() throws InterruptedException, ExecutionException { SimpleBean sb = new SimpleBean(); sb.setLng(1L); RSetCache set = redisson.getSetCache("simple"); - set.add(sb); + assertThat(set.add(sb)).isTrue(); Assert.assertEquals(sb.getLng(), set.iterator().next().getLng()); } @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 testAddOverrideExpiration() throws InterruptedException { + RSetCache set = redisson.getSetCache("simple31"); + assertThat(set.add("123", 500, TimeUnit.MILLISECONDS)).isTrue(); + Thread.sleep(500); + assertThat(set.add("123", 3, TimeUnit.SECONDS)).isTrue(); + Thread.sleep(2000); + assertThat(set.contains("123")).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 @@ -182,7 +240,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))); @@ -283,6 +341,14 @@ public class RedissonSetCacheTest extends BaseTest { Assert.assertEquals(5, set.size()); } + @Test + public void testReadAllExpired() throws InterruptedException { + RSetCache set = redisson.getSetCache("set"); + set.add(1, 1, TimeUnit.SECONDS); + Thread.sleep(1005); + assertThat(set.readAll()).isEmpty(); + } + @Test public void testReadAll() { RSetCache set = redisson.getSetCache("set"); 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(); diff --git a/src/test/java/org/redisson/RedissonSetTest.java b/src/test/java/org/redisson/RedissonSetTest.java index c2999c3e6..4e0fac31f 100644 --- a/src/test/java/org/redisson/RedissonSetTest.java +++ b/src/test/java/org/redisson/RedissonSetTest.java @@ -13,6 +13,7 @@ import java.util.concurrent.ExecutionException; import org.junit.Assert; import org.junit.Test; import org.redisson.core.RSet; +import org.redisson.core.RSetCache; import io.netty.util.concurrent.Future; @@ -32,6 +33,18 @@ public class RedissonSetTest extends BaseTest { } + @Test + public void testRemoveAll() { + RSet set = redisson.getSet("set"); + set.add(1); + set.add(2); + set.add(3); + + assertThat(set.removeAll(Arrays.asList(1, 3))).isTrue(); + assertThat(set.removeAll(Arrays.asList(1, 3))).isFalse(); + assertThat(set).containsOnly(2); + } + @Test public void testRemoveRandom() { RSet set = redisson.getSet("simple"); @@ -321,7 +334,81 @@ public class RedissonSetTest extends BaseTest { assertThat(set).containsOnly(5, 6); } + @Test + public void testDiff() { + RSet set = redisson.getSet("set"); + set.add(5); + set.add(6); + RSet set1 = redisson.getSet("set1"); + set1.add(1); + set1.add(2); + set1.add(3); + RSet set2 = redisson.getSet("set2"); + set2.add(3); + set2.add(4); + set2.add(5); + + assertThat(set.diff("set1", "set2")).isEqualTo(2); + assertThat(set).containsOnly(1, 2); + } + + @Test + public void testReadDiff() { + RSet set = redisson.getSet("set"); + set.add(5); + set.add(7); + set.add(6); + RSet set1 = redisson.getSet("set1"); + set1.add(1); + set1.add(2); + set1.add(5); + RSet set2 = redisson.getSet("set2"); + set2.add(3); + set2.add(4); + set2.add(5); + + assertThat(set.readDiff("set1", "set2")).containsOnly(7, 6); + assertThat(set).containsOnly(6, 5, 7); + } + + @Test + public void testIntersection() { + RSet set = redisson.getSet("set"); + set.add(5); + set.add(6); + RSet set1 = redisson.getSet("set1"); + set1.add(1); + set1.add(2); + set1.add(3); + RSet set2 = redisson.getSet("set2"); + set2.add(3); + set2.add(4); + set2.add(5); + + assertThat(set.intersection("set1", "set2")).isEqualTo(1); + assertThat(set).containsOnly(3); + } + + @Test + public void testReadIntersection() { + RSet set = redisson.getSet("set"); + set.add(5); + set.add(7); + set.add(6); + RSet set1 = redisson.getSet("set1"); + set1.add(1); + set1.add(2); + set1.add(5); + RSet set2 = redisson.getSet("set2"); + set2.add(3); + set2.add(4); + set2.add(5); + + assertThat(set.readIntersection("set1", "set2")).containsOnly(5); + assertThat(set).containsOnly(6, 5, 7); + } + @Test public void testMove() throws Exception { RSet set = redisson.getSet("set"); 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) 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 {