Merge branch 'redisson/master'

pull/894/head
Rui Gu 8 years ago
commit 39f876ee8f

@ -2,6 +2,138 @@ Redisson Releases History
================================
####Please Note: trunk is current development branch.
Try __ULTRA-FAST__ [Redisson PRO](https://redisson.pro) edition.
####19-Feb-2017 - versions 2.8.0 and 3.3.0 released
Feature - __`RClusteredLocalCachedMap` object added__ More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections#713-map-data-partitioning)
Feature - __`RClusteredMapCache` object added__ More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections#713-map-data-partitioning)
Feature - __`RClusteredSetCache` object added__ More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections/#732-set-data-partitioning)
Feature - __`RPriorityQueue` object added__ More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections/#715-priority-queue)
Feature - __`RPriorityDeque` object added__ More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections/#716-priority-deque)
Feature - `removeAllListeners` and `removeListener` by instance methods added for `RTopic` and `RPatternTopic`
Feature - `RLockAsync` interface added
Improvement - `RRemoteService` is now able to support method overload
Fixed - `RLocalCachedMap` is not Redis cluster compatible
Fixed - cascade slaves are not supported in cluster mode
Fixed - shutdown checking during master change state check added
Fixed - master isn't checked during new slave discovery in Sentinel mode
####02-Feb-2017 - versions 2.7.4 and 3.2.4 released
Feature - Allow to specify Redisson instance/config during JCache cache creation
Fixed - `ByteBuf.release` method invocation is missed in `LZ4Codec` and `SnappyCodec`
Fixed - AssertionError during Redisson shutdown
Fixed - `RReadWriteLock.readLock` couldn't be acquired by same thread which has already acquired `writeLock`
Fixed - failed `RFairLock.tryLock` attempt retains caller thread in fairLock queue
Fixed - `factory already defined` error
Fixed - `JCache` expiration listener doesn't work
Fixed - `RLocalCachedMap` doesn't work with `SerializationCodec`
Fixed - `Can't find entry` error during operation execution on slave nodes
####19-Jan-2017 - versions 2.7.3 and 3.2.3 released
Redisson Team is pleased to announce __ULTRA-FAST__ Redisson PRO edition.
Performance measure results available in [Benchmark whitepaper](https://redisson.pro/Redisson%20PRO%20benchmark%20whitepaper.pdf)
Feature - `RMap.getLock(key)` and `RMultimap.getLock(key)` methods added
Improvement - `RedissonSpringCacheManager` constructor with Redisson instance only added
Improvement - `CronSchedule` moved to `org.redisson.api` package
Fixed - RedissonBaseIterator.hasNext() doesn't return false in some cases
Fixed - NoSuchFieldError exception in `redisson-tomcat` modules
Fixed - ConnectionPool size not respected during redirect of cluster request
Fixed - `RSortedSet.removeAsync` and `RSortedSet.addAsync`
Fixed - `RBloomFilter.tryInit` were not validated properly
Fixed - CommandDecoder should print all replay body on error
####19-Dec-2016 - versions 2.7.2 and 3.2.2 released
Feature - `RList`, `RSet` and `RScoredSortedSet` implements `RSortable` interface with SORT command support
Feature - `NodeAsync` interface
Feature - `Node.info`, `Node.getNode` methods added
Fixed - elements distribution of `RBlockingFairQueue` across consumers
Fixed - `factory already defined` error during Redisson initialization under Apache Tomcat
####14-Dec-2016 - versions 2.7.1 and 3.2.1 released
Url format used in config files __has changed__. For example:
"//127.0.0.1:6739" now should be written as "redis://127.0.0.1:6739"
Feature - `RSet.removeRandom` allows to remove several members at once
Fixed - exceptions during shutdown
Fixed - redis url couldn't contain underscore in host name
Fixed - IndexOutOfBoundsException during response decoding
Fixed - command timeout didn't respect during topic subscription
Fixed - possible PublishSubscribe race-condition
Fixed - blocking queue/deque poll method blocks infinitely if delay less than 1 second
####26-Nov-2016 - versions 2.7.0 and 3.2.0 released
Feature - __Spring Session implementation__. More details [here](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#145-spring-session)
Feature - __Tomcat Session Manager implementation__. More details [here](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#144-tomcat-redis-session-manager)
Feature - __RDelayedQueue object added__. More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections/#714-delayed-queue)
Feature - __RBlockingFairQueue object added__. More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections/#713-blocking-fair-queue)
Feature - `RSortedSet.readAll` and `RQueue.readAll` methods added
Fixed - `RMap.getAll` doesn't not preserve the order of elements
Fixed - Wrong nodes parsing in result of cluster info command
Fixed - NullPointerException in CommandDecoder.handleResult
Fixed - Redisson shutdown status should be checked during async command invocation
####07-Nov-2016 - versions 2.6.0 and 3.1.0 released
Feature - __new object added__ `RBinaryStream`. More info about it [here](https://github.com/redisson/redisson/wiki/6.-distributed-objects/#62-binary-stream-holder)
Improvement - limit Payload String on RedisTimeoutException
Improvement - Elasticache master node change detection process optimization
####27-Oct-2016 - versions 2.5.1 and 3.0.1 released
Include all code changes from __2.2.27__ version
Fixed - RMapCache.fastPutIfAbsentAsync doesn't take in account expiration
Fixed - timer field of RedisClient hasn't been initialized properly in some cases
####27-Oct-2016 - version 2.2.27 released
This version fixes old and annonying problem with `ConnectionPool exhusted` error. From this moment connection pool waits for free connection instead of throwing pool exhausted error. This leads to more effective Redis connection utilization.
Improvement - remove `Connection pool exhausted` exception
####17-Oct-2016 - version 3.0.0 released
Fully compatible with JDK 8. Includes all code changes from __2.5.0__ version
Feature - `RFeature` extends `CompletionStage`
####17-Oct-2016 - version 2.5.0 released
This version brings greatly improved version of `RLiveObjectService` and adds cascade handling, cyclic dependency resolving, simplified object creation. Read more in this [article](https://dzone.com/articles/java-distributed-in-memory-data-model-powered-by-r)
Includes all code changes from __2.2.26__ version
Feautre - COUNT and ASC/DESC support for `RGeo` radius methods
Feature - `RGeo` extends `RScoredSortedSet`
Feature - `RCascade` annotation support LiveObjectService
Improvement - `RId` generator should be empty by default
Improvement - support setter/getter with protected visibility scope for LiveObject
Fixed - `RMapCache` doesn't keep entries insertion order during iteration
Fixed - `@RId` is returned/overwritten by similarly named methods (thanks to Rui Gu)
Fixed - typo `getRemoteSerivce` -> `getRemoteService` (thanks to Slava Rosin)
Fixed - `RPermitExpirableSemaphore.availablePermits` doesn't return actual permits account under certain conditions
Fixed - `readAllValues` and `readAllEntrySet` methods of `RLocalCacheMap` return wrong values
Fixed - setter for collection field of LiveObject entity should rewrite collection content
Fixed - `RSetCache` TTL not updated if element already present
Fixed - `RLiveObjectService` swallow exceptions during `merge` or `persist` operation
Fixed - `RLiveObjectService` doesn't support protected constructors
Fixed - object with cyclic dependencies lead to stackoverflow during `RLiveObjectService.detach` process
Fixed - not persisted `REntity` object allowed to store automatically
Fixed - `RLexSortedSet.addAll` doesn't work
Fixed - `RLiveObjectService` can't detach content of List object
Fixed - `RLiveObjectService` doesn't create objects mapped to Redisson objects in runtime during getter accesss
Fixed - `RLiveObjectService` can't recognize id field of object without setter
####17-Oct-2016 - version 2.2.26 released
Fixed - NPE in CommandDecoder
Fixed - PubSub connection re-subscription doesn't work in case when there is only one slave available
####27-Sep-2016 - version 2.4.0 released
Includes all code changes from __2.2.25__ version

@ -1,86 +1,112 @@
Redis based In-Memory Data Grid for Java. Redisson.
====
[![Maven Central](https://img.shields.io/maven-central/v/org.redisson/redisson.svg?style=flat-square)](https://maven-badges.herokuapp.com/maven-central/org.redisson/redisson/)
Based on high-performance async and lock-free Java Redis client and [Netty](http://netty.io) framework.
Redis 2.8+ and JDK 1.6+ compatible.
Redis 2.8+ compatible.
| Stable Release Version | JDK Version compatibility | Release Date |
| ------------- | ------------- | ------------|
| 3.3.0 | 1.8+ | 19.02.2017 |
| 2.8.0 | 1.6, 1.7, 1.8 and Android | 19.02.2017 |
Please read [documentation](https://github.com/mrniko/redisson/wiki) for more details.
Redisson [releases history](https://github.com/mrniko/redisson/blob/master/CHANGELOG.md).
__NOTE__: Both version lines have same features except `CompletionStage` interface supported by 3.x.x line
Please read [documentation](https://github.com/redisson/redisson/wiki) for more details.
Redisson [releases history](https://github.com/redisson/redisson/blob/master/CHANGELOG.md)
Checkout more [code examples](https://github.com/redisson/redisson-examples)
Browse [javadocs](http://www.javadoc.io/doc/org.redisson/redisson/3.2.4)
Licensed under the Apache License 2.0.
Welcome to support chat - [![Join the chat at https://gitter.im/mrniko/redisson](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mrniko/redisson?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Welcome to support chat [![Join the chat at https://gitter.im/mrniko/redisson](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mrniko/redisson?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Features
================================
* [AWS ElastiCache](https://aws.amazon.com/elasticache/) servers mode:
1. automatic new master server discovery
2. automatic new slave servers discovery
* Cluster servers mode:
* Replicated servers mode (also supports [AWS ElastiCache](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/Replication.html) and [Azure Redis Cache](https://azure.microsoft.com/en-us/services/cache/)):
1. automatic master server change discovery
* Cluster servers mode (also supports [AWS ElastiCache Cluster](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/Clusters.html) and [Azure Redis Cache](https://azure.microsoft.com/en-us/services/cache/)):
1. automatic master and slave servers discovery
2. automatic new master server discovery
3. automatic new slave servers discovery
4. automatic slave servers offline/online discovery
5. automatic slots change discovery
2. automatic status and topology update
3. automatic slots change discovery
* Sentinel servers mode:
1. automatic master and slave servers discovery
2. automatic new master server discovery
3. automatic new slave servers discovery
4. automatic slave servers offline/online discovery
5. automatic sentinel servers discovery
1. automatic master, slave and sentinel servers discovery
2. automatic status and topology update
* Master with Slave servers mode
* Single server mode
* Asynchronous interface for each object
* Asynchronous connection pool
* Thread-safe implementation
* Lua scripting
* [Distributed objects](https://github.com/mrniko/redisson/wiki/6.-Distributed-objects)
* [Distributed collections](https://github.com/mrniko/redisson/wiki/7.-Distributed-collections)
* [Distributed locks and synchronizers](https://github.com/mrniko/redisson/wiki/8.-Distributed-locks-and-synchronizers)
* [Distributed services](https://github.com/mrniko/redisson/wiki/9.-distributed-services)
* [Spring cache](https://github.com/mrniko/redisson/wiki/14.-Integration%20with%20frameworks/#141-spring-cache) integration
* [Hibernate](https://github.com/mrniko/redisson/wiki/14.-Integration%20with%20frameworks/#142-hibernate) integration
* [Reactive Streams](https://github.com/mrniko/redisson/wiki/3.-operations-execution#32-reactive-way)
* [Redis pipelining](https://github.com/mrniko/redisson/wiki/10.-additional-features#102-execution-batches-of-commands) (command batches)
* [Distributed objects](https://github.com/redisson/redisson/wiki/6.-Distributed-objects)
Object holder, Binary stream holder, Geospatial holder, BitSet, AtomicLong, AtomicDouble, PublishSubscribe,
Bloom filter, HyperLogLog
* [Distributed collections](https://github.com/redisson/redisson/wiki/7.-Distributed-collections)
Map, Multimap, Set, List, SortedSet, ScoredSortedSet, LexSortedSet, Queue, Deque, Blocking Queue, Bounded Blocking Queue, Blocking Deque, Delayed Queue
* [Distributed locks and synchronizers](https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers)
Lock, FairLock, MultiLock, RedLock, ReadWriteLock, Semaphore, PermitExpirableSemaphore, CountDownLatch
* [Distributed services](https://github.com/redisson/redisson/wiki/9.-distributed-services)
Remote service, Live Object service, Executor service, Scheduler service
* [Spring Cache](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#141-spring-cache) implementation
* [Hibernate Cache](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#142-hibernate-cache) implementation
* [JCache API (JSR-107)](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#143-jcache-api-jsr-107-implementation) implementation
* [Tomcat Session Manager](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks#144-tomcat-redis-session-manager) implementation
* [Spring Session](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#145-spring-session) implementation
* [Reactive Streams](https://github.com/redisson/redisson/wiki/3.-operations-execution#32-reactive-way)
* [Redis pipelining](https://github.com/redisson/redisson/wiki/10.-additional-features#102-execution-batches-of-commands) (command batches)
* Supports Android platform
* Supports auto-reconnect
* Supports failed to send command auto-retry
* Supports OSGi
* Supports many popular codecs ([Jackson JSON](https://github.com/FasterXML/jackson), [Avro](http://avro.apache.org/), [Smile](http://wiki.fasterxml.com/SmileFormatSpec), [CBOR](http://cbor.io/), [MsgPack](http://msgpack.org/), [Kryo](https://github.com/EsotericSoftware/kryo), [FST](https://github.com/RuedigerMoeller/fast-serialization), [LZ4](https://github.com/jpountz/lz4-java), [Snappy](https://github.com/xerial/snappy-java) and JDK Serialization)
* With over 900 unit tests
* With over 1000 unit tests
Projects using Redisson
Who uses Redisson
================================
[Setronica](http://setronica.com/), [Monits](http://monits.com/), [Brookhaven National Laboratory](http://bnl.gov/), [Netflix Dyno client] (https://github.com/Netflix/dyno), [武林Q传](http://www.nbrpg.com/), [Ocous](http://www.ocous.com/), [Invaluable](http://www.invaluable.com/), [Clover](https://www.clover.com/) , [Apache Karaf Decanter](https://karaf.apache.org/projects.html#decanter), [Atmosphere Framework](http://async-io.org/), [BrandsEye](http://brandseye.com), [Datorama](http://datorama.com/), [BrightCloud](http://brightcloud.com/)
[Electronic Arts](http://ea.com), [Baidu](http://baidu.com), [New Relic Synthetics](https://newrelic.com/synthetics), [National Australia Bank](https://www.nab.com.au/), [Brookhaven National Laboratory](http://bnl.gov/), [Singtel](http://singtel.com), [Infor](http://www.infor.com/), [Setronica](http://setronica.com/), [Monits](http://monits.com/), [Netflix Dyno client] (https://github.com/Netflix/dyno), [武林Q传](http://www.nbrpg.com/), [Ocous](http://www.ocous.com/), [Invaluable](http://www.invaluable.com/), [Clover](https://www.clover.com/) , [Apache Karaf Decanter](https://karaf.apache.org/projects.html#decanter), [Atmosphere Framework](http://async-io.org/), [BrandsEye](http://brandseye.com), [Datorama](http://datorama.com/), [BrightCloud](http://brightcloud.com/), [Azar](http://azarlive.com/), [Snapfish](http://snapfish.com), [Crimson Hexagon](http://www.crimsonhexagon.com)
Articles
================================
[Java data structures powered by Redis. Introduction to Redisson (pdf)](http://redisson.org/Redisson.pdf)
[Java data structures powered by Redis. Introduction to Redisson (pdf)](https://redisson.org/Redisson.pdf)
[Redisson PRO vs. Jedis: Which Is Faster?](https://dzone.com/articles/redisson-pro-vs-jedis-which-is-faster)
[A Look at the Java Distributed In-Memory Data Model (Powered by Redis)](https://dzone.com/articles/java-distributed-in-memory-data-model-powered-by-r)
[Distributed tasks Execution and Scheduling in Java, powered by Redis](https://dzone.com/articles/distributed-tasks-execution-and-scheduling-in-java)
[Introducing Redisson Live Objects (Object Hash Mapping)](https://dzone.com/articles/introducing-redisson-live-object-object-hash-mappi)
[Java Remote Method Invocation with Redisson](https://dzone.com/articles/java-remote-method-invocation-with-redisson)
[Java Multimaps With Redis](https://dzone.com/articles/multimaps-with-redis)
[Distributed lock with Redis](https://evuvatech.com/2016/02/05/distributed-lock-with-redis/)
Success stories
================================
[Moving from Hazelcast to Redis](https://engineering.datorama.com/moving-from-hazelcast-to-redis-b90a0769d1cb)
Quick start
===============================
#### Maven
<!-- JDK 1.8+ compatible -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.0</version>
</dependency>
<!-- JDK 1.6+ compatible -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.4.0</version>
<version>2.8.0</version>
</dependency>
#### Gradle
// JDK 1.8+ compatible
compile 'org.redisson:redisson:3.3.0'
compile 'org.redisson:redisson:2.4.0'
// JDK 1.6+ compatible
compile 'org.redisson:redisson:2.8.0'
#### Java
@ -105,8 +131,11 @@ RExecutorService executor = redisson.getExecutorService("myExecutorService");
Downloads
===============================
[Redisson 2.4.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.4.0&e=jar)
[Redisson node 2.4.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.4.0&e=jar)
[Redisson 3.3.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=3.3.0&e=jar),
[Redisson node 3.3.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.3.0&e=jar)
[Redisson 2.8.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.8.0&e=jar),
[Redisson node 2.8.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.8.0&e=jar)
### Supported by

@ -3,7 +3,7 @@
<groupId>org.redisson</groupId>
<artifactId>redisson-parent</artifactId>
<version>2.5.0-SNAPSHOT</version>
<version>2.8.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Redisson</name>
@ -16,11 +16,18 @@
<url>http://redisson.org/</url>
</organization>
<properties>
<maven.test.skip>true</maven.test.skip>
<source.version>1.6</source.version>
<test.source.version>1.8</test.source.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<scm>
<url>scm:git:git@github.com:mrniko/redisson.git</url>
<connection>scm:git:git@github.com:mrniko/redisson.git</connection>
<developerConnection>scm:git:git@github.com:mrniko/redisson.git</developerConnection>
<tag>redisson-parent-0.9.0</tag>
<url>scm:git:git@github.com:redisson/redisson.git</url>
<connection>scm:git:git@github.com:redisson/redisson.git</connection>
<developerConnection>scm:git:git@github.com:redisson/redisson.git</developerConnection>
<tag>HEAD</tag>
</scm>
<prerequisites>
@ -51,6 +58,7 @@
<modules>
<module>redisson</module>
<module>redisson-all</module>
<module>redisson-tomcat</module>
</modules>
<profiles>

@ -4,7 +4,7 @@
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-parent</artifactId>
<version>2.5.0-SNAPSHOT</version>
<version>2.8.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
@ -87,7 +87,7 @@
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<classifier>linux-x86_64</classifier>
<version>4.0.41.Final</version>
<version>4.1.8.Final</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>

@ -0,0 +1,42 @@
Redis based Tomcat Session Manager
===
Implements non-sticky session management backed by Redis.
Supports Tomcat 6.x, 7.x, 8.x
Advantages
===
Current implementation differs from any other Tomcat Session Manager in terms of efficient storage and optimized writes. Each session attribute is written into Redis during each `HttpSession.setAttribute` invocation. While other solutions serialize whole session each time.
Usage
===
1. Add `RedissonSessionManager` into `context.xml`
```xml
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/redisson.conf" />
```
`configPath` - path to Redisson JSON or YAML config. See [configuration wiki page](https://github.com/redisson/redisson/wiki/2.-Configuration) for more details.
2. Copy two jars into `TOMCAT_BASE/lib` directory:
1. __For JDK 1.8+__
[redisson-all-3.3.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.3.0&e=jar)
for Tomcat 6.x
[redisson-tomcat-6-3.3.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-6&v=3.3.0&e=jar)
for Tomcat 7.x
[redisson-tomcat-7-3.3.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-7&v=3.3.0&e=jar)
for Tomcat 8.x
[redisson-tomcat-8-3.3.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=3.3.0&e=jar)
1. __For JDK 1.6+__
[redisson-all-2.8.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.8.0&e=jar)
for Tomcat 6.x
[redisson-tomcat-6-2.8.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-6&v=2.8.0&e=jar)
for Tomcat 7.x
[redisson-tomcat-7-2.8.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-7&v=2.8.0&e=jar)
for Tomcat 8.x
[redisson-tomcat-8-2.8.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=2.8.0&e=jar)

@ -0,0 +1,86 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-parent</artifactId>
<version>2.8.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat</artifactId>
<packaging>pom</packaging>
<name>Redisson/Tomcat</name>
<modules>
<module>redisson-tomcat-6</module>
<module>redisson-tomcat-7</module>
<module>redisson-tomcat-8</module>
</modules>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>${source.version}</source>
<target>${source.version}</target>
<optimize>true</optimize>
<showDeprecations>true</showDeprecations>
</configuration>
<executions>
<execution>
<id>default-testCompile</id>
<phase>process-test-sources</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<fork>true</fork>
<source>${test.source.version}</source>
<target>${test.source.version}</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.4</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,64 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-tomcat</artifactId>
<version>2.8.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat-6</artifactId>
<packaging>jar</packaging>
<name>Redisson/Tomcat-6</name>
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
<version>6.0.48</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>3.0</version>
<configuration>
<basedir>${basedir}</basedir>
<header>${basedir}/../../header.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>false</aggregate>
<includes>
<include>src/main/java/org/redisson/</include>
</includes>
<excludes>
<exclude>target/**</exclude>
</excludes>
<useDefaultExcludes>true</useDefaultExcludes>
<mapping>
<java>JAVADOC_STYLE</java>
</mapping>
<strictCheck>true</strictCheck>
<useDefaultMapping>true</useDefaultMapping>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,187 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.tomcat;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.session.StandardSession;
import org.redisson.api.RMap;
/**
* Redisson Session object for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSession extends StandardSession {
private final RedissonSessionManager redissonManager;
private final Map<String, Object> attrs;
private RMap<String, Object> map;
public RedissonSession(RedissonSessionManager manager) {
super(manager);
this.redissonManager = manager;
try {
Field attr = StandardSession.class.getDeclaredField("attributes");
attrs = (Map<String, Object>) attr.get(this);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static final long serialVersionUID = -2518607181636076487L;
@Override
public void setId(String id, boolean notify) {
super.setId(id, notify);
map = redissonManager.getMap(id);
}
@Override
public void setCreationTime(long time) {
super.setCreationTime(time);
if (map != null) {
Map<String, Object> newMap = new HashMap<String, Object>(3);
newMap.put("session:creationTime", creationTime);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
map.putAll(newMap);
}
}
@Override
public void access() {
super.access();
if (map != null) {
Map<String, Object> newMap = new HashMap<String, Object>(2);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
map.putAll(newMap);
if (getMaxInactiveInterval() >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setMaxInactiveInterval(int interval) {
super.setMaxInactiveInterval(interval);
if (map != null) {
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setValid(boolean isValid) {
super.setValid(isValid);
if (map != null) {
map.fastPut("session:isValid", isValid);
}
}
@Override
public void setNew(boolean isNew) {
super.setNew(isNew);
if (map != null) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void endAccess() {
boolean oldValue = isNew;
super.endAccess();
if (isNew != oldValue) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
if (map != null && value != null) {
map.fastPut(name, value);
}
}
@Override
protected void removeAttributeInternal(String name, boolean notify) {
super.removeAttributeInternal(name, notify);
if (map != null) {
map.fastRemove(name);
}
}
public void save() {
Map<String, Object> newMap = new HashMap<String, Object>();
newMap.put("session:creationTime", creationTime);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
newMap.put("session:maxInactiveInterval", maxInactiveInterval);
newMap.put("session:isValid", isValid);
newMap.put("session:isNew", isNew);
for (Entry<String, Object> entry : attrs.entrySet()) {
newMap.put(entry.getKey(), entry.getValue());
}
map.putAll(newMap);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
public void load() {
Set<Entry<String, Object>> entrySet = map.readAllEntrySet();
for (Entry<String, Object> entry : entrySet) {
if ("session:creationTime".equals(entry.getKey())) {
creationTime = (Long) entry.getValue();
} else if ("session:lastAccessedTime".equals(entry.getKey())) {
lastAccessedTime = (Long) entry.getValue();
} else if ("session:thisAccessedTime".equals(entry.getKey())) {
thisAccessedTime = (Long) entry.getValue();
} else if ("session:maxInactiveInterval".equals(entry.getKey())) {
maxInactiveInterval = (Integer) entry.getValue();
} else if ("session:isValid".equals(entry.getKey())) {
isValid = (Boolean) entry.getValue();
} else if ("session:isNew".equals(entry.getKey())) {
isNew = (Boolean) entry.getValue();
} else {
setAttribute(entry.getKey(), entry.getValue(), false);
}
}
}
}

@ -0,0 +1,179 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.tomcat;
import java.io.File;
import java.io.IOException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.util.LifecycleSupport;
import org.redisson.Redisson;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* Redisson Session Manager for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSessionManager extends ManagerBase implements Lifecycle {
private final Log log = LogFactory.getLog(RedissonSessionManager.class);
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
private RedissonClient redisson;
private String configPath;
public void setConfigPath(String configPath) {
this.configPath = configPath;
}
public String getConfigPath() {
return configPath;
}
@Override
public int getRejectedSessions() {
return 0;
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void setRejectedSessions(int sessions) {
}
@Override
public void unload() throws IOException {
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public Session createSession(String sessionId) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
session.save();
return session;
}
public RMap<String, Object> getMap(String sessionId) {
return redisson.getMap("redisson_tomcat_session:" + sessionId);
}
@Override
public Session findSession(String id) throws IOException {
Session result = super.findSession(id);
if (result == null && id != null) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setId(id);
session.load();
return session;
}
return result;
}
@Override
public Session createEmptySession() {
return new RedissonSession(this);
}
@Override
public void remove(Session session) {
super.remove(session);
getMap(session.getId()).delete();
}
public RedissonClient getRedisson() {
return redisson;
}
@Override
public void start() throws LifecycleException {
Config config = null;
try {
config = Config.fromJSON(new File(configPath));
} catch (IOException e) {
// trying next format
try {
config = Config.fromYAML(new File(configPath));
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
try {
redisson = Redisson.create(config);
} catch (Exception e) {
throw new LifecycleException(e);
}
lifecycle.fireLifecycleEvent(START_EVENT, null);
}
@Override
public void stop() throws LifecycleException {
try {
if (redisson != null) {
redisson.shutdown();
}
} catch (Exception e) {
throw new LifecycleException(e);
}
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
}
}

@ -0,0 +1,153 @@
package org.redisson.tomcat;
import java.io.IOException;
import org.apache.catalina.LifecycleException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.junit.Assert;
import org.junit.Test;
public class RedissonSessionManagerTest {
@Test
public void testSwitchServer() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
Executor.closeIdleConnections();
server.stop();
server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testWriteReadRemove() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1234");
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testRecreate() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
recreate(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testUpdate() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
read(executor, "test", "1");
write(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testInvalidate() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
invalidate(executor);
Executor.closeIdleConnections();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
private void write(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/write?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void read(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/read?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void remove(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/remove?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void invalidate(Executor executor) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/invalidate";
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void recreate(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/recreate?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
}

@ -0,0 +1,94 @@
package org.redisson.tomcat;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1243830648280853203L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
if (req.getPathInfo().equals("/write")) {
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/read")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
Object attr = session.getAttribute(key);
resp.getWriter().print(attr);
} else if (req.getPathInfo().equals("/remove")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
session.removeAttribute(key);
resp.getWriter().print(String.valueOf(session.getAttribute(key)));
} else if (req.getPathInfo().equals("/invalidate")) {
session.invalidate();
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/recreate")) {
session.invalidate();
session = req.getSession();
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
}
}
}

@ -0,0 +1,109 @@
package org.redisson.tomcat;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Embedded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomcatServer {
private Embedded server;
private int port;
private boolean isRunning;
private static final Logger LOG = LoggerFactory.getLogger(TomcatServer.class);
private static final boolean isInfo = LOG.isInfoEnabled();
/**
* Create a new Tomcat embedded server instance. Setup looks like:
* <pre><Server>
* <Service>
* <Connector />
* <Engine&gt
* <Host>
* <Context />
* </Host>
* </Engine>
* </Service>
*</Server></pre>
* <Server> & <Service> will be created automcatically. We need to hook the remaining to an {@link Embedded} instnace
* @param contextPath Context path for the application
* @param port Port number to be used for the embedded Tomcat server
* @param appBase Path to the Application files (for Maven based web apps, in general: <code>/src/main/</code>)
* @param shutdownHook If true, registers a server' shutdown hook with JVM. This is useful to shutdown the server
* in erroneous cases.
* @throws Exception
*/
public TomcatServer(String contextPath, int port, String appBase) {
if(contextPath == null || appBase == null || appBase.length() == 0) {
throw new IllegalArgumentException("Context path or appbase should not be null");
}
if(!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
this.port = port;
server = new Embedded();
server.setName("TomcatEmbeddedServer");
Host localHost = server.createHost("localhost", appBase);
localHost.setAutoDeploy(false);
StandardContext rootContext = (StandardContext) server.createContext(contextPath, "webapp");
rootContext.setDefaultWebXml("web.xml");
localHost.addChild(rootContext);
Engine engine = server.createEngine();
engine.setDefaultHost(localHost.getName());
engine.setName("TomcatEngine");
engine.addChild(localHost);
server.addEngine(engine);
Connector connector = server.createConnector(localHost.getName(), port, false);
server.addConnector(connector);
}
/**
* Start the tomcat embedded server
*/
public void start() throws LifecycleException {
if(isRunning) {
LOG.warn("Tomcat server is already running @ port={}; ignoring the start", port);
return;
}
if(isInfo) LOG.info("Starting the Tomcat server @ port={}", port);
server.setAwait(true);
server.start();
isRunning = true;
}
/**
* Stop the tomcat embedded server
*/
public void stop() throws LifecycleException {
if(!isRunning) {
LOG.warn("Tomcat server is not running @ port={}", port);
return;
}
if(isInfo) LOG.info("Stopping the Tomcat server");
server.stop();
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
}

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml" />
</Context>

@ -0,0 +1,2 @@
singleServerConfig:
address: "redis://127.0.0.1:6379"

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>org.redisson.tomcat.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

@ -0,0 +1,82 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-tomcat</artifactId>
<version>2.8.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat-7</artifactId>
<packaging>jar</packaging>
<name>Redisson/Tomcat-7</name>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>3.0</version>
<configuration>
<basedir>${basedir}</basedir>
<header>${basedir}/../../header.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>false</aggregate>
<includes>
<include>src/main/java/org/redisson/</include>
</includes>
<excludes>
<exclude>target/**</exclude>
</excludes>
<useDefaultExcludes>true</useDefaultExcludes>
<mapping>
<java>JAVADOC_STYLE</java>
</mapping>
<strictCheck>true</strictCheck>
<useDefaultMapping>true</useDefaultMapping>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,186 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.tomcat;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.session.StandardSession;
import org.redisson.api.RMap;
/**
* Redisson Session object for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSession extends StandardSession {
private final RedissonSessionManager redissonManager;
private final Map<String, Object> attrs;
private RMap<String, Object> map;
public RedissonSession(RedissonSessionManager manager) {
super(manager);
this.redissonManager = manager;
try {
Field attr = StandardSession.class.getDeclaredField("attributes");
attrs = (Map<String, Object>) attr.get(this);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static final long serialVersionUID = -2518607181636076487L;
@Override
public void setId(String id, boolean notify) {
super.setId(id, notify);
map = redissonManager.getMap(id);
}
@Override
public void setCreationTime(long time) {
super.setCreationTime(time);
if (map != null) {
Map<String, Object> newMap = new HashMap<String, Object>(3);
newMap.put("session:creationTime", creationTime);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
map.putAll(newMap);
}
}
@Override
public void access() {
super.access();
if (map != null) {
Map<String, Object> newMap = new HashMap<String, Object>(2);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
map.putAll(newMap);
if (getMaxInactiveInterval() >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setMaxInactiveInterval(int interval) {
super.setMaxInactiveInterval(interval);
if (map != null) {
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setValid(boolean isValid) {
super.setValid(isValid);
if (map != null) {
map.fastPut("session:isValid", isValid);
}
}
@Override
public void setNew(boolean isNew) {
super.setNew(isNew);
if (map != null) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void endAccess() {
boolean oldValue = isNew;
super.endAccess();
if (isNew != oldValue) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
if (map != null && value != null) {
map.fastPut(name, value);
}
}
@Override
protected void removeAttributeInternal(String name, boolean notify) {
super.removeAttributeInternal(name, notify);
if (map != null) {
map.fastRemove(name);
}
}
public void save() {
Map<String, Object> newMap = new HashMap<String, Object>();
newMap.put("session:creationTime", creationTime);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
newMap.put("session:maxInactiveInterval", maxInactiveInterval);
newMap.put("session:isValid", isValid);
newMap.put("session:isNew", isNew);
for (Entry<String, Object> entry : attrs.entrySet()) {
newMap.put(entry.getKey(), entry.getValue());
}
map.putAll(newMap);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
public void load() {
Set<Entry<String, Object>> entrySet = map.readAllEntrySet();
for (Entry<String, Object> entry : entrySet) {
if ("session:creationTime".equals(entry.getKey())) {
creationTime = (Long) entry.getValue();
} else if ("session:lastAccessedTime".equals(entry.getKey())) {
lastAccessedTime = (Long) entry.getValue();
} else if ("session:thisAccessedTime".equals(entry.getKey())) {
thisAccessedTime = (Long) entry.getValue();
} else if ("session:maxInactiveInterval".equals(entry.getKey())) {
maxInactiveInterval = (Integer) entry.getValue();
} else if ("session:isValid".equals(entry.getKey())) {
isValid = (Boolean) entry.getValue();
} else if ("session:isNew".equals(entry.getKey())) {
isNew = (Boolean) entry.getValue();
} else {
setAttribute(entry.getKey(), entry.getValue(), false);
}
}
}
}

@ -0,0 +1,185 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.tomcat;
import java.io.File;
import java.io.IOException;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.redisson.Redisson;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* Redisson Session Manager for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSessionManager extends ManagerBase implements Lifecycle {
private final Log log = LogFactory.getLog(RedissonSessionManager.class);
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
private RedissonClient redisson;
private String configPath;
public void setConfigPath(String configPath) {
this.configPath = configPath;
}
public String getConfigPath() {
return configPath;
}
@Override
public String getName() {
return RedissonSessionManager.class.getSimpleName();
}
@Override
public int getRejectedSessions() {
return 0;
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void unload() throws IOException {
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public Session createSession(String sessionId) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
session.save();
return session;
}
public RMap<String, Object> getMap(String sessionId) {
return redisson.getMap("redisson_tomcat_session:" + sessionId);
}
@Override
public Session findSession(String id) throws IOException {
Session result = super.findSession(id);
if (result == null && id != null) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setId(id);
session.load();
return session;
}
return result;
}
@Override
public Session createEmptySession() {
return new RedissonSession(this);
}
@Override
public void remove(Session session) {
super.remove(session);
getMap(session.getId()).delete();
}
public RedissonClient getRedisson() {
return redisson;
}
@Override
protected void startInternal() throws LifecycleException {
super.startInternal();
Config config = null;
try {
config = Config.fromJSON(new File(configPath));
} catch (IOException e) {
// trying next format
try {
config = Config.fromYAML(new File(configPath));
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
try {
redisson = Redisson.create(config);
} catch (Exception e) {
throw new LifecycleException(e);
}
setState(LifecycleState.STARTING);
}
@Override
protected void stopInternal() throws LifecycleException {
super.stopInternal();
setState(LifecycleState.STOPPING);
try {
if (redisson != null) {
redisson.shutdown();
}
} catch (Exception e) {
throw new LifecycleException(e);
}
}
}

@ -0,0 +1,152 @@
package org.redisson.tomcat;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.junit.Assert;
import org.junit.Test;
public class RedissonSessionManagerTest {
@Test
public void testSwitchServer() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
Executor.closeIdleConnections();
server.stop();
server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testWriteReadRemove() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1234");
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testRecreate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
recreate(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testUpdate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
read(executor, "test", "1");
write(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testInvalidate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
invalidate(executor);
Executor.closeIdleConnections();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
private void write(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/write?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void read(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/read?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void remove(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/remove?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void invalidate(Executor executor) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/invalidate";
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void recreate(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/recreate?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
}

@ -0,0 +1,94 @@
package org.redisson.tomcat;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1243830648280853203L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
if (req.getPathInfo().equals("/write")) {
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/read")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
Object attr = session.getAttribute(key);
resp.getWriter().print(attr);
} else if (req.getPathInfo().equals("/remove")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
session.removeAttribute(key);
resp.getWriter().print(String.valueOf(session.getAttribute(key)));
} else if (req.getPathInfo().equals("/invalidate")) {
session.invalidate();
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/recreate")) {
session.invalidate();
session = req.getSession();
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
}
}
}

@ -0,0 +1,65 @@
package org.redisson.tomcat;
import java.net.MalformedURLException;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomcatServer {
private Tomcat tomcat = new Tomcat();
private int port;
private boolean isRunning;
private static final Logger LOG = LoggerFactory.getLogger(TomcatServer.class);
private static final boolean isInfo = LOG.isInfoEnabled();
public TomcatServer(String contextPath, int port, String appBase) throws MalformedURLException, ServletException {
if(contextPath == null || appBase == null || appBase.length() == 0) {
throw new IllegalArgumentException("Context path or appbase should not be null");
}
if(!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
tomcat.setBaseDir("."); // location where temp dir is created
tomcat.setPort(port);
tomcat.getHost().setAppBase(".");
tomcat.addWebapp(contextPath, appBase + "webapp");
}
/**
* Start the tomcat embedded server
*/
public void start() throws LifecycleException {
tomcat.start();
isRunning = true;
}
/**
* Stop the tomcat embedded server
*/
public void stop() throws LifecycleException {
if(!isRunning) {
LOG.warn("Tomcat server is not running @ port={}", port);
return;
}
if(isInfo) LOG.info("Stopping the Tomcat server");
tomcat.stop();
tomcat.destroy();
tomcat.getServer().await();
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
}

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml" />
</Context>

@ -0,0 +1,2 @@
singleServerConfig:
address: "redis://127.0.0.1:6379"

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>org.redisson.tomcat.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

@ -0,0 +1,81 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-tomcat</artifactId>
<version>2.8.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat-8</artifactId>
<packaging>jar</packaging>
<name>Redisson/Tomcat-8</name>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>3.0</version>
<configuration>
<basedir>${basedir}</basedir>
<header>${basedir}/../../header.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>false</aggregate>
<includes>
<include>src/main/java/org/redisson/</include>
</includes>
<excludes>
<exclude>target/**</exclude>
</excludes>
<useDefaultExcludes>true</useDefaultExcludes>
<mapping>
<java>JAVADOC_STYLE</java>
</mapping>
<strictCheck>true</strictCheck>
<useDefaultMapping>true</useDefaultMapping>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,187 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.tomcat;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.session.StandardSession;
import org.redisson.api.RMap;
/**
* Redisson Session object for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSession extends StandardSession {
private final RedissonSessionManager redissonManager;
private final Map<String, Object> attrs;
private RMap<String, Object> map;
public RedissonSession(RedissonSessionManager manager) {
super(manager);
this.redissonManager = manager;
try {
Field attr = StandardSession.class.getDeclaredField("attributes");
attrs = (Map<String, Object>) attr.get(this);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static final long serialVersionUID = -2518607181636076487L;
@Override
public void setId(String id, boolean notify) {
super.setId(id, notify);
map = redissonManager.getMap(id);
}
@Override
public void setCreationTime(long time) {
super.setCreationTime(time);
if (map != null) {
Map<String, Object> newMap = new HashMap<String, Object>(3);
newMap.put("session:creationTime", creationTime);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
map.putAll(newMap);
}
}
@Override
public void access() {
super.access();
if (map != null) {
Map<String, Object> newMap = new HashMap<String, Object>(2);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
map.putAll(newMap);
if (getMaxInactiveInterval() >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setMaxInactiveInterval(int interval) {
super.setMaxInactiveInterval(interval);
if (map != null) {
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setValid(boolean isValid) {
super.setValid(isValid);
if (map != null) {
map.fastPut("session:isValid", isValid);
}
}
@Override
public void setNew(boolean isNew) {
super.setNew(isNew);
if (map != null) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void endAccess() {
boolean oldValue = isNew;
super.endAccess();
if (isNew != oldValue) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
if (map != null && value != null) {
map.fastPut(name, value);
}
}
@Override
protected void removeAttributeInternal(String name, boolean notify) {
super.removeAttributeInternal(name, notify);
if (map != null) {
map.fastRemove(name);
}
}
public void save() {
Map<String, Object> newMap = new HashMap<String, Object>();
newMap.put("session:creationTime", creationTime);
newMap.put("session:lastAccessedTime", lastAccessedTime);
newMap.put("session:thisAccessedTime", thisAccessedTime);
newMap.put("session:maxInactiveInterval", maxInactiveInterval);
newMap.put("session:isValid", isValid);
newMap.put("session:isNew", isNew);
for (Entry<String, Object> entry : attrs.entrySet()) {
newMap.put(entry.getKey(), entry.getValue());
}
map.putAll(newMap);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
public void load() {
Set<Entry<String, Object>> entrySet = map.readAllEntrySet();
for (Entry<String, Object> entry : entrySet) {
if ("session:creationTime".equals(entry.getKey())) {
creationTime = (Long) entry.getValue();
} else if ("session:lastAccessedTime".equals(entry.getKey())) {
lastAccessedTime = (Long) entry.getValue();
} else if ("session:thisAccessedTime".equals(entry.getKey())) {
thisAccessedTime = (Long) entry.getValue();
} else if ("session:maxInactiveInterval".equals(entry.getKey())) {
maxInactiveInterval = (Integer) entry.getValue();
} else if ("session:isValid".equals(entry.getKey())) {
isValid = (Boolean) entry.getValue();
} else if ("session:isNew".equals(entry.getKey())) {
isNew = (Boolean) entry.getValue();
} else {
setAttribute(entry.getKey(), entry.getValue(), false);
}
}
}
}

@ -0,0 +1,185 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.tomcat;
import java.io.File;
import java.io.IOException;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.redisson.Redisson;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* Redisson Session Manager for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSessionManager extends ManagerBase implements Lifecycle {
private final Log log = LogFactory.getLog(RedissonSessionManager.class);
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
private RedissonClient redisson;
private String configPath;
public void setConfigPath(String configPath) {
this.configPath = configPath;
}
public String getConfigPath() {
return configPath;
}
@Override
public String getName() {
return RedissonSessionManager.class.getSimpleName();
}
@Override
public int getRejectedSessions() {
return 0;
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void unload() throws IOException {
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public Session createSession(String sessionId) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
session.save();
return session;
}
public RMap<String, Object> getMap(String sessionId) {
return redisson.getMap("redisson_tomcat_session:" + sessionId);
}
@Override
public Session findSession(String id) throws IOException {
Session result = super.findSession(id);
if (result == null && id != null) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setId(id);
session.load();
return session;
}
return result;
}
@Override
public Session createEmptySession() {
return new RedissonSession(this);
}
@Override
public void remove(Session session) {
super.remove(session);
getMap(session.getId()).delete();
}
public RedissonClient getRedisson() {
return redisson;
}
@Override
protected void startInternal() throws LifecycleException {
super.startInternal();
Config config = null;
try {
config = Config.fromJSON(new File(configPath));
} catch (IOException e) {
// trying next format
try {
config = Config.fromYAML(new File(configPath));
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
try {
redisson = Redisson.create(config);
} catch (Exception e) {
throw new LifecycleException(e);
}
setState(LifecycleState.STARTING);
}
@Override
protected void stopInternal() throws LifecycleException {
super.stopInternal();
setState(LifecycleState.STOPPING);
try {
if (redisson != null) {
redisson.shutdown();
}
} catch (Exception e) {
throw new LifecycleException(e);
}
}
}

@ -0,0 +1,152 @@
package org.redisson.tomcat;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.junit.Assert;
import org.junit.Test;
public class RedissonSessionManagerTest {
@Test
public void testSwitchServer() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
Executor.closeIdleConnections();
server.stop();
server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testWriteReadRemove() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1234");
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testRecreate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
recreate(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testUpdate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
read(executor, "test", "1");
write(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testInvalidate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
invalidate(executor);
Executor.closeIdleConnections();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
private void write(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/write?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void read(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/read?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void remove(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/remove?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void invalidate(Executor executor) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/invalidate";
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void recreate(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/recreate?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
}

@ -0,0 +1,94 @@
package org.redisson.tomcat;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1243830648280853203L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
if (req.getPathInfo().equals("/write")) {
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/read")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
Object attr = session.getAttribute(key);
resp.getWriter().print(attr);
} else if (req.getPathInfo().equals("/remove")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
session.removeAttribute(key);
resp.getWriter().print(String.valueOf(session.getAttribute(key)));
} else if (req.getPathInfo().equals("/invalidate")) {
session.invalidate();
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/recreate")) {
session.invalidate();
session = req.getSession();
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
}
}
}

@ -0,0 +1,65 @@
package org.redisson.tomcat;
import java.net.MalformedURLException;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomcatServer {
private Tomcat tomcat = new Tomcat();
private int port;
private boolean isRunning;
private static final Logger LOG = LoggerFactory.getLogger(TomcatServer.class);
private static final boolean isInfo = LOG.isInfoEnabled();
public TomcatServer(String contextPath, int port, String appBase) throws MalformedURLException, ServletException {
if(contextPath == null || appBase == null || appBase.length() == 0) {
throw new IllegalArgumentException("Context path or appbase should not be null");
}
if(!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
tomcat.setBaseDir("."); // location where temp dir is created
tomcat.setPort(port);
tomcat.getHost().setAppBase(".");
tomcat.addWebapp(contextPath, appBase + "webapp");
}
/**
* Start the tomcat embedded server
*/
public void start() throws LifecycleException {
tomcat.start();
isRunning = true;
}
/**
* Stop the tomcat embedded server
*/
public void stop() throws LifecycleException {
if(!isRunning) {
LOG.warn("Tomcat server is not running @ port={}", port);
return;
}
if(isInfo) LOG.info("Stopping the Tomcat server");
tomcat.stop();
tomcat.destroy();
tomcat.getServer().await();
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
}

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml" />
</Context>

@ -0,0 +1,2 @@
singleServerConfig:
address: "redis://127.0.0.1:6379"

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>org.redisson.tomcat.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

@ -1,13 +0,0 @@
Copyright 2016 Nikita Koksharov
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.

@ -4,7 +4,7 @@
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-parent</artifactId>
<version>2.5.0-SNAPSHOT</version>
<version>2.8.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
@ -21,13 +21,6 @@
<url>http://redisson.org/</url>
</organization>
<properties>
<maven.test.skip>true</maven.test.skip>
<source.version>1.6</source.version>
<test.source.version>1.8</test.source.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<profiles>
<profile>
<id>unit-test</id>
@ -41,35 +34,40 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>4.0.41.Final</version>
<version>4.1.8.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>4.0.41.Final</version>
<version>4.1.8.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>4.0.41.Final</version>
<version>4.1.8.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>4.0.41.Final</version>
<version>4.1.8.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>4.0.41.Final</version>
<version>4.1.8.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>4.0.41.Final</version>
<version>4.1.8.Final</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-stream</artifactId>
@ -107,6 +105,43 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>7.0.73</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>7.0.73</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>7.0.73</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>7.0.73</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>[3.1,)</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.jpountz.lz4</groupId>
<artifactId>lz4</artifactId>
@ -203,7 +238,12 @@
<version>[3.1,)</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.2.2.RELEASE</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
@ -359,7 +399,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>3.0.1</version>
<version>3.2.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
@ -375,7 +415,7 @@
<version>2.11</version>
<configuration>
<basedir>${basedir}</basedir>
<header>${basedir}/header.txt</header>
<header>${basedir}/../header.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>false</aggregate>

@ -20,7 +20,9 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RBlockingQueue;
@ -184,7 +186,7 @@ public abstract class BaseRemoteService {
final RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName,
getCodec());
final RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args,
final RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), getMethodSignatures(method), args,
optionsCopy, System.currentTimeMillis());
final RemotePromise<Object> result = new RemotePromise<Object>(commandExecutor.getConnectionManager().newPromise()) {
@ -243,7 +245,7 @@ public abstract class BaseRemoteService {
String canceRequestName = getCancelRequestQueueName(remoteInterface, requestId);
cancelExecution(optionsCopy, responseName, request, mayInterruptIfRunning, canceRequestName, this);
awaitUninterruptibly();
awaitUninterruptibly(60, TimeUnit.SECONDS);
return isCancelled();
}
};
@ -399,7 +401,7 @@ public abstract class BaseRemoteService {
String requestQueueName = getRequestQueueName(remoteInterface);
RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName,
getCodec());
RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args, optionsCopy,
RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), getMethodSignatures(method), args, optionsCopy,
System.currentTimeMillis());
requestQueue.add(request);
@ -537,4 +539,11 @@ public abstract class BaseRemoteService {
}
}
protected List<String> getMethodSignatures(Method method) {
List<String> list = new ArrayList();
for (Class<?> t : method.getParameterTypes()) {
list.add(t.getName());
}
return list;
}
}

@ -1,246 +0,0 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.Deque;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;
/**
* Eviction scheduler for RMapCache object.
* Deletes expired entries in time interval between 5 seconds to 2 hours.
* It analyzes deleted amount of expired keys
* and 'tune' next execution delay depending on it.
*
* @author Nikita Koksharov
*
*/
public class EvictionScheduler {
private static final Logger log = LoggerFactory.getLogger(EvictionScheduler.class);
public class RedissonCacheTask implements Runnable {
final String name;
final String timeoutSetName;
final String maxIdleSetName;
final boolean multimap;
final Deque<Integer> sizeHistory = new LinkedList<Integer>();
int delay = 10;
final int minDelay = 1;
final int maxDelay = 2*60*60;
final int keysLimit = 300;
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() {
executor.getConnectionManager().getGroup().schedule(this, delay, TimeUnit.SECONDS);
}
@Override
public void run() {
RFuture<Integer> future = cleanupExpiredEntires(name, timeoutSetName, maxIdleSetName, keysLimit, multimap);
future.addListener(new FutureListener<Integer>() {
@Override
public void operationComplete(Future<Integer> future) throws Exception {
if (!future.isSuccess()) {
schedule();
return;
}
Integer size = future.getNow();
if (sizeHistory.size() == 2) {
if (sizeHistory.peekFirst() > sizeHistory.peekLast()
&& sizeHistory.peekLast() > size) {
delay = Math.min(maxDelay, (int)(delay*1.5));
}
// if (sizeHistory.peekFirst() < sizeHistory.peekLast()
// && sizeHistory.peekLast() < size) {
// prevDelay = Math.max(minDelay, prevDelay/2);
// }
if (sizeHistory.peekFirst().intValue() == sizeHistory.peekLast()
&& sizeHistory.peekLast().intValue() == size) {
if (size == keysLimit) {
delay = Math.max(minDelay, delay/4);
}
if (size == 0) {
delay = Math.min(maxDelay, (int)(delay*1.5));
}
}
sizeHistory.pollFirst();
}
sizeHistory.add(size);
schedule();
}
});
}
}
private final ConcurrentMap<String, RedissonCacheTask> tasks = PlatformDependent.newConcurrentHashMap();
private final CommandAsyncExecutor executor;
private final ConcurrentMap<String, Long> lastExpiredTime = PlatformDependent.newConcurrentHashMap();
private final int expireTaskExecutionDelay = 1000;
private final int valuesAmountToClean = 500;
public EvictionScheduler(CommandAsyncExecutor executor) {
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, false);
RedissonCacheTask prevTask = tasks.putIfAbsent(name, task);
if (prevTask == null) {
task.schedule();
}
}
public void schedule(String name) {
schedule(name, null);
}
public void schedule(String name, String timeoutSetName, String maxIdleSetName) {
RedissonCacheTask task = new RedissonCacheTask(name, timeoutSetName, maxIdleSetName, false);
RedissonCacheTask prevTask = tasks.putIfAbsent(name, task);
if (prevTask == null) {
task.schedule();
}
}
public void runCleanTask(final String name, String timeoutSetName, long currentDate) {
final Long lastExpired = lastExpiredTime.get(name);
long now = System.currentTimeMillis();
if (lastExpired == null) {
if (lastExpiredTime.putIfAbsent(name, now) != null) {
return;
}
} else if (lastExpired + expireTaskExecutionDelay >= now) {
if (!lastExpiredTime.replace(name, lastExpired, now)) {
return;
}
} else {
return;
}
RFuture<Integer> future = cleanupExpiredEntires(name, timeoutSetName, null, valuesAmountToClean, false);
future.addListener(new FutureListener<Integer>() {
@Override
public void operationComplete(Future<Integer> future) throws Exception {
executor.getConnectionManager().getGroup().schedule(new Runnable() {
@Override
public void run() {
lastExpiredTime.remove(name, lastExpired);
}
}, expireTaskExecutionDelay*3, TimeUnit.SECONDS);
if (!future.isSuccess()) {
log.warn("Can't execute clean task for expired values. RSetCache name: " + name, future.cause());
return;
}
}
});
}
private RFuture<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]); "
+ "if #expiredKeys1 > 0 then "
+ "redis.call('zrem', KEYS[3], unpack(expiredKeys1)); "
+ "redis.call('zrem', KEYS[2], unpack(expiredKeys1)); "
+ "redis.call('hdel', KEYS[1], unpack(expiredKeys1)); "
+ "end; "
+ "local expiredKeys2 = redis.call('zrangebyscore', KEYS[3], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "if #expiredKeys2 > 0 then "
+ "redis.call('zrem', KEYS[3], unpack(expiredKeys2)); "
+ "redis.call('zrem', KEYS[2], unpack(expiredKeys2)); "
+ "redis.call('hdel', KEYS[1], unpack(expiredKeys2)); "
+ "end; "
+ "return #expiredKeys1 + #expiredKeys2;",
Arrays.<Object>asList(name, timeoutSetName, maxIdleSetName), System.currentTimeMillis(), keysLimit);
}
if (timeoutSetName == null) {
return executor.writeAsync(name, LongCodec.INSTANCE, RedisCommands.ZREMRANGEBYSCORE, name, 0, System.currentTimeMillis());
}
return executor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"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)); "
+ "redis.call('hdel', KEYS[1], unpack(expiredKeys)); "
+ "end; "
+ "return #expiredKeys;",
Arrays.<Object>asList(name, timeoutSetName), System.currentTimeMillis(), keysLimit);
}
}

@ -65,6 +65,10 @@ public class PubSubMessageListener<V> implements RedisPubSubListener<Object> {
return true;
}
public MessageListener<V> getListener() {
return listener;
}
@Override
public void onMessage(String channel, Object message) {
// could be subscribed to multiple channels

@ -65,6 +65,10 @@ public class PubSubPatternMessageListener<V> implements RedisPubSubListener<V> {
return true;
}
public PatternMessageListener<V> getListener() {
return listener;
}
@Override
public void onMessage(String channel, V message) {
}

@ -0,0 +1,52 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.concurrent.ConcurrentMap;
import io.netty.util.internal.PlatformDependent;
/**
*
* @author Nikita Koksharov
*
*/
public class QueueTransferService {
private final ConcurrentMap<String, QueueTransferTask> tasks = PlatformDependent.newConcurrentHashMap();
public synchronized void schedule(String name, QueueTransferTask task) {
QueueTransferTask oldTask = tasks.putIfAbsent(name, task);
if (oldTask == null) {
task.start();
} else {
oldTask.incUsage();
}
}
public synchronized void remove(String name) {
QueueTransferTask task = tasks.get(name);
if (task != null) {
if (task.decUsage() == 0) {
tasks.remove(name, task);
task.stop();
}
}
}
}

@ -0,0 +1,142 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.redisson.api.RFuture;
import org.redisson.api.RTopic;
import org.redisson.api.listener.BaseStatusListener;
import org.redisson.api.listener.MessageListener;
import org.redisson.connection.ConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
*/
public abstract class QueueTransferTask {
private static final Logger log = LoggerFactory.getLogger(QueueTransferTask.class);
private int usage = 1;
private final AtomicReference<Timeout> timeoutReference = new AtomicReference<Timeout>();
private final ConnectionManager connectionManager;
public QueueTransferTask(ConnectionManager connectionManager) {
super();
this.connectionManager = connectionManager;
}
public void incUsage() {
usage++;
}
public int decUsage() {
usage--;
return usage;
}
private int messageListenerId;
private int statusListenerId;
public void start() {
RTopic<Long> schedulerTopic = getTopic();
statusListenerId = schedulerTopic.addListener(new BaseStatusListener() {
@Override
public void onSubscribe(String channel) {
pushTask();
}
});
messageListenerId = schedulerTopic.addListener(new MessageListener<Long>() {
@Override
public void onMessage(String channel, Long startTime) {
scheduleTask(startTime);
}
});
}
public void stop() {
RTopic<Long> schedulerTopic = getTopic();
schedulerTopic.removeListener(messageListenerId);
schedulerTopic.removeListener(statusListenerId);
}
private void scheduleTask(final Long startTime) {
if (startTime == null) {
return;
}
Timeout oldTimeout = timeoutReference.get();
if (oldTimeout != null) {
oldTimeout.cancel();
timeoutReference.compareAndSet(oldTimeout, null);
}
long delay = startTime - System.currentTimeMillis();
if (delay > 10) {
Timeout timeout = connectionManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
pushTask();
}
}, delay, TimeUnit.MILLISECONDS);
timeoutReference.set(timeout);
} else {
pushTask();
}
}
protected abstract RTopic<Long> getTopic();
protected abstract RFuture<Long> pushTaskAsync();
private void pushTask() {
RFuture<Long> startTimeFuture = pushTaskAsync();
addListener(startTimeFuture);
}
private void addListener(RFuture<Long> startTimeFuture) {
startTimeFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(io.netty.util.concurrent.Future<Long> future) throws Exception {
if (!future.isSuccess()) {
if (future.cause() instanceof RedissonShutdownException) {
return;
}
log.error(future.cause().getMessage(), future.cause());
scheduleTask(System.currentTimeMillis() + 5 * 1000L);
return;
}
if (future.getNow() != null) {
scheduleTask(future.getNow());
}
}
});
}
}

@ -15,6 +15,7 @@
*/
package org.redisson;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -33,10 +34,17 @@ import org.redisson.connection.ConnectionListener;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.RedisClientEntry;
import org.redisson.misc.RPromise;
import org.redisson.misc.URLBuilder;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
* @param <N> node type
*/
public class RedisNodes<N extends Node> implements NodesGroup<N> {
final ConnectionManager connectionManager;
@ -45,6 +53,18 @@ public class RedisNodes<N extends Node> implements NodesGroup<N> {
this.connectionManager = connectionManager;
}
@Override
public N getNode(String address) {
Collection<N> clients = (Collection<N>) connectionManager.getClients();
InetSocketAddress addr = URLBuilder.toAddress(address);
for (N node : clients) {
if (node.getAddr().equals(addr)) {
return node;
}
}
return null;
}
@Override
public Collection<N> getNodes(NodeType type) {
Collection<N> clients = (Collection<N>) connectionManager.getClients();

@ -26,14 +26,17 @@ import org.redisson.api.NodesGroup;
import org.redisson.api.RAtomicDouble;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBatch;
import org.redisson.api.RBinaryStream;
import org.redisson.api.RBitSet;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RBlockingFairQueue;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RBoundedBlockingQueue;
import org.redisson.api.RBucket;
import org.redisson.api.RBuckets;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RDeque;
import org.redisson.api.RGeo;
import org.redisson.api.RHyperLogLog;
@ -48,6 +51,9 @@ import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.api.RPatternTopic;
import org.redisson.api.RPermitExpirableSemaphore;
import org.redisson.api.RPriorityDeque;
import org.redisson.api.RPriorityQueue;
import org.redisson.api.RQueue;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RRemoteService;
@ -55,7 +61,6 @@ import org.redisson.api.RScheduledExecutorService;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RScript;
import org.redisson.api.RSemaphore;
import org.redisson.api.RPermitExpirableSemaphore;
import org.redisson.api.RSet;
import org.redisson.api.RSetCache;
import org.redisson.api.RSetMultimap;
@ -65,17 +70,17 @@ import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonReactiveClient;
import org.redisson.client.codec.Codec;
import org.redisson.codec.CodecProvider;
import org.redisson.command.CommandExecutor;
import org.redisson.command.CommandSyncService;
import org.redisson.config.Config;
import org.redisson.config.ConfigSupport;
import org.redisson.connection.ConnectionManager;
import org.redisson.codec.CodecProvider;
import org.redisson.eviction.EvictionScheduler;
import org.redisson.liveobject.provider.ResolverProvider;
import org.redisson.misc.RedissonObjectFactory;
import org.redisson.pubsub.SemaphorePubSub;
import io.netty.util.internal.PlatformDependent;
import org.redisson.misc.RedissonObjectFactory;
/**
* Main infrastructure class allows to get access
@ -91,8 +96,8 @@ public class Redisson implements RedissonClient {
RedissonReference.warmUp();
}
protected final QueueTransferService queueTransferService = new QueueTransferService();
protected final EvictionScheduler evictionScheduler;
protected final CommandExecutor commandExecutor;
protected final ConnectionManager connectionManager;
protected final ConcurrentMap<Class<?>, Class<?>> liveObjectClassCache = PlatformDependent.newConcurrentHashMap();
@ -108,13 +113,20 @@ public class Redisson implements RedissonClient {
Config configCopy = new Config(config);
connectionManager = ConfigSupport.createConnectionManager(configCopy);
commandExecutor = new CommandSyncService(connectionManager);
evictionScheduler = new EvictionScheduler(commandExecutor);
evictionScheduler = new EvictionScheduler(connectionManager.getCommandExecutor());
codecProvider = config.getCodecProvider();
resolverProvider = config.getResolverProvider();
}
ConnectionManager getConnectionManager() {
public EvictionScheduler getEvictionScheduler() {
return evictionScheduler;
}
public CommandExecutor getCommandExecutor() {
return connectionManager.getCommandExecutor();
}
public ConnectionManager getConnectionManager() {
return connectionManager;
}
@ -174,339 +186,362 @@ public class Redisson implements RedissonClient {
return react;
}
@Override
public RBinaryStream getBinaryStream(String name) {
return new RedissonBinaryStream(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RGeo<V> getGeo(String name) {
return new RedissonGeo<V>(commandExecutor, name);
return new RedissonGeo<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RGeo<V> getGeo(String name, Codec codec) {
return new RedissonGeo<V>(codec, commandExecutor, name);
return new RedissonGeo<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBucket<V> getBucket(String name) {
return new RedissonBucket<V>(commandExecutor, name);
return new RedissonBucket<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBucket<V> getBucket(String name, Codec codec) {
return new RedissonBucket<V>(codec, commandExecutor, name);
return new RedissonBucket<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public RBuckets getBuckets() {
return new RedissonBuckets(this, commandExecutor);
return new RedissonBuckets(this, connectionManager.getCommandExecutor());
}
@Override
public RBuckets getBuckets(Codec codec) {
return new RedissonBuckets(this, codec, commandExecutor);
return new RedissonBuckets(this, codec, connectionManager.getCommandExecutor());
}
@Override
public <V> RHyperLogLog<V> getHyperLogLog(String name) {
return new RedissonHyperLogLog<V>(commandExecutor, name);
return new RedissonHyperLogLog<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RHyperLogLog<V> getHyperLogLog(String name, Codec codec) {
return new RedissonHyperLogLog<V>(codec, commandExecutor, name);
return new RedissonHyperLogLog<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RList<V> getList(String name) {
return new RedissonList<V>(commandExecutor, name);
return new RedissonList<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RList<V> getList(String name, Codec codec) {
return new RedissonList<V>(codec, commandExecutor, name);
return new RedissonList<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RListMultimap<K, V> getListMultimap(String name) {
return new RedissonListMultimap<K, V>(commandExecutor, name);
return new RedissonListMultimap<K, V>(id, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RListMultimap<K, V> getListMultimap(String name, Codec codec) {
return new RedissonListMultimap<K, V>(codec, commandExecutor, name);
return new RedissonListMultimap<K, V>(id, codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RLocalCachedMap<K, V> getLocalCachedMap(String name, LocalCachedMapOptions options) {
return new RedissonLocalCachedMap<K, V>(this, commandExecutor, name, options);
return new RedissonLocalCachedMap<K, V>(id, connectionManager.getCommandExecutor(), name, options);
}
@Override
public <K, V> RLocalCachedMap<K, V> getLocalCachedMap(String name, Codec codec, LocalCachedMapOptions options) {
return new RedissonLocalCachedMap<K, V>(this, codec, commandExecutor, name, options);
return new RedissonLocalCachedMap<K, V>(id, codec, connectionManager.getCommandExecutor(), name, options);
}
@Override
public <K, V> RMap<K, V> getMap(String name) {
return new RedissonMap<K, V>(commandExecutor, name);
return new RedissonMap<K, V>(id, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RSetMultimap<K, V> getSetMultimap(String name) {
return new RedissonSetMultimap<K, V>(commandExecutor, name);
return new RedissonSetMultimap<K, V>(id, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name) {
return new RedissonSetMultimapCache<K, V>(evictionScheduler, commandExecutor, name);
return new RedissonSetMultimapCache<K, V>(id, evictionScheduler, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name, Codec codec) {
return new RedissonSetMultimapCache<K, V>(evictionScheduler, codec, commandExecutor, name);
return new RedissonSetMultimapCache<K, V>(id, evictionScheduler, codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RListMultimapCache<K, V> getListMultimapCache(String name) {
return new RedissonListMultimapCache<K, V>(evictionScheduler, commandExecutor, name);
return new RedissonListMultimapCache<K, V>(id, evictionScheduler, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RListMultimapCache<K, V> getListMultimapCache(String name, Codec codec) {
return new RedissonListMultimapCache<K, V>(evictionScheduler, codec, commandExecutor, name);
return new RedissonListMultimapCache<K, V>(id, evictionScheduler, codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RSetMultimap<K, V> getSetMultimap(String name, Codec codec) {
return new RedissonSetMultimap<K, V>(codec, commandExecutor, name);
return new RedissonSetMultimap<K, V>(id, codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RSetCache<V> getSetCache(String name) {
return new RedissonSetCache<V>(evictionScheduler, commandExecutor, name);
return new RedissonSetCache<V>(evictionScheduler, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RSetCache<V> getSetCache(String name, Codec codec) {
return new RedissonSetCache<V>(codec, evictionScheduler, commandExecutor, name);
return new RedissonSetCache<V>(codec, evictionScheduler, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RMapCache<K, V> getMapCache(String name) {
return new RedissonMapCache<K, V>(evictionScheduler, commandExecutor, name);
return new RedissonMapCache<K, V>(id, evictionScheduler, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RMapCache<K, V> getMapCache(String name, Codec codec) {
return new RedissonMapCache<K, V>(codec, evictionScheduler, commandExecutor, name);
return new RedissonMapCache<K, V>(id, codec, evictionScheduler, connectionManager.getCommandExecutor(), name);
}
@Override
public <K, V> RMap<K, V> getMap(String name, Codec codec) {
return new RedissonMap<K, V>(codec, commandExecutor, name);
return new RedissonMap<K, V>(id, codec, connectionManager.getCommandExecutor(), name);
}
@Override
public RLock getLock(String name) {
return new RedissonLock(commandExecutor, name, id);
return new RedissonLock(connectionManager.getCommandExecutor(), name, id);
}
@Override
public RLock getFairLock(String name) {
return new RedissonFairLock(commandExecutor, name, id);
return new RedissonFairLock(connectionManager.getCommandExecutor(), name, id);
}
@Override
public RReadWriteLock getReadWriteLock(String name) {
return new RedissonReadWriteLock(commandExecutor, name, id);
return new RedissonReadWriteLock(connectionManager.getCommandExecutor(), name, id);
}
@Override
public <V> RSet<V> getSet(String name) {
return new RedissonSet<V>(commandExecutor, name);
return new RedissonSet<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RSet<V> getSet(String name, Codec codec) {
return new RedissonSet<V>(codec, commandExecutor, name);
return new RedissonSet<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public RScript getScript() {
return new RedissonScript(commandExecutor);
return new RedissonScript(connectionManager.getCommandExecutor());
}
@Override
public RScheduledExecutorService getExecutorService(String name) {
return new RedissonExecutorService(connectionManager.getCodec(), commandExecutor, this, name);
return new RedissonExecutorService(connectionManager.getCodec(), connectionManager.getCommandExecutor(), this, name);
}
@Override
public RScheduledExecutorService getExecutorService(Codec codec, String name) {
return new RedissonExecutorService(codec, commandExecutor, this, name);
return new RedissonExecutorService(codec, connectionManager.getCommandExecutor(), this, name);
}
@Override
public RRemoteService getRemoteService() {
return new RedissonRemoteService(this, commandExecutor);
return new RedissonRemoteService(this, connectionManager.getCommandExecutor());
}
@Override
public RRemoteService getRemoteService(String name) {
return new RedissonRemoteService(this, name, commandExecutor);
return new RedissonRemoteService(this, name, connectionManager.getCommandExecutor());
}
@Override
public RRemoteService getRemoteService(Codec codec) {
return new RedissonRemoteService(codec, this, commandExecutor);
return new RedissonRemoteService(codec, this, connectionManager.getCommandExecutor());
}
@Override
public RRemoteService getRemoteService(String name, Codec codec) {
return new RedissonRemoteService(codec, this, name, commandExecutor);
return new RedissonRemoteService(codec, this, name, connectionManager.getCommandExecutor());
}
@Override
public <V> RSortedSet<V> getSortedSet(String name) {
return new RedissonSortedSet<V>(commandExecutor, name, this);
return new RedissonSortedSet<V>(connectionManager.getCommandExecutor(), name, this);
}
@Override
public <V> RSortedSet<V> getSortedSet(String name, Codec codec) {
return new RedissonSortedSet<V>(codec, commandExecutor, name, this);
return new RedissonSortedSet<V>(codec, connectionManager.getCommandExecutor(), name, this);
}
@Override
public <V> RScoredSortedSet<V> getScoredSortedSet(String name) {
return new RedissonScoredSortedSet<V>(commandExecutor, name);
return new RedissonScoredSortedSet<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RScoredSortedSet<V> getScoredSortedSet(String name, Codec codec) {
return new RedissonScoredSortedSet<V>(codec, commandExecutor, name);
return new RedissonScoredSortedSet<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public RLexSortedSet getLexSortedSet(String name) {
return new RedissonLexSortedSet(commandExecutor, name);
return new RedissonLexSortedSet(connectionManager.getCommandExecutor(), name);
}
@Override
public <M> RTopic<M> getTopic(String name) {
return new RedissonTopic<M>(commandExecutor, name);
return new RedissonTopic<M>(connectionManager.getCommandExecutor(), name);
}
@Override
public <M> RTopic<M> getTopic(String name, Codec codec) {
return new RedissonTopic<M>(codec, commandExecutor, name);
return new RedissonTopic<M>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <M> RPatternTopic<M> getPatternTopic(String pattern) {
return new RedissonPatternTopic<M>(commandExecutor, pattern);
return new RedissonPatternTopic<M>(connectionManager.getCommandExecutor(), pattern);
}
@Override
public <M> RPatternTopic<M> getPatternTopic(String pattern, Codec codec) {
return new RedissonPatternTopic<M>(codec, commandExecutor, pattern);
return new RedissonPatternTopic<M>(codec, connectionManager.getCommandExecutor(), pattern);
}
@Override
public <V> RBlockingFairQueue<V> getBlockingFairQueue(String name) {
return new RedissonBlockingFairQueue<V>(connectionManager.getCommandExecutor(), name, semaphorePubSub, id);
}
@Override
public <V> RBlockingFairQueue<V> getBlockingFairQueue(String name, Codec codec) {
return new RedissonBlockingFairQueue<V>(codec, connectionManager.getCommandExecutor(), name, semaphorePubSub, id);
}
@Override
public <V> RDelayedQueue<V> getDelayedQueue(RQueue<V> destinationQueue) {
if (destinationQueue == null) {
throw new NullPointerException();
}
return new RedissonDelayedQueue<V>(queueTransferService, destinationQueue.getCodec(), connectionManager.getCommandExecutor(), destinationQueue.getName());
}
@Override
public <V> RQueue<V> getQueue(String name) {
return new RedissonQueue<V>(commandExecutor, name);
return new RedissonQueue<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RQueue<V> getQueue(String name, Codec codec) {
return new RedissonQueue<V>(codec, commandExecutor, name);
return new RedissonQueue<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBlockingQueue<V> getBlockingQueue(String name) {
return new RedissonBlockingQueue<V>(commandExecutor, name);
return new RedissonBlockingQueue<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBlockingQueue<V> getBlockingQueue(String name, Codec codec) {
return new RedissonBlockingQueue<V>(codec, commandExecutor, name);
return new RedissonBlockingQueue<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBoundedBlockingQueue<V> getBoundedBlockingQueue(String name) {
return new RedissonBoundedBlockingQueue<V>(semaphorePubSub, commandExecutor, name);
return new RedissonBoundedBlockingQueue<V>(semaphorePubSub, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBoundedBlockingQueue<V> getBoundedBlockingQueue(String name, Codec codec) {
return new RedissonBoundedBlockingQueue<V>(semaphorePubSub, codec, commandExecutor, name);
return new RedissonBoundedBlockingQueue<V>(semaphorePubSub, codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RDeque<V> getDeque(String name) {
return new RedissonDeque<V>(commandExecutor, name);
return new RedissonDeque<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RDeque<V> getDeque(String name, Codec codec) {
return new RedissonDeque<V>(codec, commandExecutor, name);
return new RedissonDeque<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBlockingDeque<V> getBlockingDeque(String name) {
return new RedissonBlockingDeque<V>(commandExecutor, name);
return new RedissonBlockingDeque<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBlockingDeque<V> getBlockingDeque(String name, Codec codec) {
return new RedissonBlockingDeque<V>(codec, commandExecutor, name);
return new RedissonBlockingDeque<V>(codec, connectionManager.getCommandExecutor(), name);
};
@Override
public RAtomicLong getAtomicLong(String name) {
return new RedissonAtomicLong(commandExecutor, name);
return new RedissonAtomicLong(connectionManager.getCommandExecutor(), name);
}
@Override
public RAtomicDouble getAtomicDouble(String name) {
return new RedissonAtomicDouble(commandExecutor, name);
return new RedissonAtomicDouble(connectionManager.getCommandExecutor(), name);
}
@Override
public RCountDownLatch getCountDownLatch(String name) {
return new RedissonCountDownLatch(commandExecutor, name, id);
return new RedissonCountDownLatch(connectionManager.getCommandExecutor(), name, id);
}
@Override
public RBitSet getBitSet(String name) {
return new RedissonBitSet(commandExecutor, name);
return new RedissonBitSet(connectionManager.getCommandExecutor(), name);
}
@Override
public RSemaphore getSemaphore(String name) {
return new RedissonSemaphore(commandExecutor, name, semaphorePubSub);
return new RedissonSemaphore(connectionManager.getCommandExecutor(), name, semaphorePubSub);
}
public RPermitExpirableSemaphore getPermitExpirableSemaphore(String name) {
return new RedissonPermitExpirableSemaphore(commandExecutor, name, semaphorePubSub);
return new RedissonPermitExpirableSemaphore(connectionManager.getCommandExecutor(), name, semaphorePubSub);
}
@Override
public <V> RBloomFilter<V> getBloomFilter(String name) {
return new RedissonBloomFilter<V>(commandExecutor, name);
return new RedissonBloomFilter<V>(connectionManager.getCommandExecutor(), name);
}
@Override
public <V> RBloomFilter<V> getBloomFilter(String name, Codec codec) {
return new RedissonBloomFilter<V>(codec, commandExecutor, name);
return new RedissonBloomFilter<V>(codec, connectionManager.getCommandExecutor(), name);
}
@Override
public RKeys getKeys() {
return new RedissonKeys(commandExecutor);
return new RedissonKeys(connectionManager.getCommandExecutor());
}
@Override
public RBatch createBatch() {
RedissonBatch batch = new RedissonBatch(evictionScheduler, connectionManager);
RedissonBatch batch = new RedissonBatch(id, evictionScheduler, connectionManager);
if (config.isRedissonReferenceEnabled()) {
batch.enableRedissonReferenceSupport(this);
}
@ -568,8 +603,29 @@ public class Redisson implements RedissonClient {
}
protected void enableRedissonReferenceSupport() {
this.commandExecutor.enableRedissonReferenceSupport(this);
this.connectionManager.getCommandExecutor().enableRedissonReferenceSupport(this);
}
@Override
public <V> RPriorityQueue<V> getPriorityQueue(String name) {
return new RedissonPriorityQueue<V>(connectionManager.getCommandExecutor(), name, this);
}
@Override
public <V> RPriorityQueue<V> getPriorityQueue(String name, Codec codec) {
return new RedissonPriorityQueue<V>(codec, connectionManager.getCommandExecutor(), name, this);
}
@Override
public <V> RPriorityDeque<V> getPriorityDeque(String name) {
return new RedissonPriorityDeque<V>(connectionManager.getCommandExecutor(), name, this);
}
@Override
public <V> RPriorityDeque<V> getPriorityDeque(String name, Codec codec) {
return new RedissonPriorityDeque<V>(codec, connectionManager.getCommandExecutor(), name, this);
}
}

@ -22,12 +22,15 @@ import java.util.List;
import java.util.NoSuchElementException;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import io.netty.buffer.ByteBuf;
abstract class RedissonBaseIterator<V> implements Iterator<V> {
private List<V> firstValues;
private List<V> lastValues;
private Iterator<V> lastIter;
private List<ByteBuf> firstValues;
private List<ByteBuf> lastValues;
private Iterator<ScanObjectEntry> lastIter;
protected long nextIterPos;
protected InetSocketAddress client;
@ -40,6 +43,8 @@ abstract class RedissonBaseIterator<V> implements Iterator<V> {
public boolean hasNext() {
if (lastIter == null || !lastIter.hasNext()) {
if (finished) {
free(firstValues);
free(lastValues);
currentElementRemoved = false;
removeExecuted = false;
@ -56,8 +61,12 @@ abstract class RedissonBaseIterator<V> implements Iterator<V> {
long prevIterPos;
do {
prevIterPos = nextIterPos;
ListScanResult<V> res = iterator(client, nextIterPos);
lastValues = new ArrayList<V>(res.getValues());
ListScanResult<ScanObjectEntry> res = iterator(client, nextIterPos);
if (lastValues != null) {
free(lastValues);
}
lastValues = convert(res.getValues());
client = res.getRedisClient();
if (nextIterPos == 0 && firstValues == null) {
@ -87,6 +96,9 @@ abstract class RedissonBaseIterator<V> implements Iterator<V> {
}
}
} else if (lastValues.removeAll(firstValues)) {
free(firstValues);
free(lastValues);
currentElementRemoved = false;
removeExecuted = false;
client = null;
@ -111,11 +123,28 @@ abstract class RedissonBaseIterator<V> implements Iterator<V> {
return lastIter.hasNext();
}
private List<ByteBuf> convert(List<ScanObjectEntry> list) {
List<ByteBuf> result = new ArrayList<ByteBuf>(list.size());
for (ScanObjectEntry entry : list) {
result.add(entry.getBuf());
}
return result;
}
private void free(List<ByteBuf> list) {
if (list == null) {
return;
}
for (ByteBuf byteBuf : list) {
byteBuf.release();
}
}
protected boolean tryAgain() {
return false;
}
abstract ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos);
abstract ListScanResult<ScanObjectEntry> iterator(InetSocketAddress client, long nextIterPos);
@Override
public V next() {
@ -123,7 +152,7 @@ abstract class RedissonBaseIterator<V> implements Iterator<V> {
throw new NoSuchElementException("No such element");
}
value = lastIter.next();
value = (V) lastIter.next().getObj();
currentElementRemoved = false;
return value;
}

@ -28,7 +28,7 @@ import org.redisson.client.protocol.decoder.ScanObjectEntry;
import io.netty.buffer.ByteBuf;
abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
public abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
private Map<ByteBuf, ByteBuf> firstValues;
private Map<ByteBuf, ByteBuf> lastValues;
@ -151,7 +151,7 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
@Override
public M next() {
if (!hasNext()) {
throw new NoSuchElementException("No such element at index");
throw new NoSuchElementException();
}
entry = lastIter.next();
@ -160,7 +160,7 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
}
@SuppressWarnings("unchecked")
M getValue(final Entry<ScanObjectEntry, ScanObjectEntry> entry) {
protected M getValue(final Entry<ScanObjectEntry, ScanObjectEntry> entry) {
return (M)new AbstractMap.SimpleEntry<K, V>((K)entry.getKey().getObj(), (V)entry.getValue().getObj()) {
@Override
@ -176,7 +176,7 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
if (currentElementRemoved) {
throw new IllegalStateException("Element been already deleted");
}
if (lastIter == null) {
if (lastIter == null || entry == null) {
throw new IllegalStateException();
}
@ -185,6 +185,7 @@ abstract class RedissonBaseMapIterator<K, V, M> implements Iterator<M> {
removeKey();
currentElementRemoved = true;
removeExecuted = true;
entry = null;
}
protected abstract void removeKey();

@ -16,6 +16,7 @@
package org.redisson;
import java.util.List;
import java.util.UUID;
import org.redisson.api.RAtomicDoubleAsync;
import org.redisson.api.RAtomicLongAsync;
@ -41,9 +42,11 @@ import org.redisson.api.RScriptAsync;
import org.redisson.api.RSetAsync;
import org.redisson.api.RSetCacheAsync;
import org.redisson.api.RTopicAsync;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandBatchService;
import org.redisson.connection.ConnectionManager;
import org.redisson.eviction.EvictionScheduler;
/**
*
@ -55,10 +58,12 @@ public class RedissonBatch implements RBatch {
private final EvictionScheduler evictionScheduler;
private final CommandBatchService executorService;
private final UUID id;
protected RedissonBatch(EvictionScheduler evictionScheduler, ConnectionManager connectionManager) {
protected RedissonBatch(UUID id, EvictionScheduler evictionScheduler, ConnectionManager connectionManager) {
this.executorService = new CommandBatchService(connectionManager);
this.evictionScheduler = evictionScheduler;
this.id = id;
}
@Override
@ -93,12 +98,12 @@ public class RedissonBatch implements RBatch {
@Override
public <K, V> RMapAsync<K, V> getMap(String name) {
return new RedissonMap<K, V>(executorService, name);
return new RedissonMap<K, V>(id, executorService, name);
}
@Override
public <K, V> RMapAsync<K, V> getMap(String name, Codec codec) {
return new RedissonMap<K, V>(codec, executorService, name);
return new RedissonMap<K, V>(id, codec, executorService, name);
}
@Override
@ -193,12 +198,12 @@ public class RedissonBatch implements RBatch {
@Override
public <K, V> RMapCacheAsync<K, V> getMapCache(String name, Codec codec) {
return new RedissonMapCache<K, V>(codec, evictionScheduler, executorService, name);
return new RedissonMapCache<K, V>(id, codec, evictionScheduler, executorService, name);
}
@Override
public <K, V> RMapCacheAsync<K, V> getMapCache(String name) {
return new RedissonMapCache<K, V>(evictionScheduler, executorService, name);
return new RedissonMapCache<K, V>(id, evictionScheduler, executorService, name);
}
@Override
@ -243,22 +248,22 @@ public class RedissonBatch implements RBatch {
@Override
public <K, V> RMultimapAsync<K, V> getSetMultimap(String name) {
return new RedissonSetMultimap<K, V>(executorService, name);
return new RedissonSetMultimap<K, V>(id, executorService, name);
}
@Override
public <K, V> RMultimapAsync<K, V> getSetMultimap(String name, Codec codec) {
return new RedissonSetMultimap<K, V>(codec, executorService, name);
return new RedissonSetMultimap<K, V>(id, codec, executorService, name);
}
@Override
public <K, V> RMultimapAsync<K, V> getListMultimap(String name) {
return new RedissonListMultimap<K, V>(executorService, name);
return new RedissonListMultimap<K, V>(id, executorService, name);
}
@Override
public <K, V> RMultimapAsync<K, V> getListMultimap(String name, Codec codec) {
return new RedissonListMultimap<K, V>(codec, executorService, name);
return new RedissonListMultimap<K, V>(id, codec, executorService, name);
}
@Override
@ -273,22 +278,22 @@ public class RedissonBatch implements RBatch {
@Override
public <K, V> RMultimapCacheAsync<K, V> getSetMultimapCache(String name) {
return new RedissonSetMultimapCache<K, V>(evictionScheduler, executorService, name);
return new RedissonSetMultimapCache<K, V>(id, evictionScheduler, executorService, name);
}
@Override
public <K, V> RMultimapCacheAsync<K, V> getSetMultimapCache(String name, Codec codec) {
return new RedissonSetMultimapCache<K, V>(evictionScheduler, codec, executorService, name);
return new RedissonSetMultimapCache<K, V>(id, evictionScheduler, codec, executorService, name);
}
@Override
public <K, V> RMultimapCacheAsync<K, V> getListMultimapCache(String name) {
return new RedissonListMultimapCache<K, V>(evictionScheduler, executorService, name);
return new RedissonListMultimapCache<K, V>(id, evictionScheduler, executorService, name);
}
@Override
public <K, V> RMultimapCacheAsync<K, V> getListMultimapCache(String name, Codec codec) {
return new RedissonListMultimapCache<K, V>(evictionScheduler, codec, executorService, name);
return new RedissonListMultimapCache<K, V>(id, evictionScheduler, codec, executorService, name);
}
protected void enableRedissonReferenceSupport(Redisson redisson) {

@ -0,0 +1,303 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import org.redisson.api.RBinaryStream;
import org.redisson.api.RFuture;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.handler.State;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.RPromise;
import io.netty.buffer.ByteBuf;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonBinaryStream extends RedissonBucket<byte[]> implements RBinaryStream {
class RedissonOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
writeBytes(new byte[] {(byte)b});
}
private void writeBytes(byte[] bytes) {
get(writeAsync(bytes));
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
byte[] dest;
if (b.length == len && off == 0) {
dest = b;
} else {
dest = new byte[len];
System.arraycopy(b, off, dest, 0, len);
}
writeBytes(dest);
}
}
class RedissonInputStream extends InputStream {
private int index;
private int mark;
@Override
public long skip(long n) throws IOException {
long k = size() - index;
if (n < k) {
k = n;
if (n < 0) {
k = 0;
}
}
index += k;
return k;
}
@Override
public void mark(int readlimit) {
mark = index;
}
@Override
public void reset() throws IOException {
index = mark;
}
@Override
public int available() throws IOException {
return (int)(size() - index);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public int read() throws IOException {
byte[] b = new byte[1];
int len = read(b);
if (len == -1) {
return -1;
}
return b[0] & 0xff;
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
if (len == 0) {
return 0;
}
if (b == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
return (Integer)get(commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Integer>("EVAL", new Decoder<Integer>() {
@Override
public Integer decode(ByteBuf buf, State state) {
if (buf.readableBytes() == 0) {
return -1;
}
int readBytes = Math.min(buf.readableBytes(), len);
buf.readBytes(b, off, readBytes);
index += readBytes;
return readBytes;
}
}),
"local parts = redis.call('get', KEYS[2]); "
+ "if parts ~= false then "
+ "local startPart = math.floor(tonumber(ARGV[1])/536870912); "
+ "local endPart = math.floor(tonumber(ARGV[2])/536870912); "
+ "local startPartName = KEYS[1]; "
+ "local endPartName = KEYS[1]; "
+ "if startPart > 0 then "
+ "startPartName = KEYS[1] .. ':' .. startPart; "
+ "end; "
+ "if endPart > 0 then "
+ "endPartName = KEYS[1] .. ':' .. endPart; "
+ "end; "
+ "if startPartName ~= endPartName then "
+ "local startIndex = tonumber(ARGV[1]) - startPart*536870912; "
+ "local endIndex = tonumber(ARGV[2]) - endPart*536870912; "
+ "local result = redis.call('getrange', startPartName, startIndex, 536870911); "
+ "result = result .. redis.call('getrange', endPartName, 0, endIndex-1); "
+ "return result; "
+ "end; "
+ "local startIndex = tonumber(ARGV[1]) - startPart*536870912; "
+ "local endIndex = tonumber(ARGV[2]) - endPart*536870912; "
+ "return redis.call('getrange', startPartName, startIndex, endIndex);"
+ "end;"
+ "return redis.call('getrange', KEYS[1], ARGV[1], ARGV[2]);",
Arrays.<Object>asList(getName(), getPartsName()), index, index + len - 1));
}
}
protected RedissonBinaryStream(CommandAsyncExecutor connectionManager, String name) {
super(ByteArrayCodec.INSTANCE, connectionManager, name);
}
@Override
public RFuture<Long> sizeAsync() {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LONG,
"local parts = redis.call('get', KEYS[2]); "
+ "local lastPartName = KEYS[1];"
+ "if parts ~= false then "
+ "lastPartName = KEYS[1] .. ':' .. (tonumber(parts)-1);"
+ "local lastPartSize = redis.call('strlen', lastPartName);"
+ "return ((tonumber(parts)-1) * 536870912) + lastPartSize;"
+ "end;"
+ "return redis.call('strlen', lastPartName);",
Arrays.<Object>asList(getName(), getPartsName()));
}
private RFuture<Void> writeAsync(byte[] bytes) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_VOID,
"local parts = redis.call('get', KEYS[2]); "
+ "local lastPartName = KEYS[1];"
+ "if parts ~= false then "
+ "lastPartName = KEYS[1] .. ':' .. (tonumber(parts)-1);"
+ "end;"
+ "local lastPartSize = redis.call('strlen', lastPartName);"
+ "if lastPartSize == 0 then "
+ "redis.call('append', lastPartName, ARGV[1]); "
+ "return; "
+ "end;"
+ "local chunkSize = 536870912 - lastPartSize; "
+ "local arraySize = string.len(ARGV[1]); "
+ "if chunkSize > 0 then "
+ "if chunkSize >= arraySize then "
+ "redis.call('append', lastPartName, ARGV[1]); "
+ "return; "
+ "else "
+ "local chunk = string.sub(ARGV[1], 1, chunkSize);"
+ "redis.call('append', lastPartName, chunk); "
+ "if parts == false then "
+ "parts = 1;"
+ "redis.call('incrby', KEYS[2], 2); "
+ "else "
+ "redis.call('incrby', KEYS[2], 1); "
+ "end; "
+ "local newPartName = KEYS[1] .. ':' .. parts; "
+ "chunk = string.sub(ARGV[1], -(arraySize - chunkSize));"
+ "redis.call('append', newPartName, chunk); "
+ "end; "
+ "else "
+ "if parts == false then "
+ "parts = 1;"
+ "redis.call('incrby', KEYS[2], 2); "
+ "else "
+ "redis.call('incrby', KEYS[2], 1); "
+ "end; "
+ "local newPartName = KEYS[1] .. ':' .. parts; "
+ "local chunk = string.sub(ARGV[1], -(arraySize - chunkSize));"
+ "redis.call('append', newPartName, ARGV[1]); "
+ "end; ",
Arrays.<Object>asList(getName(), getPartsName()), bytes);
}
@Override
public InputStream getInputStream() {
return new RedissonInputStream();
}
@Override
public OutputStream getOutputStream() {
return new RedissonOutputStream();
}
@Override
public RFuture<Void> setAsync(byte[] value) {
if (value.length > 512*1024*1024) {
RPromise<Void> result = newPromise();
int chunkSize = 10*1024*1024;
write(value, result, chunkSize, 0);
return result;
}
return super.setAsync(value);
}
private void write(final byte[] value, final RPromise<Void> result, final int chunkSize, final int i) {
final int len = Math.min(value.length - i*chunkSize, chunkSize);
byte[] bytes = Arrays.copyOfRange(value, i*chunkSize, i*chunkSize + len);
writeAsync(bytes).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
int j = i + 1;
if (j*chunkSize > value.length) {
result.trySuccess(null);
} else {
write(value, result, chunkSize, j);
}
}
});
}
private String getPartsName() {
return getName() + ":parts";
}
@Override
public RFuture<Boolean> deleteAsync() {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_AMOUNT,
"local parts = redis.call('get', KEYS[2]); "
+ "local names = {KEYS[1], KEYS[2]};"
+ "if parts ~= false then "
+ "for i = 1, tonumber(parts)-1, 1 do "
+ "table.insert(names, KEYS[1] .. ':' .. i); "
+ "end; "
+ "end;"
+ "return redis.call('del', unpack(names));",
Arrays.<Object>asList(getName(), getPartsName()));
}
}

@ -231,7 +231,7 @@ public class RedissonBlockingDeque<V> extends RedissonDeque<V> implements RBlock
for (Object name : queueNames) {
params.add(name);
}
params.add(unit.toSeconds(timeout));
params.add(toSeconds(timeout, unit));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BRPOP_VALUE, params.toArray());
}
@ -243,7 +243,7 @@ public class RedissonBlockingDeque<V> extends RedissonDeque<V> implements RBlock
@Override
public RFuture<V> pollLastAsync(long timeout, TimeUnit unit) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BRPOP_VALUE, getName(), unit.toSeconds(timeout));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BRPOP_VALUE, getName(), toSeconds(timeout, unit));
}
@Override

@ -0,0 +1,781 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.Collections;
import java.util.UUID;
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.api.RBlockingFairQueue;
import org.redisson.api.RFuture;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor;
import org.redisson.misc.RPromise;
import org.redisson.pubsub.SemaphorePubSub;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonBlockingFairQueue<V> extends RedissonBlockingQueue<V> implements RBlockingFairQueue<V> {
public static final long TIMEOUT_SECONDS = 30;
private final UUID id;
private final AtomicInteger instances = new AtomicInteger();
private final SemaphorePubSub semaphorePubSub;
protected RedissonBlockingFairQueue(CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub, UUID id) {
super(commandExecutor, name);
this.semaphorePubSub = semaphorePubSub;
this.id = id;
instances.incrementAndGet();
}
protected RedissonBlockingFairQueue(Codec codec, CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub, UUID id) {
super(codec, commandExecutor, name);
this.semaphorePubSub = semaphorePubSub;
this.id = id;
instances.incrementAndGet();
}
private String getIdsListName() {
return suffixName(getName(), "list");
}
private String getTimeoutName() {
return suffixName(getName(), "timeout");
}
private String getChannelName() {
return suffixName(getName(), getCurrentId() + ":channel");
}
private RedissonLockEntry getEntry() {
return semaphorePubSub.getEntry(getName());
}
private RFuture<RedissonLockEntry> subscribe() {
return semaphorePubSub.subscribe(getName(), getChannelName(), commandExecutor.getConnectionManager());
}
private void unsubscribe(RFuture<RedissonLockEntry> future) {
semaphorePubSub.unsubscribe(future.getNow(), getName(), getChannelName(), commandExecutor.getConnectionManager());
}
@Override
public RFuture<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getIdsListName(), getTimeoutName());
}
private Long tryAcquire() {
return get(tryAcquireAsync());
}
private RFuture<Long> tryAcquireAsync() {
long timeout = System.currentTimeMillis() + TIMEOUT_SECONDS*1000;
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
"local timeout = redis.call('get', KEYS[3]);"
+ "if timeout ~= false and tonumber(timeout) <= tonumber(ARGV[3]) then "
+ "redis.call('lpop', KEYS[2]); "
+ "local nextValue = redis.call('lindex', KEYS[2], 0); "
+ "if nextValue ~= false and nextValue ~= ARGV[1] then "
+ "redis.call('set', KEYS[3], ARGV[2]);"
+ "redis.call('publish', '{' .. KEYS[1] .. '}:' .. nextValue .. ':channel', 1);"
+ "end; "
+ "end; "
+ "local items = redis.call('lrange', KEYS[2], 0, -1) "
+ "local found = false; "
+ "for i=1,#items do "
+ "if items[i] == ARGV[1] then "
+ "found = true; "
+ "break;"
+ "end; "
+ "end; "
+ "if found == false then "
+ "redis.call('lpush', KEYS[2], ARGV[1]); "
+ "end; "
+ "local value = redis.call('lindex', KEYS[2], 0); "
+ "if value == ARGV[1] then "
+ "redis.call('set', KEYS[3], ARGV[2]);"
+ "local size = redis.call('llen', KEYS[2]); "
+ "if size > 1 then "
+ "redis.call('lpop', KEYS[2]);"
+ "redis.call('rpush', KEYS[2], value);"
+ "local nextValue = redis.call('lindex', KEYS[2], 0); "
+ "redis.call('publish', '{' .. KEYS[1] .. '}:' .. nextValue .. ':channel', 1);"
+ "end; "
+ "return nil;"
+ "end;"
+ "return tonumber(timeout) - tonumber(ARGV[3]);",
Arrays.<Object>asList(getName(), getIdsListName(), getTimeoutName()), getCurrentId(), timeout, System.currentTimeMillis());
}
private String getCurrentId() {
return id.toString();
}
@Override
public V take() throws InterruptedException {
Long currentTimeout = tryAcquire();
if (currentTimeout == null) {
return super.take();
}
RFuture<RedissonLockEntry> future = subscribe();
commandExecutor.syncSubscription(future);
try {
while (true) {
currentTimeout = tryAcquire();
if (currentTimeout == null) {
return super.take();
}
getEntry().getLatch().tryAcquire(currentTimeout, TimeUnit.MILLISECONDS);
}
} finally {
unsubscribe(future);
}
}
@Override
public void destroy() {
if (instances.decrementAndGet() == 0) {
get(commandExecutor.evalWriteAsync(getName(), StringCodec.INSTANCE, RedisCommands.EVAL_VOID_WITH_VALUES,
"for i = 1, #ARGV, 1 do "
+ "redis.call('lrem', KEYS[1], 0, ARGV[i]);"
+"end; ",
Collections.<Object>singletonList(getIdsListName()), getCurrentId()));
}
}
@Override
public RFuture<V> takeAsync() {
final RPromise<V> promise = newPromise();
RFuture<Long> tryAcquireFuture = tryAcquireAsync();
tryAcquireFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
final Long currentTimeout = future.getNow();
if (currentTimeout == null) {
final RFuture<V> pollFuture = RedissonBlockingFairQueue.super.takeAsync();
pollFuture.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(future.getNow());
}
});
} else {
final RFuture<RedissonLockEntry> subscribeFuture = subscribe();
final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
@Override
public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
if (futureRef.get() != null) {
futureRef.get().cancel();
}
tryTakeAsync(subscribeFuture, promise);
}
});
}
}
});
return promise;
}
@Override
public V poll() {
Long currentTimeout = tryAcquire();
if (currentTimeout == null) {
return super.poll();
}
return null;
}
@Override
public RFuture<V> pollAsync() {
final RPromise<V> promise = newPromise();
RFuture<Long> tryAcquireFuture = tryAcquireAsync();
tryAcquireFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
final Long currentTimeout = future.getNow();
if (currentTimeout == null) {
final RFuture<V> pollFuture = RedissonBlockingFairQueue.super.pollAsync();
pollFuture.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(future.getNow());
}
});
} else {
promise.trySuccess(null);
}
}
});
return promise;
}
@Override
public V poll(long timeout, TimeUnit unit) throws InterruptedException {
long startTime = System.currentTimeMillis();
Long currentTimeout = tryAcquire();
if (currentTimeout == null) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
return super.poll(remainTime, TimeUnit.MILLISECONDS);
}
return null;
}
RFuture<RedissonLockEntry> future = subscribe();
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (!future.awaitUninterruptibly(remainTime, TimeUnit.MILLISECONDS)) {
return null;
}
try {
while (true) {
currentTimeout = tryAcquire();
if (currentTimeout == null) {
spentTime = System.currentTimeMillis() - startTime;
remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
return super.poll(remainTime, TimeUnit.MILLISECONDS);
}
return null;
}
spentTime = System.currentTimeMillis() - startTime;
remainTime = unit.toMillis(timeout) - spentTime;
remainTime = Math.min(remainTime, currentTimeout);
if (remainTime <= 0 || !getEntry().getLatch().tryAcquire(remainTime, TimeUnit.MILLISECONDS)) {
return null;
}
}
} finally {
unsubscribe(future);
}
}
@Override
public RFuture<V> pollAsync(final long timeout, final TimeUnit unit) {
final long startTime = System.currentTimeMillis();
final RPromise<V> promise = newPromise();
RFuture<Long> tryAcquireFuture = tryAcquireAsync();
tryAcquireFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
Long currentTimeout = future.getNow();
if (currentTimeout == null) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
final RFuture<V> pollFuture = RedissonBlockingFairQueue.super.pollAsync(remainTime, TimeUnit.MILLISECONDS);
pollFuture.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(future.getNow());
}
});
} else {
promise.trySuccess(null);
}
} else {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
remainTime = Math.min(remainTime, currentTimeout);
if (remainTime <= 0) {
promise.trySuccess(null);
return;
}
final RFuture<RedissonLockEntry> subscribeFuture = subscribe();
final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
@Override
public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
if (futureRef.get() != null) {
futureRef.get().cancel();
}
tryPollAsync(startTime, timeout, unit, subscribeFuture, promise);
}
});
if (!subscribeFuture.isDone()) {
Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (!subscribeFuture.isDone()) {
subscribeFuture.cancel(false);
promise.trySuccess(null);
}
}
}, remainTime, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
});
return promise;
}
private void tryTakeAsync(final RFuture<RedissonLockEntry> subscribeFuture, final RPromise<V> promise) {
if (promise.isDone()) {
unsubscribe(subscribeFuture);
return;
}
RFuture<Long> tryAcquireFuture = tryAcquireAsync();
tryAcquireFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
unsubscribe(subscribeFuture);
promise.tryFailure(future.cause());
return;
}
Long currentTimeout = future.getNow();
if (currentTimeout == null) {
final RFuture<V> pollFuture = RedissonBlockingFairQueue.super.takeAsync();
pollFuture.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
unsubscribe(subscribeFuture);
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(future.getNow());
}
});
} else {
final RedissonLockEntry entry = getEntry();
synchronized (entry) {
if (entry.getLatch().tryAcquire()) {
tryTakeAsync(subscribeFuture, promise);
} else {
final AtomicBoolean executed = new AtomicBoolean();
final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
final Runnable listener = new Runnable() {
@Override
public void run() {
executed.set(true);
if (futureRef.get() != null) {
futureRef.get().cancel();
}
tryTakeAsync(subscribeFuture, promise);
}
};
entry.addListener(listener);
if (!executed.get()) {
Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout t) throws Exception {
synchronized (entry) {
if (entry.removeListener(listener)) {
tryTakeAsync(subscribeFuture, promise);
}
}
}
}, currentTimeout, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
}
};
});
}
private void tryPollAsync(final long startTime, final long timeout, final TimeUnit unit,
final RFuture<RedissonLockEntry> subscribeFuture, final RPromise<V> promise) {
if (promise.isDone()) {
unsubscribe(subscribeFuture);
return;
}
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime <= 0) {
unsubscribe(subscribeFuture);
promise.trySuccess(null);
return;
}
RFuture<Long> tryAcquireFuture = tryAcquireAsync();
tryAcquireFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
unsubscribe(subscribeFuture);
promise.tryFailure(future.cause());
return;
}
Long currentTimeout = future.getNow();
if (currentTimeout == null) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
final RFuture<V> pollFuture = RedissonBlockingFairQueue.super.pollAsync(remainTime, TimeUnit.MILLISECONDS);
pollFuture.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
unsubscribe(subscribeFuture);
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(future.getNow());
}
});
} else {
unsubscribe(subscribeFuture);
promise.trySuccess(null);
}
} else {
final RedissonLockEntry entry = getEntry();
synchronized (entry) {
if (entry.getLatch().tryAcquire()) {
tryPollAsync(startTime, timeout, unit, subscribeFuture, promise);
} else {
final AtomicBoolean executed = new AtomicBoolean();
final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
final Runnable listener = new Runnable() {
@Override
public void run() {
executed.set(true);
if (futureRef.get() != null) {
futureRef.get().cancel();
}
tryPollAsync(startTime, timeout, unit, subscribeFuture, promise);
}
};
entry.addListener(listener);
if (!executed.get()) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout t) throws Exception {
synchronized (entry) {
if (entry.removeListener(listener)) {
tryPollAsync(startTime, timeout, unit, subscribeFuture, promise);
}
}
}
}, remainTime, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
}
};
});
}
@Override
public V pollLastAndOfferFirstTo(String queueName, long timeout, TimeUnit unit) throws InterruptedException {
long startTime = System.currentTimeMillis();
Long currentTimeout = tryAcquire();
if (currentTimeout == null) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
return super.pollLastAndOfferFirstTo(queueName, remainTime, TimeUnit.MILLISECONDS);
}
return null;
}
RFuture<RedissonLockEntry> future = subscribe();
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (!future.awaitUninterruptibly(remainTime, TimeUnit.MILLISECONDS)) {
return null;
}
try {
while (true) {
currentTimeout = tryAcquire();
if (currentTimeout == null) {
spentTime = System.currentTimeMillis() - startTime;
remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
return super.pollLastAndOfferFirstTo(queueName, remainTime, TimeUnit.MILLISECONDS);
}
return null;
}
spentTime = System.currentTimeMillis() - startTime;
remainTime = unit.toMillis(timeout) - spentTime;
remainTime = Math.min(remainTime, currentTimeout);
if (remainTime <= 0 || !getEntry().getLatch().tryAcquire(remainTime, TimeUnit.MILLISECONDS)) {
return null;
}
}
} finally {
unsubscribe(future);
}
}
@Override
public RFuture<V> pollLastAndOfferFirstToAsync(final String queueName, final long timeout, final TimeUnit unit) {
final long startTime = System.currentTimeMillis();
final RPromise<V> promise = newPromise();
RFuture<Long> tryAcquireFuture = tryAcquireAsync();
tryAcquireFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
Long currentTimeout = future.getNow();
if (currentTimeout == null) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
final RFuture<V> pollFuture = RedissonBlockingFairQueue.super.pollLastAndOfferFirstToAsync(queueName, remainTime, TimeUnit.MILLISECONDS);
pollFuture.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(future.getNow());
}
});
} else {
promise.trySuccess(null);
}
} else {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
remainTime = Math.min(remainTime, currentTimeout);
if (remainTime <= 0) {
promise.trySuccess(null);
return;
}
final RFuture<RedissonLockEntry> subscribeFuture = subscribe();
final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
@Override
public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
if (futureRef.get() != null) {
futureRef.get().cancel();
}
tryPollLastAndOfferFirstToAsync(startTime, timeout, unit, subscribeFuture, promise, queueName);
}
});
if (!subscribeFuture.isDone()) {
Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (!subscribeFuture.isDone()) {
subscribeFuture.cancel(false);
promise.trySuccess(null);
}
}
}, remainTime, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
});
return promise;
}
private void tryPollLastAndOfferFirstToAsync(final long startTime, final long timeout, final TimeUnit unit,
final RFuture<RedissonLockEntry> subscribeFuture, final RPromise<V> promise, final String queueName) {
if (promise.isDone()) {
unsubscribe(subscribeFuture);
return;
}
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime <= 0) {
unsubscribe(subscribeFuture);
promise.trySuccess(null);
return;
}
RFuture<Long> tryAcquireFuture = tryAcquireAsync();
tryAcquireFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(Future<Long> future) throws Exception {
if (!future.isSuccess()) {
unsubscribe(subscribeFuture);
promise.tryFailure(future.cause());
return;
}
Long currentTimeout = future.getNow();
if (currentTimeout == null) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
if (remainTime > 0) {
final RFuture<V> pollFuture = RedissonBlockingFairQueue.super.pollLastAndOfferFirstToAsync(queueName, remainTime, TimeUnit.MILLISECONDS);
pollFuture.addListener(new FutureListener<V>() {
@Override
public void operationComplete(Future<V> future) throws Exception {
unsubscribe(subscribeFuture);
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(future.getNow());
}
});
} else {
unsubscribe(subscribeFuture);
promise.trySuccess(null);
}
} else {
final RedissonLockEntry entry = getEntry();
synchronized (entry) {
if (entry.getLatch().tryAcquire()) {
tryPollAsync(startTime, timeout, unit, subscribeFuture, promise);
} else {
final AtomicBoolean executed = new AtomicBoolean();
final AtomicReference<Timeout> futureRef = new AtomicReference<Timeout>();
final Runnable listener = new Runnable() {
@Override
public void run() {
executed.set(true);
if (futureRef.get() != null) {
futureRef.get().cancel();
}
tryPollLastAndOfferFirstToAsync(startTime, timeout, unit, subscribeFuture, promise, queueName);
}
};
entry.addListener(listener);
if (!executed.get()) {
long spentTime = System.currentTimeMillis() - startTime;
long remainTime = unit.toMillis(timeout) - spentTime;
Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout t) throws Exception {
synchronized (entry) {
if (entry.removeListener(listener)) {
tryPollLastAndOfferFirstToAsync(startTime, timeout, unit, subscribeFuture, promise, queueName);
}
}
}
}, remainTime, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
}
};
});
}
}

@ -84,7 +84,7 @@ public class RedissonBlockingQueue<V> extends RedissonQueue<V> implements RBlock
@Override
public RFuture<V> pollAsync(long timeout, TimeUnit unit) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BLPOP_VALUE, getName(), unit.toSeconds(timeout));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BLPOP_VALUE, getName(), toSeconds(timeout, unit));
}
/*
@ -118,13 +118,13 @@ public class RedissonBlockingQueue<V> extends RedissonQueue<V> implements RBlock
for (Object name : queueNames) {
params.add(name);
}
params.add(unit.toSeconds(timeout));
params.add(toSeconds(timeout, unit));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BLPOP_VALUE, params.toArray());
}
@Override
public RFuture<V> pollLastAndOfferFirstToAsync(String queueName, long timeout, TimeUnit unit) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BRPOPLPUSH, getName(), queueName, unit.toSeconds(timeout));
return commandExecutor.writeAsync(getName(), codec, RedisCommands.BRPOPLPUSH, getName(), queueName, toSeconds(timeout, unit));
}
@Override

@ -35,7 +35,6 @@ import org.redisson.client.protocol.decoder.ObjectMapReplayDecoder;
import org.redisson.command.CommandBatchService;
import org.redisson.command.CommandExecutor;
import io.netty.util.concurrent.Future;
import net.openhft.hashing.LongHashFunction;
/**
@ -212,9 +211,19 @@ public class RedissonBloomFilter<T> extends RedissonExpirable implements RBloomF
@Override
public boolean tryInit(long expectedInsertions, double falseProbability) {
if (falseProbability > 1) {
throw new IllegalArgumentException("Bloom filter false probability can't be greater than 1");
}
if (falseProbability < 0) {
throw new IllegalArgumentException("Bloom filter false probability can't be negative");
}
size = optimalNumOfBits(expectedInsertions, falseProbability);
if (size == 0) {
throw new IllegalArgumentException("Bloom filter calculated size is " + size);
}
if (size > MAX_SIZE) {
throw new IllegalArgumentException("Bloom filter can't be greater than " + MAX_SIZE + ". But calculated size is " + size);
throw new IllegalArgumentException("Bloom filter size can't be greater than " + MAX_SIZE + ". But calculated size is " + size);
}
hashIterations = optimalNumOfHashFunctions(expectedInsertions, size);

@ -24,6 +24,12 @@ import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class RedissonBucket<V> extends RedissonExpirable implements RBucket<V> {
protected RedissonBucket(CommandAsyncExecutor connectionManager, String name) {
@ -97,12 +103,12 @@ public class RedissonBucket<V> extends RedissonExpirable implements RBucket<V> {
}
@Override
public int size() {
public long size() {
return get(sizeAsync());
}
@Override
public RFuture<Integer> sizeAsync() {
public RFuture<Long> sizeAsync() {
return commandExecutor.readAsync(getName(), codec, RedisCommands.STRLEN, getName());
}

@ -50,9 +50,9 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown
}
public void await() throws InterruptedException {
RFuture<RedissonCountDownLatchEntry> promise = subscribe();
RFuture<RedissonCountDownLatchEntry> future = subscribe();
try {
get(promise);
commandExecutor.syncSubscription(future);
while (getCount() > 0) {
// waiting for open state
@ -62,7 +62,7 @@ public class RedissonCountDownLatch extends RedissonObject implements RCountDown
}
}
} finally {
unsubscribe(promise);
unsubscribe(future);
}
}

@ -0,0 +1,502 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RFuture;
import org.redisson.api.RQueue;
import org.redisson.api.RTopic;
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.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.convertor.VoidReplayConvertor;
import org.redisson.command.CommandAsyncExecutor;
import io.netty.util.internal.ThreadLocalRandom;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class RedissonDelayedQueue<V> extends RedissonExpirable implements RDelayedQueue<V> {
private static final RedisCommand<Void> EVAL_OFFER = new RedisCommand<Void>("EVAL", new VoidReplayConvertor(), 9);
private final QueueTransferService queueTransferService;
protected RedissonDelayedQueue(QueueTransferService queueTransferService, Codec codec, final CommandAsyncExecutor commandExecutor, String name) {
super(codec, commandExecutor, name);
QueueTransferTask task = new QueueTransferTask(commandExecutor.getConnectionManager()) {
@Override
protected RFuture<Long> pushTaskAsync() {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
"local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "if #expiredValues > 0 then "
+ "for i, v in ipairs(expiredValues) do "
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "redis.call('rpush', KEYS[1], value);"
+ "redis.call('lrem', KEYS[3], 1, v);"
+ "end; "
+ "redis.call('zrem', KEYS[2], unpack(expiredValues));"
+ "end; "
// get startTime from scheduler queue head task
+ "local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); "
+ "if v[1] ~= nil then "
+ "return v[2]; "
+ "end "
+ "return nil;",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getQueueName()),
System.currentTimeMillis(), 100);
}
@Override
protected RTopic<Long> getTopic() {
return new RedissonTopic<Long>(LongCodec.INSTANCE, commandExecutor, getChannelName());
}
};
queueTransferService.schedule(getQueueName(), task);
this.queueTransferService = queueTransferService;
}
private String getChannelName() {
return prefixName("redisson_delay_queue_channel", getName());
}
private String getQueueName() {
return prefixName("redisson_delay_queue", getName());
}
private String getTimeoutSetName() {
return prefixName("redisson_delay_queue_timeout", getName());
}
public void offer(V e, long delay, TimeUnit timeUnit) {
get(offerAsync(e, delay, timeUnit));
}
public RFuture<Void> offerAsync(V e, long delay, TimeUnit timeUnit) {
long delayInMs = timeUnit.toMillis(delay);
long timeout = System.currentTimeMillis() + delayInMs;
long randomId = ThreadLocalRandom.current().nextLong();
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_OFFER,
"local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);"
+ "redis.call('zadd', KEYS[2], ARGV[1], value);"
+ "redis.call('rpush', KEYS[3], value);"
// if new object added to queue head when publish its startTime
// to all scheduler workers
+ "local v = redis.call('zrange', KEYS[2], 0, 0); "
+ "if v[1] == value then "
+ "redis.call('publish', KEYS[4], ARGV[1]); "
+ "end;"
,
Arrays.<Object>asList(getName(), getTimeoutSetName(), getQueueName(), getChannelName()),
timeout, randomId, e);
}
@Override
public boolean add(V e) {
throw new UnsupportedOperationException("Use 'offer' method with timeout param");
}
@Override
public boolean offer(V e) {
throw new UnsupportedOperationException("Use 'offer' method with timeout param");
}
@Override
public V remove() {
V value = poll();
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
@Override
public V poll() {
return get(pollAsync());
}
@Override
public V element() {
V value = peek();
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
@Override
public V peek() {
return get(peekAsync());
}
@Override
public int size() {
return get(sizeAsync());
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean contains(Object o) {
return get(containsAsync(o));
}
V getValue(int index) {
return (V)get(commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_OBJECT,
"local v = redis.call('lindex', KEYS[1], ARGV[1]); "
+ "if v ~= false then "
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "return value; "
+ "end "
+ "return nil;",
Arrays.<Object>asList(getQueueName()), index));
}
void remove(int index) {
get(commandExecutor.evalWriteAsync(getName(), null, RedisCommands.EVAL_VOID,
"local v = redis.call('lindex', KEYS[1], ARGV[1]);" +
"if v ~= false then " +
"local randomId, value = struct.unpack('dLc0', v);" +
"redis.call('lrem', KEYS[1], 1, v);" +
"redis.call('zrem', KEYS[2], v);" +
"end; ",
Arrays.<Object>asList(getQueueName(), getTimeoutSetName()), index));
}
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
private V nextCurrentValue;
private V currentValueHasRead;
private int currentIndex = -1;
private boolean hasBeenModified = true;
@Override
public boolean hasNext() {
V val = RedissonDelayedQueue.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");
}
RedissonDelayedQueue.this.remove(currentIndex);
currentIndex--;
hasBeenModified = true;
currentValueHasRead = null;
}
};
}
@Override
public Object[] toArray() {
List<V> list = readAll();
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
List<V> list = readAll();
return list.toArray(a);
}
@Override
public List<V> readAll() {
return get(readAllAsync());
}
@Override
public RFuture<List<V>> readAllAsync() {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_LIST,
"local result = {}; " +
"local items = redis.call('lrange', KEYS[1], 0, -1); "
+ "for i, v in ipairs(items) do "
+ "local randomId, value = struct.unpack('dLc0', v); "
+ "table.insert(result, value);"
+ "end; "
+ "return result; ",
Collections.<Object>singletonList(getQueueName()));
}
@Override
public boolean remove(Object o) {
return get(removeAsync(o));
}
@Override
public RFuture<Boolean> removeAsync(Object o) {
return removeAsync(o, 1);
}
protected RFuture<Boolean> removeAsync(Object o, int count) {
return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4),
"local s = redis.call('llen', KEYS[1]);" +
"for i = 0, s-1, 1 do "
+ "local v = redis.call('lindex', KEYS[1], i);"
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "if ARGV[1] == value then "
+ "redis.call('lrem', KEYS[1], 1, v);"
+ "return 1;"
+ "end; "
+ "end;" +
"return 0;",
Collections.<Object>singletonList(getQueueName()), o);
}
@Override
public RFuture<Boolean> containsAllAsync(Collection<?> c) {
if (c.isEmpty()) {
return newSucceededFuture(true);
}
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local s = redis.call('llen', KEYS[1]);" +
"for i = 0, s-1, 1 do "
+ "local v = redis.call('lindex', KEYS[1], i);"
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "for j = 1, #ARGV, 1 do "
+ "if value == ARGV[j] then "
+ "table.remove(ARGV, j) "
+ "end; "
+ "end; "
+ "end;" +
"return #ARGV == 0 and 1 or 0;",
Collections.<Object>singletonList(getQueueName()), c.toArray());
}
@Override
public boolean containsAll(Collection<?> c) {
return get(containsAllAsync(c));
}
@Override
public boolean addAll(Collection<? extends V> c) {
throw new UnsupportedOperationException("Use 'offer' method with timeout param");
}
@Override
public RFuture<Boolean> removeAllAsync(Collection<?> c) {
if (c.isEmpty()) {
return newSucceededFuture(false);
}
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local result = 0;" +
"local s = redis.call('llen', KEYS[1]);" +
"local i = 0;" +
"while i < s do "
+ "local v = redis.call('lindex', KEYS[1], i);"
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "for j = 1, #ARGV, 1 do "
+ "if value == ARGV[j] then "
+ "result = 1; "
+ "i = i - 1; "
+ "s = s - 1; "
+ "redis.call('lrem', KEYS[1], 0, v); "
+ "break; "
+ "end; "
+ "end; "
+ "i = i + 1;"
+ "end; "
+ "return result;",
Collections.<Object>singletonList(getQueueName()), c.toArray());
}
@Override
public boolean removeAll(Collection<?> c) {
return get(removeAllAsync(c));
}
@Override
public boolean retainAll(Collection<?> c) {
return get(retainAllAsync(c));
}
@Override
public RFuture<Boolean> retainAllAsync(Collection<?> c) {
if (c.isEmpty()) {
return deleteAsync();
}
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN_WITH_VALUES,
"local changed = 0; " +
"local items = redis.call('lrange', KEYS[1], 0, -1); "
+ "local i = 1; "
+ "while i <= #items do "
+ "local randomId, element = struct.unpack('dLc0', items[i]); "
+ "local isInAgrs = false; "
+ "for j = 1, #ARGV, 1 do "
+ "if ARGV[j] == element then "
+ "isInAgrs = true; "
+ "break; "
+ "end; "
+ "end; "
+ "if isInAgrs == false then "
+ "redis.call('LREM', KEYS[1], 0, items[i]) "
+ "changed = 1; "
+ "end; "
+ "i = i + 1; "
+ "end; "
+ "return changed; ",
Collections.<Object>singletonList(getQueueName()), c.toArray());
}
@Override
public void clear() {
delete();
}
@Override
public RFuture<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getQueueName(), getTimeoutSetName());
}
@Override
public RFuture<V> peekAsync() {
return commandExecutor.evalReadAsync(getName(), codec, RedisCommands.EVAL_OBJECT,
"local v = redis.call('lindex', KEYS[1], 0); "
+ "if v ~= nil then "
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "return value; "
+ "end "
+ "return nil;",
Arrays.<Object>asList(getQueueName()));
}
@Override
public RFuture<V> pollAsync() {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_OBJECT,
"local v = redis.call('lpop', KEYS[1]); "
+ "if v ~= nil then "
+ "redis.call('zrem', KEYS[2], v); "
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "return value; "
+ "end "
+ "return nil;",
Arrays.<Object>asList(getQueueName(), getTimeoutSetName()));
}
@Override
public RFuture<Boolean> offerAsync(V e) {
throw new UnsupportedOperationException("Use 'offer' method with timeout param");
}
@Override
public RFuture<V> pollLastAndOfferFirstToAsync(String queueName) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_OBJECT,
"local v = redis.call('rpop', KEYS[1]); "
+ "if v ~= nil then "
+ "redis.call('zrem', KEYS[2], v); "
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "redis.call('lpush', KEYS[3], value); "
+ "return value; "
+ "end "
+ "return nil;",
Arrays.<Object>asList(getQueueName(), getTimeoutSetName(), queueName));
}
@Override
public RFuture<Boolean> containsAsync(Object o) {
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4),
"local s = redis.call('llen', KEYS[1]);" +
"for i = 0, s-1, 1 do "
+ "local v = redis.call('lindex', KEYS[1], i);"
+ "local randomId, value = struct.unpack('dLc0', v);"
+ "if ARGV[1] == value then "
+ "return 1;"
+ "end; "
+ "end;" +
"return 0;",
Collections.<Object>singletonList(getQueueName()), o);
}
@Override
public RFuture<Integer> sizeAsync() {
return commandExecutor.readAsync(getName(), codec, RedisCommands.LLEN_INT, getQueueName());
}
@Override
public RFuture<Boolean> addAsync(V e) {
throw new UnsupportedOperationException("Use 'offer' method with timeout param");
}
@Override
public RFuture<Boolean> addAllAsync(Collection<? extends V> c) {
throw new UnsupportedOperationException("Use 'offer' method with timeout param");
}
@Override
public V pollLastAndOfferFirstTo(String dequeName) {
return get(pollLastAndOfferFirstToAsync(dequeName));
}
@Override
public V pollLastAndOfferFirstTo(RQueue<V> deque) {
return get(pollLastAndOfferFirstToAsync(deque.getName()));
}
@Override
public void destroy() {
queueTransferService.remove(getQueueName());
}
}

@ -25,8 +25,8 @@ 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.VoidReplayConvertor;
import org.redisson.client.protocol.decoder.ListFirstObjectDecoder;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.connection.decoder.ListFirstObjectDecoder;
/**
* Distributed and concurrent implementation of {@link java.util.Queue}

@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.redisson.api.CronSchedule;
import org.redisson.api.RFuture;
import org.redisson.api.RScheduledExecutorService;
import org.redisson.api.RScheduledFuture;
@ -142,98 +143,6 @@ public class RedissonExecutorService implements RScheduledExecutorService {
asyncScheduledServiceAtFixed = scheduledRemoteService.get(RemoteExecutorServiceAsync.class, RemoteInvocationOptions.defaults().noAck().noResult());
}
private void registerScheduler() {
final AtomicReference<Timeout> timeoutReference = new AtomicReference<Timeout>();
RTopic<Long> schedulerTopic = redisson.getTopic(schedulerChannelName, LongCodec.INSTANCE);
schedulerTopic.addListener(new BaseStatusListener() {
@Override
public void onSubscribe(String channel) {
RFuture<Long> startTimeFuture = commandExecutor.evalReadAsync(schedulerQueueName, LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
// get startTime from scheduler queue head task
"local v = redis.call('zrange', KEYS[1], 0, 0, 'WITHSCORES'); "
+ "if v[1] ~= nil then "
+ "return v[2]; "
+ "end "
+ "return nil;",
Collections.<Object>singletonList(schedulerQueueName));
addListener(timeoutReference, startTimeFuture);
}
});
schedulerTopic.addListener(new MessageListener<Long>() {
@Override
public void onMessage(String channel, Long startTime) {
scheduleTask(timeoutReference, startTime);
}
});
}
private void scheduleTask(final AtomicReference<Timeout> timeoutReference, final Long startTime) {
if (startTime == null) {
return;
}
if (timeoutReference.get() != null) {
timeoutReference.get().cancel();
timeoutReference.set(null);
}
long delay = startTime - System.currentTimeMillis();
if (delay > 10) {
Timeout timeout = connectionManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
pushTask(timeoutReference, startTime);
}
}, delay, TimeUnit.MILLISECONDS);
timeoutReference.set(timeout);
} else {
pushTask(timeoutReference, startTime);
}
}
private void pushTask(AtomicReference<Timeout> timeoutReference, Long startTime) {
RFuture<Long> startTimeFuture = commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
"local expiredTaskIds = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "if #expiredTaskIds > 0 then "
+ "redis.call('zrem', KEYS[2], unpack(expiredTaskIds));"
+ "local expiredTasks = redis.call('hmget', KEYS[3], unpack(expiredTaskIds));"
+ "redis.call('rpush', KEYS[1], unpack(expiredTasks));"
+ "end; "
// get startTime from scheduler queue head task
+ "local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); "
+ "if v[1] ~= nil then "
+ "return v[2]; "
+ "end "
+ "return nil;",
Arrays.<Object>asList(requestQueueName, schedulerQueueName, schedulerTasksName),
System.currentTimeMillis(), 10);
addListener(timeoutReference, startTimeFuture);
}
private void addListener(final AtomicReference<Timeout> timeoutReference, RFuture<Long> startTimeFuture) {
startTimeFuture.addListener(new FutureListener<Long>() {
@Override
public void operationComplete(io.netty.util.concurrent.Future<Long> future) throws Exception {
if (!future.isSuccess()) {
if (future.cause() instanceof RedissonShutdownException) {
return;
}
log.error(future.cause().getMessage(), future.cause());
scheduleTask(timeoutReference, System.currentTimeMillis() + 5 * 1000L);
return;
}
if (future.getNow() != null) {
scheduleTask(timeoutReference, future.getNow());
}
}
});
}
@Override
public void registerWorkers(int workers) {
registerWorkers(workers, commandExecutor.getConnectionManager().getExecutor());
@ -241,7 +150,32 @@ public class RedissonExecutorService implements RScheduledExecutorService {
@Override
public void registerWorkers(int workers, ExecutorService executor) {
registerScheduler();
QueueTransferTask scheduler = new QueueTransferTask(connectionManager) {
@Override
protected RTopic<Long> getTopic() {
return new RedissonTopic<Long>(LongCodec.INSTANCE, commandExecutor, schedulerChannelName);
}
@Override
protected RFuture<Long> pushTaskAsync() {
return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
"local expiredTaskIds = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "if #expiredTaskIds > 0 then "
+ "redis.call('zrem', KEYS[2], unpack(expiredTaskIds));"
+ "local expiredTasks = redis.call('hmget', KEYS[3], unpack(expiredTaskIds));"
+ "redis.call('rpush', KEYS[1], unpack(expiredTasks));"
+ "end; "
// get startTime from scheduler queue head task
+ "local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); "
+ "if v[1] ~= nil then "
+ "return v[2]; "
+ "end "
+ "return nil;",
Arrays.<Object>asList(requestQueueName, schedulerQueueName, schedulerTasksName),
System.currentTimeMillis(), 100);
}
};
scheduler.start();
RemoteExecutorServiceImpl service =
new RemoteExecutorServiceImpl(commandExecutor, redisson, codec, requestQueueName);

@ -25,6 +25,11 @@ import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
/**
*
* @author Nikita Koksharov
*
*/
abstract class RedissonExpirable extends RedissonObject implements RExpirable {
RedissonExpirable(CommandAsyncExecutor connectionManager, String name) {

@ -48,11 +48,11 @@ public class RedissonFairLock extends RedissonLock implements RLock {
}
String getThreadsQueueName() {
return "redisson_lock_queue:{" + getName() + "}";
return prefixName("redisson_lock_queue", getName());
}
String getThreadElementName(long threadId) {
return "redisson_lock_thread:{" + getName() + "}:" + getLockName(threadId);
String getTimeoutSetName() {
return prefixName("redisson_lock_timeout", getName());
}
@Override
@ -72,11 +72,20 @@ public class RedissonFairLock extends RedissonLock implements RLock {
getChannelName() + ":" + getLockName(threadId), commandExecutor.getConnectionManager());
}
@Override
protected RFuture<Void> acquireFailedAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID,
"redis.call('zrem', KEYS[2], ARGV[1]); " +
"redis.call('lrem', KEYS[1], 0, ARGV[1]); ",
Arrays.<Object>asList(getThreadsQueueName(), getTimeoutSetName()), getLockName(threadId));
}
@Override
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
long threadWaitTime = 5000;
long currentTime = System.currentTimeMillis();
if (command == RedisCommands.EVAL_NULL_BOOLEAN) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// remove stale threads
@ -85,7 +94,9 @@ public class RedissonFairLock extends RedissonLock implements RLock {
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "if redis.call('exists', 'redisson_lock_thread:{' .. KEYS[1] .. '}:' .. firstThreadId2) == 0 then "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[3]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
@ -96,7 +107,7 @@ public class RedissonFairLock extends RedissonLock implements RLock {
"if (redis.call('exists', KEYS[1]) == 0) and ((redis.call('exists', KEYS[2]) == 0) "
+ "or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then " +
"redis.call('lpop', KEYS[2]); " +
"redis.call('del', KEYS[3]); " +
"redis.call('zrem', KEYS[3], ARGV[2]); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
@ -107,7 +118,8 @@ public class RedissonFairLock extends RedissonLock implements RLock {
"return nil; " +
"end; " +
"return 1;",
Arrays.<Object>asList(getName(), getThreadsQueueName(), getThreadElementName(threadId)), internalLockLeaseTime, getLockName(threadId));
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName()),
internalLockLeaseTime, getLockName(threadId), currentTime);
}
if (command == RedisCommands.EVAL_LONG) {
@ -118,17 +130,19 @@ public class RedissonFairLock extends RedissonLock implements RLock {
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "if redis.call('exists', 'redisson_lock_thread:{' .. KEYS[1] .. '}:' .. firstThreadId2) == 0 then "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[4]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
+ "end; "
+ "end;"
+
"if (redis.call('exists', KEYS[1]) == 0) and ((redis.call('exists', KEYS[2]) == 0) "
+ "if (redis.call('exists', KEYS[1]) == 0) and ((redis.call('exists', KEYS[2]) == 0) "
+ "or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then " +
"redis.call('lpop', KEYS[2]); " +
"redis.call('del', KEYS[3]); " +
"redis.call('zrem', KEYS[3], ARGV[2]); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
@ -138,19 +152,22 @@ public class RedissonFairLock extends RedissonLock implements RLock {
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"local firstThreadId = redis.call('lindex', KEYS[2], 0)" +
"local ttl = redis.call('pttl', KEYS[1]); " +
"local firstThreadId = redis.call('lindex', KEYS[2], 0); " +
"local ttl; " +
"if firstThreadId ~= false and firstThreadId ~= ARGV[2] then " +
"ttl = redis.call('pttl', 'redisson_lock_thread:{' .. KEYS[1] .. '}:' .. firstThreadId);" +
"ttl = tonumber(redis.call('zscore', KEYS[3], firstThreadId)) - tonumber(ARGV[4]);" +
"else "
+ "ttl = redis.call('pttl', KEYS[1]);" +
"end; " +
"if redis.call('exists', KEYS[3]) == 0 then " +
"local timeout = ttl + tonumber(ARGV[3]);" +
"if redis.call('zadd', KEYS[3], timeout, ARGV[2]) == 1 then " +
"redis.call('rpush', KEYS[2], ARGV[2]);" +
"redis.call('set', KEYS[3], 1);" +
"end; " +
"redis.call('pexpire', KEYS[3], ttl + tonumber(ARGV[3]));" +
"return ttl;",
Arrays.<Object>asList(getName(), getThreadsQueueName(), getThreadElementName(threadId)),
internalLockLeaseTime, getLockName(threadId), threadWaitTime);
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName()),
internalLockLeaseTime, getLockName(threadId), currentTime + threadWaitTime, currentTime);
}
throw new IllegalArgumentException();
@ -158,25 +175,39 @@ public class RedissonFairLock extends RedissonLock implements RLock {
@Override
public void unlock() {
Boolean opStatus = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
Boolean opStatus = get(unlockInnerAsync(Thread.currentThread().getId()));
if (opStatus == null) {
throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + Thread.currentThread().getId());
}
if (opStatus) {
cancelExpirationRenewal();
}
}
@Override
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// remove stale threads
"while true do "
+ "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "if redis.call('exists', 'redisson_lock_thread:{' .. KEYS[1] .. '}:' .. firstThreadId2) == 0 then "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[4]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
+ "end; "
+ "end;"
+
"if (redis.call('exists', KEYS[1]) == 0) then " +
"local nextThreadId = redis.call('lindex', KEYS[3], 0); " +
+ "if (redis.call('exists', KEYS[1]) == 0) then " +
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[2] .. ':' .. nextThreadId, ARGV[1]); " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1; " +
"end;" +
@ -189,22 +220,15 @@ public class RedissonFairLock extends RedissonLock implements RLock {
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"local nextThreadId = redis.call('lindex', KEYS[3], 0); " +
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[2] .. ':' .. nextThreadId, ARGV[1]); " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<Object>asList(getName(), getChannelName(), getThreadsQueueName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId()));
if (opStatus == null) {
throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + Thread.currentThread().getId());
}
if (opStatus) {
cancelExpirationRenewal();
}
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName(), getChannelName()),
LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId), System.currentTimeMillis());
}
@Override
@ -212,6 +236,11 @@ public class RedissonFairLock extends RedissonLock implements RLock {
throw new UnsupportedOperationException();
}
@Override
public RFuture<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getThreadsQueueName(), getTimeoutSetName());
}
@Override
public RFuture<Boolean> forceUnlockAsync() {
cancelExpirationRenewal();
@ -222,7 +251,9 @@ public class RedissonFairLock extends RedissonLock implements RLock {
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "if redis.call('exists', 'redisson_lock_thread:{' .. KEYS[1] .. '}:' .. firstThreadId2) == 0 then "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[2]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
@ -231,14 +262,15 @@ public class RedissonFairLock extends RedissonLock implements RLock {
+
"if (redis.call('del', KEYS[1]) == 1) then " +
"local nextThreadId = redis.call('lindex', KEYS[3], 0); " +
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[2] .. ':' .. nextThreadId, ARGV[1]); " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1 " +
"end " +
"return 1; " +
"end; " +
"return 0;",
Arrays.<Object>asList(getName(), getChannelName(), getThreadsQueueName()), LockPubSub.unlockMessage);
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName(), getChannelName()),
LockPubSub.unlockMessage, System.currentTimeMillis());
}
}

@ -17,6 +17,7 @@ package org.redisson;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -32,9 +33,11 @@ import org.redisson.api.RFuture;
import org.redisson.api.RKeys;
import org.redisson.api.RType;
import org.redisson.client.RedisException;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandBatchService;
import org.redisson.connection.MasterSlaveEntry;
@ -44,6 +47,11 @@ import org.redisson.misc.RPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonKeys implements RKeys {
private final CommandAsyncExecutor commandExecutor;
@ -98,12 +106,12 @@ public class RedissonKeys implements RKeys {
return getKeysByPattern(null);
}
private ListScanResult<String> scanIterator(InetSocketAddress client, MasterSlaveEntry entry, long startPos, String pattern, int count) {
private ListScanResult<ScanObjectEntry> scanIterator(InetSocketAddress client, MasterSlaveEntry entry, long startPos, String pattern, int count) {
if (pattern == null) {
RFuture<ListScanResult<String>> f = commandExecutor.readAsync(client, entry, StringCodec.INSTANCE, RedisCommands.SCAN, startPos, "COUNT", count);
RFuture<ListScanResult<ScanObjectEntry>> f = commandExecutor.readAsync(client, entry, new ScanCodec(StringCodec.INSTANCE), RedisCommands.SCAN, startPos, "COUNT", count);
return commandExecutor.get(f);
}
RFuture<ListScanResult<String>> f = commandExecutor.readAsync(client, entry, StringCodec.INSTANCE, RedisCommands.SCAN, startPos, "MATCH", pattern, "COUNT", count);
RFuture<ListScanResult<ScanObjectEntry>> f = commandExecutor.readAsync(client, entry, new ScanCodec(StringCodec.INSTANCE), RedisCommands.SCAN, startPos, "MATCH", pattern, "COUNT", count);
return commandExecutor.get(f);
}
@ -111,7 +119,7 @@ public class RedissonKeys implements RKeys {
return new RedissonBaseIterator<String>() {
@Override
ListScanResult<String> iterator(InetSocketAddress client, long nextIterPos) {
ListScanResult<ScanObjectEntry> iterator(InetSocketAddress client, long nextIterPos) {
return RedissonKeys.this.scanIterator(client, entry, nextIterPos, pattern, count);
}
@ -123,6 +131,18 @@ public class RedissonKeys implements RKeys {
};
}
@Override
public Long isExists(String... names) {
return commandExecutor.get(isExistsAsync(names));
}
@Override
public RFuture<Long> isExistsAsync(String... names) {
Object[] params = Arrays.copyOf(names, names.length, Object[].class);
return commandExecutor.readAsync((String)null, null, RedisCommands.EXISTS_LONG, params);
}
@Override
public String randomKey() {
return commandExecutor.get(randomKeyAsync());

@ -34,6 +34,7 @@ import java.util.NoSuchElementException;
import org.redisson.api.RFuture;
import org.redisson.api.RList;
import org.redisson.api.SortOrder;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
@ -446,7 +447,7 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
}
@Override
public RFuture<Void> trimAsync(long fromIndex, long toIndex) {
public RFuture<Void> trimAsync(int fromIndex, int toIndex) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LTRIM, getName(), fromIndex, toIndex);
}
@ -619,13 +620,177 @@ public class RedissonList<V> extends RedissonExpirable implements RList<V> {
}
@Override
public Integer addAfter(V elementToFind, V element) {
public int addAfter(V elementToFind, V element) {
return get(addAfterAsync(elementToFind, element));
}
@Override
public Integer addBefore(V elementToFind, V element) {
public int addBefore(V elementToFind, V element) {
return get(addBeforeAsync(elementToFind, element));
}
@Override
public List<V> readSort(SortOrder order) {
return get(readSortAsync(order));
}
@Override
public RFuture<List<V>> readSortAsync(SortOrder order) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_LIST, getName(), order);
}
@Override
public List<V> readSort(SortOrder order, int offset, int count) {
return get(readSortAsync(order, offset, count));
}
@Override
public RFuture<List<V>> readSortAsync(SortOrder order, int offset, int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_LIST, getName(), "LIMIT", offset, count, order);
}
@Override
public List<V> readSort(String byPattern, SortOrder order) {
return get(readSortAsync(byPattern, order));
}
@Override
public RFuture<List<V>> readSortAsync(String byPattern, SortOrder order) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_LIST, getName(), "BY", byPattern, order);
}
@Override
public List<V> readSort(String byPattern, SortOrder order, int offset, int count) {
return get(readSortAsync(byPattern, order, offset, count));
}
@Override
public RFuture<List<V>> readSortAsync(String byPattern, SortOrder order, int offset, int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_LIST, getName(), "BY", byPattern, "LIMIT", offset, count, order);
}
@Override
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order) {
return (Collection<T>)get(readSortAsync(byPattern, getPatterns, order));
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order) {
return readSortAsync(byPattern, getPatterns, order, -1, -1);
}
@Override
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
return (Collection<T>)get(readSortAsync(byPattern, getPatterns, order, offset, count));
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
List<Object> params = new ArrayList<Object>();
params.add(getName());
if (byPattern != null) {
params.add("BY");
params.add(byPattern);
}
if (offset != -1 && count != -1) {
params.add("LIMIT");
}
if (offset != -1) {
params.add(offset);
}
if (count != -1) {
params.add(count);
}
for (String pattern : getPatterns) {
params.add("GET");
params.add(pattern);
}
params.add(order);
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_LIST, params.toArray());
}
@Override
public int sortTo(String destName, SortOrder order) {
return get(sortToAsync(destName, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, SortOrder order) {
return sortToAsync(destName, null, Collections.<String>emptyList(), order, -1, -1);
}
@Override
public int sortTo(String destName, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, order, offset, count));
}
@Override
public RFuture<Integer> sortToAsync(String destName, SortOrder order, int offset, int count) {
return sortToAsync(destName, null, Collections.<String>emptyList(), order, offset, count);
}
@Override
public int sortTo(String destName, String byPattern, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, byPattern, order, offset, count));
}
@Override
public int sortTo(String destName, String byPattern, SortOrder order) {
return get(sortToAsync(destName, byPattern, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order) {
return sortToAsync(destName, byPattern, Collections.<String>emptyList(), order, -1, -1);
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order, int offset, int count) {
return sortToAsync(destName, byPattern, Collections.<String>emptyList(), order, offset, count);
}
@Override
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return get(sortToAsync(destName, byPattern, getPatterns, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return sortToAsync(destName, byPattern, getPatterns, order, -1, -1);
}
@Override
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, byPattern, getPatterns, order, offset, count));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
List<Object> params = new ArrayList<Object>();
params.add(getName());
if (byPattern != null) {
params.add("BY");
params.add(byPattern);
}
if (offset != -1 && count != -1) {
params.add("LIMIT");
}
if (offset != -1) {
params.add(offset);
}
if (count != -1) {
params.add(count);
}
for (String pattern : getPatterns) {
params.add("GET");
params.add(pattern);
}
params.add(order);
params.add("STORE");
params.add(destName);
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SORT_TO, params.toArray());
}
}

@ -23,11 +23,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RList;
import org.redisson.api.RListMultimap;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
@ -44,12 +46,12 @@ public class RedissonListMultimap<K, V> extends RedissonMultimap<K, V> implement
private static final RedisStrictCommand<Boolean> LLEN_VALUE = new RedisStrictCommand<Boolean>("LLEN", new BooleanAmountReplayConvertor());
RedissonListMultimap(CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
RedissonListMultimap(UUID id, CommandAsyncExecutor connectionManager, String name) {
super(id, connectionManager, name);
}
RedissonListMultimap(Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(codec, connectionManager, name);
RedissonListMultimap(UUID id, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(id, codec, connectionManager, name);
}
@Override

@ -17,6 +17,7 @@ package org.redisson;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
@ -25,6 +26,7 @@ import org.redisson.api.RListMultimapCache;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.eviction.EvictionScheduler;
/**
* @author Nikita Koksharov
@ -36,14 +38,14 @@ public class RedissonListMultimapCache<K, V> extends RedissonListMultimap<K, V>
private final RedissonMultimapCache<K> baseCache;
RedissonListMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
RedissonListMultimapCache(UUID id, EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(id, 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);
RedissonListMultimapCache(UUID id, EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(id, codec, connectionManager, name);
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());
baseCache = new RedissonMultimapCache<K>(connectionManager, name, codec, getTimeoutSetName());
}

@ -15,11 +15,6 @@
*/
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;
@ -34,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RList;
import org.redisson.api.SortOrder;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
@ -64,6 +60,7 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
public static final RedisCommand<Boolean> EVAL_BOOLEAN_ARGS2 = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS);
private final RList<V> list;
private final Object key;
private final String timeoutSetName;
@ -71,6 +68,7 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
super(codec, commandExecutor, name);
this.timeoutSetName = timeoutSetName;
this.key = key;
this.list = new RedissonList<V>(codec, commandExecutor, name);
}
@Override
@ -189,12 +187,12 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
@Override
public boolean add(V e) {
return get(addAsync(e));
return list.add(e);
}
@Override
public RFuture<Boolean> addAsync(V e) {
return commandExecutor.writeAsync(getName(), codec, RPUSH_BOOLEAN, getName(), e);
return list.addAsync(e);
}
@Override
@ -265,62 +263,22 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
@Override
public boolean addAll(Collection<? extends V> c) {
return get(addAllAsync(c));
return list.addAll(c);
}
@Override
public RFuture<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());
return list.addAllAsync(c);
}
@Override
public RFuture<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());
return list.addAllAsync(index, coll);
}
@Override
public boolean addAll(int index, Collection<? extends V> coll) {
return get(addAllAsync(index, coll));
return list.addAll(index, coll);
}
@Override
@ -451,27 +409,22 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
@Override
public V set(int index, V element) {
checkIndex(index);
return get(setAsync(index, element));
return list.set(index, element);
}
@Override
public RFuture<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);
return list.setAsync(index, element);
}
@Override
public void fastSet(int index, V element) {
get(fastSetAsync(index, element));
list.fastSet(index, element);
}
@Override
public RFuture<Void> fastSetAsync(int index, V element) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LSET, getName(), index, element);
return list.fastSetAsync(index, element);
}
@Override
@ -481,34 +434,22 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
@Override
public V remove(int index) {
return get(removeAsync(index));
return list.remove(index);
}
@Override
public RFuture<V> removeAsync(long index) {
if (index == 0) {
return commandExecutor.writeAsync(getName(), codec, LPOP, getName());
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_OBJECT,
"local v = redis.call('lindex', KEYS[1], ARGV[1]); " +
"redis.call('lset', KEYS[1], ARGV[1], 'DELETED_BY_REDISSON');" +
"redis.call('lrem', KEYS[1], 1, 'DELETED_BY_REDISSON');" +
"return v",
Collections.<Object>singletonList(getName()), index);
return list.removeAsync(index);
}
@Override
public void fastRemove(int index) {
get(fastRemoveAsync((long)index));
list.fastRemove(index);
}
@Override
public RFuture<Void> fastRemoveAsync(long index) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_VOID,
"redis.call('lset', KEYS[1], ARGV[1], 'DELETED_BY_REDISSON');" +
"redis.call('lrem', KEYS[1], 1, 'DELETED_BY_REDISSON');",
Collections.<Object>singletonList(getName()), index);
return list.fastRemoveAsync(index);
}
@Override
@ -576,12 +517,12 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
@Override
public void trim(int fromIndex, int toIndex) {
get(trimAsync(fromIndex, toIndex));
list.trim(fromIndex, toIndex);
}
@Override
public RFuture<Void> trimAsync(long fromIndex, long toIndex) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LTRIM, getName(), fromIndex, toIndex);
public RFuture<Void> trimAsync(int fromIndex, int toIndex) {
return list.trimAsync(fromIndex, toIndex);
}
@Override
@ -699,6 +640,7 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
return new RedissonSubList<V>(codec, commandExecutor, getName(), fromIndex, toIndex);
}
@Override
public String toString() {
Iterator<V> it = iterator();
if (! it.hasNext())
@ -744,22 +686,135 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
@Override
public RFuture<Integer> addAfterAsync(V elementToFind, V element) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "AFTER", elementToFind, element);
return list.addAfterAsync(elementToFind, element);
}
@Override
public RFuture<Integer> addBeforeAsync(V elementToFind, V element) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LINSERT, getName(), "BEFORE", elementToFind, element);
return list.addBeforeAsync(elementToFind, element);
}
@Override
public int addAfter(V elementToFind, V element) {
return list.addAfter(elementToFind, element);
}
@Override
public int addBefore(V elementToFind, V element) {
return list.addBefore(elementToFind, element);
}
@Override
public RFuture<List<V>> readSortAsync(SortOrder order) {
return list.readSortAsync(order);
}
@Override
public Integer addAfter(V elementToFind, V element) {
return get(addAfterAsync(elementToFind, element));
public List<V> readSort(SortOrder order) {
return list.readSort(order);
}
@Override
public Integer addBefore(V elementToFind, V element) {
return get(addBeforeAsync(elementToFind, element));
public RFuture<List<V>> readSortAsync(SortOrder order, int offset, int count) {
return list.readSortAsync(order, offset, count);
}
@Override
public List<V> readSort(SortOrder order, int offset, int count) {
return list.readSort(order, offset, count);
}
@Override
public List<V> readSort(String byPattern, SortOrder order, int offset, int count) {
return list.readSort(byPattern, order, offset, count);
}
@Override
public RFuture<List<V>> readSortAsync(String byPattern, SortOrder order, int offset, int count) {
return list.readSortAsync(byPattern, order, offset, count);
}
@Override
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
return list.readSort(byPattern, getPatterns, order, offset, count);
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order, int offset,
int count) {
return list.readSortAsync(byPattern, getPatterns, order, offset, count);
}
@Override
public int sortTo(String destName, SortOrder order) {
return list.sortTo(destName, order);
}
@Override
public RFuture<Integer> sortToAsync(String destName, SortOrder order) {
return list.sortToAsync(destName, order);
}
public List<V> readSort(String byPattern, SortOrder order) {
return list.readSort(byPattern, order);
}
public RFuture<List<V>> readSortAsync(String byPattern, SortOrder order) {
return list.readSortAsync(byPattern, order);
}
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order) {
return list.readSort(byPattern, getPatterns, order);
}
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order) {
return list.readSortAsync(byPattern, getPatterns, order);
}
public int sortTo(String destName, SortOrder order, int offset, int count) {
return list.sortTo(destName, order, offset, count);
}
public int sortTo(String destName, String byPattern, SortOrder order) {
return list.sortTo(destName, byPattern, order);
}
public RFuture<Integer> sortToAsync(String destName, SortOrder order, int offset, int count) {
return list.sortToAsync(destName, order, offset, count);
}
public int sortTo(String destName, String byPattern, SortOrder order, int offset, int count) {
return list.sortTo(destName, byPattern, order, offset, count);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order) {
return list.sortToAsync(destName, byPattern, order);
}
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return list.sortTo(destName, byPattern, getPatterns, order);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order, int offset,
int count) {
return list.sortToAsync(destName, byPattern, order, offset, count);
}
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset,
int count) {
return list.sortTo(destName, byPattern, getPatterns, order, offset, count);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns,
SortOrder order) {
return list.sortToAsync(destName, byPattern, getPatterns, order);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns,
SortOrder order, int offset, int count) {
return list.sortToAsync(destName, byPattern, getPatterns, order, offset, count);
}
}

@ -110,23 +110,6 @@ public class RedissonLiveObjectService implements RLiveObjectService {
return ClassUtils.getField(proxied, "liveObjectLiveMap");
}
@Override
public <T> T create(Class<T> entityClass) {
validateClass(entityClass);
try {
Class<? extends T> proxyClass = getProxyClass(entityClass);
Object id = generateId(entityClass);
T proxied = instantiateLiveObject(proxyClass, id);
if (!getMap(proxied).fastPut("redisson_live_object", "1")) {
throw new IllegalArgumentException("Object already exists");
}
return proxied;
} catch (Exception ex) {
unregisterClass(entityClass);
throw ex instanceof RuntimeException ? (RuntimeException) ex : new RuntimeException(ex);
}
}
private <T> Object generateId(Class<T> entityClass) throws NoSuchFieldException {
String idFieldName = getRIdFieldName(entityClass);
RId annotation = entityClass
@ -149,18 +132,6 @@ public class RedissonLiveObjectService implements RLiveObjectService {
}
}
@Override
public <T, K> T getOrCreate(Class<T> entityClass, K id) {
try {
T proxied = instantiateLiveObject(getProxyClass(entityClass), id);
getMap(proxied).fastPut("redisson_live_object", "1");
return proxied;
} catch (Exception ex) {
unregisterClass(entityClass);
throw ex instanceof RuntimeException ? (RuntimeException) ex : new RuntimeException(ex);
}
}
@Override
public <T> T attach(T detachedObject) {
validateDetached(detachedObject);
@ -588,7 +559,7 @@ public class RedissonLiveObjectService implements RLiveObjectService {
private <T, K> T instantiate(Class<T> cls, K id) throws Exception {
for (Constructor<?> constructor : cls.getDeclaredConstructors()) {
if (constructor.getParameterCount() == 0) {
if (constructor.getParameterTypes().length == 0) {
constructor.setAccessible(true);
return (T) constructor.newInstance();
}

@ -15,6 +15,7 @@
*/
package org.redisson;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.AbstractCollection;
import java.util.AbstractMap;
@ -29,13 +30,13 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.UUID;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.LocalCachedMapOptions.EvictionPolicy;
import org.redisson.api.RFuture;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.listener.MessageListener;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
@ -65,11 +66,11 @@ import io.netty.util.internal.ThreadLocalRandom;
*/
public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements RLocalCachedMap<K, V> {
public static class LocalCachedMapClear {
public static class LocalCachedMapClear implements Serializable {
}
public static class LocalCachedMapInvalidate {
public static class LocalCachedMapInvalidate implements Serializable {
private byte[] excludedId;
private byte[] keyHash;
@ -93,7 +94,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
public static class CacheKey {
public static class CacheKey implements Serializable {
private final byte[] keyHash;
@ -135,7 +136,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
public static class CacheValue {
public static class CacheValue implements Serializable {
private final Object key;
private final Object value;
@ -183,24 +184,24 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
private static final RedisCommand<Object> EVAL_PUT = new RedisCommand<Object>("EVAL", -1, ValueType.OBJECT, ValueType.MAP_VALUE);
private static final RedisCommand<Object> EVAL_REMOVE = new RedisCommand<Object>("EVAL", -1, ValueType.OBJECT, ValueType.MAP_VALUE);
private byte[] id;
private byte[] instanceId;
private RTopic<Object> invalidationTopic;
private Cache<CacheKey, CacheValue> cache;
private int invalidateEntryOnChange;
private int invalidationListenerId;
protected RedissonLocalCachedMap(RedissonClient redisson, CommandAsyncExecutor commandExecutor, String name, LocalCachedMapOptions options) {
super(commandExecutor, name);
init(redisson, name, options);
protected RedissonLocalCachedMap(UUID id, CommandAsyncExecutor commandExecutor, String name, LocalCachedMapOptions options) {
super(id, commandExecutor, name);
init(id, name, options);
}
protected RedissonLocalCachedMap(RedissonClient redisson, Codec codec, CommandAsyncExecutor connectionManager, String name, LocalCachedMapOptions options) {
super(codec, connectionManager, name);
init(redisson, name, options);
protected RedissonLocalCachedMap(UUID id, Codec codec, CommandAsyncExecutor connectionManager, String name, LocalCachedMapOptions options) {
super(id, codec, connectionManager, name);
init(id, name, options);
}
private void init(RedissonClient redisson, String name, LocalCachedMapOptions options) {
id = generateId();
private void init(UUID id, String name, LocalCachedMapOptions options) {
instanceId = generateId();
if (options.isInvalidateEntryOnChange()) {
invalidateEntryOnChange = 1;
@ -215,7 +216,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
cache = new LFUCacheMap<CacheKey, CacheValue>(options.getCacheSize(), options.getTimeToLiveInMillis(), options.getMaxIdleInMillis());
}
invalidationTopic = redisson.getTopic(name + ":topic");
invalidationTopic = new RedissonTopic<Object>(commandExecutor, suffixName(name, "topic"));
if (options.isInvalidateEntryOnChange()) {
invalidationListenerId = invalidationTopic.addListener(new MessageListener<Object>() {
@Override
@ -225,7 +226,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
if (msg instanceof LocalCachedMapInvalidate) {
LocalCachedMapInvalidate invalidateMsg = (LocalCachedMapInvalidate)msg;
if (!Arrays.equals(invalidateMsg.getExcludedId(), id)) {
if (!Arrays.equals(invalidateMsg.getExcludedId(), instanceId)) {
CacheKey key = new CacheKey(invalidateMsg.getKeyHash());
cache.remove(key);
}
@ -309,7 +310,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
byte[] mapKey = encodeMapKey(key);
CacheKey cacheKey = toCacheKey(mapKey);
byte[] msg = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
CacheValue cacheValue = new CacheValue(key, value);
cache.put(cacheKey, cacheValue);
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT,
@ -334,7 +335,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
byte[] encodedKey = encodeMapKey(key);
byte[] encodedValue = encodeMapKey(value);
CacheKey cacheKey = toCacheKey(encodedKey);
byte[] msg = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
CacheValue cacheValue = new CacheValue(key, value);
cache.put(cacheKey, cacheValue);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
@ -364,7 +365,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
byte[] keyEncoded = encodeMapKey(key);
CacheKey cacheKey = toCacheKey(keyEncoded);
byte[] msgEncoded = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
cache.remove(cacheKey);
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REMOVE,
"local v = redis.call('hget', KEYS[1], ARGV[1]); "
@ -391,7 +392,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
CacheKey cacheKey = toCacheKey(keyEncoded);
cache.remove(cacheKey);
if (invalidateEntryOnChange == 1) {
byte[] msgEncoded = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
params.add(msgEncoded);
} else {
params.add(null);
@ -414,24 +415,6 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
Map<CacheKey, CacheValue> cacheMap = new HashMap<CacheKey, CacheValue>(m.size());
for (java.util.Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
CacheKey cacheKey = toCacheKey(entry.getKey());
CacheValue cacheValue = new CacheValue(entry.getKey(), entry.getValue());
cacheMap.put(cacheKey, cacheValue);
}
cache.putAll(cacheMap);
super.putAll(m);
if (invalidateEntryOnChange == 1) {
for (CacheKey cacheKey : cacheMap.keySet()) {
invalidationTopic.publish(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
}
}
}
@Override
public RFuture<Boolean> deleteAsync() {
cache.clear();
@ -748,11 +731,12 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
params.add(mapKey);
params.add(mapValue);
CacheKey cacheKey = toCacheKey(mapKey);
byte[] msgEncoded = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
msgs.add(msgEncoded);
}
params.addAll(msgs);
final RPromise<Void> result = newPromise();
RFuture<Void> future = commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_VOID,
"redis.call('hmset', KEYS[1], unpack(ARGV, 3, tonumber(ARGV[2]) + 2));"
+ "if ARGV[1] == '1' then "
@ -770,16 +754,17 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
}
cacheMap(map);
result.trySuccess(null);
}
});
return future;
return result;
}
@Override
public RFuture<V> addAndGetAsync(final K key, Number value) {
final byte[] keyState = encodeMapKey(key);
CacheKey cacheKey = toCacheKey(keyState);
byte[] msg = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
RFuture<V> future = commandExecutor.evalWriteAsync(getName(), StringCodec.INSTANCE, new RedisCommand<Object>("EVAL", new NumberConvertor(value.getClass())),
"local result = redis.call('HINCRBYFLOAT', KEYS[1], ARGV[1], ARGV[2]); "
@ -928,7 +913,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
final byte[] keyState = encodeMapKey(key);
byte[] valueState = encodeMapValue(value);
final CacheKey cacheKey = toCacheKey(keyState);
byte[] msg = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
RFuture<V> future = commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE,
"if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
@ -967,7 +952,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
byte[] oldValueState = encodeMapValue(oldValue);
byte[] newValueState = encodeMapValue(newValue);
final CacheKey cacheKey = toCacheKey(keyState);
byte[] msg = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(key), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "
@ -1003,7 +988,7 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
final byte[] keyState = encodeMapKey(key);
byte[] valueState = encodeMapValue(value);
final CacheKey cacheKey = toCacheKey(keyState);
byte[] msg = encode(new LocalCachedMapInvalidate(id, cacheKey.getKeyHash()));
byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash()));
RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(key), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "

@ -77,10 +77,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
}
String getChannelName() {
if (getName().contains("{")) {
return "redisson_lock__channel:" + getName();
}
return "redisson_lock__channel__{" + getName() + "}";
return prefixName("redisson_lock__channel", getName());
}
String getLockName(long threadId) {
@ -113,19 +110,19 @@ public class RedissonLock extends RedissonExpirable implements RLock {
@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
Long ttl = tryAcquire(leaseTime, unit);
long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return;
}
long threadId = Thread.currentThread().getId();
RFuture<RedissonLockEntry> future = subscribe(threadId);
get(future);
commandExecutor.syncSubscription(future);
try {
while (true) {
ttl = tryAcquire(leaseTime, unit);
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
break;
@ -144,8 +141,8 @@ public class RedissonLock extends RedissonExpirable implements RLock {
// get(lockAsync(leaseTime, unit));
}
private Long tryAcquire(long leaseTime, TimeUnit unit) {
return get(tryAcquireAsync(leaseTime, unit, Thread.currentThread().getId()));
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
return get(tryAcquireAsync(leaseTime, unit, threadId));
}
private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, final long threadId) {
@ -262,12 +259,20 @@ public class RedissonLock extends RedissonExpirable implements RLock {
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
private void acquireFailed(long threadId) {
get(acquireFailedAsync(threadId));
}
protected RFuture<Void> acquireFailedAsync(long threadId) {
return newSucceededFuture(null);
}
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
final long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(leaseTime, unit);
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return true;
@ -275,6 +280,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
time -= (System.currentTimeMillis() - current);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
@ -291,18 +297,20 @@ public class RedissonLock extends RedissonExpirable implements RLock {
}
});
}
acquireFailed(threadId);
return false;
}
try {
time -= (System.currentTimeMillis() - current);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
while (true) {
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(leaseTime, unit);
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return true;
@ -310,6 +318,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
time -= (System.currentTimeMillis() - currentTime);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
@ -323,6 +332,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
time -= (System.currentTimeMillis() - currentTime);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
}
@ -351,25 +361,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
@Override
public void unlock() {
Boolean opStatus = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId()));
Boolean opStatus = get(unlockInnerAsync(Thread.currentThread().getId()));
if (opStatus == null) {
throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + Thread.currentThread().getId());
@ -418,6 +410,11 @@ public class RedissonLock extends RedissonExpirable implements RLock {
return isExists();
}
@Override
public RFuture<Boolean> isExistsAsync() {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.EXISTS, getName());
}
@Override
public boolean isHeldByCurrentThread() {
return commandExecutor.write(getName(), LongCodec.INSTANCE, RedisCommands.HEXISTS, getName(), getLockName(Thread.currentThread().getId()));
@ -442,27 +439,32 @@ public class RedissonLock extends RedissonExpirable implements RLock {
return unlockAsync(threadId);
}
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
}
public RFuture<Void> unlockAsync(final long threadId) {
final RPromise<Void> result = newPromise();
RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
RFuture<Boolean> future = unlockInnerAsync(threadId);
future.addListener(new FutureListener<Boolean>() {
@Override
@ -647,7 +649,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
time.addAndGet(-elapsed);
if (time.get() <= 0) {
result.trySuccess(false);
trySuccessFalse(currentThreadId, result);
return;
}
@ -669,12 +671,6 @@ public class RedissonLock extends RedissonExpirable implements RLock {
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
if (time.get() <= 0) {
unsubscribe(subscribeFuture, currentThreadId);
result.trySuccess(false);
return;
}
tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
}
});
@ -684,19 +680,33 @@ public class RedissonLock extends RedissonExpirable implements RLock {
public void run(Timeout timeout) throws Exception {
if (!subscribeFuture.isDone()) {
subscribeFuture.cancel(false);
result.trySuccess(false);
trySuccessFalse(currentThreadId, result);
}
}
}, time.get(), TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
});
return result;
}
private void trySuccessFalse(final long currentThreadId, final RPromise<Boolean> result) {
acquireFailedAsync(currentThreadId).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
result.trySuccess(false);
} else {
result.tryFailure(future.cause());
}
}
});
}
private void tryLockAsync(final AtomicLong time, final long leaseTime, final TimeUnit unit,
final RFuture<RedissonLockEntry> subscribeFuture, final RPromise<Boolean> result, final long currentThreadId) {
if (result.isDone()) {
@ -706,7 +716,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
if (time.get() <= 0) {
unsubscribe(subscribeFuture, currentThreadId);
result.trySuccess(false);
trySuccessFalse(currentThreadId, result);
return;
}
@ -736,7 +746,7 @@ public class RedissonLock extends RedissonExpirable implements RLock {
if (time.get() <= 0) {
unsubscribe(subscribeFuture, currentThreadId);
result.trySuccess(false);
trySuccessFalse(currentThreadId, result);
return;
}
@ -793,3 +803,4 @@ public class RedissonLock extends RedissonExpirable implements RLock {
}
;

@ -28,11 +28,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.codec.MapScanCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
@ -42,7 +44,9 @@ import org.redisson.client.protocol.convertor.NumberConvertor;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandExecutor;
import org.redisson.connection.decoder.MapGetAllDecoder;
import org.redisson.misc.Hash;
/**
* Distributed and concurrent implementation of {@link java.util.concurrent.ConcurrentMap}
@ -61,12 +65,31 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
static final RedisCommand<Boolean> EVAL_REMOVE_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.MAP);
static final RedisCommand<Object> EVAL_PUT = EVAL_REPLACE;
protected RedissonMap(CommandAsyncExecutor commandExecutor, String name) {
private final UUID id;
protected RedissonMap(UUID id, CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.id = id;
}
public RedissonMap(Codec codec, CommandAsyncExecutor commandExecutor, String name) {
public RedissonMap(UUID id, Codec codec, CommandAsyncExecutor commandExecutor, String name) {
super(codec, commandExecutor, name);
this.id = id;
}
@Override
public RLock getLock(K key) {
String lockName = getLockName(key);
return new RedissonLock((CommandExecutor)commandExecutor, lockName, id);
}
private String getLockName(Object key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
return "{" + getName() + "}:" + Hash.hashToBase64(keyState) + ":key";
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@Override
@ -86,6 +109,10 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<Integer> valueSizeAsync(K key) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
return commandExecutor.readAsync(getName(), codec, RedisCommands.HSTRLEN, getName(key), key);
}
@ -101,6 +128,10 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<Boolean> containsKeyAsync(Object key) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
return commandExecutor.readAsync(getName(key), codec, RedisCommands.HEXISTS, getName(key), key);
}
@ -111,6 +142,10 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<Boolean> containsValueAsync(Object value) {
if (value == null) {
throw new NullPointerException("map value can't be null");
}
return commandExecutor.evalReadAsync(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4),
"local s = redis.call('hvals', KEYS[1]);" +
"for i = 1, #s, 1 do "
@ -232,6 +267,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<V> putIfAbsentAsync(K key, V value) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (value == null) {
throw new NullPointerException("map value can't be null");
}
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT,
"if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then "
+ "return nil "
@ -248,6 +290,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<Boolean> fastPutIfAbsentAsync(K key, V value) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (value == null) {
throw new NullPointerException("map value can't be null");
}
return commandExecutor.writeAsync(getName(key), codec, RedisCommands.HSETNX, getName(key), key, value);
}
@ -258,6 +307,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<Boolean> removeAsync(Object key, Object value) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (value == null) {
throw new NullPointerException("map value can't be null");
}
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE_VALUE,
"if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "
+ "return redis.call('hdel', KEYS[1], ARGV[1]) "
@ -274,6 +330,17 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<Boolean> replaceAsync(K key, V oldValue, V newValue) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (oldValue == null) {
throw new NullPointerException("map oldValue can't be null");
}
if (newValue == null) {
throw new NullPointerException("map newValue can't be null");
}
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE_VALUE,
"if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[3]); "
@ -291,6 +358,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<V> replaceAsync(K key, V value) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (value == null) {
throw new NullPointerException("map value can't be null");
}
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE,
"if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
+ "local v = redis.call('hget', KEYS[1], ARGV[1]); "
@ -304,6 +378,10 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<V> getAsync(K key) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
return commandExecutor.readAsync(getName(key), codec, RedisCommands.HGET, getName(key), key);
}
@ -313,6 +391,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<V> putAsync(K key, V value) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (value == null) {
throw new NullPointerException("map value can't be null");
}
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT,
"local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
@ -323,6 +408,10 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<V> removeAsync(K key) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE,
"local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "redis.call('hdel', KEYS[1], ARGV[1]); "
@ -332,6 +421,13 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<Boolean> fastPutAsync(K key, V value) {
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (value == null) {
throw new NullPointerException("map value can't be null");
}
return commandExecutor.writeAsync(getName(key), codec, RedisCommands.HSET, getName(key), key, value);
}
@ -359,7 +455,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, InetSocketAddress client, long startPos) {
RFuture<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f
= commandExecutor.readAsync(client, name, new ScanCodec(codec), RedisCommands.HSCAN, name, startPos);
= commandExecutor.readAsync(client, name, new MapScanCodec(codec), RedisCommands.HSCAN, name, startPos);
return get(f);
}
@ -370,14 +466,17 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
@Override
public RFuture<V> addAndGetAsync(K key, Number value) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
return commandExecutor.writeAsync(getName(key), StringCodec.INSTANCE,
new RedisCommand<Object>("HINCRBYFLOAT", new NumberConvertor(value.getClass())),
getName(key), keyState, new BigDecimal(value.toString()).toPlainString());
} catch (IOException e) {
throw new IllegalArgumentException(e);
if (key == null) {
throw new NullPointerException("map key can't be null");
}
if (value == null) {
throw new NullPointerException("map value can't be null");
}
byte[] keyState = encodeMapKey(key);
return commandExecutor.writeAsync(getName(key), StringCodec.INSTANCE,
new RedisCommand<Object>("HINCRBYFLOAT", new NumberConvertor(value.getClass())),
getName(key), keyState, new BigDecimal(value.toString()).toPlainString());
}
@Override
@ -426,7 +525,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
protected Iterator<K> keyIterator() {
return new RedissonMapIterator<K, V, K>(RedissonMap.this) {
@Override
K getValue(java.util.Map.Entry<ScanObjectEntry, ScanObjectEntry> entry) {
protected K getValue(java.util.Map.Entry<ScanObjectEntry, ScanObjectEntry> entry) {
return (K) entry.getKey().getObj();
}
};
@ -464,7 +563,7 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
protected Iterator<V> valueIterator() {
return new RedissonMapIterator<K, V, V>(RedissonMap.this) {
@Override
V getValue(java.util.Map.Entry<ScanObjectEntry, ScanObjectEntry> entry) {
protected V getValue(java.util.Map.Entry<ScanObjectEntry, ScanObjectEntry> entry) {
return (V) entry.getValue().getObj();
}
};

@ -23,13 +23,14 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RMapCache;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.codec.MapScanCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
@ -45,6 +46,7 @@ import org.redisson.client.protocol.decoder.ObjectMapDecoder;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.connection.decoder.MapGetAllDecoder;
import org.redisson.eviction.EvictionScheduler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
@ -58,7 +60,7 @@ import io.netty.util.concurrent.FutureListener;
* Thus entries are checked for TTL expiration during any key/value/entry read operation.
* If key/value/entry expired then it doesn't returns and clean task runs asynchronous.
* Clean task deletes removes 100 expired entries at once.
* In addition there is {@link org.redisson.EvictionScheduler}. This scheduler
* In addition there is {@link org.redisson.eviction.EvictionScheduler}. This scheduler
* deletes expired entries in time interval between 5 seconds to 2 hours.</p>
*
* <p>If eviction is not required then it's better to use {@link org.redisson.RedissonMap} object.</p>
@ -70,32 +72,41 @@ import io.netty.util.concurrent.FutureListener;
*/
public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCache<K, V> {
static final RedisCommand<Boolean> EVAL_PUT_IF_ABSENT = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.MAP);
static final RedisCommand<Boolean> EVAL_HSET = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.MAP);
static final RedisCommand<Object> EVAL_REPLACE = new RedisCommand<Object>("EVAL", 6, ValueType.MAP, ValueType.MAP_VALUE);
static final RedisCommand<Boolean> EVAL_REPLACE_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE, ValueType.MAP_VALUE));
private static final RedisCommand<Void> EVAL_HMSET = new RedisCommand<Void>("EVAL", new VoidReplayConvertor(), 4, ValueType.MAP);
static final RedisCommand<Void> EVAL_HMSET = new RedisCommand<Void>("EVAL", new VoidReplayConvertor(), 4, ValueType.MAP);
private static final RedisCommand<Object> EVAL_REMOVE = new RedisCommand<Object>("EVAL", 4, ValueType.MAP_KEY, ValueType.MAP_VALUE);
private static final RedisCommand<Boolean> EVAL_REMOVE_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5, ValueType.MAP);
private static final RedisCommand<Object> EVAL_PUT_TTL = new RedisCommand<Object>("EVAL", 9, ValueType.MAP, ValueType.MAP_VALUE);
private static final RedisCommand<Boolean> EVAL_FAST_PUT_TTL = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 9, ValueType.MAP, ValueType.MAP_VALUE);
private static final RedisCommand<Object> EVAL_GET_TTL = new RedisCommand<Object>("EVAL", 7, ValueType.MAP_KEY, ValueType.MAP_VALUE);
private static final RedisCommand<Boolean> EVAL_CONTAINS_KEY = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.MAP_KEY);
private static final RedisCommand<Boolean> EVAL_CONTAINS_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.MAP_VALUE);
private static final RedisCommand<Long> EVAL_FAST_REMOVE = new RedisCommand<Long>("EVAL", 5, ValueType.MAP_KEY);
static final RedisCommand<Boolean> EVAL_CONTAINS_VALUE = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 7, ValueType.MAP_VALUE);
static final RedisCommand<Long> EVAL_FAST_REMOVE = new RedisCommand<Long>("EVAL", 5, ValueType.MAP_KEY);
protected RedissonMapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
RedissonMapCache(UUID id, CommandAsyncExecutor commandExecutor, String name) {
super(id, commandExecutor, name);
}
RedissonMapCache(UUID id, Codec codec, CommandAsyncExecutor commandExecutor, String name) {
super(id, codec, commandExecutor, name);
}
public RedissonMapCache(UUID id, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) {
super(id, commandExecutor, name);
evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName());
}
public RedissonMapCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) {
super(codec, commandExecutor, name);
public RedissonMapCache(UUID id, Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) {
super(id, codec, commandExecutor, name);
evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName());
}
@Override
public RFuture<Boolean> containsKeyAsync(Object key) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_KEY,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_CONTAINS_KEY,
"local value = redis.call('hget', KEYS[1], ARGV[2]); " +
"local expireDate = 92233720368547758; " +
"if value ~= false then " +
@ -121,7 +132,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "return 1;" +
"end;" +
"return 0; ",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), key);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), key);
}
@Override
@ -256,7 +267,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta;
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT_TTL,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_TTL,
"if redis.call('hexists', KEYS[1], ARGV[4]) == 0 then "
+ "if tonumber(ARGV[1]) > 0 then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
@ -275,12 +286,12 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "local t, val = struct.unpack('dLc0', value); "
+ "return val; "
+ "end",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
}
@Override
public RFuture<Boolean> removeAsync(Object key, Object value) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REMOVE_VALUE,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE_VALUE,
"local value = redis.call('hget', KEYS[1], ARGV[1]); "
+ "if value == false then "
+ "return 0; "
@ -293,12 +304,12 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "else "
+ "return 0 "
+ "end",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), key, value);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), key, value);
}
@Override
public RFuture<V> getAsync(K key) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_GET_TTL,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_GET_TTL,
"local value = redis.call('hget', KEYS[1], ARGV[2]); "
+ "if value == false then "
+ "return nil; "
@ -324,7 +335,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "return nil; "
+ "end; "
+ "return val; ",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), key);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), key);
}
@Override
@ -334,7 +345,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
@Override
public RFuture<V> putAsync(K key, V value) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT,
"local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "local value = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); "
+ "redis.call('hset', KEYS[1], ARGV[1], value); "
@ -343,12 +354,12 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "end; "
+ "local t, val = struct.unpack('dLc0', v); "
+ "return val; ",
Collections.<Object>singletonList(getName()), key, value);
Collections.<Object>singletonList(getName(key)), key, value);
}
@Override
public RFuture<V> putIfAbsentAsync(K key, V value) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT,
"local value = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); "
+ "if redis.call('hsetnx', KEYS[1], ARGV[1], value) == 1 then "
+ "return nil "
@ -360,7 +371,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "local t, val = struct.unpack('dLc0', v); "
+ "return val; "
+ "end",
Collections.<Object>singletonList(getName()), key, value);
Collections.<Object>singletonList(getName(key)), key, value);
}
@Override
@ -410,7 +421,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta;
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_FAST_PUT_TTL,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_FAST_PUT_TTL,
"if tonumber(ARGV[1]) > 0 then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
+ "else "
@ -423,7 +434,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "end; "
+ "local value = struct.pack('dLc0', ARGV[3], string.len(ARGV[5]), ARGV[5]); " +
"return redis.call('hset', KEYS[1], ARGV[4], value); ",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
}
@Override
@ -468,7 +479,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta;
}
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT_TTL,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_TTL,
"local v = redis.call('hget', KEYS[1], ARGV[4]); "
+ "if tonumber(ARGV[1]) > 0 then "
+ "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); "
@ -487,20 +498,37 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "end; "
+ "local t, val = struct.unpack('dLc0', v); "
+ "return val",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
}
String getTimeoutSetNameByKey(Object key) {
return prefixName("redisson__timeout__set", getName(key));
}
String getTimeoutSetName(String name) {
return prefixName("redisson__timeout__set", name);
}
String getTimeoutSetName() {
return "redisson__timeout__set__{" + getName() + "}";
return prefixName("redisson__timeout__set", getName());
}
String getIdleSetNameByKey(Object key) {
return prefixName("redisson__idle__set", getName(key));
}
String getIdleSetName(String name) {
return prefixName("redisson__idle__set", name);
}
String getIdleSetName() {
return "redisson__idle__set__{" + getName() + "}";
return prefixName("redisson__idle__set", getName());
}
@Override
public RFuture<V> removeAsync(K key) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REMOVE,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE,
"local v = redis.call('hget', KEYS[1], ARGV[1]); "
+ "redis.call('zrem', KEYS[2], ARGV[1]); "
+ "redis.call('zrem', KEYS[3], ARGV[1]); "
@ -510,7 +538,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "return val; "
+ "end; "
+ "return v",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), key);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), key);
}
@Override
@ -528,9 +556,13 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
@Override
MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, InetSocketAddress client, long startPos) {
return get(scanIteratorAsync(name, client, startPos));
}
public RFuture<MapScanResult<ScanObjectEntry, ScanObjectEntry>> scanIteratorAsync(final String name, InetSocketAddress client, long startPos) {
RedisCommand<MapCacheScanResult<Object, Object>> EVAL_HSCAN = new RedisCommand<MapCacheScanResult<Object, Object>>("EVAL",
new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new ScanCodec(codec)), new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()), ValueType.MAP);
RFuture<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.evalReadAsync(client, getName(), codec, EVAL_HSCAN,
new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new MapScanCodec(codec)), new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()), ValueType.MAP);
RFuture<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.evalReadAsync(client, name, codec, EVAL_HSCAN,
"local result = {}; "
+ "local idleKeys = {}; "
+ "local res = redis.call('hscan', KEYS[1], ARGV[2]); "
@ -561,7 +593,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "end; "
+ "end; "
+ "end;"
+ "return {res[1], result, idleKeys};", Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), startPos);
+ "return {res[1], result, idleKeys};", Arrays.<Object>asList(name, getTimeoutSetName(name), getIdleSetName(name)), System.currentTimeMillis(), startPos);
f.addListener(new FutureListener<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>>() {
@Override
@ -577,7 +609,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
args.add(System.currentTimeMillis());
args.addAll(res.getIdleKeys());
commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand<Map<Object, Object>>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE),
commandExecutor.evalWriteAsync(name, codec, new RedisCommand<Map<Object, Object>>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE),
"local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter
+ "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); "
+ "for i = #map, 1, -1 do "
@ -598,34 +630,65 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "end; "
+ "end; "
+ "end; ",
Arrays.<Object>asList(getName(), getIdleSetName()), args.toArray());
Arrays.<Object>asList(name, getIdleSetName(name)), args.toArray());
}
}
});
return get(f);
return (RFuture<MapScanResult<ScanObjectEntry, ScanObjectEntry>>)(Object)f;
}
@Override
public RFuture<Boolean> fastPutAsync(K key, V value) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_HSET,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_HSET,
"local val = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); "
+ "return redis.call('hset', KEYS[1], ARGV[1], val); ",
Collections.<Object>singletonList(getName()), key, value);
Collections.<Object>singletonList(getName(key)), key, value);
}
@Override
public RFuture<Boolean> fastPutIfAbsentAsync(K key, V value) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_HSET,
"local val = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); "
+ "return redis.call('hsetnx', KEYS[1], ARGV[1], val); ",
Collections.<Object>singletonList(getName()), key, value);
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_IF_ABSENT,
"local value = redis.call('hget', KEYS[1], ARGV[2]); "
+ "if value == false then "
+ "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); "
+ "redis.call('hset', KEYS[1], ARGV[2], val); "
+ "return 1; "
+ "end; "
+ "local t, val = struct.unpack('dLc0', value); "
+ "local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if t ~= 0 then "
+ "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); "
+ "if expireIdle ~= false then "
+ "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
+ "local value = struct.pack('dLc0', t, string.len(val), val); "
+ "redis.call('hset', KEYS[1], ARGV[2], value); "
+ "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); "
+ "end; "
+ "expireDate = math.min(expireDate, tonumber(expireIdle)) "
+ "end; "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then "
+ "return 0; "
+ "end; "
+ "redis.call('zrem', KEYS[2], ARGV[2]); "
+ "redis.call('zrem', KEYS[3], ARGV[2]); "
+ "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); "
+ "redis.call('hset', KEYS[1], ARGV[2], val); "
+ "return 1; ",
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), key, value);
}
@Override
public RFuture<Boolean> replaceAsync(K key, V oldValue, V newValue) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REPLACE_VALUE,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE_VALUE,
"local v = redis.call('hget', KEYS[1], ARGV[2]); "
+ "if v == false then "
+ "return 0;"
@ -654,12 +717,12 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "return 1; "
+ "end; "
+ "return 0; ",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), key, oldValue, newValue);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), key, oldValue, newValue);
}
@Override
public RFuture<V> replaceAsync(K key, V value) {
return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REPLACE,
return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE,
"local v = redis.call('hget', KEYS[1], ARGV[2]); "
+ "if v ~= false then "
+ "local t, val = struct.unpack('dLc0', v); "
@ -672,7 +735,7 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
+ "else "
+ "return nil; "
+ "end",
Arrays.<Object>asList(getName(), getTimeoutSetName()), System.currentTimeMillis(), key, value);
Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key)), System.currentTimeMillis(), key, value);
}
@Override
@ -736,6 +799,40 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()));
}
@Override
public RFuture<Set<K>> readAllKeySetAsync() {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_KEY_SET,
"local s = redis.call('hgetall', KEYS[1]); "
+ "local result = {}; "
+ "for i, v in ipairs(s) do "
+ "if i % 2 == 0 then "
+ "local t, val = struct.unpack('dLc0', v); "
+ "local key = s[i-1];" +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], key); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if t ~= 0 then "
+ "local expireIdle = redis.call('zscore', KEYS[3], key); "
+ "if expireIdle ~= false then "
+ "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
+ "local value = struct.pack('dLc0', t, string.len(val), val); "
+ "redis.call('hset', KEYS[1], key, value); "
+ "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); "
+ "end; "
+ "expireDate = math.min(expireDate, tonumber(expireIdle)) "
+ "end; "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then "
+ "table.insert(result, key); "
+ "end; "
+ "end; "
+ "end;" +
"return result;",
Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis());
}
@Override
public RFuture<Set<java.util.Map.Entry<K, V>>> readAllEntrySetAsync() {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_ENTRY,

@ -15,6 +15,7 @@
*/
package org.redisson;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.AbstractCollection;
import java.util.AbstractSet;
@ -26,19 +27,22 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RMultimap;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.codec.MapScanCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandExecutor;
import org.redisson.misc.Hash;
/**
@ -49,12 +53,31 @@ import org.redisson.misc.Hash;
*/
public abstract class RedissonMultimap<K, V> extends RedissonExpirable implements RMultimap<K, V> {
RedissonMultimap(CommandAsyncExecutor connectionManager, String name) {
private final UUID id;
RedissonMultimap(UUID id, CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
this.id = id;
}
RedissonMultimap(Codec codec, CommandAsyncExecutor connectionManager, String name) {
RedissonMultimap(UUID id, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(codec, connectionManager, name);
this.id = id;
}
@Override
public RLock getLock(K key) {
String lockName = getLockName(key);
return new RedissonLock((CommandExecutor)commandExecutor, lockName, id);
}
private String getLockName(Object key) {
try {
byte[] keyState = codec.getMapKeyEncoder().encode(key);
return "{" + getName() + "}:" + Hash.hashToBase64(keyState) + ":key";
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
protected String hash(byte[] objectState) {
@ -249,7 +272,7 @@ public abstract class RedissonMultimap<K, V> extends RedissonExpirable implement
MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(InetSocketAddress client, long startPos) {
RFuture<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec, StringCodec.INSTANCE), RedisCommands.HSCAN, getName(), startPos);
RFuture<MapScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.readAsync(client, getName(), new MapScanCodec(codec, StringCodec.INSTANCE), RedisCommands.HSCAN, getName(), startPos);
return get(f);
}
@ -263,7 +286,7 @@ public abstract class RedissonMultimap<K, V> extends RedissonExpirable implement
public Iterator<K> iterator() {
return new RedissonMultiMapKeysIterator<K, V, K>(RedissonMultimap.this) {
@Override
K getValue(java.util.Map.Entry<ScanObjectEntry, ScanObjectEntry> entry) {
protected K getValue(java.util.Map.Entry<ScanObjectEntry, ScanObjectEntry> entry) {
return (K) entry.getKey().getObj();
}
};

@ -23,6 +23,7 @@ import java.util.Map.Entry;
import org.redisson.api.RFuture;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisConnection;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.config.RedissonNodeConfig;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
@ -146,7 +147,7 @@ public class RedissonNode {
private void retrieveAdresses() {
ConnectionManager connectionManager = ((Redisson)redisson).getConnectionManager();
for (MasterSlaveEntry entry : connectionManager.getEntrySet()) {
RFuture<RedisConnection> readFuture = entry.connectionReadOp();
RFuture<RedisConnection> readFuture = entry.connectionReadOp(null);
if (readFuture.awaitUninterruptibly((long)connectionManager.getConfig().getConnectTimeout())
&& readFuture.isSuccess()) {
RedisConnection connection = readFuture.getNow();
@ -155,7 +156,7 @@ public class RedissonNode {
localAddress = (InetSocketAddress) connection.getChannel().localAddress();
return;
}
RFuture<RedisConnection> writeFuture = entry.connectionWriteOp();
RFuture<RedisConnection> writeFuture = entry.connectionWriteOp(null);
if (writeFuture.awaitUninterruptibly((long)connectionManager.getConfig().getConnectTimeout())
&& writeFuture.isSuccess()) {
RedisConnection connection = writeFuture.getNow();

@ -31,11 +31,11 @@ import org.redisson.misc.RPromise;
* @author Nikita Koksharov
*
*/
abstract class RedissonObject implements RObject {
public abstract class RedissonObject implements RObject {
final CommandAsyncExecutor commandExecutor;
protected final CommandAsyncExecutor commandExecutor;
private final String name;
final Codec codec;
protected final Codec codec;
public RedissonObject(Codec codec, CommandAsyncExecutor commandExecutor, String name) {
this.codec = codec;
@ -51,6 +51,20 @@ abstract class RedissonObject implements RObject {
return commandExecutor.await(future, timeout, timeoutUnit);
}
protected String prefixName(String prefix, String name) {
if (name.contains("{")) {
return prefix + ":" + name;
}
return prefix + ":{" + name + "}";
}
protected String suffixName(String name, String suffix) {
if (name.contains("{")) {
return name + ":" + suffix;
}
return "{" + name + "}:" + suffix;
}
protected <V> V get(RFuture<V> future) {
return commandExecutor.get(future);
}
@ -68,6 +82,10 @@ abstract class RedissonObject implements RObject {
return name;
}
protected String getName(Object o) {
return getName();
}
@Override
public void rename(String newName) {
get(renameAsync(newName));

@ -64,7 +64,7 @@ public class RedissonPatternTopic<M> implements RPatternTopic<M> {
private int addListener(RedisPubSubListener<?> pubSubListener) {
RFuture<PubSubConnectionEntry> future = commandExecutor.getConnectionManager().psubscribe(name, codec, pubSubListener);
future.syncUninterruptibly();
commandExecutor.syncSubscription(future);
return System.identityHashCode(pubSubListener);
}
@ -87,6 +87,45 @@ public class RedissonPatternTopic<M> implements RPatternTopic<M> {
}
}
@Override
public void removeAllListeners() {
AsyncSemaphore semaphore = commandExecutor.getConnectionManager().getSemaphore(name);
semaphore.acquireUninterruptibly();
PubSubConnectionEntry entry = commandExecutor.getConnectionManager().getPubSubEntry(name);
if (entry == null) {
semaphore.release();
return;
}
entry.removeAllListeners(name);
if (!entry.hasListeners(name)) {
commandExecutor.getConnectionManager().punsubscribe(name, semaphore);
} else {
semaphore.release();
}
}
@Override
public void removeListener(PatternMessageListener<M> listener) {
AsyncSemaphore semaphore = commandExecutor.getConnectionManager().getSemaphore(name);
semaphore.acquireUninterruptibly();
PubSubConnectionEntry entry = commandExecutor.getConnectionManager().getPubSubEntry(name);
if (entry == null) {
semaphore.release();
return;
}
entry.removeListener(name, listener);
if (!entry.hasListeners(name)) {
commandExecutor.getConnectionManager().punsubscribe(name, semaphore);
} else {
semaphore.release();
}
}
@Override
public List<String> getPatternNames() {
return Collections.singletonList(name);

@ -91,7 +91,7 @@ public class RedissonPermitExpirableSemaphore extends RedissonExpirable implemen
}
RFuture<RedissonLockEntry> future = subscribe();
get(future);
commandExecutor.syncSubscription(future);
try {
while (true) {
final Long nearestTimeout;
@ -672,7 +672,8 @@ public class RedissonPermitExpirableSemaphore extends RedissonExpirable implemen
"end;" +
"return value; " +
"end; " +
"return redis.call('get', KEYS[1]); ",
"local ret = redis.call('get', KEYS[1]); " +
"return ret == false and 0 or ret;",
Arrays.<Object>asList(getName(), timeoutName, getChannelName()), System.currentTimeMillis());
}

@ -0,0 +1,243 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.Iterator;
import java.util.NoSuchElementException;
import org.redisson.api.RFuture;
import org.redisson.api.RPriorityDeque;
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.VoidReplayConvertor;
import org.redisson.client.protocol.decoder.ListFirstObjectDecoder;
import org.redisson.command.CommandExecutor;
/**
* Distributed and concurrent implementation of {@link java.util.Queue}
*
* @author Nikita Koksharov
*
* @param <V> the type of elements held in this collection
*/
public class RedissonPriorityDeque<V> extends RedissonPriorityQueue<V> implements RPriorityDeque<V> {
private static final RedisCommand<Void> RPUSH_VOID = new RedisCommand<Void>("RPUSH", new VoidReplayConvertor(), 2, ValueType.OBJECTS);
private static final RedisCommand<Object> LRANGE_SINGLE = new RedisCommand<Object>("LRANGE", new ListFirstObjectDecoder());
protected RedissonPriorityDeque(CommandExecutor commandExecutor, String name, Redisson redisson) {
super(commandExecutor, name, redisson);
}
public RedissonPriorityDeque(Codec codec, CommandExecutor commandExecutor, String name, Redisson redisson) {
super(codec, commandExecutor, name, redisson);
}
@Override
public void addFirst(V e) {
get(addFirstAsync(e));
}
// @Override
public RFuture<Void> addFirstAsync(V e) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LPUSH_VOID, getName(), e);
}
@Override
public void addLast(V e) {
get(addLastAsync(e));
}
// @Override
public RFuture<Void> addLastAsync(V e) {
return commandExecutor.writeAsync(getName(), codec, RPUSH_VOID, getName(), e);
}
@Override
public Iterator<V> descendingIterator() {
return new Iterator<V>() {
private int currentIndex = size();
private boolean removeExecuted;
@Override
public boolean hasNext() {
int size = size();
return currentIndex > 0 && size > 0;
}
@Override
public V next() {
if (!hasNext()) {
throw new NoSuchElementException("No such element at index " + currentIndex);
}
currentIndex--;
removeExecuted = false;
return RedissonPriorityDeque.this.get(currentIndex);
}
@Override
public void remove() {
if (removeExecuted) {
throw new IllegalStateException("Element been already deleted");
}
RedissonPriorityDeque.this.remove(currentIndex);
currentIndex++;
removeExecuted = true;
}
};
}
// @Override
public RFuture<V> getLastAsync() {
return commandExecutor.readAsync(getName(), codec, LRANGE_SINGLE, getName(), -1, -1);
}
@Override
public V getLast() {
V result = get(getLastAsync());
if (result == null) {
throw new NoSuchElementException();
}
return result;
}
@Override
public boolean offerFirst(V e) {
return get(offerFirstAsync(e));
}
// @Override
public RFuture<Boolean> offerFirstAsync(V e) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LPUSH_BOOLEAN, getName(), e);
}
// @Override
public RFuture<Boolean> offerLastAsync(V e) {
return offerAsync(e);
}
@Override
public boolean offerLast(V e) {
return get(offerLastAsync(e));
}
// @Override
public RFuture<V> peekFirstAsync() {
return getAsync(0);
}
@Override
public V peekFirst() {
return get(peekFirstAsync());
}
// @Override
public RFuture<V> peekLastAsync() {
return getLastAsync();
}
@Override
public V peekLast() {
return get(getLastAsync());
}
// @Override
public RFuture<V> pollFirstAsync() {
return pollAsync();
}
@Override
public V pollFirst() {
return poll();
}
// @Override
public RFuture<V> pollLastAsync() {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.RPOP, getName());
}
@Override
public V pollLast() {
return get(pollLastAsync());
}
// @Override
public RFuture<V> popAsync() {
return pollAsync();
}
@Override
public V pop() {
return removeFirst();
}
// @Override
public RFuture<Void> pushAsync(V e) {
return addFirstAsync(e);
}
@Override
public void push(V e) {
addFirst(e);
}
// @Override
public RFuture<Boolean> removeFirstOccurrenceAsync(Object o) {
return removeAsync(o, 1);
}
@Override
public boolean removeFirstOccurrence(Object o) {
return remove(o, 1);
}
// @Override
public RFuture<V> removeFirstAsync() {
return pollAsync();
}
// @Override
public RFuture<V> removeLastAsync() {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.RPOP, getName());
}
@Override
public V removeLast() {
V value = get(removeLastAsync());
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
// @Override
public RFuture<Boolean> removeLastOccurrenceAsync(Object o) {
return removeAsync(o, -1);
}
@Override
public boolean removeLastOccurrence(Object o) {
return remove(o, -1);
}
}

@ -0,0 +1,414 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.redisson.api.RBucket;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RPriorityQueue;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class RedissonPriorityQueue<V> extends RedissonList<V> implements RPriorityQueue<V> {
private static class NaturalComparator<V> implements Comparator<V>, Serializable {
private static final long serialVersionUID = 7207038068494060240L;
static final NaturalComparator NATURAL_ORDER = new NaturalComparator();
public int compare(V c1, V c2) {
Comparable<Object> c1co = (Comparable<Object>) c1;
Comparable<Object> c2co = (Comparable<Object>) c2;
return c1co.compareTo(c2co);
}
}
public static class BinarySearchResult<V> {
private V value;
private Integer index;
public BinarySearchResult(V value) {
super();
this.value = value;
}
public BinarySearchResult() {
}
public void setIndex(Integer index) {
this.index = index;
}
public Integer getIndex() {
return index;
}
public V getValue() {
return value;
}
}
private Comparator<? super V> comparator = NaturalComparator.NATURAL_ORDER;
CommandExecutor commandExecutor;
private RLock lock;
private RBucket<String> comparatorHolder;
protected RedissonPriorityQueue(CommandExecutor commandExecutor, String name, Redisson redisson) {
super(commandExecutor, name);
this.commandExecutor = commandExecutor;
comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE);
lock = redisson.getLock("redisson_sortedset_lock:{" + getName() + "}");
loadComparator();
}
public RedissonPriorityQueue(Codec codec, CommandExecutor commandExecutor, String name, Redisson redisson) {
super(codec, commandExecutor, name);
this.commandExecutor = commandExecutor;
comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE);
lock = redisson.getLock("redisson_sortedset_lock:{" + getName() + "}");
loadComparator();
}
private void loadComparator() {
try {
String comparatorSign = comparatorHolder.get();
if (comparatorSign != null) {
String[] parts = comparatorSign.split(":");
String className = parts[0];
String sign = parts[1];
String result = calcClassSign(className);
if (!result.equals(sign)) {
throw new IllegalStateException("Local class signature of " + className + " differs from used by this SortedSet!");
}
Class<?> clazz = Class.forName(className);
comparator = (Comparator<V>) clazz.newInstance();
}
} catch (IllegalStateException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// TODO cache result
private static String calcClassSign(String name) {
try {
Class<?> clazz = Class.forName(name);
ByteArrayOutputStream result = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(result);
outputStream.writeObject(clazz);
outputStream.close();
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(result.toByteArray());
return new BigInteger(1, crypt.digest()).toString(16);
} catch (Exception e) {
throw new IllegalStateException("Can't calculate sign of " + name, e);
}
}
@Override
public List<V> readAll() {
return get(readAllAsync());
}
@Override
public RFuture<List<V>> readAllAsync() {
return commandExecutor.readAsync(getName(), codec, RedisCommands.LRANGE, getName(), 0, -1);
}
@Override
public boolean offer(V e) {
return add(e);
}
// @Override
public RFuture<Boolean> offerAsync(V e) {
return addAsync(e);
}
@Override
public boolean contains(final Object o) {
return binarySearch((V)o, codec).getIndex() >= 0;
}
@Override
public boolean add(V value) {
lock.lock();
try {
checkComparator();
BinarySearchResult<V> res = binarySearch(value, codec);
int index = 0;
if (res.getIndex() < 0) {
index = -(res.getIndex() + 1);
} else {
index = res.getIndex() + 1;
}
byte[] encodedValue = encode(value);
commandExecutor.evalWrite(getName(), RedisCommands.EVAL_VOID,
"local len = redis.call('llen', KEYS[1]);"
+ "if tonumber(ARGV[1]) < len then "
+ "local pivot = redis.call('lindex', KEYS[1], ARGV[1]);"
+ "redis.call('linsert', KEYS[1], 'before', pivot, ARGV[2]);"
+ "return;"
+ "end;"
+ "redis.call('rpush', KEYS[1], ARGV[2]);", Arrays.<Object>asList(getName()), index, encodedValue);
return true;
} finally {
lock.unlock();
}
}
private void checkComparator() {
String comparatorSign = comparatorHolder.get();
if (comparatorSign != null) {
String[] vals = comparatorSign.split(":");
String className = vals[0];
if (!comparator.getClass().getName().equals(className)) {
loadComparator();
}
}
}
@Override
public boolean remove(Object value) {
lock.lock();
try {
checkComparator();
BinarySearchResult<V> res = binarySearch((V) value, codec);
if (res.getIndex() < 0) {
return false;
}
remove((int)res.getIndex());
return true;
} finally {
lock.unlock();
}
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object object : c) {
if (!contains(object)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection<? extends V> c) {
boolean changed = false;
for (V v : c) {
if (add(v)) {
changed = true;
}
}
return changed;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean changed = false;
for (Iterator<?> iterator = iterator(); iterator.hasNext();) {
Object object = (Object) iterator.next();
if (!c.contains(object)) {
iterator.remove();
changed = true;
}
}
return changed;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean changed = false;
for (Object obj : c) {
if (remove(obj)) {
changed = true;
}
}
return changed;
}
@Override
public void clear() {
delete();
}
@Override
public Comparator<? super V> comparator() {
return comparator;
}
// @Override
public RFuture<V> pollAsync() {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.LPOP, getName());
}
public V getFirst() {
V value = getValue(0);
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
@Override
public V poll() {
return get(pollAsync());
}
@Override
public V element() {
return getFirst();
}
// @Override
public RFuture<V> peekAsync() {
return getAsync(0);
}
@Override
public V peek() {
return getValue(0);
}
private String getComparatorKeyName() {
return "redisson_sortedset_comparator:{" + getName() + "}";
}
@Override
public boolean trySetComparator(Comparator<? super V> comparator) {
String className = comparator.getClass().getName();
final String comparatorSign = className + ":" + calcClassSign(className);
Boolean res = commandExecutor.evalWrite(getName(), StringCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if redis.call('llen', KEYS[1]) == 0 then "
+ "redis.call('set', KEYS[2], ARGV[1]); "
+ "return 1; "
+ "else "
+ "return 0; "
+ "end",
Arrays.<Object>asList(getName(), getComparatorKeyName()), comparatorSign);
if (res) {
this.comparator = comparator;
}
return res;
}
@Override
public V remove() {
return removeFirst();
}
public V removeFirst() {
V value = poll();
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
// TODO optimize: get three values each time instead of single
public BinarySearchResult<V> binarySearch(V value, Codec codec) {
int size = size();
int upperIndex = size - 1;
int lowerIndex = 0;
while (lowerIndex <= upperIndex) {
int index = lowerIndex + (upperIndex - lowerIndex) / 2;
V res = getValue(index);
if (res == null) {
return new BinarySearchResult<V>();
}
int cmp = comparator.compare(value, res);
if (cmp == 0) {
BinarySearchResult<V> indexRes = new BinarySearchResult<V>();
indexRes.setIndex(index);
return indexRes;
} else if (cmp < 0) {
upperIndex = index - 1;
} else {
lowerIndex = index + 1;
}
}
BinarySearchResult<V> indexRes = new BinarySearchResult<V>();
indexRes.setIndex(-(lowerIndex + 1));
return indexRes;
}
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(' ');
}
}
}

@ -16,6 +16,7 @@
package org.redisson;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RQueue;
@ -66,6 +67,14 @@ public class RedissonQueue<V> extends RedissonList<V> implements RQueue<V> {
return value;
}
protected long toSeconds(long timeout, TimeUnit unit) {
long seconds = unit.toSeconds(timeout);
if (timeout != 0 && seconds == 0) {
seconds = 1;
}
return seconds;
}
@Override
public V remove() {
return removeFirst();

@ -18,6 +18,7 @@ package org.redisson;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.redisson.api.ClusterNode;
import org.redisson.api.Node;
@ -50,6 +51,7 @@ import org.redisson.command.CommandReactiveService;
import org.redisson.config.Config;
import org.redisson.config.ConfigSupport;
import org.redisson.connection.ConnectionManager;
import org.redisson.eviction.EvictionScheduler;
import org.redisson.reactive.RedissonAtomicLongReactive;
import org.redisson.reactive.RedissonBatchReactive;
import org.redisson.reactive.RedissonBitSetReactive;
@ -84,6 +86,7 @@ public class RedissonReactive implements RedissonReactiveClient {
protected final ConnectionManager connectionManager;
protected final Config config;
protected final CodecProvider codecProvider;
protected final UUID id = UUID.randomUUID();
protected RedissonReactive(Config config) {
this.config = config;
@ -98,12 +101,12 @@ public class RedissonReactive implements RedissonReactiveClient {
@Override
public <K, V> RMapCacheReactive<K, V> getMapCache(String name, Codec codec) {
return new RedissonMapCacheReactive<K, V>(codec, evictionScheduler, commandExecutor, name);
return new RedissonMapCacheReactive<K, V>(id, evictionScheduler, codec, commandExecutor, name);
}
@Override
public <K, V> RMapCacheReactive<K, V> getMapCache(String name) {
return new RedissonMapCacheReactive<K, V>(evictionScheduler, commandExecutor, name);
return new RedissonMapCacheReactive<K, V>(id, evictionScheduler, commandExecutor, name);
}
@Override
@ -262,7 +265,7 @@ public class RedissonReactive implements RedissonReactiveClient {
@Override
public RBatchReactive createBatch() {
RedissonBatchReactive batch = new RedissonBatchReactive(evictionScheduler, connectionManager);
RedissonBatchReactive batch = new RedissonBatchReactive(id, evictionScheduler, connectionManager);
if (config.isRedissonReferenceEnabled()) {
batch.enableRedissonReferenceSupport(this);
}

@ -52,6 +52,10 @@ public class RedissonReadLock extends RedissonLock implements RLock {
return "redisson_rwlock__{" + getName() + "}";
}
String getWriteLockName(long threadId) {
return super.getLockName(threadId) + ":write";
}
@Override
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
@ -64,13 +68,13 @@ public class RedissonReadLock extends RedissonLock implements RLock {
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (mode == 'read') then " +
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
Arrays.<Object>asList(getName()), internalLockLeaseTime, getLockName(threadId));
Arrays.<Object>asList(getName()), internalLockLeaseTime, getLockName(threadId), getWriteLockName(threadId));
}
@Override
@ -80,8 +84,8 @@ public class RedissonReadLock extends RedissonLock implements RLock {
"if (mode == false) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end; "
+ "if (mode == 'read') then " +
"end; " +
// "if (mode == 'read') then " +
"local lockExists = redis.call('hexists', KEYS[1], ARGV[3]); " +
"if (lockExists == 0) then " +
"return nil;" +
@ -99,7 +103,7 @@ public class RedissonReadLock extends RedissonLock implements RLock {
"return 1; "+
"end; " +
"end; " +
"end; " +
// "end; " +
"return nil; ",
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId()));
if (opStatus == null) {
@ -146,18 +150,4 @@ public class RedissonReadLock extends RedissonLock implements RLock {
return "read".equals(res);
}
@Override
public boolean isHeldByCurrentThread() {
return commandExecutor.write(getName(), LongCodec.INSTANCE, RedisCommands.HEXISTS, getName(), getLockName(Thread.currentThread().getId()));
}
@Override
public int getHoldCount() {
Long res = commandExecutor.write(getName(), LongCodec.INSTANCE, RedisCommands.HGET, getName(), getLockName(Thread.currentThread().getId()));
if (res == null) {
return 0;
}
return res.intValue();
}
}

@ -93,7 +93,7 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS
}
for (Method method : remoteInterface.getMethods()) {
RemoteServiceMethod value = new RemoteServiceMethod(method, object);
RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName());
RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName(), getMethodSignatures(method));
if (beans.put(key, value) != null) {
return;
}
@ -113,6 +113,7 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS
@Override
public void operationComplete(Future<RemoteServiceRequest> future) throws Exception {
if (!future.isSuccess()) {
log.error("Can't process the remote service request.", future.cause());
if (future.cause() instanceof RedissonShutdownException) {
return;
}
@ -183,7 +184,7 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS
private <T> void executeMethod(final Class<T> remoteInterface, final RBlockingQueue<RemoteServiceRequest> requestQueue,
final ExecutorService executor, final RemoteServiceRequest request) {
final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName()));
final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName(), request.getSignatures()));
final String responseName = getResponseQueueName(remoteInterface, request.getRequestId());
RBlockingQueue<RemoteServiceCancelRequest> cancelRequestQueue =

@ -25,13 +25,16 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.redisson.api.RFuture;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.SortOrder;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.DoubleCodec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.codec.ScoredCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
@ -39,8 +42,15 @@ import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.ScoredEntry;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
/**
*
* @author Nikita Koksharov
*
* @param <V> value type
*/
public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RScoredSortedSet<V> {
public RedissonScoredSortedSet(CommandAsyncExecutor commandExecutor, String name) {
@ -251,8 +261,8 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANK_INT, getName(), o);
}
private ListScanResult<V> scanIterator(InetSocketAddress client, long startPos) {
RFuture<ListScanResult<V>> f = commandExecutor.readAsync(client, getName(), codec, RedisCommands.ZSCAN, getName(), startPos);
private ListScanResult<ScanObjectEntry> scanIterator(InetSocketAddress client, long startPos) {
RFuture<ListScanResult<ScanObjectEntry>> f = commandExecutor.readAsync(client, getName(), new ScanCodec(codec), RedisCommands.ZSCAN, getName(), startPos);
return get(f);
}
@ -261,7 +271,7 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
return new RedissonBaseIterator<V>() {
@Override
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
ListScanResult<ScanObjectEntry> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(client, nextIterPos);
}
@ -627,5 +637,168 @@ public class RedissonScoredSortedSet<V> extends RedissonExpirable implements RSc
return commandExecutor.writeAsync(getName(), LongCodec.INSTANCE, RedisCommands.ZUNIONSTORE_INT, args.toArray());
}
@Override
public Set<V> readSort(SortOrder order) {
return get(readSortAsync(order));
}
@Override
public RFuture<Set<V>> readSortAsync(SortOrder order) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), order);
}
@Override
public Set<V> readSort(SortOrder order, int offset, int count) {
return get(readSortAsync(order, offset, count));
}
@Override
public RFuture<Set<V>> readSortAsync(SortOrder order, int offset, int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), "LIMIT", offset, count, order);
}
@Override
public Set<V> readSort(String byPattern, SortOrder order) {
return get(readSortAsync(byPattern, order));
}
@Override
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), "BY", byPattern, order);
}
@Override
public Set<V> readSort(String byPattern, SortOrder order, int offset, int count) {
return get(readSortAsync(byPattern, order, offset, count));
}
@Override
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order, int offset, int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), "BY", byPattern, "LIMIT", offset, count, order);
}
@Override
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order) {
return (Collection<T>)get(readSortAsync(byPattern, getPatterns, order));
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order) {
return readSortAsync(byPattern, getPatterns, order, -1, -1);
}
@Override
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
return (Collection<T>)get(readSortAsync(byPattern, getPatterns, order, offset, count));
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
List<Object> params = new ArrayList<Object>();
params.add(getName());
if (byPattern != null) {
params.add("BY");
params.add(byPattern);
}
if (offset != -1 && count != -1) {
params.add("LIMIT");
}
if (offset != -1) {
params.add(offset);
}
if (count != -1) {
params.add(count);
}
for (String pattern : getPatterns) {
params.add("GET");
params.add(pattern);
}
params.add(order);
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, params.toArray());
}
@Override
public int sortTo(String destName, SortOrder order) {
return get(sortToAsync(destName, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, SortOrder order) {
return sortToAsync(destName, null, Collections.<String>emptyList(), order, -1, -1);
}
@Override
public int sortTo(String destName, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, order, offset, count));
}
@Override
public RFuture<Integer> sortToAsync(String destName, SortOrder order, int offset, int count) {
return sortToAsync(destName, null, Collections.<String>emptyList(), order, offset, count);
}
@Override
public int sortTo(String destName, String byPattern, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, byPattern, order, offset, count));
}
@Override
public int sortTo(String destName, String byPattern, SortOrder order) {
return get(sortToAsync(destName, byPattern, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order) {
return sortToAsync(destName, byPattern, Collections.<String>emptyList(), order, -1, -1);
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order, int offset, int count) {
return sortToAsync(destName, byPattern, Collections.<String>emptyList(), order, offset, count);
}
@Override
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return get(sortToAsync(destName, byPattern, getPatterns, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return sortToAsync(destName, byPattern, getPatterns, order, -1, -1);
}
@Override
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, byPattern, getPatterns, order, offset, count));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
List<Object> params = new ArrayList<Object>();
params.add(getName());
if (byPattern != null) {
params.add("BY");
params.add(byPattern);
}
if (offset != -1 && count != -1) {
params.add("LIMIT");
}
if (offset != -1) {
params.add(offset);
}
if (count != -1) {
params.add(count);
}
for (String pattern : getPatterns) {
params.add("GET");
params.add(pattern);
}
params.add(order);
params.add("STORE");
params.add(destName);
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SORT_TO, params.toArray());
}
}

@ -79,7 +79,7 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore {
}
RFuture<RedissonLockEntry> future = subscribe();
get(future);
commandExecutor.syncSubscription(future);
try {
while (true) {
if (tryAcquire(permits)) {

@ -19,18 +19,22 @@ import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.redisson.api.RFuture;
import org.redisson.api.RSet;
import org.redisson.api.SortOrder;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
/**
@ -40,7 +44,7 @@ import org.redisson.command.CommandAsyncExecutor;
*
* @param <V> value
*/
public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
public class RedissonSet<V> extends RedissonExpirable implements RSet<V>, ScanIterator {
protected RedissonSet(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
@ -79,8 +83,9 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
return getName();
}
ListScanResult<V> scanIterator(String name, InetSocketAddress client, long startPos) {
RFuture<ListScanResult<V>> f = commandExecutor.readAsync(client, name, codec, RedisCommands.SSCAN, name, startPos);
@Override
public ListScanResult<ScanObjectEntry> scanIterator(String name, InetSocketAddress client, long startPos) {
RFuture<ListScanResult<ScanObjectEntry>> f = commandExecutor.readAsync(client, name, new ScanCodec(codec), RedisCommands.SSCAN, name, startPos);
return get(f);
}
@ -89,7 +94,7 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
return new RedissonBaseIterator<V>() {
@Override
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
ListScanResult<ScanObjectEntry> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(getName(), client, nextIterPos);
}
@ -143,6 +148,16 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SPOP_SINGLE, getName());
}
@Override
public Set<V> removeRandom(int amount) {
return get(removeRandomAsync(amount));
}
@Override
public RFuture<Set<V>> removeRandomAsync(int amount) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SPOP, getName(), amount);
}
@Override
public V random() {
return get(randomAsync());
@ -346,4 +361,168 @@ public class RedissonSet<V> extends RedissonExpirable implements RSet<V> {
}
}
@Override
public Set<V> readSort(SortOrder order) {
return get(readSortAsync(order));
}
@Override
public RFuture<Set<V>> readSortAsync(SortOrder order) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), order);
}
@Override
public Set<V> readSort(SortOrder order, int offset, int count) {
return get(readSortAsync(order, offset, count));
}
@Override
public RFuture<Set<V>> readSortAsync(SortOrder order, int offset, int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), "LIMIT", offset, count, order);
}
@Override
public Set<V> readSort(String byPattern, SortOrder order) {
return get(readSortAsync(byPattern, order));
}
@Override
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), "BY", byPattern, order);
}
@Override
public Set<V> readSort(String byPattern, SortOrder order, int offset, int count) {
return get(readSortAsync(byPattern, order, offset, count));
}
@Override
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order, int offset, int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, getName(), "BY", byPattern, "LIMIT", offset, count, order);
}
@Override
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order) {
return (Collection<T>)get(readSortAsync(byPattern, getPatterns, order));
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order) {
return readSortAsync(byPattern, getPatterns, order, -1, -1);
}
@Override
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
return (Collection<T>)get(readSortAsync(byPattern, getPatterns, order, offset, count));
}
@Override
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
List<Object> params = new ArrayList<Object>();
params.add(getName());
if (byPattern != null) {
params.add("BY");
params.add(byPattern);
}
if (offset != -1 && count != -1) {
params.add("LIMIT");
}
if (offset != -1) {
params.add(offset);
}
if (count != -1) {
params.add(count);
}
for (String pattern : getPatterns) {
params.add("GET");
params.add(pattern);
}
params.add(order);
return commandExecutor.readAsync(getName(), codec, RedisCommands.SORT_SET, params.toArray());
}
@Override
public int sortTo(String destName, SortOrder order) {
return get(sortToAsync(destName, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, SortOrder order) {
return sortToAsync(destName, null, Collections.<String>emptyList(), order, -1, -1);
}
@Override
public int sortTo(String destName, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, order, offset, count));
}
@Override
public RFuture<Integer> sortToAsync(String destName, SortOrder order, int offset, int count) {
return sortToAsync(destName, null, Collections.<String>emptyList(), order, offset, count);
}
@Override
public int sortTo(String destName, String byPattern, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, byPattern, order, offset, count));
}
@Override
public int sortTo(String destName, String byPattern, SortOrder order) {
return get(sortToAsync(destName, byPattern, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order) {
return sortToAsync(destName, byPattern, Collections.<String>emptyList(), order, -1, -1);
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order, int offset, int count) {
return sortToAsync(destName, byPattern, Collections.<String>emptyList(), order, offset, count);
}
@Override
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return get(sortToAsync(destName, byPattern, getPatterns, order));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return sortToAsync(destName, byPattern, getPatterns, order, -1, -1);
}
@Override
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
return get(sortToAsync(destName, byPattern, getPatterns, order, offset, count));
}
@Override
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
List<Object> params = new ArrayList<Object>();
params.add(getName());
if (byPattern != null) {
params.add("BY");
params.add(byPattern);
}
if (offset != -1 && count != -1) {
params.add("LIMIT");
}
if (offset != -1) {
params.add(offset);
}
if (count != -1) {
params.add(count);
}
for (String pattern : getPatterns) {
params.add("GET");
params.add(pattern);
}
params.add(order);
params.add("STORE");
params.add(destName);
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SORT_TO, params.toArray());
}
}

@ -28,13 +28,16 @@ import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RSetCache;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.eviction.EvictionScheduler;
/**
* <p>Set-based cache with ability to set TTL for each entry via
@ -45,7 +48,7 @@ import org.redisson.command.CommandAsyncExecutor;
* Thus values are checked for TTL expiration during any value read operation.
* If entry expired then it doesn't returns and clean task runs asynchronous.
* Clean task deletes removes 100 expired entries at once.
* In addition there is {@link org.redisson.EvictionScheduler}. This scheduler
* In addition there is {@link org.redisson.eviction.EvictionScheduler}. This scheduler
* deletes expired entries in time interval between 5 seconds to 2 hours.</p>
*
* <p>If eviction is not required then it's better to use {@link org.redisson.api.RSet}.</p>
@ -54,7 +57,15 @@ import org.redisson.command.CommandAsyncExecutor;
*
* @param <V> value
*/
public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<V> {
public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<V>, ScanIterator {
RedissonSetCache(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
}
RedissonSetCache(Codec codec, CommandAsyncExecutor commandExecutor, String name) {
super(codec, commandExecutor, name);
}
public RedissonSetCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
@ -88,7 +99,7 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override
public RFuture<Boolean> containsAsync(Object o) {
return commandExecutor.evalReadAsync(getName(), codec, new RedisStrictCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5),
return commandExecutor.evalReadAsync(getName(o), codec, new RedisStrictCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 5),
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2]); " +
"if expireDateScore ~= false then " +
"if tonumber(expireDateScore) <= tonumber(ARGV[1]) then " +
@ -99,16 +110,16 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
"else " +
"return 0;" +
"end; ",
Arrays.<Object>asList(getName()), System.currentTimeMillis(), o);
Arrays.<Object>asList(getName(o)), System.currentTimeMillis(), o);
}
ListScanResult<V> scanIterator(InetSocketAddress client, long startPos) {
RFuture<ListScanResult<V>> f = scanIteratorAsync(client, startPos);
public ListScanResult<ScanObjectEntry> scanIterator(String name, InetSocketAddress client, long startPos) {
RFuture<ListScanResult<ScanObjectEntry>> f = scanIteratorAsync(name, client, startPos);
return get(f);
}
public RFuture<ListScanResult<V>> scanIteratorAsync(InetSocketAddress client, long startPos) {
return commandExecutor.evalReadAsync(client, getName(), codec, RedisCommands.EVAL_ZSCAN,
public RFuture<ListScanResult<ScanObjectEntry>> scanIteratorAsync(String name, InetSocketAddress client, long startPos) {
return commandExecutor.evalReadAsync(client, name, new ScanCodec(codec), RedisCommands.EVAL_ZSCAN,
"local result = {}; "
+ "local res = redis.call('zscan', KEYS[1], ARGV[1]); "
+ "for i, value in ipairs(res[2]) do "
@ -119,7 +130,7 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
+ "end; "
+ "end;"
+ "end;"
+ "return {res[1], result};", Arrays.<Object>asList(getName()), startPos, System.currentTimeMillis());
+ "return {res[1], result};", Arrays.<Object>asList(name), startPos, System.currentTimeMillis());
}
@Override
@ -127,8 +138,8 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
return new RedissonBaseIterator<V>() {
@Override
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(client, nextIterPos);
ListScanResult<ScanObjectEntry> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(getName(), client, nextIterPos);
}
@Override
@ -146,27 +157,18 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override
public RFuture<Set<V>> readAllAsync() {
return (RFuture<Set<V>>)readAllAsync(RedisCommands.ZRANGEBYSCORE);
}
private RFuture<?> readAllAsync(RedisCommand<? extends Collection<?>> command) {
return commandExecutor.readAsync(getName(), codec, command, getName(), System.currentTimeMillis(), 92233720368547758L);
}
private RFuture<List<Object>> readAllasListAsync() {
return (RFuture<List<Object>>)readAllAsync(RedisCommands.ZRANGEBYSCORE_LIST);
return commandExecutor.readAsync(getName(), codec, RedisCommands.ZRANGEBYSCORE, getName(), System.currentTimeMillis(), 92233720368547758L);
}
@Override
public Object[] toArray() {
List<Object> res = get(readAllasListAsync());
Set<V> res = get(readAllAsync());
return res.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
List<Object> res = get(readAllasListAsync());
Set<V> res = get(readAllAsync());
return res.toArray(a);
}
@ -196,14 +198,14 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
byte[] objectState = encode(value);
long timeoutDate = System.currentTimeMillis() + unit.toMillis(ttl);
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
return commandExecutor.evalWriteAsync(getName(value), codec, RedisCommands.EVAL_BOOLEAN,
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); " +
"redis.call('zadd', KEYS[1], ARGV[2], ARGV[3]); " +
"if expireDateScore ~= false and tonumber(expireDateScore) > tonumber(ARGV[1]) then " +
"return 0;" +
"end; " +
"return 1; ",
Arrays.<Object>asList(getName()), System.currentTimeMillis(), timeoutDate, objectState);
Arrays.<Object>asList(getName(value)), System.currentTimeMillis(), timeoutDate, objectState);
}
@Override
@ -213,7 +215,7 @@ public class RedissonSetCache<V> extends RedissonExpirable implements RSetCache<
@Override
public RFuture<Boolean> removeAsync(Object o) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.ZREM, getName(), o);
return commandExecutor.writeAsync(getName(o), codec, RedisCommands.ZREM, getName(o), o);
}
@Override

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
@ -47,12 +48,12 @@ public class RedissonSetMultimap<K, V> extends RedissonMultimap<K, V> implements
private static final RedisStrictCommand<Boolean> SCARD_VALUE = new RedisStrictCommand<Boolean>("SCARD", new BooleanAmountReplayConvertor());
private static final RedisCommand<Boolean> SISMEMBER_VALUE = new RedisCommand<Boolean>("SISMEMBER", new BooleanReplayConvertor());
RedissonSetMultimap(CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
RedissonSetMultimap(UUID id, CommandAsyncExecutor connectionManager, String name) {
super(id, connectionManager, name);
}
RedissonSetMultimap(Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(codec, connectionManager, name);
RedissonSetMultimap(UUID id, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(id, codec, connectionManager, name);
}
@Override

@ -17,6 +17,7 @@ package org.redisson;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
@ -25,6 +26,7 @@ import org.redisson.api.RSetMultimapCache;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.eviction.EvictionScheduler;
/**
* @author Nikita Koksharov
@ -36,14 +38,14 @@ public class RedissonSetMultimapCache<K, V> extends RedissonSetMultimap<K, V> im
private final RedissonMultimapCache<K> baseCache;
RedissonSetMultimapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(connectionManager, name);
RedissonSetMultimapCache(UUID id, EvictionScheduler evictionScheduler, CommandAsyncExecutor connectionManager, String name) {
super(id, 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);
RedissonSetMultimapCache(UUID id, EvictionScheduler evictionScheduler, Codec codec, CommandAsyncExecutor connectionManager, String name) {
super(id, codec, connectionManager, name);
evictionScheduler.scheduleCleanMultimap(name, getTimeoutSetName());
baseCache = new RedissonMultimapCache<K>(connectionManager, name, codec, getTimeoutSetName());
}

@ -27,7 +27,9 @@ import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.api.RSet;
import org.redisson.api.SortOrder;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.ScanCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
@ -38,6 +40,7 @@ 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.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
/**
@ -55,6 +58,7 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
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 RSet<V> set;
private final Object key;
private final String timeoutSetName;
@ -62,6 +66,7 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
super(codec, commandExecutor, name);
this.timeoutSetName = timeoutSetName;
this.key = key;
this.set = new RedissonSet<V>(codec, commandExecutor, name);
}
@Override
@ -158,8 +163,8 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
Arrays.<Object>asList(timeoutSetName, getName()), System.currentTimeMillis(), key, o);
}
private ListScanResult<V> scanIterator(InetSocketAddress client, long startPos) {
RFuture<ListScanResult<V>> f = commandExecutor.evalReadAsync(client, getName(), codec, EVAL_SSCAN,
private ListScanResult<ScanObjectEntry> scanIterator(InetSocketAddress client, long startPos) {
RFuture<ListScanResult<ScanObjectEntry>> f = commandExecutor.evalReadAsync(client, getName(), new ScanCodec(codec), EVAL_SSCAN,
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[1], ARGV[3]); "
+ "if expireDateScore ~= false then "
@ -179,7 +184,7 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
return new RedissonBaseIterator<V>() {
@Override
ListScanResult<V> iterator(InetSocketAddress client, long nextIterPos) {
ListScanResult<ScanObjectEntry> iterator(InetSocketAddress client, long nextIterPos) {
return scanIterator(client, nextIterPos);
}
@ -225,17 +230,17 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
@Override
public boolean add(V e) {
return get(addAsync(e));
return set.add(e);
}
@Override
public RFuture<Boolean> addAsync(V e) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SADD_SINGLE, getName(), e);
return set.addAsync(e);
}
@Override
public V removeRandom() {
return get(removeRandomAsync());
return set.removeRandom();
}
@Override
@ -243,6 +248,16 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SPOP_SINGLE, getName());
}
@Override
public Set<V> removeRandom(int amount) {
return get(removeRandomAsync(amount));
}
@Override
public RFuture<Set<V>> removeRandomAsync(int amount) {
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SPOP, getName(), amount);
}
@Override
public V random() {
return get(randomAsync());
@ -505,4 +520,106 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
return commandExecutor.writeAsync(getName(), codec, RedisCommands.SINTER, args.toArray());
}
public RFuture<Set<V>> readSortAsync(SortOrder order) {
return set.readSortAsync(order);
}
public Set<V> readSort(SortOrder order) {
return set.readSort(order);
}
public RFuture<Set<V>> readSortAsync(SortOrder order, int offset, int count) {
return set.readSortAsync(order, offset, count);
}
public Set<V> readSort(SortOrder order, int offset, int count) {
return set.readSort(order, offset, count);
}
public Set<V> readSort(String byPattern, SortOrder order) {
return set.readSort(byPattern, order);
}
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order) {
return set.readSortAsync(byPattern, order);
}
public Set<V> readSort(String byPattern, SortOrder order, int offset, int count) {
return set.readSort(byPattern, order, offset, count);
}
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order, int offset, int count) {
return set.readSortAsync(byPattern, order, offset, count);
}
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order) {
return set.readSort(byPattern, getPatterns, order);
}
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order) {
return set.readSortAsync(byPattern, getPatterns, order);
}
public <T> Collection<T> readSort(String byPattern, List<String> getPatterns, SortOrder order, int offset,
int count) {
return set.readSort(byPattern, getPatterns, order, offset, count);
}
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order,
int offset, int count) {
return set.readSortAsync(byPattern, getPatterns, order, offset, count);
}
public int sortTo(String destName, SortOrder order) {
return set.sortTo(destName, order);
}
public RFuture<Integer> sortToAsync(String destName, SortOrder order) {
return set.sortToAsync(destName, order);
}
public int sortTo(String destName, SortOrder order, int offset, int count) {
return set.sortTo(destName, order, offset, count);
}
public RFuture<Integer> sortToAsync(String destName, SortOrder order, int offset, int count) {
return set.sortToAsync(destName, order, offset, count);
}
public int sortTo(String destName, String byPattern, SortOrder order) {
return set.sortTo(destName, byPattern, order);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order) {
return set.sortToAsync(destName, byPattern, order);
}
public int sortTo(String destName, String byPattern, SortOrder order, int offset, int count) {
return set.sortTo(destName, byPattern, order, offset, count);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, SortOrder order, int offset, int count) {
return set.sortToAsync(destName, byPattern, order, offset, count);
}
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return set.sortTo(destName, byPattern, getPatterns, order);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order) {
return set.sortToAsync(destName, byPattern, getPatterns, order);
}
public int sortTo(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset,
int count) {
return set.sortTo(destName, byPattern, getPatterns, order, offset, count);
}
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order,
int offset, int count) {
return set.sortToAsync(destName, byPattern, getPatterns, order, offset, count);
}
}

@ -16,7 +16,6 @@
package org.redisson;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigInteger;
@ -26,6 +25,7 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import org.redisson.api.RBucket;
@ -38,13 +38,11 @@ import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor;
import org.redisson.misc.RPromise;
import io.netty.channel.EventLoopGroup;
/**
*
* @author Nikita Koksharov
*
* @param <V> value
* @param <V> value type
*/
public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V> {
@ -162,6 +160,16 @@ public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V
}
}
@Override
public Set<V> readAll() {
return get(readAllAsync());
}
@Override
public RFuture<Set<V>> readAllAsync() {
return commandExecutor.readAsync(getName(), codec, RedisCommands.LRANGE_SET, getName(), 0, -1);
}
@Override
public int size() {
return list.size();
@ -203,12 +211,7 @@ public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V
if (res.getIndex() < 0) {
int index = -(res.getIndex() + 1);
byte[] encodedValue = null;
try {
encodedValue = codec.getValueEncoder().encode(value);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
byte[] encodedValue = encode(value);
commandExecutor.evalWrite(getName(), RedisCommands.EVAL_VOID,
"local len = redis.call('llen', KEYS[1]);"
@ -240,7 +243,7 @@ public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V
public RFuture<Boolean> addAsync(final V value) {
final RPromise<Boolean> promise = newPromise();
commandExecutor.getConnectionManager().getGroup().execute(new Runnable() {
commandExecutor.getConnectionManager().getExecutor().execute(new Runnable() {
public void run() {
try {
boolean res = add(value);
@ -255,10 +258,8 @@ public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V
@Override
public RFuture<Boolean> removeAsync(final V value) {
EventLoopGroup group = commandExecutor.getConnectionManager().getGroup();
final RPromise<Boolean> promise = newPromise();
group.execute(new Runnable() {
commandExecutor.getConnectionManager().getExecutor().execute(new Runnable() {
@Override
public void run() {
try {
@ -316,7 +317,7 @@ public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V
@Override
public boolean retainAll(Collection<?> c) {
boolean changed = false;
for (Iterator iterator = iterator(); iterator.hasNext();) {
for (Iterator<?> iterator = iterator(); iterator.hasNext();) {
Object object = (Object) iterator.next();
if (!c.contains(object)) {
iterator.remove();
@ -404,6 +405,7 @@ public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V
return res;
}
// TODO optimize: get three values each time instead of single
public BinarySearchResult<V> binarySearch(V value, Codec codec) {
int size = list.size();
int upperIndex = size - 1;

@ -470,7 +470,7 @@ public class RedissonSubList<V> extends RedissonList<V> implements RList<V> {
}
@Override
public RFuture<Void> trimAsync(long fromIndex, long toIndex) {
public RFuture<Void> trimAsync(int fromIndex, int toIndex) {
if (fromIndex < this.fromIndex || toIndex >= this.toIndex.get()) {
throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " toIndex: " + toIndex);
}

@ -79,10 +79,49 @@ public class RedissonTopic<M> implements RTopic<M> {
private int addListener(RedisPubSubListener<?> pubSubListener) {
RFuture<PubSubConnectionEntry> future = commandExecutor.getConnectionManager().subscribe(codec, name, pubSubListener);
future.syncUninterruptibly();
commandExecutor.syncSubscription(future);
return System.identityHashCode(pubSubListener);
}
@Override
public void removeAllListeners() {
AsyncSemaphore semaphore = commandExecutor.getConnectionManager().getSemaphore(name);
semaphore.acquireUninterruptibly();
PubSubConnectionEntry entry = commandExecutor.getConnectionManager().getPubSubEntry(name);
if (entry == null) {
semaphore.release();
return;
}
entry.removeAllListeners(name);
if (!entry.hasListeners(name)) {
commandExecutor.getConnectionManager().unsubscribe(name, semaphore);
} else {
semaphore.release();
}
}
@Override
public void removeListener(MessageListener<?> listener) {
AsyncSemaphore semaphore = commandExecutor.getConnectionManager().getSemaphore(name);
semaphore.acquireUninterruptibly();
PubSubConnectionEntry entry = commandExecutor.getConnectionManager().getPubSubEntry(name);
if (entry == null) {
semaphore.release();
return;
}
entry.removeListener(name, listener);
if (!entry.hasListeners(name)) {
commandExecutor.getConnectionManager().unsubscribe(name, semaphore);
} else {
semaphore.release();
}
}
@Override
public void removeListener(int listenerId) {
AsyncSemaphore semaphore = commandExecutor.getConnectionManager().getSemaphore(name);

@ -52,6 +52,11 @@ public class RedissonWriteLock extends RedissonLock implements RLock {
return "redisson_rwlock__{" + getName() + "}";
}
@Override
String getLockName(long threadId) {
return super.getLockName(threadId) + ":write";
}
@Override
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
@ -97,6 +102,9 @@ public class RedissonWriteLock extends RedissonLock implements RLock {
"if (redis.call('hlen', KEYS[1]) == 1) then " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"else " +
// has unlocked read-locks
"redis.call('hset', KEYS[1], 'mode', 'read'); " +
"end; " +
"return 1; "+
"end; " +
@ -148,18 +156,4 @@ public class RedissonWriteLock extends RedissonLock implements RLock {
return "write".equals(res);
}
@Override
public boolean isHeldByCurrentThread() {
return commandExecutor.write(getName(), LongCodec.INSTANCE, RedisCommands.HEXISTS, getName(), getLockName(Thread.currentThread().getId()));
}
@Override
public int getHoldCount() {
Long res = commandExecutor.write(getName(), LongCodec.INSTANCE, RedisCommands.HGET, getName(), getLockName(Thread.currentThread().getId()));
if (res == null) {
return 0;
}
return res.intValue();
}
}

@ -13,20 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.misc;
package org.redisson;
import java.net.URI;
import java.net.InetSocketAddress;
public class URIBuilder {
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
public static URI create(String uri) {
String[] parts = uri.split(":");
if (parts.length-1 >= 3) {
String port = parts[parts.length-1];
uri = "[" + uri.replace(":" + port, "") + "]:" + port;
}
public interface ScanIterator {
return URI.create("//" + uri);
}
ListScanResult<ScanObjectEntry> scanIterator(String name, InetSocketAddress client, long startPos);
boolean remove(Object value);
}

@ -25,12 +25,15 @@ import java.util.Map;
*/
public interface ClusterNode extends Node {
// Use {@link #clusterInfo()}
@Deprecated
Map<String, String> info();
/**
* Execute CLUSTER INFO operation.
*
* @return Map extracted via each response line splitting
* by ':' symbol
* @return value mapped by field
*/
Map<String, String> info();
Map<String, String> clusterInfo();
}

@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson;
package org.redisson.api;
import org.redisson.api.RScheduledExecutorService;
import org.redisson.executor.CronExpression;
/**
@ -27,7 +26,7 @@ import org.redisson.executor.CronExpression;
* @author Nikita Koksharov
*
*/
public class CronSchedule {
public final class CronSchedule {
private CronExpression expression;

@ -86,7 +86,7 @@ public class LocalCachedMapOptions {
}
/**
* Sets cache size. If size is <code>0</code> then cache is unbounded.
* Sets cache size. If size is <code>0</code> then local cache is unbounded.
*
* @param cacheSize - size of cache
* @return LocalCachedMapOptions instance

@ -16,6 +16,7 @@
package org.redisson.api;
import java.net.InetSocketAddress;
import java.util.Map;
/**
* Redis node interface
@ -23,7 +24,11 @@ import java.net.InetSocketAddress;
* @author Nikita Koksharov
*
*/
public interface Node {
public interface Node extends NodeAsync {
enum InfoSection {ALL, DEFAULT, SERVER, CLIENTS, MEMORY, PERSISTENCE, STATS, REPLICATION, CPU, COMMANDSTATS, CLUSTER, KEYSPACE}
Map<String, String> info(InfoSection section);
/**
* Returns current Redis server time in seconds

@ -0,0 +1,38 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.api;
import java.util.Map;
import org.redisson.api.Node.InfoSection;
/**
* Redis node interface
*
* @author Nikita Koksharov
*
*/
public interface NodeAsync {
RFuture<Map<String, String>> infoAsync(InfoSection section);
RFuture<Long> timeAsync();
RFuture<Boolean> pingAsync();
RFuture<Map<String, String>> clusterInfoAsync();
}

@ -43,7 +43,15 @@ public interface NodesGroup<N extends Node> {
void removeConnectionListener(int listenerId);
/**
* Get all nodes by type
* Get Redis node by address in format: <code>host:port</code>
*
* @param address of node
* @return node
*/
N getNode(String address);
/**
* Get all Redis nodes by type
*
* @param type - type of node
* @return collection of nodes

@ -403,7 +403,9 @@ public interface RBatch {
* Command replies are skipped such approach saves response bandwidth.
* <p>
* If cluster configuration used then operations are grouped by slot ids
* and may be executed on different servers. Thus command execution order could be changed
* and may be executed on different servers. Thus command execution order could be changed.
* <p>
* NOTE: Redis 3.2+ required
*
* @throws RedisException in case of any error
*
@ -416,6 +418,8 @@ public interface RBatch {
* <p>
* If cluster configuration used then operations are grouped by slot ids
* and may be executed on different servers. Thus command execution order could be changed
* <p>
* NOTE: Redis 3.2+ required
*
* @return void
* @throws RedisException in case of any error

@ -0,0 +1,45 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.api;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Binary stream holder. Maximum size of stream is limited by available memory of Redis master node.
*
* @author Nikita Koksharov
*
*/
public interface RBinaryStream extends RBucket<byte[]> {
/**
* Returns inputStream which reads binary stream.
* This stream isn't thread-safe.
*
* @return stream
*/
InputStream getInputStream();
/**
* Returns outputStream which writes binary stream.
* This stream isn't thread-safe.
*
* @return stream
*/
OutputStream getOutputStream();
}

@ -0,0 +1,28 @@
/**
* Copyright 2016 Nikita Koksharov
*
* 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.api;
/**
* Blocking queue with fair polling and
* guarantees access order for poll and take methods.
*
* @author Nikita Koksharov
*
* @param <V> value
*/
public interface RBlockingFairQueue<V> extends RBlockingQueue<V>, RDestroyable {
}

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

Loading…
Cancel
Save