@@ -157,21 +157,38 @@ public final class ValkeyClusterClient: Sendable {
157
157
try await self . client ( for: hashSlots)
158
158
}
159
159
160
- while true {
160
+ while !Task . isCancelled {
161
161
do {
162
- let respToken = try await self . retryingSend (
163
- clientSelector: clientSelector,
164
- command: command,
165
- logger: logger
166
- )
167
- return try Command . Response ( fromRESP: respToken)
162
+ let client = try await clientSelector ( )
163
+ return try await client. send ( command: command)
164
+ } catch ValkeyClusterError . noNodeToTalkTo {
165
+ // TODO: Rerun node discovery!
168
166
} catch let error as ValkeyClientError where error. errorCode == . commandError {
169
167
guard let errorMessage = error. message, let movedError = ValkeyMovedError ( errorMessage) else {
170
168
throw error
171
169
}
172
170
clientSelector = { try await self . client ( for: movedError) }
173
171
}
174
172
}
173
+ throw CancellationError ( )
174
+ }
175
+
176
+ /// Get connection from cluster and run operation using connection
177
+ ///
178
+ /// - Parameters:
179
+ /// - keys: Keys affected by operation. This is used to choose the cluster node
180
+ /// - isolation: Actor isolation
181
+ /// - operation: Closure handling Valkey connection
182
+ /// - Returns: Value returned by closure
183
+ @inlinable
184
+ public func withConnection< Value> (
185
+ forKeys keys: some Collection < ValkeyKey > ,
186
+ isolation: isolated ( any Actor ) ? = #isolation,
187
+ operation: ( ValkeyConnection ) async throws -> sending Value
188
+ ) async throws -> Value {
189
+ let hashSlots = keys. map { HashSlot ( key: $0) }
190
+ let client = try await self . client ( for: hashSlots)
191
+ return try await client. withConnection ( isolation: isolation, operation: operation)
175
192
}
176
193
177
194
/// Starts running the cluster client.
@@ -455,39 +472,6 @@ public final class ValkeyClusterClient: Sendable {
455
472
throw ValkeyClusterError . clusterIsMissingSlotAssignment
456
473
}
457
474
458
- /// Sends a command to the Valkey cluster with automatic retries and error handling.
459
- ///
460
- /// This internal method handles:
461
- /// - Client selection strategy using the provided selector function
462
- /// - Automatic retries for certain types of failures
463
- /// - Task cancellation
464
- ///
465
- /// - Parameters:
466
- /// - clientSelector: A function that resolves to the appropriate client for this command.
467
- /// - command: The command to send to the cluster.
468
- /// - logger: The logger to use for diagnostic information.
469
- /// - Returns: The raw RESP token response.
470
- /// - Throws:
471
- /// - `CancellationError` if the task is cancelled
472
- /// - Other errors if command execution fails after retries
473
- @inlinable
474
- func retryingSend(
475
- clientSelector: ( ) async throws -> ValkeyClient ,
476
- command: some ValkeyCommand ,
477
- logger: Logger
478
- ) async throws -> RESPToken {
479
- while !Task. isCancelled {
480
- do {
481
- let client = try await clientSelector ( )
482
- return try await client. _send ( command)
483
- } catch ValkeyClusterError . noNodeToTalkTo {
484
- // TODO: Rerun node discovery!
485
- }
486
- }
487
-
488
- throw CancellationError ( )
489
- }
490
-
491
475
/// Generates a new unique request ID for tracking internal operations.
492
476
///
493
477
/// - Returns: A unique integer ID for tracking requests and waiters.
0 commit comments