/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.connection;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.redisson.api.NodeType;
import org.redisson.api.RFuture;
import org.redisson.client.ReconnectListener;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisConnection;
import org.redisson.client.RedisPubSubConnection;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.misc.RPromise;
import org.redisson.pubsub.AsyncSemaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
public class ClientConnectionsEntry {
final Logger log = LoggerFactory.getLogger(getClass());
private final Queue<RedisPubSubConnection> allSubscribeConnections = new ConcurrentLinkedQueue<RedisPubSubConnection>();
private final Queue<RedisPubSubConnection> freeSubscribeConnections = new ConcurrentLinkedQueue<RedisPubSubConnection>();
private final AsyncSemaphore freeSubscribeConnectionsCounter;
private final Queue<RedisConnection> freeConnections = new ConcurrentLinkedQueue<RedisConnection>();
private final AsyncSemaphore freeConnectionsCounter;
public enum FreezeReason {MANAGER, RECONNECT, SYSTEM}
private volatile boolean freezed;
private FreezeReason freezeReason;
final RedisClient client;
private final NodeType nodeType;
private ConnectionManager connectionManager;
private final AtomicInteger failedAttempts = new AtomicInteger();
public ClientConnectionsEntry(RedisClient client, int poolMinSize, int poolMaxSize, int subscribePoolMinSize, int subscribePoolMaxSize,
ConnectionManager connectionManager, NodeType serverMode) {
this.client = client;
this.freeConnectionsCounter = new AsyncSemaphore(poolMaxSize);
this.connectionManager = connectionManager;
this.nodeType = serverMode;
this.freeSubscribeConnectionsCounter = new AsyncSemaphore(subscribePoolMaxSize);
if (subscribePoolMaxSize > 0) {
connectionManager.getConnectionWatcher().add(subscribePoolMinSize, subscribePoolMaxSize, freeSubscribeConnections, freeSubscribeConnectionsCounter);
}
connectionManager.getConnectionWatcher().add(poolMinSize, poolMaxSize, freeConnections, freeConnectionsCounter);
}
public NodeType getNodeType() {
return nodeType;
}
public void resetFailedAttempts() {
failedAttempts.set(0);
}
public int getFailedAttempts() {
return failedAttempts.get();
}
public int incFailedAttempts() {
return failedAttempts.incrementAndGet();
}
public RedisClient getClient() {
return client;
}
public boolean isFreezed() {
return freezed;
}
public void setFreezeReason(FreezeReason freezeReason) {
this.freezeReason = freezeReason;
}
public FreezeReason getFreezeReason() {
return freezeReason;
}
public void setFreezed(boolean freezed) {
this.freezed = freezed;
}
public int getFreeAmount() {
return freeConnectionsCounter.getCounter();
}
public void acquireConnection(Runnable runnable) {
freeConnectionsCounter.acquire(runnable);
}
public void removeConnection(Runnable runnable) {
freeConnectionsCounter.remove(runnable);
}
public void releaseConnection() {
freeConnectionsCounter.release();
}
public RedisConnection pollConnection() {
return freeConnections.poll();
}
public void releaseConnection(RedisConnection connection) {
connection.setLastUsageTime(System.currentTimeMillis());
freeConnections.add(connection);
}
public RFuture<RedisConnection> connect() {
final RPromise<RedisConnection> connectionFuture = connectionManager.newPromise();
RFuture<RedisConnection> future = client.connectAsync();
future.addListener(new FutureListener<RedisConnection>() {
@Override
public void operationComplete(Future<RedisConnection> future) throws Exception {
if (!future.isSuccess()) {
connectionFuture.tryFailure(future.cause());
return;
}
RedisConnection conn = future.getNow();
log.debug("new connection created: {}", conn);
addReconnectListener(connectionFuture, conn);
}
});
return connectionFuture;
}
private <T extends RedisConnection> void addReconnectListener(RPromise<T> connectionFuture, T conn) {
addFireEventListener(conn, connectionFuture);
conn.setReconnectListener(new ReconnectListener() {
@Override
public void onReconnect(RedisConnection conn, RPromise<RedisConnection> connectionFuture) {
addFireEventListener(conn, connectionFuture);
}
});
}
private <T extends RedisConnection> void addFireEventListener(T conn, RPromise<T> connectionFuture) {
connectionManager.getConnectListener().onConnect(connectionFuture, conn, nodeType, connectionManager.getConfig());
if (connectionFuture.isSuccess()) {
connectionManager.getConnectionEventsHub().fireConnect(connectionFuture.getNow().getRedisClient().getAddr());
return;
}
connectionFuture.addListener(new FutureListener<T>() {
@Override
public void operationComplete(Future<T> future) throws Exception {
if (future.isSuccess()) {
connectionManager.getConnectionEventsHub().fireConnect(future.getNow().getRedisClient().getAddr());
}
}
});
}
public MasterSlaveServersConfig getConfig() {
return connectionManager.getConfig();
}
public RFuture<RedisPubSubConnection> connectPubSub() {
final RPromise<RedisPubSubConnection> connectionFuture = connectionManager.newPromise();
RFuture<RedisPubSubConnection> future = client.connectPubSubAsync();
future.addListener(new FutureListener<RedisPubSubConnection>() {
@Override
public void operationComplete(Future<RedisPubSubConnection> future) throws Exception {
if (!future.isSuccess()) {
connectionFuture.tryFailure(future.cause());
return;
}
RedisPubSubConnection conn = future.getNow();
log.debug("new pubsub connection created: {}", conn);
addReconnectListener(connectionFuture, conn);
allSubscribeConnections.add(conn);
}
});
return connectionFuture;
}
public Queue<RedisPubSubConnection> getAllSubscribeConnections() {
return allSubscribeConnections;
}
public RedisPubSubConnection pollSubscribeConnection() {
return freeSubscribeConnections.poll();
}
public void releaseSubscribeConnection(RedisPubSubConnection connection) {
connection.setLastUsageTime(System.currentTimeMillis());
freeSubscribeConnections.add(connection);
}
public void acquireSubscribeConnection(Runnable runnable) {
freeSubscribeConnectionsCounter.acquire(runnable);
}
public void releaseSubscribeConnection() {
freeSubscribeConnectionsCounter.release();
}
public boolean freezeMaster(FreezeReason reason) {
synchronized (this) {
setFreezed(true);
// only RECONNECT freeze reason could be replaced
if (getFreezeReason() == null
|| getFreezeReason() == FreezeReason.RECONNECT) {
setFreezeReason(reason);
return true;
}
}
return false;
}
@Override
public String toString() {
return "[freeSubscribeConnectionsAmount=" + freeSubscribeConnections.size()
+ ", freeSubscribeConnectionsCounter=" + freeSubscribeConnectionsCounter
+ ", freeConnectionsAmount=" + freeConnections.size() + ", freeConnectionsCounter="
+ freeConnectionsCounter + ", freezed=" + freezed + ", freezeReason=" + freezeReason
+ ", client=" + client + ", nodeType=" + nodeType + ", failedAttempts=" + failedAttempts
+ "]";
}
}