mirror of
https://github.com/swift-server/RediStack.git
synced 2026-05-03 07:32:28 +00:00
ef5fdf7e63
In some circumstances users may have connection pools configured without any SocketAddresses ready to go. This is particularly likely in service discovery configurations. Right now, the effect of attempting to use such a pool is two fold. First, we'll emit a bunch of error level logs telling users we have no addresses. Second, we'll fall into the exponential backoff phase of connection establishment. The first property is annoying, but the second one is actively harmful. If your construction is timed incorrectly, we'll have the awkward problem of burning a bunch of CPU trying to create connections we know we cannot, and then a lengthy delay after the addresses are actually configured before we start trying to use them. That's the worst of all worlds. This patch adds logic to detect the attempt to create connections when we don't have any configured addresses and delays them. This path should improve performance and ergonomics when in this mode. Authored-by: Cory Benfield <lukasa@apple.com>
113 lines
4.2 KiB
Swift
113 lines
4.2 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the RediStack open source project
|
|
//
|
|
// Copyright (c) 2020 RediStack project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of RediStack project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import NIOCore
|
|
import RediStack
|
|
import XCTest
|
|
import NIOPosix
|
|
|
|
/// A helper `XCTestCase` subclass that does the standard work of creating a connection pool to use in test cases.
|
|
///
|
|
/// This is essentially the pooled version of `RedisIntegrationTestCase`
|
|
open class RedisConnectionPoolIntegrationTestCase: XCTestCase {
|
|
/// An overridable value of the Redis instance's hostname to connect to for the test suite(s).
|
|
///
|
|
/// The default value is `RedisConnection.defaultHostname`
|
|
///
|
|
/// This is especially useful to override if you build on Linux & macOS where Redis might be installed locally vs. through Docker.
|
|
open var redisHostname: String { RedisConnection.Configuration.defaultHostname }
|
|
|
|
/// The port to connect over to Redis, defaulting to `RedisConnection.defaultPort`.
|
|
open var redisPort: Int { RedisConnection.Configuration.defaultPort }
|
|
|
|
/// The password to use to connect to Redis. Default is `nil` - no password authentication.
|
|
open var redisPassword: String? { return nil }
|
|
|
|
public var pool: RedisConnectionPool!
|
|
|
|
public let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 3)
|
|
|
|
deinit {
|
|
do {
|
|
try self.eventLoopGroup.syncShutdownGracefully()
|
|
} catch {
|
|
print("Failed to gracefully shutdown ELG: \(error)")
|
|
}
|
|
}
|
|
|
|
/// Creates a `RediStack.RedisConnectionPool` for the next test case, calling `fatalError` if it was not successful.
|
|
///
|
|
/// See `XCTest.XCTestCase.setUp()`
|
|
open override func setUp() {
|
|
do {
|
|
self.pool = try self.makeNewPool()
|
|
} catch {
|
|
fatalError("Failed to make a RedisConnectionPool: \(error)")
|
|
}
|
|
}
|
|
|
|
/// Sends a "FLUSHALL" command to Redis to clear it of any data from the previous test, then closes the connection.
|
|
///
|
|
/// If any steps fail, a `fatalError` is thrown.
|
|
///
|
|
/// See `XCTest.XCTestCase.tearDown()`
|
|
open override func tearDown() {
|
|
do {
|
|
_ = try self.pool.send(command: "FLUSHALL").wait()
|
|
} catch let err as RedisConnectionPoolError where err == .poolClosed {
|
|
// Ok, this is fine.
|
|
} catch {
|
|
fatalError("Failed to clean up the pool: \(error)")
|
|
}
|
|
|
|
self.pool.close()
|
|
self.pool = nil
|
|
}
|
|
|
|
public func makeNewPool(
|
|
connectionRetryTimeout: TimeAmount? = .seconds(5),
|
|
minimumConnectionCount: Int = 0
|
|
) throws -> RedisConnectionPool {
|
|
try self.makeNewPool(
|
|
initialAddresses: nil,
|
|
initialConnectionBackoffDelay: .milliseconds(100),
|
|
connectionRetryTimeout: connectionRetryTimeout,
|
|
minimumConnectionCount: minimumConnectionCount
|
|
)
|
|
}
|
|
|
|
public func makeNewPool(
|
|
initialAddresses: [SocketAddress]?,
|
|
initialConnectionBackoffDelay: TimeAmount,
|
|
connectionRetryTimeout: TimeAmount?,
|
|
minimumConnectionCount: Int
|
|
) throws -> RedisConnectionPool {
|
|
let addresses = try initialAddresses ?? [SocketAddress.makeAddressResolvingHost(self.redisHostname, port: self.redisPort)]
|
|
let pool = RedisConnectionPool(
|
|
configuration: RedisConnectionPool.Configuration(
|
|
initialServerConnectionAddresses: addresses,
|
|
maximumConnectionCount: .maximumActiveConnections(4),
|
|
connectionFactoryConfiguration: .init(connectionPassword: self.redisPassword),
|
|
minimumConnectionCount: minimumConnectionCount,
|
|
initialConnectionBackoffDelay: initialConnectionBackoffDelay,
|
|
connectionRetryTimeout: connectionRetryTimeout
|
|
),
|
|
boundEventLoop: self.eventLoopGroup.next()
|
|
)
|
|
pool.activate()
|
|
|
|
return pool
|
|
}
|
|
}
|