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

org.redisson.connection.MasterSlaveConnectionManager Maven / Gradle / Ivy

/**
 * 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.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceArray;

import org.redisson.Version;
import org.redisson.api.NodeType;
import org.redisson.api.RFuture;
import org.redisson.client.BaseRedisPubSubListener;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisClientConfig;
import org.redisson.client.RedisConnection;
import org.redisson.client.RedisNodeNotFoundException;
import org.redisson.client.RedisPubSubConnection;
import org.redisson.client.RedisPubSubListener;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.pubsub.PubSubType;
import org.redisson.cluster.ClusterSlotRange;
import org.redisson.command.CommandSyncService;
import org.redisson.config.BaseMasterSlaveServersConfig;
import org.redisson.config.Config;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.misc.InfinitySemaphoreLatch;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.misc.TransferListener;
import org.redisson.misc.URIBuilder;
import org.redisson.pubsub.AsyncSemaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsServerAddressStreamProviders;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;

/**
 *
 * @author Nikita Koksharov
 *
 */
public class MasterSlaveConnectionManager implements ConnectionManager {

    private final Timeout dummyTimeout = new Timeout() {
        @Override
        public Timer timer() {
            return null;
        }

        @Override
        public TimerTask task() {
            return null;
        }

        @Override
        public boolean isExpired() {
            return false;
        }

        @Override
        public boolean isCancelled() {
            return false;
        }

        @Override
        public boolean cancel() {
            return false;
        }
    };

    public static final int MAX_SLOT = 16384;

    protected final ClusterSlotRange singleSlotRange = new ClusterSlotRange(0, MAX_SLOT-1);

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

    private HashedWheelTimer timer;

    protected Codec codec;

    protected final EventLoopGroup group;

    protected final Class socketChannelClass;

    protected final ConcurrentMap name2PubSubConnection = PlatformDependent.newConcurrentHashMap();
    
    protected final Queue freePubSubConnections = new ConcurrentLinkedQueue();

    protected DNSMonitor dnsMonitor;
    
    protected MasterSlaveServersConfig config;

    private final AtomicReferenceArray slot2entry = new AtomicReferenceArray(MAX_SLOT);
    private final Map client2entry = PlatformDependent.newConcurrentHashMap();

    private final RPromise shutdownPromise;

    private final InfinitySemaphoreLatch shutdownLatch = new InfinitySemaphoreLatch();

    private final Map clientEntries = PlatformDependent.newConcurrentHashMap();

    private IdleConnectionWatcher connectionWatcher;

    private final ConnectionEventsHub connectionEventsHub = new ConnectionEventsHub();
    
    private final AsyncSemaphore[] locks = new AsyncSemaphore[50];
    
    private final ExecutorService executor; 
    
    private final AsyncSemaphore freePubSubLock = new AsyncSemaphore(1);
    
    private final CommandSyncService commandExecutor;
    
    private final Config cfg;

    private final DnsAddressResolverGroup resolverGroup;
    
    {
        for (int i = 0; i < locks.length; i++) {
            locks[i] = new AsyncSemaphore(1);
        }
    }

    public MasterSlaveConnectionManager(MasterSlaveServersConfig cfg, Config config) {
        this(config);
        initTimer(cfg);
        this.config = cfg;
        initSingleEntry();
    }

    public MasterSlaveConnectionManager(Config cfg) {
        Version.logVersion();

        if (cfg.isUseLinuxNativeEpoll()) {
            if (cfg.getEventLoopGroup() == null) {
                this.group = new EpollEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
            } else {
                this.group = cfg.getEventLoopGroup();
            }

            this.socketChannelClass = EpollSocketChannel.class;
            this.resolverGroup = new DnsAddressResolverGroup(EpollDatagramChannel.class, DnsServerAddressStreamProviders.platformDefault());
        } else {
            if (cfg.getEventLoopGroup() == null) {
                this.group = new NioEventLoopGroup(cfg.getNettyThreads(), new DefaultThreadFactory("redisson-netty"));
            } else {
                this.group = cfg.getEventLoopGroup();
            }

            this.socketChannelClass = NioSocketChannel.class;
            this.resolverGroup = new DnsAddressResolverGroup(NioDatagramChannel.class, DnsServerAddressStreamProviders.platformDefault());
        }
        
        if (cfg.getExecutor() == null) {
            int threads = Runtime.getRuntime().availableProcessors() * 2;
            if (cfg.getThreads() != 0) {
                threads = cfg.getThreads();
            }
            executor = Executors.newFixedThreadPool(threads, new DefaultThreadFactory("redisson"));
        } else {
            executor = cfg.getExecutor();
        }

        this.cfg = cfg;
        this.codec = cfg.getCodec();
        this.shutdownPromise = newPromise();
        this.commandExecutor = new CommandSyncService(this);
    }

    public boolean isClusterMode() {
        return false;
    }
    
    public CommandSyncService getCommandExecutor() {
        return commandExecutor;
    }

    public IdleConnectionWatcher getConnectionWatcher() {
        return connectionWatcher;
    }

    public Config getCfg() {
        return cfg;
    }
    
    @Override
    public MasterSlaveServersConfig getConfig() {
        return config;
    }

    @Override
    public Codec getCodec() {
        return codec;
    }

    @Override
    public Collection getEntrySet() {
        return client2entry.values();
    }
    
    protected void initTimer(MasterSlaveServersConfig config) {
        int[] timeouts = new int[]{config.getRetryInterval(), config.getTimeout(), config.getReconnectionTimeout()};
        Arrays.sort(timeouts);
        int minTimeout = timeouts[0];
        if (minTimeout % 100 != 0) {
            minTimeout = (minTimeout % 100) / 2;
        } else if (minTimeout == 100) {
            minTimeout = 50;
        } else {
            minTimeout = 100;
        }
        
        timer = new HashedWheelTimer(Executors.defaultThreadFactory(), minTimeout, TimeUnit.MILLISECONDS, 1024);
        
        // to avoid assertion error during timer.stop invocation
        try {
            Field leakField = HashedWheelTimer.class.getDeclaredField("leak");
            leakField.setAccessible(true);
            leakField.set(timer, null);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        
        connectionWatcher = new IdleConnectionWatcher(this, config);
    }

    protected void initSingleEntry() {
        try {
            HashSet slots = new HashSet();
            slots.add(singleSlotRange);
    
            MasterSlaveEntry entry;
            if (config.checkSkipSlavesInit()) {
                entry = new SingleEntry(slots, this, config);
            } else {
                entry = createMasterSlaveEntry(config, slots);
            }
            RFuture f = entry.setupMasterEntry(config.getMasterAddress());
            f.syncUninterruptibly();
            
            for (int slot = singleSlotRange.getStartSlot(); slot < singleSlotRange.getEndSlot() + 1; slot++) {
                addEntry(slot, entry);
            }
            
            if (config.getDnsMonitoringInterval() != -1) {
                dnsMonitor = new DNSMonitor(this, f.getNow(), 
                        config.getSlaveAddresses(), config.getDnsMonitoringInterval(), resolverGroup);
                dnsMonitor.start();
            }
        } catch (RuntimeException e) {
            stopThreads();
            throw e;
        }
    }
    
    protected MasterSlaveEntry createMasterSlaveEntry(MasterSlaveServersConfig config,
            HashSet slots) {
        MasterSlaveEntry entry = new MasterSlaveEntry(slots, this, config);
        List> fs = entry.initSlaveBalancer(java.util.Collections.emptySet());
        for (RFuture future : fs) {
            future.syncUninterruptibly();
        }
        return entry;
    }

    protected MasterSlaveServersConfig create(BaseMasterSlaveServersConfig cfg) {
        MasterSlaveServersConfig c = new MasterSlaveServersConfig();
        
        c.setSslEnableEndpointIdentification(cfg.isSslEnableEndpointIdentification());
        c.setSslProvider(cfg.getSslProvider());
        c.setSslTruststore(cfg.getSslTruststore());
        c.setSslTruststorePassword(cfg.getSslTruststorePassword());
        c.setSslKeystore(cfg.getSslKeystore());
        c.setSslKeystorePassword(cfg.getSslKeystorePassword());
        
        c.setRetryInterval(cfg.getRetryInterval());
        c.setRetryAttempts(cfg.getRetryAttempts());
        c.setTimeout(cfg.getTimeout());
        c.setPingTimeout(cfg.getPingTimeout());
        c.setLoadBalancer(cfg.getLoadBalancer());
        c.setPassword(cfg.getPassword());
        c.setClientName(cfg.getClientName());
        c.setMasterConnectionPoolSize(cfg.getMasterConnectionPoolSize());
        c.setSlaveConnectionPoolSize(cfg.getSlaveConnectionPoolSize());
        c.setSubscriptionConnectionPoolSize(cfg.getSubscriptionConnectionPoolSize());
        c.setSubscriptionsPerConnection(cfg.getSubscriptionsPerConnection());
        c.setConnectTimeout(cfg.getConnectTimeout());
        c.setIdleConnectionTimeout(cfg.getIdleConnectionTimeout());

        c.setFailedAttempts(cfg.getFailedAttempts());
        c.setReconnectionTimeout(cfg.getReconnectionTimeout());
        c.setMasterConnectionMinimumIdleSize(cfg.getMasterConnectionMinimumIdleSize());
        c.setSlaveConnectionMinimumIdleSize(cfg.getSlaveConnectionMinimumIdleSize());
        c.setSubscriptionConnectionMinimumIdleSize(cfg.getSubscriptionConnectionMinimumIdleSize());
        c.setReadMode(cfg.getReadMode());
        c.setSubscriptionMode(cfg.getSubscriptionMode());

        return c;
    }

    @Override
    public RedisClient createClient(NodeType type, URI address) {
        RedisClient client = createClient(type, address, config.getConnectTimeout(), config.getRetryInterval() * config.getRetryAttempts());
        clientEntries.put(client, new RedisClientEntry(client, commandExecutor, type));
        return client;
    }
    
    @Override
    public RedisClient createClient(NodeType type, InetSocketAddress address, URI uri) {
        RedisClient client = createClient(type, address, uri, config.getConnectTimeout(), config.getRetryInterval() * config.getRetryAttempts());
        clientEntries.put(client, new RedisClientEntry(client, commandExecutor, type));
        return client;
    }

    @Override
    public void shutdownAsync(RedisClient client) {
        if (clientEntries.remove(client) == null) {
            log.error("Can't find client {}", client);
        }
        client.shutdownAsync();
    }

    @Override
    public RedisClient createClient(NodeType type, URI address, int timeout, int commandTimeout) {
        RedisClientConfig redisConfig = createRedisConfig(type, address, timeout, commandTimeout);
        return RedisClient.create(redisConfig);
    }
    
    private RedisClient createClient(NodeType type, InetSocketAddress address, URI uri, int timeout, int commandTimeout) {
        RedisClientConfig redisConfig = createRedisConfig(type, null, timeout, commandTimeout);
        redisConfig.setAddress(address, uri);
        return RedisClient.create(redisConfig);
    }


    protected RedisClientConfig createRedisConfig(NodeType type, URI address, int timeout, int commandTimeout) {
        RedisClientConfig redisConfig = new RedisClientConfig();
        redisConfig.setAddress(address)
              .setTimer(timer)
              .setExecutor(executor)
              .setResolverGroup(resolverGroup)
              .setGroup(group)
              .setSocketChannelClass(socketChannelClass)
              .setConnectTimeout(timeout)
              .setCommandTimeout(commandTimeout)
              .setSslEnableEndpointIdentification(config.isSslEnableEndpointIdentification())
              .setSslProvider(config.getSslProvider())
              .setSslTruststore(config.getSslTruststore())
              .setSslTruststorePassword(config.getSslTruststorePassword())
              .setSslKeystore(config.getSslKeystore())
              .setSslKeystorePassword(config.getSslKeystorePassword())
              .setClientName(config.getClientName())
              .setKeepPubSubOrder(cfg.isKeepPubSubOrder())
              .setPingConnectionInterval(config.getPingConnectionInterval())
              .setKeepAlive(config.isKeepAlive())
              .setTcpNoDelay(config.isTcpNoDelay());
        
        if (type != NodeType.SENTINEL) {
            redisConfig.setDatabase(config.getDatabase());
            redisConfig.setPassword(config.getPassword());
        }
        
        return redisConfig;
    }

    @Override
    public int calcSlot(String key) {
        return singleSlotRange.getStartSlot();
    }

    @Override
    public PubSubConnectionEntry getPubSubEntry(String channelName) {
        return name2PubSubConnection.get(channelName);
    }

    @Override
    public RFuture psubscribe(final String channelName, final Codec codec, final RedisPubSubListener... listeners) {
        final AsyncSemaphore lock = getSemaphore(channelName);
        final RPromise result = newPromise();
        lock.acquire(new Runnable() {
            @Override
            public void run() {
                RFuture future = psubscribe(channelName, codec, lock, listeners);
                future.addListener(new TransferListener(result));
            }
        });
        return result;
    }
    
    public RFuture psubscribe(String channelName, Codec codec, AsyncSemaphore semaphore, RedisPubSubListener... listeners) {
        RPromise promise = newPromise();
        subscribe(codec, channelName, promise, PubSubType.PSUBSCRIBE, semaphore, listeners);
        return promise;
    }

    public RFuture subscribe(final Codec codec, final String channelName, final RedisPubSubListener... listeners) {
        final AsyncSemaphore lock = getSemaphore(channelName);
        final RPromise result = newPromise();
        lock.acquire(new Runnable() {
            @Override
            public void run() {
                RFuture future = subscribe(codec, channelName, lock, listeners);
                future.addListener(new TransferListener(result));
            }
        });
        return result;
    }
    
    public RFuture subscribe(Codec codec, String channelName, AsyncSemaphore semaphore, RedisPubSubListener... listeners) {
        RPromise promise = newPromise();
        subscribe(codec, channelName, promise, PubSubType.SUBSCRIBE, semaphore, listeners);
        return promise;
    }

    public AsyncSemaphore getSemaphore(String channelName) {
        return locks[Math.abs(channelName.hashCode() % locks.length)];
    }
    
    private void subscribe(final Codec codec, final String channelName, 
            final RPromise promise, final PubSubType type, final AsyncSemaphore lock, final RedisPubSubListener... listeners) {
        final PubSubConnectionEntry connEntry = name2PubSubConnection.get(channelName);
        if (connEntry != null) {
            subscribe(channelName, promise, type, lock, connEntry, listeners);
            return;
        }

        freePubSubLock.acquire(new Runnable() {

            @Override
            public void run() {
                if (promise.isDone()) {
                    return;
                }
                
                final PubSubConnectionEntry freeEntry = freePubSubConnections.peek();
                if (freeEntry == null) {
                    connect(codec, channelName, promise, type, lock, listeners);
                    return;
                }
                
                int remainFreeAmount = freeEntry.tryAcquire();
                if (remainFreeAmount == -1) {
                    throw new IllegalStateException();
                }
                
                final PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, freeEntry);
                if (oldEntry != null) {
                    freeEntry.release();
                    freePubSubLock.release();
                    
                    subscribe(channelName, promise, type, lock, oldEntry, listeners);
                    return;
                }
                
                if (remainFreeAmount == 0) {
                    freePubSubConnections.poll();
                }
                freePubSubLock.release();
                
                subscribe(channelName, promise, type, lock, freeEntry, listeners);
                
                if (PubSubType.PSUBSCRIBE == type) {
                    freeEntry.psubscribe(codec, channelName);
                } else {
                    freeEntry.subscribe(codec, channelName);
                }
            }
            
        });
    }

    private void subscribe(final String channelName, final RPromise promise,
            final PubSubType type, final AsyncSemaphore lock, final PubSubConnectionEntry connEntry,
            final RedisPubSubListener... listeners) {
        for (RedisPubSubListener listener : listeners) {
            connEntry.addListener(channelName, listener);
        }
        connEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                lock.release();
                promise.trySuccess(connEntry);
            }
        });
    }

    private void connect(final Codec codec, final String channelName,
            final RPromise promise, final PubSubType type, final AsyncSemaphore lock, final RedisPubSubListener... listeners) {
        final int slot = calcSlot(channelName);
        RFuture connFuture = nextPubSubConnection(slot);
        connFuture.addListener(new FutureListener() {

            @Override
            public void operationComplete(Future future) throws Exception {
                if (!future.isSuccess()) {
                    freePubSubLock.release();
                    lock.release();
                    promise.tryFailure(future.cause());
                    return;
                }

                RedisPubSubConnection conn = future.getNow();
                
                final PubSubConnectionEntry entry = new PubSubConnectionEntry(conn, config.getSubscriptionsPerConnection());
                entry.tryAcquire();
                
                final PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, entry);
                if (oldEntry != null) {
                    releaseSubscribeConnection(slot, entry);
                    
                    freePubSubLock.release();

                    subscribe(channelName, promise, type, lock, oldEntry, listeners);
                    return;
                }
                
                freePubSubConnections.add(entry);
                freePubSubLock.release();
                
                subscribe(channelName, promise, type, lock, entry, listeners);
                
                if (PubSubType.PSUBSCRIBE == type) {
                    entry.psubscribe(codec, channelName);
                } else {
                    entry.subscribe(codec, channelName);
                }
                
            }
        });
    }

    public Codec unsubscribe(final String channelName, final AsyncSemaphore lock) {
        final PubSubConnectionEntry entry = name2PubSubConnection.remove(channelName);
        if (entry == null) {
            lock.release();
            return null;
        }
        
        Codec entryCodec = entry.getConnection().getChannels().get(channelName);
        entry.unsubscribe(channelName, new BaseRedisPubSubListener() {
            
            @Override
            public boolean onStatus(PubSubType type, String channel) {
                if (type == PubSubType.UNSUBSCRIBE && channel.equals(channelName)) {
                    
                    if (entry.release() == 1) {
                        freePubSubConnections.add(entry);
                    }
                    
                    lock.release();
                    return true;
                }
                return false;
            }
            
        });
        
        return entryCodec;
    }
    
    @Override
    public RFuture unsubscribe(final String channelName, boolean temporaryDown) {
        final PubSubConnectionEntry entry = name2PubSubConnection.remove(channelName);
        if (entry == null) {
            return null;
        }
        freePubSubConnections.remove(entry);
        
        final Codec entryCodec = entry.getConnection().getChannels().get(channelName);
        if (temporaryDown) {
            final RPromise result = newPromise();
            entry.unsubscribe(channelName, new BaseRedisPubSubListener() {
                
                @Override
                public boolean onStatus(PubSubType type, String channel) {
                    if (type == PubSubType.UNSUBSCRIBE && channel.equals(channelName)) {
                        result.trySuccess(entryCodec);
                        return true;
                    }
                    return false;
                }
                
            });
            return result;
        }
        entry.unsubscribe(channelName, null);
        return newSucceededFuture(entryCodec);
    }
    
    public Codec punsubscribe(final String channelName, final AsyncSemaphore lock) {
        final PubSubConnectionEntry entry = name2PubSubConnection.remove(channelName);
        if (entry == null) {
            lock.release();
            return null;
        }
        
        Codec entryCodec = entry.getConnection().getPatternChannels().get(channelName);
        entry.punsubscribe(channelName, new BaseRedisPubSubListener() {
            
            @Override
            public boolean onStatus(PubSubType type, String channel) {
                if (type == PubSubType.PUNSUBSCRIBE && channel.equals(channelName)) {
                    
                    if (entry.release() == 1) {
                        freePubSubConnections.add(entry);
                    }
                    
                    lock.release();
                    return true;
                }
                return false;
            }
            
        });
        
        return entryCodec;
    }

    
    @Override
    public RFuture punsubscribe(final String channelName, boolean temporaryDown) {
        final PubSubConnectionEntry entry = name2PubSubConnection.remove(channelName);
        if (entry == null) {
            return null;
        }
        freePubSubConnections.remove(entry);
        
        final Codec entryCodec = entry.getConnection().getChannels().get(channelName);
        if (temporaryDown) {
            final RPromise result = newPromise();
            entry.punsubscribe(channelName, new BaseRedisPubSubListener() {
                
                @Override
                public boolean onStatus(PubSubType type, String channel) {
                    if (type == PubSubType.PUNSUBSCRIBE && channel.equals(channelName)) {
                        result.trySuccess(entryCodec);
                        return true;
                    }
                    return false;
                }
                
            });
            return result;
        }
        entry.punsubscribe(channelName, null);
        return newSucceededFuture(entryCodec);
    }

    public MasterSlaveEntry getEntry(InetSocketAddress address) {
        for (MasterSlaveEntry entry : client2entry.values()) {
            InetSocketAddress addr = entry.getClient().getAddr();
            if (addr.getAddress().equals(address.getAddress()) && addr.getPort() == address.getPort()) {
                return entry;
            }
        }
        return null;
    }
    
    private MasterSlaveEntry getEntry(URI addr) {
        for (MasterSlaveEntry entry : client2entry.values()) {
            if (URIBuilder.compare(entry.getClient().getAddr(), addr)) {
                return entry;
            }
            if (entry.hasSlave(addr)) {
                return entry;
            }
        }
        return null;
    }
    
    public MasterSlaveEntry getEntry(RedisClient redisClient) {
        MasterSlaveEntry entry = client2entry.get(redisClient);
        if (entry != null) {
            return entry;
        }
        
        for (MasterSlaveEntry mentry : client2entry.values()) {
            if (mentry.hasSlave(redisClient)) {
                return mentry;
            }
        }
        return null;
    }
    
    @Override
    public MasterSlaveEntry getEntry(int slot) {
        return slot2entry.get(slot);
    }
    
    protected final void changeMaster(int slot, URI address) {
        MasterSlaveEntry entry = getEntry(slot);
        client2entry.remove(entry.getClient());
        entry.changeMaster(address);
        client2entry.put(entry.getClient(), entry);
    }

    protected final void addEntry(Integer slot, MasterSlaveEntry entry) {
        slot2entry.set(slot, entry);
        entry.addSlotRange(slot);
        client2entry.put(entry.getClient(), entry);
    }

    protected final MasterSlaveEntry removeEntry(Integer slot) {
        MasterSlaveEntry entry = slot2entry.getAndSet(slot, null);
        entry.removeSlotRange(slot);
        if (entry.getSlotRanges().isEmpty()) {
            client2entry.remove(entry.getClient());
        }
        return entry;
    }

    @Override
    public RFuture connectionWriteOp(NodeSource source, RedisCommand command) {
        MasterSlaveEntry entry = getEntry(source);
        if (entry == null) {
            RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source + " hasn't been discovered yet");
            return RedissonPromise.newFailedFuture(ex);
        }
        return entry.connectionWriteOp(command);
    }

    private MasterSlaveEntry getEntry(NodeSource source) {
        if (source.getRedirect() != null) {
            return getEntry(source.getAddr());
        }

        MasterSlaveEntry entry = source.getEntry();
        if (entry == null && source.getSlot() != null) {
            entry = getEntry(source.getSlot());
        }
        if (source.getRedisClient() != null) {
            entry = getEntry(source.getRedisClient());
        }
        return entry;
    }
    
    @Override
    public RFuture connectionReadOp(NodeSource source, RedisCommand command) {
        MasterSlaveEntry entry = getEntry(source);
        if (entry == null) {
            RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node: " + source + " hasn't been discovered yet");
            return RedissonPromise.newFailedFuture(ex);
        }

        if (source.getRedirect() != null) {
            return entry.connectionReadOp(command, source.getAddr());
        }
        
        return entry.connectionReadOp(command);
    }

    RFuture nextPubSubConnection(int slot) {
        MasterSlaveEntry entry = getEntry(slot);
        if (entry == null) {
            RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node for slot: " + slot + " hasn't been discovered yet");
            return RedissonPromise.newFailedFuture(ex);
        }
        return entry.nextPubSubConnection();
    }

    protected void releaseSubscribeConnection(int slot, PubSubConnectionEntry pubSubEntry) {
        MasterSlaveEntry entry = getEntry(slot);
        if (entry == null) {
            log.error("Node for slot: " + slot + " can't be found");
        } else {
            entry.returnPubSubConnection(pubSubEntry);
        }
    }

    @Override
    public void releaseWrite(NodeSource source, RedisConnection connection) {
        MasterSlaveEntry entry = getEntry(source);
        if (entry == null) {
            log.error("Node: " + source + " can't be found");
        } else {
            entry.releaseWrite(connection);
        }
    }

    @Override
    public void releaseRead(NodeSource source, RedisConnection connection) {
        MasterSlaveEntry entry = getEntry(source);
        if (entry == null) {
            log.error("Node: " + source + " can't be found");
        } else {
            entry.releaseRead(connection);
        }
        
    }

    @Override
    public void shutdown() {
        shutdown(2, 15, TimeUnit.SECONDS);//default netty value
    }

    @Override
    public void shutdown(long quietPeriod, long timeout, TimeUnit unit) {
        if (dnsMonitor != null) {
            dnsMonitor.stop();
        }

        timer.stop();
        
        shutdownLatch.close();
        shutdownPromise.trySuccess(true);
        shutdownLatch.awaitUninterruptibly();

        for (MasterSlaveEntry entry : getEntrySet()) {
            entry.shutdown();
        }

        if (cfg.getExecutor() == null) {
            executor.shutdown();
            try {
                executor.awaitTermination(timeout, unit);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        resolverGroup.close();
        
        if (cfg.getEventLoopGroup() == null) {
            group.shutdownGracefully(quietPeriod, timeout, unit).syncUninterruptibly();
        }
    }

    @Override
    public boolean isShuttingDown() {
        return shutdownLatch.isClosed();
    }

    @Override
    public boolean isShutdown() {
        return group.isTerminated();
    }

    @Override
    public Collection getClients() {
        return Collections.unmodifiableCollection(clientEntries.values());
    }

    @Override
    public  RPromise newPromise() {
        return new RedissonPromise();
    }

    @Override
    public  RFuture newSucceededFuture(R value) {
        return RedissonPromise.newSucceededFuture(value);
    }

    @Override
    public  RFuture newFailedFuture(Throwable cause) {
        return RedissonPromise.newFailedFuture(cause);
    }

    @Override
    public EventLoopGroup getGroup() {
        return group;
    }

    @Override
    public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
        try {
            return timer.newTimeout(task, delay, unit);
        } catch (IllegalStateException e) {
            // timer is shutdown
            return dummyTimeout;
        }
    }

    @Override
    public InfinitySemaphoreLatch getShutdownLatch() {
        return shutdownLatch;
    }

    @Override
    public RFuture getShutdownPromise() {
        return shutdownPromise;
    }

    @Override
    public ConnectionEventsHub getConnectionEventsHub() {
        return connectionEventsHub;
    }

    protected void stopThreads() {
        timer.stop();
        executor.shutdown();
        try {
            executor.awaitTermination(15, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        group.shutdownGracefully().syncUninterruptibly();
    }
    
    public ExecutorService getExecutor() {
        return executor;
    }
    
    public URI getLastClusterNode() {
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy