Merge branch 'mrniko/master' into LUA-array-indices-start-fix

pull/469/head
jackygurui 9 years ago
commit d1da920180

@ -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,36 @@ Redisson Releases History
================================
####Please Note: trunk is current development branch.
####04-Apr-2016 - version 2.2.11 released
Since this version Redisson has __perfomance boost up to 43%__
Feature - __new object added__ `RGeo`
Feature - __new object added__ `RBuckets`
Feature - travis-ci integration (thanks to jackygurui)
Improvement - `RScoredSortedSet.removeAllAsync` & `removeAll` methods optimization
Improvement - `RemoteService` reliability tuned up
Improvement - Reattaching RBlockingQueue\Deque blocking commands (poll, take ...) after Redis failover process or channel reconnection
Fixed - iterator objects may skip results in some cases
Fixed - RTopic listeners hangs during synchronous commands execution inside it
Fixed - Redisson hangs during shutdown if `RBlockingQueue\Deque.take` or `RBlockingQueue\Deque.poll` methods were invoked
####23-Mar-2016 - version 2.2.10 released
Feature - __new object added__ `RRemoteService`
Feature - __new object added__ `RSetMultimapCache`
Feature - __new object added__ `RListMultimapCache`
Improvement - ability to cancel BRPOP and BLPOP async command execution
Improvement - Config params validation
Improvement - test RedisRunner improvements (thanks to jackygurui)
Improvement - `Double.NEGATIVE_INFINITY` and `Double.POSITIVE_INFINITY` handling for ScoredSortedSet (thanks to jackygurui)
Fixed - MOVED, ASK handling in cluster mode using RBatch
Fixed - delete and expire logic for Multimap objects
Fixed - `RLock.tryLockAsync` NPE
Fixed - possible NPE during Redisson version logging
Fixed - Netty threads shutdown after connection error
####04-Mar-2016 - version 2.2.9 released
Feature - __new object added__ `RSetMultimap`

@ -63,6 +63,7 @@ Features
* [Spring cache](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html) integration
* Supports [Reactive Streams](http://www.reactive-streams.org)
* Supports [Redis pipelining](http://redis.io/topics/pipelining) (command batches)
* Supports [Remote services](https://github.com/mrniko/redisson/wiki/5.-distributed-objects#513-remote-service)
* Supports Android platform
* Supports auto-reconnect
* Supports failed to send command auto-retry
@ -81,12 +82,12 @@ Include the following to your dependency list:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.2.9</version>
<version>2.2.11</version>
</dependency>
### Gradle
compile 'org.redisson:redisson:2.2.9'
compile 'org.redisson:redisson:2.2.11'
### Supported by

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

@ -49,6 +49,7 @@ public class EvictionScheduler {
final String name;
final String timeoutSetName;
final String maxIdleSetName;
final boolean multimap;
final Deque<Integer> sizeHistory = new LinkedList<Integer>();
int delay = 10;
@ -56,10 +57,11 @@ public class EvictionScheduler {
final int maxDelay = 2*60*60;
final int keysLimit = 300;
public RedissonCacheTask(String name, String timeoutSetName, String maxIdleSetName) {
public RedissonCacheTask(String name, String timeoutSetName, String maxIdleSetName, boolean multimap) {
this.name = name;
this.timeoutSetName = timeoutSetName;
this.maxIdleSetName = maxIdleSetName;
this.multimap = multimap;
}
public void schedule() {
@ -68,7 +70,7 @@ public class EvictionScheduler {
@Override
public void run() {
Future<Integer> future = cleanupExpiredEntires(name, timeoutSetName, maxIdleSetName, keysLimit);
Future<Integer> future = cleanupExpiredEntires(name, timeoutSetName, maxIdleSetName, keysLimit, multimap);
future.addListener(new FutureListener<Integer>() {
@Override
@ -123,16 +125,25 @@ public class EvictionScheduler {
this.executor = executor;
}
public void scheduleCleanMultimap(String name, String timeoutSetName) {
RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null, true);
RedissonCacheTask prevTask = tasks.putIfAbsent(name, task);
if (prevTask == null) {
task.schedule();
}
}
public void schedule(String name, String timeoutSetName) {
RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null);
RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, null, false);
RedissonCacheTask prevTask = tasks.putIfAbsent(name, task);
if (prevTask == null) {
task.schedule();
}
}
public void schedule(String name, String timeoutSetName, String maxIdleSetName) {
RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName);
RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName, false);
RedissonCacheTask prevTask = tasks.putIfAbsent(name, task);
if (prevTask == null) {
task.schedule();
@ -155,7 +166,7 @@ public class EvictionScheduler {
return;
}
Future<Integer> future = cleanupExpiredEntires(name, timeoutSetName, null, valuesAmountToClean);
Future<Integer> future = cleanupExpiredEntires(name, timeoutSetName, null, valuesAmountToClean, false);
future.addListener(new FutureListener<Integer>() {
@Override
@ -175,7 +186,27 @@ public class EvictionScheduler {
});
}
private Future<Integer> cleanupExpiredEntires(String name, String timeoutSetName, String maxIdleSetName, int keysLimit) {
private Future<Integer> cleanupExpiredEntires(String name, String timeoutSetName, String maxIdleSetName, int keysLimit, boolean multimap) {
if (multimap) {
return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "if #expiredKeys > 0 then "
+ "redis.call('zrem', KEYS[2], unpack(expiredKeys)); "
+ "local values = redis.call('hmget', KEYS[1], unpack(expiredKeys)); "
+ "local keys = {}; "
+ "for i, v in ipairs(values) do "
+ "local name = '{' .. KEYS[1] .. '}:' .. v; "
+ "table.insert(keys, name); "
+ "end; "
+ "redis.call('del', unpack(keys)); "
+ "redis.call('hdel', KEYS[1], unpack(expiredKeys)); "
+ "end; "
+ "return #expiredKeys;",
Arrays.<Object>asList(name, timeoutSetName), System.currentTimeMillis(), keysLimit);
}
if (maxIdleSetName != null) {
return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"local expiredKeys1 = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
@ -193,6 +224,7 @@ public class EvictionScheduler {
+ "return #expiredKeys1 + #expiredKeys2;",
Arrays.<Object>asList(name, timeoutSetName, maxIdleSetName), System.currentTimeMillis(), keysLimit);
}
return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"local expiredKeys = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "if #expiredKeys > 0 then "

@ -47,25 +47,30 @@ import org.redisson.core.RBlockingDeque;
import org.redisson.core.RBlockingQueue;
import org.redisson.core.RBloomFilter;
import org.redisson.core.RBucket;
import org.redisson.core.RBuckets;
import org.redisson.core.RCountDownLatch;
import org.redisson.core.RDeque;
import org.redisson.core.RGeo;
import org.redisson.core.RHyperLogLog;
import org.redisson.core.RKeys;
import org.redisson.core.RLexSortedSet;
import org.redisson.core.RList;
import org.redisson.core.RListMultimap;
import org.redisson.core.RListMultimapCache;
import org.redisson.core.RLock;
import org.redisson.core.RMap;
import org.redisson.core.RMapCache;
import org.redisson.core.RPatternTopic;
import org.redisson.core.RQueue;
import org.redisson.core.RReadWriteLock;
import org.redisson.core.RRemoteService;
import org.redisson.core.RScoredSortedSet;
import org.redisson.core.RScript;
import org.redisson.core.RSemaphore;
import org.redisson.core.RSet;
import org.redisson.core.RSetCache;
import org.redisson.core.RSetMultimap;
import org.redisson.core.RSetMultimapCache;
import org.redisson.core.RSortedSet;
import org.redisson.core.RTopic;
@ -90,15 +95,21 @@ public class Redisson implements RedissonClient {
Redisson(Config config) {
this.config = config;
Config configCopy = new Config(config);
if (configCopy.getMasterSlaveServersConfig() != null) {
validate(configCopy.getMasterSlaveServersConfig());
connectionManager = new MasterSlaveConnectionManager(configCopy.getMasterSlaveServersConfig(), configCopy);
} else if (configCopy.getSingleServerConfig() != null) {
validate(configCopy.getSingleServerConfig());
connectionManager = new SingleConnectionManager(configCopy.getSingleServerConfig(), configCopy);
} else if (configCopy.getSentinelServersConfig() != null) {
validate(configCopy.getSentinelServersConfig());
connectionManager = new SentinelConnectionManager(configCopy.getSentinelServersConfig(), configCopy);
} else if (configCopy.getClusterServersConfig() != null) {
validate(configCopy.getClusterServersConfig());
connectionManager = new ClusterConnectionManager(configCopy.getClusterServersConfig(), configCopy);
} else if (configCopy.getElasticacheServersConfig() != null) {
validate(configCopy.getElasticacheServersConfig());
connectionManager = new ElasticacheConnectionManager(configCopy.getElasticacheServersConfig(), configCopy);
} else {
throw new IllegalArgumentException("server(s) address(es) not defined!");
@ -107,7 +118,23 @@ public class Redisson implements RedissonClient {
evictionScheduler = new EvictionScheduler(commandExecutor);
}
private void validate(SingleServerConfig config) {
if (config.getConnectionPoolSize() < config.getConnectionMinimumIdleSize()) {
throw new IllegalArgumentException("connectionPoolSize can't be lower than connectionMinimumIdleSize");
}
}
private void validate(BaseMasterSlaveServersConfig<?> config) {
if (config.getSlaveConnectionPoolSize() < config.getSlaveConnectionMinimumIdleSize()) {
throw new IllegalArgumentException("slaveConnectionPoolSize can't be lower than slaveConnectionMinimumIdleSize");
}
if (config.getMasterConnectionPoolSize() < config.getMasterConnectionMinimumIdleSize()) {
throw new IllegalArgumentException("masterConnectionPoolSize can't be lower than masterConnectionMinimumIdleSize");
}
if (config.getSlaveSubscriptionConnectionPoolSize() < config.getSlaveSubscriptionConnectionMinimumIdleSize()) {
throw new IllegalArgumentException("slaveSubscriptionConnectionMinimumIdleSize can't be lower than slaveSubscriptionConnectionPoolSize");
}
}
/**
* Create sync/async Redisson instance with default config
@ -156,6 +183,16 @@ public class Redisson implements RedissonClient {
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
public <V> RBucket<V> getBucket(String name) {
return new RedissonBucket<V>(commandExecutor, name);
@ -166,6 +203,16 @@ public class Redisson implements RedissonClient {
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
public <V> List<RBucket<V>> findBuckets(String pattern) {
Collection<String> keys = commandExecutor.get(commandExecutor.<List<String>, String>readAllAsync(RedisCommands.KEYS, pattern));
@ -179,10 +226,12 @@ public class Redisson implements RedissonClient {
return buckets;
}
@Override
public <V> Map<String, V> loadBucketValues(Collection<String> keys) {
return loadBucketValues(keys.toArray(new String[keys.size()]));
}
@Override
public <V> Map<String, V> loadBucketValues(String ... keys) {
if (keys.length == 0) {
return Collections.emptyMap();
@ -203,6 +252,7 @@ public class Redisson implements RedissonClient {
return result;
}
@Override
public void saveBuckets(Map<String, ?> buckets) {
if (buckets.isEmpty()) {
return;
@ -221,11 +271,6 @@ public class Redisson implements RedissonClient {
commandExecutor.write(params.get(0).toString(), RedisCommands.MSET, params.toArray());
}
@Override
public <V> List<RBucket<V>> getBuckets(String pattern) {
return findBuckets(pattern);
}
@Override
public <V> RHyperLogLog<V> getHyperLogLog(String name) {
return new RedissonHyperLogLog<V>(commandExecutor, name);
@ -266,6 +311,26 @@ public class Redisson implements RedissonClient {
return new RedissonSetMultimap<K, V>(commandExecutor, name);
}
@Override
public <K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name) {
return new RedissonSetMultimapCache<K, V>(evictionScheduler, commandExecutor, name);
}
@Override
public <K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name, Codec codec) {
return new RedissonSetMultimapCache<K, V>(evictionScheduler, codec, commandExecutor, name);
}
@Override
public <K, V> RListMultimapCache<K, V> getListMultimapCache(String name) {
return new RedissonListMultimapCache<K, V>(evictionScheduler, commandExecutor, name);
}
@Override
public <K, V> RListMultimapCache<K, V> getListMultimapCache(String name, Codec codec) {
return new RedissonListMultimapCache<K, V>(evictionScheduler, codec, commandExecutor, name);
}
@Override
public <K, V> RSetMultimap<K, V> getSetMultimap(String name, Codec codec) {
return new RedissonSetMultimap<K, V>(codec, commandExecutor, name);
@ -321,6 +386,10 @@ public class Redisson implements RedissonClient {
return new RedissonScript(commandExecutor);
}
public RRemoteService getRemoteSerivce() {
return new RedissonRemoteService(this);
}
@Override
public <V> RSortedSet<V> getSortedSet(String name) {
return new RedissonSortedSet<V>(commandExecutor, name);
@ -461,10 +530,12 @@ public class Redisson implements RedissonClient {
return config;
}
@Override
public NodesGroup<Node> getNodesGroup() {
return new RedisNodes<Node>(connectionManager);
}
@Override
public NodesGroup<ClusterNode> getClusterNodesGroup() {
if (!config.isClusterConfig()) {
throw new IllegalStateException("Redisson is not in cluster mode!");
@ -472,16 +543,6 @@ public class Redisson implements RedissonClient {
return new RedisNodes<ClusterNode>(connectionManager);
}
@Override
public void flushdb() {
commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHDB));
}
@Override
public void flushall() {
commandExecutor.get(commandExecutor.writeAllAsync(RedisCommands.FLUSHALL));
}
@Override
public boolean isShutdown() {
return connectionManager.isShutdown();

@ -0,0 +1,96 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.redisson.client.protocol.decoder.ListScanResult;
abstract class RedissonBaseIterator<V> implements Iterator<V> {
private List<V> firstValues;
private Iterator<V> iter;
private InetSocketAddress client;
private long nextIterPos;
private long startPos = -1;
private boolean currentElementRemoved;
private boolean removeExecuted;
private V value;
@Override
public boolean hasNext() {
if (iter == null || !iter.hasNext()) {
if (nextIterPos == -1) {
return false;
}
long prevIterPos;
do {
prevIterPos = nextIterPos;
ListScanResult<V> res = iterator(client, nextIterPos);
client = res.getRedisClient();
if (startPos == -1) {
startPos = res.getPos();
}
if (nextIterPos == 0 && firstValues == null) {
firstValues = res.getValues();
} else if (res.getValues().equals(firstValues) && res.getPos() == startPos) {
return false;
}
iter = res.getValues().iterator();
nextIterPos = res.getPos();
} while (!iter.hasNext() && nextIterPos != prevIterPos);
if (prevIterPos == nextIterPos && !removeExecuted) {
nextIterPos = -1;
}
}
return iter.hasNext();
}
abstract ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos);
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException("No such element");
}
value = iter.next();
currentElementRemoved = false;
return value;
}
@Override
public void remove() {
if (currentElementRemoved) {
throw new IllegalStateException("Element been already deleted");
}
if (iter == null) {
throw new IllegalStateException();
}
iter.remove();
remove(value);
currentElementRemoved = true;
removeExecuted = true;
}
abstract void remove(V value);
}

@ -23,6 +23,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
@ -32,10 +33,12 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
private Map<ByteBuf, ByteBuf> firstValues;
private Iterator<Map.Entry<ScanObjectEntry, ScanObjectEntry>> iter;
protected long iterPos = 0;
protected long nextIterPos;
protected long startPos = -1;
protected InetSocketAddress client;
private boolean finished;
private boolean currentElementRemoved;
private boolean removeExecuted;
protected Map.Entry<ScanObjectEntry, ScanObjectEntry> entry;
@ -44,14 +47,24 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
if (finished) {
return false;
}
if (iter == null || !iter.hasNext()) {
if (nextIterPos == -1) {
return false;
}
long prevIterPos;
do {
prevIterPos = nextIterPos;
MapScanResult<ScanObjectEntry, ScanObjectEntry> res = iterator();
client = res.getRedisClient();
if (iterPos == 0 && firstValues == null) {
if (startPos == -1) {
startPos = res.getPos();
}
if (nextIterPos == 0 && firstValues == null) {
firstValues = convert(res.getMap());
} else {
Map<ByteBuf, ByteBuf> newValues = convert(res.getMap());
if (newValues.equals(firstValues)) {
if (firstValues.entrySet().containsAll(newValues.entrySet())) {
finished = true;
free(firstValues);
free(newValues);
@ -61,9 +74,14 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
free(newValues);
}
iter = res.getMap().entrySet().iterator();
iterPos = res.getPos();
nextIterPos = res.getPos();
} while (!iter.hasNext() && nextIterPos != prevIterPos);
if (prevIterPos == nextIterPos && !removeExecuted) {
nextIterPos = -1;
}
}
return iter.hasNext();
}
protected abstract MapScanResult<ScanObjectEntry, ScanObjectEntry> iterator();
@ -90,7 +108,7 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
}
entry = iter.next();
removeExecuted = false;
currentElementRemoved = false;
return getValue(entry);
}
@ -108,14 +126,16 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
@Override
public void remove() {
if (removeExecuted) {
if (currentElementRemoved) {
throw new IllegalStateException("Element been already deleted");
}
if (iter == null) {
throw new IllegalStateException();
}
// lazy init iterator
hasNext();
iter.remove();
removeKey();
currentElementRemoved = true;
removeExecuted = true;
}

@ -200,7 +200,7 @@ public class RedissonBloomFilter<T> extends RedissonExpirable implements RBloomF
@Override
public Future<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getConfigName());
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getConfigName());
}
private void readConfig() {

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

@ -31,25 +31,30 @@ import org.redisson.core.RBlockingDeque;
import org.redisson.core.RBlockingQueue;
import org.redisson.core.RBloomFilter;
import org.redisson.core.RBucket;
import org.redisson.core.RMapCache;
import org.redisson.core.RBuckets;
import org.redisson.core.RCountDownLatch;
import org.redisson.core.RDeque;
import org.redisson.core.RGeo;
import org.redisson.core.RHyperLogLog;
import org.redisson.core.RKeys;
import org.redisson.core.RLexSortedSet;
import org.redisson.core.RList;
import org.redisson.core.RListMultimap;
import org.redisson.core.RListMultimapCache;
import org.redisson.core.RLock;
import org.redisson.core.RMap;
import org.redisson.core.RMapCache;
import org.redisson.core.RPatternTopic;
import org.redisson.core.RQueue;
import org.redisson.core.RReadWriteLock;
import org.redisson.core.RRemoteService;
import org.redisson.core.RScoredSortedSet;
import org.redisson.core.RScript;
import org.redisson.core.RSemaphore;
import org.redisson.core.RSet;
import org.redisson.core.RSetCache;
import org.redisson.core.RSetMultimap;
import org.redisson.core.RSetMultimapCache;
import org.redisson.core.RSortedSet;
import org.redisson.core.RTopic;
@ -62,6 +67,24 @@ import org.redisson.core.RTopic;
*/
public interface RedissonClient {
/**
* Returns geospatial items holder instance by <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>.
* Supports value eviction with a given TTL value.
@ -129,57 +152,43 @@ public interface RedissonClient {
<V> RBucket<V> getBucket(String name, Codec codec);
/**
* <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.
*
* <p>Uses <code>KEYS</code> Redis command.
* Returns interface for mass operations with Bucket objects.
*
* @param pattern
* @return
*/
<V> List<RBucket<V>> findBuckets(String pattern);
RBuckets getBuckets();
/**
* <p>Returns Redis object mapped by key. Result Map is not contains
* key-value entry for null values.
*
* <p>Uses <code>MGET</code> Redis command.
* Returns interface for mass operations with Bucket objects
* using provided codec for object.
*
* @param keys
* @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
* key-value entry for null values.
*
* <p>Uses <code>MGET</code> Redis command.
*
* @param keys
* @return
* Use {@link RBuckets#find(String)}
*/
<V> Map<String, V> loadBucketValues(String ... keys);
@Deprecated
<V> List<RBucket<V>> findBuckets(String pattern);
/**
* Saves Redis object mapped by key.
*
* @param buckets
* Use {@link RBuckets#get(String...)}
*/
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
<V> List<RBucket<V>> getBuckets(String pattern);
void saveBuckets(Map<String, ?> buckets);
/**
* Returns HyperLogLog instance by name.
@ -218,7 +227,7 @@ public interface RedissonClient {
<V> RList<V> getList(String name, Codec codec);
/**
* Returns List based MultiMap instance by name.
* Returns List based Multimap instance by name.
*
* @param name
* @return
@ -226,7 +235,7 @@ public interface RedissonClient {
<K, V> RListMultimap<K, V> getListMultimap(String name);
/**
* Returns List based MultiMap instance by name
* Returns List based Multimap instance by name
* using provided codec for both map keys and values.
*
* @param name
@ -235,6 +244,29 @@ public interface RedissonClient {
*/
<K, V> RListMultimap<K, V> getListMultimap(String name, Codec codec);
/**
* Returns List based Multimap instance by name.
* Supports key-entry eviction with a given TTL value.
*
* <p>If eviction is not required then it's better to use regular map {@link #getSetMultimap(String)}.</p>
*
* @param name
* @return
*/
<K, V> RListMultimapCache<K, V> getListMultimapCache(String name);
/**
* Returns List based Multimap instance by name
* using provided codec for both map keys and values.
* Supports key-entry eviction with a given TTL value.
*
* <p>If eviction is not required then it's better to use regular map {@link #getSetMultimap(String, Codec)}.</p>
*
* @param name
* @return
*/
<K, V> RListMultimapCache<K, V> getListMultimapCache(String name, Codec codec);
/**
* Returns map instance by name.
*
@ -254,7 +286,7 @@ public interface RedissonClient {
<K, V> RMap<K, V> getMap(String name, Codec codec);
/**
* Returns Set based MultiMap instance by name.
* Returns Set based Multimap instance by name.
*
* @param name
* @return
@ -262,7 +294,7 @@ public interface RedissonClient {
<K, V> RSetMultimap<K, V> getSetMultimap(String name);
/**
* Returns Set based MultiMap instance by name
* Returns Set based Multimap instance by name
* using provided codec for both map keys and values.
*
* @param name
@ -271,6 +303,29 @@ public interface RedissonClient {
*/
<K, V> RSetMultimap<K, V> getSetMultimap(String name, Codec codec);
/**
* Returns Set based Multimap instance by name.
* Supports key-entry eviction with a given TTL value.
*
* <p>If eviction is not required then it's better to use regular map {@link #getSetMultimap(String)}.</p>
*
* @param name
* @return
*/
<K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name);
/**
* Returns Set based Multimap instance by name
* using provided codec for both map keys and values.
* Supports key-entry eviction with a given TTL value.
*
* <p>If eviction is not required then it's better to use regular map {@link #getSetMultimap(String, Codec)}.</p>
*
* @param name
* @return
*/
<K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name, Codec codec);
/**
* Returns semaphore instance by name
*
@ -537,6 +592,13 @@ public interface RedissonClient {
*/
RScript getScript();
/**
* Returns object for remote operations
*
* @return
*/
RRemoteService getRemoteSerivce();
/**
* Return batch object which executes group of
* command in pipeline.
@ -583,18 +645,6 @@ public interface RedissonClient {
*/
NodesGroup<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.
*

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

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

@ -32,14 +32,29 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
super(StringCodec.INSTANCE, commandExecutor, name);
}
@Override
public int removeRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
return removeRangeByLex(fromElement, fromInclusive, toElement, toInclusive);
}
@Override
public int removeRangeByLex(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
return get(removeRangeByLexAsync(fromElement, fromInclusive, toElement, toInclusive));
return get(removeRangeAsync(fromElement, fromInclusive, toElement, toInclusive));
}
@Override
public int removeRangeHead(String toElement, boolean toInclusive) {
return removeRangeHeadByLex(toElement, toInclusive);
}
@Override
public int removeRangeHeadByLex(String toElement, boolean toInclusive) {
return get(removeRangeHeadByLexAsync(toElement, toInclusive));
return get(removeRangeHeadAsync(toElement, toInclusive));
}
@Override
public Future<Integer> removeRangeHeadAsync(String toElement, boolean toInclusive) {
return removeRangeHeadByLexAsync(toElement, toInclusive);
}
@Override
@ -48,9 +63,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.writeAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZREMRANGEBYLEX, getName(), "-", toValue);
}
@Override
public int removeRangeTail(String fromElement, boolean fromInclusive) {
return removeRangeTailByLex(fromElement, fromInclusive);
}
@Override
public int removeRangeTailByLex(String fromElement, boolean fromInclusive) {
return get(removeRangeTailByLexAsync(fromElement, fromInclusive));
return get(removeRangeTailAsync(fromElement, fromInclusive));
}
@Override
public Future<Integer> removeRangeTailAsync(String fromElement, boolean fromInclusive) {
return removeRangeTailByLexAsync(fromElement, fromInclusive);
}
@Override
@ -59,6 +84,12 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
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
public Future<Integer> removeRangeByLexAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
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);
}
@Override
public Collection<String> range(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
return lexRange(fromElement, fromInclusive, toElement, toInclusive);
}
@Override
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
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
@ -83,9 +129,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZRANGEBYLEX, getName(), "-", toValue);
}
@Override
public Collection<String> rangeTail(String fromElement, boolean fromInclusive) {
return lexRangeTail(fromElement, fromInclusive);
}
@Override
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
@ -94,6 +150,11 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
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
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);
}
@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
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
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
@ -119,9 +196,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
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
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
@ -130,6 +217,12 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
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
public Future<Collection<String>> lexRangeAsync(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive, int offset, int count) {
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);
}
@Override
public int countTail(String fromElement, boolean fromInclusive) {
return lexCountTail(fromElement, fromInclusive);
}
@Override
public int lexCountTail(String fromElement, boolean fromInclusive) {
return get(lexCountTailAsync(fromElement, fromInclusive));
return get(countTailAsync(fromElement, fromInclusive));
}
@Override
public Future<Integer> countTailAsync(String fromElement, boolean fromInclusive) {
return lexCountTailAsync(fromElement, fromInclusive);
}
@Override
@ -150,9 +253,19 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), fromValue, "+");
}
@Override
public int countHead(String toElement, boolean toInclusive) {
return lexCountHead(toElement, toInclusive);
}
@Override
public int lexCountHead(String toElement, boolean toInclusive) {
return get(lexCountHeadAsync(toElement, toInclusive));
return get(countHeadAsync(toElement, toInclusive));
}
@Override
public Future<Integer> countHeadAsync(String toElement, boolean toInclusive) {
return lexCountHeadAsync(toElement, toInclusive);
}
@Override
@ -162,9 +275,20 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
return commandExecutor.readAsync(getName(), StringCodec.INSTANCE, RedisCommands.ZLEXCOUNT, getName(), "-", toValue);
}
@Override
public int count(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
return lexCount(fromElement, fromInclusive, toElement, toInclusive);
}
@Override
public int lexCount(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive) {
return get(lexCountAsync(fromElement, fromInclusive, toElement, toInclusive));
return get(countAsync(fromElement, fromInclusive, toElement, toInclusive));
}
@Override
public Future<Integer> countAsync(String fromElement, boolean fromInclusive, String toElement,
boolean toInclusive) {
return lexCountAsync(fromElement, fromInclusive, toElement, toInclusive);
}
@Override
@ -192,6 +316,9 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
@Override
public Future<Boolean> addAllAsync(Collection<? extends String> c) {
if (c.isEmpty()) {
return newSucceededFuture(false);
}
List<Object> params = new ArrayList<Object>(2*c.size());
for (Object param : c) {
params.add(0);
@ -210,4 +337,14 @@ public class RedissonLexSortedSet extends RedissonScoredSortedSet<String> implem
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,
"local items = redis.call('lrange', KEYS[1], 0, -1) " +
"for i=1, #items do " +
"for j = 0, table.getn(ARGV), 1 do " +
"for j = 1, #ARGV, 1 do " +
"if items[i] == ARGV[j] then " +
"table.remove(ARGV, j) " +
"end " +
"end " +
"end " +
"return table.getn(ARGV) == 0 and 1 or 0",
"return #ARGV == 0 and 1 or 0",
Collections.<Object>singletonList(getName()), c.toArray());
}
@ -222,7 +222,7 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
public Future<Boolean> removeAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local v = 0 " +
"for i = 0, table.getn(ARGV), 1 do "
"for i = 1, #ARGV, 1 do "
+ "if redis.call('lrem', KEYS[1], 0, ARGV[i]) == 1 "
+ "then v = 1 end "
+"end "
@ -246,11 +246,10 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
"local changed = 0 " +
"local items = redis.call('lrange', KEYS[1], 0, -1) "
+ "local i = 1 "
+ "local s = table.getn(items) "
+ "while i <= s do "
+ "while i <= #items do "
+ "local element = items[i] "
+ "local isInAgrs = false "
+ "for j = 0, table.getn(ARGV), 1 do "
+ "for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == element then "
+ "isInAgrs = true "
+ "break "
@ -390,7 +389,7 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
"local key = KEYS[1] " +
"local obj = ARGV[1] " +
"local items = redis.call('lrange', key, 0, -1) " +
"for i = #items, 0, -1 do " +
"for i = #items, 1, -1 do " +
"if items[i] == obj then " +
"return i - 1 " +
"end " +

@ -74,7 +74,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
String setName = getValuesName(keyHash);
return commandExecutor.readAsync(getName(), codec, LLEN_VALUE, setName);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -99,7 +99,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
"return 0; ",
Arrays.<Object>asList(getName()), valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -126,7 +126,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
"return 0; ",
Collections.<Object>singletonList(setName), valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -148,7 +148,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
"return 1; ",
Arrays.<Object>asList(getName(), setName), keyState, keyHash, valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -167,7 +167,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
+ "return res; ",
Arrays.<Object>asList(getName(), setName), keyState, valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -189,7 +189,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
"return redis.call('rpush', KEYS[2], unpack(ARGV, 3, #ARGV)); ",
Arrays.<Object>asList(getName(), setName), params.toArray());
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -203,7 +203,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
return new RedissonList<V>(codec, commandExecutor, setName);
} catch (Exception e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -220,7 +220,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
return commandExecutor.readAsync(getName(), codec, RedisCommands.LRANGE, setName, 0, -1);
} catch (Exception e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -242,7 +242,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
"return members; ",
Arrays.<Object>asList(getName(), setName), keyState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -272,7 +272,7 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
"return members; ",
Arrays.<Object>asList(getName(), setName), params.toArray());
} catch (Exception e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}

@ -0,0 +1,232 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RListMultimapCache;
import io.netty.util.concurrent.Future;
/**
* @author Nikita Koksharov
*
* @param <K> key
* @param <V> value
*/
public class RedissonListMultimapCache<K, V> extends RedissonListMultimap<K, V> implements RListMultimapCache<K, V> {
private final RedissonMultimapCache<K> baseCache;
RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());
baseCache = new RedissonMultimapCache<K>(connectionManager, name, codec, getTimeoutSetName());
}
RedissonListMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(codec, connectionManager, name);
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());
baseCache = new RedissonMultimapCache<K>(connectionManager, name, codec, getTimeoutSetName());
}
public Future<Boolean> containsKeyAsync(Object key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String valuesName = getValuesName(keyHash);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('hget', KEYS[1], ARGV[2]); " +
"if value ~= false then " +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; "
+ "return redis.call('llen', ARGV[3]) > 0 and 1 or 0;" +
"end;" +
"return 0; ",
Arrays.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), keyState, valuesName);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
String getTimeoutSetName() {
return "redisson_list_multimap_ttl{" + getName() + "}";
}
public Future<Boolean> containsValueAsync(Object value) {
try {
byte[] valueState = codec.getMapValueEncoder().encode(value);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"local keys = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(keys) do " +
"if i % 2 == 0 then " +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], keys[i-1]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate > tonumber(ARGV[2]) then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"local items = redis.call('lrange', name, 0, -1) " +
"for i=1,#items do " +
"if items[i] == ARGV[1] then " +
"return 1; " +
"end; " +
"end; " +
"end; " +
"end;" +
"end; " +
"return 0; ",
Arrays.<Object>asList(getName(), getTimeoutSetName()), valueState, System.currentTimeMillis());
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public Future<Boolean> containsEntryAsync(Object key, Object value) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
byte[] valueState = codec.getMapValueEncoder().encode(value);
String valuesName = getValuesName(keyHash);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then " +
"local items = redis.call('lrange', KEYS[1], 0, -1); " +
"for i=0, #items do " +
"if items[i] == ARGV[3] then " +
"return 1; " +
"end; " +
"end; " +
"end; " +
"return 0; ",
Arrays.<Object>asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState, valueState);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public List<V> get(K key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String valuesName = getValuesName(keyHash);
return new RedissonListMultimapValues<V>(codec, commandExecutor, valuesName, getTimeoutSetName(), key);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
public Future<Collection<V>> getAllAsync(K key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String valuesName = getValuesName(keyHash);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LIST,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then " +
"return redis.call('lrange', KEYS[1], 0, -1); " +
"end; " +
"return {}; ",
Arrays.<Object>asList(valuesName, getTimeoutSetName()), System.currentTimeMillis(), keyState);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public Future<Collection<V>> removeAllAsync(Object key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String valuesName = getValuesName(keyHash);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET,
"redis.call('hdel', KEYS[1], ARGV[1]); " +
"local members = redis.call('lrange', KEYS[2], 0, -1); " +
"redis.call('del', KEYS[2]); " +
"redis.call('zrem', KEYS[3], ARGV[1]); " +
"return members; ",
Arrays.<Object>asList(getName(), valuesName, getTimeoutSetName()), keyState);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public boolean expireKey(K key, long timeToLive, TimeUnit timeUnit) {
return get(expireKeyAsync(key, timeToLive, timeUnit));
}
@Override
public Future<Boolean> expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) {
return baseCache.expireKeyAsync(key, timeToLive, timeUnit);
}
@Override
public Future<Boolean> deleteAsync() {
return baseCache.deleteAsync();
}
@Override
public Future<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
return baseCache.expireAsync(timeToLive, timeUnit);
}
@Override
public Future<Boolean> expireAtAsync(long timestamp) {
return baseCache.expireAtAsync(timestamp);
}
@Override
public Future<Boolean> clearExpireAsync() {
return baseCache.clearExpireAsync();
}
}

@ -0,0 +1,688 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import static org.redisson.client.protocol.RedisCommands.EVAL_OBJECT;
import static org.redisson.client.protocol.RedisCommands.LPOP;
import static org.redisson.client.protocol.RedisCommands.LPUSH_BOOLEAN;
import static org.redisson.client.protocol.RedisCommands.RPUSH_BOOLEAN;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Set;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.BooleanNumberReplayConvertor;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.convertor.Convertor;
import org.redisson.client.protocol.convertor.IntegerReplayConvertor;
import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RList;
import io.netty.util.concurrent.Future;
/**
* List based Multimap Cache values holder
*
* @author Nikita Koksharov
*
* @param <V> the type of elements held in this collection
*/
public class RedissonListMultimapValues<V> extends RedissonExpirable implements RList<V> {
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_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<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);
public static final RedisCommand<Boolean> EVAL_BOOLEAN_ARGS2 = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS);
private final Object key;
private final String timeoutSetName;
public RedissonListMultimapValues(Codec codec, CommandAsyncExecutor commandExecutor, String name, String timeoutSetName, Object key) {
super(codec, commandExecutor, name);
this.timeoutSetName = timeoutSetName;
this.key = key;
}
@Override
public int size() {
return get(sizeAsync());
}
public Future<Integer> sizeAsync() {
return commandExecutor.evalReadAsync(getName(), codec, EVAL_SIZE,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; "
+ "return redis.call('llen', KEYS[2]);",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key);
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean contains(Object o) {
return get(containsAsync(o));
}
@Override
public Iterator<V> iterator() {
return listIterator();
}
@Override
public Object[] toArray() {
List<V> list = readAll();
return list.toArray();
}
@Override
public List<V> readAll() {
return get(readAllAsync());
}
@Override
public Future<List<V>> readAllAsync() {
return commandExecutor.evalReadAsync(getName(), codec, EVAL_READALL,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return {};"
+ "end; "
+ "return redis.call('lrange', KEYS[2], 0, -1);",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key);
}
@Override
public <T> T[] toArray(T[] a) {
List<V> list = readAll();
return list.toArray(a);
}
@Override
public boolean add(V e) {
return get(addAsync(e));
}
@Override
public Future<Boolean> addAsync(V e) {
return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, getName(), e);
}
@Override
public boolean remove(Object o) {
return get(removeAsync(o));
}
@Override
public Future<Boolean> removeAsync(Object o) {
return removeAsync(o, 1);
}
protected Future<Boolean> removeAsync(Object o, int count) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; "
+ "return redis.call('lrem', KEYS[2], ARGV[2], ARGV[4]) > 0 and 1 or 0;",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), count, key, o);
}
protected boolean remove(Object o, int count) {
return get(removeAsync(o, count));
}
@Override
public Future<Boolean> containsAllAsync(Collection<?> c) {
List<Object> args = new ArrayList<Object>(c.size() + 2);
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
args.add(System.currentTimeMillis());
args.add(keyState);
args.addAll(c);
} catch (IOException e) {
throw new RuntimeException(e);
}
return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; " +
"local items = redis.call('lrange', KEYS[2], 0, -1);" +
"for i = 1, #items, 1 do " +
"for j = 2, #ARGV, 1 do "
+ "if ARGV[j] == items[i] "
+ "then table.remove(ARGV, j) end "
+ "end; "
+ "end;"
+ "return #ARGV == 2 and 1 or 0; ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
}
@Override
public boolean containsAll(Collection<?> c) {
return get(containsAllAsync(c));
}
@Override
public boolean addAll(Collection<? extends V> c) {
return get(addAllAsync(c));
}
@Override
public Future<Boolean> addAllAsync(final Collection<? extends V> c) {
if (c.isEmpty()) {
return newSucceededFuture(false);
}
List<Object> args = new ArrayList<Object>(c.size() + 1);
args.add(getName());
args.addAll(c);
return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, args.toArray());
}
public Future<Boolean> addAllAsync(int index, Collection<? extends V> coll) {
if (index < 0) {
throw new IndexOutOfBoundsException("index: " + index);
}
if (coll.isEmpty()) {
return newSucceededFuture(false);
}
if (index == 0) { // prepend elements to list
List<Object> elements = new ArrayList<Object>(coll);
Collections.reverse(elements);
elements.add(0, getName());
return commandExecutor.writeAsync(getName(), codec, LPUSH_BOOLEAN, elements.toArray());
}
List<Object> args = new ArrayList<Object>(coll.size() + 1);
args.add(index);
args.addAll(coll);
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_BOOLEAN_ARGS2,
"local ind = table.remove(ARGV, 1); " + // index is the first parameter
"local size = redis.call('llen', KEYS[1]); " +
"assert(tonumber(ind) <= size, 'index: ' .. ind .. ' but current size: ' .. size); " +
"local tail = redis.call('lrange', KEYS[1], ind, -1); " +
"redis.call('ltrim', KEYS[1], 0, ind - 1); " +
"for i=1, #ARGV, 5000 do "
+ "redis.call('rpush', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); "
+ "end " +
"if #tail > 0 then " +
"for i=1, #tail, 5000 do "
+ "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); "
+ "end "
+ "end;" +
"return 1;",
Collections.<Object>singletonList(getName()), args.toArray());
}
@Override
public boolean addAll(int index, Collection<? extends V> coll) {
return get(addAllAsync(index, coll));
}
@Override
public Future<Boolean> removeAllAsync(Collection<?> c) {
List<Object> args = new ArrayList<Object>(c.size() + 2);
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
args.add(System.currentTimeMillis());
args.add(keyState);
args.addAll(c);
} catch (IOException e) {
throw new RuntimeException(e);
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; " +
"local v = 0 " +
"for i = 2, #ARGV, 1 do "
+ "if redis.call('lrem', KEYS[2], 0, ARGV[i]) == 1 "
+ "then v = 1 end "
+"end "
+ "return v ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
}
@Override
public boolean removeAll(Collection<?> c) {
return get(removeAllAsync(c));
}
@Override
public boolean retainAll(Collection<?> c) {
return get(retainAllAsync(c));
}
@Override
public Future<Boolean> retainAllAsync(Collection<?> c) {
List<Object> args = new ArrayList<Object>(c.size() + 2);
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
args.add(System.currentTimeMillis());
args.add(keyState);
args.addAll(c);
} catch (IOException e) {
throw new RuntimeException(e);
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; " +
"local changed = 0; " +
"local s = redis.call('lrange', KEYS[2], 0, -1); "
+ "local i = 1; "
+ "while i <= #s do "
+ "local element = s[i]; "
+ "local isInAgrs = false; "
+ "for j = 2, #ARGV, 1 do "
+ "if ARGV[j] == element then "
+ "isInAgrs = true; "
+ "break; "
+ "end; "
+ "end; "
+ "if isInAgrs == false then "
+ "redis.call('lrem', KEYS[2], 0, element); "
+ "changed = 1; "
+ "end; "
+ "i = i + 1; "
+ "end; "
+ "return changed; ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
}
@Override
public void clear() {
delete();
}
@Override
public Future<V> getAsync(int index) {
return commandExecutor.evalReadAsync(getName(), codec, EVAL_GET,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore); "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return nil;"
+ "end; "
+ "return redis.call('lindex', KEYS[2], ARGV[2]);",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), index, key);
}
@Override
public V get(int index) {
checkIndex(index);
return getValue(index);
}
V getValue(int index) {
return get(getAsync(index));
}
private void checkIndex(int index) {
int size = size();
if (!isInRange(index, size))
throw new IndexOutOfBoundsException("index: " + index + " but current size: "+ size);
}
private boolean isInRange(int index, int size) {
return index >= 0 && index < size;
}
@Override
public V set(int index, V element) {
checkIndex(index);
return get(setAsync(index, element));
}
@Override
public Future<V> setAsync(int index, V element) {
return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Object>("EVAL", 5),
"local v = redis.call('lindex', KEYS[1], ARGV[1]); " +
"redis.call('lset', KEYS[1], ARGV[1], ARGV[2]); " +
"return v",
Collections.<Object>singletonList(getName()), index, element);
}
@Override
public void fastSet(int index, V element) {
get(fastSetAsync(index, element));
}
@Override
public Future<Void> fastSetAsync(int index, V element) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LSET, getName(), index, element);
}
@Override
public void add(int index, V element) {
addAll(index, Collections.singleton(element));
}
@Override
public V remove(int index) {
checkIndex(index);
if (index == 0) {
Future<V> f = commandExecutor.writeAsync(getName(), codec, LPOP, getName());
return get(f);
}
Future<V> f = commandExecutor.evalWriteAsync(getName(), codec, EVAL_OBJECT,
"local v = redis.call('lindex', KEYS[1], ARGV[1]); " +
"local tail = redis.call('lrange', KEYS[1], ARGV[1] + 1, -1);" +
"redis.call('ltrim', KEYS[1], 0, ARGV[1] - 1);" +
"if #tail > 0 then " +
"for i=1, #tail, 5000 do "
+ "redis.call('rpush', KEYS[1], unpack(tail, i, math.min(i+4999, #tail))); "
+ "end "
+ "end;" +
"return v",
Collections.<Object>singletonList(getName()), index);
return get(f);
}
@Override
public int indexOf(Object o) {
return get(indexOfAsync(o));
}
@Override
public Future<Boolean> containsAsync(Object o) {
return indexOfAsync(o, new BooleanNumberReplayConvertor(-1L));
}
private <R> Future<R> indexOfAsync(Object o, Convertor<R> convertor) {
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<R>("EVAL", convertor, 6, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE)),
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore); "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return -1;"
+ "end; " +
"local items = redis.call('lrange', KEYS[2], 0, -1); " +
"for i=1,#items do " +
"if items[i] == ARGV[3] then " +
"return i - 1; " +
"end; " +
"end; " +
"return -1;",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o);
}
@Override
public Future<Integer> indexOfAsync(Object o) {
return indexOfAsync(o, new IntegerReplayConvertor());
}
@Override
public int lastIndexOf(Object o) {
return get(lastIndexOfAsync(o));
}
@Override
public Future<Integer> lastIndexOfAsync(Object o) {
return commandExecutor.evalReadAsync(getName(), codec, LAST_INDEX,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore); "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return -1;"
+ "end; " +
"local items = redis.call('lrange', KEYS[1], 0, -1) " +
"for i = #items, 1, -1 do " +
"if items[i] == ARGV[1] then " +
"return i - 1 " +
"end " +
"end " +
"return -1",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o);
}
@Override
public void trim(int fromIndex, int toIndex) {
get(trimAsync(fromIndex, toIndex));
}
@Override
public Future<Void> trimAsync(int fromIndex, int toIndex) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LTRIM, getName(), fromIndex, toIndex);
}
@Override
public ListIterator<V> listIterator() {
return listIterator(0);
}
@Override
public ListIterator<V> listIterator(final int ind) {
return new ListIterator<V>() {
private V prevCurrentValue;
private V nextCurrentValue;
private V currentValueHasRead;
private int currentIndex = ind - 1;
private boolean hasBeenModified = true;
@Override
public boolean hasNext() {
V val = RedissonListMultimapValues.this.getValue(currentIndex+1);
if (val != null) {
nextCurrentValue = val;
}
return val != null;
}
@Override
public V next() {
if (nextCurrentValue == null && !hasNext()) {
throw new NoSuchElementException("No such element at index " + currentIndex);
}
currentIndex++;
currentValueHasRead = nextCurrentValue;
nextCurrentValue = null;
hasBeenModified = false;
return currentValueHasRead;
}
@Override
public void remove() {
if (currentValueHasRead == null) {
throw new IllegalStateException("Neither next nor previous have been called");
}
if (hasBeenModified) {
throw new IllegalStateException("Element been already deleted");
}
RedissonListMultimapValues.this.remove(currentIndex);
currentIndex--;
hasBeenModified = true;
currentValueHasRead = null;
}
@Override
public boolean hasPrevious() {
if (currentIndex < 0) {
return false;
}
V val = RedissonListMultimapValues.this.getValue(currentIndex);
if (val != null) {
prevCurrentValue = val;
}
return val != null;
}
@Override
public V previous() {
if (prevCurrentValue == null && !hasPrevious()) {
throw new NoSuchElementException("No such element at index " + currentIndex);
}
currentIndex--;
hasBeenModified = false;
currentValueHasRead = prevCurrentValue;
prevCurrentValue = null;
return currentValueHasRead;
}
@Override
public int nextIndex() {
return currentIndex + 1;
}
@Override
public int previousIndex() {
return currentIndex;
}
@Override
public void set(V e) {
if (hasBeenModified) {
throw new IllegalStateException();
}
RedissonListMultimapValues.this.fastSet(currentIndex, e);
}
@Override
public void add(V e) {
RedissonListMultimapValues.this.add(currentIndex+1, e);
currentIndex++;
hasBeenModified = true;
}
};
}
@Override
public RList<V> subList(int fromIndex, int toIndex) {
int size = size();
if (fromIndex < 0 || toIndex > size) {
throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " toIndex: " + toIndex + " size: " + size);
}
if (fromIndex > toIndex) {
throw new IllegalArgumentException("fromIndex: " + fromIndex + " toIndex: " + toIndex);
}
return new RedissonSubList<V>(codec, commandExecutor, getName(), fromIndex, toIndex);
}
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(' ');
}
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
Iterator<V> e1 = iterator();
Iterator<?> e2 = ((List<?>) o).iterator();
while (e1.hasNext() && e2.hasNext()) {
V o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
@Override
public int hashCode() {
int hashCode = 1;
for (V e : this) {
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
}
return hashCode;
}
}

@ -110,7 +110,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
}
Future<RedissonLockEntry> future = subscribe();
future.sync();
get(future);
try {
while (true) {
@ -229,7 +229,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
}
Future<RedissonLockEntry> future = subscribe();
if (!future.await(time, TimeUnit.MILLISECONDS)) {
if (!await(future, time, TimeUnit.MILLISECONDS)) {
future.addListener(new FutureListener<RedissonLockEntry>() {
@Override
public void operationComplete(Future<RedissonLockEntry> future) throws Exception {

@ -107,7 +107,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
public Future<Boolean> containsValueAsync(Object value) {
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4),
"local s = redis.call('hvals', KEYS[1]);" +
"for i = 0, table.getn(s), 1 do "
"for i = 1, #s, 1 do "
+ "if ARGV[1] == s[i] then "
+ "return 1 "
+ "end "
@ -130,7 +130,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
List<Object> args = new ArrayList<Object>(keys.size() + 1);
args.add(getName());
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

@ -162,11 +162,11 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
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.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 expireIdleHead = redis.call('zrange', KEYS[3], 0, 0, 'withscores');" +
"local maxDate = table.remove(ARGV, 1); " // index is the first parameter
@ -655,7 +655,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
@Override
public Future<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName());
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName());
}
@Override

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

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

@ -20,15 +20,18 @@ import java.net.InetSocketAddress;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.Set;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
@ -152,10 +155,12 @@ public abstract class RedissonMultimap<K, V> extends RedissonExpirable implement
return new EntrySet();
}
@Override
public long fastRemove(K ... keys) {
return get(fastRemoveAsync(keys));
}
@Override
public Future<Long> fastRemoveAsync(K ... keys) {
if (keys == null || keys.length == 0) {
return newSucceededFuture(0L);
@ -185,6 +190,69 @@ public abstract class RedissonMultimap<K, V> extends RedissonExpirable implement
}
}
@Override
public Future<Boolean> deleteAsync() {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT,
"local entries = redis.call('hgetall', KEYS[1]); " +
"local keys = {KEYS[1]}; " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"table.insert(keys, name); " +
"end;" +
"end; " +
"local n = 0 "
+ "for i=1, #keys,5000 do "
+ "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) "
+ "end; "
+ "return n;",
Arrays.<Object>asList(getName()));
}
@Override
public Future<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local entries = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"redis.call('pexpire', name, ARGV[1]); " +
"end;" +
"end; " +
"return redis.call('pexpire', KEYS[1], ARGV[1]); ",
Arrays.<Object>asList(getName()), timeUnit.toMillis(timeToLive));
}
@Override
public Future<Boolean> expireAtAsync(long timestamp) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local entries = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"redis.call('pexpireat', name, ARGV[1]); " +
"end;" +
"end; " +
"return redis.call('pexpireat', KEYS[1], ARGV[1]); ",
Arrays.<Object>asList(getName()), timestamp);
}
@Override
public Future<Boolean> clearExpireAsync() {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local entries = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"redis.call('persist', name); " +
"end;" +
"end; " +
"return redis.call('persist', KEYS[1]); ",
Arrays.<Object>asList(getName()));
}
MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(InetSocketAddress client, long startPos) {
Future<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec, StringCodec.INSTANCE), RedisCommands.HSCAN, getName(), startPos);
return get(f);

@ -0,0 +1,129 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.command.CommandAsyncExecutor;
import io.netty.util.concurrent.Future;
public class RedissonMultimapCache<K> {
private static final RedisCommand<Boolean> EVAL_EXPIRE_KEY = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 6, ValueType.MAP_KEY);
private final CommandAsyncExecutor commandExecutor;
private final String name;
private final Codec codec;
private final String timeoutSetName;
public RedissonMultimapCache(CommandAsyncExecutor commandExecutor, String name, Codec codec, String timeoutSetName) {
this.commandExecutor = commandExecutor;
this.name = name;
this.codec = codec;
this.timeoutSetName = timeoutSetName;
}
public Future<Boolean> expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) {
long ttlTimeout = System.currentTimeMillis() + timeUnit.toMillis(timeToLive);
return commandExecutor.evalWriteAsync(name, codec, EVAL_EXPIRE_KEY,
"if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then "
+ "if tonumber(ARGV[1]) > 0 then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[2]); " +
"else " +
"redis.call('zrem', KEYS[2], ARGV[2]); "
+ "end; "
+ "return 1; "
+ "else "
+ "return 0; "
+ "end",
Arrays.<Object>asList(name, timeoutSetName), ttlTimeout, key);
}
public Future<Boolean> deleteAsync() {
return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT,
"local entries = redis.call('hgetall', KEYS[1]); " +
"local keys = {KEYS[1], KEYS[2]}; " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"table.insert(keys, name); " +
"end;" +
"end; " +
"local n = 0 "
+ "for i=1, #keys,5000 do "
+ "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) "
+ "end; "
+ "return n;",
Arrays.<Object>asList(name, timeoutSetName));
}
public Future<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag'); " +
"local entries = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"redis.call('pexpire', name, ARGV[1]); " +
"end;" +
"end; " +
"redis.call('pexpire', KEYS[2], ARGV[1]); " +
"return redis.call('pexpire', KEYS[1], ARGV[1]); ",
Arrays.<Object>asList(name, timeoutSetName), timeUnit.toMillis(timeToLive));
}
public Future<Boolean> expireAtAsync(long timestamp) {
return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" +
"local entries = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"redis.call('pexpireat', name, ARGV[1]); " +
"end;" +
"end; " +
"redis.call('pexpireat', KEYS[2], ARGV[1]); " +
"return redis.call('pexpireat', KEYS[1], ARGV[1]); ",
Arrays.<Object>asList(name, timeoutSetName), timestamp);
}
public Future<Boolean> clearExpireAsync() {
return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"redis.call('zrem', KEYS[2], 'redisson__expiretag'); " +
"local entries = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(entries) do " +
"if i % 2 == 0 then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"redis.call('persist', name); " +
"end;" +
"end; " +
"redis.call('persist', KEYS[2]); " +
"return redis.call('persist', KEYS[1]); ",
Arrays.<Object>asList(name, timeoutSetName));
}
}

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

@ -0,0 +1,212 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.redisson.core.RBatch;
import org.redisson.core.RBlockingQueue;
import org.redisson.core.RBlockingQueueAsync;
import org.redisson.core.RRemoteService;
import org.redisson.remote.RRemoteServiceResponse;
import org.redisson.remote.RemoteServiceAck;
import org.redisson.remote.RemoteServiceAckTimeoutException;
import org.redisson.remote.RemoteServiceKey;
import org.redisson.remote.RemoteServiceMethod;
import org.redisson.remote.RemoteServiceRequest;
import org.redisson.remote.RemoteServiceResponse;
import org.redisson.remote.RemoteServiceTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.ThreadLocalRandom;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonRemoteService implements RRemoteService {
private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class);
private final Map<RemoteServiceKey, RemoteServiceMethod> beans = PlatformDependent.newConcurrentHashMap();
private final Redisson redisson;
public RedissonRemoteService(Redisson redisson) {
this.redisson = redisson;
}
@Override
public <T> void register(Class<T> remoteInterface, T object) {
register(remoteInterface, object, 1);
}
@Override
public <T> void register(Class<T> remoteInterface, T object, int executorsAmount) {
if (executorsAmount < 1) {
throw new IllegalArgumentException("executorsAmount can't be lower than 1");
}
for (Method method : remoteInterface.getMethods()) {
RemoteServiceMethod value = new RemoteServiceMethod(method, object);
RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName());
if (beans.put(key, value) != null) {
return;
}
}
for (int i = 0; i < executorsAmount; i++) {
String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}";
RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName);
subscribe(remoteInterface, requestQueue);
}
}
private <T> void subscribe(final Class<T> remoteInterface, final RBlockingQueue<RemoteServiceRequest> requestQueue) {
Future<RemoteServiceRequest> take = requestQueue.takeAsync();
take.addListener(new FutureListener<RemoteServiceRequest>() {
@Override
public void operationComplete(Future<RemoteServiceRequest> future) throws Exception {
if (!future.isSuccess()) {
if (future.cause() instanceof RedissonShutdownException) {
return;
}
subscribe(remoteInterface, requestQueue);
return;
}
subscribe(remoteInterface, requestQueue);
final RemoteServiceRequest request = future.getNow();
if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) {
log.debug("request: {} has been skipped due to ackTimeout");
return;
}
final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName()));
final String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + request.getRequestId();
Future<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());
return;
}
invokeMethod(request, method, responseName);
}
});
}
});
}
private void invokeMethod(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());
return;
}
}
});
}
@Override
public <T> T get(Class<T> remoteInterface) {
return get(remoteInterface, 30, TimeUnit.SECONDS);
}
@Override
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() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String requestId = generateRequestId();
String requestQueueName = "redisson_remote_service:{" + remoteInterface.getName() + "}";
RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName);
RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args,
ackTimeUnit.toMillis(ackTimeout), executionTimeUnit.toMillis(executionTimeout), System.currentTimeMillis());
requestQueue.add(request);
String responseName = "redisson_remote_service:{" + remoteInterface.getName() + "}:" + requestId;
RBlockingQueue<RRemoteServiceResponse> responseQueue = redisson.getBlockingQueue(responseName);
RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(ackTimeout, ackTimeUnit);
if (ack == null) {
throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request);
}
RemoteServiceResponse response = (RemoteServiceResponse) responseQueue.poll(executionTimeout, executionTimeUnit);
if (response == null) {
throw new RemoteServiceTimeoutException("No response after " + executionTimeUnit.toMillis(executionTimeout) + "ms for request: " + request);
}
if (response.getError() != null) {
throw response.getError();
}
return response.getResult();
}
};
return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler);
}
private String generateRequestId() {
byte[] id = new byte[16];
// TODO JDK UPGRADE replace to native ThreadLocalRandom
ThreadLocalRandom.current().nextBytes(id);
return ByteBufUtil.hexDump(id);
}
private <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();
}
}

@ -25,16 +25,12 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.ScoredCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.ScoredEntry;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RScoredSortedSet;
@ -124,6 +120,9 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
@Override
public Future<Long> addAllAsync(Map<V, Double> objects) {
if (objects.isEmpty()) {
return newSucceededFuture(0L);
}
List<Object> params = new ArrayList<Object>(objects.size()*2+1);
params.add(getName());
try {
@ -245,52 +244,16 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
private List<V> firstValues;
private Iterator<V> iter;
private InetSocketAddress client;
private long iterPos;
private boolean removeExecuted;
private V value;
return new RedissonBaseIterator<V>() {
@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();
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(client, nextIterPos);
}
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException("No such element at index");
}
value = iter.next();
removeExecuted = false;
return value;
}
@Override
public void remove() {
if (removeExecuted) {
throw new IllegalStateException("Element been already deleted");
}
iter.remove();
void remove(V value) {
RedissonScoredSortedSet.this.remove(value);
removeExecuted = true;
}
};
@ -315,27 +278,26 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
@Override
public Future<Boolean> containsAllAsync(Collection<?> c) {
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS),
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local s = redis.call('zrange', KEYS[1], 0, -1);" +
"for i = 0, table.getn(s), 1 do " +
"for j = 0, table.getn(ARGV), 1 do "
"for i = 1, #s, 1 do " +
"for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == s[i] "
+ "then table.remove(ARGV, j) end "
+ "end; "
+ "end;"
+ "return table.getn(ARGV) == 0 and 1 or 0; ",
+ "return #ARGV == 0 and 1 or 0; ",
Collections.<Object>singletonList(getName()), c.toArray());
}
@Override
public Future<Boolean> removeAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS),
"local v = 0 " +
"for i = 0, table.getn(ARGV), 1 do "
+ "if redis.call('zrem', KEYS[1], ARGV[i]) == 1 "
+ "then v = 1 end "
+"end "
+ "return v ",
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local v = 0;"
+ "for i=1, #ARGV, 5000 do "
+ "v = v + redis.call('zrem', KEYS[1], unpack(ARGV, i, math.min(i+4999, #ARGV))); "
+ "end "
+ "return v > 0;",
Collections.<Object>singletonList(getName()), c.toArray());
}
@ -351,14 +313,14 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
@Override
public Future<Boolean> retainAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS),
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local changed = 0 " +
"local s = redis.call('zrange', KEYS[1], 0, -1) "
+ "local i = 0 "
+ "while i <= table.getn(s) do "
+ "local i = 1 "
+ "while i <= #s do "
+ "local element = s[i] "
+ "local isInAgrs = false "
+ "for j = 0, table.getn(ARGV), 1 do "
+ "for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == element then "
+ "isInAgrs = true "
+ "break "

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

@ -22,7 +22,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import org.redisson.client.codec.Codec;
@ -82,64 +81,16 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
@Override
public Iterator<V> iterator() {
return new Iterator<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();
}
return new RedissonBaseIterator<V>() {
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException("No such element at index");
}
value = iter.next();
currentElementRemoved = false;
return value;
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(client, nextIterPos);
}
@Override
public void remove() {
if (currentElementRemoved) {
throw new IllegalStateException("Element been already deleted");
}
if (iter == null) {
throw new IllegalStateException();
}
iter.remove();
void remove(V value) {
RedissonSet.this.remove(value);
currentElementRemoved = true;
removeExecuted = true;
}
};
@ -216,13 +167,13 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
public Future<Boolean> containsAllAsync(Collection<?> c) {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local s = redis.call('smembers', KEYS[1]);" +
"for i = 0, table.getn(s), 1 do " +
"for j = 0, table.getn(ARGV), 1 do "
"for i = 1, #s, 1 do " +
"for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == s[i] "
+ "then table.remove(ARGV, j) end "
+ "end; "
+ "end;"
+ "return table.getn(ARGV) == 0 and 1 or 0; ",
+ "return #ARGV == 0 and 1 or 0; ",
Collections.<Object>singletonList(getName()), c.toArray());
}
@ -253,11 +204,11 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local changed = 0 " +
"local s = redis.call('smembers', KEYS[1]) "
+ "local i = 0 "
+ "while i <= table.getn(s) do "
+ "local i = 1 "
+ "while i <= #s do "
+ "local element = s[i] "
+ "local isInAgrs = false "
+ "for j = 0, table.getn(ARGV), 1 do "
+ "for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == element then "
+ "isInAgrs = true "
+ "break "
@ -277,7 +228,7 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
public Future<Boolean> removeAllAsync(Collection<?> c) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local v = 0 " +
"for i = 0, table.getn(ARGV), 1 do "
"for i = 1, #ARGV, 1 do "
+ "if redis.call('srem', KEYS[1], ARGV[i]) == 1 "
+ "then v = 1 end "
+"end "

@ -17,14 +17,12 @@ package org.redisson;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@ -39,6 +37,8 @@ import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RSetCache;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.concurrent.Future;
import net.openhft.hashing.LongHashFunction;
@ -114,10 +114,12 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
long h1 = LongHashFunction.farmUo().hashBytes(objectState);
long h2 = LongHashFunction.xx_r39().hashBytes(objectState);
return ByteBuffer.allocate((2 * Long.SIZE) / Byte.SIZE)
.putLong(h1)
.putLong(h2)
.array();
ByteBuf buf = Unpooled.buffer((2 * Long.SIZE) / Byte.SIZE).writeLong(h1).writeLong(h2);
try {
return buf.array();
} finally {
buf.release();
}
}
String getTimeoutSetName() {
@ -158,64 +160,16 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
private List<V> firstValues;
private Iterator<V> iter;
private InetSocketAddress client;
private long nextIterPos;
private boolean currentElementRemoved;
private boolean removeExecuted;
private V value;
return new RedissonBaseIterator<V>() {
@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();
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(client, nextIterPos);
}
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException("No such element at index");
}
value = iter.next();
currentElementRemoved = false;
return value;
}
@Override
public void remove() {
if (currentElementRemoved) {
throw new IllegalStateException("Element been already deleted");
}
if (iter == null) {
throw new IllegalStateException();
}
iter.remove();
void remove(V value) {
RedissonSetCache.this.remove(value);
currentElementRemoved = true;
removeExecuted = true;
}
};
@ -314,13 +268,20 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
long timeoutDate = System.currentTimeMillis() + unit.toMillis(ttl);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"redis.call('zadd', KEYS[2], ARGV[1], ARGV[3]); " +
"if redis.call('hexists', KEYS[1], ARGV[3]) == 0 then " +
"redis.call('hset', KEYS[1], ARGV[3], ARGV[2]); " +
"return 1; " +
"local value = redis.call('hexists', KEYS[1], ARGV[3]); " +
"if value == 1 then " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[3]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[2]); "
+ "return 1;"
+ "else "
+ "return 0;"
+ "end; " +
"end;" +
"return 0; ",
Arrays.<Object>asList(getName(), getTimeoutSetName()), timeoutDate, objectState, key);
"redis.call('zadd', KEYS[2], ARGV[2], ARGV[3]); " +
"redis.call('hset', KEYS[1], ARGV[3], ARGV[4]); " +
"return 1; ",
Arrays.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), timeoutDate, key, objectState);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -336,12 +297,19 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
byte[] objectState = encode(value);
byte[] key = hash(objectState);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " +
"redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " +
"return 1; " +
"end; " +
"return 0; ",
Arrays.<Object>asList(getName()), key, objectState);
"local value = redis.call('hexists', KEYS[1], ARGV[2]); " +
"if value == 1 then " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false and tonumber(expireDateScore) <= tonumber(ARGV[1]) then "
+ "redis.call('zrem', KEYS[2], ARGV[2]); "
+ "return 1;"
+ "else "
+ "return 0;"
+ "end; " +
"end;" +
"redis.call('hset', KEYS[1], ARGV[2], ARGV[3]); " +
"return 1; ",
Arrays.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), key, objectState);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -367,14 +335,14 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
public Future<Boolean> containsAllAsync(Collection<?> c) {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local s = redis.call('hvals', KEYS[1]);" +
"for i = 0, table.getn(s), 1 do " +
"for j = 0, table.getn(ARGV), 1 do "
"for i = 1, #s, 1 do " +
"for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == s[i] then "
+ "table.remove(ARGV, j) "
+ "end "
+ "end; "
+ "end;"
+ "return table.getn(ARGV) == 0 and 1 or 0; ",
+ "return #ARGV == 0 and 1 or 0; ",
Collections.<Object>singletonList(getName()), c.toArray());
}
@ -468,7 +436,7 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override
public Future<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName());
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName());
}
@Override

@ -77,7 +77,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
String setName = getValuesName(keyHash);
return commandExecutor.readAsync(getName(), codec, SCARD_VALUE, setName);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -98,7 +98,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
"return 0; ",
Arrays.<Object>asList(getName()), valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -111,7 +111,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
String setName = getValuesName(keyHash);
return commandExecutor.readAsync(getName(), codec, SISMEMBER_VALUE, setName, valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -127,7 +127,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
"return redis.call('sadd', KEYS[2], ARGV[3]); ",
Arrays.<Object>asList(getName(), setName), keyState, keyHash, valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -146,7 +146,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
+ "return res; ",
Arrays.<Object>asList(getName(), setName), keyState, valueState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -168,7 +168,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
"return redis.call('sadd', KEYS[2], unpack(ARGV, 3, #ARGV)); ",
Arrays.<Object>asList(getName(), setName), params.toArray());
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -181,7 +181,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
return new RedissonSet<V>(codec, commandExecutor, setName);
} catch (Exception e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -198,7 +198,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
return commandExecutor.readAsync(getName(), codec, RedisCommands.SMEMBERS, setName);
} catch (Exception e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -220,7 +220,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
"return members; ",
Arrays.<Object>asList(getName(), setName), keyState);
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}
@ -268,7 +268,7 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
"return members; ",
Arrays.<Object>asList(getName(), setName), params.toArray());
} catch (Exception e) {
throw new RuntimeException(e);
throw new IllegalArgumentException(e);
}
}

@ -0,0 +1,224 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RSetMultimapCache;
import io.netty.util.concurrent.Future;
/**
* @author Nikita Koksharov
*
* @param <K> key
* @param <V> value
*/
public class RedissonSetMultimapCache<K, V> extends RedissonSetMultimap<K, V> implements RSetMultimapCache<K, V> {
private final RedissonMultimapCache<K> baseCache;
RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());
baseCache = new RedissonMultimapCache<K>(connectionManager, name, codec, getTimeoutSetName());
}
RedissonSetMultimapCache(EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(codec, connectionManager, name);
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());
baseCache = new RedissonMultimapCache<K>(connectionManager, name, codec, getTimeoutSetName());
}
public Future<Boolean> containsKeyAsync(Object key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String setName = getValuesName(keyHash);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('hget', KEYS[1], ARGV[2]); " +
"if value ~= false then " +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; "
+ "return redis.call('scard', ARGV[3]) > 0 and 1 or 0;" +
"end;" +
"return 0; ",
Arrays.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), keyState, setName);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
String getTimeoutSetName() {
return "redisson_set_multimap_ttl{" + getName() + "}";
}
public Future<Boolean> containsValueAsync(Object value) {
try {
byte[] valueState = codec.getMapValueEncoder().encode(value);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"local keys = redis.call('hgetall', KEYS[1]); " +
"for i, v in ipairs(keys) do " +
"if i % 2 == 0 then " +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], keys[i-1]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate > tonumber(ARGV[2]) then " +
"local name = '{' .. KEYS[1] .. '}:' .. v; " +
"if redis.call('sismember', name, ARGV[1]) == 1 then "
+ "return 1; " +
"end;" +
"end; " +
"end;" +
"end; " +
"return 0; ",
Arrays.<Object>asList(getName(), getTimeoutSetName()), valueState, System.currentTimeMillis());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Future<Boolean> containsEntryAsync(Object key, Object value) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
byte[] valueState = codec.getMapValueEncoder().encode(value);
String setName = getValuesName(keyHash);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then " +
"if redis.call('sismember', KEYS[1], ARGV[3]) == 1 then "
+ "return 1; " +
"end;" +
"end; " +
"return 0; ",
Arrays.<Object>asList(setName, getTimeoutSetName()), System.currentTimeMillis(), keyState, valueState);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Set<V> get(K key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String setName = getValuesName(keyHash);
return new RedissonSetMultimapValues<V>(codec, commandExecutor, setName, getTimeoutSetName(), key);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Future<Collection<V>> getAllAsync(K key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String setName = getValuesName(keyHash);
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_SET,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then " +
"return redis.call('smembers', KEYS[1]); " +
"end; " +
"return {}; ",
Arrays.<Object>asList(setName, getTimeoutSetName()), System.currentTimeMillis(), keyState);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Future<Collection<V>> removeAllAsync(Object key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
String keyHash = hash(keyState);
String setName = getValuesName(keyHash);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_SET,
"redis.call('hdel', KEYS[1], ARGV[1]); " +
"local members = redis.call('smembers', KEYS[2]); " +
"redis.call('del', KEYS[2]); " +
"redis.call('zrem', KEYS[3], ARGV[1]); " +
"return members; ",
Arrays.<Object>asList(getName(), setName, getTimeoutSetName()), keyState);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean expireKey(K key, long timeToLive, TimeUnit timeUnit) {
return get(expireKeyAsync(key, timeToLive, timeUnit));
}
@Override
public Future<Boolean> expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) {
return baseCache.expireKeyAsync(key, timeToLive, timeUnit);
}
@Override
public Future<Boolean> deleteAsync() {
return baseCache.deleteAsync();
}
@Override
public Future<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
return baseCache.expireAsync(timeToLive, timeUnit);
}
@Override
public Future<Boolean> expireAtAsync(long timestamp) {
return baseCache.expireAtAsync(timestamp);
}
@Override
public Future<Boolean> clearExpireAsync() {
return baseCache.clearExpireAsync();
}
}

@ -0,0 +1,397 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.convertor.IntegerReplayConvertor;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ListScanResultReplayDecoder;
import org.redisson.client.protocol.decoder.NestedMultiDecoder;
import org.redisson.client.protocol.decoder.ObjectListReplayDecoder;
import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.core.RSet;
import io.netty.util.concurrent.Future;
/**
* Set based Multimap Cache values holder
*
* @author Nikita Koksharov
*
* @param <V> value
*/
public class RedissonSetMultimapValues<V> extends RedissonExpirable implements RSet<V> {
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<Set<Object>> EVAL_READALL = new RedisCommand<Set<Object>>("EVAL", new ObjectSetReplayDecoder(), 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_ALL_WITH_VALUES = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.OBJECTS);
private final Object key;
private final String timeoutSetName;
public RedissonSetMultimapValues(Codec codec, CommandAsyncExecutor commandExecutor, String name, String timeoutSetName, Object key) {
super(codec, commandExecutor, name);
this.timeoutSetName = timeoutSetName;
this.key = key;
}
@Override
public int size() {
return get(sizeAsync());
}
@Override
public Future<Integer> sizeAsync() {
return commandExecutor.evalReadAsync(getName(), codec, EVAL_SIZE,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; "
+ "return redis.call('scard', KEYS[2]);",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key);
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean contains(Object o) {
return get(containsAsync(o));
}
@Override
public Future<Boolean> containsAsync(Object o) {
return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_VALUE,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; "
+ "return redis.call('sismember', KEYS[2], ARGV[3]);",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o);
}
private ListScanResult<V> scanIterator(InetSocketAddress client, long startPos) {
Future<ListScanResult<V>> f = commandExecutor.evalReadAsync(client, getName(), codec, EVAL_SSCAN,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return {0, {}};"
+ "end;"
+ "return redis.call('sscan', KEYS[2], ARGV[2]);",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), startPos, key);
return get(f);
}
@Override
public Iterator<V> iterator() {
return new RedissonBaseIterator<V>() {
@Override
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(client, nextIterPos);
}
@Override
void remove(V value) {
RedissonSetMultimapValues.this.remove(value);
}
};
}
@Override
public Future<Set<V>> readAllAsync() {
return commandExecutor.evalReadAsync(getName(), codec, EVAL_READALL,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return {};"
+ "end; "
+ "return redis.call('smembers', KEYS[2]);",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key);
}
@Override
public Set<V> readAll() {
return get(readAllAsync());
}
@Override
public Object[] toArray() {
Set<Object> res = (Set<Object>) get(readAllAsync());
return res.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
Set<Object> res = (Set<Object>) get(readAllAsync());
return res.toArray(a);
}
@Override
public boolean add(V e) {
return get(addAsync(e));
}
@Override
public Future<Boolean> addAsync(V e) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_SINGLE, getName(), e);
}
@Override
public V removeRandom() {
return get(removeRandomAsync());
}
@Override
public Future<V> removeRandomAsync() {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SPOP_SINGLE, getName());
}
@Override
public Future<Boolean> removeAsync(Object o) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; "
+ "return redis.call('srem', KEYS[2], ARGV[3]) > 0 and 1 or 0;",
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o);
}
@Override
public boolean remove(Object value) {
return get(removeAsync((V)value));
}
@Override
public Future<Boolean> moveAsync(String destination, V member) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SMOVE, getName(), destination, member);
}
@Override
public boolean move(String destination, V member) {
return get(moveAsync(destination, member));
}
@Override
public boolean containsAll(Collection<?> c) {
return get(containsAllAsync(c));
}
@Override
public Future<Boolean> containsAllAsync(Collection<?> c) {
List<Object> args = new ArrayList<Object>(c.size() + 2);
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
args.add(System.currentTimeMillis());
args.add(keyState);
args.addAll(c);
} catch (IOException e) {
throw new RuntimeException(e);
}
return commandExecutor.evalReadAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; " +
"local s = redis.call('smembers', KEYS[2]);" +
"for i = 1, #s, 1 do " +
"for j = 2, #ARGV, 1 do "
+ "if ARGV[j] == s[i] "
+ "then table.remove(ARGV, j) end "
+ "end; "
+ "end;"
+ "return #ARGV == 2 and 1 or 0; ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
}
@Override
public boolean addAll(Collection<? extends V> c) {
if (c.isEmpty()) {
return false;
}
return get(addAllAsync(c));
}
@Override
public Future<Boolean> addAllAsync(Collection<? extends V> c) {
List<Object> args = new ArrayList<Object>(c.size() + 1);
args.add(getName());
args.addAll(c);
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_BOOL, args.toArray());
}
@Override
public boolean retainAll(Collection<?> c) {
return get(retainAllAsync(c));
}
@Override
public Future<Boolean> retainAllAsync(Collection<?> c) {
List<Object> args = new ArrayList<Object>(c.size() + 2);
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
args.add(System.currentTimeMillis());
args.add(keyState);
args.addAll(c);
} catch (IOException e) {
throw new RuntimeException(e);
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; " +
"local changed = 0 " +
"local s = redis.call('smembers', KEYS[2]) "
+ "local i = 1 "
+ "while i <= #s do "
+ "local element = s[i] "
+ "local isInAgrs = false "
+ "for j = 2, #ARGV, 1 do "
+ "if ARGV[j] == element then "
+ "isInAgrs = true "
+ "break "
+ "end "
+ "end "
+ "if isInAgrs == false then "
+ "redis.call('SREM', KEYS[2], element) "
+ "changed = 1 "
+ "end "
+ "i = i + 1 "
+ "end "
+ "return changed ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
}
@Override
public Future<Boolean> removeAllAsync(Collection<?> c) {
List<Object> args = new ArrayList<Object>(c.size() + 2);
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
args.add(System.currentTimeMillis());
args.add(keyState);
args.addAll(c);
} catch (IOException e) {
throw new RuntimeException(e);
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_ALL_WITH_VALUES,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if expireDate <= tonumber(ARGV[1]) then "
+ "return 0;"
+ "end; " +
"local v = 0 " +
"for i = 2, #ARGV, 1 do "
+ "if redis.call('srem', KEYS[2], ARGV[i]) == 1 "
+ "then v = 1 end "
+"end "
+ "return v ",
Arrays.<Object>asList(timeoutSetName, getName()), args.toArray());
}
@Override
public boolean removeAll(Collection<?> c) {
return get(removeAllAsync(c));
}
@Override
public int union(String... names) {
return get(unionAsync(names));
}
@Override
public Future<Integer> unionAsync(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.SUNIONSTORE_INT, args.toArray());
}
@Override
public Set<V> readUnion(String... names) {
return get(readUnionAsync(names));
}
@Override
public Future<Set<V>> readUnionAsync(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.SUNION, args.toArray());
}
@Override
public void clear() {
delete();
}
}

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

@ -38,6 +38,7 @@ import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
public class RedisClient {
@ -95,7 +96,7 @@ public class RedisClient {
}
public Future<RedisConnection> connectAsync() {
final Promise<RedisConnection> f = bootstrap.group().next().newPromise();
final Promise<RedisConnection> f = ImmediateEventExecutor.INSTANCE.newPromise();
ChannelFuture channelFuture = bootstrap.connect();
channelFuture.addListener(new ChannelFutureListener() {
@Override
@ -122,7 +123,7 @@ public class RedisClient {
}
public Future<RedisPubSubConnection> connectPubSubAsync() {
final Promise<RedisPubSubConnection> f = bootstrap.group().next().newPromise();
final Promise<RedisPubSubConnection> f = ImmediateEventExecutor.INSTANCE.newPromise();
ChannelFuture channelFuture = bootstrap.connect();
channelFuture.addListener(new ChannelFutureListener() {
@Override

@ -15,21 +15,24 @@
*/
package org.redisson.client;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.redisson.client.codec.Codec;
import org.redisson.client.handler.CommandsQueue;
import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.QueueCommand;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.connection.FastSuccessFuture;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
@ -45,7 +48,7 @@ public class RedisConnection implements RedisCommands {
private ReconnectListener reconnectListener;
private long lastUsageTime;
private final Future<?> acquireFuture = new FastSuccessFuture<Object>(this);
private final Future<?> acquireFuture = ImmediateEventExecutor.INSTANCE.newSucceededFuture(this);
public RedisConnection(RedisClient redisClient, Channel channel) {
super();
@ -59,6 +62,18 @@ public class RedisConnection implements RedisCommands {
return (C) channel.attr(RedisConnection.CONNECTION).get();
}
public void removeCurrentCommand() {
channel.attr(CommandsQueue.CURRENT_COMMAND).remove();
}
public CommandData getCurrentCommand() {
QueueCommand command = channel.attr(CommandsQueue.CURRENT_COMMAND).get();
if (command instanceof CommandData) {
return (CommandData)command;
}
return null;
}
public long getLastUsageTime() {
return lastUsageTime;
}
@ -97,21 +112,34 @@ public class RedisConnection implements RedisCommands {
return redisClient;
}
public <R> R await(Future<R> cmd) {
public <R> R await(Future<R> future) {
final CountDownLatch l = new CountDownLatch(1);
future.addListener(new FutureListener<R>() {
@Override
public void operationComplete(Future<R> future) throws Exception {
l.countDown();
}
});
try {
// TODO change connectTimeout to timeout
if (!cmd.awaitUninterruptibly(redisClient.getTimeout(), TimeUnit.MILLISECONDS)) {
Promise<R> promise = (Promise<R>)cmd;
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 (!cmd.isSuccess()) {
if (cmd.cause() instanceof RedisException) {
throw (RedisException) cmd.cause();
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) {
@ -137,13 +165,13 @@ public class RedisConnection implements RedisCommands {
}
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));
return promise;
}
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() {
@Override
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) {
Promise<R> promise = redisClient.getBootstrap().group().next().<R>newPromise();
Promise<R> promise = ImmediateEventExecutor.INSTANCE.newPromise();
return new CommandData<T, R>(promise, encoder, command, params);
}
@ -174,8 +202,8 @@ public class RedisConnection implements RedisCommands {
return closed;
}
public void forceReconnect() {
channel.close();
public ChannelFuture forceReconnectAsync() {
return channel.close();
}
/**

@ -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 final Codec delegate;
private final Codec delegate;
public ScoredCodec(Codec delegate) {
super();

@ -68,11 +68,11 @@ public class CommandDecoder extends ReplayingDecoder<State> {
private static final char ZERO = '0';
// 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, CommandData<Object, Object>> channels = PlatformDependent.newConcurrentHashMap();
private final Map<String, MultiDecoder<Object>> pubSubMessageDecoders = new HashMap<String, MultiDecoder<Object>>();
private final Map<String, CommandData<Object, Object>> pubSubChannels = PlatformDependent.newConcurrentHashMap();
public void addChannel(String channel, CommandData<Object, Object> data) {
channels.put(channel, data);
public void addPubSubCommand(String channel, CommandData<Object, Object> data) {
pubSubChannels.put(channel, data);
}
@Override
@ -84,12 +84,12 @@ public class CommandDecoder extends ReplayingDecoder<State> {
currentDecoder = StringCodec.INSTANCE.getValueDecoder();
}
if (state() == null) {
state(new State());
if (log.isTraceEnabled()) {
log.trace("channel: {} message: {}", ctx.channel(), in.toString(0, in.writerIndex(), CharsetUtil.UTF_8));
}
if (state() == null) {
state(new State());
}
state().setDecoderState(null);
@ -100,8 +100,10 @@ public class CommandDecoder extends ReplayingDecoder<State> {
try {
// if (state().getSize() > 0) {
// List<Object> respParts = new ArrayList<Object>();
// respParts.addAll(state().getRespParts());
// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts);
// if (state().getRespParts() != null) {
// respParts = state().getRespParts();
// }
// decodeMulti(in, cmd, null, ctx.channel(), currentDecoder, state().getSize(), respParts, true);
// } else {
decode(in, cmd, null, ctx.channel(), currentDecoder);
// }
@ -137,9 +139,13 @@ public class CommandDecoder extends ReplayingDecoder<State> {
cmd.getPromise().tryFailure(e);
}
if (!cmd.getPromise().isSuccess()) {
if (!(cmd.getPromise().cause() instanceof RedisMovedException
|| cmd.getPromise().cause() instanceof RedisAskException
|| cmd.getPromise().cause() instanceof RedisLoadingException)) {
error = (RedisException) cmd.getPromise().cause();
}
}
}
if (i == commands.getCommands().size()) {
Promise<Void> promise = commands.getPromise();
@ -149,7 +155,6 @@ public class CommandDecoder extends ReplayingDecoder<State> {
}
} else {
if (!promise.trySuccess(null) && promise.cause() instanceof RedisTimeoutException) {
// TODO try increase timeout
log.warn("response has been skipped due to timeout! channel: {}, command: {}", ctx.channel(), data);
}
}
@ -201,9 +206,7 @@ public class CommandDecoder extends ReplayingDecoder<State> {
}
}
} else if (code == ':') {
String status = in.readBytes(in.bytesBefore((byte) '\r')).toString(CharsetUtil.UTF_8);
in.skipBytes(2);
Object result = Long.valueOf(status);
Long result = readLong(in);
handleResult(data, parts, result, false, channel);
} else if (code == '$') {
ByteBuf buf = readBytes(in);
@ -214,21 +217,27 @@ public class CommandDecoder extends ReplayingDecoder<State> {
handleResult(data, parts, result, false, channel);
} else if (code == '*') {
long size = readLong(in);
// state().setSizeOnce(size);
List<Object> respParts = new ArrayList<Object>();
boolean top = false;
// if (state().trySetSize(size)) {
// state().setRespParts(respParts);
// top = true;
// }
decodeMulti(in, data, parts, channel, currentDecoder, size, respParts);
decodeMulti(in, data, parts, channel, currentDecoder, size, respParts, top);
} else {
throw new IllegalStateException("Can't decode replay " + (char)code);
}
}
private void decodeMulti(ByteBuf in, CommandData<Object, Object> data, List<Object> parts,
Channel channel, Decoder<Object> currentDecoder, long size, List<Object> respParts)
Channel channel, Decoder<Object> currentDecoder, long size, List<Object> respParts, boolean top)
throws IOException {
for (int i = respParts.size(); i < size; i++) {
decode(in, data, respParts, channel, currentDecoder);
// if (top) {
// checkpoint();
// }
}
MultiDecoder<Object> decoder = messageDecoder(data, respParts, channel);
@ -250,9 +259,6 @@ public class CommandDecoder extends ReplayingDecoder<State> {
}
} else {
handleMultiResult(data, parts, channel, result);
// if (parts != null && !decoder.isApplicable(parts.size(), state())) {
// state().setRespParts(parts);
// }
}
}
@ -263,14 +269,14 @@ public class CommandDecoder extends ReplayingDecoder<State> {
} else {
if (result instanceof PubSubStatusMessage) {
String channelName = ((PubSubStatusMessage) result).getChannel();
CommandData<Object, Object> d = channels.get(channelName);
CommandData<Object, Object> d = pubSubChannels.get(channelName);
if (Arrays.asList("PSUBSCRIBE", "SUBSCRIBE").contains(d.getCommand().getName())) {
channels.remove(channelName);
messageDecoders.put(channelName, d.getMessageDecoder());
pubSubChannels.remove(channelName);
pubSubMessageDecoders.put(channelName, d.getMessageDecoder());
}
if (Arrays.asList("PUNSUBSCRIBE", "UNSUBSCRIBE").contains(d.getCommand().getName())) {
channels.remove(channelName);
messageDecoders.remove(channelName);
pubSubChannels.remove(channelName);
pubSubMessageDecoders.remove(channelName);
}
}
@ -306,17 +312,17 @@ public class CommandDecoder extends ReplayingDecoder<State> {
if (data == null) {
if (Arrays.asList("subscribe", "psubscribe", "punsubscribe", "unsubscribe").contains(parts.get(0))) {
String channelName = (String) parts.get(1);
CommandData<Object, Object> commandData = channels.get(channelName);
CommandData<Object, Object> commandData = pubSubChannels.get(channelName);
if (commandData == null) {
return null;
}
return commandData.getCommand().getReplayMultiDecoder();
} else if (parts.get(0).equals("message")) {
String channelName = (String) parts.get(1);
return messageDecoders.get(channelName);
return pubSubMessageDecoders.get(channelName);
} else if (parts.get(0).equals("pmessage")) {
String patternName = (String) parts.get(1);
return messageDecoders.get(patternName);
return pubSubMessageDecoders.get(patternName);
}
}
@ -327,11 +333,11 @@ public class CommandDecoder extends ReplayingDecoder<State> {
if (data == null) {
if (parts.size() == 2 && parts.get(0).equals("message")) {
String channelName = (String) parts.get(1);
return messageDecoders.get(channelName);
return pubSubMessageDecoders.get(channelName);
}
if (parts.size() == 3 && parts.get(0).equals("pmessage")) {
String patternName = (String) parts.get(1);
return messageDecoders.get(patternName);
return pubSubMessageDecoders.get(patternName);
}
return currentDecoder;
}

@ -18,6 +18,7 @@ package org.redisson.client.handler;
import java.util.List;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.Encoder;
import org.redisson.client.protocol.DefaultParamsEncoder;
@ -112,8 +113,11 @@ public class CommandEncoder extends MessageToByteEncoder<CommandData<Object, Obj
if (inParamType.get(typeIndex) == ValueType.OBJECTS) {
return msg.getCodec().getValueEncoder();
}
if (inParamType.get(typeIndex) == ValueType.BINARY) {
return ByteArrayCodec.INSTANCE.getValueEncoder();
if (inParamType.get(typeIndex) == ValueType.OBJECT) {
return msg.getCodec().getValueEncoder();
}
if (inParamType.get(typeIndex) == ValueType.STRING) {
return StringCodec.INSTANCE.getValueEncoder();
}
throw new IllegalStateException();
}

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

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

@ -29,11 +29,12 @@ public class State {
super();
}
public void setSizeOnce(long size) {
public boolean trySetSize(long size) {
if (this.size != 0) {
return;
return false;
}
this.size = size;
return true;
}
public long getSize() {
return size;

@ -78,4 +78,8 @@ public class CommandData<T, R> implements QueueCommand {
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> TIMEOUTLESS_COMMANDS = new HashSet<String>(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(),
RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName()));
List<CommandData<Object, Object>> getPubSubOperations();
}

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

@ -15,10 +15,11 @@
*/
package org.redisson.client.protocol;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.Set;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.convertor.BitSetReplayConvertor;
@ -40,7 +41,7 @@ import org.redisson.client.protocol.decoder.ListScanResultReplayDecoder;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.MapScanResultReplayDecoder;
import org.redisson.client.protocol.decoder.NestedMultiDecoder;
import org.redisson.client.protocol.decoder.NestedMultiDecoder2;
import org.redisson.client.protocol.decoder.FlatNestedMultiDecoder;
import org.redisson.client.protocol.decoder.ObjectFirstResultReplayDecoder;
import org.redisson.client.protocol.decoder.ObjectListReplayDecoder;
import org.redisson.client.protocol.decoder.ObjectMapEntryReplayDecoder;
@ -57,6 +58,12 @@ import org.redisson.client.protocol.pubsub.PubSubStatusDecoder;
public interface RedisCommands {
RedisStrictCommand<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<Boolean> GETBIT = new RedisStrictCommand<Boolean>("GETBIT", new BooleanReplayConvertor());
@ -189,6 +196,7 @@ public interface RedisCommands {
RedisStrictCommand<List<String>> KEYS = new RedisStrictCommand<List<String>>("KEYS", new StringListReplayDecoder());
RedisCommand<List<Object>> MGET = new RedisCommand<List<Object>>("MGET", new ObjectListReplayDecoder<Object>());
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> HSET = new RedisCommand<Boolean>("HSET", new BooleanReplayConvertor(), 2, ValueType.MAP);
@ -208,6 +216,7 @@ public interface RedisCommands {
RedisStrictCommand<Long> DEL = new RedisStrictCommand<Long>("DEL");
RedisStrictCommand<Long> DBSIZE = new RedisStrictCommand<Long>("DBSIZE");
RedisStrictCommand<Boolean> DEL_BOOL = new RedisStrictCommand<Boolean>("DEL", new BooleanReplayConvertor());
RedisStrictCommand<Boolean> DEL_OBJECTS = new RedisStrictCommand<Boolean>("DEL", new BooleanAmountReplayConvertor());
RedisStrictCommand<Void> DEL_VOID = new RedisStrictCommand<Void>("DEL", new VoidReplayConvertor());
RedisCommand<Object> GET = new RedisCommand<Object>("GET");
@ -237,7 +246,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());
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());

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

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

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

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

@ -346,10 +346,11 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
failedSlaves.removeAll(currentPart.getFailedSlaveAddresses());
for (URI uri : failedSlaves) {
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());
}
}
}
private void addRemoveSlaves(final MasterSlaveEntry entry, final ClusterPartition currentPart, final ClusterPartition newPart) {
Set<URI> removedSlaves = new HashSet<URI>(currentPart.getSlaveAddresses());
@ -358,9 +359,10 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager {
for (URI uri : removedSlaves) {
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());
}
}
Set<URI> addedSlaves = new HashSet<URI>(newPart.getSlaveAddresses());
addedSlaves.removeAll(currentPart.getSlaveAddresses());

@ -23,8 +23,15 @@ import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
@ -32,6 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import io.netty.buffer.ByteBuf;
@ -49,6 +57,12 @@ public class JsonJacksonCodec implements Codec {
public static final JsonJacksonCodec INSTANCE = new JsonJacksonCodec();
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
@JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.PUBLIC_ONLY, setterVisibility = Visibility.PUBLIC_ONLY, isGetterVisibility = Visibility.PUBLIC_ONLY)
public static class ThrowableMixIn {
}
private final ObjectMapper mapObjectMapper = initObjectMapper();
protected ObjectMapper initObjectMapper() {
@ -110,7 +124,9 @@ public class JsonJacksonCodec implements Codec {
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
objectMapper.addMixIn(Throwable.class, ThrowableMixIn.class);
}
@Override

@ -18,6 +18,7 @@ package org.redisson.command;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.redisson.SlotCallback;
import org.redisson.client.RedisException;
@ -38,6 +39,8 @@ public interface CommandAsyncExecutor {
<V> RedisException convertException(Future<V> future);
boolean await(Future<?> future, long timeout, TimeUnit timeoutUnit) throws InterruptedException;
<V> V get(Future<V> future);
<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.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.redisson.RedisClientResult;
import org.redisson.RedissonShutdownException;
import org.redisson.SlotCallback;
import org.redisson.client.RedisAskException;
import org.redisson.client.RedisConnection;
@ -38,6 +41,7 @@ import org.redisson.client.WriteRedisConnectionException;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.QueueCommand;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.cluster.ClusterSlotRange;
@ -47,6 +51,7 @@ import org.redisson.connection.NodeSource.Redirect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.Timeout;
@ -55,6 +60,7 @@ import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
/**
*
@ -67,9 +73,6 @@ public class CommandAsyncService implements CommandAsyncExecutor {
final ConnectionManager connectionManager;
private final Set<String> skipTimeout = new HashSet<String>(Arrays.asList(RedisCommands.BLPOP_VALUE.getName(),
RedisCommands.BRPOP_VALUE.getName(), RedisCommands.BRPOPLPUSH.getName()));
public CommandAsyncService(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
@ -81,13 +84,38 @@ public class CommandAsyncService implements CommandAsyncExecutor {
@Override
public <V> 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()) {
return future.getNow();
}
throw convertException(future);
}
@Override
public boolean await(Future<?> future, long timeout, TimeUnit timeoutUnit) throws InterruptedException {
final CountDownLatch l = new CountDownLatch(1);
future.addListener(new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
l.countDown();
}
});
return l.await(timeout, timeoutUnit);
}
@Override
public <T, R> Future<R> readAsync(InetSocketAddress client, String key, Codec codec, RedisCommand<T> command, Object ... params) {
Promise<R> mainPromise = connectionManager.newPromise();
@ -354,7 +382,7 @@ public class CommandAsyncService implements CommandAsyncExecutor {
}
if (!connectionManager.getShutdownLatch().acquire()) {
mainPromise.setFailure(new IllegalStateException("Redisson is shutdown"));
mainPromise.setFailure(new RedissonShutdownException("Redisson is shutdown"));
return;
}
@ -460,8 +488,9 @@ public class CommandAsyncService implements CommandAsyncExecutor {
details.getTimeout().cancel();
int timeoutTime = connectionManager.getConfig().getTimeout();
if (skipTimeout.contains(details.getCommand().getName())) {
if (QueueCommand.TIMEOUTLESS_COMMANDS.contains(details.getCommand().getName())) {
Integer popTimeout = Integer.valueOf(details.getParams()[details.getParams().length - 1].toString());
handleBlockingOperations(details, connection, popTimeout);
if (popTimeout == 0) {
return;
}
@ -482,6 +511,71 @@ public class CommandAsyncService implements CommandAsyncExecutor {
details.setTimeout(timeout);
}
private <R, V> void handleBlockingOperations(final AsyncDetails<V, R> details, final RedisConnection connection, Integer popTimeout) {
final FutureListener<Boolean> listener = new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
details.getMainPromise().tryFailure(new RedissonShutdownException("Redisson is shutdown"));
}
};
final AtomicBoolean canceledByScheduler = new AtomicBoolean();
final ScheduledFuture<?> scheduledFuture;
if (popTimeout != 0) {
// to handle cases when connection has been lost
final Channel orignalChannel = connection.getChannel();
scheduledFuture = connectionManager.getGroup().schedule(new Runnable() {
@Override
public void run() {
// there is no re-connection was made
// and connection is still active
if (orignalChannel == connection.getChannel()
&& connection.isActive()) {
return;
}
canceledByScheduler.set(true);
details.getAttemptPromise().trySuccess(null);
}
}, popTimeout, TimeUnit.SECONDS);
} else {
scheduledFuture = null;
}
details.getMainPromise().addListener(new FutureListener<R>() {
@Override
public void operationComplete(Future<R> future) throws Exception {
if (scheduledFuture != null) {
scheduledFuture.cancel(false);
}
connectionManager.getShutdownPromise().removeListener(listener);
// handling cancel operation for commands from skipTimeout collection
if ((future.isCancelled() && details.getAttemptPromise().cancel(true))
|| canceledByScheduler.get()) {
connection.forceReconnectAsync();
return;
}
if (future.cause() instanceof RedissonShutdownException) {
details.getAttemptPromise().tryFailure(future.cause());
}
}
});
details.getAttemptPromise().addListener(new FutureListener<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);
}
private <R, V> void checkConnectionFuture(final NodeSource source,
final AsyncDetails<V, R> details) {
if (details.getAttemptPromise().isDone() || details.getMainPromise().isCancelled() || details.getConnectionFuture().isCancelled()) {
@ -601,7 +695,7 @@ public class CommandAsyncService implements CommandAsyncExecutor {
}
details.getMainPromise().setSuccess(res);
} else {
details.getMainPromise().setFailure(future.cause());
details.getMainPromise().tryFailure(future.cause());
}
AsyncDetails.release(details);
}

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

@ -27,7 +27,6 @@ import org.redisson.client.RedisPubSubListener;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.cluster.ClusterSlotRange;
import org.redisson.connection.ClientConnectionsEntry.FreezeReason;
import org.redisson.core.NodeType;
import org.redisson.misc.InfinitySemaphoreLatch;
@ -62,8 +61,6 @@ public interface ConnectionManager {
<R> Future<R> newFailedFuture(Throwable cause);
void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason);
Collection<RedisClientEntry> getClients();
void shutdownAsync(RedisClient client);
@ -108,4 +105,6 @@ public interface ConnectionManager {
InfinitySemaphoreLatch getShutdownLatch();
Future<Boolean> getShutdownPromise();
}

@ -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.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.PlatformDependent;
@ -122,6 +123,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
protected final Map<ClusterSlotRange, MasterSlaveEntry> entries = PlatformDependent.newConcurrentHashMap();
private final Promise<Boolean> shutdownPromise;
private final InfinitySemaphoreLatch shutdownLatch = new InfinitySemaphoreLatch();
private final Set<RedisClientEntry> clients = Collections.newSetFromMap(PlatformDependent.<RedisClientEntry, Boolean>newConcurrentHashMap());
@ -156,6 +159,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
this.socketChannelClass = NioSocketChannel.class;
}
this.codec = cfg.getCodec();
this.shutdownPromise = newPromise();
this.isClusterMode = cfg.isClusterConfig();
}
@ -538,77 +542,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
return null;
}
public void slaveDown(MasterSlaveEntry entry, String host, int port, FreezeReason freezeReason) {
Collection<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) {
MasterSlaveEntry entry = getEntry(slotRange);
slaveDown(entry, host, port, freezeReason);
getEntry(slotRange).slaveDown(host, port, freezeReason);
}
protected void changeMaster(ClusterSlotRange slotRange, String host, int port) {
@ -674,7 +609,10 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
@Override
public void shutdown() {
shutdownLatch.closeAndAwaitUninterruptibly();
shutdownLatch.close();
shutdownPromise.trySuccess(true);
shutdownLatch.awaitUninterruptibly();
for (MasterSlaveEntry entry : entries.values()) {
entry.shutdown();
}
@ -699,17 +637,17 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
@Override
public <R> Promise<R> newPromise() {
return group.next().newPromise();
return ImmediateEventExecutor.INSTANCE.newPromise();
}
@Override
public <R> Future<R> newSucceededFuture(R value) {
return new FastSuccessFuture<R>(value);
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(value);
}
@Override
public <R> Future<R> newFailedFuture(Throwable cause) {
return new FastFailedFuture<R>(cause);
return ImmediateEventExecutor.INSTANCE.newFailedFuture(cause);
}
@Override
@ -732,6 +670,11 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
return shutdownLatch;
}
@Override
public Future<Boolean> getShutdownPromise() {
return shutdownPromise;
}
@Override
public ConnectionEventsHub getConnectionEventsHub() {
return connectionEventsHub;

@ -28,6 +28,9 @@ import org.redisson.ReadMode;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisConnection;
import org.redisson.client.RedisPubSubConnection;
import org.redisson.client.RedisPubSubListener;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.CommandData;
import org.redisson.cluster.ClusterSlotRange;
import org.redisson.connection.ClientConnectionsEntry.FreezeReason;
import org.redisson.connection.balancer.LoadBalancerManager;
@ -37,7 +40,10 @@ import org.redisson.core.NodeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
@ -90,8 +96,11 @@ public class MasterSlaveEntry {
return writeConnectionHolder.add(masterEntry);
}
public Collection<RedisPubSubConnection> slaveDown(String host, int port, FreezeReason freezeReason) {
Collection<RedisPubSubConnection> conns = slaveBalancer.freeze(host, port, freezeReason);
public boolean slaveDown(String host, int port, FreezeReason freezeReason) {
ClientConnectionsEntry entry = slaveBalancer.freeze(host, port, freezeReason);
if (entry == null) {
return false;
}
// add master as slave if no more slaves available
if (config.getReadMode() == ReadMode.SLAVE && slaveBalancer.getAvailableClients() == 0) {
@ -100,7 +109,153 @@ public class MasterSlaveEntry {
log.info("master {}:{} used as slave", addr.getHostName(), addr.getPort());
}
}
return conns;
// close all connections
while (true) {
final RedisConnection connection = entry.pollConnection();
if (connection == null) {
break;
}
connection.closeAsync().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
reattachBlockingQueue(connection);
}
});
}
// close all pub/sub connections
while (true) {
RedisPubSubConnection connection = entry.pollSubscribeConnection();
if (connection == null) {
break;
}
connection.closeAsync();
}
for (RedisPubSubConnection connection : entry.getAllSubscribeConnections()) {
reattachPubSub(connection);
}
entry.getAllSubscribeConnections().clear();
return true;
}
private void reattachPubSub(RedisPubSubConnection redisPubSubConnection) {
for (String channelName : redisPubSubConnection.getChannels().keySet()) {
PubSubConnectionEntry pubSubEntry = connectionManager.getPubSubEntry(channelName);
synchronized (pubSubEntry) {
pubSubEntry.close();
Collection<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) {
@ -134,7 +289,7 @@ public class MasterSlaveEntry {
// exclude master from slaves
if (config.getReadMode() == ReadMode.SLAVE
&& (!addr.getHostName().equals(host) || port != addr.getPort())) {
connectionManager.slaveDown(this, addr.getHostName(), addr.getPort(), FreezeReason.SYSTEM);
slaveDown(addr.getHostName(), addr.getPort(), FreezeReason.SYSTEM);
log.info("master {}:{} excluded from slaves", addr.getHostName(), addr.getPort());
}
return true;
@ -155,7 +310,7 @@ public class MasterSlaveEntry {
// more than one slave available, so master can be removed from slaves
if (config.getReadMode() == ReadMode.SLAVE
&& slaveBalancer.getAvailableClients() > 1) {
connectionManager.slaveDown(this, host, port, FreezeReason.SYSTEM);
slaveDown(host, port, FreezeReason.SYSTEM);
}
connectionManager.shutdownAsync(oldMaster.getClient());
}

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

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

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

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

@ -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,110 @@ import java.util.Set;
public interface RLexSortedSet extends RLexSortedSetAsync, Set<String>, RExpirable {
int removeRangeTail(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSet#removeRangeTail(String, boolean)}
*/
@Deprecated
int removeRangeTailByLex(String fromElement, boolean fromInclusive);
int removeRangeHead(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSet#removeRangeHead(String, boolean)}
*/
@Deprecated
int removeRangeHeadByLex(String toElement, boolean toInclusive);
int removeRange(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSet#removeRange(String, boolean)}
*/
@Deprecated
int removeRangeByLex(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
int countTail(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSet#countTail(String, boolean)}
*/
@Deprecated
int lexCountTail(String fromElement, boolean fromInclusive);
int countHead(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSet#countHead(String, boolean)}
*/
@Deprecated
int lexCountHead(String toElement, boolean toInclusive);
Collection<String> rangeTail(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSet#rangeTail(String, boolean)}
*/
@Deprecated
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> 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> 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> 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> 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);
int count(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSet#count(String, boolean, String, boolean)}
*/
@Deprecated
int lexCount(String fromElement, boolean fromInclusive, String toElement, boolean toInclusive);
int rank(String o);
Collection<String> range(int startIndex, int endIndex);
/**
* Use {@link RLexSortedSet#range(int, int)}
*/
@Deprecated
Collection<String> valueRange(int startIndex, int endIndex);
}

@ -21,32 +21,110 @@ import io.netty.util.concurrent.Future;
public interface RLexSortedSetAsync extends RCollectionAsync<String> {
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> removeRangeTailAsync(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSetAsync#removeRangeTailAsync(String, boolean, String, boolean)}
*/
@Deprecated
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> countTailAsync(String fromElement, boolean fromInclusive);
/**
* Use {@link RLexSortedSetAsync#countTailAsync(String, boolean)}
*/
@Deprecated
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<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>> rangeHeadAsync(String toElement, boolean toInclusive);
/**
* Use {@link RLexSortedSetAsync#rangeHeadAsync(String, boolean)}
*/
@Deprecated
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>> 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>> 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>> 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<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> 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);
}

@ -0,0 +1,20 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.core;
public interface RListMultimapCache<K, V> extends RListMultimap<K, V>, RMultimapCache<K, V> {
}

@ -0,0 +1,33 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.core;
import java.util.concurrent.TimeUnit;
public interface RMultimapCache<K, V> extends RMultimap<K, V>, RMultimapCacheAsync<K, V> {
/**
* Set a timeout for key. After the timeout has expired,
* the key and its values will automatically be deleted.
*
* @param key
* @param timeToLive - timeout before key will be deleted
* @param timeUnit - timeout time unit
* @return <code>true</code> if key exists and the timeout was set and <code>false</code> if key not exists
*/
boolean expireKey(K key, long timeToLive, TimeUnit timeUnit);
}

@ -0,0 +1,35 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.core;
import java.util.concurrent.TimeUnit;
import io.netty.util.concurrent.Future;
public interface RMultimapCacheAsync<K, V> extends RMultimapAsync<K, V> {
/**
* Set a timeout for key in async mode. After the timeout has expired,
* the key and its values will automatically be deleted.
*
* @param key
* @param timeToLive - timeout before key will be deleted
* @param timeUnit - timeout time unit
* @return <code>true</code> if key exists and the timeout was set and <code>false</code> if key not exists
*/
Future<Boolean> expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit);
}

@ -0,0 +1,115 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.core;
import java.util.concurrent.TimeUnit;
/**
* Allows to execute object methods remotely between Redisson instances (Server side and Client side instances in terms of remote invocation).
* <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 {
/**
* Register remote service with single executor
*
* @param remoteInterface
* @param object
*/
<T> void register(Class<T> remoteInterface, T object);
/**
* Register remote service with custom executors amount
*
* @param remoteInterface
* @param object
* @param executorsAmount
*/
<T> void register(Class<T> remoteInterface, T object, int executorsAmount);
/**
* Get remote service object for remote invocations.
* <p/>
* Ack timeout = 1000 ms by default
* <p/>
* Execution timeout = 30 sec by default
*
* @param remoteInterface
* @return
*/
<T> T get(Class<T> remoteInterface);
/**
* Get remote service object for remote invocations
* 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 executionTimeout - invocation timeout
* @param executionTimeUnit
* @param ackTimeout - ack timeout
* @param ackTimeUnit
* @return
*/
<T> T get(Class<T> remoteInterface, long executionTimeout, TimeUnit executionTimeUnit, long ackTimeout, TimeUnit ackTimeUnit);
}

@ -0,0 +1,20 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.core;
public interface RSetMultimapCache<K, V> extends RSetMultimap<K, V>, RMultimapCache<K, V> {
}

@ -20,19 +20,16 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.PlatformDependent;
/**
* Groups multiple independent locks and handles them as one lock.
@ -75,52 +72,16 @@ public class RedissonMultiLock implements Lock {
}
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Object> result = new AtomicReference<Object>();
Promise<Void> promise = new DefaultPromise<Void>() {
public Promise<Void> setSuccess(Void result) {
latch.countDown();
return this;
};
public Promise<Void> setFailure(Throwable cause) {
result.set(cause);
latch.countDown();
return this;
};
};
Promise<Void> promise = ImmediateEventExecutor.INSTANCE.newPromise();
lock(promise, 0, leaseTime, unit);
latch.await();
if (result.get() instanceof Throwable) {
PlatformDependent.throwException((Throwable)result.get());
}
promise.sync();
}
@Override
public void lockInterruptibly() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Object> result = new AtomicReference<Object>();
Promise<Void> promise = new DefaultPromise<Void>() {
public Promise<Void> setSuccess(Void result) {
latch.countDown();
return this;
};
public Promise<Void> setFailure(Throwable cause) {
result.set(cause);
latch.countDown();
return this;
};
};
lock(promise, 0, -1, null);
latch.await();
if (result.get() instanceof Throwable) {
PlatformDependent.throwException((Throwable)result.get());
}
lockInterruptibly(-1, null);
}
private void lock(final Promise<Void> promise, final long waitTime, final long leaseTime, final TimeUnit unit) throws InterruptedException {

@ -83,9 +83,12 @@ public class InfinitySemaphoreLatch extends AbstractQueuedSynchronizer {
return closed;
}
// waiting for an open state
public final boolean closeAndAwaitUninterruptibly() {
public void close() {
closed = true;
}
// waiting for an open state
public final boolean awaitUninterruptibly() {
try {
return await(15, TimeUnit.SECONDS);
} catch (InterruptedException e) {

@ -15,6 +15,8 @@
*/
package org.redisson.reactive;
import java.util.concurrent.atomic.AtomicLong;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.redisson.api.RCollectionReactive;
@ -40,9 +42,10 @@ public class PublisherAdder<V> {
c.subscribe(new DefaultSubscriber<V>() {
volatile boolean completed;
AtomicLong values = new AtomicLong();
Subscription s;
Long lastSize = 0L;
V lastValue;
@Override
public void onSubscribe(Subscription s) {
@ -52,7 +55,7 @@ public class PublisherAdder<V> {
@Override
public void onNext(V o) {
lastValue = o;
values.getAndIncrement();
destination.add(o).subscribe(new DefaultSubscriber<Long>() {
@Override
@ -68,19 +71,18 @@ public class PublisherAdder<V> {
@Override
public void onNext(Long o) {
lastSize = sum(lastSize, o);
}
@Override
public void onComplete() {
lastValue = null;
s.request(1);
if (values.decrementAndGet() == 0 && completed) {
promise.onNext(lastSize);
}
}
});
}
@Override
public void onComplete() {
if (lastValue == null) {
completed = true;
if (values.get() == 0) {
promise.onNext(lastSize);
}
}

@ -361,7 +361,7 @@ public class RedissonMapCacheReactive<K, V> extends RedissonMapReactive<K, V> im
@Override
public Publisher<Boolean> delete() {
return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName());
return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName());
}
@Override

@ -305,7 +305,7 @@ public class RedissonSetCacheReactive<V> extends RedissonExpirableReactive imple
@Override
public Publisher<Boolean> delete() {
return commandExecutor.writeReactive(getName(), RedisCommands.DEL_BOOL, getName(), getTimeoutSetName());
return commandExecutor.writeReactive(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName());
}
@Override

@ -16,7 +16,6 @@
package org.redisson.reactive;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.reactivestreams.Publisher;
@ -24,56 +23,36 @@ import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.redisson.client.protocol.decoder.ListScanResult;
import reactor.core.reactivestreams.SubscriberBarrier;
import reactor.rx.Stream;
import reactor.rx.subscription.ReactiveSubscription;
public abstract class SetReactiveIterator<V> extends Stream<V> {
@Override
public void subscribe(final Subscriber<? super V> t) {
t.onSubscribe(new SubscriberBarrier<V, V>(t) {
t.onSubscribe(new ReactiveSubscription<V>(this, t) {
private List<V> firstValues;
private long nextIterPos;
private InetSocketAddress client;
private long currentIndex;
private List<V> prevValues = new ArrayList<V>();
@Override
protected void doRequest(long n) {
protected void onRequest(long n) {
currentIndex = n;
if (!prevValues.isEmpty()) {
List<V> vals = new ArrayList<V>(prevValues);
prevValues.clear();
handle(vals);
if (currentIndex == 0) {
return;
}
}
nextValues();
}
private void handle(List<V> vals) {
for (V val : vals) {
if (currentIndex > 0) {
onNext(val);
} else {
prevValues.add(val);
}
currentIndex--;
if (currentIndex == 0) {
onComplete();
}
}
}
protected void nextValues() {
final SubscriberBarrier<V, V> m = this;
final ReactiveSubscription<V> m = this;
scanIteratorReactive(client, nextIterPos).subscribe(new Subscriber<ListScanResult<V>>() {
@Override

@ -0,0 +1,20 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.remote;
public interface RRemoteServiceResponse {
}

@ -0,0 +1,26 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.remote;
/**
* Worker sends this message when it has received a {@link RemoteServiceRequest}.
*
* @author Nikita Koksharov
*
*/
public class RemoteServiceAck implements RRemoteServiceResponse {
}

@ -0,0 +1,36 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.remote;
/**
* Rises when remote method executor has not answered
* within Ack timeout.
* <p/>
* Method invocation has not been started in this case.
* So a new invocation attempt can be made.
*
* @author Nikita Koksharov
*
*/
public class RemoteServiceAckTimeoutException extends RuntimeException {
private static final long serialVersionUID = 1820133675653636587L;
public RemoteServiceAckTimeoutException(String message) {
super(message);
}
}

@ -0,0 +1,68 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.remote;
public class RemoteServiceKey {
private final Class<?> serviceInterface;
private final String methodName;
public RemoteServiceKey(Class<?> serviceInterface, String methodName) {
super();
this.serviceInterface = serviceInterface;
this.methodName = methodName;
}
public String getMethodName() {
return methodName;
}
public Class<?> getServiceInterface() {
return serviceInterface;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((methodName == null) ? 0 : methodName.hashCode());
result = prime * result + ((serviceInterface == null) ? 0 : serviceInterface.getName().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RemoteServiceKey other = (RemoteServiceKey) obj;
if (methodName == null) {
if (other.methodName != null)
return false;
} else if (!methodName.equals(other.methodName))
return false;
if (serviceInterface == null) {
if (other.serviceInterface != null)
return false;
} else if (!serviceInterface.equals(other.serviceInterface))
return false;
return true;
}
}

@ -0,0 +1,39 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.remote;
import java.lang.reflect.Method;
public class RemoteServiceMethod {
private final Object bean;
private final Method method;
public RemoteServiceMethod(Method method, Object bean) {
super();
this.method = method;
this.bean = bean;
}
public Object getBean() {
return bean;
}
public Method getMethod() {
return method;
}
}

@ -0,0 +1,73 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.remote;
import java.util.Arrays;
public class RemoteServiceRequest {
private String requestId;
private String methodName;
private Object[] args;
private long ackTimeout;
private long responseTimeout;
private long date;
public RemoteServiceRequest() {
}
public RemoteServiceRequest(String requestId, String methodName, Object[] args, long ackTimeout, long responseTimeout, long date) {
super();
this.requestId = requestId;
this.methodName = methodName;
this.args = args;
this.ackTimeout = ackTimeout;
this.responseTimeout = responseTimeout;
this.date = date;
}
public long getResponseTimeout() {
return responseTimeout;
}
public long getDate() {
return date;
}
public long getAckTimeout() {
return ackTimeout;
}
public String getRequestId() {
return requestId;
}
public Object[] getArgs() {
return args;
}
public String getMethodName() {
return methodName;
}
@Override
public String toString() {
return "RemoteServiceRequest [requestId=" + requestId + ", methodName=" + methodName + ", args="
+ Arrays.toString(args) + ", ackTimeout=" + ackTimeout + ", date=" + date + "]";
}
}

@ -0,0 +1,47 @@
/**
* Copyright 2014 Nikita Koksharov, Nickolay Borbit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.remote;
public class RemoteServiceResponse implements RRemoteServiceResponse {
private Object result;
private Throwable error;
public RemoteServiceResponse() {
}
public RemoteServiceResponse(Object result) {
this.result = result;
}
public RemoteServiceResponse(Throwable error) {
this.error = error;
}
public Throwable getError() {
return error;
}
public Object getResult() {
return result;
}
@Override
public String toString() {
return "RemoteServiceResponse [result=" + result + ", error=" + error + "]";
}
}

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

Loading…
Cancel
Save