Merge branch 'mrniko/master'

pull/499/head
Rui Gu 9 years ago
commit a4c9b412df

@ -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

@ -2,6 +2,63 @@ Redisson Releases History
================================ ================================
####Please Note: trunk is current development branch. ####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 ####04-Mar-2016 - version 2.2.9 released
Feature - __new object added__ `RSetMultimap` Feature - __new object added__ `RSetMultimap`

@ -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/) [![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 * [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 [Reactive Streams](http://www.reactive-streams.org)
* Supports [Redis pipelining](http://redis.io/topics/pipelining) (command batches) * 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 Android platform
* Supports auto-reconnect * Supports auto-reconnect
* Supports failed to send command auto-retry * Supports failed to send command auto-retry
@ -81,12 +82,12 @@ Include the following to your dependency list:
<dependency> <dependency>
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson</artifactId> <artifactId>redisson</artifactId>
<version>2.2.9</version> <version>2.2.13</version>
</dependency> </dependency>
### Gradle ### Gradle
compile 'org.redisson:redisson:2.2.9' compile 'org.redisson:redisson:2.2.13'
### Supported by ### Supported by

@ -3,7 +3,7 @@
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson</artifactId> <artifactId>redisson</artifactId>
<version>2.2.11-SNAPSHOT</version> <version>2.2.14-SNAPSHOT</version>
<packaging>bundle</packaging> <packaging>bundle</packaging>
<name>Redisson</name> <name>Redisson</name>
@ -100,33 +100,33 @@
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId> <artifactId>netty-transport-native-epoll</artifactId>
<version>4.0.34.Final</version> <version>4.0.36.Final</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-common</artifactId> <artifactId>netty-common</artifactId>
<version>4.0.34.Final</version> <version>4.0.36.Final</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId> <artifactId>netty-codec</artifactId>
<version>4.0.34.Final</version> <version>4.0.36.Final</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId> <artifactId>netty-buffer</artifactId>
<version>4.0.34.Final</version> <version>4.0.36.Final</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId> <artifactId>netty-transport</artifactId>
<version>4.0.34.Final</version> <version>4.0.36.Final</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId> <artifactId>netty-handler</artifactId>
<version>4.0.34.Final</version> <version>4.0.36.Final</version>
</dependency> </dependency>
<dependency> <dependency>
@ -145,6 +145,7 @@
<groupId>com.jayway.awaitility</groupId> <groupId>com.jayway.awaitility</groupId>
<artifactId>awaitility</artifactId> <artifactId>awaitility</artifactId>
<version>1.7.0</version> <version>1.7.0</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
@ -260,7 +261,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId> <artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version> <version>2.10</version>
<configuration> <configuration>
<downloadSources>true</downloadSources> <downloadSources>true</downloadSources>
<forceRecheck>true</forceRecheck> <forceRecheck>true</forceRecheck>
@ -309,7 +310,7 @@
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version> <version>3.5.1</version>
<configuration> <configuration>
<source>${source.version}</source> <source>${source.version}</source>
<target>${source.version}</target> <target>${source.version}</target>
@ -335,7 +336,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>2.4</version> <version>3.0.0</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
@ -350,7 +351,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version> <version>2.19.1</version>
</plugin> </plugin>
<plugin> <plugin>

@ -141,6 +141,9 @@ public class EvictionScheduler {
} }
} }
public void schedule(String name) {
schedule(name, null);
}
public void schedule(String name, String timeoutSetName, String maxIdleSetName) { public void schedule(String name, String timeoutSetName, String maxIdleSetName) {
RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName, false); RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName, false);
@ -225,6 +228,10 @@ public class EvictionScheduler {
Arrays.<Object>asList(name, timeoutSetName, maxIdleSetName), System.currentTimeMillis(), keysLimit); Arrays.<Object>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, return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); " "local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "if #expiredKeys > 0 then " + "if #expiredKeys > 0 then "

@ -47,8 +47,10 @@ import org.redisson.core.RBlockingDeque;
import org.redisson.core.RBlockingQueue; import org.redisson.core.RBlockingQueue;
import org.redisson.core.RBloomFilter; import org.redisson.core.RBloomFilter;
import org.redisson.core.RBucket; import org.redisson.core.RBucket;
import org.redisson.core.RBuckets;
import org.redisson.core.RCountDownLatch; import org.redisson.core.RCountDownLatch;
import org.redisson.core.RDeque; import org.redisson.core.RDeque;
import org.redisson.core.RGeo;
import org.redisson.core.RHyperLogLog; import org.redisson.core.RHyperLogLog;
import org.redisson.core.RKeys; import org.redisson.core.RKeys;
import org.redisson.core.RLexSortedSet; import org.redisson.core.RLexSortedSet;
@ -73,6 +75,7 @@ import org.redisson.core.RSortedSet;
import org.redisson.core.RTopic; import org.redisson.core.RTopic;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/** /**
* Main infrastructure class allows to get access * Main infrastructure class allows to get access
@ -181,6 +184,16 @@ public class Redisson implements RedissonClient {
return new RedissonReactive(config); return new RedissonReactive(config);
} }
@Override
public <V> RGeo<V> getGeo(String name) {
return new RedissonGeo<V>(commandExecutor, name);
}
@Override
public <V> RGeo<V> getGeo(String name, Codec codec) {
return new RedissonGeo<V>(codec, commandExecutor, name);
}
@Override @Override
public <V> RBucket<V> getBucket(String name) { public <V> RBucket<V> getBucket(String name) {
return new RedissonBucket<V>(commandExecutor, name); return new RedissonBucket<V>(commandExecutor, name);
@ -191,6 +204,16 @@ public class Redisson implements RedissonClient {
return new RedissonBucket<V>(codec, commandExecutor, name); return new RedissonBucket<V>(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 @Override
public <V> List<RBucket<V>> findBuckets(String pattern) { public <V> List<RBucket<V>> findBuckets(String pattern) {
Collection<String> keys = commandExecutor.get(commandExecutor.<List<String>, String>readAllAsync(RedisCommands.KEYS, pattern)); Collection<String> keys = commandExecutor.get(commandExecutor.<List<String>, String>readAllAsync(RedisCommands.KEYS, pattern));
@ -249,11 +272,6 @@ public class Redisson implements RedissonClient {
commandExecutor.write(params.get(0).toString(), RedisCommands.MSET, params.toArray()); commandExecutor.write(params.get(0).toString(), RedisCommands.MSET, params.toArray());
} }
@Override
public <V> List<RBucket<V>> getBuckets(String pattern) {
return findBuckets(pattern);
}
@Override @Override
public <V> RHyperLogLog<V> getHyperLogLog(String name) { public <V> RHyperLogLog<V> getHyperLogLog(String name) {
return new RedissonHyperLogLog<V>(commandExecutor, name); return new RedissonHyperLogLog<V>(commandExecutor, name);
@ -373,6 +391,11 @@ public class Redisson implements RedissonClient {
return new RedissonRemoteService(this); return new RedissonRemoteService(this);
} }
@Override
public RRemoteService getRemoteSerivce(String name) {
return new RedissonRemoteService(this, name);
}
@Override @Override
public <V> RSortedSet<V> getSortedSet(String name) { public <V> RSortedSet<V> getSortedSet(String name) {
return new RedissonSortedSet<V>(commandExecutor, name); return new RedissonSortedSet<V>(commandExecutor, name);
@ -508,6 +531,12 @@ public class Redisson implements RedissonClient {
connectionManager.shutdown(); connectionManager.shutdown();
} }
@Override
public void shutdown(long quietPeriod, long timeout, TimeUnit unit) {
connectionManager.shutdown(quietPeriod, timeout, unit);
}
@Override @Override
public Config getConfig() { public Config getConfig() {
return config; return config;
@ -526,16 +555,6 @@ public class Redisson implements RedissonClient {
return new RedisNodes<ClusterNode>(connectionManager); return new RedisNodes<ClusterNode>(connectionManager);
} }
@Override
public void flushdb() {
commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHDB));
}
@Override
public void flushall() {
commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHALL));
}
@Override @Override
public boolean isShutdown() { public boolean isShutdown() {
return connectionManager.isShutdown(); return connectionManager.isShutdown();

@ -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<V> implements Iterator<V> {
private List<V> firstValues;
private List<V> lastValues;
private Iterator<V> 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<V> res = iterator(client, nextIterPos);
lastValues = new ArrayList<V>(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<V> 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);
}

@ -31,44 +31,103 @@ import io.netty.buffer.ByteBuf;
abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> { abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
private Map<ByteBuf, ByteBuf> firstValues; private Map<ByteBuf, ByteBuf> firstValues;
private Iterator<Map.Entry<ScanObjectEntry, ScanObjectEntry>> iter; private Map<ByteBuf, ByteBuf> lastValues;
protected long iterPos = 0; private Iterator<Map.Entry<ScanObjectEntry, ScanObjectEntry>> lastIter;
protected long nextIterPos;
protected InetSocketAddress client; protected InetSocketAddress client;
private boolean finished; private boolean finished;
private boolean currentElementRemoved;
private boolean removeExecuted; private boolean removeExecuted;
protected Map.Entry<ScanObjectEntry, ScanObjectEntry> entry; protected Map.Entry<ScanObjectEntry, ScanObjectEntry> entry;
@Override @Override
public boolean hasNext() { public boolean hasNext() {
if (finished) { if (lastIter == null || !lastIter.hasNext()) {
return false; if (finished) {
} free(firstValues);
if (iter == null || !iter.hasNext()) { free(lastValues);
MapScanResult<ScanObjectEntry, ScanObjectEntry> res = iterator();
client = res.getRedisClient(); currentElementRemoved = false;
if (iterPos == 0 && firstValues == null) { removeExecuted = false;
firstValues = convert(res.getMap()); client = null;
} else { firstValues = null;
Map<ByteBuf, ByteBuf> newValues = convert(res.getMap()); lastValues = null;
if (newValues.equals(firstValues)) { nextIterPos = 0;
finished = true;
free(firstValues); if (!tryAgain()) {
free(newValues);
firstValues = null;
return false; return false;
} }
free(newValues); finished = false;
}
long prevIterPos;
do {
prevIterPos = nextIterPos;
MapScanResult<ScanObjectEntry, ScanObjectEntry> 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<ScanObjectEntry, ScanObjectEntry> iterator(); protected abstract MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator();
private void free(Map<ByteBuf, ByteBuf> map) { private void free(Map<ByteBuf, ByteBuf> map) {
if (map == null) {
return;
}
for (Entry<ByteBuf, ByteBuf> entry : map.entrySet()) { for (Entry<ByteBuf, ByteBuf> entry : map.entrySet()) {
entry.getKey().release(); entry.getKey().release();
entry.getValue().release(); entry.getValue().release();
@ -89,8 +148,8 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
throw new NoSuchElementException("No such element at index"); throw new NoSuchElementException("No such element at index");
} }
entry = iter.next(); entry = lastIter.next();
removeExecuted = false; currentElementRemoved = false;
return getValue(entry); return getValue(entry);
} }
@ -108,14 +167,17 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
@Override @Override
public void remove() { public void remove() {
if (removeExecuted) { if (currentElementRemoved) {
throw new IllegalStateException("Element been already deleted"); throw new IllegalStateException("Element been already deleted");
} }
if (lastIter == null) {
throw new IllegalStateException();
}
// lazy init iterator firstValues.remove(entry.getKey().getBuf());
hasNext(); lastIter.remove();
iter.remove();
removeKey(); removeKey();
currentElementRemoved = true;
removeExecuted = true; removeExecuted = true;
} }

@ -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 <V> List<RBucket<V>> find(String pattern) {
Collection<String> keys = commandExecutor.get(commandExecutor.<List<String>, String>readAllAsync(RedisCommands.KEYS, pattern));
List<RBucket<V>> buckets = new ArrayList<RBucket<V>>(keys.size());
for (String key : keys) {
if(key == null) {
continue;
}
buckets.add(redisson.<V>getBucket(key, codec));
}
return buckets;
}
@Override
public <V> Map<String, V> get(String... keys) {
if (keys.length == 0) {
return Collections.emptyMap();
}
RedisCommand<Map<Object, Object>> command = new RedisCommand<Map<Object, Object>>("MGET", new MapGetAllDecoder(Arrays.<Object>asList(keys), 0), ValueType.OBJECTS);
Future<Map<String, V>> future = commandExecutor.readAsync(keys[0], new DelegateDecoderCodec(codec), command, keys);
return commandExecutor.get(future);
}
@Override
public boolean trySet(Map<String, ?> buckets) {
if (buckets.isEmpty()) {
return false;
}
List<Object> params = new ArrayList<Object>(buckets.size());
for (Entry<String, ?> 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<String, ?> buckets) {
if (buckets.isEmpty()) {
return;
}
List<Object> params = new ArrayList<Object>(buckets.size());
for (Entry<String, ?> 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());
}
}

@ -18,6 +18,7 @@ package org.redisson;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.core.ClusterNode; import org.redisson.core.ClusterNode;
@ -31,8 +32,10 @@ import org.redisson.core.RBlockingDeque;
import org.redisson.core.RBlockingQueue; import org.redisson.core.RBlockingQueue;
import org.redisson.core.RBloomFilter; import org.redisson.core.RBloomFilter;
import org.redisson.core.RBucket; import org.redisson.core.RBucket;
import org.redisson.core.RBuckets;
import org.redisson.core.RCountDownLatch; import org.redisson.core.RCountDownLatch;
import org.redisson.core.RDeque; import org.redisson.core.RDeque;
import org.redisson.core.RGeo;
import org.redisson.core.RHyperLogLog; import org.redisson.core.RHyperLogLog;
import org.redisson.core.RKeys; import org.redisson.core.RKeys;
import org.redisson.core.RLexSortedSet; import org.redisson.core.RLexSortedSet;
@ -65,6 +68,24 @@ import org.redisson.core.RTopic;
*/ */
public interface RedissonClient { public interface RedissonClient {
/**
* Returns geospatial items holder instance by <code>name</code>.
*
* @param name
* @return
*/
<V> RGeo<V> getGeo(String name);
/**
* Returns geospatial items holder instance by <code>name</code>
* using provided codec for geospatial members.
*
* @param name
* @param geospatial member codec
* @return
*/
<V> RGeo<V> getGeo(String name, Codec codec);
/** /**
* Returns set-based cache instance by <code>name</code>. * Returns set-based cache instance by <code>name</code>.
* Supports value eviction with a given TTL value. * Supports value eviction with a given TTL value.
@ -132,57 +153,43 @@ public interface RedissonClient {
<V> RBucket<V> getBucket(String name, Codec codec); <V> RBucket<V> getBucket(String name, Codec codec);
/** /**
* <p>Returns a list of object holder instances by a key pattern. * Returns interface for mass operations with Bucket objects.
* *
* <pre>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</pre>
* <p>Use \ to escape special characters if you want to match them verbatim.
*
* <p>Uses <code>KEYS</code> Redis command.
*
* @param pattern
* @return * @return
*/ */
<V> List<RBucket<V>> findBuckets(String pattern); RBuckets getBuckets();
/** /**
* <p>Returns Redis object mapped by key. Result Map is not contains * Returns interface for mass operations with Bucket objects
* key-value entry for null values. * using provided codec for object.
*
* <p>Uses <code>MGET</code> Redis command.
* *
* @param keys
* @return * @return
*/ */
<V> Map<String, V> loadBucketValues(Collection<String> keys); RBuckets getBuckets(Codec codec);
/** /**
* <p>Returns Redis object mapped by key. Result Map is not contains * Use {@link RBuckets#find(String)}
* key-value entry for null values.
*
* <p>Uses <code>MGET</code> Redis command.
*
* @param keys
* @return
*/ */
<V> Map<String, V> loadBucketValues(String ... keys); @Deprecated
<V> List<RBucket<V>> findBuckets(String pattern);
/** /**
* Saves Redis object mapped by key. * Use {@link RBuckets#get(String...)}
*
* @param buckets
*/ */
void saveBuckets(Map<String, ?> buckets); @Deprecated
<V> Map<String, V> loadBucketValues(Collection<String> keys);
/**
* Use {@link RBuckets#get(String...)}
*/
@Deprecated
<V> Map<String, V> loadBucketValues(String ... keys);
/** /**
* Use {@link #findBuckets(String)} * Use {@link RBuckets#set(Map)}
*/ */
@Deprecated @Deprecated
<V> List<RBucket<V>> getBuckets(String pattern); void saveBuckets(Map<String, ?> buckets);
/** /**
* Returns HyperLogLog instance by name. * Returns HyperLogLog instance by name.
@ -587,12 +594,20 @@ public interface RedissonClient {
RScript getScript(); RScript getScript();
/** /**
* Returns object for remote operations * Returns object for remote operations prefixed with the default name (redisson_remote_service)
* *
* @return * @return
*/ */
RRemoteService getRemoteSerivce(); 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 * Return batch object which executes group of
* command in pipeline. * command in pipeline.
@ -613,9 +628,25 @@ public interface RedissonClient {
/** /**
* Shuts down Redisson instance <b>NOT</b> Redis server * Shuts down Redisson instance <b>NOT</b> Redis server
*
* This equates to invoke shutdown(2, 15, TimeUnit.SECONDS);
*/ */
void shutdown(); void shutdown();
/**
* Shuts down Redisson instance <b>NOT</b> Redis server
*
* Shutdown ensures that no tasks are submitted for <i>'the quiet period'</i>
* (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 * Allows to get configuration provided
* during Redisson instance creation. Further changes on * during Redisson instance creation. Further changes on
@ -639,18 +670,6 @@ public interface RedissonClient {
*/ */
NodesGroup<ClusterNode> getClusterNodesGroup(); NodesGroup<ClusterNode> 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. * Returns {@code true} if this Redisson instance has been shut down.
* *

@ -53,7 +53,7 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown
public void await() throws InterruptedException { public void await() throws InterruptedException {
Future<RedissonCountDownLatchEntry> promise = subscribe(); Future<RedissonCountDownLatchEntry> promise = subscribe();
try { try {
promise.await(); get(promise);
while (getCount() > 0) { while (getCount() > 0) {
// waiting for open state // waiting for open state
@ -71,7 +71,7 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown
public boolean await(long time, TimeUnit unit) throws InterruptedException { public boolean await(long time, TimeUnit unit) throws InterruptedException {
Future<RedissonCountDownLatchEntry> promise = subscribe(); Future<RedissonCountDownLatchEntry> promise = subscribe();
try { try {
if (!promise.await(time, unit)) { if (!await(promise, time, unit)) {
return false; return false;
} }

@ -58,12 +58,12 @@ abstract class RedissonExpirable extends RedissonObject implements RExpirable {
@Override @Override
public boolean expireAt(Date timestamp) { public boolean expireAt(Date timestamp) {
return expireAt(timestamp.getTime() / 1000); return expireAt(timestamp.getTime());
} }
@Override @Override
public Future<Boolean> expireAtAsync(Date timestamp) { public Future<Boolean> expireAtAsync(Date timestamp) {
return expireAtAsync(timestamp.getTime() / 1000); return expireAtAsync(timestamp.getTime());
} }
@Override @Override

@ -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<V> extends RedissonExpirable implements RGeo<V> {
MultiDecoder<Map<Object, Object>> postitionDecoder;
MultiDecoder<Map<Object, Object>> 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<Long> 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<Long> addAsync(GeoEntry... entries) {
List<Object> params = new ArrayList<Object>(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<Double> distAsync(V firstMember, V secondMember, GeoUnit geoUnit) {
return commandExecutor.readAsync(getName(), new ScoredCodec(codec), RedisCommands.GEODIST, getName(), firstMember, secondMember, geoUnit);
}
@Override
public Map<V, String> hash(V... members) {
return get(hashAsync(members));
}
@Override
public Future<Map<V, String>> hashAsync(V... members) {
List<Object> params = new ArrayList<Object>(members.length + 1);
params.add(getName());
params.addAll(Arrays.asList(members));
RedisCommand<Map<Object, Object>> command = new RedisCommand<Map<Object, Object>>("GEOHASH", new MapGetAllDecoder(params, 1), 2, ValueType.OBJECTS);
return commandExecutor.readAsync(getName(), new ScoredCodec(codec), command, params.toArray());
}
@Override
public Map<V, GeoPosition> pos(V... members) {
return get(posAsync(members));
}
@Override
public Future<Map<V, GeoPosition>> posAsync(V... members) {
List<Object> params = new ArrayList<Object>(members.length + 1);
params.add(getName());
params.addAll(Arrays.asList(members));
MultiDecoder<Map<Object, Object>> decoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoPositionMapDecoder(params), true);
RedisCommand<Map<Object, Object>> command = new RedisCommand<Map<Object, Object>>("GEOPOS", decoder, 2, ValueType.OBJECTS);
return commandExecutor.readAsync(getName(), new ScoredCodec(codec), command, params.toArray());
}
@Override
public List<V> radius(double longitude, double latitude, double radius, GeoUnit geoUnit) {
return get(radiusAsync(longitude, latitude, radius, geoUnit));
}
@Override
public Future<List<V>> 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<V, Double> radiusWithDistance(double longitude, double latitude, double radius, GeoUnit geoUnit) {
return get(radiusWithDistanceAsync(longitude, latitude, radius, geoUnit));
}
@Override
public Future<Map<V, Double>> radiusWithDistanceAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) {
RedisCommand<Map<Object, Object>> command = new RedisCommand<Map<Object, Object>>("GEORADIUS", distanceDecoder);
return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHDIST");
}
@Override
public Map<V, GeoPosition> radiusWithPosition(double longitude, double latitude, double radius, GeoUnit geoUnit) {
return get(radiusWithPositionAsync(longitude, latitude, radius, geoUnit));
}
@Override
public Future<Map<V, GeoPosition>> radiusWithPositionAsync(double longitude, double latitude, double radius, GeoUnit geoUnit) {
RedisCommand<Map<Object, Object>> command = new RedisCommand<Map<Object, Object>>("GEORADIUS", postitionDecoder);
return commandExecutor.readAsync(getName(), codec, command, getName(), convert(longitude), convert(latitude), radius, geoUnit, "WITHCOORD");
}
@Override
public List<V> radius(V member, double radius, GeoUnit geoUnit) {
return get(radiusAsync(member, radius, geoUnit));
}
@Override
public Future<List<V>> radiusAsync(V member, double radius, GeoUnit geoUnit) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.GEORADIUSBYMEMBER, getName(), member, radius, geoUnit);
}
@Override
public Map<V, Double> radiusWithDistance(V member, double radius, GeoUnit geoUnit) {
return get(radiusWithDistanceAsync(member, radius, geoUnit));
}
@Override
public Future<Map<V, Double>> 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<V, GeoPosition> radiusWithPosition(V member, double radius, GeoUnit geoUnit) {
return get(radiusWithPositionAsync(member, radius, geoUnit));
}
@Override
public Future<Map<V, GeoPosition>> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit) {
RedisCommand<Map<Object, Object>> command = new RedisCommand<Map<Object, Object>>("GEORADIUSBYMEMBER", postitionDecoder, 2);
return commandExecutor.readAsync(getName(), codec, command, getName(), member, radius, geoUnit, "WITHCOORD");
}
}

@ -15,6 +15,7 @@
*/ */
package org.redisson; package org.redisson;
import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -23,7 +24,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -90,53 +90,16 @@ public class RedissonKeys implements RKeys {
} }
private Iterator<String> createKeysIterator(final int slot, final String pattern) { private Iterator<String> createKeysIterator(final int slot, final String pattern) {
return new Iterator<String>() { return new RedissonBaseIterator<String>() {
private List<String> firstValues;
private Iterator<String> iter;
private long iterPos;
private boolean removeExecuted;
private String value;
@Override
public boolean hasNext() {
if (iter == null || !iter.hasNext()) {
ListScanResult<String> 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();
}
@Override @Override
public String next() { ListScanResult<String> iterator(InetSocketAddress client, long nextIterPos) {
if (!hasNext()) { return RedissonKeys.this.scanIterator(slot, nextIterPos, pattern);
throw new NoSuchElementException("No such element");
}
value = iter.next();
removeExecuted = false;
return value;
} }
@Override @Override
public void remove() { void remove(String value) {
if (removeExecuted) { RedissonKeys.this.delete(value);
throw new IllegalStateException("Element been already deleted");
}
if (iter == null) {
throw new IllegalStateException();
}
iter.remove();
delete(value);
removeExecuted = true;
} }
}; };

@ -32,14 +32,29 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
super(StringCodec.INSTANCE, commandExecutor, name); super(StringCodec.INSTANCE, commandExecutor, name);
} }
@Override
public int removeRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
return removeRangeByLex(fromElement, fromInclusive, toElement, toInclusive);
}
@Override @Override
public int removeRangeByLex(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { 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 @Override
public int removeRangeHeadByLex(String toElement, boolean toInclusive) { public int removeRangeHeadByLex(String toElement, boolean toInclusive) {
return get(removeRangeHeadByLexAsync(toElement, toInclusive)); return get(removeRangeHeadAsync(toElement, toInclusive));
}
@Override
public Future<Integer> removeRangeHeadAsync(String toElement, boolean toInclusive) {
return removeRangeHeadByLexAsync(toElement, toInclusive);
} }
@Override @Override
@ -48,9 +63,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), "-", toValue); return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), "-", toValue);
} }
@Override
public int removeRangeTail(String fromElement, boolean fromInclusive) {
return removeRangeTailByLex(fromElement, fromInclusive);
}
@Override @Override
public int removeRangeTailByLex(String fromElement, boolean fromInclusive) { public int removeRangeTailByLex(String fromElement, boolean fromInclusive) {
return get(removeRangeTailByLexAsync(fromElement, fromInclusive)); return get(removeRangeTailAsync(fromElement, fromInclusive));
}
@Override
public Future<Integer> removeRangeTailAsync(String fromElement, boolean fromInclusive) {
return removeRangeTailByLexAsync(fromElement, fromInclusive);
} }
@Override @Override
@ -59,6 +84,12 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, "+"); return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, "+");
} }
@Override
public Future<Integer> removeRangeAsync(String fromElement, boolean fromInclusive, String toElement,
boolean toInclusive) {
return removeRangeByLexAsync(fromElement, fromInclusive, toElement, toInclusive);
}
@Override @Override
public Future<Integer> removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { public Future<Integer> removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
String fromValue = value(fromElement, fromInclusive); String fromValue = value(fromElement, fromInclusive);
@ -67,14 +98,29 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, toValue); return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), fromValue, toValue);
} }
@Override
public Collection<String> range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
return lexRange(fromElement, fromInclusive, toElement, toInclusive);
}
@Override @Override
public Collection<String> lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { public Collection<String> 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<String> rangeHead(String toElement, boolean toInclusive) {
return lexRangeHead(toElement, toInclusive);
} }
@Override @Override
public Collection<String> lexRangeHead(String toElement, boolean toInclusive) { public Collection<String> lexRangeHead(String toElement, boolean toInclusive) {
return get(lexRangeHeadAsync(toElement, toInclusive)); return get(rangeHeadAsync(toElement, toInclusive));
}
@Override
public Future<Collection<String>> rangeHeadAsync(String toElement, boolean toInclusive) {
return lexRangeHeadAsync(toElement, toInclusive);
} }
@Override @Override
@ -83,9 +129,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue);
} }
@Override
public Collection<String> rangeTail(String fromElement, boolean fromInclusive) {
return lexRangeTail(fromElement, fromInclusive);
}
@Override @Override
public Collection<String> lexRangeTail(String fromElement, boolean fromInclusive) { public Collection<String> lexRangeTail(String fromElement, boolean fromInclusive) {
return get(lexRangeTailAsync(fromElement, fromInclusive)); return get(rangeTailAsync(fromElement, fromInclusive));
}
@Override
public Future<Collection<String>> rangeTailAsync(String fromElement, boolean fromInclusive) {
return lexRangeTailAsync(fromElement, fromInclusive);
} }
@Override @Override
@ -94,6 +150,11 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+"); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+");
} }
@Override
public Future<Collection<String>> rangeAsync(String fromElement, boolean fromInclusive, String toElement,
boolean toInclusive) {
return lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive);
}
@Override @Override
public Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { public Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
@ -103,14 +164,30 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, toValue); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, toValue);
} }
@Override
public Collection<String> range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive,
int offset, int count) {
return lexRange(fromElement, fromInclusive, toElement, toInclusive, offset, count);
}
@Override @Override
public Collection<String> lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) { public Collection<String> 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<String> rangeHead(String toElement, boolean toInclusive, int offset, int count) {
return lexRangeHead(toElement, toInclusive, offset, count);
} }
@Override @Override
public Collection<String> lexRangeHead(String toElement, boolean toInclusive, int offset, int count) { public Collection<String> 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<Collection<String>> rangeHeadAsync(String toElement, boolean toInclusive, int offset, int count) {
return lexRangeHeadAsync(toElement, toInclusive, offset, count);
} }
@Override @Override
@ -119,9 +196,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue, "LIMIT", offset, count); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue, "LIMIT", offset, count);
} }
@Override
public Collection<String> rangeTail(String fromElement, boolean fromInclusive, int offset, int count) {
return lexRangeTail(fromElement, fromInclusive, offset, count);
}
@Override @Override
public Collection<String> lexRangeTail(String fromElement, boolean fromInclusive, int offset, int count) { public Collection<String> 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<Collection<String>> rangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count) {
return lexRangeTailAsync(fromElement, fromInclusive, offset, count);
} }
@Override @Override
@ -130,6 +217,12 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+", "LIMIT", offset, count); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, "+", "LIMIT", offset, count);
} }
@Override
public Future<Collection<String>> rangeAsync(String fromElement, boolean fromInclusive, String toElement,
boolean toInclusive, int offset, int count) {
return lexRangeAsync(fromElement, fromInclusive, toElement, toInclusive, offset, count);
}
@Override @Override
public Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) { public Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) {
String fromValue = value(fromElement, fromInclusive); String fromValue = value(fromElement, fromInclusive);
@ -138,9 +231,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), fromValue, toValue, "LIMIT", offset, count); 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 @Override
public int lexCountTail(String fromElement, boolean fromInclusive) { public int lexCountTail(String fromElement, boolean fromInclusive) {
return get(lexCountTailAsync(fromElement, fromInclusive)); return get(countTailAsync(fromElement, fromInclusive));
}
@Override
public Future<Integer> countTailAsync(String fromElement, boolean fromInclusive) {
return lexCountTailAsync(fromElement, fromInclusive);
} }
@Override @Override
@ -150,9 +253,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), fromValue, "+"); return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), fromValue, "+");
} }
@Override
public int countHead(String toElement, boolean toInclusive) {
return lexCountHead(toElement, toInclusive);
}
@Override @Override
public int lexCountHead(String toElement, boolean toInclusive) { public int lexCountHead(String toElement, boolean toInclusive) {
return get(lexCountHeadAsync(toElement, toInclusive)); return get(countHeadAsync(toElement, toInclusive));
}
@Override
public Future<Integer> countHeadAsync(String toElement, boolean toInclusive) {
return lexCountHeadAsync(toElement, toInclusive);
} }
@Override @Override
@ -162,9 +275,20 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), "-", toValue); 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 @Override
public int lexCount(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) { 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<Integer> countAsync(String fromElement, boolean fromInclusive, String toElement,
boolean toInclusive) {
return lexCountAsync(fromElement, fromInclusive, toElement, toInclusive);
} }
@Override @Override
@ -192,6 +316,9 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
@Override @Override
public Future<Boolean> addAllAsync(Collection<? extends String> c) { public Future<Boolean> addAllAsync(Collection<? extends String> c) {
if (c.isEmpty()) {
return newSucceededFuture(false);
}
List<Object> params = new ArrayList<Object>(2*c.size()); List<Object> params = new ArrayList<Object>(2*c.size());
for (Object param : c) { for (Object param : c) {
params.add(0); params.add(0);
@ -210,4 +337,14 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return get(addAllAsync(c)); return get(addAllAsync(c));
} }
@Override
public Collection<String> range(int startIndex, int endIndex) {
return valueRange(startIndex, endIndex);
}
@Override
public Future<Collection<String>> rangeAsync(int startIndex, int endIndex) {
return valueRangeAsync(startIndex, endIndex);
}
} }

@ -143,13 +143,13 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local items = redis.call('lrange', KEYS[1], 0, -1) " + "local items = redis.call('lrange', KEYS[1], 0, -1) " +
"for i=1, #items do " + "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 " + "if items[i] == ARGV[j] then " +
"table.remove(ARGV, j) " + "table.remove(ARGV, j) " +
"end " + "end " +
"end " + "end " +
"end " + "end " +
"return table.getn(ARGV) == 0 and 1 or 0", "return #ARGV == 0 and 1 or 0",
Collections.<Object>singletonList(getName()), c.toArray()); Collections.<Object>singletonList(getName()), c.toArray());
} }
@ -222,7 +222,7 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
public Future<Boolean> removeAllAsync(Collection<?> c) { public Future<Boolean> removeAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local v = 0 " + "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 " + "if redis.call('lrem', KEYS[1], 0, ARGV[i]) == 1 "
+ "then v = 1 end " + "then v = 1 end "
+"end " +"end "
@ -246,11 +246,10 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
"local changed = 0 " + "local changed = 0 " +
"local items = redis.call('lrange', KEYS[1], 0, -1) " "local items = redis.call('lrange', KEYS[1], 0, -1) "
+ "local i = 1 " + "local i = 1 "
+ "local s = table.getn(items) " + "while i <= #items do "
+ "while i <= s do "
+ "local element = items[i] " + "local element = items[i] "
+ "local isInAgrs = false " + "local isInAgrs = false "
+ "for j = 0, table.getn(ARGV), 1 do " + "for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == element then " + "if ARGV[j] == element then "
+ "isInAgrs = true " + "isInAgrs = true "
+ "break " + "break "
@ -390,7 +389,7 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
"local key = KEYS[1] " + "local key = KEYS[1] " +
"local obj = ARGV[1] " + "local obj = ARGV[1] " +
"local items = redis.call('lrange', key, 0, -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 " + "if items[i] == obj then " +
"return i - 1 " + "return i - 1 " +
"end " + "end " +
@ -567,4 +566,24 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
return hashCode; return hashCode;
} }
@Override
public Future<Integer> addAfterAsync(V elementToFind, V element) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "AFTER", elementToFind, element);
}
@Override
public Future<Integer> 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));
}
} }

@ -131,7 +131,7 @@ public class RedissonListMultimapCache<K, V> extends RedissonListMultimap<K, V>
+ "end; " + "end; "
+ "if expireDate > tonumber(ARGV[1]) then " + + "if expireDate > tonumber(ARGV[1]) then " +
"local items = redis.call('lrange', KEYS[1], 0, -1); " + "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 " + "if items[i] == ARGV[3] then " +
"return 1; " + "return 1; " +
"end; " + "end; " +

@ -57,7 +57,7 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
private static final RedisCommand<Integer> LAST_INDEX = new RedisCommand<Integer>("EVAL", new IntegerReplayConvertor(), 4, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); private static final RedisCommand<Integer> LAST_INDEX = new RedisCommand<Integer>("EVAL", new IntegerReplayConvertor(), 4, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE));
private static final RedisCommand<Integer> EVAL_SIZE = new RedisCommand<Integer>("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY); private static final RedisCommand<Integer> EVAL_SIZE = new RedisCommand<Integer>("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY);
private static final RedisCommand<Integer> EVAL_GET = new RedisCommand<Integer>("EVAL", 7, ValueType.MAP_KEY); private static final RedisCommand<Integer> EVAL_GET = new RedisCommand<Integer>("EVAL", 7, ValueType.MAP_KEY);
private static final RedisCommand<Set<Object>> EVAL_READALL = new RedisCommand<Set<Object>>("EVAL", new ObjectSetReplayDecoder(), 6, ValueType.MAP_KEY); private static final RedisCommand<Set<Object>> EVAL_READALL = new RedisCommand<Set<Object>>("EVAL", new ObjectSetReplayDecoder<Object>(), 6, ValueType.MAP_KEY);
private static final RedisCommand<Boolean> EVAL_CONTAINS_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); private static final RedisCommand<Boolean> EVAL_CONTAINS_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE));
private static final RedisCommand<Boolean> EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS); private static final RedisCommand<Boolean> EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS);
@ -199,13 +199,13 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
+ "return 0;" + "return 0;"
+ "end; " + + "end; " +
"local items = redis.call('lrange', KEYS[2], 0, -1);" + "local items = redis.call('lrange', KEYS[2], 0, -1);" +
"for i = 0, #items, 1 do " + "for i = 1, #items, 1 do " +
"for j = 2, table.getn(ARGV), 1 do " "for j = 2, #ARGV, 1 do "
+ "if ARGV[j] == items[i] " + "if ARGV[j] == items[i] "
+ "then table.remove(ARGV, j) end " + "then table.remove(ARGV, j) end "
+ "end; " + "end; "
+ "end;" + "end;"
+ "return table.getn(ARGV) == 2 and 1 or 0; ", + "return #ARGV == 2 and 1 or 0; ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray()); Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
} }
@ -340,7 +340,7 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
"local changed = 0; " + "local changed = 0; " +
"local s = redis.call('lrange', KEYS[2], 0, -1); " "local s = redis.call('lrange', KEYS[2], 0, -1); "
+ "local i = 0; " + "local i = 1; "
+ "while i <= #s do " + "while i <= #s do "
+ "local element = s[i]; " + "local element = s[i]; "
+ "local isInAgrs = false; " + "local isInAgrs = false; "
@ -508,7 +508,7 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
+ "end; " + + "end; " +
"local items = redis.call('lrange', KEYS[1], 0, -1) " + "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 " + "if items[i] == ARGV[1] then " +
"return i - 1 " + "return i - 1 " +
"end " + "end " +
@ -685,4 +685,24 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
return hashCode; return hashCode;
} }
@Override
public Future<Integer> addAfterAsync(V elementToFind, V element) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "AFTER", elementToFind, element);
}
@Override
public Future<Integer> 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));
}
} }

@ -30,6 +30,8 @@ import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor; import org.redisson.command.CommandExecutor;
import org.redisson.core.RLock; import org.redisson.core.RLock;
import org.redisson.pubsub.LockPubSub; import org.redisson.pubsub.LockPubSub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.util.Timeout; import io.netty.util.Timeout;
import io.netty.util.TimerTask; import io.netty.util.TimerTask;
@ -49,6 +51,8 @@ import io.netty.util.internal.PlatformDependent;
*/ */
public class RedissonLock extends RedissonExpirable implements RLock { public class RedissonLock extends RedissonExpirable implements RLock {
private final Logger log = LoggerFactory.getLogger(RedissonLock.class);
public static final long LOCK_EXPIRATION_INTERVAL_SECONDS = 30; public static final long LOCK_EXPIRATION_INTERVAL_SECONDS = 30;
private static final ConcurrentMap<String, Timeout> expirationRenewalMap = PlatformDependent.newConcurrentHashMap(); private static final ConcurrentMap<String, Timeout> expirationRenewalMap = PlatformDependent.newConcurrentHashMap();
protected long internalLockLeaseTime = TimeUnit.SECONDS.toMillis(LOCK_EXPIRATION_INTERVAL_SECONDS); protected long internalLockLeaseTime = TimeUnit.SECONDS.toMillis(LOCK_EXPIRATION_INTERVAL_SECONDS);
@ -110,7 +114,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
Future<RedissonLockEntry> future = subscribe(); Future<RedissonLockEntry> future = subscribe();
future.sync(); get(future);
try { try {
while (true) { while (true) {
@ -155,7 +159,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
private Future<Long> tryLockInnerAsync(long threadId) { private Future<Long> tryLockInnerAsync(final long threadId) {
Future<Long> ttlRemaining = tryLockInnerAsync(LOCK_EXPIRATION_INTERVAL_SECONDS, TimeUnit.SECONDS, threadId); Future<Long> ttlRemaining = tryLockInnerAsync(LOCK_EXPIRATION_INTERVAL_SECONDS, TimeUnit.SECONDS, threadId);
ttlRemaining.addListener(new FutureListener<Long>() { ttlRemaining.addListener(new FutureListener<Long>() {
@Override @Override
@ -175,26 +179,39 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
private void scheduleExpirationRenewal() { private void scheduleExpirationRenewal() {
if (expirationRenewalMap.containsKey(getName())) { if (expirationRenewalMap.containsKey(getEntryName())) {
return; return;
} }
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override @Override
public void run(Timeout timeout) throws Exception { public void run(Timeout timeout) throws Exception {
expireAsync(internalLockLeaseTime, TimeUnit.MILLISECONDS); Future<Boolean> future = expireAsync(internalLockLeaseTime, TimeUnit.MILLISECONDS);
expirationRenewalMap.remove(getName()); future.addListener(new FutureListener<Boolean>() {
scheduleExpirationRenewal(); // reschedule itself @Override
public void operationComplete(Future<Boolean> 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); }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
if (expirationRenewalMap.putIfAbsent(getName(), task) != null) { if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
task.cancel(); task.cancel();
} }
} }
void cancelExpirationRenewal() { void cancelExpirationRenewal() {
Timeout task = expirationRenewalMap.remove(getName()); Timeout task = expirationRenewalMap.remove(getEntryName());
if (task != null) { if (task != null) {
task.cancel(); task.cancel();
} }
@ -229,7 +246,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
Future<RedissonLockEntry> future = subscribe(); Future<RedissonLockEntry> future = subscribe();
if (!future.await(time, TimeUnit.MILLISECONDS)) { if (!await(future, time, TimeUnit.MILLISECONDS)) {
future.addListener(new FutureListener<RedissonLockEntry>() { future.addListener(new FutureListener<RedissonLockEntry>() {
@Override @Override
public void operationComplete(Future<RedissonLockEntry> future) throws Exception { public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
@ -377,6 +394,11 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
public Future<Void> unlockAsync() { public Future<Void> unlockAsync() {
long threadId = Thread.currentThread().getId();
return unlockAsync(threadId);
}
public Future<Void> unlockAsync(final long threadId) {
final Promise<Void> result = newPromise(); final Promise<Void> result = newPromise();
Future<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, Future<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('exists', KEYS[1]) == 0) then " + "if (redis.call('exists', KEYS[1]) == 0) then " +
@ -396,7 +418,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
"return 1; "+ "return 1; "+
"end; " + "end; " +
"return nil;", "return nil;",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId())); Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
future.addListener(new FutureListener<Boolean>() { future.addListener(new FutureListener<Boolean>() {
@Override @Override
@ -409,7 +431,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
Boolean opStatus = future.getNow(); Boolean opStatus = future.getNow();
if (opStatus == null) { if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " 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); result.setFailure(cause);
return; return;
} }
@ -428,8 +450,12 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
public Future<Void> lockAsync(final long leaseTime, final TimeUnit unit) { public Future<Void> lockAsync(final long leaseTime, final TimeUnit unit) {
final Promise<Void> result = newPromise();
final long currentThreadId = Thread.currentThread().getId(); final long currentThreadId = Thread.currentThread().getId();
return lockAsync(leaseTime, unit, currentThreadId);
}
public Future<Void> lockAsync(final long leaseTime, final TimeUnit unit, final long currentThreadId) {
final Promise<Void> result = newPromise();
Future<Long> ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); Future<Long> ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId);
ttlFuture.addListener(new FutureListener<Long>() { ttlFuture.addListener(new FutureListener<Long>() {
@Override @Override
@ -525,8 +551,13 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
public Future<Boolean> tryLockAsync() { public Future<Boolean> tryLockAsync() {
long threadId = Thread.currentThread().getId();
return tryLockAsync(threadId);
}
public Future<Boolean> tryLockAsync(long threadId) {
final Promise<Boolean> result = newPromise(); final Promise<Boolean> result = newPromise();
Future<Long> future = tryLockInnerAsync(Thread.currentThread().getId()); Future<Long> future = tryLockInnerAsync(threadId);
future.addListener(new FutureListener<Long>() { future.addListener(new FutureListener<Long>() {
@Override @Override
public void operationComplete(Future<Long> future) throws Exception { public void operationComplete(Future<Long> future) throws Exception {
@ -546,10 +577,15 @@ public class RedissonLock extends RedissonExpirable implements RLock {
} }
public Future<Boolean> tryLockAsync(final long waitTime, final long leaseTime, final TimeUnit unit) { public Future<Boolean> tryLockAsync(final long waitTime, final long leaseTime, final TimeUnit unit) {
final long currentThreadId = Thread.currentThread().getId();
return tryLockAsync(waitTime, leaseTime, unit, currentThreadId);
}
public Future<Boolean> tryLockAsync(final long waitTime, final long leaseTime, final TimeUnit unit,
final long currentThreadId) {
final Promise<Boolean> result = newPromise(); final Promise<Boolean> result = newPromise();
final AtomicLong time = new AtomicLong(unit.toMillis(waitTime)); final AtomicLong time = new AtomicLong(unit.toMillis(waitTime));
final long currentThreadId = Thread.currentThread().getId();
Future<Long> ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); Future<Long> ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId);
ttlFuture.addListener(new FutureListener<Long>() { ttlFuture.addListener(new FutureListener<Long>() {
@Override @Override

@ -95,7 +95,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override @Override
public Future<Boolean> containsKeyAsync(Object key) { public Future<Boolean> containsKeyAsync(Object key) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.HEXISTS, getName(), key); return commandExecutor.readAsync(getName(key), codec, RedisCommands.HEXISTS, getName(key), key);
} }
@Override @Override
@ -107,7 +107,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
public Future<Boolean> containsValueAsync(Object value) { public Future<Boolean> containsValueAsync(Object value) {
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4), return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4),
"local s = redis.call('hvals', KEYS[1]);" + "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 " + "if ARGV[1] == s[i] then "
+ "return 1 " + "return 1 "
+ "end " + "end "
@ -130,7 +130,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
List<Object> args = new ArrayList<Object>(keys.size() + 1); List<Object> args = new ArrayList<Object>(keys.size() + 1);
args.add(getName()); args.add(getName());
args.addAll(keys); args.addAll(keys);
return commandExecutor.readAsync(getName(), codec, new RedisCommand<Map<Object, Object>>("HMGET", new MapGetAllDecoder(args), 2, ValueType.MAP_KEY, ValueType.MAP_VALUE), args.toArray()); return commandExecutor.readAsync(getName(), codec, new RedisCommand<Map<Object, Object>>("HMGET", new MapGetAllDecoder(args, 1), 2, ValueType.MAP_KEY, ValueType.MAP_VALUE), args.toArray());
} }
@Override @Override
@ -226,13 +226,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override @Override
public Future<V> putIfAbsentAsync(K key, V value) { public Future<V> 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 " "if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then "
+ "return nil " + "return nil "
+ "else " + "else "
+ "return redis.call('hget', KEYS[1], ARGV[1]) " + "return redis.call('hget', KEYS[1], ARGV[1]) "
+ "end", + "end",
Collections.<Object>singletonList(getName()), key, value); Collections.<Object>singletonList(getName(key)), key, value);
} }
@Override @Override
@ -242,7 +242,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override @Override
public Future<Boolean> fastPutIfAbsentAsync(K key, V value) { public Future<Boolean> 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 @Override
@ -252,13 +252,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override @Override
public Future<Long> removeAsync(Object key, Object value) { public Future<Long> 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 " "if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "
+ "return redis.call('hdel', KEYS[1], ARGV[1]) " + "return redis.call('hdel', KEYS[1], ARGV[1]) "
+ "else " + "else "
+ "return 0 " + "return 0 "
+ "end", + "end",
Collections.<Object>singletonList(getName()), key, value); Collections.<Object>singletonList(getName(key)), key, value);
} }
@Override @Override
@ -268,14 +268,14 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override @Override
public Future<Boolean> replaceAsync(K key, V oldValue, V newValue) { public Future<Boolean> 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 " "if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[1], ARGV[3]); "
+ "return 1; " + "return 1; "
+ "else " + "else "
+ "return 0; " + "return 0; "
+ "end", + "end",
Collections.<Object>singletonList(getName()), key, oldValue, newValue); Collections.<Object>singletonList(getName(key)), key, oldValue, newValue);
} }
@Override @Override
@ -285,7 +285,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override @Override
public Future<V> replaceAsync(K key, V value) { public Future<V> 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 " "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
+ "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
@ -293,36 +293,40 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
+ "else " + "else "
+ "return nil; " + "return nil; "
+ "end", + "end",
Collections.<Object>singletonList(getName()), key, value); Collections.<Object>singletonList(getName(key)), key, value);
} }
@Override @Override
public Future<V> getAsync(K key) { public Future<V> 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 @Override
public Future<V> putAsync(K key, V value) { public Future<V> 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]); " "local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
+ "return v", + "return v",
Collections.<Object>singletonList(getName()), key, value); Collections.<Object>singletonList(getName(key)), key, value);
} }
@Override @Override
public Future<V> removeAsync(K key) { public Future<V> 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]); " "local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "redis.call('hdel', KEYS[1], ARGV[1]); " + "redis.call('hdel', KEYS[1], ARGV[1]); "
+ "return v", + "return v",
Collections.<Object>singletonList(getName()), key); Collections.<Object>singletonList(getName(key)), key);
} }
@Override @Override
public Future<Boolean> fastPutAsync(K key, V value) { public Future<Boolean> 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 @Override
@ -347,8 +351,9 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
return get(fastRemoveAsync(keys)); return get(fastRemoveAsync(keys));
} }
MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(InetSocketAddress client, long startPos) { MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, InetSocketAddress client, long startPos) {
Future<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec), RedisCommands.HSCAN, getName(), startPos); Future<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f
= commandExecutor.readAsync(client, name, new ScanCodec(codec), RedisCommands.HSCAN, name, startPos);
return get(f); return get(f);
} }
@ -423,9 +428,9 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
public Future<V> addAndGetAsync(K key, Number value) { public Future<V> addAndGetAsync(K key, Number value) {
try { try {
byte[] keyState = codec.getMapKeyEncoder().encode(key); byte[] keyState = codec.getMapKeyEncoder().encode(key);
return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, return commandExecutor.writeAsync(getName(key), StringCodec.INSTANCE,
new RedisCommand<Object>("HINCRBYFLOAT", new NumberConvertor(value.getClass())), new RedisCommand<Object>("HINCRBYFLOAT", new NumberConvertor(value.getClass())),
getName(), keyState, new BigDecimal(value.toString()).toPlainString()); getName(key), keyState, new BigDecimal(value.toString()).toPlainString());
} catch (IOException e) { } catch (IOException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }

@ -18,6 +18,7 @@ package org.redisson;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; 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.BooleanReplayConvertor;
import org.redisson.client.protocol.convertor.LongReplayConvertor; import org.redisson.client.protocol.convertor.LongReplayConvertor;
import org.redisson.client.protocol.convertor.VoidReplayConvertor; 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.MapScanResult;
import org.redisson.client.protocol.decoder.MapScanResultReplayDecoder; import org.redisson.client.protocol.decoder.ObjectListDecoder;
import org.redisson.client.protocol.decoder.NestedMultiDecoder; 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.ObjectMapReplayDecoder;
import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor; import org.redisson.command.CommandAsyncExecutor;
@ -43,6 +49,7 @@ import org.redisson.connection.decoder.MapGetAllDecoder;
import org.redisson.core.RMapCache; import org.redisson.core.RMapCache;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/** /**
* <p>Map-based cache with ability to set TTL for each entry via * <p>Map-based cache with ability to set TTL for each entry via
@ -69,7 +76,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
static final RedisCommand<Object> EVAL_REPLACE = new RedisCommand<Object>("EVAL", 6, ValueType.MAP, ValueType.MAP_VALUE); static final RedisCommand<Object> EVAL_REPLACE = new RedisCommand<Object>("EVAL", 6, ValueType.MAP, ValueType.MAP_VALUE);
static final RedisCommand<Boolean> EVAL_REPLACE_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE, ValueType.MAP_VALUE)); static final RedisCommand<Boolean> EVAL_REPLACE_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE, ValueType.MAP_VALUE));
private static final RedisCommand<Void> EVAL_HMSET = new RedisCommand<Void>("EVAL", new VoidReplayConvertor(), 4, ValueType.MAP); private static final RedisCommand<Void> EVAL_HMSET = new RedisCommand<Void>("EVAL", new VoidReplayConvertor(), 4, ValueType.MAP);
private static final RedisCommand<MapScanResult<Object, Object>> EVAL_HSCAN = new RedisCommand<MapScanResult<Object, Object>>("EVAL", new NestedMultiDecoder(new ObjectMapReplayDecoder(), new MapScanResultReplayDecoder()), ValueType.MAP); private static final RedisCommand<MapCacheScanResult<Object, Object>> EVAL_HSCAN = new RedisCommand<MapCacheScanResult<Object, Object>>("EVAL", new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapReplayDecoder(), new ObjectListReplayDecoder()), ValueType.MAP);
private static final RedisCommand<Object> EVAL_REMOVE = new RedisCommand<Object>("EVAL", 4, ValueType.MAP_KEY, ValueType.MAP_VALUE); private static final RedisCommand<Object> EVAL_REMOVE = new RedisCommand<Object>("EVAL", 4, ValueType.MAP_KEY, ValueType.MAP_VALUE);
private static final RedisCommand<Long> EVAL_REMOVE_VALUE = new RedisCommand<Long>("EVAL", new LongReplayConvertor(), 5, ValueType.MAP); private static final RedisCommand<Long> EVAL_REMOVE_VALUE = new RedisCommand<Long>("EVAL", new LongReplayConvertor(), 5, ValueType.MAP);
private static final RedisCommand<Object> EVAL_PUT_TTL = new RedisCommand<Object>("EVAL", 9, ValueType.MAP, ValueType.MAP_VALUE); private static final RedisCommand<Object> EVAL_PUT_TTL = new RedisCommand<Object>("EVAL", 9, ValueType.MAP, ValueType.MAP_VALUE);
@ -162,16 +169,14 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
return newSucceededFuture(Collections.<K, V>emptyMap()); return newSucceededFuture(Collections.<K, V>emptyMap());
} }
List<Object> args = new ArrayList<Object>(keys.size() + 2); List<Object> args = new ArrayList<Object>(keys.size() + 1);
args.add(System.currentTimeMillis()); args.add(System.currentTimeMillis());
args.addAll(keys); args.addAll(keys);
return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Map<Object, Object>>("EVAL", new MapGetAllDecoder(args), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Map<Object, Object>>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE),
"local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" +
"local expireIdleHead = redis.call('zrange', KEYS[3], 0, 0, 'withscores');" + "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter
"local maxDate = table.remove(ARGV, 1); " // index is the first parameter + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime; "
+ "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= tonumber(maxDate); "
+ "local hasExpireIdle = #expireIdleHead == 2 and tonumber(expireIdleHead[2]) <= tonumber(maxDate); "
+ "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); " + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); "
+ "for i = #map, 1, -1 do " + "for i = #map, 1, -1 do "
+ "local value = map[i]; " + "local value = map[i]; "
@ -182,18 +187,18 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "if hasExpire then " + "if hasExpire then "
+ "local expireDate = redis.call('zscore', KEYS[2], key); " + "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; " + "map[i] = false; "
+ "end; " + "end; "
+ "end; " + "end; "
+ "if hasExpireIdle and t ~= 0 then " + "if t ~= 0 then "
+ "local expireIdle = redis.call('zscore', KEYS[3], key); " + "local expireIdle = redis.call('zscore', KEYS[3], key); "
+ "if expireIdle ~= false then " + "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); " + "local value = struct.pack('dLc0', t, string.len(val), val); "
+ "redis.call('hset', KEYS[1], key, value); " + "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 " + "else "
+ "map[i] = false; " + "map[i] = false; "
+ "end; " + "end; "
@ -525,10 +530,14 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
} }
@Override @Override
MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(InetSocketAddress client, long startPos) { MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, InetSocketAddress client, long startPos) {
Future<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.evalReadAsync(client, getName(), new ScanCodec(codec), EVAL_HSCAN, RedisCommand<MapCacheScanResult<Object, Object>> EVAL_HSCAN = new RedisCommand<MapCacheScanResult<Object, Object>>("EVAL",
new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new ScanCodec(codec)), new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()), ValueType.MAP);
Future<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.evalReadAsync(client, getName(), codec, EVAL_HSCAN,
"local result = {}; " "local result = {}; "
+ "local idleKeys = {}; "
+ "local res = redis.call('hscan', KEYS[1], ARGV[2]); " + "local res = redis.call('hscan', KEYS[1], ARGV[2]); "
+ "local currentTime = tonumber(ARGV[1]); "
+ "for i, value in ipairs(res[2]) do " + "for i, value in ipairs(res[2]) do "
+ "if i % 2 == 0 then " + "if i % 2 == 0 then "
+ "local key = res[2][i-1]; " + + "local key = res[2][i-1]; " +
@ -542,22 +551,61 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "if t ~= 0 then " + "if t ~= 0 then "
+ "local expireIdle = redis.call('zscore', KEYS[3], key); " + "local expireIdle = redis.call('zscore', KEYS[3], key); "
+ "if expireIdle ~= false then " + "if expireIdle ~= false then "
+ "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + "if tonumber(expireIdle) > currentTime and expireDate > currentTime then "
+ "local value = struct.pack('dLc0', t, string.len(val), val); " + "table.insert(idleKeys, key); "
+ "redis.call('hset', KEYS[1], key, value); "
+ "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); "
+ "end; " + "end; "
+ "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "expireDate = math.min(expireDate, tonumber(expireIdle)) "
+ "end; " + "end; "
+ "end; " + "end; "
+ "if expireDate > tonumber(ARGV[1]) then " + "if expireDate > currentTime then "
+ "table.insert(result, key); " + "table.insert(result, key); "
+ "table.insert(result, val); " + "table.insert(result, val); "
+ "end; " + "end; "
+ "end; " + "end; "
+ "end;" + "end;"
+ "return {res[1], result};", Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), startPos); + "return {res[1], result, idleKeys};", Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), startPos);
f.addListener(new FutureListener<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>>() {
@Override
public void operationComplete(Future<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>> future)
throws Exception {
if (future.isSuccess()) {
MapCacheScanResult<ScanObjectEntry, ScanObjectEntry> res = future.getNow();
if (res.getIdleKeys().isEmpty()) {
return;
}
List<Object> args = new ArrayList<Object>(res.getIdleKeys().size() + 1);
args.add(System.currentTimeMillis());
args.addAll(res.getIdleKeys());
commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Map<Object, Object>>("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.<Object>asList(getName(), getIdleSetName()), args.toArray());
}
}
});
return get(f); return get(f);
} }
@ -691,4 +739,73 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName())); Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()));
} }
@Override
public Future<Set<java.util.Map.Entry<K, V>>> 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.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis());
}
@Override
public Future<Collection<V>> 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.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis());
}
} }

@ -29,7 +29,7 @@ public class RedissonMapIterator<K, V, M> extends RedissonBaseMapIterator<K, V,
} }
protected MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator() { protected MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator() {
return map.scanIterator(client, iterPos); return map.scanIterator(map.getName(), client, nextIterPos);
} }
protected void removeKey() { protected void removeKey() {

@ -29,7 +29,7 @@ public class RedissonMultiMapKeysIterator<K, V, M> extends RedissonBaseMapIterat
} }
protected MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator() { protected MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator() {
return map.scanIterator(client, iterPos); return map.scanIterator(client, nextIterPos);
} }
protected void removeKey() { protected void removeKey() {

@ -22,13 +22,12 @@ import java.util.AbstractSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.LongCodec;
@ -167,24 +166,24 @@ public abstract class RedissonMultimap<K, V> extends RedissonExpirable implement
} }
try { try {
List<Object> args = new ArrayList<Object>(keys.length*2); List<Object> mapKeys = new ArrayList<Object>(keys.length);
List<Object> hashes = new ArrayList<Object>(); List<Object> listKeys = new ArrayList<Object>(keys.length + 1);
listKeys.add(getName());
for (K key : keys) { for (K key : keys) {
byte[] keyState = codec.getMapKeyEncoder().encode(key); byte[] keyState = codec.getMapKeyEncoder().encode(key);
args.add(keyState); mapKeys.add(keyState);
String keyHash = hash(keyState); String keyHash = hash(keyState);
String name = getValuesName(keyHash); String name = getValuesName(keyHash);
hashes.add(name); listKeys.add(name);
} }
args.addAll(hashes);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG, 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 " + "if res > 0 then " +
"redis.call('del', unpack(ARGV, #ARGV/2, #ARGV)); " + "redis.call('del', unpack(KEYS, 2, #KEYS)); " +
"end; " + "end; " +
"return res; ", "return res; ",
Collections.<Object>singletonList(getName()), args.toArray()); listKeys, mapKeys.toArray());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

@ -15,6 +15,8 @@
*/ */
package org.redisson; package org.redisson;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor; import org.redisson.command.CommandAsyncExecutor;
@ -45,16 +47,20 @@ abstract class RedissonObject implements RObject {
this(commandExecutor.getConnectionManager().getCodec(), commandExecutor, name); 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> V get(Future<V> future) { protected <V> V get(Future<V> future) {
return commandExecutor.get(future); return commandExecutor.get(future);
} }
protected <V> Promise<V> newPromise() { protected <V> Promise<V> newPromise() {
return commandExecutor.getConnectionManager().getGroup().next().<V>newPromise(); return commandExecutor.getConnectionManager().newPromise();
} }
protected <V> Future<V> newSucceededFuture(V result) { protected <V> Future<V> newSucceededFuture(V result) {
return commandExecutor.getConnectionManager().<V>newSucceededFuture(result); return commandExecutor.getConnectionManager().newSucceededFuture(result);
} }
@Override @Override

@ -18,17 +18,23 @@ package org.redisson;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.redisson.client.RedisException; import org.redisson.core.RBatch;
import org.redisson.client.RedisTimeoutException;
import org.redisson.core.MessageListener;
import org.redisson.core.RBlockingQueue; import org.redisson.core.RBlockingQueue;
import org.redisson.core.RBlockingQueueAsync;
import org.redisson.core.RRemoteService; 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.Logger;
import org.slf4j.LoggerFactory; 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.PlatformDependent;
import io.netty.util.internal.ThreadLocalRandom; import io.netty.util.internal.ThreadLocalRandom;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonRemoteService implements RRemoteService { public class RedissonRemoteService implements RRemoteService {
private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class);
@ -45,9 +56,15 @@ public class RedissonRemoteService implements RRemoteService {
private final Map<RemoteServiceKey, RemoteServiceMethod> beans = PlatformDependent.newConcurrentHashMap(); private final Map<RemoteServiceKey, RemoteServiceMethod> beans = PlatformDependent.newConcurrentHashMap();
private final Redisson redisson; private final Redisson redisson;
private final String name;
public RedissonRemoteService(Redisson redisson) { public RedissonRemoteService(Redisson redisson) {
this(redisson, "redisson_remote_service");
}
public RedissonRemoteService(Redisson redisson, String name) {
this.redisson = redisson; this.redisson = redisson;
this.name = name;
} }
@Override @Override
@ -69,7 +86,7 @@ public class RedissonRemoteService implements RRemoteService {
} }
for (int i = 0; i < executorsAmount; i++) { for (int i = 0; i < executorsAmount; i++) {
String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; String requestQueueName = name + ":{" + remoteInterface.getName() + "}";
RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName); RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName);
subscribe(remoteInterface, requestQueue); subscribe(remoteInterface, requestQueue);
} }
@ -81,27 +98,73 @@ public class RedissonRemoteService implements RRemoteService {
@Override @Override
public void operationComplete(Future<RemoteServiceRequest> future) throws Exception { public void operationComplete(Future<RemoteServiceRequest> future) throws Exception {
if (!future.isSuccess()) { if (!future.isSuccess()) {
if (future.cause() instanceof RedissonShutdownException) {
return;
}
// re-subscribe after a failed takeAsync
subscribe(remoteInterface, requestQueue);
return; return;
} }
RemoteServiceRequest request = future.getNow(); // do not subscribe now, see https://github.com/mrniko/redisson/issues/493
RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); // subscribe(remoteInterface, requestQueue);
String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId();
RTopic<RemoteServiceResponse> 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);
}
long clients = topic.publish(response); final RemoteServiceRequest request = future.getNow();
if (clients == 0) { if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) {
log.error("None of clients has not received a response: {} for request: {}", response, request); log.debug("request: {} has been skipped due to ackTimeout");
// re-subscribe after a skipped ackTimeout
subscribe(remoteInterface, requestQueue);
return;
} }
final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName()));
final String responseName = name + ":{" + remoteInterface.getName() + "}:" + request.getRequestId();
Future<List<?>> ackClientsFuture = send(request.getAckTimeout(), responseName, new RemoteServiceAck());
ackClientsFuture.addListener(new FutureListener<List<?>>() {
@Override
public void operationComplete(Future<List<?>> 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 <T> void invokeMethod(final Class<T> remoteInterface, final RBlockingQueue<RemoteServiceRequest> requestQueue, final RemoteServiceRequest request, RemoteServiceMethod method, String responseName) {
final AtomicReference<RemoteServiceResponse> responseHolder = new AtomicReference<RemoteServiceResponse>();
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<List<?>> clientsFuture = send(request.getResponseTimeout(), responseName, responseHolder.get());
clientsFuture.addListener(new FutureListener<List<?>>() {
@Override
public void operationComplete(Future<List<?>> 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); subscribe(remoteInterface, requestQueue);
} }
}); });
@ -109,47 +172,43 @@ public class RedissonRemoteService implements RRemoteService {
@Override @Override
public <T> T get(Class<T> remoteInterface) { public <T> T get(Class<T> remoteInterface) {
return get(remoteInterface, -1, null); return get(remoteInterface, 30, TimeUnit.SECONDS);
} }
@Override @Override
public <T> T get(final Class<T> remoteInterface, final int timeout, final TimeUnit timeUnit) { public <T> T get(final Class<T> remoteInterface, final long executionTimeout, final TimeUnit executionTimeUnit) {
return get(remoteInterface, executionTimeout, executionTimeUnit, 1, TimeUnit.SECONDS);
}
public <T> T get(final Class<T> remoteInterface, final long executionTimeout, final TimeUnit executionTimeUnit,
final long ackTimeout, final TimeUnit ackTimeUnit) {
InvocationHandler handler = new InvocationHandler() { InvocationHandler handler = new InvocationHandler() {
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String requestId = generateRequestId(); String requestId = generateRequestId();
String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}"; String requestQueueName = name + ":{" + remoteInterface.getName() + "}";
RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName); RBlockingQueue<RemoteServiceRequest> 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); requestQueue.add(request);
String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId; String responseName = name + ":{" + remoteInterface.getName() + "}:" + requestId;
final RTopic<RemoteServiceResponse> topic = redisson.getTopic(responseName); RBlockingQueue<RRemoteServiceResponse> responseQueue = redisson.getBlockingQueue(responseName);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<RemoteServiceResponse> response = new AtomicReference<RemoteServiceResponse>();
int listenerId = topic.addListener(new MessageListener<RemoteServiceResponse>() {
@Override
public void onMessage(String channel, RemoteServiceResponse msg) {
response.set(msg);
latch.countDown();
}
});
if (timeout == -1) { RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(ackTimeout, ackTimeUnit);
latch.await(); if (ack == null) {
} else { throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request);
if (!latch.await(timeout, timeUnit)) {
topic.removeListener(listenerId);
throw new RedisTimeoutException("No response after " + timeUnit.toMillis(timeout) + "ms for request: " + request);
}
} }
topic.removeListener(listenerId);
RemoteServiceResponse msg = response.get(); RemoteServiceResponse response = (RemoteServiceResponse) responseQueue.poll(executionTimeout, executionTimeUnit);
if (msg.getError() != null) { if (response == null) {
throw msg.getError(); 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); return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler);
@ -162,4 +221,12 @@ public class RedissonRemoteService implements RRemoteService {
return ByteBufUtil.hexDump(id); return ByteBufUtil.hexDump(id);
} }
private <T extends RRemoteServiceResponse> Future<List<?>> send(long timeout, String responseName, T response) {
RBatch batch = redisson.createBatch();
RBlockingQueueAsync<T> queue = batch.getBlockingQueue(responseName);
queue.putAsync(response);
queue.expireAsync(timeout, TimeUnit.MILLISECONDS);
return batch.executeAsync();
}
} }

@ -19,13 +19,13 @@ import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.codec.ScoredCodec; import org.redisson.client.codec.ScoredCodec;
@ -51,6 +51,16 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
super(codec, commandExecutor, name); super(codec, commandExecutor, name);
} }
@Override
public Collection<V> readAll() {
return get(readAllAsync());
}
@Override
public Future<Collection<V>> readAllAsync() {
return valueRangeAsync(0, -1);
}
@Override @Override
public V pollFirst() { public V pollFirst() {
return get(pollFirstAsync()); return get(pollFirstAsync());
@ -124,6 +134,9 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
@Override @Override
public Future<Long> addAllAsync(Map<V, Double> objects) { public Future<Long> addAllAsync(Map<V, Double> objects) {
if (objects.isEmpty()) {
return newSucceededFuture(0L);
}
List<Object> params = new ArrayList<Object>(objects.size()*2+1); List<Object> params = new ArrayList<Object>(objects.size()*2+1);
params.add(getName()); params.add(getName());
try { try {
@ -229,7 +242,7 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
} }
@Override @Override
public int rank(V o) { public Integer rank(V o) {
return get(rankAsync(o)); return get(rankAsync(o));
} }
@ -245,52 +258,16 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
@Override @Override
public Iterator<V> iterator() { public Iterator<V> iterator() {
return new Iterator<V>() { return new RedissonBaseIterator<V>() {
private List<V> firstValues;
private Iterator<V> iter;
private InetSocketAddress client;
private long iterPos;
private boolean removeExecuted;
private V value;
@Override
public boolean hasNext() {
if (iter == null || !iter.hasNext()) {
ListScanResult<V> 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();
}
@Override @Override
public V next() { ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
if (!hasNext()) { return scanIterator(client, nextIterPos);
throw new NoSuchElementException("No such element at index");
}
value = iter.next();
removeExecuted = false;
return value;
} }
@Override @Override
public void remove() { void remove(V value) {
if (removeExecuted) {
throw new IllegalStateException("Element been already deleted");
}
iter.remove();
RedissonScoredSortedSet.this.remove(value); RedissonScoredSortedSet.this.remove(value);
removeExecuted = true;
} }
}; };
@ -315,28 +292,32 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
@Override @Override
public Future<Boolean> containsAllAsync(Collection<?> c) { public Future<Boolean> containsAllAsync(Collection<?> c) {
if (c.isEmpty()) {
return newSucceededFuture(true);
}
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS),
"local s = redis.call('zrange', KEYS[1], 0, -1);" + "for j = 1, #ARGV, 1 do "
"for i = 0, table.getn(s), 1 do " + + "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[j]) "
"for j = 0, table.getn(ARGV), 1 do " + "if expireDateScore == false then "
+ "if ARGV[j] == s[i] " + "return 0;"
+ "then table.remove(ARGV, j) end " + "end; "
+ "end; " + "end; "
+ "end;" + "return 1; ",
+ "return table.getn(ARGV) == 0 and 1 or 0; ",
Collections.<Object>singletonList(getName()), c.toArray()); Collections.<Object>singletonList(getName()), c.toArray());
} }
@Override @Override
public Future<Boolean> removeAllAsync(Collection<?> c) { public Future<Boolean> removeAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), if (c.isEmpty()) {
"local v = 0 " + return newSucceededFuture(false);
"for i = 0, table.getn(ARGV), 1 do " }
+ "if redis.call('zrem', KEYS[1], ARGV[i]) == 1 "
+ "then v = 1 end " List<Object> params = new ArrayList<Object>(c.size()+1);
+"end " params.add(getName());
+ "return v ", params.addAll(c);
Collections.<Object>singletonList(getName()), c.toArray());
return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZREM, params.toArray());
} }
@Override @Override
@ -349,29 +330,33 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
return get(retainAllAsync(c)); return get(retainAllAsync(c));
} }
private byte[] encode(V value) {
try {
return codec.getValueEncoder().encode(value);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
@Override @Override
public Future<Boolean> retainAllAsync(Collection<?> c) { public Future<Boolean> retainAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), if (c.isEmpty()) {
"local changed = 0 " + return deleteAsync();
"local s = redis.call('zrange', KEYS[1], 0, -1) " }
+ "local i = 0 "
+ "while i <= table.getn(s) do " List<Object> params = new ArrayList<Object>(c.size()*2);
+ "local element = s[i] " for (Object object : c) {
+ "local isInAgrs = false " params.add(0);
+ "for j = 0, table.getn(ARGV), 1 do " params.add(encode((V)object));
+ "if ARGV[j] == element then " }
+ "isInAgrs = true "
+ "break " return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
+ "end " "redis.call('zadd', KEYS[2], unpack(ARGV)); "
+ "end " + "local prevSize = redis.call('zcard', KEYS[1]); "
+ "if isInAgrs == false then " + "local size = redis.call('zinterstore', KEYS[1], 2, KEYS[1], KEYS[2], 'aggregate', 'sum');"
+ "redis.call('zrem', KEYS[1], element) " + "redis.call('del', KEYS[2]); "
+ "changed = 1 " + "return size ~= prevSize and 1 or 0; ",
+ "end " Arrays.<Object>asList(getName(), "redisson_temp__{" + getName() + "}"), params.toArray());
+ "i = i + 1 "
+ "end "
+ "return changed ",
Collections.<Object>singletonList(getName()), c.toArray());
} }
@Override @Override
@ -414,7 +399,7 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
public Future<Collection<V>> valueRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { public Future<Collection<V>> valueRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) {
String startValue = value(startScore, startScoreInclusive); String startValue = value(startScore, startScoreInclusive);
String endValue = value(endScore, endScoreInclusive); 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 @Override
@ -453,7 +438,7 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
public Future<Collection<V>> valueRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { public Future<Collection<V>> valueRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) {
String startValue = value(startScore, startScoreInclusive); String startValue = value(startScore, startScoreInclusive);
String endValue = value(endScore, endScoreInclusive); 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 @Override
@ -473,6 +458,11 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
return get(entryRangeAsync(startScore, startScoreInclusive, endScore, endScoreInclusive, offset, count)); return get(entryRangeAsync(startScore, startScoreInclusive, endScore, endScoreInclusive, offset, count));
} }
@Override
public Collection<ScoredEntry<V>> entryRangeReversed(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) {
return get(entryRangeReversedAsync(startScore, startScoreInclusive, endScore, endScoreInclusive, offset, count));
}
@Override @Override
public Future<Collection<ScoredEntry<V>>> entryRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { public Future<Collection<ScoredEntry<V>>> entryRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) {
String startValue = value(startScore, startScoreInclusive); String startValue = value(startScore, startScoreInclusive);
@ -480,14 +470,31 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE_ENTRY, getName(), startValue, endValue, "WITHSCORES", "LIMIT", offset, count); return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE_ENTRY, getName(), startValue, endValue, "WITHSCORES", "LIMIT", offset, count);
} }
@Override
public Future<Collection<ScoredEntry<V>>> 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 @Override
public Future<Integer> revRankAsync(V o) { public Future<Integer> revRankAsync(V o) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.ZREVRANK_INT, getName(), o); return commandExecutor.readAsync(getName(), codec, RedisCommands.ZREVRANK_INT, getName(), o);
} }
@Override @Override
public int revRank(V o) { public Integer revRank(V o) {
return get(revRankAsync(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<Long> 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);
}
} }

@ -70,7 +70,8 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore {
return; return;
} }
Future<RedissonLockEntry> future = subscribe().sync(); Future<RedissonLockEntry> future = subscribe();
get(future);
try { try {
while (true) { while (true) {
if (tryAcquire(permits)) { if (tryAcquire(permits)) {
@ -113,7 +114,7 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore {
long time = unit.toMillis(waitTime); long time = unit.toMillis(waitTime);
Future<RedissonLockEntry> future = subscribe(); Future<RedissonLockEntry> future = subscribe();
if (!future.await(time, TimeUnit.MILLISECONDS)) { if (!await(future, time, TimeUnit.MILLISECONDS)) {
return false; return false;
} }

@ -19,14 +19,15 @@ import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import org.redisson.client.codec.Codec; 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.RedisCommands;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.command.CommandAsyncExecutor; import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RSet; import org.redisson.core.RSet;
@ -72,74 +73,30 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
@Override @Override
public Future<Boolean> containsAsync(Object o) { public Future<Boolean> 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<V> scanIterator(InetSocketAddress client, long startPos) { protected String getName(Object o) {
Future<ListScanResult<V>> f = commandExecutor.readAsync(client, getName(), codec, RedisCommands.SSCAN, getName(), startPos); return getName();
}
ListScanResult<V> scanIterator(String name, InetSocketAddress client, long startPos) {
Future<ListScanResult<V>> f = commandExecutor.readAsync(client, name, codec, RedisCommands.SSCAN, name, startPos);
return get(f); return get(f);
} }
@Override @Override
public Iterator<V> iterator() { public Iterator<V> iterator() {
return new Iterator<V>() { return new RedissonBaseIterator<V>() {
private List<V> firstValues;
private Iterator<V> iter;
private InetSocketAddress client;
private long nextIterPos;
private boolean currentElementRemoved;
private boolean removeExecuted;
private V value;
@Override @Override
public boolean hasNext() { ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
if (iter == null || !iter.hasNext()) { return scanIterator(getName(), client, nextIterPos);
if (nextIterPos == -1) {
return false;
}
long prevIterPos = nextIterPos;
ListScanResult<V> res = scanIterator(client, nextIterPos);
client = res.getRedisClient();
if (nextIterPos == 0 && firstValues == null) {
firstValues = res.getValues();
} else if (res.getValues().equals(firstValues)) {
return false;
}
iter = res.getValues().iterator();
nextIterPos = res.getPos();
if (prevIterPos == nextIterPos && !removeExecuted) {
nextIterPos = -1;
}
}
return iter.hasNext();
} }
@Override @Override
public V next() { void remove(V value) {
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();
RedissonSet.this.remove(value); RedissonSet.this.remove(value);
currentElementRemoved = true;
removeExecuted = true;
} }
}; };
@ -174,7 +131,7 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
@Override @Override
public Future<Boolean> addAsync(V e) { public Future<Boolean> 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 @Override
@ -189,7 +146,7 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
@Override @Override
public Future<Boolean> removeAsync(Object o) { public Future<Boolean> 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 @Override
@ -199,7 +156,7 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
@Override @Override
public Future<Boolean> moveAsync(String destination, V member) { public Future<Boolean> 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 @Override
@ -214,29 +171,29 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
@Override @Override
public Future<Boolean> containsAllAsync(Collection<?> c) { public Future<Boolean> containsAllAsync(Collection<?> c) {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, if (c.isEmpty()) {
"local s = redis.call('smembers', KEYS[1]);" + return newSucceededFuture(true);
"for i = 0, table.getn(s), 1 do " + }
"for j = 0, table.getn(ARGV), 1 do "
+ "if ARGV[j] == s[i] " return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS),
+ "then table.remove(ARGV, j) end " "redis.call('sadd', KEYS[2], unpack(ARGV)); "
+ "end; " + "local size = redis.call('sdiff', KEYS[2], KEYS[1]);"
+ "end;" + "redis.call('del', KEYS[2]); "
+ "return table.getn(ARGV) == 0 and 1 or 0; ", + "return #size == 0 and 1 or 0; ",
Collections.<Object>singletonList(getName()), c.toArray()); Arrays.<Object>asList(getName(), "redisson_temp__{" + getName() + "}"), c.toArray());
} }
@Override @Override
public boolean addAll(Collection<? extends V> c) { public boolean addAll(Collection<? extends V> c) {
if (c.isEmpty()) {
return false;
}
return get(addAllAsync(c)); return get(addAllAsync(c));
} }
@Override @Override
public Future<Boolean> addAllAsync(Collection<? extends V> c) { public Future<Boolean> addAllAsync(Collection<? extends V> c) {
if (c.isEmpty()) {
return newSucceededFuture(false);
}
List<Object> args = new ArrayList<Object>(c.size() + 1); List<Object> args = new ArrayList<Object>(c.size() + 1);
args.add(getName()); args.add(getName());
args.addAll(c); args.addAll(c);
@ -250,39 +207,29 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
@Override @Override
public Future<Boolean> retainAllAsync(Collection<?> c) { public Future<Boolean> retainAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, if (c.isEmpty()) {
"local changed = 0 " + return deleteAsync();
"local s = redis.call('smembers', KEYS[1]) " }
+ "local i = 0 " return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS),
+ "while i <= table.getn(s) do " "redis.call('sadd', KEYS[2], unpack(ARGV)); "
+ "local element = s[i] " + "local prevSize = redis.call('scard', KEYS[1]); "
+ "local isInAgrs = false " + "local size = redis.call('sinterstore', KEYS[1], KEYS[1], KEYS[2]);"
+ "for j = 0, table.getn(ARGV), 1 do " + "redis.call('del', KEYS[2]); "
+ "if ARGV[j] == element then " + "return size ~= prevSize and 1 or 0; ",
+ "isInAgrs = true " Arrays.<Object>asList(getName(), "redisson_temp__{" + getName() + "}"), c.toArray());
+ "break "
+ "end "
+ "end "
+ "if isInAgrs == false then "
+ "redis.call('SREM', KEYS[1], element) "
+ "changed = 1 "
+ "end "
+ "i = i + 1 "
+ "end "
+ "return changed ",
Collections.<Object>singletonList(getName()), c.toArray());
} }
@Override @Override
public Future<Boolean> removeAllAsync(Collection<?> c) { public Future<Boolean> removeAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, if (c.isEmpty()) {
"local v = 0 " + return newSucceededFuture(false);
"for i = 0, table.getn(ARGV), 1 do " }
+ "if redis.call('srem', KEYS[1], ARGV[i]) == 1 "
+ "then v = 1 end " List<Object> args = new ArrayList<Object>(c.size() + 1);
+"end " args.add(getName());
+ "return v ", args.addAll(c);
Collections.<Object>singletonList(getName()), c.toArray());
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SREM_SINGLE, args.toArray());
} }
@Override @Override
@ -316,9 +263,78 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SUNION, args.toArray()); return commandExecutor.writeAsync(getName(), codec, RedisCommands.SUNION, args.toArray());
} }
@Override
public int diff(String... names) {
return get(diffAsync(names));
}
@Override
public Future<Integer> diffAsync(String... names) {
List<Object> args = new ArrayList<Object>(names.length + 1);
args.add(getName());
args.addAll(Arrays.asList(names));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SDIFFSTORE_INT, args.toArray());
}
@Override
public Set<V> readDiff(String... names) {
return get(readDiffAsync(names));
}
@Override
public Future<Set<V>> readDiffAsync(String... names) {
List<Object> args = new ArrayList<Object>(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<Integer> intersectionAsync(String... names) {
List<Object> args = new ArrayList<Object>(names.length + 1);
args.add(getName());
args.addAll(Arrays.asList(names));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTERSTORE_INT, args.toArray());
}
@Override
public Set<V> readIntersection(String... names) {
return get(readIntersectionAsync(names));
}
@Override
public Future<Set<V>> readIntersectionAsync(String... names) {
List<Object> args = new ArrayList<Object>(names.length + 1);
args.add(getName());
args.addAll(Arrays.asList(names));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTER, args.toArray());
}
@Override @Override
public void clear() { public void clear() {
delete(); delete();
} }
@Override
public String toString() {
Iterator<V> 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(' ');
}
}
} }

@ -17,40 +17,30 @@ package org.redisson;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand; import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor; 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.ListScanResult;
import org.redisson.command.CommandAsyncExecutor; import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RSetCache; 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 io.netty.util.concurrent.Future;
import net.openhft.hashing.LongHashFunction;
/** /**
* <p>Set-based cache with ability to set TTL for each entry via * <p>Set-based cache with ability to set TTL for each entry via
* {@link #put(Object, Object, long, TimeUnit)} method. * {@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.
* </p> * </p>
* *
* <p>Current Redis implementation doesn't have set entry eviction functionality. * <p>Current Redis implementation doesn't have set entry eviction functionality.
@ -69,17 +59,14 @@ import net.openhft.hashing.LongHashFunction;
*/ */
public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<V> { public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<V> {
private static final RedisCommand<Void> ADD_ALL = new RedisCommand<Void>("HMSET", new VoidReplayConvertor()); public RedissonSetCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) {
private static final RedisStrictCommand<Boolean> HDEL = new RedisStrictCommand<Boolean>("HDEL", new BooleanReplayConvertor());
protected RedissonSetCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, 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); super(codec, commandExecutor, name);
evictionScheduler.schedule(getName(), getTimeoutSetName()); evictionScheduler.schedule(getName());
} }
@Override @Override
@ -89,7 +76,7 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override @Override
public Future<Integer> sizeAsync() { public Future<Integer> sizeAsync() {
return commandExecutor.readAsync(getName(), codec, RedisCommands.HLEN, getName()); return commandExecutor.readAsync(getName(), codec, RedisCommands.ZCARD_INT, getName());
} }
@Override @Override
@ -102,126 +89,54 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
return get(containsAsync(o)); 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 @Override
public Future<Boolean> containsAsync(Object o) { public Future<Boolean> containsAsync(Object o) {
byte[] key = hash(o); return commandExecutor.evalReadAsync(getName(), codec, new RedisStrictCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5),
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, "local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " +
"local value = redis.call('hexists', KEYS[1], ARGV[2]); " + "if expireDateScore ~= false then " +
"if value == 1 then " + "if tonumber(expireDateScore) <= tonumber(ARGV[1]) then " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " "return 0;" +
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then " "else " +
+ "return 0;" "return 1;" +
+ "end; " + "end;" +
"end;" + "else " +
"return value; ", "return 0;" +
Arrays.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), key); "end; ",
Arrays.<Object>asList(getName()), System.currentTimeMillis(), o);
} }
ListScanResult<V> scanIterator(InetSocketAddress client, long startPos) { ListScanResult<V> scanIterator(InetSocketAddress client, long startPos) {
Future<ListScanResult<V>> f = commandExecutor.evalReadAsync(client, getName(), codec, RedisCommands.EVAL_SSCAN, Future<ListScanResult<V>> f = scanIteratorAsync(client, startPos);
return get(f);
}
public Future<ListScanResult<V>> scanIteratorAsync(InetSocketAddress client, long startPos) {
return commandExecutor.evalReadAsync(client, getName(), codec, RedisCommands.EVAL_ZSCAN,
"local result = {}; " "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 " + "for i, value in ipairs(res[2]) do "
+ "if i % 2 == 0 then " + "if i % 2 == 0 then "
+ "local key = res[2][i-1]; " + "local expireDate = value; "
+ "local expireDate = redis.call('zscore', KEYS[2], key); " + "if tonumber(expireDate) > tonumber(ARGV[2]) then "
+ "if (expireDate == false) or (expireDate ~= false and tonumber(expireDate) > tonumber(ARGV[2])) then " + "table.insert(result, res[2][i-1]); "
+ "table.insert(result, value); "
+ "end; " + "end; "
+ "end; " + "end;"
+ "end;" + "end;"
+ "return {res[1], result};", Arrays.<Object>asList(getName(), getTimeoutSetName()), startPos, System.currentTimeMillis()); + "return {res[1], result};", Arrays.<Object>asList(getName()), startPos, System.currentTimeMillis());
return get(f);
} }
@Override @Override
public Iterator<V> iterator() { public Iterator<V> iterator() {
return new Iterator<V>() { return new RedissonBaseIterator<V>() {
private List<V> firstValues;
private Iterator<V> iter;
private InetSocketAddress client;
private long nextIterPos;
private boolean currentElementRemoved;
private boolean removeExecuted;
private V value;
@Override @Override
public boolean hasNext() { ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
if (iter == null || !iter.hasNext()) { return scanIterator(client, nextIterPos);
if (nextIterPos == -1) {
return false;
}
long prevIterPos = nextIterPos;
ListScanResult<V> res = scanIterator(client, nextIterPos);
client = res.getRedisClient();
if (nextIterPos == 0 && firstValues == null) {
firstValues = res.getValues();
} else if (res.getValues().equals(firstValues)) {
return false;
}
iter = res.getValues().iterator();
nextIterPos = res.getPos();
if (prevIterPos == nextIterPos && !removeExecuted) {
nextIterPos = -1;
}
}
return iter.hasNext();
} }
@Override @Override
public V next() { void remove(V value) {
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();
RedissonSetCache.this.remove(value); RedissonSetCache.this.remove(value);
currentElementRemoved = true;
removeExecuted = true;
} }
}; };
@ -234,49 +149,16 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override @Override
public Future<Set<V>> readAllAsync() { public Future<Set<V>> readAllAsync() {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_SET, return (Future<Set<V>>)readAllAsync(RedisCommands.ZRANGEBYSCORE);
"local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + }
"local keys = redis.call('hkeys', KEYS[1]); "
+ "if #keys == 0 then " private Future<?> readAllAsync(RedisCommand<? extends Collection<?>> command) {
+ "return {}; " return commandExecutor.readAsync(getName(), codec, command, getName(), System.currentTimeMillis(), 92233720368547758L);
+ "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.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis());
} }
private Future<List<Object>> readAllasListAsync() { private Future<List<Object>> readAllasListAsync() {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LIST, return (Future<List<Object>>)readAllAsync(RedisCommands.ZRANGEBYSCORE_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.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis());
} }
@Override @Override
@ -314,49 +196,35 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
throw new NullPointerException("TimeUnit param can't be null"); 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.<Object>asList(getName()), System.currentTimeMillis(), timeoutDate, objectState);
}
private byte[] encode(V value) {
try { try {
byte[] objectState = encode(value); return codec.getValueEncoder().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.<Object>asList(getName(), getTimeoutSetName()), timeoutDate, objectState, key);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new IllegalArgumentException(e);
} }
} }
private byte[] encode(V value) throws IOException {
return codec.getValueEncoder().encode(value);
}
@Override @Override
public Future<Boolean> addAsync(V value) { public Future<Boolean> addAsync(V value) {
try { return addAsync(value, 92233720368547758L - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
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.<Object>asList(getName()), key, objectState);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
@Override @Override
public Future<Boolean> removeAsync(Object o) { public Future<Boolean> removeAsync(Object o) {
byte[] key = hash(o); return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZREM, getName(), o);
return commandExecutor.writeAsync(getName(), codec, HDEL, getName(), key);
} }
@Override @Override
@ -371,17 +239,27 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override @Override
public Future<Boolean> containsAllAsync(Collection<?> c) { public Future<Boolean> containsAllAsync(Collection<?> c) {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES, if (c.isEmpty()) {
"local s = redis.call('hvals', KEYS[1]);" + return newSucceededFuture(true);
"for i = 0, table.getn(s), 1 do " + }
"for j = 0, table.getn(ARGV), 1 do "
+ "if ARGV[j] == s[i] then " List<Object> params = new ArrayList<Object>(c.size() + 1);
+ "table.remove(ARGV, j) " params.add(System.currentTimeMillis());
+ "end " params.addAll(c);
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("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; "
+ "end;" + "return 1; ",
+ "return table.getn(ARGV) == 0 and 1 or 0; ", Collections.<Object>singletonList(getName()), params.toArray());
Collections.<Object>singletonList(getName()), c.toArray());
} }
@Override @Override
@ -395,20 +273,16 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
return newSucceededFuture(false); return newSucceededFuture(false);
} }
long score = 92233720368547758L - System.currentTimeMillis();
List<Object> params = new ArrayList<Object>(c.size()*2 + 1); List<Object> params = new ArrayList<Object>(c.size()*2 + 1);
params.add(getName()); params.add(getName());
try { for (V value : c) {
for (V value : c) { byte[] objectState = encode(value);
byte[] objectState = encode(value); params.add(score);
byte[] key = hash(objectState); params.add(objectState);
params.add(key);
params.add(objectState);
}
} catch (IOException e) {
throw new RuntimeException(e);
} }
return commandExecutor.writeAsync(getName(), codec, ADD_ALL, params.toArray()); return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZADD_BOOL_RAW, params.toArray());
} }
@Override @Override
@ -418,48 +292,37 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override @Override
public Future<Boolean> retainAllAsync(Collection<?> c) { public Future<Boolean> retainAllAsync(Collection<?> c) {
List<byte[]> params = new ArrayList<byte[]>(c.size()); if (c.isEmpty()) {
return deleteAsync();
}
long score = 92233720368547758L - System.currentTimeMillis();
List<Object> params = new ArrayList<Object>(c.size()*2);
for (Object object : c) { for (Object object : c) {
params.add(hash(object)); params.add(score);
params.add(encode((V)object));
} }
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"local keys = redis.call('hkeys', KEYS[1]); " + "redis.call('zadd', KEYS[2], unpack(ARGV)); "
"local i=1;" + + "local prevSize = redis.call('zcard', KEYS[1]); "
"while i <= #keys do " + "local size = redis.call('zinterstore', KEYS[1], #ARGV/2, KEYS[1], KEYS[2], 'aggregate', 'min');"
+ "local changed = false;" + "redis.call('del', KEYS[2]); "
+ "local element = keys[i];" + "return size ~= prevSize and 1 or 0; ",
+ "for j, argElement in pairs(ARGV) do " Arrays.<Object>asList(getName(), "redisson_temp__{" + getName() + "}"), params.toArray());
+ "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.<Object>asList(getName(), getTimeoutSetName()), params.toArray());
} }
@Override @Override
public Future<Boolean> removeAllAsync(Collection<?> c) { public Future<Boolean> removeAllAsync(Collection<?> c) {
if (c.isEmpty()) {
return newSucceededFuture(false);
}
List<Object> params = new ArrayList<Object>(c.size()+1); List<Object> params = new ArrayList<Object>(c.size()+1);
params.add(getName()); params.add(getName());
for (Object object : c) { params.addAll(c);
params.add(hash(object));
}
return commandExecutor.writeAsync(getName(), codec, HDEL, params.toArray()); return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZREM, params.toArray());
} }
@Override @Override
@ -472,36 +335,4 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
delete(); delete();
} }
@Override
public Future<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName());
}
@Override
public Future<Boolean> 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.<Object>asList(getName(), getTimeoutSetName()), timeUnit.toMillis(timeToLive));
}
@Override
public Future<Boolean> 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.<Object>asList(getName(), getTimeoutSetName()), timestamp);
}
@Override
public Future<Boolean> 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.<Object>asList(getName(), getTimeoutSetName()));
}
} }

@ -22,7 +22,6 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
@ -52,7 +51,7 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
private static final RedisCommand<ListScanResult<Object>> EVAL_SSCAN = new RedisCommand<ListScanResult<Object>>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder<Object>(), new ListScanResultReplayDecoder()), 7, ValueType.MAP_KEY, ValueType.OBJECT); private static final RedisCommand<ListScanResult<Object>> EVAL_SSCAN = new RedisCommand<ListScanResult<Object>>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder<Object>(), new ListScanResultReplayDecoder()), 7, ValueType.MAP_KEY, ValueType.OBJECT);
private static final RedisCommand<Integer> EVAL_SIZE = new RedisCommand<Integer>("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY); private static final RedisCommand<Integer> EVAL_SIZE = new RedisCommand<Integer>("EVAL", new IntegerReplayConvertor(), 6, ValueType.MAP_KEY);
private static final RedisCommand<Set<Object>> EVAL_READALL = new RedisCommand<Set<Object>>("EVAL", new ObjectSetReplayDecoder(), 6, ValueType.MAP_KEY); private static final RedisCommand<Set<Object>> EVAL_READALL = new RedisCommand<Set<Object>>("EVAL", new ObjectSetReplayDecoder<Object>(), 6, ValueType.MAP_KEY);
private static final RedisCommand<Boolean> EVAL_CONTAINS_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)); private static final RedisCommand<Boolean> EVAL_CONTAINS_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE));
private static final RedisCommand<Boolean> EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS); private static final RedisCommand<Boolean> EVAL_CONTAINS_ALL_WITH_VALUES = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS);
@ -128,64 +127,16 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
@Override @Override
public Iterator<V> iterator() { public Iterator<V> iterator() {
return new Iterator<V>() { return new RedissonBaseIterator<V>() {
private List<V> firstValues;
private Iterator<V> 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<V> res = scanIterator(client, nextIterPos);
client = res.getRedisClient();
if (nextIterPos == 0 && firstValues == null) {
firstValues = res.getValues();
} else if (res.getValues().equals(firstValues)) {
return false;
}
iter = res.getValues().iterator();
nextIterPos = res.getPos();
if (prevIterPos == nextIterPos && !removeExecuted) {
nextIterPos = -1;
}
}
return iter.hasNext();
}
@Override @Override
public V next() { ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
if (!hasNext()) { return scanIterator(client, nextIterPos);
throw new NoSuchElementException("No such element at index");
}
value = iter.next();
currentElementRemoved = false;
return value;
} }
@Override @Override
public void remove() { void remove(V value) {
if (currentElementRemoved) {
throw new IllegalStateException("Element been already deleted");
}
if (iter == null) {
throw new IllegalStateException();
}
iter.remove();
RedissonSetMultimapValues.this.remove(value); RedissonSetMultimapValues.this.remove(value);
currentElementRemoved = true;
removeExecuted = true;
} }
}; };
@ -300,13 +251,13 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
+ "return 0;" + "return 0;"
+ "end; " + + "end; " +
"local s = redis.call('smembers', KEYS[2]);" + "local s = redis.call('smembers', KEYS[2]);" +
"for i = 0, table.getn(s), 1 do " + "for i = 1, #s, 1 do " +
"for j = 2, table.getn(ARGV), 1 do " "for j = 2, #ARGV, 1 do "
+ "if ARGV[j] == s[i] " + "if ARGV[j] == s[i] "
+ "then table.remove(ARGV, j) end " + "then table.remove(ARGV, j) end "
+ "end; " + "end; "
+ "end;" + "end;"
+ "return table.getn(ARGV) == 2 and 1 or 0; ", + "return #ARGV == 2 and 1 or 0; ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray()); Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
} }
@ -356,11 +307,11 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
"local changed = 0 " + "local changed = 0 " +
"local s = redis.call('smembers', KEYS[2]) " "local s = redis.call('smembers', KEYS[2]) "
+ "local i = 0 " + "local i = 1 "
+ "while i <= table.getn(s) do " + "while i <= #s do "
+ "local element = s[i] " + "local element = s[i] "
+ "local isInAgrs = false " + "local isInAgrs = false "
+ "for j = 2, table.getn(ARGV), 1 do " + "for j = 2, #ARGV, 1 do "
+ "if ARGV[j] == element then " + "if ARGV[j] == element then "
+ "isInAgrs = true " + "isInAgrs = true "
+ "break " + "break "
@ -399,7 +350,7 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
+ "end; " + + "end; " +
"local v = 0 " + "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 " + "if redis.call('srem', KEYS[2], ARGV[i]) == 1 "
+ "then v = 1 end " + "then v = 1 end "
+"end " +"end "
@ -443,4 +394,56 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
delete(); delete();
} }
@Override
public int diff(String... names) {
return get(diffAsync(names));
}
@Override
public Future<Integer> diffAsync(String... names) {
List<Object> args = new ArrayList<Object>(names.length + 1);
args.add(getName());
args.addAll(Arrays.asList(names));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SDIFFSTORE_INT, args.toArray());
}
@Override
public Set<V> readDiff(String... names) {
return get(readDiffAsync(names));
}
@Override
public Future<Set<V>> readDiffAsync(String... names) {
List<Object> args = new ArrayList<Object>(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<Integer> intersectionAsync(String... names) {
List<Object> args = new ArrayList<Object>(names.length + 1);
args.add(getName());
args.addAll(Arrays.asList(names));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTERSTORE_INT, args.toArray());
}
@Override
public Set<V> readIntersection(String... names) {
return get(readIntersectionAsync(names));
}
@Override
public Future<Set<V>> readIntersectionAsync(String... names) {
List<Object> args = new ArrayList<Object>(names.length + 1);
args.add(getName());
args.addAll(Arrays.asList(names));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTER, args.toArray());
}
} }

@ -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);
}
}

@ -104,13 +104,13 @@ public class RedissonSubList<V> extends RedissonList<V> implements RList<V> {
"local toIndex = table.remove(ARGV, 2);" + "local toIndex = table.remove(ARGV, 2);" +
"local items = redis.call('lrange', KEYS[1], tonumber(fromIndex), tonumber(toIndex)) " + "local items = redis.call('lrange', KEYS[1], tonumber(fromIndex), tonumber(toIndex)) " +
"for i=1, #items do " + "for i=1, #items do " +
"for j = 0, #ARGV, 1 do " + "for j = 1, #ARGV, 1 do " +
"if items[i] == ARGV[j] then " + "if items[i] == ARGV[j] then " +
"table.remove(ARGV, j) " + "table.remove(ARGV, j) " +
"end " + "end " +
"end " + "end " +
"end " + "end " +
"return table.getn(ARGV) == 0 and 1 or 0", "return #ARGV == 0 and 1 or 0",
Collections.<Object>singletonList(getName()), params.toArray()); Collections.<Object>singletonList(getName()), params.toArray());
} }
@ -179,7 +179,7 @@ public class RedissonSubList<V> extends RedissonList<V> implements RList<V> {
"local items = redis.call('lrange', KEYS[1], fromIndex, toIndex); " + "local items = redis.call('lrange', KEYS[1], fromIndex, toIndex); " +
"for i=1, #items do " + "for i=1, #items do " +
"for j = 0, #ARGV, 1 do " + "for j = 1, #ARGV, 1 do " +
"if items[i] == ARGV[j] then " + "if items[i] == ARGV[j] then " +
"redis.call('lrem', KEYS[1], count, ARGV[i]); " + "redis.call('lrem', KEYS[1], count, ARGV[i]); " +
"v = 1; " + "v = 1; " +
@ -203,11 +203,10 @@ public class RedissonSubList<V> extends RedissonList<V> implements RList<V> {
"local toIndex = table.remove(ARGV, 2);" + "local toIndex = table.remove(ARGV, 2);" +
"local items = redis.call('lrange', KEYS[1], fromIndex, toIndex) " "local items = redis.call('lrange', KEYS[1], fromIndex, toIndex) "
+ "local i = 1 " + "local i = 1 "
+ "local s = table.getn(items) " + "while i <= #items do "
+ "while i <= s do "
+ "local element = items[i] " + "local element = items[i] "
+ "local isInAgrs = false " + "local isInAgrs = false "
+ "for j = 0, table.getn(ARGV), 1 do " + "for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == element then " + "if ARGV[j] == element then "
+ "isInAgrs = true " + "isInAgrs = true "
+ "break " + "break "

@ -19,7 +19,7 @@ import java.net.InetSocketAddress;
import org.redisson.client.handler.CommandDecoder; import org.redisson.client.handler.CommandDecoder;
import org.redisson.client.handler.CommandEncoder; 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.CommandsQueue;
import org.redisson.client.handler.ConnectionWatchdog; 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.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
public class RedisClient { public class RedisClient {
@ -61,8 +62,8 @@ public class RedisClient {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addFirst(new ConnectionWatchdog(bootstrap, channels), ch.pipeline().addFirst(new ConnectionWatchdog(bootstrap, channels),
new CommandEncoder(), CommandEncoder.INSTANCE,
new CommandsListEncoder(), CommandBatchEncoder.INSTANCE,
new CommandsQueue(), new CommandsQueue(),
new CommandDecoder()); new CommandDecoder());
} }
@ -95,7 +96,7 @@ public class RedisClient {
} }
public Future<RedisConnection> connectAsync() { public Future<RedisConnection> connectAsync() {
final Promise<RedisConnection> f = bootstrap.group().next().newPromise(); final Promise<RedisConnection> f = ImmediateEventExecutor.INSTANCE.newPromise();
ChannelFuture channelFuture = bootstrap.connect(); ChannelFuture channelFuture = bootstrap.connect();
channelFuture.addListener(new ChannelFutureListener() { channelFuture.addListener(new ChannelFutureListener() {
@Override @Override
@ -122,7 +123,7 @@ public class RedisClient {
} }
public Future<RedisPubSubConnection> connectPubSubAsync() { public Future<RedisPubSubConnection> connectPubSubAsync() {
final Promise<RedisPubSubConnection> f = bootstrap.group().next().newPromise(); final Promise<RedisPubSubConnection> f = ImmediateEventExecutor.INSTANCE.newPromise();
ChannelFuture channelFuture = bootstrap.connect(); ChannelFuture channelFuture = bootstrap.connect();
channelFuture.addListener(new ChannelFutureListener() { channelFuture.addListener(new ChannelFutureListener() {
@Override @Override

@ -15,21 +15,24 @@
*/ */
package org.redisson.client; package org.redisson.client;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.handler.CommandsQueue;
import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData; import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.QueueCommand;
import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand; import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.connection.FastSuccessFuture;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture; import io.netty.util.concurrent.ScheduledFuture;
@ -45,7 +48,7 @@ public class RedisConnection implements RedisCommands {
private ReconnectListener reconnectListener; private ReconnectListener reconnectListener;
private long lastUsageTime; private long lastUsageTime;
private final Future<?> acquireFuture = new FastSuccessFuture<Object>(this); private final Future<?> acquireFuture = ImmediateEventExecutor.INSTANCE.newSucceededFuture(this);
public RedisConnection(RedisClient redisClient, Channel channel) { public RedisConnection(RedisClient redisClient, Channel channel) {
super(); super();
@ -59,6 +62,18 @@ public class RedisConnection implements RedisCommands {
return (C) channel.attr(RedisConnection.CONNECTION).get(); 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() { public long getLastUsageTime() {
return lastUsageTime; return lastUsageTime;
} }
@ -97,21 +112,34 @@ public class RedisConnection implements RedisCommands {
return redisClient; return redisClient;
} }
public <R> R await(Future<R> cmd) { public <R> R await(Future<R> future) {
// TODO change connectTimeout to timeout final CountDownLatch l = new CountDownLatch(1);
if (!cmd.awaitUninterruptibly(redisClient.getTimeout(), TimeUnit.MILLISECONDS)) { future.addListener(new FutureListener<R>() {
Promise<R> promise = (Promise<R>)cmd; @Override
RedisTimeoutException ex = new RedisTimeoutException("Command execution timeout for " + redisClient.getAddr()); public void operationComplete(Future<R> future) throws Exception {
promise.setFailure(ex); l.countDown();
throw ex; }
} });
if (!cmd.isSuccess()) {
if (cmd.cause() instanceof RedisException) { try {
throw (RedisException) cmd.cause(); // TODO change connectTimeout to timeout
if (!l.await(redisClient.getTimeout(), TimeUnit.MILLISECONDS)) {
Promise<R> promise = (Promise<R>)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> T sync(RedisStrictCommand<T> command, Object ... params) { public <T> T sync(RedisStrictCommand<T> command, Object ... params) {
@ -137,13 +165,13 @@ public class RedisConnection implements RedisCommands {
} }
public <T, R> Future<R> async(Codec encoder, RedisCommand<T> command, Object ... params) { public <T, R> Future<R> async(Codec encoder, RedisCommand<T> command, Object ... params) {
Promise<R> promise = redisClient.getBootstrap().group().next().<R>newPromise(); Promise<R> promise = ImmediateEventExecutor.INSTANCE.newPromise();
send(new CommandData<T, R>(promise, encoder, command, params)); send(new CommandData<T, R>(promise, encoder, command, params));
return promise; return promise;
} }
public <T, R> Future<R> asyncWithTimeout(Codec encoder, RedisCommand<T> command, Object ... params) { public <T, R> Future<R> asyncWithTimeout(Codec encoder, RedisCommand<T> command, Object ... params) {
final Promise<R> promise = redisClient.getBootstrap().group().next().<R>newPromise(); final Promise<R> promise = ImmediateEventExecutor.INSTANCE.newPromise();
final ScheduledFuture<?> scheduledFuture = redisClient.getBootstrap().group().next().schedule(new Runnable() { final ScheduledFuture<?> scheduledFuture = redisClient.getBootstrap().group().next().schedule(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -162,7 +190,7 @@ public class RedisConnection implements RedisCommands {
} }
public <T, R> CommandData<T, R> create(Codec encoder, RedisCommand<T> command, Object ... params) { public <T, R> CommandData<T, R> create(Codec encoder, RedisCommand<T> command, Object ... params) {
Promise<R> promise = redisClient.getBootstrap().group().next().<R>newPromise(); Promise<R> promise = ImmediateEventExecutor.INSTANCE.newPromise();
return new CommandData<T, R>(promise, encoder, command, params); return new CommandData<T, R>(promise, encoder, command, params);
} }
@ -194,7 +222,7 @@ public class RedisConnection implements RedisCommands {
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + " [redisClient=" + redisClient + ", channel=" + channel + "]"; return getClass().getSimpleName() + "@" + System.identityHashCode(this) + " [redisClient=" + redisClient + ", channel=" + channel + "]";
} }
public Future<?> getAcquireFuture() { public Future<?> getAcquireFuture() {

@ -18,7 +18,7 @@ package org.redisson.client;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
class RedisRedirectException extends RedisException { public class RedisRedirectException extends RedisException {
private static final long serialVersionUID = 181505625075250011L; private static final long serialVersionUID = 181505625075250011L;

@ -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<Object> getValueDecoder() {
return delegate.getValueDecoder();
}
}

@ -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<Integer> pos = new ThreadLocal<Integer>() {
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();
}
}

@ -19,7 +19,7 @@ import org.redisson.client.protocol.Encoder;
public class ScoredCodec extends StringCodec { public class ScoredCodec extends StringCodec {
public final Codec delegate; private final Codec delegate;
public ScoredCodec(Codec delegate) { public ScoredCodec(Codec delegate) {
super(); super();

@ -20,6 +20,7 @@ import org.redisson.client.protocol.CommandsData;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
/** /**
@ -27,12 +28,16 @@ import io.netty.handler.codec.MessageToByteEncoder;
* @author Nikita Koksharov * @author Nikita Koksharov
* *
*/ */
public class CommandsListEncoder extends MessageToByteEncoder<CommandsData> { @Sharable
public class CommandBatchEncoder extends MessageToByteEncoder<CommandsData> {
public static final CommandBatchEncoder INSTANCE = new CommandBatchEncoder();
@Override @Override
protected void encode(ChannelHandlerContext ctx, CommandsData msg, ByteBuf out) throws Exception { protected void encode(ChannelHandlerContext ctx, CommandsData msg, ByteBuf out) throws Exception {
CommandEncoder encoder = ctx.pipeline().get(CommandEncoder.class);
for (CommandData<?, ?> commandData : msg.getCommands()) { for (CommandData<?, ?> commandData : msg.getCommands()) {
ctx.pipeline().get(CommandEncoder.class).encode(ctx, (CommandData<Object, Object>)commandData, out); encoder.encode(ctx, commandData, out);
} }
} }

@ -35,7 +35,9 @@ import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.Decoder; import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.QueueCommand; import org.redisson.client.protocol.QueueCommand;
import org.redisson.client.protocol.RedisCommand.ValueType; 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.MultiDecoder;
import org.redisson.client.protocol.decoder.NestedMultiDecoder;
import org.redisson.client.protocol.pubsub.Message; import org.redisson.client.protocol.pubsub.Message;
import org.redisson.client.protocol.pubsub.PubSubMessage; import org.redisson.client.protocol.pubsub.PubSubMessage;
import org.redisson.client.protocol.pubsub.PubSubPatternMessage; import org.redisson.client.protocol.pubsub.PubSubPatternMessage;
@ -68,52 +70,56 @@ public class CommandDecoder extends ReplayingDecoder<State> {
private static final char ZERO = '0'; private static final char ZERO = '0';
// It is not needed to use concurrent map because responses are coming consecutive // It is not needed to use concurrent map because responses are coming consecutive
private final Map<String, MultiDecoder<Object>> messageDecoders = new HashMap<String, MultiDecoder<Object>>(); private final Map<String, MultiDecoder<Object>> pubSubMessageDecoders = new HashMap<String, MultiDecoder<Object>>();
private final Map<String, CommandData<Object, Object>> channels = PlatformDependent.newConcurrentHashMap(); private final Map<String, CommandData<Object, Object>> pubSubChannels = PlatformDependent.newConcurrentHashMap();
public void addChannel(String channel, CommandData<Object, Object> data) { public void addPubSubCommand(String channel, CommandData<Object, Object> data) {
channels.put(channel, data); pubSubChannels.put(channel, data);
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
QueueCommand data = ctx.channel().attr(CommandsQueue.CURRENT_COMMAND).get(); QueueCommand data = ctx.channel().attr(CommandsQueue.CURRENT_COMMAND).get();
Decoder<Object> currentDecoder = null; if (log.isTraceEnabled()) {
if (data == null) { log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8));
currentDecoder = StringCodec.INSTANCE.getValueDecoder();
} }
if (state() == null) { if (state() == null) {
state(new State()); boolean makeCheckpoint = data != null;
if (data != null) {
if (log.isTraceEnabled()) { if (data instanceof CommandsData) {
log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8)); makeCheckpoint = false;
} else {
CommandData<Object, Object> cmd = (CommandData<Object, Object>)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); state().setDecoderState(null);
if (data == null) { if (data == null) {
decode(in, null, null, ctx.channel(), currentDecoder); decode(in, null, null, ctx.channel());
} else if (data instanceof CommandData) { } else if (data instanceof CommandData) {
CommandData<Object, Object> cmd = (CommandData<Object, Object>)data; CommandData<Object, Object> cmd = (CommandData<Object, Object>)data;
try { try {
// if (state().getSize() > 0) { if (state().getLevels().size() > 0) {
// List<Object> respParts = new ArrayList<Object>(); decodeFromCheckpoint(ctx, in, data, cmd);
// if (state().getRespParts() != null) { } else {
// respParts = state().getRespParts(); decode(in, cmd, null, ctx.channel());
// } }
// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts, true);
// } else {
decode(in, cmd, null, ctx.channel(), currentDecoder);
// }
} catch (IOException e) { } catch (IOException e) {
cmd.getPromise().tryFailure(e); cmd.tryFailure(e);
} }
} else if (data instanceof CommandsData) { } else if (data instanceof CommandsData) {
CommandsData commands = (CommandsData)data; CommandsData commands = (CommandsData)data;
handleCommandsDataResponse(ctx, in, data, currentDecoder, commands); decodeCommandBatch(ctx, in, data, commands);
return; return;
} }
@ -122,33 +128,65 @@ public class CommandDecoder extends ReplayingDecoder<State> {
state(null); state(null);
} }
private void handleCommandsDataResponse(ChannelHandlerContext ctx, ByteBuf in, QueueCommand data, private void decodeFromCheckpoint(ChannelHandlerContext ctx, ByteBuf in, QueueCommand data,
Decoder<Object> currentDecoder, CommandsData commands) { CommandData<Object, Object> cmd) throws IOException {
int i = state().getIndex(); 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<Object> 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; RedisException error = null;
while (in.writerIndex() > in.readerIndex()) { while (in.writerIndex() > in.readerIndex()) {
CommandData<Object, Object> cmd = null; CommandData<Object, Object> cmd = null;
try { try {
checkpoint(); checkpoint();
state().setIndex(i); state().setBatchIndex(i);
cmd = (CommandData<Object, Object>) commands.getCommands().get(i); cmd = (CommandData<Object, Object>) commandBatch.getCommands().get(i);
decode(in, cmd, null, ctx.channel(), currentDecoder); decode(in, cmd, null, ctx.channel());
i++; i++;
} catch (IOException e) { } catch (IOException e) {
cmd.getPromise().tryFailure(e); cmd.tryFailure(e);
} }
if (!cmd.getPromise().isSuccess()) { if (!cmd.isSuccess()) {
if (!(cmd.getPromise().cause() instanceof RedisMovedException error = (RedisException) cmd.cause();
|| cmd.getPromise().cause() instanceof RedisAskException
|| cmd.getPromise().cause() instanceof RedisLoadingException)) {
error = (RedisException) cmd.getPromise().cause();
}
} }
} }
if (i == commands.getCommands().size()) { if (i == commandBatch.getCommands().size()) {
Promise<Void> promise = commands.getPromise(); Promise<Void> promise = commandBatch.getPromise();
if (error != null) { if (error != null) {
if (!promise.tryFailure(error) && promise.cause() instanceof RedisTimeoutException) { if (!promise.tryFailure(error) && promise.cause() instanceof RedisTimeoutException) {
log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data); log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data);
@ -164,11 +202,11 @@ public class CommandDecoder extends ReplayingDecoder<State> {
state(null); state(null);
} else { } else {
checkpoint(); checkpoint();
state().setIndex(i); state().setBatchIndex(i);
} }
} }
private void decode(ByteBuf in, CommandData<Object, Object> data, List<Object> parts, Channel channel, Decoder<Object> currentDecoder) throws IOException { private void decode(ByteBuf in, CommandData<Object, Object> data, List<Object> parts, Channel channel) throws IOException {
int code = in.readByte(); int code = in.readByte();
if (code == '+') { if (code == '+') {
String result = in.readBytes(in.bytesBefore((byte) '\r')).toString(CharsetUtil.UTF_8); String result = in.readBytes(in.bytesBefore((byte) '\r')).toString(CharsetUtil.UTF_8);
@ -183,63 +221,69 @@ public class CommandDecoder extends ReplayingDecoder<State> {
String[] errorParts = error.split(" "); String[] errorParts = error.split(" ");
int slot = Integer.valueOf(errorParts[1]); int slot = Integer.valueOf(errorParts[1]);
String addr = errorParts[2]; String addr = errorParts[2];
data.getPromise().tryFailure(new RedisMovedException(slot, addr)); data.tryFailure(new RedisMovedException(slot, addr));
} else if (error.startsWith("ASK")) { } else if (error.startsWith("ASK")) {
String[] errorParts = error.split(" "); String[] errorParts = error.split(" ");
int slot = Integer.valueOf(errorParts[1]); int slot = Integer.valueOf(errorParts[1]);
String addr = errorParts[2]; String addr = errorParts[2];
data.getPromise().tryFailure(new RedisAskException(slot, addr)); data.tryFailure(new RedisAskException(slot, addr));
} else if (error.startsWith("LOADING")) { } else if (error.startsWith("LOADING")) {
data.getPromise().tryFailure(new RedisLoadingException(error data.tryFailure(new RedisLoadingException(error
+ ". channel: " + channel + " data: " + data)); + ". channel: " + channel + " data: " + data));
} else if (error.startsWith("OOM")) { } 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)); + ". channel: " + channel + " data: " + data));
} else if (error.contains("-OOM ")) { } 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)); + ". channel: " + channel + " data: " + data));
} else { } else {
if (data != null) { if (data != null) {
data.getPromise().tryFailure(new RedisException(error + ". channel: " + channel + " command: " + data)); data.tryFailure(new RedisException(error + ". channel: " + channel + " command: " + data));
} else { } else {
log.error("Error: {} channel: {} data: {}", error, channel, data); log.error("Error: {} channel: {} data: {}", error, channel, data);
} }
} }
} else if (code == ':') { } else if (code == ':') {
String status = in.readBytes(in.bytesBefore((byte) '\r')).toString(CharsetUtil.UTF_8); Long result = readLong(in);
in.skipBytes(2);
Object result = Long.valueOf(status);
handleResult(data, parts, result, false, channel); handleResult(data, parts, result, false, channel);
} else if (code == '$') { } else if (code == '$') {
ByteBuf buf = readBytes(in); ByteBuf buf = readBytes(in);
Object result = null; Object result = null;
if (buf != null) { if (buf != null) {
result = decoder(data, parts, currentDecoder).decode(buf, state()); Decoder<Object> decoder = selectDecoder(data, parts);
result = decoder.decode(buf, state());
} }
handleResult(data, parts, result, false, channel); handleResult(data, parts, result, false, channel);
} else if (code == '*') { } else if (code == '*') {
int level = state().incLevel();
long size = readLong(in); long size = readLong(in);
List<Object> respParts = new ArrayList<Object>(); List<Object> respParts;
boolean top = false; if (state().getLevels().size()-1 >= level) {
// if (state().trySetSize(size)) { StateLevel stateLevel = state().getLevels().get(level);
// state().setRespParts(respParts); respParts = stateLevel.getParts();
// top = true; size = stateLevel.getSize();
// } } else {
respParts = new ArrayList<Object>();
decodeMulti(in, data, parts, channel, currentDecoder, size, respParts, top); if (state().isMakeCheckpoint()) {
state().addLevel(new StateLevel(size, respParts));
}
}
decodeList(in, data, parts, channel, size, respParts);
} else { } else {
throw new IllegalStateException("Can't decode replay " + (char)code); throw new IllegalStateException("Can't decode replay " + (char)code);
} }
} }
private void decodeMulti(ByteBuf in, CommandData<Object, Object> data, List<Object> parts, private void decodeList(ByteBuf in, CommandData<Object, Object> data, List<Object> parts,
Channel channel, Decoder<Object> currentDecoder, long size, List<Object> respParts, boolean top) Channel channel, long size, List<Object> respParts)
throws IOException { throws IOException {
for (int i = respParts.size(); i < size; i++) { for (int i = respParts.size(); i < size; i++) {
decode(in, data, respParts, channel, currentDecoder); decode(in, data, respParts, channel);
// if (top) { if (state().isMakeCheckpoint()) {
// checkpoint(); checkpoint();
// } }
} }
MultiDecoder<Object> decoder = messageDecoder(data, respParts, channel); MultiDecoder<Object> decoder = messageDecoder(data, respParts, channel);
@ -248,7 +292,10 @@ public class CommandDecoder extends ReplayingDecoder<State> {
} }
Object result = decoder.decode(respParts, state()); Object result = decoder.decode(respParts, state());
if (data != null) {
handleResult(data, parts, result, true, channel);
return;
}
if (result instanceof Message) { if (result instanceof Message) {
// store current message index // store current message index
@ -257,40 +304,34 @@ public class CommandDecoder extends ReplayingDecoder<State> {
handleMultiResult(data, null, channel, result); handleMultiResult(data, null, channel, result);
// has next messages? // has next messages?
if (in.writerIndex() > in.readerIndex()) { 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<Object, Object> data, List<Object> parts, private void handleMultiResult(CommandData<Object, Object> data, List<Object> parts,
Channel channel, Object result) { Channel channel, Object result) {
if (data != null) { if (result instanceof PubSubStatusMessage) {
handleResult(data, parts, result, true, channel); String channelName = ((PubSubStatusMessage) result).getChannel();
} else { CommandData<Object, Object> d = pubSubChannels.get(channelName);
if (result instanceof PubSubStatusMessage) { if (Arrays.asList("PSUBSCRIBE", "SUBSCRIBE").contains(d.getCommand().getName())) {
String channelName = ((PubSubStatusMessage) result).getChannel(); pubSubChannels.remove(channelName);
CommandData<Object, Object> d = channels.get(channelName); pubSubMessageDecoders.put(channelName, d.getMessageDecoder());
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 (Arrays.asList("PUNSUBSCRIBE", "UNSUBSCRIBE").contains(d.getCommand().getName())) {
RedisPubSubConnection pubSubConnection = RedisPubSubConnection.getFrom(channel); pubSubChannels.remove(channelName);
if (result instanceof PubSubStatusMessage) { pubSubMessageDecoders.remove(channelName);
pubSubConnection.onMessage((PubSubStatusMessage) result);
} else if (result instanceof PubSubMessage) {
pubSubConnection.onMessage((PubSubMessage) result);
} else {
pubSubConnection.onMessage((PubSubPatternMessage) result);
} }
} }
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<Object, Object> data, List<Object> parts, Object result, boolean multiResult, Channel channel) { private void handleResult(CommandData<Object, Object> data, List<Object> parts, Object result, boolean multiResult, Channel channel) {
@ -304,7 +345,7 @@ public class CommandDecoder extends ReplayingDecoder<State> {
if (parts != null) { if (parts != null) {
parts.add(result); parts.add(result);
} else { } 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); log.warn("response has been skipped due to timeout! channel: {}, command: {}, result: {}", channel, data, result);
} }
} }
@ -314,34 +355,34 @@ public class CommandDecoder extends ReplayingDecoder<State> {
if (data == null) { if (data == null) {
if (Arrays.asList("subscribe", "psubscribe", "punsubscribe", "unsubscribe").contains(parts.get(0))) { if (Arrays.asList("subscribe", "psubscribe", "punsubscribe", "unsubscribe").contains(parts.get(0))) {
String channelName = (String) parts.get(1); String channelName = (String) parts.get(1);
CommandData<Object, Object> commandData = channels.get(channelName); CommandData<Object, Object> commandData = pubSubChannels.get(channelName);
if (commandData == null) { if (commandData == null) {
return null; return null;
} }
return commandData.getCommand().getReplayMultiDecoder(); return commandData.getCommand().getReplayMultiDecoder();
} else if (parts.get(0).equals("message")) { } else if (parts.get(0).equals("message")) {
String channelName = (String) parts.get(1); String channelName = (String) parts.get(1);
return messageDecoders.get(channelName); return pubSubMessageDecoders.get(channelName);
} else if (parts.get(0).equals("pmessage")) { } else if (parts.get(0).equals("pmessage")) {
String patternName = (String) parts.get(1); String patternName = (String) parts.get(1);
return messageDecoders.get(patternName); return pubSubMessageDecoders.get(patternName);
} }
} }
return data.getCommand().getReplayMultiDecoder(); return data.getCommand().getReplayMultiDecoder();
} }
private Decoder<Object> decoder(CommandData<Object, Object> data, List<Object> parts, Decoder<Object> currentDecoder) { private Decoder<Object> selectDecoder(CommandData<Object, Object> data, List<Object> parts) {
if (data == null) { if (data == null) {
if (parts.size() == 2 && parts.get(0).equals("message")) { if (parts.size() == 2 && parts.get(0).equals("message")) {
String channelName = (String) parts.get(1); String channelName = (String) parts.get(1);
return messageDecoders.get(channelName); return pubSubMessageDecoders.get(channelName);
} }
if (parts.size() == 3 && parts.get(0).equals("pmessage")) { if (parts.size() == 3 && parts.get(0).equals("pmessage")) {
String patternName = (String) parts.get(1); String patternName = (String) parts.get(1);
return messageDecoders.get(patternName); return pubSubMessageDecoders.get(patternName);
} }
return currentDecoder; return StringCodec.INSTANCE.getValueDecoder();
} }
Decoder<Object> decoder = data.getCommand().getReplayDecoder(); Decoder<Object> decoder = data.getCommand().getReplayDecoder();

@ -15,18 +15,21 @@
*/ */
package org.redisson.client.handler; package org.redisson.client.handler;
import java.util.HashMap;
import java.util.List; 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.CommandData;
import org.redisson.client.protocol.Encoder;
import org.redisson.client.protocol.DefaultParamsEncoder; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.redisson.client.protocol.RedisCommand.ValueType;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
@ -38,7 +41,10 @@ import io.netty.util.CharsetUtil;
* @author Nikita Koksharov * @author Nikita Koksharov
* *
*/ */
public class CommandEncoder extends MessageToByteEncoder<CommandData<Object, Object>> { @Sharable
public class CommandEncoder extends MessageToByteEncoder<CommandData<?, ?>> {
public static final CommandEncoder INSTANCE = new CommandEncoder();
private final Logger log = LoggerFactory.getLogger(getClass()); private final Logger log = LoggerFactory.getLogger(getClass());
@ -48,14 +54,16 @@ public class CommandEncoder extends MessageToByteEncoder<CommandData<Object, Obj
private static final char BYTES_PREFIX = '$'; private static final char BYTES_PREFIX = '$';
private static final byte[] CRLF = "\r\n".getBytes(); private static final byte[] CRLF = "\r\n".getBytes();
private static final Map<Long, byte[]> longCache = new HashMap<Long, byte[]>();
@Override @Override
protected void encode(ChannelHandlerContext ctx, CommandData<Object, Object> msg, ByteBuf out) throws Exception { protected void encode(ChannelHandlerContext ctx, CommandData<?, ?> msg, ByteBuf out) throws Exception {
out.writeByte(ARGS_PREFIX); out.writeByte(ARGS_PREFIX);
int len = 1 + msg.getParams().length; int len = 1 + msg.getParams().length;
if (msg.getCommand().getSubName() != null) { if (msg.getCommand().getSubName() != null) {
len++; len++;
} }
out.writeBytes(toChars(len)); out.writeBytes(convert(len));
out.writeBytes(CRLF); out.writeBytes(CRLF);
writeArgument(out, msg.getCommand().getName().getBytes("UTF-8")); writeArgument(out, msg.getCommand().getName().getBytes("UTF-8"));
@ -90,7 +98,7 @@ public class CommandEncoder extends MessageToByteEncoder<CommandData<Object, Obj
} }
} }
private Encoder selectEncoder(CommandData<Object, Object> msg, int param) { private Encoder selectEncoder(CommandData<?, ?> msg, int param) {
int typeIndex = 0; int typeIndex = 0;
List<ValueType> inParamType = msg.getCommand().getInParamType(); List<ValueType> inParamType = msg.getCommand().getInParamType();
if (inParamType.size() > 1) { if (inParamType.size() > 1) {
@ -112,15 +120,18 @@ public class CommandEncoder extends MessageToByteEncoder<CommandData<Object, Obj
if (inParamType.get(typeIndex) == ValueType.OBJECTS) { if (inParamType.get(typeIndex) == ValueType.OBJECTS) {
return msg.getCodec().getValueEncoder(); return msg.getCodec().getValueEncoder();
} }
if (inParamType.get(typeIndex) == ValueType.BINARY) { if (inParamType.get(typeIndex) == ValueType.OBJECT) {
return ByteArrayCodec.INSTANCE.getValueEncoder(); return msg.getCodec().getValueEncoder();
}
if (inParamType.get(typeIndex) == ValueType.STRING) {
return StringCodec.INSTANCE.getValueEncoder();
} }
throw new IllegalStateException(); throw new IllegalStateException();
} }
private void writeArgument(ByteBuf out, byte[] arg) { private void writeArgument(ByteBuf out, byte[] arg) {
out.writeByte(BYTES_PREFIX); out.writeByte(BYTES_PREFIX);
out.writeBytes(toChars(arg.length)); out.writeBytes(convert(arg.length));
out.writeBytes(CRLF); out.writeBytes(CRLF);
out.writeBytes(arg); out.writeBytes(arg);
out.writeBytes(CRLF); out.writeBytes(CRLF);
@ -185,6 +196,13 @@ public class CommandEncoder extends MessageToByteEncoder<CommandData<Object, Obj
} }
} }
public static byte[] convert(long i) {
if (i >= 0 && i <= 255) {
return longCache.get(i);
}
return toChars(i);
}
public static byte[] toChars(long i) { public static byte[] toChars(long i) {
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
byte[] buf = new byte[size]; byte[] buf = new byte[size];
@ -192,4 +210,12 @@ public class CommandEncoder extends MessageToByteEncoder<CommandData<Object, Obj
return buf; return buf;
} }
static {
for (long i = 0; i < 256; i++) {
byte[] value = toChars(i);
longCache.put(i, value);
}
}
} }

@ -23,10 +23,10 @@ import org.redisson.client.protocol.QueueCommand;
import org.redisson.client.protocol.QueueCommandHolder; import org.redisson.client.protocol.QueueCommandHolder;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
@ -37,7 +37,7 @@ import io.netty.util.internal.PlatformDependent;
* @author Nikita Koksharov * @author Nikita Koksharov
* *
*/ */
public class CommandsQueue extends ChannelDuplexHandler { public class CommandsQueue extends ChannelOutboundHandlerAdapter {
public static final AttributeKey<QueueCommand> CURRENT_COMMAND = AttributeKey.valueOf("promise"); public static final AttributeKey<QueueCommand> CURRENT_COMMAND = AttributeKey.valueOf("promise");
@ -82,7 +82,7 @@ public class CommandsQueue extends ChannelDuplexHandler {
if (!pubSubOps.isEmpty()) { if (!pubSubOps.isEmpty()) {
for (CommandData<Object, Object> cd : pubSubOps) { for (CommandData<Object, Object> cd : pubSubOps) {
for (Object channel : cd.getParams()) { for (Object channel : cd.getParams()) {
ch.pipeline().get(CommandDecoder.class).addChannel(channel.toString(), cd); ch.pipeline().get(CommandDecoder.class).addPubSubCommand(channel.toString(), cd);
} }
} }
} else { } else {

@ -22,6 +22,7 @@ import org.redisson.client.RedisConnection;
import org.redisson.client.RedisException; import org.redisson.client.RedisException;
import org.redisson.client.RedisPubSubConnection; import org.redisson.client.RedisPubSubConnection;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.CommandData;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -35,6 +36,7 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.ChannelGroup;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { public class ConnectionWatchdog extends ChannelInboundHandlerAdapter {
@ -115,24 +117,22 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter {
if (connection.getReconnectListener() != null) { if (connection.getReconnectListener() != null) {
// new connection used only for channel init // new connection used only for channel init
RedisConnection rc = new RedisConnection(connection.getRedisClient(), channel); RedisConnection rc = new RedisConnection(connection.getRedisClient(), channel);
Promise<RedisConnection> connectionFuture = bootstrap.group().next().newPromise(); Promise<RedisConnection> connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise();
connection.getReconnectListener().onReconnect(rc, connectionFuture); connection.getReconnectListener().onReconnect(rc, connectionFuture);
connectionFuture.addListener(new FutureListener<RedisConnection>() { connectionFuture.addListener(new FutureListener<RedisConnection>() {
@Override @Override
public void operationComplete(Future<RedisConnection> future) throws Exception { public void operationComplete(Future<RedisConnection> future) throws Exception {
if (future.isSuccess()) { if (future.isSuccess()) {
connection.updateChannel(channel); refresh(connection, channel);
resubscribe(connection);
} }
} }
}); });
} else { } else {
connection.updateChannel(channel); refresh(connection, channel);
resubscribe(connection);
} }
} }
private void resubscribe(RedisConnection connection) { private void reattachPubSub(RedisConnection connection) {
if (connection instanceof RedisPubSubConnection) { if (connection instanceof RedisPubSubConnection) {
RedisPubSubConnection conn = (RedisPubSubConnection) connection; RedisPubSubConnection conn = (RedisPubSubConnection) connection;
for (Entry<String, Codec> entry : conn.getChannels().entrySet()) { for (Entry<String, Codec> entry : conn.getChannels().entrySet()) {
@ -149,4 +149,29 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter {
ctx.channel().close(); 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);
}
}
});
}
} }

@ -15,50 +15,80 @@
*/ */
package org.redisson.client.handler; package org.redisson.client.handler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.redisson.client.protocol.decoder.DecoderState;
public class State { public class State {
private int index; private int batchIndex;
private Object decoderState; private DecoderState decoderState;
private long size; private int level = -1;
private List<Object> respParts; private List<StateLevel> levels;
private DecoderState decoderStateCopy;
private final boolean makeCheckpoint;
public State() { public State(boolean makeCheckpoint) {
super(); this.makeCheckpoint = makeCheckpoint;
} }
public boolean trySetSize(long size) { public boolean isMakeCheckpoint() {
if (this.size != 0) { return makeCheckpoint;
return false; }
}
this.size = size; public void resetLevel() {
return true; level = -1;
} }
public long getSize() { public int decLevel() {
return size; return --level;
}
public int incLevel() {
return ++level;
} }
public void setRespParts(List<Object> respParts) { public void addLevel(StateLevel stateLevel) {
this.respParts = respParts; if (levels == null) {
levels = new ArrayList<StateLevel>(2);
}
levels.add(stateLevel);
} }
public List<Object> getRespParts() { public List<StateLevel> getLevels() {
return respParts; if (levels == null) {
return Collections.emptyList();
}
return levels;
} }
public void setIndex(int index) { public void setBatchIndex(int index) {
this.index = index; this.batchIndex = index;
} }
public int getIndex() { public int getBatchIndex() {
return index; return batchIndex;
} }
public <T> T getDecoderState() { public <T extends DecoderState> T getDecoderState() {
return (T)decoderState; return (T) decoderState;
} }
public void setDecoderState(Object decoderState) { public void setDecoderState(DecoderState decoderState) {
this.decoderState = decoderState; this.decoderState = decoderState;
} }
public DecoderState getDecoderStateCopy() {
return decoderStateCopy;
}
public void setDecoderStateCopy(DecoderState decoderStateCopy) {
this.decoderStateCopy = decoderStateCopy;
}
@Override
public String toString() {
return "State [batchIndex=" + batchIndex + ", decoderState=" + decoderState + ", level=" + level + ", levels="
+ levels + ", decoderStateCopy=" + decoderStateCopy + "]";
}
} }

@ -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<Object> parts;
public StateLevel(long size, List<Object> parts) {
super();
this.size = size;
this.parts = parts;
}
public long getSize() {
return size;
}
public List<Object> getParts() {
return parts;
}
@Override
public String toString() {
return "StateLevel [size=" + size + ", parts=" + parts + "]";
}
}

@ -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<T, R> extends CommandData<T, R> {
private final AtomicReference<RedisRedirectException> redirectError = new AtomicReference<RedisRedirectException>();
public BatchCommandData(Promise<R> promise, Codec codec, RedisCommand<T> 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);
}
}

@ -60,6 +60,18 @@ public class CommandData<T, R> implements QueueCommand {
return promise; 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() { public Codec getCodec() {
return codec; return codec;
} }
@ -78,4 +90,8 @@ public class CommandData<T, R> implements QueueCommand {
return Collections.emptyList(); return Collections.emptyList();
} }
public boolean isBlockingCommand() {
return QueueCommand.TIMEOUTLESS_COMMANDS.contains(command.getName()) && !promise.isDone();
}
} }

@ -24,6 +24,9 @@ public interface QueueCommand {
Set<String> PUBSUB_COMMANDS = new HashSet<String>(Arrays.asList("PSUBSCRIBE", "SUBSCRIBE", "PUNSUBSCRIBE", "UNSUBSCRIBE")); Set<String> PUBSUB_COMMANDS = new HashSet<String>(Arrays.asList("PSUBSCRIBE", "SUBSCRIBE", "PUNSUBSCRIBE", "UNSUBSCRIBE"));
Set<String> TIMEOUTLESS_COMMANDS = new HashSet<String>(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(),
RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName()));
List<CommandData<Object, Object>> getPubSubOperations(); List<CommandData<Object, Object>> getPubSubOperations();
} }

@ -24,7 +24,7 @@ import org.redisson.client.protocol.decoder.MultiDecoder;
public class RedisCommand<R> { public class RedisCommand<R> {
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 ValueType outParamType = ValueType.OBJECT;
private List<ValueType> inParamType = Arrays.asList(ValueType.OBJECT); private List<ValueType> inParamType = Arrays.asList(ValueType.OBJECT);

@ -15,10 +15,11 @@
*/ */
package org.redisson.client.protocol; package org.redisson.client.protocol;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.convertor.BitSetReplayConvertor; 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.MapScanResult;
import org.redisson.client.protocol.decoder.MapScanResultReplayDecoder; import org.redisson.client.protocol.decoder.MapScanResultReplayDecoder;
import org.redisson.client.protocol.decoder.NestedMultiDecoder; 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.ObjectFirstResultReplayDecoder;
import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; import org.redisson.client.protocol.decoder.ObjectListReplayDecoder;
import org.redisson.client.protocol.decoder.ObjectMapEntryReplayDecoder; import org.redisson.client.protocol.decoder.ObjectMapEntryReplayDecoder;
@ -57,6 +58,12 @@ import org.redisson.client.protocol.pubsub.PubSubStatusDecoder;
public interface RedisCommands { public interface RedisCommands {
RedisStrictCommand<Long> GEOADD = new RedisStrictCommand<Long>("GEOADD", 4);
RedisStrictCommand<Long> GEOADD_ENTRIES = new RedisStrictCommand<Long>("GEOADD", 2, ValueType.OBJECTS);
RedisCommand<Double> GEODIST = new RedisCommand<Double>("GEODIST", new DoubleReplayConvertor(), 2, Arrays.asList(ValueType.OBJECT, ValueType.OBJECT, ValueType.STRING));
RedisCommand<List<Object>> GEORADIUS = new RedisCommand<List<Object>>("GEORADIUS", new ObjectListReplayDecoder<Object>());
RedisCommand<List<Object>> GEORADIUSBYMEMBER = new RedisCommand<List<Object>>("GEORADIUSBYMEMBER", new ObjectListReplayDecoder<Object>(), 2);
RedisStrictCommand<Integer> KEYSLOT = new RedisStrictCommand<Integer>("CLUSTER", "KEYSLOT", new IntegerReplayConvertor()); RedisStrictCommand<Integer> KEYSLOT = new RedisStrictCommand<Integer>("CLUSTER", "KEYSLOT", new IntegerReplayConvertor());
RedisStrictCommand<Boolean> GETBIT = new RedisStrictCommand<Boolean>("GETBIT", new BooleanReplayConvertor()); RedisStrictCommand<Boolean> GETBIT = new RedisStrictCommand<Boolean>("GETBIT", new BooleanReplayConvertor());
@ -76,9 +83,10 @@ public interface RedisCommands {
RedisCommand<Boolean> ZADD_BOOL_RAW = new RedisCommand<Boolean>("ZADD", new BooleanAmountReplayConvertor()); RedisCommand<Boolean> ZADD_BOOL_RAW = new RedisCommand<Boolean>("ZADD", new BooleanAmountReplayConvertor());
RedisCommand<Boolean> ZADD_RAW = new RedisCommand<Boolean>("ZADD"); RedisCommand<Boolean> ZADD_RAW = new RedisCommand<Boolean>("ZADD");
RedisCommand<Long> ZADD = new RedisCommand<Long>("ZADD"); RedisCommand<Long> ZADD = new RedisCommand<Long>("ZADD");
RedisCommand<Boolean> ZREM = new RedisCommand<Boolean>("ZREM", new BooleanAmountReplayConvertor(), 2); RedisCommand<Boolean> ZREM = new RedisCommand<Boolean>("ZREM", new BooleanAmountReplayConvertor(), 2, ValueType.OBJECTS);
RedisStrictCommand<Integer> ZCARD_INT = new RedisStrictCommand<Integer>("ZCARD", new IntegerReplayConvertor()); RedisStrictCommand<Integer> ZCARD_INT = new RedisStrictCommand<Integer>("ZCARD", new IntegerReplayConvertor());
RedisStrictCommand<Long> ZCARD = new RedisStrictCommand<Long>("ZCARD"); RedisStrictCommand<Long> ZCARD = new RedisStrictCommand<Long>("ZCARD");
RedisStrictCommand<Long> ZCOUNT = new RedisStrictCommand<Long>("ZCOUNT");
RedisStrictCommand<Integer> ZLEXCOUNT = new RedisStrictCommand<Integer>("ZLEXCOUNT", new IntegerReplayConvertor()); RedisStrictCommand<Integer> ZLEXCOUNT = new RedisStrictCommand<Integer>("ZLEXCOUNT", new IntegerReplayConvertor());
RedisCommand<Boolean> ZSCORE_CONTAINS = new RedisCommand<Boolean>("ZSCORE", new BooleanNotNullReplayConvertor(), 2); RedisCommand<Boolean> ZSCORE_CONTAINS = new RedisCommand<Boolean>("ZSCORE", new BooleanNotNullReplayConvertor(), 2);
RedisStrictCommand<Double> ZSCORE = new RedisStrictCommand<Double>("ZSCORE", new DoubleReplayConvertor(), 2); RedisStrictCommand<Double> ZSCORE = new RedisStrictCommand<Double>("ZSCORE", new DoubleReplayConvertor(), 2);
@ -91,8 +99,10 @@ public interface RedisCommands {
RedisStrictCommand<Integer> ZREMRANGEBYSCORE = new RedisStrictCommand<Integer>("ZREMRANGEBYSCORE", new IntegerReplayConvertor()); RedisStrictCommand<Integer> ZREMRANGEBYSCORE = new RedisStrictCommand<Integer>("ZREMRANGEBYSCORE", new IntegerReplayConvertor());
RedisStrictCommand<Integer> ZREMRANGEBYLEX = new RedisStrictCommand<Integer>("ZREMRANGEBYLEX", new IntegerReplayConvertor()); RedisStrictCommand<Integer> ZREMRANGEBYLEX = new RedisStrictCommand<Integer>("ZREMRANGEBYLEX", new IntegerReplayConvertor());
RedisCommand<List<Object>> ZRANGEBYLEX = new RedisCommand<List<Object>>("ZRANGEBYLEX", new ObjectListReplayDecoder<Object>()); RedisCommand<List<Object>> ZRANGEBYLEX = new RedisCommand<List<Object>>("ZRANGEBYLEX", new ObjectListReplayDecoder<Object>());
RedisCommand<List<Object>> ZRANGEBYSCORE = new RedisCommand<List<Object>>("ZRANGEBYSCORE", new ObjectListReplayDecoder<Object>()); RedisCommand<Set<Object>> ZRANGEBYSCORE = new RedisCommand<Set<Object>>("ZRANGEBYSCORE", new ObjectSetReplayDecoder<Object>());
RedisCommand<List<Object>> ZRANGEBYSCORE_LIST = new RedisCommand<List<Object>>("ZRANGEBYSCORE", new ObjectListReplayDecoder<Object>());
RedisCommand<List<Object>> ZREVRANGEBYSCORE = new RedisCommand<List<Object>>("ZREVRANGEBYSCORE", new ObjectListReplayDecoder<Object>()); RedisCommand<List<Object>> ZREVRANGEBYSCORE = new RedisCommand<List<Object>>("ZREVRANGEBYSCORE", new ObjectListReplayDecoder<Object>());
RedisCommand<List<ScoredEntry<Object>>> ZREVRANGEBYSCORE_ENTRY = new RedisCommand<List<ScoredEntry<Object>>>("ZREVRANGEBYSCORE", new ScoredSortedSetReplayDecoder<Object>());
RedisCommand<List<ScoredEntry<Object>>> ZRANGE_ENTRY = new RedisCommand<List<ScoredEntry<Object>>>("ZRANGE", new ScoredSortedSetReplayDecoder<Object>()); RedisCommand<List<ScoredEntry<Object>>> ZRANGE_ENTRY = new RedisCommand<List<ScoredEntry<Object>>>("ZRANGE", new ScoredSortedSetReplayDecoder<Object>());
RedisCommand<List<ScoredEntry<Object>>> ZRANGEBYSCORE_ENTRY = new RedisCommand<List<ScoredEntry<Object>>>("ZRANGEBYSCORE", new ScoredSortedSetReplayDecoder<Object>()); RedisCommand<List<ScoredEntry<Object>>> ZRANGEBYSCORE_ENTRY = new RedisCommand<List<ScoredEntry<Object>>>("ZRANGEBYSCORE", new ScoredSortedSetReplayDecoder<Object>());
RedisCommand<ListScanResult<Object>> ZSCAN = new RedisCommand<ListScanResult<Object>>("ZSCAN", new NestedMultiDecoder(new ScoredSortedSetScanDecoder<Object>(), new ScoredSortedSetScanReplayDecoder()), ValueType.OBJECT); RedisCommand<ListScanResult<Object>> ZSCAN = new RedisCommand<ListScanResult<Object>>("ZSCAN", new NestedMultiDecoder(new ScoredSortedSetScanDecoder<Object>(), new ScoredSortedSetScanReplayDecoder()), ValueType.OBJECT);
@ -107,29 +117,33 @@ public interface RedisCommands {
RedisStrictCommand<Void> MULTI = new RedisStrictCommand<Void>("MULTI", new VoidReplayConvertor()); RedisStrictCommand<Void> MULTI = new RedisStrictCommand<Void>("MULTI", new VoidReplayConvertor());
RedisCommand<List<Object>> EXEC = new RedisCommand<List<Object>>("EXEC", new ObjectListReplayDecoder<Object>()); RedisCommand<List<Object>> EXEC = new RedisCommand<List<Object>>("EXEC", new ObjectListReplayDecoder<Object>());
RedisCommand<Long> SREM = new RedisCommand<Long>("SREM", 2, ValueType.OBJECTS);
RedisCommand<Boolean> SADD_BOOL = new RedisCommand<Boolean>("SADD", new BooleanAmountReplayConvertor(), 2, ValueType.OBJECTS); RedisCommand<Boolean> SADD_BOOL = new RedisCommand<Boolean>("SADD", new BooleanAmountReplayConvertor(), 2, ValueType.OBJECTS);
RedisStrictCommand<Long> SADD = new RedisStrictCommand<Long>("SADD", 2, ValueType.OBJECTS); RedisStrictCommand<Long> SADD = new RedisStrictCommand<Long>("SADD", 2, ValueType.OBJECTS);
RedisCommand<Object> SPOP_SINGLE = new RedisCommand<Object>("SPOP"); RedisCommand<Object> SPOP_SINGLE = new RedisCommand<Object>("SPOP");
RedisCommand<Boolean> SADD_SINGLE = new RedisCommand<Boolean>("SADD", new BooleanReplayConvertor(), 2); RedisCommand<Boolean> SADD_SINGLE = new RedisCommand<Boolean>("SADD", new BooleanReplayConvertor(), 2);
RedisCommand<Boolean> SREM_SINGLE = new RedisCommand<Boolean>("SREM", new BooleanReplayConvertor(), 2); RedisCommand<Boolean> SREM_SINGLE = new RedisCommand<Boolean>("SREM", new BooleanAmountReplayConvertor(), 2, ValueType.OBJECTS);
RedisCommand<Boolean> SMOVE = new RedisCommand<Boolean>("SMOVE", new BooleanReplayConvertor(), 3); RedisCommand<Boolean> SMOVE = new RedisCommand<Boolean>("SMOVE", new BooleanReplayConvertor(), 3);
RedisCommand<Set<Object>> SMEMBERS = new RedisCommand<Set<Object>>("SMEMBERS", new ObjectSetReplayDecoder()); RedisCommand<Set<Object>> SMEMBERS = new RedisCommand<Set<Object>>("SMEMBERS", new ObjectSetReplayDecoder<Object>());
RedisCommand<ListScanResult<Object>> SSCAN = new RedisCommand<ListScanResult<Object>>("SSCAN", new NestedMultiDecoder(new ObjectListReplayDecoder<Object>(), new ListScanResultReplayDecoder()), ValueType.OBJECT); RedisCommand<ListScanResult<Object>> SSCAN = new RedisCommand<ListScanResult<Object>>("SSCAN", new NestedMultiDecoder(new ObjectListReplayDecoder<Object>(), new ListScanResultReplayDecoder()), ValueType.OBJECT);
RedisCommand<ListScanResult<Object>> EVAL_SSCAN = new RedisCommand<ListScanResult<Object>>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder<Object>(), new ListScanResultReplayDecoder()), ValueType.OBJECT); RedisCommand<ListScanResult<Object>> EVAL_SSCAN = new RedisCommand<ListScanResult<Object>>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder<Object>(), new ListScanResultReplayDecoder()), ValueType.OBJECT);
RedisCommand<ListScanResult<Object>> EVAL_ZSCAN = new RedisCommand<ListScanResult<Object>>("EVAL", new NestedMultiDecoder(new ObjectListReplayDecoder<Object>(), new ListScanResultReplayDecoder()), ValueType.OBJECT);
RedisCommand<Boolean> SISMEMBER = new RedisCommand<Boolean>("SISMEMBER", new BooleanReplayConvertor(), 2); RedisCommand<Boolean> SISMEMBER = new RedisCommand<Boolean>("SISMEMBER", new BooleanReplayConvertor(), 2);
RedisStrictCommand<Integer> SCARD_INT = new RedisStrictCommand<Integer>("SCARD", new IntegerReplayConvertor()); RedisStrictCommand<Integer> SCARD_INT = new RedisStrictCommand<Integer>("SCARD", new IntegerReplayConvertor());
RedisStrictCommand<Long> SCARD = new RedisStrictCommand<Long>("SCARD"); RedisStrictCommand<Long> SCARD = new RedisStrictCommand<Long>("SCARD");
RedisStrictCommand<Integer> SUNIONSTORE_INT = new RedisStrictCommand<Integer>("SUNIONSTORE", new IntegerReplayConvertor()); RedisStrictCommand<Integer> SUNIONSTORE_INT = new RedisStrictCommand<Integer>("SUNIONSTORE", new IntegerReplayConvertor());
RedisStrictCommand<Integer> SDIFFSTORE_INT = new RedisStrictCommand<Integer>("SDIFFSTORE", new IntegerReplayConvertor());
RedisStrictCommand<Integer> SINTERSTORE_INT = new RedisStrictCommand<Integer>("SINTERSTORE", new IntegerReplayConvertor());
RedisStrictCommand<Long> SUNIONSTORE = new RedisStrictCommand<Long>("SUNIONSTORE"); RedisStrictCommand<Long> SUNIONSTORE = new RedisStrictCommand<Long>("SUNIONSTORE");
RedisCommand<Set<Object>> SUNION = new RedisCommand<Set<Object>>("SUNION", new ObjectSetReplayDecoder()); RedisCommand<Set<Object>> SUNION = new RedisCommand<Set<Object>>("SUNION", new ObjectSetReplayDecoder<Object>());
RedisCommand<Set<Object>> SDIFF = new RedisCommand<Set<Object>>("SDIFF", new ObjectSetReplayDecoder<Object>());
RedisCommand<Set<Object>> SINTER = new RedisCommand<Set<Object>>("SINTER", new ObjectSetReplayDecoder<Object>());
RedisCommand<Void> LSET = new RedisCommand<Void>("LSET", new VoidReplayConvertor(), 3); RedisCommand<Void> LSET = new RedisCommand<Void>("LSET", new VoidReplayConvertor(), 3);
RedisCommand<Object> LPOP = new RedisCommand<Object>("LPOP"); RedisCommand<Object> LPOP = new RedisCommand<Object>("LPOP");
RedisCommand<Boolean> LREM_SINGLE = new RedisCommand<Boolean>("LREM", new BooleanReplayConvertor(), 3); RedisCommand<Boolean> LREM_SINGLE = new RedisCommand<Boolean>("LREM", new BooleanReplayConvertor(), 3);
RedisStrictCommand<Long> LREM = new RedisStrictCommand<Long>("LREM", 3); RedisStrictCommand<Long> LREM = new RedisStrictCommand<Long>("LREM", 3);
RedisCommand<Object> LINDEX = new RedisCommand<Object>("LINDEX"); RedisCommand<Object> LINDEX = new RedisCommand<Object>("LINDEX");
RedisCommand<Object> LINSERT = new RedisCommand<Object>("LINSERT", 3, ValueType.OBJECTS); RedisCommand<Integer> LINSERT = new RedisCommand<Integer>("LINSERT", new IntegerReplayConvertor(), 3, ValueType.OBJECTS);
RedisStrictCommand<Integer> LLEN_INT = new RedisStrictCommand<Integer>("LLEN", new IntegerReplayConvertor()); RedisStrictCommand<Integer> LLEN_INT = new RedisStrictCommand<Integer>("LLEN", new IntegerReplayConvertor());
RedisStrictCommand<Long> LLEN = new RedisStrictCommand<Long>("LLEN"); RedisStrictCommand<Long> LLEN = new RedisStrictCommand<Long>("LLEN");
RedisStrictCommand<Void> LTRIM = new RedisStrictCommand<Void>("LTRIM", new VoidReplayConvertor()); RedisStrictCommand<Void> LTRIM = new RedisStrictCommand<Void>("LTRIM", new VoidReplayConvertor());
@ -169,9 +183,10 @@ public interface RedisCommands {
RedisStrictCommand<Long> EVAL_LONG = new RedisStrictCommand<Long>("EVAL"); RedisStrictCommand<Long> EVAL_LONG = new RedisStrictCommand<Long>("EVAL");
RedisStrictCommand<Void> EVAL_VOID = new RedisStrictCommand<Void>("EVAL", new VoidReplayConvertor()); RedisStrictCommand<Void> EVAL_VOID = new RedisStrictCommand<Void>("EVAL", new VoidReplayConvertor());
RedisCommand<List<Object>> EVAL_LIST = new RedisCommand<List<Object>>("EVAL", new ObjectListReplayDecoder<Object>()); RedisCommand<List<Object>> EVAL_LIST = new RedisCommand<List<Object>>("EVAL", new ObjectListReplayDecoder<Object>());
RedisCommand<Set<Object>> EVAL_SET = new RedisCommand<Set<Object>>("EVAL", new ObjectSetReplayDecoder()); RedisCommand<Set<Object>> EVAL_SET = new RedisCommand<Set<Object>>("EVAL", new ObjectSetReplayDecoder<Object>());
RedisCommand<Object> EVAL_OBJECT = new RedisCommand<Object>("EVAL"); RedisCommand<Object> EVAL_OBJECT = new RedisCommand<Object>("EVAL");
RedisCommand<Object> EVAL_MAP_VALUE = new RedisCommand<Object>("EVAL", ValueType.MAP_VALUE); RedisCommand<Object> EVAL_MAP_VALUE = new RedisCommand<Object>("EVAL", ValueType.MAP_VALUE);
RedisCommand<Set<Entry<Object, Object>>> EVAL_MAP_ENTRY = new RedisCommand<Set<Entry<Object, Object>>>("EVAL", new ObjectMapEntryReplayDecoder(), ValueType.MAP);
RedisCommand<List<Object>> EVAL_MAP_VALUE_LIST = new RedisCommand<List<Object>>("EVAL", new ObjectListReplayDecoder<Object>(), ValueType.MAP_VALUE); RedisCommand<List<Object>> EVAL_MAP_VALUE_LIST = new RedisCommand<List<Object>>("EVAL", new ObjectListReplayDecoder<Object>(), ValueType.MAP_VALUE);
RedisStrictCommand<Long> INCR = new RedisStrictCommand<Long>("INCR"); RedisStrictCommand<Long> INCR = new RedisStrictCommand<Long>("INCR");
@ -189,6 +204,7 @@ public interface RedisCommands {
RedisStrictCommand<List<String>> KEYS = new RedisStrictCommand<List<String>>("KEYS", new StringListReplayDecoder()); RedisStrictCommand<List<String>> KEYS = new RedisStrictCommand<List<String>>("KEYS", new StringListReplayDecoder());
RedisCommand<List<Object>> MGET = new RedisCommand<List<Object>>("MGET", new ObjectListReplayDecoder<Object>()); RedisCommand<List<Object>> MGET = new RedisCommand<List<Object>>("MGET", new ObjectListReplayDecoder<Object>());
RedisStrictCommand<Void> MSET = new RedisStrictCommand<Void>("MSET", new VoidReplayConvertor()); RedisStrictCommand<Void> MSET = new RedisStrictCommand<Void>("MSET", new VoidReplayConvertor());
RedisStrictCommand<Boolean> MSETNX = new RedisStrictCommand<Boolean>("MSETNX", new BooleanReplayConvertor());
RedisCommand<Boolean> HSETNX = new RedisCommand<Boolean>("HSETNX", new BooleanReplayConvertor(), 2, ValueType.MAP); RedisCommand<Boolean> HSETNX = new RedisCommand<Boolean>("HSETNX", new BooleanReplayConvertor(), 2, ValueType.MAP);
RedisCommand<Boolean> HSET = new RedisCommand<Boolean>("HSET", new BooleanReplayConvertor(), 2, ValueType.MAP); RedisCommand<Boolean> HSET = new RedisCommand<Boolean>("HSET", new BooleanReplayConvertor(), 2, ValueType.MAP);
@ -238,7 +254,7 @@ public interface RedisCommands {
RedisStrictCommand<List<String>> SENTINEL_GET_MASTER_ADDR_BY_NAME = new RedisStrictCommand<List<String>>("SENTINEL", "GET-MASTER-ADDR-BY-NAME", new StringListReplayDecoder()); RedisStrictCommand<List<String>> SENTINEL_GET_MASTER_ADDR_BY_NAME = new RedisStrictCommand<List<String>>("SENTINEL", "GET-MASTER-ADDR-BY-NAME", new StringListReplayDecoder());
RedisCommand<List<Map<String, String>>> SENTINEL_SLAVES = new RedisCommand<List<Map<String, String>>>("SENTINEL", "SLAVES", RedisCommand<List<Map<String, String>>> SENTINEL_SLAVES = new RedisCommand<List<Map<String, String>>>("SENTINEL", "SLAVES",
new NestedMultiDecoder2(new ObjectMapReplayDecoder(), new ListResultReplayDecoder()), ValueType.OBJECT new FlatNestedMultiDecoder(new ObjectMapReplayDecoder(), new ListResultReplayDecoder()), ValueType.OBJECT
); );
RedisStrictCommand<String> INFO_REPLICATION = new RedisStrictCommand<String>("INFO", "replication", new StringDataDecoder()); RedisStrictCommand<String> INFO_REPLICATION = new RedisStrictCommand<String>("INFO", "replication", new StringDataDecoder());

@ -19,7 +19,7 @@ public class DoubleReplayConvertor extends SingleConvertor<Double> {
@Override @Override
public Double convert(Object obj) { public Double convert(Object obj) {
if (obj == null) { if (obj == null || obj.toString().isEmpty()) {
return null; return null;
} }
return Double.valueOf(obj.toString()); return Double.valueOf(obj.toString());

@ -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();
}

@ -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<T> extends NestedMultiDecoder {
public FlatNestedMultiDecoder(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> 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);
}
}

@ -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<List<Object>> {
private final ThreadLocal<Integer> pos = new ThreadLocal<Integer>();
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<Object> decode(List<Object> parts, State state) {
return parts;
}
}

@ -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<Map<Object, Object>> {
private final ThreadLocal<Integer> pos = new ThreadLocal<Integer>();
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<Object, Object> decode(List<Object> parts, State state) {
Map<Object, Object> result = new HashMap<Object, Object>(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;
}
}

@ -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<Map<Object, Object>> {
@Override
public Object decode(ByteBuf buf, State state) {
throw new UnsupportedOperationException();
}
@Override
public Map<Object, Object> decode(List<Object> parts, State state) {
Map<Object, Object> result = new HashMap<Object, Object>(parts.size());
for (Object object : parts) {
List<Object> vals = ((List<Object>) object);
result.put(vals.get(0), vals.get(1));
}
return result;
}
@Override
public boolean isApplicable(int paramNum, State state) {
return false;
}
}

@ -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<GeoPosition> {
@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<Object> 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);
}
}

@ -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<Map<Object, Object>> {
private final List<Object> args;
public GeoPositionMapDecoder(List<Object> 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<Object, Object> decode(List<Object> parts, State state) {
if (parts.isEmpty()) {
return Collections.emptyMap();
}
Map<Object, Object> result = new HashMap<Object, Object>(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;
}
}

@ -27,7 +27,7 @@ public class KeyValueObjectDecoder implements MultiDecoder<Object> {
@Override @Override
public Object decode(ByteBuf buf, State state) { public Object decode(ByteBuf buf, State state) {
String status = buf.toString(CharsetUtil.UTF_8); String status = buf.toString(CharsetUtil.UTF_8);
buf.skipBytes(2); buf.skipBytes(1);
return status; return status;
} }

@ -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<T> implements MultiDecoder<Object> {
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<Object> 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;
}
}

@ -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<Object> {
@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<Object> parts, State state) {
return null;
}
}

@ -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<K, V> extends MapScanResult<K, V> {
private final List<K> idleKeys;
public MapCacheScanResult(Long pos, Map<K, V> values, List<K> idleKeys) {
super(pos, values);
this.idleKeys = idleKeys;
};
public List<K> getIdleKeys() {
return idleKeys;
}
}

@ -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<MapCacheScanResult<Object, Object>> {
@Override
public Object decode(ByteBuf buf, State state) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public MapCacheScanResult<Object, Object> decode(List<Object> parts, State state) {
Long pos = (Long)parts.get(0);
Map<Object, Object> values = (Map<Object, Object>)parts.get(1);
List<Object> idleKeys = (List<Object>) parts.get(2);
return new MapCacheScanResult<Object, Object>(pos, values, idleKeys);
}
@Override
public boolean isApplicable(int paramNum, State state) {
return false;
}
}

@ -16,9 +16,6 @@
package org.redisson.client.protocol.decoder; package org.redisson.client.protocol.decoder;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.List; import java.util.List;
import org.redisson.client.handler.State; import org.redisson.client.handler.State;
@ -27,55 +24,120 @@ import io.netty.buffer.ByteBuf;
public class NestedMultiDecoder<T> implements MultiDecoder<Object> { public class NestedMultiDecoder<T> implements MultiDecoder<Object> {
public static class DecoderState { public static class NestedDecoderState implements DecoderState {
Deque<MultiDecoder<?>> decoders; int decoderIndex;
Deque<MultiDecoder<?>> flipDecoders; int flipDecoderIndex;
public DecoderState(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> secondDecoder) { public NestedDecoderState() {
}
public NestedDecoderState(int decoderIndex, int flipDecoderIndex) {
super(); super();
this.decoders = new ArrayDeque<MultiDecoder<?>>(Arrays.asList(firstDecoder, secondDecoder)); this.decoderIndex = decoderIndex;
this.flipDecoders = new ArrayDeque<MultiDecoder<?>>(Arrays.asList(firstDecoder, secondDecoder, firstDecoder)); this.flipDecoderIndex = flipDecoderIndex;
} }
public Deque<MultiDecoder<?>> getDecoders() { public int getDecoderIndex() {
return decoders; return decoderIndex;
}
public void resetDecoderIndex() {
decoderIndex = 0;
}
public void incDecoderIndex() {
decoderIndex++;
} }
public Deque<MultiDecoder<?>> getFlipDecoders() { public int getFlipDecoderIndex() {
return flipDecoders; return flipDecoderIndex;
}
public void resetFlipDecoderIndex() {
flipDecoderIndex = 0;
}
public void incFlipDecoderIndex() {
flipDecoderIndex++;
}
@Override
public DecoderState copy() {
return new NestedDecoderState(decoderIndex, flipDecoderIndex);
}
@Override
public String toString() {
return "NestedDecoderState [decoderIndex=" + decoderIndex + ", flipDecoderIndex=" + flipDecoderIndex + "]";
} }
} }
private final MultiDecoder<Object> firstDecoder; protected final MultiDecoder<Object> firstDecoder;
private final MultiDecoder<Object> secondDecoder; protected final MultiDecoder<Object> secondDecoder;
private MultiDecoder<Object> thirdDecoder;
private boolean handleEmpty;
public NestedMultiDecoder(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> secondDecoder) { public NestedMultiDecoder(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> secondDecoder) {
this(firstDecoder, secondDecoder, false);
}
public NestedMultiDecoder(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> secondDecoder, boolean handleEmpty) {
this(firstDecoder, secondDecoder, null, handleEmpty);
}
public NestedMultiDecoder(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> secondDecoder, MultiDecoder<Object> thirdDecoder) {
this(firstDecoder, secondDecoder, thirdDecoder, false);
}
public NestedMultiDecoder(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> secondDecoder, MultiDecoder<Object> thirdDecoder, boolean handleEmpty) {
this.firstDecoder = firstDecoder; this.firstDecoder = firstDecoder;
this.secondDecoder = secondDecoder; this.secondDecoder = secondDecoder;
this.thirdDecoder = thirdDecoder;
this.handleEmpty = handleEmpty;
} }
@Override @Override
public Object decode(ByteBuf buf, State state) throws IOException { public Object decode(ByteBuf buf, State state) throws IOException {
DecoderState ds = getDecoder(state); NestedDecoderState ds = getDecoder(state);
return ds.getFlipDecoders().peek().decode(buf, state);
MultiDecoder<?> decoder = null;
if (ds.getFlipDecoderIndex() == 2) {
decoder = firstDecoder;
}
if (ds.getFlipDecoderIndex() == 1) {
decoder = secondDecoder;
}
return decoder.decode(buf, state);
} }
@Override @Override
public boolean isApplicable(int paramNum, State state) { public boolean isApplicable(int paramNum, State state) {
DecoderState ds = getDecoder(state); NestedDecoderState ds = getDecoder(state);
if (paramNum == 0) { if (paramNum == 0) {
ds.getFlipDecoders().poll(); ds.incFlipDecoderIndex();
ds.resetDecoderIndex();
}
// used only with thirdDecoder
if (ds.getFlipDecoderIndex() == 3) {
ds.resetFlipDecoderIndex();
ds.incFlipDecoderIndex();
} }
return ds.getFlipDecoders().peek().isApplicable(paramNum, state);
MultiDecoder<?> decoder = null;
if (ds.getFlipDecoderIndex() == 2) {
decoder = firstDecoder;
}
if (ds.getFlipDecoderIndex() == 1) {
decoder = secondDecoder;
}
return decoder.isApplicable(paramNum, state);
} }
private DecoderState getDecoder(State state) { protected final NestedDecoderState getDecoder(State state) {
DecoderState ds = state.getDecoderState(); NestedDecoderState ds = state.getDecoderState();
if (ds == null) { if (ds == null) {
ds = new DecoderState(firstDecoder, secondDecoder); ds = new NestedDecoderState();
state.setDecoderState(ds); state.setDecoderState(ds);
} }
return ds; return ds;
@ -83,8 +145,32 @@ public class NestedMultiDecoder<T> implements MultiDecoder<Object> {
@Override @Override
public Object decode(List<Object> parts, State state) { public Object decode(List<Object> parts, State state) {
DecoderState ds = getDecoder(state); if (parts.isEmpty() && state.getDecoderState() == null && handleEmpty) {
return ds.getDecoders().poll().decode(parts, state); 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);
} }
} }

@ -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<T> implements MultiDecoder<Object> {
private final MultiDecoder<Object> firstDecoder;
private final MultiDecoder<Object> secondDecoder;
public NestedMultiDecoder2(MultiDecoder<Object> firstDecoder, MultiDecoder<Object> 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<Object> 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);
}
}

@ -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<T> implements MultiDecoder<List<T>> {
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<T> decode(List<Object> parts, State state) {
return (List<T>) parts;
}
@Override
public boolean isApplicable(int paramNum, State state) {
return false;
}
}

@ -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<Map<Object, Object>> {
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<Object, Object> decode(List<Object> parts, State state) {
Map<Object, Object> result = new HashMap<Object, Object>(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;
}
}

@ -23,7 +23,7 @@ import org.redisson.client.handler.State;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
public class ObjectSetReplayDecoder implements MultiDecoder<Set<Object>> { public class ObjectSetReplayDecoder<T> implements MultiDecoder<Set<T>> {
@Override @Override
public Object decode(ByteBuf buf, State state) { public Object decode(ByteBuf buf, State state) {
@ -31,8 +31,8 @@ public class ObjectSetReplayDecoder implements MultiDecoder<Set<Object>> {
} }
@Override @Override
public Set<Object> decode(List<Object> parts, State state) { public Set<T> decode(List<Object> parts, State state) {
return new HashSet<Object>(parts); return new HashSet(parts);
} }
@Override @Override

@ -75,17 +75,30 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
RedisConnection connection = connectionFuture.syncUninterruptibly().getNow(); RedisConnection connection = connectionFuture.syncUninterruptibly().getNow();
String nodesValue = connection.sync(RedisCommands.CLUSTER_NODES); String nodesValue = connection.sync(RedisCommands.CLUSTER_NODES);
log.debug("cluster nodes state from {} during startup:\n{}", connection.getRedisClient().getAddr(), nodesValue);
Collection<ClusterPartition> partitions = parsePartitions(nodesValue); Collection<ClusterPartition> partitions = parsePartitions(nodesValue);
List<Future<Collection<Future<Void>>>> futures = new ArrayList<Future<Collection<Future<Void>>>>(); List<Future<Collection<Future<Void>>>> futures = new ArrayList<Future<Collection<Future<Void>>>>();
for (ClusterPartition partition : partitions) { for (ClusterPartition partition : partitions) {
if (partition.isMasterFail()) {
continue;
}
Future<Collection<Future<Void>>> masterFuture = addMasterEntry(partition, cfg); Future<Collection<Future<Void>>> masterFuture = addMasterEntry(partition, cfg);
futures.add(masterFuture); futures.add(masterFuture);
} }
for (Future<Collection<Future<Void>>> masterFuture : futures) { for (Future<Collection<Future<Void>>> masterFuture : futures) {
masterFuture.syncUninterruptibly(); masterFuture.awaitUninterruptibly();
if (!masterFuture.isSuccess()) {
log.error("Can't connect to master node.", masterFuture.cause());
continue;
}
for (Future<Void> future : masterFuture.getNow()) { for (Future<Void> future : masterFuture.getNow()) {
future.syncUninterruptibly(); future.awaitUninterruptibly();
if (!future.isSuccess()) {
log.error("Can't add nodes.", masterFuture.cause());
continue;
}
} }
} }
break; break;
@ -100,7 +113,13 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
throw new RedisConnectionException("Can't connect to servers!", lastException); 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<RedisConnection> connect(ClusterServersConfig cfg, final URI addr) { private Future<RedisConnection> connect(ClusterServersConfig cfg, final URI addr) {
@ -175,13 +194,13 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
} }
final RedisConnection connection = future.getNow(); final RedisConnection connection = future.getNow();
Future<Map<String, String>> clusterFuture = connection.async(RedisCommands.CLUSTER_INFO); Future<Map<String, String>> clusterFuture = connection.asyncWithTimeout(null, RedisCommands.CLUSTER_INFO);
clusterFuture.addListener(new FutureListener<Map<String, String>>() { clusterFuture.addListener(new FutureListener<Map<String, String>>() {
@Override @Override
public void operationComplete(Future<Map<String, String>> future) throws Exception { public void operationComplete(Future<Map<String, String>> future) throws Exception {
if (!future.isSuccess()) { 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()); result.setFailure(future.cause());
return; return;
} }
@ -243,26 +262,31 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
return result; return result;
} }
private void scheduleClusterChangeCheck(final ClusterServersConfig cfg) { private void scheduleClusterChangeCheck(final ClusterServersConfig cfg, final Iterator<URI> iterator) {
monitorFuture = GlobalEventExecutor.INSTANCE.schedule(new Runnable() { monitorFuture = GlobalEventExecutor.INSTANCE.schedule(new Runnable() {
@Override @Override
public void run() { public void run() {
List<URI> nodes = new ArrayList<URI>();
List<URI> slaves = new ArrayList<URI>();
AtomicReference<Throwable> lastException = new AtomicReference<Throwable>(); AtomicReference<Throwable> lastException = new AtomicReference<Throwable>();
for (ClusterPartition partition : lastPartitions.values()) { Iterator<URI> nodesIterator = iterator;
if (!partition.isMasterFail()) { if (nodesIterator == null) {
nodes.add(partition.getMasterAddress()); List<URI> nodes = new ArrayList<URI>();
List<URI> slaves = new ArrayList<URI>();
for (ClusterPartition partition : lastPartitions.values()) {
if (!partition.isMasterFail()) {
nodes.add(partition.getMasterAddress());
}
Set<URI> partitionSlaves = new HashSet<URI>(partition.getSlaveAddresses());
partitionSlaves.removeAll(partition.getFailedSlaveAddresses());
slaves.addAll(partitionSlaves);
} }
// master nodes first
nodes.addAll(slaves);
Set<URI> partitionSlaves = new HashSet<URI>(partition.getSlaveAddresses()); nodesIterator = nodes.iterator();
partitionSlaves.removeAll(partition.getFailedSlaveAddresses());
slaves.addAll(partitionSlaves);
} }
// master nodes first
nodes.addAll(slaves);
checkClusterState(cfg, nodes.iterator(), lastException); checkClusterState(cfg, nodesIterator, lastException);
} }
}, cfg.getScanInterval(), TimeUnit.MILLISECONDS); }, cfg.getScanInterval(), TimeUnit.MILLISECONDS);
@ -271,7 +295,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
private void checkClusterState(final ClusterServersConfig cfg, final Iterator<URI> iterator, final AtomicReference<Throwable> lastException) { private void checkClusterState(final ClusterServersConfig cfg, final Iterator<URI> iterator, final AtomicReference<Throwable> lastException) {
if (!iterator.hasNext()) { if (!iterator.hasNext()) {
log.error("Can't update cluster state", lastException.get()); log.error("Can't update cluster state", lastException.get());
scheduleClusterChangeCheck(cfg); scheduleClusterChangeCheck(cfg, null);
return; return;
} }
URI uri = iterator.next(); URI uri = iterator.next();
@ -286,19 +310,20 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
} }
RedisConnection connection = future.getNow(); RedisConnection connection = future.getNow();
updateClusterState(cfg, connection); updateClusterState(cfg, connection, iterator);
} }
}); });
} }
private void updateClusterState(final ClusterServersConfig cfg, final RedisConnection connection) { private void updateClusterState(final ClusterServersConfig cfg, final RedisConnection connection, final Iterator<URI> iterator) {
Future<String> future = connection.async(RedisCommands.CLUSTER_NODES); Future<String> future = connection.asyncWithTimeout(null, RedisCommands.CLUSTER_NODES);
future.addListener(new FutureListener<String>() { future.addListener(new FutureListener<String>() {
@Override @Override
public void operationComplete(Future<String> future) throws Exception { public void operationComplete(Future<String> future) throws Exception {
if (!future.isSuccess()) { if (!future.isSuccess()) {
log.error("Can't execute CLUSTER_NODES with " + connection.getRedisClient().getAddr(), future.cause()); log.error("Can't execute CLUSTER_NODES with " + connection.getRedisClient().getAddr(), future.cause());
scheduleClusterChangeCheck(cfg); close(connection);
scheduleClusterChangeCheck(cfg, iterator);
return; return;
} }
@ -309,7 +334,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
checkMasterNodesChange(newPartitions); checkMasterNodesChange(newPartitions);
checkSlaveNodesChange(newPartitions); checkSlaveNodesChange(newPartitions);
checkSlotsChange(cfg, newPartitions); checkSlotsChange(cfg, newPartitions);
scheduleClusterChangeCheck(cfg); scheduleClusterChangeCheck(cfg, null);
} }
}); });
} }
@ -346,8 +371,9 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
failedSlaves.removeAll(currentPart.getFailedSlaveAddresses()); failedSlaves.removeAll(currentPart.getFailedSlaveAddresses());
for (URI uri : failedSlaves) { for (URI uri : failedSlaves) {
currentPart.addFailedSlaveAddress(uri); currentPart.addFailedSlaveAddress(uri);
slaveDown(entry, uri.getHost(), uri.getPort(), FreezeReason.MANAGER); if (entry.slaveDown(uri.getHost(), uri.getPort(), FreezeReason.MANAGER)) {
log.warn("slave: {} has down for slot ranges: {}", uri, currentPart.getSlotRanges()); log.warn("slave: {} has down for slot ranges: {}", uri, currentPart.getSlotRanges());
}
} }
} }
@ -358,8 +384,9 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
for (URI uri : removedSlaves) { for (URI uri : removedSlaves) {
currentPart.removeSlaveAddress(uri); currentPart.removeSlaveAddress(uri);
slaveDown(entry, uri.getHost(), uri.getPort(), FreezeReason.MANAGER); if (entry.slaveDown(uri.getHost(), uri.getPort(), FreezeReason.MANAGER)) {
log.info("slave {} removed for slot ranges: {}", uri, currentPart.getSlotRanges()); log.info("slave {} removed for slot ranges: {}", uri, currentPart.getSlotRanges());
}
} }
Set<URI> addedSlaves = new HashSet<URI>(newPart.getSlaveAddresses()); Set<URI> addedSlaves = new HashSet<URI>(newPart.getSlaveAddresses());
@ -417,7 +444,6 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
URI oldUri = currentPart.getMasterAddress(); URI oldUri = currentPart.getMasterAddress();
changeMaster(currentSlotRange, newUri.getHost(), newUri.getPort()); changeMaster(currentSlotRange, newUri.getHost(), newUri.getPort());
slaveDown(currentSlotRange, oldUri.getHost(), oldUri.getPort(), FreezeReason.MANAGER);
currentPart.setMasterAddress(newMasterPart.getMasterAddress()); currentPart.setMasterAddress(newMasterPart.getMasterAddress());
} }
@ -483,7 +509,9 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
List<ClusterPartition> currentPartitions = new ArrayList<ClusterPartition>(lastPartitions.values()); List<ClusterPartition> currentPartitions = new ArrayList<ClusterPartition>(lastPartitions.values());
for (ClusterPartition currentPartition : currentPartitions) { for (ClusterPartition currentPartition : currentPartitions) {
for (ClusterPartition newPartition : newPartitions) { 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; continue;
} }

@ -93,4 +93,11 @@ public class ClusterPartition {
failedSlaves.remove(uri); failedSlaves.remove(uri);
} }
@Override
public String toString() {
return "ClusterPartition [nodeId=" + nodeId + ", masterFail=" + masterFail + ", masterAddress=" + masterAddress
+ ", slaveAddresses=" + slaveAddresses + ", failedSlaves=" + failedSlaves + ", slotRanges=" + slotRanges
+ "]";
}
} }

@ -124,6 +124,7 @@ public class JsonJacksonCodec implements Codec {
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE)); .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true); objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class); objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class);
} }

@ -18,6 +18,7 @@ package org.redisson.command;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import org.redisson.SlotCallback; import org.redisson.SlotCallback;
import org.redisson.client.RedisException; import org.redisson.client.RedisException;
@ -38,6 +39,8 @@ public interface CommandAsyncExecutor {
<V> RedisException convertException(Future<V> future); <V> RedisException convertException(Future<V> future);
boolean await(Future<?> future, long timeout, TimeUnit timeoutUnit) throws InterruptedException;
<V> V get(Future<V> future); <V> V get(Future<V> future);
<T, R> Future<R> writeAsync(Integer slot, Codec codec, RedisCommand<T> command, Object ... params); <T, R> Future<R> writeAsync(Integer slot, Codec codec, RedisCommand<T> command, Object ... params);

@ -20,13 +20,16 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.redisson.RedisClientResult; import org.redisson.RedisClientResult;
import org.redisson.RedissonShutdownException;
import org.redisson.SlotCallback; import org.redisson.SlotCallback;
import org.redisson.client.RedisAskException; import org.redisson.client.RedisAskException;
import org.redisson.client.RedisConnection; import org.redisson.client.RedisConnection;
@ -38,6 +41,7 @@ import org.redisson.client.WriteRedisConnectionException;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData; import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.QueueCommand;
import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.RedisCommands;
import org.redisson.cluster.ClusterSlotRange; import org.redisson.cluster.ClusterSlotRange;
@ -47,6 +51,7 @@ import org.redisson.connection.NodeSource.Redirect;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.util.Timeout; 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.Future;
import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
/** /**
* *
@ -67,9 +73,6 @@ public class CommandAsyncService implements CommandAsyncExecutor {
final ConnectionManager connectionManager; final ConnectionManager connectionManager;
private final Set<String> skipTimeout = new HashSet<String>(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(),
RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName()));
public CommandAsyncService(ConnectionManager connectionManager) { public CommandAsyncService(ConnectionManager connectionManager) {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
} }
@ -81,13 +84,38 @@ public class CommandAsyncService implements CommandAsyncExecutor {
@Override @Override
public <V> V get(Future<V> future) { public <V> V get(Future<V> future) {
future.awaitUninterruptibly(); final CountDownLatch l = new CountDownLatch(1);
future.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> 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()) { if (future.isSuccess()) {
return future.getNow(); return future.getNow();
} }
throw convertException(future); 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<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
l.countDown();
}
});
return l.await(timeout, timeoutUnit);
}
@Override @Override
public <T, R> Future<R> readAsync(InetSocketAddress client, String key, Codec codec, RedisCommand<T> command, Object ... params) { public <T, R> Future<R> readAsync(InetSocketAddress client, String key, Codec codec, RedisCommand<T> command, Object ... params) {
Promise<R> mainPromise = connectionManager.newPromise(); Promise<R> mainPromise = connectionManager.newPromise();
@ -354,7 +382,7 @@ public class CommandAsyncService implements CommandAsyncExecutor {
} }
if (!connectionManager.getShutdownLatch().acquire()) { if (!connectionManager.getShutdownLatch().acquire()) {
mainPromise.setFailure(new IllegalStateException("Redisson is shutdown")); mainPromise.setFailure(new RedissonShutdownException("Redisson is shutdown"));
return; return;
} }
@ -460,9 +488,9 @@ public class CommandAsyncService implements CommandAsyncExecutor {
details.getTimeout().cancel(); details.getTimeout().cancel();
int timeoutTime = connectionManager.getConfig().getTimeout(); 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()); Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString());
handleBlockingOperations(details, connection); handleBlockingOperations(details, connection, popTimeout);
if (popTimeout == 0) { if (popTimeout == 0) {
return; return;
} }
@ -483,26 +511,68 @@ public class CommandAsyncService implements CommandAsyncExecutor {
details.setTimeout(timeout); details.setTimeout(timeout);
} }
private <R, V> void handleBlockingOperations(final AsyncDetails<V, R> details, final RedisConnection connection) { private <R, V> void handleBlockingOperations(final AsyncDetails<V, R> details, final RedisConnection connection, Integer popTimeout) {
final FutureListener<Boolean> listener = new FutureListener<Boolean>() { final FutureListener<Boolean> listener = new FutureListener<Boolean>() {
@Override @Override
public void operationComplete(Future<Boolean> future) throws Exception { public void operationComplete(Future<Boolean> 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<R>() { details.getMainPromise().addListener(new FutureListener<R>() {
@Override @Override
public void operationComplete(Future<R> future) throws Exception { public void operationComplete(Future<R> future) throws Exception {
if (scheduledFuture != null) {
scheduledFuture.cancel(false);
}
connectionManager.getShutdownPromise().removeListener(listener); 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; return;
} }
// cancel handling for commands from skipTimeout collection
if (details.getAttemptPromise().cancel(true)) { if (future.cause() instanceof RedissonShutdownException) {
connection.forceReconnectAsync(); details.getAttemptPromise().tryFailure(future.cause());
} }
} }
}); });
details.getAttemptPromise().addListener(new FutureListener<R>() {
@Override
public void operationComplete(Future<R> future) throws Exception {
if (future.isCancelled()) {
// command should be removed due to
// ConnectionWatchdog blockingQueue reconnection logic
connection.removeCurrentCommand();
}
}
});
connectionManager.getShutdownPromise().addListener(listener); connectionManager.getShutdownPromise().addListener(listener);
} }
@ -625,7 +695,7 @@ public class CommandAsyncService implements CommandAsyncExecutor {
} }
details.getMainPromise().setSuccess(res); details.getMainPromise().setSuccess(res);
} else { } else {
details.getMainPromise().setFailure(future.cause()); details.getMainPromise().tryFailure(future.cause());
} }
AsyncDetails.release(details); AsyncDetails.release(details);
} }

@ -31,6 +31,7 @@ import org.redisson.client.RedisTimeoutException;
import org.redisson.client.WriteRedisConnectionException; import org.redisson.client.WriteRedisConnectionException;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec; import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.BatchCommandData;
import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData; import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand;
@ -52,16 +53,16 @@ public class CommandBatchService extends CommandReactiveService {
public static class CommandEntry implements Comparable<CommandEntry> { public static class CommandEntry implements Comparable<CommandEntry> {
final CommandData<?, ?> command; final BatchCommandData<?, ?> command;
final int index; final int index;
public CommandEntry(CommandData<?, ?> command, int index) { public CommandEntry(BatchCommandData<?, ?> command, int index) {
super(); super();
this.command = command; this.command = command;
this.index = index; this.index = index;
} }
public CommandData<?, ?> getCommand() { public BatchCommandData<?, ?> getCommand() {
return command; return command;
} }
@ -90,13 +91,19 @@ public class CommandBatchService extends CommandReactiveService {
return readOnlyMode; return readOnlyMode;
} }
public void clearErrors() {
for (CommandEntry commandEntry : commands) {
commandEntry.getCommand().clearError();
}
}
} }
private final AtomicInteger index = new AtomicInteger(); private final AtomicInteger index = new AtomicInteger();
private ConcurrentMap<Integer, Entry> commands = PlatformDependent.newConcurrentHashMap(); private ConcurrentMap<Integer, Entry> commands = PlatformDependent.newConcurrentHashMap();
private boolean executed; private volatile boolean executed;
public CommandBatchService(ConnectionManager connectionManager) { public CommandBatchService(ConnectionManager connectionManager) {
super(connectionManager); super(connectionManager);
@ -106,7 +113,7 @@ public class CommandBatchService extends CommandReactiveService {
protected <V, R> void async(boolean readOnlyMode, NodeSource nodeSource, protected <V, R> void async(boolean readOnlyMode, NodeSource nodeSource,
Codec codec, RedisCommand<V> command, Object[] params, Promise<R> mainPromise, int attempt) { Codec codec, RedisCommand<V> command, Object[] params, Promise<R> mainPromise, int attempt) {
if (executed) { if (executed) {
throw new IllegalStateException("Batch already executed!"); throw new IllegalStateException("Batch already has been executed!");
} }
Entry entry = commands.get(nodeSource.getSlot()); Entry entry = commands.get(nodeSource.getSlot());
if (entry == null) { if (entry == null) {
@ -120,7 +127,9 @@ public class CommandBatchService extends CommandReactiveService {
if (!readOnlyMode) { if (!readOnlyMode) {
entry.setReadOnlyMode(false); entry.setReadOnlyMode(false);
} }
entry.getCommands().add(new CommandEntry(new CommandData<V, R>(mainPromise, codec, command, params), index.incrementAndGet()));
BatchCommandData<V, R> commandData = new BatchCommandData<V, R>(mainPromise, codec, command, params);
entry.getCommands().add(new CommandEntry(commandData, index.incrementAndGet()));
} }
public List<?> execute() { public List<?> execute() {
@ -278,15 +287,18 @@ public class CommandBatchService extends CommandReactiveService {
if (future.cause() instanceof RedisMovedException) { if (future.cause() instanceof RedisMovedException) {
RedisMovedException ex = (RedisMovedException)future.cause(); RedisMovedException ex = (RedisMovedException)future.cause();
entry.clearErrors();
execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.MOVED), mainPromise, slots, attempt); execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.MOVED), mainPromise, slots, attempt);
return; return;
} }
if (future.cause() instanceof RedisAskException) { if (future.cause() instanceof RedisAskException) {
RedisAskException ex = (RedisAskException)future.cause(); RedisAskException ex = (RedisAskException)future.cause();
entry.clearErrors();
execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.ASK), mainPromise, slots, attempt); execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.ASK), mainPromise, slots, attempt);
return; return;
} }
if (future.cause() instanceof RedisLoadingException) { if (future.cause() instanceof RedisLoadingException) {
entry.clearErrors();
execute(entry, source, mainPromise, slots, attempt); execute(entry, source, mainPromise, slots, attempt);
return; return;
} }

@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
public class ClientConnectionsEntry { public class ClientConnectionsEntry {
@ -137,7 +138,7 @@ public class ClientConnectionsEntry {
} }
public Future<RedisConnection> connect() { public Future<RedisConnection> connect() {
final Promise<RedisConnection> connectionFuture = client.getBootstrap().group().next().newPromise(); final Promise<RedisConnection> connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise();
Future<RedisConnection> future = client.connectAsync(); Future<RedisConnection> future = client.connectAsync();
future.addListener(new FutureListener<RedisConnection>() { future.addListener(new FutureListener<RedisConnection>() {
@Override @Override
@ -157,19 +158,19 @@ public class ClientConnectionsEntry {
} }
private <T extends RedisConnection> void addReconnectListener(Promise<T> connectionFuture, T conn) { private <T extends RedisConnection> void addReconnectListener(Promise<T> connectionFuture, T conn) {
connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); addFireEventListener(conn, connectionFuture);
addFireEventListener(connectionFuture);
conn.setReconnectListener(new ReconnectListener() { conn.setReconnectListener(new ReconnectListener() {
@Override @Override
public void onReconnect(RedisConnection conn, Promise<RedisConnection> connectionFuture) { public void onReconnect(RedisConnection conn, Promise<RedisConnection> connectionFuture) {
connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig()); addFireEventListener(conn, connectionFuture);
addFireEventListener(connectionFuture);
} }
}); });
} }
private <T extends RedisConnection> void addFireEventListener(Promise<T> connectionFuture) { private <T extends RedisConnection> void addFireEventListener(T conn, Promise<T> connectionFuture) {
connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig());
if (connectionFuture.isSuccess()) { if (connectionFuture.isSuccess()) {
connectionManager.getConnectionEventsHub().fireConnect(connectionFuture.getNow().getRedisClient().getAddr()); connectionManager.getConnectionEventsHub().fireConnect(connectionFuture.getNow().getRedisClient().getAddr());
return; return;
@ -186,7 +187,7 @@ public class ClientConnectionsEntry {
} }
public Future<RedisPubSubConnection> connectPubSub() { public Future<RedisPubSubConnection> connectPubSub() {
final Promise<RedisPubSubConnection> connectionFuture = client.getBootstrap().group().next().newPromise(); final Promise<RedisPubSubConnection> connectionFuture = ImmediateEventExecutor.INSTANCE.newPromise();
Future<RedisPubSubConnection> future = client.connectPubSubAsync(); Future<RedisPubSubConnection> future = client.connectPubSubAsync();
future.addListener(new FutureListener<RedisPubSubConnection>() { future.addListener(new FutureListener<RedisPubSubConnection>() {
@Override @Override
@ -195,11 +196,13 @@ public class ClientConnectionsEntry {
connectionFuture.tryFailure(future.cause()); connectionFuture.tryFailure(future.cause());
return; return;
} }
RedisPubSubConnection conn = future.getNow(); RedisPubSubConnection conn = future.getNow();
log.debug("new pubsub connection created: {}", conn); log.debug("new pubsub connection created: {}", conn);
addReconnectListener(connectionFuture, conn); addReconnectListener(connectionFuture, conn);
allSubscribeConnections.add(conn); allSubscribeConnections.add(conn);
} }
}); });

@ -27,7 +27,6 @@ import org.redisson.client.RedisPubSubListener;
import org.redisson.client.codec.Codec; import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand;
import org.redisson.cluster.ClusterSlotRange; import org.redisson.cluster.ClusterSlotRange;
import org.redisson.connection.ClientConnectionsEntry.FreezeReason;
import org.redisson.core.NodeType; import org.redisson.core.NodeType;
import org.redisson.misc.InfinitySemaphoreLatch; import org.redisson.misc.InfinitySemaphoreLatch;
@ -62,8 +61,6 @@ public interface ConnectionManager {
<R> Future<R> newFailedFuture(Throwable cause); <R> Future<R> newFailedFuture(Throwable cause);
void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason);
Collection<RedisClientEntry> getClients(); Collection<RedisClientEntry> getClients();
void shutdownAsync(RedisClient client); void shutdownAsync(RedisClient client);
@ -102,6 +99,8 @@ public interface ConnectionManager {
void shutdown(); void shutdown();
void shutdown(long quietPeriod, long timeout, TimeUnit unit);
EventLoopGroup getGroup(); EventLoopGroup getGroup();
Timeout newTimeout(TimerTask task, long delay, TimeUnit unit); Timeout newTimeout(TimerTask task, long delay, TimeUnit unit);

@ -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 <V>
*/
public abstract class FastCompleteFuture<V> extends CompleteFuture<V> {
private static final Logger logger = LoggerFactory.getLogger(FastCompleteFuture.class);
protected FastCompleteFuture() {
super(null);
}
@Override
public Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
if (listener == null) {
throw new NullPointerException("listener");
}
notify(listener);
return this;
}
private void notify(GenericFutureListener<? extends Future<? super V>> 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<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners) {
if (listeners == null) {
throw new NullPointerException("listeners");
}
for (GenericFutureListener<? extends Future<? super V>> l: listeners) {
if (l == null) {
break;
}
notify(l);
}
return this;
}
}

@ -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 <V>
*/
public class FastFailedFuture<V> extends FastCompleteFuture<V> {
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<V> sync() {
PlatformDependent.throwException(cause);
return this;
}
@Override
public Future<V> syncUninterruptibly() {
PlatformDependent.throwException(cause);
return this;
}
@Override
public V getNow() {
return null;
}
}

@ -61,6 +61,7 @@ import io.netty.util.Timer;
import io.netty.util.TimerTask; import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import io.netty.util.internal.PlatformDependent; 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); protected final ClusterSlotRange singleSlotRange = new ClusterSlotRange(0, MAX_SLOT);
@ -120,7 +121,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
protected boolean isClusterMode; protected boolean isClusterMode;
protected final Map<ClusterSlotRange, MasterSlaveEntry> entries = PlatformDependent.newConcurrentHashMap(); private final Map<ClusterSlotRange, MasterSlaveEntry> entries = PlatformDependent.newConcurrentHashMap();
private final Promise<Boolean> shutdownPromise; private final Promise<Boolean> shutdownPromise;
@ -158,7 +159,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
this.socketChannelClass = NioSocketChannel.class; this.socketChannelClass = NioSocketChannel.class;
} }
this.codec = cfg.getCodec(); this.codec = cfg.getCodec();
this.shutdownPromise = group.next().newPromise(); this.shutdownPromise = newPromise();
this.isClusterMode = cfg.isClusterConfig(); this.isClusterMode = cfg.isClusterConfig();
} }
@ -188,7 +189,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
protected void init(MasterSlaveServersConfig config) { protected void init(MasterSlaveServersConfig config) {
this.config = 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); Arrays.sort(timeouts);
int minTimeout = timeouts[0]; int minTimeout = timeouts[0];
if (minTimeout % 100 != 0) { if (minTimeout % 100 != 0) {
@ -541,77 +542,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
return null; return null;
} }
public void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason) {
Collection<RedisPubSubConnection> allPubSubConnections = entry.slaveDown(host, port, freezeReason);
if (allPubSubConnections.isEmpty()) {
return;
}
// reattach listeners to other channels
for (Entry<String, PubSubConnectionEntry> 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<RedisPubSubListener> listeners = pubSubEntry.getListeners(channelName);
if (pubSubEntry.getConnection().getPatternChannels().get(channelName) != null) {
Codec subscribeCodec = punsubscribe(channelName);
if (!listeners.isEmpty()) {
Future<PubSubConnectionEntry> future = psubscribe(channelName, subscribeCodec);
future.addListener(new FutureListener<PubSubConnectionEntry>() {
@Override
public void operationComplete(Future<PubSubConnectionEntry> 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<PubSubConnectionEntry> future = subscribe(subscribeCodec, channelName, null);
future.addListener(new FutureListener<PubSubConnectionEntry>() {
@Override
public void operationComplete(Future<PubSubConnectionEntry> 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) { protected void slaveDown(ClusterSlotRange slotRange, String host, int port, FreezeReason freezeReason) {
MasterSlaveEntry entry = getEntry(slotRange); getEntry(slotRange).slaveDown(host, port, freezeReason);
slaveDown(entry, host, port, freezeReason);
} }
protected void changeMaster(ClusterSlotRange slotRange, String host, int port) { protected void changeMaster(ClusterSlotRange slotRange, String host, int port) {
@ -677,13 +609,20 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
@Override @Override
public void shutdown() { 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); shutdownPromise.trySuccess(true);
shutdownLatch.closeAndAwaitUninterruptibly(); shutdownLatch.awaitUninterruptibly();
for (MasterSlaveEntry entry : entries.values()) { for (MasterSlaveEntry entry : entries.values()) {
entry.shutdown(); entry.shutdown();
} }
timer.stop(); timer.stop();
group.shutdownGracefully().syncUninterruptibly(); group.shutdownGracefully(quietPeriod, timeout, unit).syncUninterruptibly();
} }
@Override @Override
@ -703,17 +642,17 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
@Override @Override
public <R> Promise<R> newPromise() { public <R> Promise<R> newPromise() {
return group.next().newPromise(); return ImmediateEventExecutor.INSTANCE.newPromise();
} }
@Override @Override
public <R> Future<R> newSucceededFuture(R value) { public <R> Future<R> newSucceededFuture(R value) {
return new FastSuccessFuture<R>(value); return ImmediateEventExecutor.INSTANCE.newSucceededFuture(value);
} }
@Override @Override
public <R> Future<R> newFailedFuture(Throwable cause) { public <R> Future<R> newFailedFuture(Throwable cause) {
return new FastFailedFuture<R>(cause); return ImmediateEventExecutor.INSTANCE.newFailedFuture(cause);
} }
@Override @Override

@ -28,6 +28,9 @@ import org.redisson.ReadMode;
import org.redisson.client.RedisClient; import org.redisson.client.RedisClient;
import org.redisson.client.RedisConnection; import org.redisson.client.RedisConnection;
import org.redisson.client.RedisPubSubConnection; 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.cluster.ClusterSlotRange;
import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.connection.ClientConnectionsEntry.FreezeReason;
import org.redisson.connection.balancer.LoadBalancerManager; import org.redisson.connection.balancer.LoadBalancerManager;
@ -37,7 +40,10 @@ import org.redisson.core.NodeType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.Future;
import io.netty.util.concurrent.FutureListener;
/** /**
* *
@ -90,9 +96,25 @@ public class MasterSlaveEntry {
return writeConnectionHolder.add(masterEntry); return writeConnectionHolder.add(masterEntry);
} }
public Collection<RedisPubSubConnection> slaveDown(String host, int port, FreezeReason freezeReason) { private boolean slaveDown(ClientConnectionsEntry entry, FreezeReason freezeReason) {
Collection<RedisPubSubConnection> conns = slaveBalancer.freeze(host, port, 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 // add master as slave if no more slaves available
if (config.getReadMode() == ReadMode.SLAVE && slaveBalancer.getAvailableClients() == 0) { if (config.getReadMode() == ReadMode.SLAVE && slaveBalancer.getAvailableClients() == 0) {
InetSocketAddress addr = masterEntry.getClient().getAddr(); InetSocketAddress addr = masterEntry.getClient().getAddr();
@ -100,7 +122,153 @@ public class MasterSlaveEntry {
log.info("master {}:{} used as slave", addr.getHostName(), addr.getPort()); 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<RedisPubSubListener> listeners = pubSubEntry.getListeners(channelName);
reattachPubSubListeners(channelName, listeners);
}
}
for (String channelName : redisPubSubConnection.getPatternChannels().keySet()) {
PubSubConnectionEntry pubSubEntry = connectionManager.getPubSubEntry(channelName);
synchronized (pubSubEntry) {
pubSubEntry.close();
Collection<RedisPubSubListener> listeners = pubSubEntry.getListeners(channelName);
reattachPatternPubSubListeners(channelName, listeners);
}
}
}
private void reattachPubSubListeners(final String channelName, final Collection<RedisPubSubListener> listeners) {
Codec subscribeCodec = connectionManager.unsubscribe(channelName);
if (!listeners.isEmpty()) {
Future<PubSubConnectionEntry> future = connectionManager.subscribe(subscribeCodec, channelName, null);
future.addListener(new FutureListener<PubSubConnectionEntry>() {
@Override
public void operationComplete(Future<PubSubConnectionEntry> 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<RedisPubSubListener> listeners) {
Codec subscribeCodec = connectionManager.punsubscribe(channelName);
if (!listeners.isEmpty()) {
Future<PubSubConnectionEntry> future = connectionManager.psubscribe(channelName, subscribeCodec);
future.addListener(new FutureListener<PubSubConnectionEntry>() {
@Override
public void operationComplete(Future<PubSubConnectionEntry> 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<RedisConnection> newConnection = connectionReadOp();
newConnection.addListener(new FutureListener<RedisConnection>() {
@Override
public void operationComplete(Future<RedisConnection> future) throws Exception {
if (!future.isSuccess()) {
log.error("Can't resubscribe blocking queue {}", commandData);
return;
}
final RedisConnection newConnection = future.getNow();
final FutureListener<Object> listener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> 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<Void> addSlave(String host, int port) { public Future<Void> addSlave(String host, int port) {
@ -134,7 +302,7 @@ public class MasterSlaveEntry {
// exclude master from slaves // exclude master from slaves
if (config.getReadMode() == ReadMode.SLAVE if (config.getReadMode() == ReadMode.SLAVE
&& (!addr.getHostName().equals(host) || port != addr.getPort())) { && (!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()); log.info("master {}:{} excluded from slaves", addr.getHostName(), addr.getPort());
} }
return true; return true;
@ -150,12 +318,12 @@ public class MasterSlaveEntry {
ClientConnectionsEntry oldMaster = masterEntry; ClientConnectionsEntry oldMaster = masterEntry;
setupMasterEntry(host, port); setupMasterEntry(host, port);
writeConnectionHolder.remove(oldMaster); writeConnectionHolder.remove(oldMaster);
oldMaster.freezeMaster(FreezeReason.MANAGER); slaveDown(oldMaster, FreezeReason.MANAGER);
// more than one slave available, so master can be removed from slaves // more than one slave available, so master can be removed from slaves
if (config.getReadMode() == ReadMode.SLAVE if (config.getReadMode() == ReadMode.SLAVE
&& slaveBalancer.getAvailableClients() > 1) { && slaveBalancer.getAvailableClients() > 1) {
connectionManager.slaveDown(this, host, port, FreezeReason.SYSTEM); slaveDown(host, port, FreezeReason.SYSTEM);
} }
connectionManager.shutdownAsync(oldMaster.getClient()); connectionManager.shutdownAsync(oldMaster.getClient());
} }

@ -16,7 +16,6 @@
package org.redisson.connection.balancer; package org.redisson.connection.balancer;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection;
import org.redisson.client.RedisConnection; import org.redisson.client.RedisConnection;
import org.redisson.client.RedisPubSubConnection; import org.redisson.client.RedisPubSubConnection;
@ -37,7 +36,9 @@ public interface LoadBalancerManager {
boolean unfreeze(String host, int port, FreezeReason freezeReason); boolean unfreeze(String host, int port, FreezeReason freezeReason);
Collection<RedisPubSubConnection> freeze(String host, int port, FreezeReason freezeReason); ClientConnectionsEntry freeze(ClientConnectionsEntry connectionEntry, FreezeReason freezeReason);
ClientConnectionsEntry freeze(String host, int port, FreezeReason freezeReason);
Future<Void> add(ClientConnectionsEntry entry); Future<Void> add(ClientConnectionsEntry entry);

@ -16,10 +16,6 @@
package org.redisson.connection.balancer; package org.redisson.connection.balancer;
import java.net.InetSocketAddress; 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 java.util.Map;
import org.redisson.MasterSlaveServersConfig; import org.redisson.MasterSlaveServersConfig;
@ -99,46 +95,31 @@ public class LoadBalancerManagerImpl implements LoadBalancerManager {
return false; return false;
} }
public Collection<RedisPubSubConnection> freeze(String host, int port, FreezeReason freezeReason) { public ClientConnectionsEntry freeze(String host, int port, FreezeReason freezeReason) {
InetSocketAddress addr = new InetSocketAddress(host, port); InetSocketAddress addr = new InetSocketAddress(host, port);
ClientConnectionsEntry connectionEntry = addr2Entry.get(addr); ClientConnectionsEntry connectionEntry = addr2Entry.get(addr);
return freeze(connectionEntry, freezeReason);
}
public ClientConnectionsEntry freeze(ClientConnectionsEntry connectionEntry, FreezeReason freezeReason) {
if (connectionEntry == null) { if (connectionEntry == null) {
return Collections.emptyList(); return null;
} }
synchronized (connectionEntry) { synchronized (connectionEntry) {
log.debug("{} freezed", addr);
connectionEntry.setFreezed(true);
// only RECONNECT freeze reason could be replaced // only RECONNECT freeze reason could be replaced
if (connectionEntry.getFreezeReason() == null if (connectionEntry.getFreezeReason() == null
|| connectionEntry.getFreezeReason() == FreezeReason.RECONNECT) { || connectionEntry.getFreezeReason() == FreezeReason.RECONNECT) {
connectionEntry.setFreezed(true);
connectionEntry.setFreezeReason(freezeReason); connectionEntry.setFreezeReason(freezeReason);
return connectionEntry;
} }
} if (connectionEntry.isFreezed()) {
return null;
// close all connections
while (true) {
RedisConnection connection = connectionEntry.pollConnection();
if (connection == null) {
break;
} }
connection.closeAsync();
} }
// close all pub/sub connections return connectionEntry;
while (true) {
RedisPubSubConnection connection = connectionEntry.pollSubscribeConnection();
if (connection == null) {
break;
}
connection.closeAsync();
}
synchronized (connectionEntry) {
List<RedisPubSubConnection> list = new ArrayList<RedisPubSubConnection>(connectionEntry.getAllSubscribeConnections());
connectionEntry.getAllSubscribeConnections().clear();
return list;
}
} }
public Future<RedisPubSubConnection> nextPubSubConnection() { public Future<RedisPubSubConnection> nextPubSubConnection() {

@ -21,11 +21,11 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.handler.State; import org.redisson.client.handler.State;
import org.redisson.client.protocol.decoder.MultiDecoder; import org.redisson.client.protocol.decoder.MultiDecoder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
public class CacheGetAllDecoder implements MultiDecoder<List<Object>> { public class CacheGetAllDecoder implements MultiDecoder<List<Object>> {
@ -37,7 +37,7 @@ public class CacheGetAllDecoder implements MultiDecoder<List<Object>> {
@Override @Override
public Object decode(ByteBuf buf, State state) throws IOException { 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 @Override

@ -16,6 +16,7 @@
package org.redisson.connection.decoder; package org.redisson.connection.decoder;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -27,10 +28,12 @@ import io.netty.buffer.ByteBuf;
public class MapGetAllDecoder implements MultiDecoder<Map<Object, Object>> { public class MapGetAllDecoder implements MultiDecoder<Map<Object, Object>> {
private final int shiftIndex;
private final List<Object> args; private final List<Object> args;
public MapGetAllDecoder(List<Object> args) { public MapGetAllDecoder(List<Object> args, int shiftIndex) {
this.args = args; this.args = args;
this.shiftIndex = shiftIndex;
} }
@Override @Override
@ -45,13 +48,16 @@ public class MapGetAllDecoder implements MultiDecoder<Map<Object, Object>> {
@Override @Override
public Map<Object, Object> decode(List<Object> parts, State state) { public Map<Object, Object> decode(List<Object> parts, State state) {
if (parts.isEmpty()) {
return Collections.emptyMap();
}
Map<Object, Object> result = new HashMap<Object, Object>(parts.size()); Map<Object, Object> result = new HashMap<Object, Object>(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); Object value = parts.get(index);
if (value == null) { if (value == null) {
continue; continue;
} }
result.put(args.get(index+1), value); result.put(args.get(index+shiftIndex), value);
} }
return result; return result;
} }

@ -153,10 +153,15 @@ abstract class ConnectionPool<T extends RedisConnection> {
} }
} }
StringBuilder errorMsg = new StringBuilder("Publish/Subscribe connection pool exhausted! All connections are busy. Try to increase Publish/Subscribe connection pool size."); StringBuilder errorMsg;
// if (!freezed.isEmpty()) { if (connectionManager.isClusterMode()) {
// errorMsg.append(" Disconnected hosts: " + freezed); 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()) { if (!zeroConnectionsAmount.isEmpty()) {
errorMsg.append(" Hosts with fully busy connections: " + zeroConnectionsAmount); errorMsg.append(" Hosts with fully busy connections: " + zeroConnectionsAmount);
} }
@ -277,7 +282,7 @@ abstract class ConnectionPool<T extends RedisConnection> {
private void checkForReconnect(ClientConnectionsEntry entry) { private void checkForReconnect(ClientConnectionsEntry entry) {
if (entry.getNodeType() == NodeType.SLAVE) { if (entry.getNodeType() == NodeType.SLAVE) {
connectionManager.slaveDown(masterSlaveEntry, entry.getClient().getAddr().getHostName(), masterSlaveEntry.slaveDown(entry.getClient().getAddr().getHostName(),
entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT); entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT);
log.warn("slave {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts()); log.warn("slave {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts());
scheduleCheck(entry); scheduleCheck(entry);

@ -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;
}
}

@ -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 + "]";
}
}

@ -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";
}
}
}

@ -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 {
/**
* <p>Returns a list of object holder instances by a key pattern.
*
* <pre>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</pre>
* <p>Use \ to escape special characters if you want to match them verbatim.
*
* @param pattern
* @return
*/
<V> List<RBucket<V>> find(String pattern);
/**
* Returns Redis object mapped by key. Result Map is not contains
* key-value entry for null values.
*
* @param keys
* @return
*/
<V> Map<String, V> 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<String, ?> buckets);
/**
* Saves objects mapped by Redis key.
*
* @param buckets
*/
void set(Map<String, ?> buckets);
}

@ -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 <V>
*/
public interface RGeo<V> extends RExpirable, RGeoAsync<V> {
/**
* 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 <code>GeoUnit</code> 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<V, String> hash(V... members);
/**
* Returns geo-position mapped by defined member.
*
* @param members
* @return
*/
Map<V, GeoPosition> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
List<V> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Map<V, Double> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Map<V, GeoPosition> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
List<V> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Map<V, Double> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Map<V, GeoPosition> radiusWithPosition(V member, double radius, GeoUnit geoUnit);
}

@ -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 <V>
*/
public interface RGeoAsync<V> 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<Long> 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<Long> addAsync(GeoEntry... entries);
/**
* Returns distance between members in <code>GeoUnit</code> units.
*
* @see {@link GeoUnit}
*
* @param firstMember
* @param secondMember
* @param geoUnit
* @return
*/
Future<Double> distAsync(V firstMember, V secondMember, GeoUnit geoUnit);
/**
* Returns 11 characters Geohash string mapped by defined member.
*
* @param members
* @return
*/
Future<Map<V, String>> hashAsync(V... members);
/**
* Returns geo-position mapped by defined member.
*
* @param members
* @return
*/
Future<Map<V, GeoPosition>> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Future<List<V>> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Future<Map<V, Double>> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Future<Map<V, GeoPosition>> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Future<List<V>> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Future<Map<V, Double>> 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 <code>GeoUnit</code> units.
*
* @param longitude
* @param latitude
* @param radius
* @param geoUnit
* @return
*/
Future<Map<V, GeoPosition>> radiusWithPositionAsync(V member, double radius, GeoUnit geoUnit);
}

@ -20,32 +20,133 @@ import java.util.Set;
public interface RLexSortedSet extends RLexSortedSetAsync, Set<String>, RExpirable { public interface RLexSortedSet extends RLexSortedSetAsync, Set<String>, 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 <code>null</code> if value does not exist
*/
Integer revRank(String o);
/**
* Read all values at once.
*
* @return
*/
Collection<String> readAll();
int removeRangeTail(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSet#removeRangeTail(String, boolean)}
*/
@Deprecated
int removeRangeTailByLex(String fromElement, boolean fromInclusive); 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 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 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 lexCountTail(String fromElement, boolean fromInclusive);
int countHead(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSet#countHead(String, boolean)}
*/
@Deprecated
int lexCountHead(String toElement, boolean toInclusive); int lexCountHead(String toElement, boolean toInclusive);
Collection<String> rangeTail(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSet#rangeTail(String, boolean)}
*/
@Deprecated
Collection<String> lexRangeTail(String fromElement, boolean fromInclusive); Collection<String> lexRangeTail(String fromElement, boolean fromInclusive);
Collection<String> rangeHead(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSet#rangeHead(String, boolean)}
*/
@Deprecated
Collection<String> lexRangeHead(String toElement, boolean toInclusive); Collection<String> lexRangeHead(String toElement, boolean toInclusive);
Collection<String> range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSet#range(String, boolean, String, boolean)}
*/
@Deprecated
Collection<String> lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); Collection<String> lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
Collection<String> rangeTail(String fromElement, boolean fromInclusive, int offset, int count);
/**
* Use {@link RLexSortedSet#rangeTail(String, boolean, int, int)}
*/
@Deprecated
Collection<String> lexRangeTail(String fromElement, boolean fromInclusive, int offset, int count); Collection<String> lexRangeTail(String fromElement, boolean fromInclusive, int offset, int count);
Collection<String> rangeHead(String toElement, boolean toInclusive, int offset, int count);
/**
* Use {@link RLexSortedSet#rangeHead(String, boolean, int, int)}
*/
@Deprecated
Collection<String> lexRangeHead(String toElement, boolean toInclusive, int offset, int count); Collection<String> lexRangeHead(String toElement, boolean toInclusive, int offset, int count);
Collection<String> 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<String> lexRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count); Collection<String> 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 lexCount(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
int rank(String o); Integer rank(String o);
Collection<String> range(int startIndex, int endIndex);
/**
* Use {@link RLexSortedSet#range(int, int)}
*/
@Deprecated
Collection<String> valueRange(int startIndex, int endIndex); Collection<String> valueRange(int startIndex, int endIndex);
} }

@ -21,32 +21,133 @@ import io.netty.util.concurrent.Future;
public interface RLexSortedSetAsync extends RCollectionAsync<String> { public interface RLexSortedSetAsync extends RCollectionAsync<String> {
Future<String> pollLastAsync();
Future<String> pollFirstAsync();
Future<String> firstAsync();
Future<String> lastAsync();
/**
* Read all values at once.
*
* @return
*/
Future<Collection<String>> readAllAsync();
Future<Integer> removeRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSetAsync#removeRangeAsync(String, boolean, String, boolean)}
*/
@Deprecated
Future<Integer> removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); Future<Integer> removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
Future<Integer> removeRangeTailAsync(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSetAsync#removeRangeTailAsync(String, boolean, String, boolean)}
*/
@Deprecated
Future<Integer> removeRangeTailByLexAsync(String fromElement, boolean fromInclusive); Future<Integer> removeRangeTailByLexAsync(String fromElement, boolean fromInclusive);
Future<Integer> removeRangeHeadAsync(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSetAsync#removeRangeHeadAsync(String, boolean)}
*/
@Deprecated
Future<Integer> removeRangeHeadByLexAsync(String toElement, boolean toInclusive); Future<Integer> removeRangeHeadByLexAsync(String toElement, boolean toInclusive);
Future<Integer> countTailAsync(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSetAsync#countTailAsync(String, boolean)}
*/
@Deprecated
Future<Integer> lexCountTailAsync(String fromElement, boolean fromInclusive); Future<Integer> lexCountTailAsync(String fromElement, boolean fromInclusive);
Future<Integer> countHeadAsync(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSetAsync#countHeadAsync(String, boolean)}
*/
@Deprecated
Future<Integer> lexCountHeadAsync(String toElement, boolean toInclusive); Future<Integer> lexCountHeadAsync(String toElement, boolean toInclusive);
Future<Collection<String>> rangeTailAsync(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSetAsync#rangeTailAsync(String, boolean)}
*/
@Deprecated
Future<Collection<String>> lexRangeTailAsync(String fromElement, boolean fromInclusive); Future<Collection<String>> lexRangeTailAsync(String fromElement, boolean fromInclusive);
Future<Collection<String>> rangeHeadAsync(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSetAsync#rangeHeadAsync(String, boolean)}
*/
@Deprecated
Future<Collection<String>> lexRangeHeadAsync(String toElement, boolean toInclusive); Future<Collection<String>> lexRangeHeadAsync(String toElement, boolean toInclusive);
Future<Collection<String>> rangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSetAsync#rangeAsync(String, boolean, String, boolean)}
*/
@Deprecated
Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
Future<Collection<String>> rangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count);
/**
* Use {@link RLexSortedSetAsync#rangeTailAsync(String, boolean, int, int)}
*/
@Deprecated
Future<Collection<String>> lexRangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count); Future<Collection<String>> lexRangeTailAsync(String fromElement, boolean fromInclusive, int offset, int count);
Future<Collection<String>> rangeHeadAsync(String toElement, boolean toInclusive, int offset, int count);
/**
* Use {@link RLexSortedSetAsync#rangeHeadAsync(String, boolean, int, int)}
*/
@Deprecated
Future<Collection<String>> lexRangeHeadAsync(String toElement, boolean toInclusive, int offset, int count); Future<Collection<String>> lexRangeHeadAsync(String toElement, boolean toInclusive, int offset, int count);
Future<Collection<String>> 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<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count); Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count);
Future<Integer> countAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSetAsync#countAsync(String, boolean, String, boolean)}
*/
@Deprecated
Future<Integer> lexCountAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive); Future<Integer> lexCountAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
Future<Integer> rankAsync(String o); Future<Integer> rankAsync(String o);
Future<Collection<String>> rangeAsync(int startIndex, int endIndex);
/**
* Use {@link RLexSortedSetAsync#rangeAsync(int, int)}
*/
@Deprecated
Future<Collection<String>> valueRangeAsync(int startIndex, int endIndex); Future<Collection<String>> valueRangeAsync(int startIndex, int endIndex);
/**
* Returns rank of value, with the scores ordered from high to low.
*
* @param o
* @return rank or <code>null</code> if value does not exist
*/
Future<Integer> revRankAsync(String o);
} }

@ -27,6 +27,32 @@ import java.util.RandomAccess;
*/ */
public interface RList<V> extends List<V>, RExpirable, RListAsync<V>, RandomAccess { public interface RList<V> extends List<V>, RExpirable, RListAsync<V>, RandomAccess {
/**
* Add <code>element</code> after <code>elementToFind</code>
*
* @param elementToFind
* @param element
* @return new list size
*/
Integer addAfter(V elementToFind, V element);
/**
* Add <code>element</code> before <code>elementToFind</code>
*
* @param elementToFind
* @param element
* @return new list size
*/
Integer addBefore(V elementToFind, V element);
/**
* Set <code>element</code> at <code>index</code>.
* Works faster than {@link #set(int, Object)} but
* doesn't return previous element.
*
* @param index
* @param element
*/
void fastSet(int index, V element); void fastSet(int index, V element);
RList<V> subList(int fromIndex, int toIndex); RList<V> subList(int fromIndex, int toIndex);

@ -30,12 +30,38 @@ import io.netty.util.concurrent.Future;
*/ */
public interface RListAsync<V> extends RCollectionAsync<V>, RandomAccess { public interface RListAsync<V> extends RCollectionAsync<V>, RandomAccess {
/**
* Add <code>element</code> after <code>elementToFind</code>
*
* @param elementToFind
* @param element
* @return new list size
*/
Future<Integer> addAfterAsync(V elementToFind, V element);
/**
* Add <code>element</code> before <code>elementToFind</code>
*
* @param elementToFind
* @param element
* @return new list size
*/
Future<Integer> addBeforeAsync(V elementToFind, V element);
Future<Boolean> addAllAsync(int index, Collection<? extends V> coll); Future<Boolean> addAllAsync(int index, Collection<? extends V> coll);
Future<Integer> lastIndexOfAsync(Object o); Future<Integer> lastIndexOfAsync(Object o);
Future<Integer> indexOfAsync(Object o); Future<Integer> indexOfAsync(Object o);
/**
* Set <code>element</code> at <code>index</code>.
* Works faster than {@link #setAsync(int, Object)} but
* doesn't return previous element.
*
* @param index
* @param element
*/
Future<Void> fastSetAsync(int index, V element); Future<Void> fastSetAsync(int index, V element);
Future<V> setAsync(int index, V element); Future<V> setAsync(int index, V element);

@ -17,6 +17,44 @@ package org.redisson.core;
import java.util.concurrent.TimeUnit; 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).
* <p/>
* <b>1. Server side instance (worker instance).</b> Register object with RRemoteService instance.
* <p/>
* <code>
* RRemoteService remoteService = redisson.getRemoteService();<br/>
* <br/>
* // register remote service before any remote invocation<br/>
* remoteService.register(SomeServiceInterface.class, someServiceImpl);
* </code>
* <p/>
* <b>2. Client side instance.</b> Invokes method remotely.
* <p/>
* <code>
* RRemoteService remoteService = redisson.getRemoteService();<br/>
* SomeServiceInterface service = remoteService.get(SomeServiceInterface.class);<br/>
* <br/>
* String result = service.doSomeStuff(1L, "secondParam", new AnyParam());
* </code>
* <p/>
* <p/>
* There are two timeouts during execution:
* <p/>
* <b>Acknowledge (Ack) timeout.</b>Client side instance waits for acknowledge message from Server side instance.
* <p/>
* If acknowledge has not been received by Client side instance then <code>RemoteServiceAckTimeoutException</code> will be thrown.
* And next invocation attempt can be made.
* <p/>
* 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.
* <p/>
* <b>Execution timeout.</b> Client side instance received acknowledge message. If it hasn't received any result or error
* from server side during execution timeout then <code>RemoteServiceTimeoutException</code> will be thrown.
*
* @author Nikita Koksharov
*
*/
public interface RRemoteService { public interface RRemoteService {
/** /**
@ -37,7 +75,11 @@ public interface RRemoteService {
<T> void register(Class<T> remoteInterface, T object, int executorsAmount); <T> void register(Class<T> remoteInterface, T object, int executorsAmount);
/** /**
* Get remote service object for remote invocations * Get remote service object for remote invocations.
* <p/>
* Ack timeout = 1000 ms by default
* <p/>
* Execution timeout = 30 sec by default
* *
* @param remoteInterface * @param remoteInterface
* @return * @return
@ -46,13 +88,28 @@ public interface RRemoteService {
/** /**
* Get remote service object for remote invocations * Get remote service object for remote invocations
* with specified invocation timeout * with specified invocation timeout.
* <p/>
* Ack timeout = 1000 ms by default
*
* @param remoteInterface
* @param executionTimeout - invocation timeout
* @param executionTimeUnit
* @return
*/
<T> T get(Class<T> remoteInterface, long executionTimeout, TimeUnit executionTimeUnit);
/**
* Get remote service object for remote invocations
* with specified invocation and ack timeouts
* *
* @param remoteInterface * @param remoteInterface
* @param timeout - invocation timeout * @param executionTimeout - invocation timeout
* @param timeUnit * @param executionTimeUnit
* @param ackTimeout - ack timeout
* @param ackTimeUnit
* @return * @return
*/ */
<T> T get(Class<T> remoteInterface, int timeout, TimeUnit timeUnit); <T> T get(Class<T> remoteInterface, long executionTimeout, TimeUnit executionTimeUnit, long ackTimeout, TimeUnit ackTimeUnit);
} }

@ -36,9 +36,21 @@ public interface RScoredSortedSet<V> extends RScoredSortedSetAsync<V>, Iterable<
int removeRangeByRank(int startIndex, int endIndex); 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 <code>null</code> 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 <code>null</code> if value does not exist
*/
Integer revRank(V o);
Double getScore(V o); Double getScore(V o);
@ -100,4 +112,24 @@ public interface RScoredSortedSet<V> extends RScoredSortedSetAsync<V>, Iterable<
Collection<ScoredEntry<V>> entryRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count); Collection<ScoredEntry<V>> entryRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count);
Collection<ScoredEntry<V>> entryRangeReversed(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count);
/**
* Returns the number of elements with a score between <code>startScore</code> and <code>endScore</code>.
*
* @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<V> readAll();
} }

@ -94,4 +94,24 @@ public interface RScoredSortedSetAsync<V> extends RExpirableAsync {
Future<Collection<ScoredEntry<V>>> entryRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count); Future<Collection<ScoredEntry<V>>> entryRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count);
Future<Collection<ScoredEntry<V>>> entryRangeReversedAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count);
/**
* Returns the number of elements with a score between <code>startScore</code> and <code>endScore</code>.
*
* @param startScore
* @param startScoreInclusive
* @param endScore
* @param endScoreInclusive
* @return
*/
Future<Long> countAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive);
/**
* Read all values at once.
*
* @return
*/
Future<Collection<V>> readAllAsync();
} }

@ -68,4 +68,40 @@ public interface RSet<V> extends Set<V>, RExpirable, RSetAsync<V> {
*/ */
Set<V> readUnion(String... names); Set<V> 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<V> 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<V> readIntersection(String... names);
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save