Skip to content

Commit dcfd81b

Browse files
committed
Add NIORedis that handles creating new connections
1 parent 20ec21a commit dcfd81b

File tree

2 files changed

+87
-8
lines changed

2 files changed

+87
-8
lines changed

Sources/NIORedis/NIORedis.swift

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,71 @@
1-
struct NIORedis {
2-
var text = "Hello, World!"
1+
import NIO
2+
import NIOConcurrencyHelpers
3+
4+
/// A factory that handles all necessary details for creating connections to a Redis database instance.
5+
public final class NIORedis {
6+
/// The threading model to use for asynchronous tasks.
7+
///
8+
/// Using `.eventLoopGroup` will allow an external provider to handle the lifetime of the `EventLoopGroup`,
9+
/// while using `spawnThreads` will cause this `NIORedis` instance to handle the lifetime of a new `EventLoopGroup`.
10+
public enum ExecutionModel {
11+
case spawnThreads(Int)
12+
case eventLoopGroup(EventLoopGroup)
13+
}
14+
15+
private let executionModel: ExecutionModel
16+
private let elg: EventLoopGroup
17+
private let isRunning = Atomic<Bool>(value: true)
18+
19+
deinit { assert(!isRunning.load(), "Redis driver was not properly shut down!") }
20+
21+
/// Creates a handle to create connections to a Redis instance using the `ExecutionModel` provided.
22+
/// - Parameter executionModel: The model to use for handling asynchronous scheduling.
23+
public init(executionModel model: ExecutionModel) {
24+
self.executionModel = model
25+
26+
switch model {
27+
case .spawnThreads(let count):
28+
self.elg = MultiThreadedEventLoopGroup(numberOfThreads: count)
29+
case .eventLoopGroup(let group):
30+
self.elg = group
31+
}
32+
}
33+
34+
/// Creates a new `NIORedisConnection` with the connection parameters provided.
35+
public func makeConnection(
36+
hostname: String = "localhost",
37+
port: Int = 6379,
38+
password: String? = nil
39+
) -> EventLoopFuture<NIORedisConnection> {
40+
let channelHandler = RedisMessenger(on: elg.next())
41+
let bootstrap = ClientBootstrap(group: self.elg)
42+
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
43+
.channelInitializer { channel in
44+
return channel.pipeline.addHandlers(
45+
RedisDataEncoder(),
46+
ByteToMessageHandler(RedisDataDecoder()),
47+
channelHandler
48+
)
49+
}
50+
51+
return bootstrap.connect(host: hostname, port: port)
52+
.map { return NIORedisConnection(channel: $0, handler: channelHandler) }
53+
}
54+
55+
/// Handles the proper shutdown of managed resources.
56+
/// - Important: This method should always be called before deinit.
57+
public func terminate() throws {
58+
guard isRunning.exchange(with: false) else { return }
59+
60+
switch executionModel {
61+
case .spawnThreads: try self.elg.syncShutdownGracefully()
62+
case .eventLoopGroup: return
63+
}
64+
}
65+
}
66+
67+
private extension ChannelPipeline {
68+
func addHandlers(_ handlers: ChannelHandler...) -> EventLoopFuture<Void> {
69+
return EventLoopFuture<Void>.andAll(handlers.map { add(handler: $0) }, eventLoop: eventLoop)
70+
}
371
}

Tests/NIORedisTests/NIORedisTests.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@
22
import XCTest
33

44
final class NIORedisTests: XCTestCase {
5-
func testExample() {
6-
// This is an example of a functional test case.
7-
// Use XCTAssert and related functions to verify your tests produce the correct
8-
// results.
9-
XCTAssertEqual(NIORedis().text, "Hello, World!")
5+
func test_makeConnection() {
6+
let redis = NIORedis(executionModel: .spawnThreads(1))
7+
defer { try? redis.terminate() }
8+
9+
XCTAssertNoThrow(try redis.makeConnection().wait())
10+
}
11+
12+
func test_command() throws {
13+
let redis = NIORedis(executionModel: .spawnThreads(1))
14+
defer { try? redis.terminate() }
15+
16+
let connection = try redis.makeConnection().wait()
17+
let result = try connection.command("SADD", [.bulkString("key".convertedToData()), try 3.convertToRedisData()]).wait()
18+
XCTAssertNotNil(result.int)
19+
XCTAssertEqual(result.int, 1)
20+
try connection.command("DEL", [.bulkString("key".convertedToData())]).wait()
1021
}
1122

1223
static var allTests = [
13-
("testExample", testExample),
24+
("test_makeConnection", test_makeConnection),
1425
]
1526
}

0 commit comments

Comments
 (0)