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

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

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2021 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.connection.pool;

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

import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;

/**
 * Base connection pool class 
 * 
 * @author Nikita Koksharov
 *
 * @param  - connection type
 */
abstract class ConnectionPool {

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

    protected final Queue entries = new ConcurrentLinkedQueue<>();

    final ConnectionManager connectionManager;

    final MasterSlaveServersConfig config;

    final MasterSlaveEntry masterSlaveEntry;

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

    public CompletableFuture add(ClientConnectionsEntry entry) {
        CompletableFuture promise = initConnections(entry, true);
        return promise.thenAccept(r -> {
            entries.add(entry);
        });
    }

    public CompletableFuture initConnections(ClientConnectionsEntry entry) {
        return initConnections(entry, false);
    }
    
    private CompletableFuture initConnections(ClientConnectionsEntry entry, boolean checkFreezed) {
        int minimumIdleSize = getMinimumIdleSize(entry);

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

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

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

        if ((checkFreezed && entry.isFreezed()) || !tryAcquireConnection(entry)) {
            int totalInitializedConnections = minimumIdleSize - initializedConnections.get();
            Throwable cause = new RedisConnectionException(
                    "Unable to init enough connections amount! Only " + totalInitializedConnections + " of " + minimumIdleSize + " were initialized. Server: "
                                        + entry.getClient().getAddr());
            initPromise.completeExceptionally(cause);
            return;
        }
        
        acquireConnection(entry, new Runnable() {
            
            @Override
            public void run() {
                CompletableFuture promise = new CompletableFuture();
                createConnection(entry, promise);
                promise.whenComplete((conn, e) -> {
                        if (e == null) {
                            if (!initPromise.isDone()) {
                                entry.addConnection(conn);
                            } else {
                                conn.closeAsync();
                            }
                        }

                        releaseConnection(entry);

                        if (e != null) {
                            if (initPromise.isDone()) {
                                return;
                            }
                            
                            for (RedisConnection connection : entry.getAllConnections()) {
                                if (!connection.isClosed()) {
                                    connection.closeAsync();
                                }
                            }
                            entry.getAllConnections().clear();
                            
                            for (RedisConnection connection : entry.getAllSubscribeConnections()) {
                                if (!connection.isClosed()) {
                                    connection.closeAsync();
                                }
                            }
                            entry.getAllSubscribeConnections().clear();
                            
                            int totalInitializedConnections = minimumIdleSize - initializedConnections.get();
                            String errorMsg;
                            if (totalInitializedConnections == 0) {
                                errorMsg = "Unable to connect to Redis server: " + entry.getClient().getAddr();
                            } else {
                                errorMsg = "Unable to init enough connections amount! Only " + totalInitializedConnections 
                                        + " of " + minimumIdleSize + " were initialized. Redis server: " + entry.getClient().getAddr();
                            }
                            Throwable cause = new RedisConnectionException(errorMsg, e);
                            initPromise.completeExceptionally(cause);
                            return;
                        }

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

    protected void acquireConnection(ClientConnectionsEntry entry, Runnable runnable, RedisCommand command) {
        entry.acquireConnection(runnable, command);
    }

    protected abstract int getMinimumIdleSize(ClientConnectionsEntry entry);

    public CompletableFuture get(RedisCommand command) {
        List entriesCopy = new LinkedList(entries);
        for (Iterator iterator = entriesCopy.iterator(); iterator.hasNext();) {
            ClientConnectionsEntry entry = iterator.next();
            if (!((!entry.isFreezed() || entry.isMasterForRead()) 
                    && tryAcquireConnection(entry))) {
                iterator.remove();
            }
        }
        if (!entriesCopy.isEmpty()) {
            ClientConnectionsEntry entry = config.getLoadBalancer().getEntry(entriesCopy);
            return acquireConnection(command, entry);
        }
        
        List failed = new LinkedList<>();
        List freezed = new LinkedList<>();
        for (ClientConnectionsEntry entry : entries) {
            if (entry.isFailed()) {
                failed.add(entry.getClient().getAddr());
            } else if (entry.isFreezed()) {
                freezed.add(entry.getClient().getAddr());
            }
        }

        StringBuilder errorMsg = new StringBuilder(getClass().getSimpleName() + " no available Redis entries. Master entry host: " + masterSlaveEntry.getClient().getAddr());
        if (!freezed.isEmpty()) {
            errorMsg.append(" Disconnected hosts: ").append(freezed);
        }
        if (!failed.isEmpty()) {
            errorMsg.append(" Hosts disconnected due to errors during `failedSlaveCheckInterval`: ").append(failed);
        }

        RedisConnectionException exception = new RedisConnectionException(errorMsg.toString());
        CompletableFuture result = new CompletableFuture<>();
        result.completeExceptionally(exception);
        return result;
    }

    public CompletableFuture get(RedisCommand command, ClientConnectionsEntry entry) {
            return acquireConnection(command, entry);
        }

    public abstract static class AcquireCallback implements Runnable, BiConsumer {
        
    }
    
    protected final CompletableFuture acquireConnection(RedisCommand command, ClientConnectionsEntry entry) {
        CompletableFuture result = new CompletableFuture();

        Runnable callback = () -> {
            connectTo(entry, result, command);
        };
        acquireConnection(entry, callback, command);
        return result;
    }
        
    protected boolean tryAcquireConnection(ClientConnectionsEntry entry) {
        if (entry.getNodeType() == NodeType.SLAVE && entry.isFailed()) {
            checkForReconnect(entry, null);
            return false;
        }
        return true;
    }

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

    protected CompletionStage connect(ClientConnectionsEntry entry) {
        return (CompletionStage) entry.connect();
    }

    private void connectTo(ClientConnectionsEntry entry, CompletableFuture promise, RedisCommand command) {
        if (promise.isDone()) {
            connectionManager.getGroup().submit(() -> {
                releaseConnection(entry);
            });
            return;
        }

        T conn = poll(entry, command);
        if (conn != null) {
            if (!conn.isActive() && entry.getNodeType() == NodeType.SLAVE) {
                entry.trySetupFistFail();
            }

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

        createConnection(entry, promise);
    }

    private void createConnection(ClientConnectionsEntry entry, CompletableFuture promise) {
        CompletionStage connFuture = connect(entry);
        connFuture.whenComplete((conn, e) -> {
            if (e != null) {
                promiseFailure(entry, promise, e);
                return;
            }

            if (!conn.isActive()) {
                promiseFailure(entry, promise, conn);
                return;
            }

            connectedSuccessful(entry, promise, conn);
        });
    }

    private void connectedSuccessful(ClientConnectionsEntry entry, CompletableFuture promise, T conn) {
        if (conn.isActive() && entry.getNodeType() == NodeType.SLAVE) {
            entry.resetFirstFail();
        }

        if (!promise.complete(conn)) {
            releaseConnection(entry, conn);
            releaseConnection(entry);
        }
    }

    private void promiseFailure(ClientConnectionsEntry entry, CompletableFuture promise, Throwable cause) {
        if (entry.getNodeType() == NodeType.SLAVE) {
            entry.trySetupFistFail();
            if (entry.isFailed()) {
            checkForReconnect(entry, cause);
        }
        }

        releaseConnection(entry);

        promise.completeExceptionally(cause);
    }

    private void promiseFailure(ClientConnectionsEntry entry, CompletableFuture promise, T conn) {
        if (entry.getNodeType() == NodeType.SLAVE) {
            entry.trySetupFistFail();
            if (entry.isFailed()) {
            conn.closeAsync();
            entry.getAllConnections().remove(conn);
            checkForReconnect(entry, null);
            } else {
            releaseConnection(entry, conn);
        }
        } else {
            releaseConnection(entry, conn);
        }

        releaseConnection(entry);

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

    private void checkForReconnect(ClientConnectionsEntry entry, Throwable cause) {
        if (masterSlaveEntry.slaveDown(entry, FreezeReason.RECONNECT)) {
            log.error("slave " + entry.getClient().getAddr() + " has been disconnected after " 
                        + config.getFailedSlaveCheckInterval() + " ms interval since moment of the first failed connection", cause);
            scheduleCheck(entry);
            }
        }

    private void scheduleCheck(ClientConnectionsEntry entry) {

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

        connectionManager.newTimeout(timeout -> {
            synchronized (entry) {
                if (entry.getFreezeReason() != FreezeReason.RECONNECT
                        || connectionManager.isShuttingDown()) {
                    return;
                }
            }

            CompletionStage connectionFuture = entry.getClient().connectAsync();
            connectionFuture.whenComplete((c, e) -> {
                    synchronized (entry) {
                        if (entry.getFreezeReason() != FreezeReason.RECONNECT) {
                            return;
                        }
                    }

                    if (e != null) {
                        scheduleCheck(entry);
                        return;
                    }
                    if (!c.isActive()) {
                        c.closeAsync();
                        scheduleCheck(entry);
                        return;
                    }

                    RFuture f = c.async(RedisCommands.PING);
                    f.whenComplete((t, ex) -> {
                        try {
                            synchronized (entry) {
                                if (entry.getFreezeReason() != FreezeReason.RECONNECT) {
                                    return;
                                }
                            }

                            if ("PONG".equals(t)) {
                                if (masterSlaveEntry.slaveUp(entry, FreezeReason.RECONNECT)) {
                                    log.info("slave {} has been successfully reconnected", entry.getClient().getAddr());
                                }
                            } else {
                                scheduleCheck(entry);
                            }
                        } finally {
                            c.closeAsync();
                        }
                    });
            });
        }, config.getFailedSlaveReconnectionInterval(), TimeUnit.MILLISECONDS);
    }

    public void returnConnection(ClientConnectionsEntry entry, T connection) {
        if (entry == null) {
            connection.closeAsync();
            return;
        }
        if (entry.isFreezed() && entry.getFreezeReason() != FreezeReason.SYSTEM) {
            connection.closeAsync();
            entry.getAllConnections().remove(connection);
        } 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