@@ -42,7 +42,9 @@ public struct ServerAddress: Sendable, Equatable {
4242}
4343
4444/// Single connection to a Valkey database
45- public final class ValkeyConnection : Sendable {
45+ public final actor ValkeyConnection : Sendable {
46+ nonisolated public let unownedExecutor : UnownedSerialExecutor
47+
4648 /// Logger used by Server
4749 let logger : Logger
4850 @usableFromInline
@@ -59,6 +61,7 @@ public final class ValkeyConnection: Sendable {
5961 configuration: ValkeyClientConfiguration ,
6062 logger: Logger
6163 ) {
64+ self . unownedExecutor = channel. eventLoop. executor. asUnownedSerialExecutor ( )
6265 self . channel = channel
6366 self . channelHandler = channelHandler
6467 self . configuration = configuration
@@ -77,16 +80,17 @@ public final class ValkeyConnection: Sendable {
7780 public static func connect(
7881 address: ServerAddress ,
7982 configuration: ValkeyClientConfiguration ,
80- eventLoopGroup : EventLoopGroup = MultiThreadedEventLoopGroup . singleton,
83+ eventLoop : EventLoop = MultiThreadedEventLoopGroup . singleton. any ( ) ,
8184 logger: Logger
8285 ) async throws -> ValkeyConnection {
83- let ( channel, channelHandler) = try await makeClient (
84- address: address,
85- eventLoopGroup: eventLoopGroup,
86- configuration: configuration,
87- logger: logger
88- )
89- let connection = ValkeyConnection ( channel: channel, channelHandler: channelHandler, configuration: configuration, logger: logger)
86+ let future = if eventLoop. inEventLoop {
87+ self . _makeClient ( address: address, eventLoop: eventLoop, configuration: configuration, logger: logger)
88+ } else {
89+ eventLoop. flatSubmit {
90+ self . _makeClient ( address: address, eventLoop: eventLoop, configuration: configuration, logger: logger)
91+ }
92+ }
93+ let connection = try await future. get ( )
9094 if configuration. respVersion == . v3 {
9195 try await connection. resp3Upgrade ( )
9296 }
@@ -109,12 +113,7 @@ public final class ValkeyConnection: Sendable {
109113
110114 @inlinable
111115 public func send< Command: RESPCommand > ( command: Command ) async throws -> Command . Response {
112- var encoder = RESPCommandEncoder ( )
113- command. encode ( into: & encoder)
114-
115- let promise = channel. eventLoop. makePromise ( of: RESPToken . self)
116- channelHandler. write ( request: ValkeyRequest . single ( buffer: encoder. buffer, promise: promise) )
117- return try await . init( from: promise. futureResult. get ( ) )
116+ try await self . channelHandler. send ( command: command)
118117 }
119118
120119 /// Pipeline a series of commands to Valkey connection
@@ -126,90 +125,73 @@ public final class ValkeyConnection: Sendable {
126125 public func pipeline< each Command : RESPCommand > (
127126 _ commands: repeat each Command
128127 ) async throws -> ( repeat ( each Command ) . Response) {
129- // this currently allocates a promise for every command. We could collpase this down to one promise
130- var promises : [ EventLoopPromise < RESPToken > ] = [ ]
131- var encoder = RESPCommandEncoder ( )
132- for command in repeat each commands {
133- command. encode ( into: & encoder)
134- promises. append ( channel. eventLoop. makePromise ( of: RESPToken . self) )
135- }
136- // write directly to channel handler
137- channelHandler. write ( request: ValkeyRequest . multiple ( buffer: encoder. buffer, promises: promises) )
138- // get response from channel handler
139- var index = AutoIncrementingInteger ( )
140- return try await ( repeat ( each Command) . Response ( from: promises [ index. next ( ) ] . futureResult. get ( ) ) )
128+ try await self . channelHandler. pipeline ( repeat each commands)
141129 }
142130
143131 /// Try to upgrade to RESP3
144132 private func resp3Upgrade( ) async throws {
145133 _ = try await send ( command: HELLO ( arguments: . init( protover: 3 , auth: nil , clientname: nil ) ) )
146134 }
147135
148- /// Create Valkey connection and return channel connection is running on and the Valkey channel handler
149- private static func makeClient(
136+ private static func _makeClient(
150137 address: ServerAddress ,
151- eventLoopGroup : EventLoopGroup ,
138+ eventLoop : EventLoop ,
152139 configuration: ValkeyClientConfiguration ,
153140 logger: Logger
154- ) async throws -> ( Channel , ValkeyChannelHandler ) {
141+ ) -> EventLoopFuture < ValkeyConnection > {
155142 // get bootstrap
156- let bootstrap : ClientBootstrapProtocol
143+ eventLoop. assertInEventLoop ( )
144+
145+ let bootstrap : NIOClientTCPBootstrapProtocol
157146 #if canImport(Network)
158- if let tsBootstrap = createTSBootstrap ( eventLoopGroup: eventLoopGroup , tlsOptions: nil ) {
147+ if let tsBootstrap = createTSBootstrap ( eventLoopGroup: eventLoop , tlsOptions: nil ) {
159148 bootstrap = tsBootstrap
160149 } else {
161150 #if os(iOS) || os(tvOS)
162151 self . logger. warning (
163152 " Running BSD sockets on iOS or tvOS is not recommended. Please use NIOTSEventLoopGroup, to run with the Network framework "
164153 )
165154 #endif
166- bootstrap = self . createSocketsBootstrap ( eventLoopGroup: eventLoopGroup )
155+ bootstrap = self . createSocketsBootstrap ( eventLoopGroup: eventLoop )
167156 }
168157 #else
169- bootstrap = self . createSocketsBootstrap ( eventLoopGroup: eventLoopGroup )
158+ bootstrap = self . createSocketsBootstrap ( eventLoopGroup: eventLoop )
170159 #endif
171160
172- // connect
173- let channel : Channel
174- let channelHandler : ValkeyChannelHandler
175- do {
176- switch address. value {
177- case . hostname( let host, let port) :
178- ( channel, channelHandler) =
179- try await bootstrap
180- . connect ( host: host, port: port) { channel in
181- setupChannel ( channel, configuration: configuration, logger: logger)
182- }
161+ let connect = bootstrap. channelInitializer { channel in
162+ do {
163+ let sync = channel. pipeline. syncOperations
164+ if case . enable( let sslContext, let tlsServerName) = configuration. tls. base {
165+ try sync. addHandler ( NIOSSLClientHandler ( context: sslContext, serverHostname: tlsServerName) )
166+ }
167+ let valkeyChannelHandler = ValkeyChannelHandler (
168+ eventLoop: channel. eventLoop,
169+ logger: logger
170+ )
171+ try sync. addHandler ( valkeyChannelHandler)
172+ return eventLoop. makeSucceededVoidFuture ( )
173+ } catch {
174+ return eventLoop. makeFailedFuture ( error)
175+ }
176+ }
177+
178+ let future : EventLoopFuture < Channel >
179+ switch address. value {
180+ case . hostname( let host, let port) :
181+ future = connect. connect ( host: host, port: port)
182+ future. whenSuccess { _ in
183183 logger. debug ( " Client connnected to \( host) : \( port) " )
184- case . unixDomainSocket( let path) :
185- ( channel, channelHandler) =
186- try await bootstrap
187- . connect ( unixDomainSocketPath: path) { channel in
188- setupChannel ( channel, configuration: configuration, logger: logger)
189- }
184+ }
185+ case . unixDomainSocket( let path) :
186+ future = connect. connect ( unixDomainSocketPath: path)
187+ future. whenSuccess { _ in
190188 logger. debug ( " Client connnected to socket path \( path) " )
191189 }
192- return ( channel, channelHandler)
193- } catch {
194- throw error
195190 }
196- }
197191
198- private static func setupChannel(
199- _ channel: Channel ,
200- configuration: ValkeyClientConfiguration ,
201- logger: Logger
202- ) -> EventLoopFuture < ( Channel , ValkeyChannelHandler ) > {
203- channel. eventLoop. makeCompletedFuture {
204- if case . enable( let sslContext, let tlsServerName) = configuration. tls. base {
205- try channel. pipeline. syncOperations. addHandler ( NIOSSLClientHandler ( context: sslContext, serverHostname: tlsServerName) )
206- }
207- let valkeyChannelHandler = ValkeyChannelHandler (
208- channel: channel,
209- logger: logger
210- )
211- try channel. pipeline. syncOperations. addHandler ( valkeyChannelHandler)
212- return ( channel, valkeyChannelHandler)
192+ return future. flatMapThrowing { channel in
193+ let handler = try channel. pipeline. syncOperations. handler ( type: ValkeyChannelHandler . self)
194+ return ValkeyConnection ( channel: channel, channelHandler: handler, configuration: configuration, logger: logger)
213195 }
214196 }
215197
0 commit comments