Skip to content

Commit 49a2d58

Browse files
committed
WIP: refactor for Sendable and cleaner shared state handling
1 parent 946e3a8 commit 49a2d58

File tree

9 files changed

+567
-546
lines changed

9 files changed

+567
-546
lines changed

Package.resolved

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
// swift-tools-version: 5.7
2-
// The swift-tools-version declares the minimum version of Swift required to build this package.
3-
1+
// swift-tools-version: 5.9
42
import PackageDescription
53

64
let package = Package(
@@ -12,14 +10,19 @@ let package = Package(
1210
dependencies: [
1311
.package(url: "https://github.com/apple/swift-nio.git", from: "2.48.0"),
1412
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.23.0"),
15-
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.0"))
13+
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.0")),
1614
],
1715
targets: [
1816
.target(
1917
name: "AMQPProtocol",
2018
dependencies: [
2119
.product(name: "NIOCore", package: "swift-nio"),
22-
]),
20+
],
21+
swiftSettings: [
22+
SwiftSetting.unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"]),
23+
.enableUpcomingFeature("StrictConcurrency"),
24+
]
25+
),
2326
.target(
2427
name: "AMQPClient",
2528
dependencies: [
@@ -29,9 +32,19 @@ let package = Package(
2932
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
3033
.product(name: "NIOSSL", package: "swift-nio-ssl"),
3134
.product(name: "Collections", package: "swift-collections"),
32-
]),
35+
],
36+
swiftSettings: [
37+
SwiftSetting.unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"]),
38+
.enableUpcomingFeature("StrictConcurrency"),
39+
]
40+
),
3341
.testTarget(
3442
name: "AMQPClientTests",
35-
dependencies: ["AMQPClient"]),
43+
dependencies: ["AMQPClient"],
44+
swiftSettings: [
45+
SwiftSetting.unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"]),
46+
.enableUpcomingFeature("StrictConcurrency"),
47+
]
48+
),
3649
]
3750
)

Sources/AMQPClient/AMQPChannel.swift

Lines changed: 145 additions & 144 deletions
Large diffs are not rendered by default.

Sources/AMQPClient/AMQPConnection.swift

Lines changed: 63 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,36 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313

14+
import NIOConcurrencyHelpers
1415
import NIOCore
1516
import NIOPosix
1617
import NIOSSL
17-
import NIOConcurrencyHelpers
1818

19-
public final class AMQPConnection {
19+
public final class AMQPConnection: Sendable {
2020
internal enum ConnectionState {
2121
case open
2222
case shuttingDown
2323
case closed
2424
}
25-
25+
2626
public var isConnected: Bool {
2727
// `Channel.isActive` is set to false before the `closeFuture` resolves in cases where the channel might be
2828
// closed, or closing, before our state has been updated
29-
return self.channel.isActive && self.state.withLockedValue { $0 == .open }
30-
}
31-
32-
public var closeFuture: NIOCore.EventLoopFuture<Void> {
33-
return self.channel.closeFuture
29+
return channel.isActive && state.withLockedValue { $0 == .open }
3430
}
3531

36-
public var eventLoop: EventLoop { return self.channel.eventLoop }
32+
public var closeFuture: NIOCore.EventLoopFuture<Void> { connectionHandler.channel.closeFuture }
33+
public var eventLoop: EventLoop { return connectionHandler.channel.eventLoop }
3734

38-
private let channel: NIOCore.Channel
39-
private let multiplexer: AMQPConnectionMultiplexHandler
35+
private let connectionHandler: AMQPConnectionHandler
36+
private var channel: NIOCore.Channel { connectionHandler.channel }
4037

4138
private let state = NIOLockedValueBox(ConnectionState.open)
4239
private let channels: NIOLockedValueBox<AMQPChannels>
4340

44-
init(channel: NIOCore.Channel, multiplexer: AMQPConnectionMultiplexHandler, channelMax: UInt16) {
45-
self.channel = channel
46-
self.multiplexer = multiplexer
47-
self.channels = .init(AMQPChannels(channelMax: channelMax))
41+
init(connectionHandler: AMQPConnectionHandler, channelMax: UInt16) {
42+
self.connectionHandler = connectionHandler
43+
channels = .init(AMQPChannels(channelMax: channelMax))
4844
}
4945

5046
/// Connect to broker.
@@ -54,20 +50,27 @@ public final class AMQPConnection {
5450
/// - Returns: EventLoopFuture with AMQP Connection.
5551
public static func connect(use eventLoop: EventLoop, from config: AMQPConnectionConfiguration) -> EventLoopFuture<AMQPConnection> {
5652
let promise = eventLoop.makePromise(of: AMQPResponse.self)
57-
let multiplexer = AMQPConnectionMultiplexHandler(eventLoop: eventLoop, config: config.server, onReady: promise)
5853

5954
return eventLoop.flatSubmit { () -> EventLoopFuture<AMQPConnection> in
60-
let result = self.boostrapChannel(use: eventLoop, from: config, with: multiplexer).flatMap { channel in
55+
let multiplexer = NIOLoopBound(
56+
AMQPConnectionMultiplexHandler(eventLoop: eventLoop, config: config.server, onReady: promise),
57+
eventLoop: eventLoop
58+
)
59+
let result = self.boostrapChannel(use: eventLoop, from: config, with: multiplexer.value).flatMap { channel in
6160
promise.futureResult.flatMapThrowing { response in
62-
guard case .connection(let connection) = response, case .connected(let connected) = connection else {
61+
guard case let .connection(connection) = response, case let .connected(connected) = connection else {
6362
throw AMQPConnectionError.invalidResponse(response)
6463
}
6564

66-
return AMQPConnection(channel: channel, multiplexer: multiplexer, channelMax: connected.channelMax)
65+
return AMQPConnection(
66+
connectionHandler: .init(channel: channel, multiplexer: multiplexer.value),
67+
channelMax: connected.channelMax
68+
)
6769
}
6870
}
6971

70-
result.whenFailure { err in multiplexer.failAllResponses(because: err) }
72+
// TODO: fix passing around of multiplexer
73+
result.whenFailure { err in multiplexer.value.failAllResponses(because: err) }
7174
return result
7275
}
7376
}
@@ -77,24 +80,22 @@ public final class AMQPConnection {
7780
/// Channel ID is automatically assigned (next free one).
7881
/// - Returns: EventLoopFuture with AMQP Channel.
7982
public func openChannel() -> EventLoopFuture<AMQPChannel> {
80-
guard self.isConnected else { return self.eventLoop.makeFailedFuture(AMQPConnectionError.connectionClosed()) }
83+
guard isConnected else { return eventLoop.makeFailedFuture(AMQPConnectionError.connectionClosed()) }
8184

8285
let channelID = channels.withLockedValue { $0.reserveNext() }
83-
86+
8487
guard let channelID = channelID else {
85-
return self.eventLoop.makeFailedFuture(AMQPConnectionError.tooManyOpenedChannels)
88+
return eventLoop.makeFailedFuture(AMQPConnectionError.tooManyOpenedChannels)
8689
}
8790

88-
return self.eventLoop.flatSubmit {
89-
let future = self.multiplexer.openChannel(id: channelID)
91+
let future = connectionHandler.openChannel(id: channelID)
9092

91-
future.whenFailure { _ in self.channels.withLockedValue { $0.remove(id: channelID) } }
92-
93-
return future.map { channel in
94-
let amqpChannel = AMQPChannel(channelID: channelID, eventLoop: self.eventLoop, channel: channel)
95-
self.channels.withLockedValue { $0.add(channel: amqpChannel) }
96-
return amqpChannel
97-
}
93+
future.whenFailure { _ in self.channels.withLockedValue { $0.remove(id: channelID) } }
94+
95+
return future.map { channel in
96+
let amqpChannel = AMQPChannel(channelID: channelID, eventLoop: self.eventLoop, channel: channel)
97+
self.channels.withLockedValue { $0.add(channel: amqpChannel) }
98+
return amqpChannel
9899
}
99100
}
100101

@@ -109,37 +110,35 @@ public final class AMQPConnection {
109110
state = .shuttingDown
110111
return true
111112
}
112-
113+
113114
return false
114115
}
115-
116-
guard shouldClose else { return self.channel.closeFuture }
117-
118-
return self.eventLoop.flatSubmit {
119-
let result = self.multiplexer.close(reason: reason, code: code)
120-
.map { () in
121-
return nil as Error?
116+
117+
guard shouldClose else { return closeFuture }
118+
119+
let result = connectionHandler.close(reason: reason, code: code)
120+
.map { () in
121+
nil as Error?
122+
}
123+
.recover { $0 }
124+
.flatMap { result in
125+
self.channel.close().map {
126+
self.state.withLockedValue { $0 = .closed }
127+
return (result, nil) as (Error?, Error?)
122128
}
123-
.recover { $0 }
124-
.flatMap { result in
125-
self.channel.close().map {
129+
.recover { error in
130+
if case ChannelError.alreadyClosed = error {
126131
self.state.withLockedValue { $0 = .closed }
127-
return (result, nil) as (Error?, Error?)
128-
}
129-
.recover { error in
130-
if case ChannelError.alreadyClosed = error {
131-
self.state.withLockedValue { $0 = .closed }
132-
return (result, nil)
133-
}
134-
135-
return (result, error)
132+
return (result, nil)
136133
}
134+
135+
return (result, error)
137136
}
138-
return result.flatMapThrowing {
139-
let (broker, conn) = $0
140-
if (broker ?? conn) != nil { throw AMQPConnectionError.connectionClose(broker: broker, connection: conn) }
141-
return ()
142137
}
138+
return result.flatMapThrowing {
139+
let (broker, conn) = $0
140+
if (broker ?? conn) != nil { throw AMQPConnectionError.connectionClose(broker: broker, connection: conn) }
141+
return ()
143142
}
144143
}
145144

@@ -161,17 +160,16 @@ public final class AMQPConnection {
161160
channel.pipeline.addHandlers([
162161
MessageToByteHandler(AMQPFrameEncoder()),
163162
ByteToMessageHandler(AMQPFrameDecoder()),
164-
handler
163+
handler,
165164
])
166165
}
167166
.connect(host: config.server.host, port: config.server.port)
168-
.map { channelPromise.succeed($0) }
169-
.cascadeFailure(to: channelPromise)
167+
.cascade(to: channelPromise)
170168
} catch {
171169
channelPromise.fail(error)
172170
}
173171

174-
return channelPromise.futureResult
172+
return channelPromise.futureResult
175173
}
176174

177175
private static func boostrapClient(
@@ -182,17 +180,17 @@ public final class AMQPConnection {
182180
preconditionFailure("Cannot create bootstrap for the supplied EventLoop")
183181
}
184182

185-
switch config.connection {
186-
case .plain:
183+
switch config.connection {
184+
case .plain:
187185
return NIOClientTCPBootstrap(clientBootstrap, tls: NIOInsecureNoTLS())
188-
case .tls(let tls, let sniServerName):
186+
case let .tls(tls, sniServerName):
189187
let sslContext = try NIOSSLContext(configuration: tls ?? TLSConfiguration.clientDefault)
190188
let tlsProvider = try NIOSSLClientTLSProvider<ClientBootstrap>(context: sslContext, serverHostname: sniServerName ?? config.server.host)
191189
let bootstrap = NIOClientTCPBootstrap(clientBootstrap, tls: tlsProvider)
192190
return bootstrap.enableTLS()
193-
}
191+
}
194192
}
195-
193+
196194
deinit {
197195
if isConnected {
198196
assertionFailure("close() was not called before deinit!")

Sources/AMQPClient/AsyncAwaitSupport/AMQPChannel+async.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
// SPDX-License-Identifier: Apache-2.0
1111
//
1212
//===----------------------------------------------------------------------===//
13-
14-
#if compiler(>=5.5) && canImport(_Concurrency)
15-
1613
import NIOCore
1714
import AMQPProtocol
1815

@@ -461,6 +458,4 @@ final class AMQPStream<Element> {
461458

462459
return AMQPSequence(stream, name: self.name)
463460
}
464-
}
465-
466-
#endif // compiler(>=5.5)
461+
}

0 commit comments

Comments
 (0)