Full Code of CodisLabs/jodis for AI

master 6fd0b153e408 cached
14 files
50.9 KB
11.8k tokens
65 symbols
1 requests
Download .txt
Repository: CodisLabs/jodis
Branch: master
Commit: 6fd0b153e408
Files: 14
Total size: 50.9 KB

Directory structure:
gitextract_z0l835z5/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
├── src/
│   ├── main/
│   │   └── java/
│   │       └── io/
│   │           └── codis/
│   │               └── jodis/
│   │                   ├── BoundedExponentialBackoffRetryUntilElapsed.java
│   │                   ├── CodisProxyInfo.java
│   │                   ├── JedisPoolAdaptor.java
│   │                   ├── JedisResourcePool.java
│   │                   └── RoundRobinJedisPool.java
│   └── test/
│       └── java/
│           └── io/
│               └── codis/
│                   └── jodis/
│                       ├── RedisServer.java
│                       ├── TestBoundedExponentialBackoffRetryUntilElapsed.java
│                       └── TestRoundRobinJedisPool.java
└── wandoujia-LICENSE

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.classpath
.project
.settings
target


================================================
FILE: .travis.yml
================================================
language: java

jdk:
  - oraclejdk8

install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true -B -V


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015 CodisLabs.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# Jodis - Java client for codis

[![Build Status](https://travis-ci.org/CodisLabs/jodis.svg)](https://travis-ci.org/CodisLabs/jodis)

Jodis is a java client for codis based on [Jedis](https://github.com/xetorthio/jedis) and [Curator](http://curator.apache.org/).

# Features
- Use a round robin policy to balance load to multiple codis proxies.
- Detect proxy online and offline automatically.

# How to use
Add this to your pom.xml. We deploy jodis to https://oss.sonatype.org.
```xml
<dependency>
  <groupId>io.codis.jodis</groupId>
  <artifactId>jodis</artifactId>
  <version>0.5.1</version>
</dependency>
```
To use it for Codis2.x:
```java
JedisResourcePool jedisPool = RoundRobinJedisPool.create()
        .curatorClient("zkserver:2181", 30000).zkProxyDir("/zk/codis/db_xxx/proxy").build();
try (Jedis jedis = jedisPool.getResource()) {
    jedis.set("foo", "bar");
    String value = jedis.get("foo");
    System.out.println(value);
}
```
Or for Codis3.x with `jodis_compatible=false`:
```java
JedisResourcePool jedisPool = RoundRobinJedisPool.create()
        .curatorClient("zkserver:2181", 30000).zkProxyDir("/jodis/xxx").build();
try (Jedis jedis = jedisPool.getResource()) {
    jedis.set("foo", "bar");
    String value = jedis.get("foo");
    System.out.println(value);
}
```
Note: JDK8 is required to use and build jodis, as JDK7 has been EOL since May 2015.


================================================
FILE: pom.xml
================================================
<!--
 Copyright (c) 2014 CodisLabs.

 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
 "Software"), to deal in the Software without restriction, including
 without limitation the rights to use, copy, modify, merge, publish,
 distribute, sublicense, and/or sell copies of the Software, and to
 permit persons to whom the Software is furnished to do so, subject to
 the following conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.codis.jodis</groupId>
    <artifactId>jodis</artifactId>
    <version>0.6.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>Round Robin jedis pool for codis</description>
    <url>https://github.com/CodisLabs/jodis</url>
    <scm>
        <connection>scm:git:ssh://github.com:CodisLabs/jodis.git</connection>
        <developerConnection>scm:git:ssh://github.com:CodisLabs/jodis.git</developerConnection>
        <url>ssh://github.com:CodisLabs/codis.git</url>
        <tag>HEAD</tag>
    </scm>
    <properties>
        <java.version>1.7</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
        <maven.compile.encoding>UTF-8</maven.compile.encoding>
        <zookeeper.version>3.4.11</zookeeper.version>
        <curator.version>4.0.1</curator.version>
        <curator-test.version>2.12.0</curator-test.version>
        <jedis.version>2.9.0</jedis.version>
        <jackson.version>2.9.5</jackson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>${zookeeper.version}</version>
        </dependency>  
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>${curator.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.12</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-test</artifactId>
            <version>${curator-test.version}</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <distributionManagement>
        <snapshotRepository>
            <id>ossrh</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        </snapshotRepository>
        <repository>
            <id>ossrh</id>
            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
        </repository>
    </distributionManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.10.3</version>
                <configuration>
                    <additionalparam>-Xdoclint:none</additionalparam>
                </configuration>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-gpg-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                        <id>sign-artifacts</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>sign</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.sonatype.plugins</groupId>
                <artifactId>nexus-staging-maven-plugin</artifactId>
                <version>1.6.7</version>
                <extensions>true</extensions>
                <configuration>
                    <serverId>ossrh</serverId>
                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                    <autoReleaseAfterClose>false</autoReleaseAfterClose>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.7.5.201505241946</version>
                <executions>
                    <execution>
                        <id>default-prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-report</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
                <configuration>
                    <forkMode>always</forkMode>
                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
                    <argLine>${argLine} -Dfile.encoding=UTF-8</argLine>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.8.2</version>
                <executions>
                    <execution>
                        <id>deploy</id>
                        <phase>deploy</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-deploy</id>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.4</version>
                <configuration>
                    <reportPlugins>
                        <plugin>
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>findbugs-maven-plugin</artifactId>
                        </plugin>
                        <plugin>
                            <groupId>org.jacoco</groupId>
                            <artifactId>jacoco-maven-plugin</artifactId>
                        </plugin>
                    </reportPlugins>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>findbugs-maven-plugin</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <xmlOutput>true</xmlOutput>
                    <xmlOutputDirectory>target/findbugs</xmlOutputDirectory>
                    <findbugsXmlOutputDirectory>target/findbugs</findbugsXmlOutputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <licenses>
        <license>
            <name>MIT License</name>
            <url>http://opensource.org/licenses/MIT</url>
            <distribution>repo</distribution>
        </license>
    </licenses>
    <developers>
        <developer>
            <id>apache9</id>
            <name>Apache9</name>
            <email>palomino219@gmail.com</email>
            <roles>
                <role>developer</role>
            </roles>
            <timezone>+8</timezone>
        </developer>
    </developers>
</project>


================================================
FILE: src/main/java/io/codis/jodis/BoundedExponentialBackoffRetryUntilElapsed.java
================================================
/**
 * @(#)BoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. 
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import org.apache.curator.RetryPolicy;
import org.apache.curator.RetrySleeper;

/**
 * Similar to {@link org.apache.curator.retry.BoundedExponentialBackoffRetry},
 * but limit the retry elapsed time, not retry number.
 * 
 * @author Apache9
 */
public class BoundedExponentialBackoffRetryUntilElapsed implements RetryPolicy {

    private final int baseSleepTimeMs;

    private final int maxSleepTimeMs;

    private final long maxElapsedTimeMs;

    /**
     * @param baseSleepTimeMs
     *            initial amount of time to wait between retries
     * @param maxSleepTimeMs
     *            max time in ms to sleep on each retry
     * @param maxElapsedTimeMs
     *            total time in ms to retry
     */
    public BoundedExponentialBackoffRetryUntilElapsed(int baseSleepTimeMs, int maxSleepTimeMs,
            long maxElapsedTimeMs) {
        this.baseSleepTimeMs = baseSleepTimeMs;
        this.maxSleepTimeMs = maxSleepTimeMs;
        if (maxElapsedTimeMs < 0) {
            this.maxElapsedTimeMs = Long.MAX_VALUE;
        } else {
            this.maxElapsedTimeMs = maxElapsedTimeMs;
        }
    }

    private long getSleepTimeMs(int retryCount, long elapsedTimeMs) {
        return Math.min(
                maxSleepTimeMs,
                (long) baseSleepTimeMs
                        * Math.max(
                                1,
                                ThreadLocalRandom.current().nextInt(
                                        1 << Math.min(30, retryCount + 1))));
    }

    @Override
    public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper) {
        if (elapsedTimeMs >= maxElapsedTimeMs) {
            return false;
        }
        long sleepTimeMs = Math.min(maxElapsedTimeMs - elapsedTimeMs,
                getSleepTimeMs(retryCount, elapsedTimeMs));
        try {
            sleeper.sleepFor(sleepTimeMs, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
        return true;
    }
}


================================================
FILE: src/main/java/io/codis/jodis/CodisProxyInfo.java
================================================
/**
 * @(#)CodisProxyInfo.java, 2015-10-14. 
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

/**
 * We only care about the proxy address and if it is online.
 * 
 * @author Apache9
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public class CodisProxyInfo {

    private String addr;

    private String state;

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}


================================================
FILE: src/main/java/io/codis/jodis/JedisPoolAdaptor.java
================================================
/**
 * @(#)JedisPoolAdaptor.java, 2014-12-2. 
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import java.net.URI;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import redis.clients.jedis.JedisPool;

/**
 * Adaptor of JedisPool to make writing testcase easier.
 * 
 * @author Apache9
 */
public class JedisPoolAdaptor extends JedisPool implements JedisResourcePool {

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,
            String password, int database, String clientName) {
        super(poolConfig, host, port, timeout, password, database, clientName);
    }

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,
            String password, int database) {
        super(poolConfig, host, port, timeout, password, database);
    }

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout,
            String password) {
        super(poolConfig, host, port, timeout, password);
    }

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port, int timeout) {
        super(poolConfig, host, port, timeout);
    }

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host, int port) {
        super(poolConfig, host, port);
    }

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String host) {
        super(poolConfig, host);
    }

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri, int timeout) {
        super(poolConfig, uri, timeout);
    }

    public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri) {
        super(poolConfig, uri);
    }

    public JedisPoolAdaptor(String host, int port) {
        super(host, port);
    }

    public JedisPoolAdaptor(String host) {
        super(host);
    }

    public JedisPoolAdaptor(URI uri, int timeout) {
        super(uri, timeout);
    }

    public JedisPoolAdaptor(URI uri) {
        super(uri);
    }

}


================================================
FILE: src/main/java/io/codis/jodis/JedisResourcePool.java
================================================
/**
 * @(#)JedisResourcePool.java, 2014-12-2. 
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import java.io.Closeable;

import redis.clients.jedis.Jedis;

/**
 * Describe a pool which we can acquire jedis instance.
 * 
 * @author Apache9
 */
public interface JedisResourcePool extends Closeable {

    /**
     * Get a jedis instance from pool.
     * <p>
     * We do not have a returnResource method, just close the jedis instance
     * returned directly.
     */
    Jedis getResource();
}


================================================
FILE: src/main/java/io/codis/jodis/RoundRobinJedisPool.java
================================================
/**
 * @(#)RoundRobinJedisPool.java, 2014-11-30.
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import static org.apache.curator.framework.imps.CuratorFrameworkState.LATENT;
import static org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode.BUILD_INITIAL_CACHE;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_UPDATED;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Closeables;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisException;

/**
 * A round robin connection pool for connecting multiple codis proxies based on
 * Jedis and Curator.
 * 
 * @author Apache9
 * @see https://github.com/xetorthio/jedis
 * @see http://curator.apache.org/
 */
public class RoundRobinJedisPool implements JedisResourcePool {

    private static final Logger LOG = LoggerFactory.getLogger(RoundRobinJedisPool.class);

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private static final String CODIS_PROXY_STATE_ONLINE = "online";

    private static final int CURATOR_RETRY_BASE_SLEEP_MS = 100;

    private static final int CURATOR_RETRY_MAX_SLEEP_MS = 30 * 1000;

    private static final long DELAY_BEFORE_CLOSING_POOL = 10000; // milliseconds

    private static final ImmutableSet<PathChildrenCacheEvent.Type> RESET_TYPES = Sets
            .immutableEnumSet(CHILD_ADDED, CHILD_UPDATED, CHILD_REMOVED);

    private final CuratorFramework curatorClient;

    private final boolean closeCurator;

    private final PathChildrenCache watcher;

    private static final class PooledObject {
        public final String addr;

        public final JedisPool pool;

        public PooledObject(String addr, JedisPool pool) {
            this.addr = addr;
            this.pool = pool;
        }

        public Jedis getResource() {
            return pool.getResource();
        }

        public void close() {
            try {
                pool.close();
                LOG.info("Connection pool to {} closed", addr);
            } catch (Exception e) {
                LOG.error("Error closing connection pool to " + addr, e);
            }
        }
    }

    private volatile ImmutableList<PooledObject> pools = ImmutableList.of();

    private final AtomicInteger nextIdx = new AtomicInteger(-1);

    private final JedisPoolConfig poolConfig;

    private final int connectionTimeoutMs;

    private final int soTimeoutMs;

    private final String password;

    private final int database;

    private final String clientName;

    private final ScheduledThreadPoolExecutor jedisPoolClosingExecutor =
            new ScheduledThreadPoolExecutor(1);

    private RoundRobinJedisPool(CuratorFramework curatorClient, boolean closeCurator,
            String zkProxyDir, JedisPoolConfig poolConfig, int connectionTimeoutMs, int soTimeoutMs,
            String password, int database, String clientName) {
        this.poolConfig = poolConfig;
        this.connectionTimeoutMs = connectionTimeoutMs;
        this.soTimeoutMs = soTimeoutMs;
        this.password = password;
        this.database = database;
        this.clientName = clientName;
        this.curatorClient = curatorClient;
        this.closeCurator = closeCurator;
        watcher = new PathChildrenCache(curatorClient, zkProxyDir, true);
        watcher.getListenable().addListener(new PathChildrenCacheListener() {

            private void logEvent(PathChildrenCacheEvent event) {
                StringBuilder msg = new StringBuilder("Receive child event: ");
                msg.append("type=").append(event.getType());
                ChildData data = event.getData();
                if (data != null) {
                    msg.append(", path=").append(data.getPath());
                    msg.append(", stat=").append(data.getStat());
                    if (data.getData() != null) {
                        msg.append(", bytes length=").append(data.getData().length);
                    } else {
                        msg.append(", no bytes");
                    }
                } else {
                    msg.append(", no data");
                }
                LOG.info(msg.toString());
            }

            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
                    throws Exception {
                synchronized (RoundRobinJedisPool.this) {
                    logEvent(event);
                    if (RESET_TYPES.contains(event.getType())) {
                        resetPools();
                    }
                }
            }
        });
        synchronized (this) {
            try {
                watcher.start(BUILD_INITIAL_CACHE);
            } catch (Exception e) {
                close();
                throw new JedisException(e);
            }
            resetPools();
        }
    }

    private void resetPools() {
        ImmutableList<PooledObject> pools = this.pools;
        Map<String, PooledObject> addr2Pool = Maps.newHashMapWithExpectedSize(pools.size());
        for (PooledObject pool: pools) {
            addr2Pool.put(pool.addr, pool);
        }
        ImmutableList.Builder<PooledObject> builder = ImmutableList.builder();
        for (ChildData childData : watcher.getCurrentData()) {
            try {
                CodisProxyInfo proxyInfo = MAPPER.readValue(childData.getData(),
                        CodisProxyInfo.class);
                if (!CODIS_PROXY_STATE_ONLINE.equals(proxyInfo.getState())) {
                    continue;
                }
                String addr = proxyInfo.getAddr();
                PooledObject pool = addr2Pool.remove(addr);
                if (pool == null) {
                    String[] hostAndPort = addr.split(":");
                    String host = hostAndPort[0];
                    int port = Integer.parseInt(hostAndPort[1]);
                    pool = new PooledObject(addr,
                            new JedisPool(poolConfig, host, port, connectionTimeoutMs, soTimeoutMs,
                                    password, database, clientName, false, null, null, null));
                    LOG.info("Add new proxy: " + addr);
                }
                builder.add(pool);
            } catch (Exception e) {
                LOG.warn("parse " + childData.getPath() + " failed", e);
            }
        }
        this.pools = builder.build();
        for (final PooledObject pool: addr2Pool.values()) {
            LOG.info("Remove proxy: " + pool.addr);
            jedisPoolClosingExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    pool.close();
                }
            }, DELAY_BEFORE_CLOSING_POOL, TimeUnit.MILLISECONDS);
        }
    }

    @Override
    public Jedis getResource() {
        ImmutableList<PooledObject> pools = this.pools;
        if (pools.isEmpty()) {
            throw new JedisException("Proxy list empty");
        }
        for (;;) {
            int current = nextIdx.get();
            int next = current >= pools.size() - 1 ? 0 : current + 1;
            if (nextIdx.compareAndSet(current, next)) {
                return pools.get(next).getResource();
            }
        }
    }

    @Override
    public void close() {
        try {
            Closeables.close(watcher, true);
        } catch (IOException e) {
            throw new AssertionError("IOException should not have been thrown", e);
        }
        if (closeCurator) {
            curatorClient.close();
        }
        List<PooledObject> pools = this.pools;
        this.pools = ImmutableList.of();
        for (PooledObject pool: pools) {
            pool.close();
        }
        jedisPoolClosingExecutor.shutdown();
    }

    /**
     * Create a {@link RoundRobinJedisPool} using the fluent style api.
     * 
     * @return
     */
    public static Builder create() {
        return new Builder();
    }

    public static final class Builder {

        private CuratorFramework curatorClient;

        private boolean closeCurator;

        private String zkProxyDir;

        private String zkAddr;

        private int zkSessionTimeoutMs;

        private JedisPoolConfig poolConfig;

        private int connectionTimeoutMs = Protocol.DEFAULT_TIMEOUT;

        private int soTimeoutMs = Protocol.DEFAULT_TIMEOUT;

        private String password;

        private int database = Protocol.DEFAULT_DATABASE;

        private String clientName;

        private Builder() {}

        /**
         * Set curator client.
         * 
         * @param curatorClient
         *            the client to be used
         * @param closeCurator
         *            whether to close curator client while closing pool
         */
        public Builder curatorClient(CuratorFramework curatorClient, boolean closeCurator) {
            this.curatorClient = curatorClient;
            this.closeCurator = closeCurator;
            return this;
        }

        /**
         * Set codis proxy path on zk.
         * 
         * @param zkProxyDir
         *            the codis proxy dir on ZooKeeper. e.g.,
         *            "/zk/codis/db_xxx/proxy"
         */
        public Builder zkProxyDir(String zkProxyDir) {
            this.zkProxyDir = zkProxyDir;
            return this;
        }

        /**
         * Set curator client.
         * <p>
         * We will create curator client based on these parameters and close it
         * while closing pool.
         * 
         * @param zkAddr
         *            ZooKeeper connect string. e.g., "zk1:2181"
         * @param zkSessionTimeoutMs
         *            ZooKeeper session timeout in milliseconds
         */
        public Builder curatorClient(String zkAddr, int zkSessionTimeoutMs) {
            this.zkAddr = zkAddr;
            this.zkSessionTimeoutMs = zkSessionTimeoutMs;
            return this;
        }

        /**
         * Set jedis pool config.
         */
        public Builder poolConfig(JedisPoolConfig poolConfig) {
            this.poolConfig = poolConfig;
            return this;
        }

        /**
         * Set jedis pool timeout in milliseconds.
         * <p>
         * We will set connectionTimeoutMs and soTimeoutMs both.
         * 
         * @param timeoutMs
         *            timeout is milliseconds
         */
        public Builder timeoutMs(int timeoutMs) {
            this.connectionTimeoutMs = this.soTimeoutMs = timeoutMs;
            return this;
        }

        /**
         * Set jedis pool connection timeout in milliseconds.
         * 
         * @param connectionTimeoutMs
         *            timeout is milliseconds
         */
        public Builder connectionTimeoutMs(int connectionTimeoutMs) {
            this.connectionTimeoutMs = connectionTimeoutMs;
            return this;
        }

        /**
         * Set jedis pool connection soTimeout in milliseconds.
         * 
         * @param soTimeoutMs
         *            timeout is milliseconds
         */
        public Builder soTimeoutMs(int soTimeoutMs) {
            this.soTimeoutMs = soTimeoutMs;
            return this;
        }

        /**
         * Set password.
         */
        public Builder password(String password) {
            this.password = password;
            return this;
        }

        /**
         * Set redis database.
         */
        public Builder database(int database) {
            this.database = database;
            return this;
        }

        /**
         * Set redis client name.
         */
        public Builder clientName(String clientName) {
            this.clientName = clientName;
            return this;
        }

        private void validate() {
            Preconditions.checkNotNull(zkProxyDir, "zkProxyDir can not be null");
            if (curatorClient == null) {
                Preconditions.checkNotNull(zkAddr, "zk client can not be null");
                curatorClient = CuratorFrameworkFactory.builder().connectString(zkAddr)
                        .sessionTimeoutMs(zkSessionTimeoutMs)
                        .retryPolicy(new BoundedExponentialBackoffRetryUntilElapsed(
                                CURATOR_RETRY_BASE_SLEEP_MS, CURATOR_RETRY_MAX_SLEEP_MS, -1L))
                        .build();
                curatorClient.start();
                closeCurator = true;
            } else {
                // we need to get the initial data so client must be started
                if (curatorClient.getState() == LATENT) {
                    curatorClient.start();
                }
            }
            if (poolConfig == null) {
                poolConfig = new JedisPoolConfig();
            }
        }

        /**
         * Create the {@link RoundRobinJedisPool}.
         */
        public RoundRobinJedisPool build() {
            validate();
            return new RoundRobinJedisPool(curatorClient, closeCurator, zkProxyDir, poolConfig,
                    connectionTimeoutMs, soTimeoutMs, password, database, clientName);
        }
    }
}


================================================
FILE: src/test/java/io/codis/jodis/RedisServer.java
================================================
/**
 * @(#)RedisServer.java, 2014-11-30. 
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import java.io.IOException;

/**
 * @author Apache9
 */
public class RedisServer {

    private final ProcessBuilder builder;

    private Process process;

    public RedisServer(int port) {
        builder = new ProcessBuilder().command("redis-server", "--port",
                Long.toString(port)).inheritIO();
    }

    public void start() throws IOException {
        process = builder.start();
    }

    public void stop() {
        if (process != null) {
            process.destroy();
        }
    }
}


================================================
FILE: src/test/java/io/codis/jodis/TestBoundedExponentialBackoffRetryUntilElapsed.java
================================================
/**
 * @(#)TestBoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. 
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import org.apache.curator.RetrySleeper;
import org.junit.Test;

/**
 * @author Apache9
 */
public class TestBoundedExponentialBackoffRetryUntilElapsed {

    private static final class FakeRetrySleeper implements RetrySleeper {

        public long sleepTimeMs;

        @Override
        public void sleepFor(long time, TimeUnit unit) {
            this.sleepTimeMs = unit.toMillis(time);
        }
    }

    @Test
    public void test() {
        FakeRetrySleeper sleeper = new FakeRetrySleeper();
        BoundedExponentialBackoffRetryUntilElapsed r = new BoundedExponentialBackoffRetryUntilElapsed(
                10, 2000, 60000);
        for (int i = 0; i < 100; i++) {
            assertTrue(r.allowRetry(i, ThreadLocalRandom.current().nextInt(60000), sleeper));
            System.out.println(sleeper.sleepTimeMs);
            assertTrue(sleeper.sleepTimeMs <= 2000);
        }
        assertTrue(r.allowRetry(1000, 59900, sleeper));
        System.out.println(sleeper.sleepTimeMs);
        assertTrue(sleeper.sleepTimeMs <= 100);
        sleeper.sleepTimeMs = -1L;
        assertFalse(r.allowRetry(1, 60000, sleeper));
        assertEquals(-1L, sleeper.sleepTimeMs);
    }
}


================================================
FILE: src/test/java/io/codis/jodis/TestRoundRobinJedisPool.java
================================================
/**
 * @(#)TestRoundRobinJedisPool.java, 2014-12-1. 
 * 
 * Copyright (c) 2014 CodisLabs.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.codis.jodis;

import static org.junit.Assert.assertEquals;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

import org.apache.curator.test.TestingServer;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.io.Closeables;

import io.codis.jodis.RoundRobinJedisPool;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisException;

/**
 * @author Apache9
 */
public class TestRoundRobinJedisPool {

    private ObjectMapper mapper = new ObjectMapper();

    private int zkPort;

    private File testDir = new File(getClass().getName());

    private TestingServer zkServer;

    private int redisPort1;

    private RedisServer redis1;

    private int redisPort2;

    private RedisServer redis2;

    private Jedis jedis1;

    private Jedis jedis2;

    private String zkProxyDir = "/" + getClass().getName();

    private RoundRobinJedisPool jodisPool;

    private static int probeFreePort() throws IOException {
        try (ServerSocket ss = new ServerSocket(0)) {
            ss.setReuseAddress(true);
            return ss.getLocalPort();
        }
    }

    private static void waitUntilRedisStarted(int port) throws InterruptedException {
        for (;;) {
            try (Jedis jedis = new Jedis("127.0.0.1", port)) {
                if ("PONG".equals(jedis.ping())) {
                    break;
                }
            } catch (JedisException e) {}
            Thread.sleep(100);
        }
    }

    private void deleteDirectory(File directory) throws IOException {
        if (!directory.exists()) {
            return;
        }
        Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                    throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                    throws IOException {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }

        });
    }

    private void addNode(String name, int port, String state)
            throws IOException, InterruptedException, KeeperException {
        ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null);
        try {
            if (zk.exists(zkProxyDir, null) == null) {
                zk.create(zkProxyDir, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            ObjectNode node = mapper.createObjectNode();
            node.put("addr", "127.0.0.1:" + port);
            node.put("state", state);
            zk.create(zkProxyDir + "/" + name, mapper.writer().writeValueAsBytes(node),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        } finally {
            zk.close();
        }
    }

    private void removeNode(String name) throws InterruptedException, KeeperException, IOException {
        ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null);
        try {
            zk.delete(zkProxyDir + "/" + name, -1);
        } finally {
            zk.close();
        }
    }

    @Before
    public void setUp() throws Exception {
        deleteDirectory(testDir);
        testDir.mkdirs();
        zkServer = new TestingServer(-1, testDir, true);
        zkPort = zkServer.getPort();
        redisPort1 = probeFreePort();
        redis1 = new RedisServer(redisPort1);
        redis1.start();
        waitUntilRedisStarted(redisPort1);
        redisPort2 = probeFreePort();
        redis2 = new RedisServer(redisPort2);
        redis2.start();
        waitUntilRedisStarted(redisPort2);

        jedis1 = new Jedis("localhost", redisPort1);
        jedis2 = new Jedis("localhost", redisPort2);
        addNode("node1", redisPort1, "online");
        jodisPool = RoundRobinJedisPool.create().curatorClient("localhost:" + zkPort, 5000)
                .zkProxyDir(zkProxyDir).build();
    }

    @After
    public void tearDown() throws IOException {
        Closeables.close(jodisPool, true);
        Closeables.close(jedis1, true);
        Closeables.close(jedis2, true);
        if (redis1 != null) {
            redis1.stop();
        }
        if (redis2 != null) {
            redis2.stop();
        }
        if (zkServer != null) {
            zkServer.stop();
        }
        deleteDirectory(testDir);
    }

    @Test
    public void test() throws IOException, InterruptedException, KeeperException {
        try (Jedis jedis = jodisPool.getResource()) {
            jedis.set("k1", "v1");
        }
        assertEquals("v1", jedis1.get("k1"));
        // fake node
        addNode("node2", 12345, "offline");
        Thread.sleep(3000);
        try (Jedis jedis = jodisPool.getResource()) {
            jedis.set("k2", "v2");
        }
        assertEquals("v2", jedis1.get("k2"));

        addNode("node3", redisPort2, "online");
        Thread.sleep(3000);
        try (Jedis jedis = jodisPool.getResource()) {
            jedis.set("k3", "v3");
        }
        assertEquals("v3", jedis2.get("k3"));

        removeNode("node1");
        Thread.sleep(3000);
        try (Jedis jedis = jodisPool.getResource()) {
            jedis.set("k4", "v4");
        }
        assertEquals("v4", jedis2.get("k4"));
    }
}


================================================
FILE: wandoujia-LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015 Wandoujia Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Download .txt
gitextract_z0l835z5/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
├── src/
│   ├── main/
│   │   └── java/
│   │       └── io/
│   │           └── codis/
│   │               └── jodis/
│   │                   ├── BoundedExponentialBackoffRetryUntilElapsed.java
│   │                   ├── CodisProxyInfo.java
│   │                   ├── JedisPoolAdaptor.java
│   │                   ├── JedisResourcePool.java
│   │                   └── RoundRobinJedisPool.java
│   └── test/
│       └── java/
│           └── io/
│               └── codis/
│                   └── jodis/
│                       ├── RedisServer.java
│                       ├── TestBoundedExponentialBackoffRetryUntilElapsed.java
│                       └── TestRoundRobinJedisPool.java
└── wandoujia-LICENSE
Download .txt
SYMBOL INDEX (65 symbols across 8 files)

FILE: src/main/java/io/codis/jodis/BoundedExponentialBackoffRetryUntilElapsed.java
  class BoundedExponentialBackoffRetryUntilElapsed (line 39) | public class BoundedExponentialBackoffRetryUntilElapsed implements Retry...
    method BoundedExponentialBackoffRetryUntilElapsed (line 55) | public BoundedExponentialBackoffRetryUntilElapsed(int baseSleepTimeMs,...
    method getSleepTimeMs (line 66) | private long getSleepTimeMs(int retryCount, long elapsedTimeMs) {
    method allowRetry (line 76) | @Override

FILE: src/main/java/io/codis/jodis/CodisProxyInfo.java
  class CodisProxyInfo (line 34) | @JsonIgnoreProperties(ignoreUnknown = true)
    method getAddr (line 41) | public String getAddr() {
    method setAddr (line 45) | public void setAddr(String addr) {
    method getState (line 49) | public String getState() {
    method setState (line 53) | public void setState(String state) {

FILE: src/main/java/io/codis/jodis/JedisPoolAdaptor.java
  class JedisPoolAdaptor (line 38) | public class JedisPoolAdaptor extends JedisPool implements JedisResource...
    method JedisPoolAdaptor (line 40) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String hos...
    method JedisPoolAdaptor (line 45) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String hos...
    method JedisPoolAdaptor (line 50) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String hos...
    method JedisPoolAdaptor (line 55) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String hos...
    method JedisPoolAdaptor (line 59) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String hos...
    method JedisPoolAdaptor (line 63) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, String hos...
    method JedisPoolAdaptor (line 67) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri, i...
    method JedisPoolAdaptor (line 71) | public JedisPoolAdaptor(GenericObjectPoolConfig poolConfig, URI uri) {
    method JedisPoolAdaptor (line 75) | public JedisPoolAdaptor(String host, int port) {
    method JedisPoolAdaptor (line 79) | public JedisPoolAdaptor(String host) {
    method JedisPoolAdaptor (line 83) | public JedisPoolAdaptor(URI uri, int timeout) {
    method JedisPoolAdaptor (line 87) | public JedisPoolAdaptor(URI uri) {

FILE: src/main/java/io/codis/jodis/JedisResourcePool.java
  type JedisResourcePool (line 36) | public interface JedisResourcePool extends Closeable {
    method getResource (line 44) | Jedis getResource();

FILE: src/main/java/io/codis/jodis/RoundRobinJedisPool.java
  class RoundRobinJedisPool (line 71) | public class RoundRobinJedisPool implements JedisResourcePool {
    class PooledObject (line 94) | private static final class PooledObject {
      method PooledObject (line 99) | public PooledObject(String addr, JedisPool pool) {
      method getResource (line 104) | public Jedis getResource() {
      method close (line 108) | public void close() {
    method RoundRobinJedisPool (line 137) | private RoundRobinJedisPool(CuratorFramework curatorClient, boolean cl...
    method resetPools (line 191) | private void resetPools() {
    method getResource (line 233) | @Override
    method close (line 248) | @Override
    method create (line 271) | public static Builder create() {
    class Builder (line 275) | public static final class Builder {
      method Builder (line 299) | private Builder() {}
      method curatorClient (line 309) | public Builder curatorClient(CuratorFramework curatorClient, boolean...
      method zkProxyDir (line 322) | public Builder zkProxyDir(String zkProxyDir) {
      method curatorClient (line 338) | public Builder curatorClient(String zkAddr, int zkSessionTimeoutMs) {
      method poolConfig (line 347) | public Builder poolConfig(JedisPoolConfig poolConfig) {
      method timeoutMs (line 360) | public Builder timeoutMs(int timeoutMs) {
      method connectionTimeoutMs (line 371) | public Builder connectionTimeoutMs(int connectionTimeoutMs) {
      method soTimeoutMs (line 382) | public Builder soTimeoutMs(int soTimeoutMs) {
      method password (line 390) | public Builder password(String password) {
      method database (line 398) | public Builder database(int database) {
      method clientName (line 406) | public Builder clientName(String clientName) {
      method validate (line 411) | private void validate() {
      method build (line 436) | public RoundRobinJedisPool build() {

FILE: src/test/java/io/codis/jodis/RedisServer.java
  class RedisServer (line 32) | public class RedisServer {
    method RedisServer (line 38) | public RedisServer(int port) {
    method start (line 43) | public void start() throws IOException {
    method stop (line 47) | public void stop() {

FILE: src/test/java/io/codis/jodis/TestBoundedExponentialBackoffRetryUntilElapsed.java
  class TestBoundedExponentialBackoffRetryUntilElapsed (line 40) | public class TestBoundedExponentialBackoffRetryUntilElapsed {
    class FakeRetrySleeper (line 42) | private static final class FakeRetrySleeper implements RetrySleeper {
      method sleepFor (line 46) | @Override
    method test (line 52) | @Test

FILE: src/test/java/io/codis/jodis/TestRoundRobinJedisPool.java
  class TestRoundRobinJedisPool (line 58) | public class TestRoundRobinJedisPool {
    method probeFreePort (line 84) | private static int probeFreePort() throws IOException {
    method waitUntilRedisStarted (line 91) | private static void waitUntilRedisStarted(int port) throws Interrupted...
    method deleteDirectory (line 102) | private void deleteDirectory(File directory) throws IOException {
    method addNode (line 125) | private void addNode(String name, int port, String state)
    method removeNode (line 142) | private void removeNode(String name) throws InterruptedException, Keep...
    method setUp (line 151) | @Before
    method tearDown (line 173) | @After
    method test (line 190) | @Test
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (55K chars).
[
  {
    "path": ".gitignore",
    "chars": 37,
    "preview": ".classpath\n.project\n.settings\ntarget\n"
  },
  {
    "path": ".travis.yml",
    "chars": 123,
    "preview": "language: java\n\njdk:\n  - oraclejdk8\n\ninstall: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true -B "
  },
  {
    "path": "LICENSE",
    "chars": 1078,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 CodisLabs.\n\nPermission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "README.md",
    "chars": 1374,
    "preview": "# Jodis - Java client for codis\n\n[![Build Status](https://travis-ci.org/CodisLabs/jodis.svg)](https://travis-ci.org/Codi"
  },
  {
    "path": "pom.xml",
    "chars": 11714,
    "preview": "<!--\n Copyright (c) 2014 CodisLabs.\n\n Permission is hereby granted, free of charge, to any person obtaining\n a copy of t"
  },
  {
    "path": "src/main/java/io/codis/jodis/BoundedExponentialBackoffRetryUntilElapsed.java",
    "chars": 3372,
    "preview": "/**\n * @(#)BoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Perm"
  },
  {
    "path": "src/main/java/io/codis/jodis/CodisProxyInfo.java",
    "chars": 1742,
    "preview": "/**\n * @(#)CodisProxyInfo.java, 2015-10-14. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, f"
  },
  {
    "path": "src/main/java/io/codis/jodis/JedisPoolAdaptor.java",
    "chars": 3127,
    "preview": "/**\n * @(#)JedisPoolAdaptor.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, "
  },
  {
    "path": "src/main/java/io/codis/jodis/JedisResourcePool.java",
    "chars": 1595,
    "preview": "/**\n * @(#)JedisResourcePool.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted,"
  },
  {
    "path": "src/main/java/io/codis/jodis/RoundRobinJedisPool.java",
    "chars": 15452,
    "preview": "/**\n * @(#)RoundRobinJedisPool.java, 2014-11-30.\n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby grante"
  },
  {
    "path": "src/test/java/io/codis/jodis/RedisServer.java",
    "chars": 1703,
    "preview": "/**\n * @(#)RedisServer.java, 2014-11-30. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby granted, free"
  },
  {
    "path": "src/test/java/io/codis/jodis/TestBoundedExponentialBackoffRetryUntilElapsed.java",
    "chars": 2628,
    "preview": "/**\n * @(#)TestBoundedExponentialBackoffRetryUntilElapsed.java, 2014-12-2. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * "
  },
  {
    "path": "src/test/java/io/codis/jodis/TestRoundRobinJedisPool.java",
    "chars": 7089,
    "preview": "/**\n * @(#)TestRoundRobinJedisPool.java, 2014-12-1. \n * \n * Copyright (c) 2014 CodisLabs.\n * \n * Permission is hereby gr"
  },
  {
    "path": "wandoujia-LICENSE",
    "chars": 1082,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Wandoujia Inc.\n\nPermission is hereby granted, free of charge, to any person ob"
  }
]

About this extraction

This page contains the full source code of the CodisLabs/jodis GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (50.9 KB), approximately 11.8k tokens, and a symbol index with 65 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!