Skip to content

Commit 7b621c1

Browse files
authored
Enable StrictConcurrency checking (#483)
1 parent 6c3d0a9 commit 7b621c1

File tree

9 files changed

+63
-50
lines changed

9 files changed

+63
-50
lines changed

Package.swift

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// swift-tools-version:5.8
22
import PackageDescription
33

4+
let swiftSettings: [SwiftSetting] = [
5+
.enableUpcomingFeature("StrictConcurrency")
6+
]
7+
48
let package = Package(
59
name: "postgres-nio",
610
platforms: [
@@ -41,23 +45,26 @@ let package = Package(
4145
.product(name: "NIOSSL", package: "swift-nio-ssl"),
4246
.product(name: "NIOFoundationCompat", package: "swift-nio"),
4347
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
44-
]
48+
],
49+
swiftSettings: swiftSettings
4550
),
4651
.target(
4752
name: "_ConnectionPoolModule",
4853
dependencies: [
4954
.product(name: "Atomics", package: "swift-atomics"),
5055
.product(name: "DequeModule", package: "swift-collections"),
5156
],
52-
path: "Sources/ConnectionPoolModule"
57+
path: "Sources/ConnectionPoolModule",
58+
swiftSettings: swiftSettings
5359
),
5460
.testTarget(
5561
name: "PostgresNIOTests",
5662
dependencies: [
5763
.target(name: "PostgresNIO"),
5864
.product(name: "NIOEmbedded", package: "swift-nio"),
5965
.product(name: "NIOTestUtils", package: "swift-nio"),
60-
]
66+
],
67+
swiftSettings: swiftSettings
6168
),
6269
.testTarget(
6370
name: "ConnectionPoolModuleTests",
@@ -67,14 +74,16 @@ let package = Package(
6774
.product(name: "NIOCore", package: "swift-nio"),
6875
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
6976
.product(name: "NIOEmbedded", package: "swift-nio"),
70-
]
77+
],
78+
swiftSettings: swiftSettings
7179
),
7280
.testTarget(
7381
name: "IntegrationTests",
7482
dependencies: [
7583
.target(name: "PostgresNIO"),
7684
.product(name: "NIOTestUtils", package: "swift-nio"),
77-
]
85+
],
86+
swiftSettings: swiftSettings
7887
),
7988
]
8089
)

Sources/ConnectionPoolModule/ConnectionPool.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
3-
public struct ConnectionAndMetadata<Connection: PooledConnection> {
3+
public struct ConnectionAndMetadata<Connection: PooledConnection>: Sendable {
44

55
public var connection: Connection
66

@@ -495,7 +495,7 @@ public final class ConnectionPool<
495495
}
496496

497497
@usableFromInline
498-
enum TimerRunResult {
498+
enum TimerRunResult: Sendable {
499499
case timerTriggered
500500
case timerCancelled
501501
case cancellationContinuationFinished

Sources/ConnectionPoolModule/ConnectionPoolObservabilityDelegate.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public protocol ConnectionPoolObservabilityDelegate: Sendable {
3737
func requestQueueDepthChanged(_ newDepth: Int)
3838
}
3939

40-
public struct NoOpConnectionPoolMetrics<ConnectionID: Hashable>: ConnectionPoolObservabilityDelegate {
40+
public struct NoOpConnectionPoolMetrics<ConnectionID: Hashable & Sendable>: ConnectionPoolObservabilityDelegate {
4141
public init(connectionIDType: ConnectionID.Type) {}
4242

4343
public func startedConnecting(id: ConnectionID) {}

Sources/PostgresNIO/Message/PostgresMessage+Identifier.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ extension PostgresMessage {
44
/// Identifies an incoming or outgoing postgres message. Sent as the first byte, before the message size.
55
/// Values are not unique across all identifiers, meaning some messages will require keeping state to identify.
66
@available(*, deprecated, message: "Will be removed from public API.")
7-
public struct Identifier: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
7+
public struct Identifier: Sendable, ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
88
// special
99
public static let none: Identifier = 0x00
1010
// special

Sources/PostgresNIO/Pool/PostgresClient.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ extension PostgresConnection: PooledConnection {
478478
self.channel.close(mode: .all, promise: nil)
479479
}
480480

481-
public func onClose(_ closure: @escaping ((any Error)?) -> ()) {
481+
public func onClose(_ closure: @escaping @Sendable ((any Error)?) -> ()) {
482482
self.closeFuture.whenComplete { _ in closure(nil) }
483483
}
484484
}

Sources/PostgresNIO/Utilities/PostgresError+Code.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
extension PostgresError {
2-
public struct Code: ExpressibleByStringLiteral, Equatable {
2+
public struct Code: Sendable, ExpressibleByStringLiteral, Equatable {
33
// Class 00 — Successful Completion
44
public static let successfulCompletion: Code = "00000"
55

Tests/ConnectionPoolModuleTests/Mocks/MockConnectionFactory.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import DequeModule
33

44
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
5-
final class MockConnectionFactory<Clock: _Concurrency.Clock> where Clock.Duration == Duration {
5+
final class MockConnectionFactory<Clock: _Concurrency.Clock>: Sendable where Clock.Duration == Duration {
66
typealias ConnectionIDGenerator = _ConnectionPoolModule.ConnectionIDGenerator
77
typealias Request = ConnectionRequest<MockConnection>
88
typealias KeepAliveBehavior = MockPingPongBehavior

Tests/IntegrationTests/PostgresNIOTests.swift

+31-30
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Logging
22
@testable import PostgresNIO
3+
import Atomics
34
import XCTest
45
import NIOCore
56
import NIOPosix
@@ -112,59 +113,59 @@ final class PostgresNIOTests: XCTestCase {
112113
XCTAssertNoThrow(conn = try PostgresConnection.test(on: eventLoop).wait())
113114
defer { XCTAssertNoThrow( try conn?.close().wait() ) }
114115

115-
var receivedNotifications: [PostgresMessage.NotificationResponse] = []
116+
let receivedNotifications = ManagedAtomic<Int>(0)
116117
conn?.addListener(channel: "example") { context, notification in
117-
receivedNotifications.append(notification)
118+
receivedNotifications.wrappingIncrement(ordering: .relaxed)
119+
XCTAssertEqual(notification.channel, "example")
120+
XCTAssertEqual(notification.payload, "")
118121
}
119122
XCTAssertNoThrow(_ = try conn?.simpleQuery("LISTEN example").wait())
120123
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example").wait())
121124
// Notifications are asynchronous, so we should run at least one more query to make sure we'll have received the notification response by then
122125
XCTAssertNoThrow(_ = try conn?.simpleQuery("SELECT 1").wait())
123-
XCTAssertEqual(receivedNotifications.count, 1)
124-
XCTAssertEqual(receivedNotifications.first?.channel, "example")
125-
XCTAssertEqual(receivedNotifications.first?.payload, "")
126+
XCTAssertEqual(receivedNotifications.load(ordering: .relaxed), 1)
126127
}
127128

128129
func testNotificationsNonEmptyPayload() {
129130
var conn: PostgresConnection?
130131
XCTAssertNoThrow(conn = try PostgresConnection.test(on: eventLoop).wait())
131132
defer { XCTAssertNoThrow( try conn?.close().wait() ) }
132-
var receivedNotifications: [PostgresMessage.NotificationResponse] = []
133+
let receivedNotifications = ManagedAtomic<Int>(0)
133134
conn?.addListener(channel: "example") { context, notification in
134-
receivedNotifications.append(notification)
135+
receivedNotifications.wrappingIncrement(ordering: .relaxed)
136+
XCTAssertEqual(notification.channel, "example")
137+
XCTAssertEqual(notification.payload, "Notification payload example")
135138
}
136139
XCTAssertNoThrow(_ = try conn?.simpleQuery("LISTEN example").wait())
137140
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example, 'Notification payload example'").wait())
138141
// Notifications are asynchronous, so we should run at least one more query to make sure we'll have received the notification response by then
139142
XCTAssertNoThrow(_ = try conn?.simpleQuery("SELECT 1").wait())
140-
XCTAssertEqual(receivedNotifications.count, 1)
141-
XCTAssertEqual(receivedNotifications.first?.channel, "example")
142-
XCTAssertEqual(receivedNotifications.first?.payload, "Notification payload example")
143+
XCTAssertEqual(receivedNotifications.load(ordering: .relaxed), 1)
143144
}
144145

145146
func testNotificationsRemoveHandlerWithinHandler() {
146147
var conn: PostgresConnection?
147148
XCTAssertNoThrow(conn = try PostgresConnection.test(on: eventLoop).wait())
148149
defer { XCTAssertNoThrow( try conn?.close().wait() ) }
149-
var receivedNotifications = 0
150+
let receivedNotifications = ManagedAtomic<Int>(0)
150151
conn?.addListener(channel: "example") { context, notification in
151-
receivedNotifications += 1
152+
receivedNotifications.wrappingIncrement(ordering: .relaxed)
152153
context.stop()
153154
}
154155
XCTAssertNoThrow(_ = try conn?.simpleQuery("LISTEN example").wait())
155156
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example").wait())
156157
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example").wait())
157158
XCTAssertNoThrow(_ = try conn?.simpleQuery("SELECT 1").wait())
158-
XCTAssertEqual(receivedNotifications, 1)
159+
XCTAssertEqual(receivedNotifications.load(ordering: .relaxed), 1)
159160
}
160161

161162
func testNotificationsRemoveHandlerOutsideHandler() {
162163
var conn: PostgresConnection?
163164
XCTAssertNoThrow(conn = try PostgresConnection.test(on: eventLoop).wait())
164165
defer { XCTAssertNoThrow( try conn?.close().wait() ) }
165-
var receivedNotifications = 0
166+
let receivedNotifications = ManagedAtomic<Int>(0)
166167
let context = conn?.addListener(channel: "example") { context, notification in
167-
receivedNotifications += 1
168+
receivedNotifications.wrappingIncrement(ordering: .relaxed)
168169
}
169170
XCTAssertNotNil(context)
170171
XCTAssertNoThrow(_ = try conn?.simpleQuery("LISTEN example").wait())
@@ -173,47 +174,47 @@ final class PostgresNIOTests: XCTestCase {
173174
context?.stop()
174175
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example").wait())
175176
XCTAssertNoThrow(_ = try conn?.simpleQuery("SELECT 1").wait())
176-
XCTAssertEqual(receivedNotifications, 1)
177+
XCTAssertEqual(receivedNotifications.load(ordering: .relaxed), 1)
177178
}
178179

179180
func testNotificationsMultipleRegisteredHandlers() {
180181
var conn: PostgresConnection?
181182
XCTAssertNoThrow(conn = try PostgresConnection.test(on: eventLoop).wait())
182183
defer { XCTAssertNoThrow( try conn?.close().wait() ) }
183-
var receivedNotifications1 = 0
184+
let receivedNotifications1 = ManagedAtomic<Int>(0)
184185
conn?.addListener(channel: "example") { context, notification in
185-
receivedNotifications1 += 1
186+
receivedNotifications1.wrappingIncrement(ordering: .relaxed)
186187
}
187-
var receivedNotifications2 = 0
188+
let receivedNotifications2 = ManagedAtomic<Int>(0)
188189
conn?.addListener(channel: "example") { context, notification in
189-
receivedNotifications2 += 1
190+
receivedNotifications2.wrappingIncrement(ordering: .relaxed)
190191
}
191192
XCTAssertNoThrow(_ = try conn?.simpleQuery("LISTEN example").wait())
192193
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example").wait())
193194
XCTAssertNoThrow(_ = try conn?.simpleQuery("SELECT 1").wait())
194-
XCTAssertEqual(receivedNotifications1, 1)
195-
XCTAssertEqual(receivedNotifications2, 1)
195+
XCTAssertEqual(receivedNotifications1.load(ordering: .relaxed), 1)
196+
XCTAssertEqual(receivedNotifications2.load(ordering: .relaxed), 1)
196197
}
197198

198199
func testNotificationsMultipleRegisteredHandlersRemoval() throws {
199200
var conn: PostgresConnection?
200201
XCTAssertNoThrow(conn = try PostgresConnection.test(on: eventLoop).wait())
201202
defer { XCTAssertNoThrow( try conn?.close().wait() ) }
202-
var receivedNotifications1 = 0
203+
let receivedNotifications1 = ManagedAtomic<Int>(0)
203204
XCTAssertNotNil(conn?.addListener(channel: "example") { context, notification in
204-
receivedNotifications1 += 1
205+
receivedNotifications1.wrappingIncrement(ordering: .relaxed)
205206
context.stop()
206207
})
207-
var receivedNotifications2 = 0
208+
let receivedNotifications2 = ManagedAtomic<Int>(0)
208209
XCTAssertNotNil(conn?.addListener(channel: "example") { context, notification in
209-
receivedNotifications2 += 1
210+
receivedNotifications2.wrappingIncrement(ordering: .relaxed)
210211
})
211212
XCTAssertNoThrow(_ = try conn?.simpleQuery("LISTEN example").wait())
212213
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example").wait())
213214
XCTAssertNoThrow(_ = try conn?.simpleQuery("NOTIFY example").wait())
214215
XCTAssertNoThrow(_ = try conn?.simpleQuery("SELECT 1").wait())
215-
XCTAssertEqual(receivedNotifications1, 1)
216-
XCTAssertEqual(receivedNotifications2, 2)
216+
XCTAssertEqual(receivedNotifications1.load(ordering: .relaxed), 1)
217+
XCTAssertEqual(receivedNotifications2.load(ordering: .relaxed), 2)
217218
}
218219

219220
func testNotificationHandlerFiltersOnChannel() {
@@ -1283,11 +1284,11 @@ final class PostgresNIOTests: XCTestCase {
12831284
XCTAssertNoThrow(conn = try PostgresConnection.test(on: eventLoop).wait())
12841285
defer { XCTAssertNoThrow( try conn?.close().wait() ) }
12851286
var queries: [[PostgresRow]]?
1286-
XCTAssertNoThrow(queries = try conn?.prepare(query: "SELECT $1::text as foo;", handler: { query in
1287+
XCTAssertNoThrow(queries = try conn?.prepare(query: "SELECT $1::text as foo;", handler: { [eventLoop] query in
12871288
let a = query.execute(["a"])
12881289
let b = query.execute(["b"])
12891290
let c = query.execute(["c"])
1290-
return EventLoopFuture.whenAllSucceed([a, b, c], on: self.eventLoop)
1291+
return EventLoopFuture.whenAllSucceed([a, b, c], on: eventLoop)
12911292
}).wait())
12921293
XCTAssertEqual(queries?.count, 3)
12931294
var resultIterator = queries?.makeIterator()

Tests/PostgresNIOTests/New/PostgresConnectionTests.swift

+11-8
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ class PostgresConnectionTests: XCTestCase {
187187
func testSimpleListenConnectionDrops() async throws {
188188
let (connection, channel) = try await self.makeTestConnectionWithAsyncTestingChannel()
189189

190-
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
190+
try await withThrowingTaskGroup(of: Void.self) { [logger] taskGroup in
191191
taskGroup.addTask {
192192
let events = try await connection.listen("foo")
193193
var iterator = events.makeAsyncIterator()
@@ -197,7 +197,7 @@ class PostgresConnectionTests: XCTestCase {
197197
_ = try await iterator.next()
198198
XCTFail("Did not expect to not throw")
199199
} catch {
200-
self.logger.error("error", metadata: ["error": "\(error)"])
200+
logger.error("error", metadata: ["error": "\(error)"])
201201
}
202202
}
203203

@@ -226,10 +226,10 @@ class PostgresConnectionTests: XCTestCase {
226226

227227
func testCloseGracefullyClosesWhenInternalQueueIsEmpty() async throws {
228228
let (connection, channel) = try await self.makeTestConnectionWithAsyncTestingChannel()
229-
try await withThrowingTaskGroup(of: Void.self) { taskGroup async throws -> () in
229+
try await withThrowingTaskGroup(of: Void.self) { [logger] taskGroup async throws -> () in
230230
for _ in 1...2 {
231231
taskGroup.addTask {
232-
let rows = try await connection.query("SELECT 1;", logger: self.logger)
232+
let rows = try await connection.query("SELECT 1;", logger: logger)
233233
var iterator = rows.decode(Int.self).makeAsyncIterator()
234234
let first = try await iterator.next()
235235
XCTAssertEqual(first, 1)
@@ -286,10 +286,10 @@ class PostgresConnectionTests: XCTestCase {
286286
func testCloseClosesImmediatly() async throws {
287287
let (connection, channel) = try await self.makeTestConnectionWithAsyncTestingChannel()
288288

289-
try await withThrowingTaskGroup(of: Void.self) { taskGroup async throws -> () in
289+
try await withThrowingTaskGroup(of: Void.self) { [logger] taskGroup async throws -> () in
290290
for _ in 1...2 {
291291
taskGroup.addTask {
292-
try await connection.query("SELECT 1;", logger: self.logger)
292+
try await connection.query("SELECT 1;", logger: logger)
293293
}
294294
}
295295

@@ -319,8 +319,9 @@ class PostgresConnectionTests: XCTestCase {
319319

320320
func testIfServerJustClosesTheErrorReflectsThat() async throws {
321321
let (connection, channel) = try await self.makeTestConnectionWithAsyncTestingChannel()
322+
let logger = self.logger
322323

323-
async let response = try await connection.query("SELECT 1;", logger: self.logger)
324+
async let response = try await connection.query("SELECT 1;", logger: logger)
324325

325326
let listenMessage = try await channel.waitForUnpreparedRequest()
326327
XCTAssertEqual(listenMessage.parse.query, "SELECT 1;")
@@ -423,6 +424,7 @@ class PostgresConnectionTests: XCTestCase {
423424
case pleaseDontCrash
424425
}
425426
channel.pipeline.fireUserInboundEventTriggered(MyEvent.pleaseDontCrash)
427+
try await connection.close()
426428
}
427429

428430
func testSerialExecutionOfSamePreparedStatement() async throws {
@@ -651,7 +653,8 @@ class PostgresConnectionTests: XCTestCase {
651653
database: "database"
652654
)
653655

654-
async let connectionPromise = PostgresConnection.connect(on: eventLoop, configuration: configuration, id: 1, logger: self.logger)
656+
let logger = self.logger
657+
async let connectionPromise = PostgresConnection.connect(on: eventLoop, configuration: configuration, id: 1, logger: logger)
655658
let message = try await channel.waitForOutboundWrite(as: PostgresFrontendMessage.self)
656659
XCTAssertEqual(message, .startup(.versionThree(parameters: .init(user: "username", database: "database", options: [], replication: .false))))
657660
try await channel.writeInbound(PostgresBackendMessage.authentication(.ok))

0 commit comments

Comments
 (0)