/// <inheritdoc />
        public void FailoverTo(params string[] readWriteHosts)
        {
            Interlocked.Increment(ref RedisState.TotalFailovers);

            lock (this._clients) {
                for (var i = 0; i < this._clients.Length; i++)
                {
                    var redis = this._clients[i];
                    if (redis != null)
                    {
                        RedisState.DeactivateClient(redis);
                    }

                    this._clients[i] = null;
                }

                this.RedisResolver.ResetMasters(readWriteHosts);
            }

            if (this.OnFailover != null)
            {
                foreach (var callback in this.OnFailover)
                {
                    try {
                        callback(this);
                    } catch (Exception ex) {
                        _logger.Error("Error firing OnFailover callback(): ", ex);
                    }
                }
            }
        }
        protected virtual void Dispose(bool disposing)
        {
            if (Interlocked.Increment(ref this._disposeAttempts) > 1)
            {
                return;
            }

            if (disposing)
            {
                // get rid of managed resources
            }

            try {
                // get rid of unmanaged resources
                foreach (var t in this._writeClients)
                {
                    this.Dispose(t);
                }

                foreach (var t in this._readClients)
                {
                    this.Dispose(t);
                }
            } catch (Exception ex) {
                _logger.Error("Error when trying to dispose of PooledRedisClientManager", ex);
            }

            RedisState.DisposeAllDeactivatedClients();
        }
        /// <summary>
        ///     Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts
        /// </summary>
        public IRedisClient GetClient()
        {
            try {
                int inactivePoolIndex;
                lock (this._clients) {
                    this.AssertValidPool();

                    // -1 when no available clients otherwise index of reservedSlot or existing Client
                    inactivePoolIndex = this.GetInActiveClient(out var inActiveClient);

                    // inActiveClient != null only for Valid InActive Clients
                    if (inActiveClient != null)
                    {
                        this.PoolIndex++;
                        inActiveClient.Active = true;

                        return(!this.AssertAccessOnlyOnSameThread
                            ? inActiveClient
                            : inActiveClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace));
                    }
                }

                // Reaches here when there's no Valid InActive Clients
                try {
                    // inactivePoolIndex == -1 || index of reservedSlot || index of invalid client
                    var existingClient = inactivePoolIndex >= 0 && inactivePoolIndex < this._clients.Length
                        ? this._clients[inactivePoolIndex]
                        : null;

                    if (existingClient != null && existingClient != _reservedSlot && existingClient.HadExceptions)
                    {
                        RedisState.DeactivateClient(existingClient);
                    }

                    var newClient = this.InitNewClient(this.RedisResolver.CreateMasterClient(Math.Max(inactivePoolIndex, 0)));

                    // Put all blocking I/O or potential Exceptions before lock
                    lock (this._clients) {
                        // Create new client outside of pool when max pool size exceeded
                        // Reverting free-slot not needed when -1 since slot wasn't reserved or
                        // when existingClient changed (failover) since no longer reserved
                        var stillReserved = inactivePoolIndex >= 0 && inactivePoolIndex < this._clients.Length &&
                                            this._clients[inactivePoolIndex] == existingClient;
                        if (inactivePoolIndex == -1 || !stillReserved)
                        {
                            if (_logger.IsDebugEnabled())
                            {
                                _logger.Debug(string.Format("clients[inactivePoolIndex] != existingClient: {0}",
                                                            !stillReserved ? "!stillReserved" : "-1"));
                            }

                            Interlocked.Increment(ref RedisState.TotalClientsCreatedOutsidePool);

                            // Don't handle callbacks for new client outside pool
                            newClient.ClientManager = null;
                            return(newClient);
                        }

                        this.PoolIndex++;
                        this._clients[inactivePoolIndex] = newClient;

                        return(!this.AssertAccessOnlyOnSameThread
                            ? newClient
                            : newClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace));
                    }
                } catch {
                    // Revert free-slot for any I/O exceptions that can throw (before lock)
                    lock (this._clients) {
                        if (inactivePoolIndex >= 0 && inactivePoolIndex < this._clients.Length)
                        {
                            this._clients[inactivePoolIndex] = null;
                        }
                    }

                    throw;
                }
            } finally {
                RedisState.DisposeExpiredClients();
            }
        }
        /// <summary>
        ///     Returns a ReadOnly client using the hosts defined in ReadOnlyHosts.
        /// </summary>
        public virtual IRedisClient GetReadOnlyClient()
        {
            try {
                var poolTimedOut = false;
                int inactivePoolIndex;
                lock (this._readClients) {
                    this.AssertValidReadOnlyPool();

                    RedisClient inActiveClient;
                    while ((inactivePoolIndex = this.GetInActiveReadClient(out inActiveClient)) == -1)
                    {
                        if (this.PoolTimeout.HasValue)
                        {
                            // wait for a connection, break out if made to wait too long
                            if (!Monitor.Wait(this._readClients, this.PoolTimeout.Value))
                            {
                                poolTimedOut = true;
                                break;
                            }
                        }
                        else
                        {
                            Monitor.Wait(this._readClients, this.RecheckPoolAfterMs);
                        }
                    }

                    // inActiveClient != null only for Valid InActive Clients
                    if (inActiveClient != null)
                    {
                        this.ReadPoolIndex++;
                        inActiveClient.Active = true;

                        this.InitClient(inActiveClient);

                        return(inActiveClient);
                    }
                }

                if (poolTimedOut)
                {
                    throw new TimeoutException(_poolTimeoutError);
                }

                // Reaches here when there's no Valid InActive Clients
                try {
                    // inactivePoolIndex = index of reservedSlot || index of invalid client
                    var existingClient = this._readClients[inactivePoolIndex];
                    if (existingClient != null && existingClient != _reservedSlot && existingClient.HadExceptions)
                    {
                        RedisState.DeactivateClient(existingClient);
                    }

                    var newClient = this.InitNewClient(this.RedisResolver.CreateSlaveClient(inactivePoolIndex));

                    // Put all blocking I/O or potential Exceptions before lock
                    lock (this._readClients) {
                        // If existingClient at inactivePoolIndex changed (failover) return new client outside of pool
                        if (this._readClients[inactivePoolIndex] != existingClient)
                        {
                            if (_logger.IsDebugEnabled())
                            {
                                _logger.Debug(string.Format("readClients[inactivePoolIndex] != existingClient: {0}",
                                                            this._readClients[inactivePoolIndex]));
                            }

                            Interlocked.Increment(ref RedisState.TotalClientsCreatedOutsidePool);

                            // Don't handle callbacks for new client outside pool
                            newClient.ClientManager = null;
                            return(newClient); // return client outside of pool
                        }

                        this.ReadPoolIndex++;
                        this._readClients[inactivePoolIndex] = newClient;
                        return(newClient);
                    }
                } catch {
                    // Revert free-slot for any I/O exceptions that can throw
                    lock (this._readClients) {
                        this._readClients[inactivePoolIndex] = null; // free slot
                    }

                    throw;
                }
            } finally {
                RedisState.DisposeExpiredClients();
            }
        }
        /// <summary>
        ///     Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts
        /// </summary>
        /// <inheritdoc />
        public IRedisClient GetClient()
        {
            try {
                var poolTimedOut = false;
                int inactivePoolIndex;
                lock (this._writeClients) {
                    this.AssertValidReadWritePool();

                    RedisClient inActiveClient;
                    while ((inactivePoolIndex = this.GetInActiveWriteClient(out inActiveClient)) == -1)
                    {
                        if (this.PoolTimeout.HasValue)
                        {
                            // wait for a connection, cry out if made to wait too long
                            if (!Monitor.Wait(this._writeClients, this.PoolTimeout.Value))
                            {
                                poolTimedOut = true;
                                break;
                            }
                        }
                        else
                        {
                            Monitor.Wait(this._writeClients, this.RecheckPoolAfterMs);
                        }
                    }

                    // inActiveClient != null only for Valid InActive Clients
                    if (inActiveClient != null)
                    {
                        this.WritePoolIndex++;
                        inActiveClient.Active = true;

                        this.InitClient(inActiveClient);

                        return(!this.AssertAccessOnlyOnSameThread
                            ? inActiveClient
                            : inActiveClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace));
                    }
                }

                if (poolTimedOut)
                {
                    throw new TimeoutException(_poolTimeoutError);
                }

                // Reaches here when there's no Valid InActive Clients
                try {
                    // inactivePoolIndex = index of reservedSlot || index of invalid client
                    var existingClient = this._writeClients[inactivePoolIndex];
                    if (existingClient != null && existingClient != _reservedSlot && existingClient.HadExceptions)
                    {
                        RedisState.DeactivateClient(existingClient);
                    }

                    var newClient = this.InitNewClient(this.RedisResolver.CreateMasterClient(inactivePoolIndex));

                    // Put all blocking I/O or potential Exceptions before lock
                    lock (this._writeClients) {
                        // If existingClient at inactivePoolIndex changed (failover) return new client outside of pool
                        if (this._writeClients[inactivePoolIndex] != existingClient)
                        {
                            if (_logger.IsDebugEnabled())
                            {
                                _logger.Debug(
                                    string.Format("writeClients[inactivePoolIndex] != existingClient: {0}",
                                                  this._writeClients[inactivePoolIndex]));
                            }

                            return(newClient); // return client outside of pool
                        }

                        this.WritePoolIndex++;
                        this._writeClients[inactivePoolIndex] = newClient;

                        return(!this.AssertAccessOnlyOnSameThread
                            ? newClient
                            : newClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace));
                    }
                } catch {
                    // Revert free-slot for any I/O exceptions that can throw (before lock)
                    lock (this._writeClients) {
                        this._writeClients[inactivePoolIndex] = null; // free slot
                    }

                    throw;
                }
            } finally {
                RedisState.DisposeExpiredClients();
            }
        }