Skip to content

Commit

Permalink
Sendable Take 2 (#136)
Browse files Browse the repository at this point in the history
* Add dev containers to gitignore

* Revert "Revert "Add Sendable conformances to WebsocketKit (#131)" (#135)"

This reverts commit 0b43ce5.

* Add @preconcurrency annotations to work around unsafe users

* More preconcurrency annotations

* Add API breakage allowlist

* Add missing preconcurrency annotations

* Last missing annotations

* Update allowlist

---------

Co-authored-by: Gwynne Raskind <[email protected]>
  • Loading branch information
0xTim and gwynne authored May 25, 2023
1 parent 0b43ce5 commit a8f3ec8
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 175 deletions.
14 changes: 14 additions & 0 deletions .api-breakage/allowlist-branch-sendable-take-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
API breakage: func WebSocket.onText(_:) is now with @preconcurrency
API breakage: func WebSocket.onBinary(_:) is now with @preconcurrency
API breakage: func WebSocket.onPong(_:) is now with @preconcurrency
API breakage: func WebSocket.onPing(_:) is now with @preconcurrency
API breakage: func WebSocket.connect(to:headers:configuration:on:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocket.connect(scheme:host:port:path:query:headers:configuration:on:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocket.connect(scheme:host:port:path:query:headers:proxy:proxyPort:proxyHeaders:proxyConnectDeadline:configuration:on:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocket.connect(to:headers:proxy:proxyPort:proxyHeaders:proxyConnectDeadline:configuration:on:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocket.client(on:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocket.client(on:config:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocket.server(on:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocket.server(on:config:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocketClient.connect(scheme:host:port:path:query:headers:onUpgrade:) is now with @preconcurrency
API breakage: func WebSocketClient.connect(scheme:host:port:path:query:headers:proxy:proxyPort:proxyHeaders:proxyConnectDeadline:onUpgrade:) is now with @preconcurrency
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ concurrency:
on:
pull_request: { types: [opened, reopened, synchronize, ready_for_review] }
push: { branches: [ main ] }

jobs:

vapor-integration:
if: ${{ !(github.event.pull_request.draft || false) }}
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
DerivedData
.swiftpm
Package.resolved
.devcontainer/
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio.git", from: "2.53.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.16.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.24.0"),
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.16.0"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.1.0"),
],
targets: [
.target(name: "WebSocketKit", dependencies: [
Expand Down
52 changes: 30 additions & 22 deletions Sources/WebSocketKit/Concurrency/WebSocket+Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,44 +40,52 @@ extension WebSocket {
try await close(code: code).get()
}

public func onText(_ callback: @escaping (WebSocket, String) async -> ()) {
onText { socket, text in
Task {
await callback(socket, text)
@preconcurrency public func onText(_ callback: @Sendable @escaping (WebSocket, String) async -> ()) {
self.eventLoop.execute {
self.onText { socket, text in
Task {
await callback(socket, text)
}
}
}
}

public func onBinary(_ callback: @escaping (WebSocket, ByteBuffer) async -> ()) {
onBinary { socket, binary in
Task {
await callback(socket, binary)
@preconcurrency public func onBinary(_ callback: @Sendable @escaping (WebSocket, ByteBuffer) async -> ()) {
self.eventLoop.execute {
self.onBinary { socket, binary in
Task {
await callback(socket, binary)
}
}
}
}

public func onPong(_ callback: @escaping (WebSocket) async -> ()) {
onPong { socket in
Task {
await callback(socket)
@preconcurrency public func onPong(_ callback: @Sendable @escaping (WebSocket) async -> ()) {
self.eventLoop.execute {
self.onPong { socket in
Task {
await callback(socket)
}
}
}
}

public func onPing(_ callback: @escaping (WebSocket) async -> ()) {
onPing { socket in
Task {
await callback(socket)
@preconcurrency public func onPing(_ callback: @Sendable @escaping (WebSocket) async -> ()) {
self.eventLoop.execute {
self.onPing { socket in
Task {
await callback(socket)
}
}
}
}

public static func connect(
@preconcurrency public static func connect(
to url: String,
headers: HTTPHeaders = [:],
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) async -> ()
onUpgrade: @Sendable @escaping (WebSocket) async -> ()
) async throws {
return try await self.connect(
to: url,
Expand All @@ -92,12 +100,12 @@ extension WebSocket {
).get()
}

public static func connect(
@preconcurrency public static func connect(
to url: URL,
headers: HTTPHeaders = [:],
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) async -> ()
onUpgrade: @Sendable @escaping (WebSocket) async -> ()
) async throws {
return try await self.connect(
to: url,
Expand All @@ -112,7 +120,7 @@ extension WebSocket {
).get()
}

public static func connect(
@preconcurrency public static func connect(
scheme: String = "ws",
host: String,
port: Int = 80,
Expand All @@ -121,7 +129,7 @@ extension WebSocket {
headers: HTTPHeaders = [:],
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) async -> ()
onUpgrade: @Sendable @escaping (WebSocket) async -> ()
) async throws {
return try await self.connect(
scheme: scheme,
Expand Down
4 changes: 0 additions & 4 deletions Sources/WebSocketKit/Exports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
@_documentation(visibility: internal) @_exported import protocol NIOCore.EventLoopGroup
@_documentation(visibility: internal) @_exported import struct NIOCore.EventLoopPromise
@_documentation(visibility: internal) @_exported import class NIOCore.EventLoopFuture

@_documentation(visibility: internal) @_exported import struct NIOHTTP1.HTTPHeaders

@_documentation(visibility: internal) @_exported import struct Foundation.URL

#else
Expand All @@ -19,9 +17,7 @@
@_exported import protocol NIOCore.EventLoopGroup
@_exported import struct NIOCore.EventLoopPromise
@_exported import class NIOCore.EventLoopFuture

@_exported import struct NIOHTTP1.HTTPHeaders

@_exported import struct Foundation.URL

#endif
15 changes: 10 additions & 5 deletions Sources/WebSocketKit/WebSocket+Connect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ extension WebSocket {
/// - eventLoopGroup: Event loop group to be used by the WebSocket client.
/// - onUpgrade: An escaping closure to be executed after the upgrade is completed by `NIOWebSocketClientUpgrader`.
/// - Returns: An future which completes when the connection to the WebSocket server is established.
@preconcurrency
public static func connect(
to url: String,
headers: HTTPHeaders = [:],
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) -> ()
onUpgrade: @Sendable @escaping (WebSocket) -> ()
) -> EventLoopFuture<Void> {
guard let url = URL(string: url) else {
return eventLoopGroup.any().makeFailedFuture(WebSocketClient.Error.invalidURL)
Expand All @@ -40,12 +41,13 @@ extension WebSocket {
/// - eventLoopGroup: Event loop group to be used by the WebSocket client.
/// - onUpgrade: An escaping closure to be executed after the upgrade is completed by `NIOWebSocketClientUpgrader`.
/// - Returns: An future which completes when the connection to the WebSocket server is established.
@preconcurrency
public static func connect(
to url: URL,
headers: HTTPHeaders = [:],
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) -> ()
onUpgrade: @Sendable @escaping (WebSocket) -> ()
) -> EventLoopFuture<Void> {
let scheme = url.scheme ?? "ws"
return self.connect(
Expand Down Expand Up @@ -74,6 +76,7 @@ extension WebSocket {
/// - eventLoopGroup: Event loop group to be used by the WebSocket client.
/// - onUpgrade: An escaping closure to be executed after the upgrade is completed by `NIOWebSocketClientUpgrader`.
/// - Returns: An future which completes when the connection to the WebSocket server is established.
@preconcurrency
public static func connect(
scheme: String = "ws",
host: String,
Expand All @@ -83,7 +86,7 @@ extension WebSocket {
headers: HTTPHeaders = [:],
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) -> ()
onUpgrade: @Sendable @escaping (WebSocket) -> ()
) -> EventLoopFuture<Void> {
return WebSocketClient(
eventLoopGroupProvider: .shared(eventLoopGroup),
Expand Down Expand Up @@ -116,6 +119,7 @@ extension WebSocket {
/// - eventLoopGroup: Event loop group to be used by the WebSocket client.
/// - onUpgrade: An escaping closure to be executed after the upgrade is completed by `NIOWebSocketClientUpgrader`.
/// - Returns: An future which completes when the connection to the origin server is established.
@preconcurrency
public static func connect(
scheme: String = "ws",
host: String,
Expand All @@ -129,7 +133,7 @@ extension WebSocket {
proxyConnectDeadline: NIODeadline = NIODeadline.distantFuture,
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) -> ()
onUpgrade: @Sendable @escaping (WebSocket) -> ()
) -> EventLoopFuture<Void> {
return WebSocketClient(
eventLoopGroupProvider: .shared(eventLoopGroup),
Expand Down Expand Up @@ -162,6 +166,7 @@ extension WebSocket {
/// - eventLoopGroup: Event loop group to be used by the WebSocket client.
/// - onUpgrade: An escaping closure to be executed after the upgrade is completed by `NIOWebSocketClientUpgrader`.
/// - Returns: An future which completes when the connection to the origin server is established.
@preconcurrency
public static func connect(
to url: String,
headers: HTTPHeaders = [:],
Expand All @@ -171,7 +176,7 @@ extension WebSocket {
proxyConnectDeadline: NIODeadline = NIODeadline.distantFuture,
configuration: WebSocketClient.Configuration = .init(),
on eventLoopGroup: EventLoopGroup,
onUpgrade: @escaping (WebSocket) -> ()
onUpgrade: @Sendable @escaping (WebSocket) -> ()
) -> EventLoopFuture<Void> {
guard let url = URL(string: url) else {
return eventLoopGroup.any().makeFailedFuture(WebSocketClient.Error.invalidURL)
Expand Down
Loading

0 comments on commit a8f3ec8

Please sign in to comment.