/**
 * Copyright 2024 Beijing Baolande Software Corporation
 *
 * 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.
 *
 * Runtime Library Exception to the Apache 2.0 License:
 *
 * As an exception, if you use this Software to compile your source code and
 * portions of this Software are embedded into the binary product as a result,
 * you may redistribute such product without providing attribution as would
 * otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
 */

package redis_sdk.client

public open class ClusterInfoCache {
    static let logger: Logger = LoggerFactory.getLogger("redis-sdk")

    private static let initCacheLock = Mutex()

    private static let idGenerator = AtomicInt64(0)

    private let id: Int64

    /**
     * 缓存ClusterInfoCache
     */
    private static let clusterInfoCaches = HashMap<ClusterInfoCacheKey, ClusterInfoCache>()

    private let clusterConfig: ClusterRedisClientConfig

    private let startNodes: HashSet<HostAndPort>

    private let respVersionAware: RespVersionAware = DefaultRespVersionAware()

    private let rwl = ReadWriteLock()

    private let r = rwl.readLock

    private let w = rwl.writeLock

    private let slots = Array<?ClientTcpEndpoint>(CLUSTER_HASHSLOTS, repeat: None)

    private let slotNodes = Array<?HostAndPort>(CLUSTER_HASHSLOTS, repeat: None)

    private let nodes = HashMap<String, ClientTcpEndpoint>()

    private let threadPool: ThreadPool

    private let rediscoverLock = Mutex()

    private let initialized = AtomicBool(false)

    /**
     * 处理Cache引用相关的锁
     */
    private let refCacheMutex = Mutex()

    private let clusterConnectionProviders = ArrayList<ClusterConnectionProvider>()

    private var closeTaskRef: ?CloseClusterInfoCacheTask = None

    private let refCount = AtomicUInt64(0)

    private let closed = AtomicBool(false)

    private var topologyRefreshExecutor: ?Timer = None

    private static let MASTER_NODE_INDEX = 2

    private static let CLUSTER_HASHSLOTS = 16384

    private let subscribeMode: Bool

    /**
     * 从缓存中获取ClusterInfoCache
     */
    static func getClusterInfoCache(clusterConfig: ClusterRedisClientConfig): ClusterInfoCache {
        return getClusterInfoCache(clusterConfig, subscribeMode: false)
    }

    /**
     * 从缓存中获取ClusterInfoCache
     *
     */
    static func getClusterInfoCache(clusterConfig: ClusterRedisClientConfig, subscribeMode!: Bool): ClusterInfoCache {
        let clusterInfoCacheKey = ClusterInfoCacheKey(clusterConfig, subscribeMode: subscribeMode)
        synchronized(initCacheLock) {
            if (let Some(clusterInfoCache) <- clusterInfoCaches.get(clusterInfoCacheKey)) {
                if (clusterInfoCache.retain()) {
                    return clusterInfoCache
                } else {
                    clusterInfoCaches.remove(clusterInfoCacheKey)
                }
            }

            let clusterInfoCache = ClusterInfoCache(clusterConfig, subscribeMode: subscribeMode)
            clusterInfoCache.initialize()
            clusterInfoCaches.add(clusterInfoCacheKey, clusterInfoCache)

            // 新创建ClusterInfoCache的也需要调用retain,确保引用计数正确
            clusterInfoCache.retain()
            return clusterInfoCache
        }
    }

    public init(clusterConfig: ClusterRedisClientConfig) {
        this(clusterConfig, subscribeMode: false)
    }

    public init(clusterConfig: ClusterRedisClientConfig, subscribeMode!: Bool) {
        this.id = idGenerator.fetchAdd(1)
        this.clusterConfig = clusterConfig
        this.startNodes = clusterConfig.getClusterHostAndPorts()
        this.subscribeMode = subscribeMode
        // 集群中多个实例的客户端共用一个线程池
        this.threadPool = RedisClientBuilder.buildThreadPool(clusterConfig)
    }

    public func getClusterConfig() {
        return this.clusterConfig
    }

    public func getRespVersion(): ?ProtocolVersion {
        return respVersionAware.getRespVersion()
    }

    /**
     * 添加使用该ClusterInfoCache的ClusterConnectionProvider
     *
     */
    func addClusterConnectionProvider(clusterConnectionProvider: ClusterConnectionProvider) {
        checkOpen()

        synchronized(refCacheMutex) {
            if (!this.clusterConnectionProviders.contains(clusterConnectionProvider)) {
                this.clusterConnectionProviders.add(clusterConnectionProvider)
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug(
                "ClusterInfoCache::addClusterConnectionProvider, add ${clusterConnectionProvider} to ${getClusterInfoCacheStatus()}"
            )
        }
    }

    /**
     * 移除使用该ClusterInfoCache的ClusterConnectionProvider
     *
     * 没有ClusterConnectionProvider使用该ClusterInfoCache时,则关闭ClusterInfoCache
     *
     */
    func removeClusterConnectionProvider(clusterConnectionProvider: ClusterConnectionProvider) {
        func checkEquals(item: ClusterConnectionProvider) {
            if (refEq(clusterConnectionProvider, item) || clusterConnectionProvider == item) {
                if (refCount.load() > 0) {
                    refCount.fetchSub(1)
                }
                return true
            }

            return false
        }

        synchronized(refCacheMutex) {
            this.clusterConnectionProviders.removeIf(checkEquals)

            if (logger.isDebugEnabled()) {
                logger.debug(
                    "ClusterInfoCache::removeClusterConnectionProvider, remove ${clusterConnectionProvider} from ${getClusterInfoCacheStatus()}"
                )
            }

            if (refCount.load() == 0 && clusterConnectionProviders.isEmpty()) {
                if (closeTaskRef.isNone()) {
                    let closeTask = CloseClusterInfoCacheTask(this)
                    closeTaskRef = closeTask

                    if (logger.isDebugEnabled()) {
                        logger.debug(
                            "ClusterInfoCache::removeClusterConnectionProvider, create CloseClusterInfoCacheTask for ${getClusterInfoCacheStatus()}"
                        )
                    }

                    spawn {
                        Thread.currentThread.name = "ClusterInfoCache-Destory"
                        closeTask.run()
                    }
                }
            }
        }
    }

    /**
     * 保留ClusterInfoCache,避免被关闭
     */
    private func retain(): Bool {
        synchronized(refCacheMutex) {
            if (!closed.load()) {
                refCount.fetchAdd(1)
                return true
            }
        }

        return false
    }

    func getClusterInfoCacheStatus(): String {
        return "ClusterInfoCache{id: ${id}, start nodes: ${clusterConfig.getStartNodes()}, reference count: ${refCount.load()}}"
    }

    public open func initialize() {
        if (initialized.compareAndSwap(false, true)) {
            initializeSlotsCache(clusterConfig.getClusterHostAndPorts(), clusterConfig)

            //init slot cache refresh timer
            if (let Some(topologyRefreshPeriod) <- clusterConfig.topologyRefreshPeriod) {
                logger.info(
                    "Cluster topology refresh start, period: {clusterConfig.topologyRefreshPeriod}, startNodes: ${getStartNodes()}"
                )
                topologyRefreshExecutor = Timer.repeat(
                    topologyRefreshPeriod,
                    topologyRefreshPeriod,
                    {
                        =>
                        if (logger.isDebugEnabled()) {
                            logger.debug("Cluster topology refresh run, old nodes:  ${getNodesKeySet()}")
                        }
                        renewClusterSlots(None)
                        if (logger.isDebugEnabled()) {
                            logger.debug("Cluster topology refresh run, new nodes:  ${getNodesKeySet()}")
                        }
                    }
                )
            }
        }
    }

    public func initializeSlotsCache(startNodes: HashSet<HostAndPort>, clusterConfig: ClusterRedisClientConfig): Unit {
        if (startNodes.isEmpty()) {
            throw RedisClusterOperationException("No nodes to initialize cluster slots cache.")
        }
        var firstMessage = ""
        var startNodeList = ArrayList<HostAndPort>(startNodes)
        ClusterUtils.shuffle(startNodeList)
        for (node in startNodeList) {
            var tcpEndPoint: ?ClientTcpEndpoint = None
            try {
                let endPoint = this.buildClusterEndPoint(node, subscribeMode)

                tcpEndPoint = endPoint
                discoverClusterNodesAndSlots(endPoint)
                return
            } catch (ex: Exception) {
                if (firstMessage == "") {
                    firstMessage = ex.message
                }
            } finally {
                if (let Some(v) <- tcpEndPoint) {
                    v.stop()
                }
            }
        }
        var message = ""
        if (firstMessage != "") {
            message = "Could not initialize cluster slots cache, ${firstMessage}"
        } else {
            message = "Could not initialize cluster slots cache."
        }
        throw RedisClusterOperationException(message)
    }

    /**
     * 创建连接Cluster实例的ClientTcpEndpoint
     */
    private func buildClusterEndPoint(hostAndPort: HostAndPort, isSubscribedMode: Bool): ClientTcpEndpoint {
        let config = ClientEndpointConfig()
        config.host = hostAndPort.host
        config.port = hostAndPort.port

        clusterConfig.configTcpEndpointOptions(config)
        let tcpEndpoint = ClientTcpEndpoint(config, this.threadPool)

        if (!isSubscribedMode) {
            // 添加Redis命令相关的编解码器
            RedisClientBuilder.configEndpoint(tcpEndpoint)
        } else {
            RedisSubscriberBuilder.configEndpoint(tcpEndpoint, RedisSubscriberHandler.getInstance())
        }

        let handshakeInfo = RedisClientBuilder.getHandShakeInfo(clusterConfig)
        let clientInitializer: RedisClientInitializer = RedisClientInitializer(
            respVersionAware,
            handshakeInfo,
            clusterConfig.waitResponseTimeout
        )
        tcpEndpoint.setConnectionInitializer(clientInitializer)

        tcpEndpoint.start()
        // 设置ClientInitializer之后再验证连接
        RedisClientBuilder.validClientEndPoint(tcpEndpoint)

        return tcpEndpoint
    }

    public func getConnection(args: CommandArgs): ClientTcpEndpoint {
        let slot = args.getKeySlot()
        var clientTcpEndpoint: ClientTcpEndpoint
        if (slot >= 0) {
            clientTcpEndpoint = getConnectionFromSlot(slot)
        } else {
            clientTcpEndpoint = getConnection()
        }
        return clientTcpEndpoint
    }

    public func getConnection(node: HostAndPort): ClientTcpEndpoint {
        let endpoint = setupNodeIfNotExist(node)
        return endpoint
    }

    public func getConnection(): ClientTcpEndpoint {
        var pools = getShuffledNodesPool()
        var firstExceptionMessage = ""
        for (pool in pools) {
            try {
                executePing(pool)
                return pool
            } catch (ex: Exception) {
                if (firstExceptionMessage == "") {
                    firstExceptionMessage = ex.message
                }
            }
        }
        var message = ""
        if (firstExceptionMessage != "") {
            message = "No reachable node in cluster, ${firstExceptionMessage}"
        } else {
            message = "No reachable node in cluster."
        }
        throw RedisClusterOperationException(message)
    }

    public func executeCommand(clientTcpEndPoint: ClientTcpEndpoint, command: RedisCommand): RedisMessage {
        checkOpen()
        var needCloseSession: ?Session = None<Session>
        try {
            let session = clientTcpEndPoint.createSession()
            needCloseSession = Some(session)
            session.writeAndFlushMessage(command)
            return command.waitForResponse(clusterConfig.waitResponseTimeout)
        } catch (ex: Exception) {
            throw ex
        } finally {
            if (let Some(session) <- needCloseSession) {
                session.close()
            }
        }
    }

    public func getConnectionFromSlot(slot: Int64): ClientTcpEndpoint {
        var poolVal = getSlotPool(slot)
        if (let Some(pool) <- poolVal) {
            return pool
        } else {
            renewClusterSlots(None)
            poolVal = getSlotPool(slot)
            if (let Some(pool) <- poolVal) {
                return pool
            } else {
                return getConnection()
            }
        }
    }

    public func renewClusterSlots(clientTcpEndpointVal: ?ClientTcpEndpoint): Unit {
        if (rediscoverLock.tryLock()) {
            try {

                //First, if pool is available, use pool renew.
                if (let Some(clientTcpEndpoint) <- clientTcpEndpointVal) {
                    try {
                        discoverClusterSlots(clientTcpEndpoint)
                        return
                    } catch (ex: RedisException) {
                        // try nodes form all pools
                    }
                }

                // Then, we use startNodes to try, as long as startNodes is available,
                // whether it is vip, domain, or physical ip, it will succeed.
                if (startNodes.size > 0) {
                    for (node in startNodes) {
                        var tcpEndPoint: ?ClientTcpEndpoint = None
                        try {
                            let endPoint = this.buildClusterEndPoint(node, subscribeMode)
                            tcpEndPoint = endPoint
                            discoverClusterSlots(endPoint)
                            return
                        } catch (ex: Exception) {
                        // try next nodes
                        } finally {
                            if (let Some(v) <- tcpEndPoint) {
                                v.stop()
                            }
                        }
                    }
                }

                //Finally, we go back to the ShuffledNodesPool and try the remaining physical nodes.
                for (pool in getShuffledNodesPool()) {
                    try {
                        if (startNodes.contains(HostAndPort.toHostAndPort(pool.getRemoteAddress()))) {
                            continue
                        }
                        discoverClusterSlots(pool)
                        return
                    } catch (ex: Exception) {
                        // try next nodes
                    }
                }
            } finally {
                rediscoverLock.unlock()
            }
        }
    }

    public func getShuffledNodesPool(): Array<ClientTcpEndpoint> {
        r.lock()
        try {
            let pools = nodes.values().toArray()
            ClusterUtils.shuffle(pools)
            return pools
        } finally {
            r.unlock()
        }
    }

    public func discoverClusterSlots(clientTcpEndpoint: ClientTcpEndpoint): Unit {
        let slotsInfo = executeClusterSlot(clientTcpEndpoint)
        if (slotsInfo.size == 0) {
            throw RedisClusterOperationException("Cluster slots list is empty.")
        }
        w.lock()
        try {
            ClusterUtils.fillNone(slots)
            ClusterUtils.fillNone(slotNodes)
            let hostAndPortKeys = HashSet<String>()
            for (slotObj in slotsInfo) {
                if (let Some(slotObj) <- (slotObj as ArrayList<Any>)) {
                    if (slotObj.size <= MASTER_NODE_INDEX) {
                        continue
                    }
                    var slotNums = getAssignedSlotArray(slotObj)

                    var size = slotObj.size
                    for (i in MASTER_NODE_INDEX..size : 1) {
                        if (let Some(items) <- (slotObj[i] as ArrayList<Any>)) {
                            if (items.isEmpty()) {
                                continue
                            }

                            let targetNode: HostAndPort = generateHostAndPort(items)
                            hostAndPortKeys.add(getNodeKey(targetNode))
                            setupNodeIfNotExist(targetNode)
                            if (i == MASTER_NODE_INDEX) {
                                assignSlotsToNode(slotNums, targetNode)
                            }
                        }
                    }
                }
            }

            // Remove dead nodes according to the latest query
            var entryIt = nodes.iterator()
            while (let Some((key, pool)) <- entryIt.next()) {
                if (!hostAndPortKeys.contains(key)) {
                    try {
                        pool.stop()
                    } catch (ex: Exception) {
                        //ignore
                    }
                    entryIt.remove()
                }
            }
        } finally {
            w.unlock()
        }
    }

    public func discoverClusterNodesAndSlots(clientTcpEndpoint: ClientTcpEndpoint) {
        let clusterSlots: ArrayList<Any> = executeClusterSlot(clientTcpEndpoint)
        if (clusterSlots.size == 0) {
            throw RedisClusterOperationException("Cluster slots list is empty.")
        }
        w.lock()
        try {
            reset()
            let hostAndPortKeys = HashSet<String>()
            for (slotObj in clusterSlots) {
                if (let Some(slotObj) <- (slotObj as ArrayList<Any>)) {
                    if (slotObj.size <= MASTER_NODE_INDEX) {
                        continue
                    }
                    var slotNums = getAssignedSlotArray(slotObj)
                    var size = slotObj.size
                    for (i in MASTER_NODE_INDEX..size : 1) {
                        if (let Some(items) <- (slotObj[i] as ArrayList<Any>)) {
                            if (items.isEmpty()) {
                                continue
                            }

                            let targetNode: HostAndPort = generateHostAndPort(items)
                            setupNodeIfNotExist(targetNode)
                            if (i == MASTER_NODE_INDEX) {
                                assignSlotsToNode(slotNums, targetNode)
                            }
                        }
                    }
                }
            }
        } finally {
            w.unlock()
        }
    }

    public func close(): Unit {
        var realClose = false
        synchronized(refCacheMutex) {
            closeTaskRef = None

            if (refCount.load() == 0 && clusterConnectionProviders.isEmpty()) {
                // 确保不被ClusterConnectionProvider引用时,才关闭
                if (closed.compareAndSwap(false, true)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("ClusterInfoCache::close, close ${getClusterInfoCacheStatus()}")
                    }

                    realClose = true
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                        "ClusterInfoCache::close, abandon CloseClusterInfoCacheTask, because the ${getClusterInfoCacheStatus()} is reused"
                    )
                }
            }
        }

        if (realClose) {
            synchronized(initCacheLock) {
                // 从缓存中移除
                let clusterInfoCacheKey = ClusterInfoCacheKey(clusterConfig, subscribeMode: subscribeMode)
                clusterInfoCaches.remove(clusterInfoCacheKey)
            }

            // 会锁ReadWriteMutex,移到synchronized(refCacheMutex)之外
            reset()
            if (let Some(executor) <- topologyRefreshExecutor) {
                logger.info("Cluster topology execute cancel method, startNodes: ${startNodes}");
                executor.cancel()
            }
        }
    }

    public func reset(): Unit {
        w.lock()
        try {
            for (pool in nodes.values()) {
                try {
                    pool.stop()
                } catch (e: RedisException) {
                    // pass
                }
            }
            nodes.clear()
            ClusterUtils.fillNone(slots)
            ClusterUtils.fillNone(slotNodes)
        } finally {
            w.unlock()
        }
    }

    public func assignSlotsToNode(targetSlots: ArrayList<Int64>, targetNode: HostAndPort) {
        w.lock()
        try {
            let targetPool = setupNodeIfNotExist(targetNode)
            for (slot in targetSlots) {
                slots[slot] = targetPool
                slotNodes[slot] = targetNode
            }
        } finally {
            w.unlock()
        }
    }

    public func getSlotPool(slot: Int64): ?ClientTcpEndpoint {
        r.lock()
        try {
            return slots[slot]
        } finally {
            r.unlock()
        }
    }

    public func setupNodeIfNotExist(node: HostAndPort): ClientTcpEndpoint {
        w.lock()
        try {
            let nodeKey = getNodeKey(node)
            if (let Some(v) <- nodes.get(nodeKey)) {
                return v
            }

            let nodePool = this.buildClusterEndPoint(node, subscribeMode)
            nodes.add(nodeKey, nodePool)
            return nodePool
        } finally {
            w.unlock()
        }
    }

    public func getNodesKeySet(): ArrayList<String> {
        let keys = ArrayList<String>()
        var entryIt = nodes.iterator()
        while (let Some((key, pool)) <- entryIt.next()) {
            keys.add(key)
        }
        return keys
    }

    public func getStartNodes(): HashSet<HostAndPort> {
        return startNodes
    }

    public func getNodes() {
        r.lock()
        try {
            return HashMap<String, ClientTcpEndpoint>(nodes)
        } finally {
            r.unlock()
        }
    }

    private func generateHostAndPort(items: ArrayList<Any>): HostAndPort {
        var host = getStringFromAny(items[0])
        var port = getInt64FromAny(items[1])
        return HostAndPort(host, UInt16(port))
    }

    private func getNodeKey(hnp: HostAndPort) {
        return hnp.toString()
    }

    private func getAssignedSlotArray(slotInfo: ArrayList<Any>): ArrayList<Int64> {
        let slotNums = ArrayList<Int64>()
        let start = getInt64FromAny(slotInfo[0])
        let end = getInt64FromAny(slotInfo[1])
        var slot = start
        while (slot <= end) {
            slotNums.add(slot)
            slot++
        }
        return slotNums
    }

    private func getStringFromAny(obj: Any): String {
        if (let Some(v) <- (obj as String)) {
            return v
        }
        throw RedisException("Failure to convert any type to string type")
    }

    private func getInt64FromAny(obj: Any): Int64 {
        if (let Some(v) <- (obj as Int64)) {
            return v
        }
        throw RedisException("Failure to convert any type to int64 type")
    }

    /**
        返回数据格式
        1) 1) (integer) 0
           2) (integer) 5460
           3) 1) "127.0.0.1"
              2) (integer) 30001
              3) "09dbe9720cda62f7865eabc5fd8857c5d2678366"
              4) 1) hostname
                 2) "host-1.redis.example.com"
           4) 1) "127.0.0.1"
              2) (integer) 30004
              3) "821d8ca00d7ccf931ed3ffc7e3db0599d2271abf"
              4) 1) hostname
                 2) "host-2.redis.example.com"
        2) 1) (integer) 5461
           2) (integer) 10922
           3) 1) "127.0.0.1"
              2) (integer) 30002
              3) "c9d93d9f2c0c524ff34cc11838c2003d8c29e013"
              4) 1) hostname
                 2) "host-3.redis.example.com"
           4) 1) "127.0.0.1"
              2) (integer) 30005
              3) "faadb3eb99009de4ab72ad6b6ed87634c7ee410f"
              4) 1) hostname
                 2) "host-4.redis.example.com"
     */
    private func executeClusterSlot(clientEndPoint: ClientTcpEndpoint): ArrayList<Any> {
        let command = RedisCommandBuilder.clusterSlots()
        let message = executeCommand(clientEndPoint, command)
        return command.getBuilder().build(message)
    }

    private func executePing(clientEndPoint: ClientTcpEndpoint): String {
        let command = RedisCommandBuilder.ping()
        let message = executeCommand(clientEndPoint, command)
        return command.getBuilder().build(message)
    }

    /**
     * 检查客户端是否已经关闭
     */
    private func checkOpen(): Unit {
        if (isClosed()) {
            throw RedisException("Client is already closed")
        }
    }

    private func isClosed(): Bool {
        return closed.load()
    }
}

/**
 * ClusterInfoCache的缓存中,用于关联ClusterInfoCache的key
 *
 * 普通模式和订阅模式,不能共享ClusterInfoCache
 */
class ClusterInfoCacheKey <: Hashable & Equatable<ClusterInfoCacheKey> {
    private let subscribeMode: Bool

    private let clusterConfig: ClusterRedisClientConfig

    public init(clusterConfig: ClusterRedisClientConfig) {
        this(clusterConfig, subscribeMode: false)
    }

    public init(clusterConfig: ClusterRedisClientConfig, subscribeMode!: Bool) {
        this.clusterConfig = clusterConfig
        this.subscribeMode = subscribeMode
    }

    @OverflowWrapping
    public override func hashCode(): Int64 {
        var hashCode = 53 * subscribeMode.hashCode()
        hashCode = hashCode + clusterConfig.hashCode()
        return hashCode
    }

    public operator override func ==(other: ClusterInfoCacheKey): Bool {
        if (subscribeMode != other.subscribeMode) {
            return false
        }

        if (clusterConfig != other.clusterConfig) {
            return false
        }

        return true
    }

    public operator override func !=(other: ClusterInfoCacheKey): Bool {
        if (subscribeMode != other.subscribeMode) {
            return true
        }

        if (clusterConfig != other.clusterConfig) {
            return true
        }

        return false
    }
}

/**
 * 延时关闭ClusterInfoCache的任务
 */
class CloseClusterInfoCacheTask <: Runnable {
    private let clusterInfoCache: ClusterInfoCache

    public init(clusterInfoCache: ClusterInfoCache) {
        this.clusterInfoCache = clusterInfoCache
    }

    public func run(): Unit {
        try {
            // 延迟一段时间再关闭, 在该期间内ClusterInfoCache再次被引用则取消关闭
            let delayDuraion = clusterInfoCache.getClusterConfig().closeClusterInfoCacheDelayDuration
            if (delayDuraion > Duration.Zero) {
                // 延迟一段时间再关闭, 在该期间内SentinelManager再次被引用则取消关闭
                sleep(delayDuraion)
            }
            clusterInfoCache.close()
        } catch (ex: Exception) {
            SentinelManager
                .logger
                .warn(
                    "Failure to close ClusterInfoCache ${clusterInfoCache.getClusterInfoCacheStatus()}",
                    ex
                )
        }
    }
}