All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redisson.connection.pool.ConnectionPool Maven / Gradle / Ivy

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

import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.redisson.MasterSlaveServersConfig;
import org.redisson.client.RedisConnection;
import org.redisson.client.RedisConnectionException;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.connection.ClientConnectionsEntry;
import org.redisson.connection.ClientConnectionsEntry.FreezeReason;
import org.redisson.connection.ClientConnectionsEntry.NodeType;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;

abstract class ConnectionPool {

    private final Logger log = LoggerFactory.getLogger(getClass());

    protected final List entries = new CopyOnWriteArrayList();

    final ConnectionManager connectionManager;

    final MasterSlaveServersConfig config;

    final MasterSlaveEntry masterSlaveEntry;

    public ConnectionPool(MasterSlaveServersConfig config, ConnectionManager connectionManager, MasterSlaveEntry masterSlaveEntry) {
        this.config = config;
        this.masterSlaveEntry = masterSlaveEntry;
        this.connectionManager = connectionManager;
    }

    public Future add(final ClientConnectionsEntry entry) {
        final Promise promise = connectionManager.newPromise();
        promise.addListener(new FutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                entries.add(entry);
            }
        });
        initConnections(entry, promise, true);
        return promise;
    }

    private void initConnections(final ClientConnectionsEntry entry, final Promise initPromise, boolean checkFreezed) {
        final int minimumIdleSize = getMinimumIdleSize(entry);

        if (minimumIdleSize == 0 || (checkFreezed && entry.isFreezed())) {
            initPromise.setSuccess(null);
            return;
        }

        final AtomicInteger initializedConnections = new AtomicInteger(minimumIdleSize);
        int startAmount = Math.min(50, minimumIdleSize);
        final AtomicInteger requests = new AtomicInteger(startAmount);
        for (int i = 0; i < startAmount; i++) {
            createConnection(checkFreezed, requests, entry, initPromise, minimumIdleSize, initializedConnections);
        }
    }

    private void createConnection(final boolean checkFreezed, final AtomicInteger requests, final ClientConnectionsEntry entry, final Promise initPromise,
            final int minimumIdleSize, final AtomicInteger initializedConnections) {

        if ((checkFreezed && entry.isFreezed()) || !tryAcquireConnection(entry)) {
            Throwable cause = new RedisConnectionException(
                    "Can't init enough connections amount! Only " + (minimumIdleSize - initializedConnections.get()) + " from " + minimumIdleSize + " were initialized. Server: "
                                        + entry.getClient().getAddr());
            initPromise.tryFailure(cause);
            return;
        }

        Future promise = createConnection(entry);
        promise.addListener(new FutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                if (future.isSuccess()) {
                    T conn = future.getNow();
                    releaseConnection(entry, conn);
                }

                releaseConnection(entry);

                if (!future.isSuccess()) {
                    Throwable cause = new RedisConnectionException(
                            "Can't init enough connections amount! Only " + (minimumIdleSize - initializedConnections.get()) + " from " + minimumIdleSize + " were initialized. Server: "
                                                + entry.getClient().getAddr(), future.cause());
                    initPromise.tryFailure(cause);
                    return;
                }

                int value = initializedConnections.decrementAndGet();
                if (value == 0) {
                    log.info("{} connections initialized for {}", minimumIdleSize, entry.getClient().getAddr());
                    initPromise.setSuccess(null);
                } else if (value > 0 && !initPromise.isDone()) {
                    if (requests.incrementAndGet() <= minimumIdleSize) {
                        createConnection(checkFreezed, requests, entry, initPromise, minimumIdleSize, initializedConnections);
                    }
                }
            }
        });
    }

    protected abstract int getMinimumIdleSize(ClientConnectionsEntry entry);

    protected ClientConnectionsEntry getEntry() {
        return config.getLoadBalancer().getEntry(entries);
    }

    public Future get() {
        for (int j = entries.size() - 1; j >= 0; j--) {
            ClientConnectionsEntry entry = getEntry();
            if (!entry.isFreezed() && tryAcquireConnection(entry)) {
                return connectTo(entry);
            }
        }

        List zeroConnectionsAmount = new LinkedList();
        List freezed = new LinkedList();
        for (ClientConnectionsEntry entry : entries) {
            if (entry.isFreezed()) {
                freezed.add(entry.getClient().getAddr());
            } else {
                zeroConnectionsAmount.add(entry.getClient().getAddr());
            }
        }

        StringBuilder errorMsg = new StringBuilder("Connection pool exhausted! All connections are busy. Try to increase connection pool size.");
//        if (!freezed.isEmpty()) {
//            errorMsg.append(" Disconnected hosts: " + freezed);
//        }
        if (!zeroConnectionsAmount.isEmpty()) {
            errorMsg.append(" Hosts with fully busy connections: " + zeroConnectionsAmount);
        }

        RedisConnectionException exception = new RedisConnectionException(errorMsg.toString());
        return connectionManager.newFailedFuture(exception);
    }

    public Future get(ClientConnectionsEntry entry) {
        if (((entry.getNodeType() == NodeType.MASTER && entry.getFreezeReason() == FreezeReason.SYSTEM) || !entry.isFreezed())
                && tryAcquireConnection(entry)) {
            return connectTo(entry);
        }

        RedisConnectionException exception = new RedisConnectionException(
                "Can't aquire connection to " + entry.getClient().getAddr());
        return connectionManager.newFailedFuture(exception);
    }

    protected boolean tryAcquireConnection(ClientConnectionsEntry entry) {
        return entry.getFailedAttempts() < config.getFailedAttempts() && entry.tryAcquireConnection();
    }

    protected T poll(ClientConnectionsEntry entry) {
        return (T) entry.pollConnection();
    }

    protected Future connect(ClientConnectionsEntry entry) {
        return (Future) entry.connect(config);
    }

    private Future connectTo(final ClientConnectionsEntry entry) {
        T conn = poll(entry);
        if (conn != null) {
            if (!conn.isActive()) {
                return promiseFailure(entry, conn);
            }

            return promiseSuccessful(entry, conn);
        }

        return createConnection(entry);
    }

    private Future createConnection(final ClientConnectionsEntry entry) {
        final Promise promise = connectionManager.newPromise();
        Future connFuture = connect(entry);
        connFuture.addListener(new FutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                if (!future.isSuccess()) {
                    releaseConnection(entry);

                    promiseFailure(entry, promise, future.cause());
                    return;
                }

                T conn = future.getNow();
                if (!conn.isActive()) {
                    promiseFailure(entry, promise, conn);
                    return;
                }

                promiseSuccessful(entry, promise, conn);
            }
        });
        return promise;
    }

    private void promiseSuccessful(ClientConnectionsEntry entry, Promise promise, T conn) {
        entry.resetFailedAttempts();
        if (!promise.trySuccess(conn)) {
            releaseConnection(entry, conn);
            releaseConnection(entry);
        }
    }

    private Future promiseSuccessful(ClientConnectionsEntry entry, T conn) {
        entry.resetFailedAttempts();
        return (Future) conn.getAcquireFuture();
    }

    private void promiseFailure(ClientConnectionsEntry entry, Promise promise, Throwable cause) {
        if (entry.incFailedAttempts() == config.getFailedAttempts()) {
            checkForReconnect(entry);
        }

        promise.tryFailure(cause);
    }

    private void promiseFailure(ClientConnectionsEntry entry, Promise promise, T conn) {
        int attempts = entry.incFailedAttempts();
        if (attempts == config.getFailedAttempts()) {
            checkForReconnect(entry);
        } else if (attempts < config.getFailedAttempts()) {
            releaseConnection(entry, conn);
        }

        releaseConnection(entry);

        RedisConnectionException cause = new RedisConnectionException(conn + " is not active!");
        promise.tryFailure(cause);
    }

    private Future promiseFailure(ClientConnectionsEntry entry, T conn) {
        int attempts = entry.incFailedAttempts();
        if (attempts == config.getFailedAttempts()) {
            checkForReconnect(entry);
        } else if (attempts < config.getFailedAttempts()) {
            releaseConnection(entry, conn);
        }

        releaseConnection(entry);

        RedisConnectionException cause = new RedisConnectionException(conn + " is not active!");
        return connectionManager.newFailedFuture(cause);
    }

    private void checkForReconnect(ClientConnectionsEntry entry) {
        if (entry.getNodeType() == NodeType.SLAVE) {
            connectionManager.slaveDown(masterSlaveEntry, entry.getClient().getAddr().getHostName(),
                    entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT);
            log.warn("slave {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts());
            scheduleCheck(entry);
        } else {
            if (entry.freezeMaster(FreezeReason.RECONNECT)) {
                log.warn("host {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts());
                scheduleCheck(entry);
            }
        }
    }

    private void scheduleCheck(final ClientConnectionsEntry entry) {

        connectionManager.getConnectionEventsHub().fireDisconnect(entry.getClient().getAddr());

        connectionManager.newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                if (entry.getFreezeReason() != FreezeReason.RECONNECT
                        || !entry.isFreezed()) {
                    return;
                }

                Future connectionFuture = entry.getClient().connectAsync();
                connectionFuture.addListener(new FutureListener() {
                    @Override
                    public void operationComplete(Future future) throws Exception {
                        if (entry.getFreezeReason() != FreezeReason.RECONNECT
                                || !entry.isFreezed()) {
                            return;
                        }

                        if (!future.isSuccess()) {
                            scheduleCheck(entry);
                            return;
                        }
                        final RedisConnection c = future.getNow();
                        if (!c.isActive()) {
                            c.closeAsync();
                            scheduleCheck(entry);
                            return;
                        }

                        Future f = c.asyncWithTimeout(null, RedisCommands.PING);
                        f.addListener(new FutureListener() {
                            @Override
                            public void operationComplete(Future future) throws Exception {
                                try {
                                    if (entry.getFreezeReason() != FreezeReason.RECONNECT
                                            || !entry.isFreezed()) {
                                        return;
                                    }

                                    if (future.isSuccess() && "PONG".equals(future.getNow())) {
                                        entry.resetFailedAttempts();
                                        Promise promise = connectionManager.newPromise();
                                        promise.addListener(new FutureListener() {
                                            @Override
                                            public void operationComplete(Future future)
                                                    throws Exception {
                                                if (entry.getNodeType() == NodeType.SLAVE) {
                                                    masterSlaveEntry.slaveUp(entry.getClient().getAddr().getHostName(), entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT);
                                                    log.info("slave {} successfully reconnected", entry.getClient().getAddr());
                                                } else {
                                                    synchronized (entry) {
                                                        if (entry.getFreezeReason() == FreezeReason.RECONNECT) {
                                                            entry.setFreezed(false);
                                                            entry.setFreezeReason(null);
                                                            log.info("host {} successfully reconnected", entry.getClient().getAddr());
                                                        }
                                                    }
                                                }
                                            }
                                        });
                                        initConnections(entry, promise, false);
                                    } else {
                                        scheduleCheck(entry);
                                    }
                                } finally {
                                    c.closeAsync();
                                }
                            }
                        });
                    }
                });
            }
        }, config.getReconnectionTimeout(), TimeUnit.MILLISECONDS);
    }

    public void returnConnection(ClientConnectionsEntry entry, T connection) {
        if (entry.isFreezed()) {
            connection.closeAsync();
        } else {
            releaseConnection(entry, connection);
        }
        releaseConnection(entry);
    }

    protected void releaseConnection(ClientConnectionsEntry entry) {
        entry.releaseConnection();
    }

    protected void releaseConnection(ClientConnectionsEntry entry, T conn) {
        entry.releaseConnection(conn);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy