Skip to content

Commit

Permalink
Add Sendable Conformances to undelying types (#3054)
Browse files Browse the repository at this point in the history
* Fix exports

* Bump Swift version

* Turn on Sendable checking

* Update routes file

* Make StackTrace Sendable

* Middleware and passwords should be sendable

* More Sendable warnings

* More easy wins

* Routes should be sendable

* Don't need to make everything Sendable yet

* Validations are now Sendable

* Websocket APIs should be Sendable

* Fix up more warnings

* Fix warnings internally in RFC1123

* Fix a few more warnings

* Enable concurrency checking in CI

* Migrate to new CI

* Manually overwrite security events

* Update test.yml

* Fix warning in XCTVapor

* PR review changes

* More PR reviews

* Thread safe implementation for caching the date

* Add new manifest to make testing strict concurrency easy

* Try and fix manifests

* Update CI as we don't need a flag

* Functions should not be Sendable

* Remove redundant CI job

---------

Co-authored-by: Gwynne Raskind <gwynne@vapor.codes>
  • Loading branch information
0xTim and gwynne committed Aug 18, 2023
1 parent fe973db commit 1bb4a2e
Show file tree
Hide file tree
Showing 46 changed files with 466 additions and 209 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Expand Up @@ -43,7 +43,7 @@ jobs:
run: swift test --package-path ./provider

unit-tests:
uses: vapor/ci/.github/workflows/run-unit-tests.yml@reusable-workflows
uses: vapor/ci/.github/workflows/run-unit-tests.yml@main
with:
with_coverage: true
with_tsan: false
Expand Down
15 changes: 4 additions & 11 deletions Package.swift
@@ -1,5 +1,6 @@
// swift-tools-version:5.6
// swift-tools-version:5.7
import PackageDescription
import Foundation

let package = Package(
name: "vapor",
Expand Down Expand Up @@ -54,7 +55,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),

// WebSocket client library built on SwiftNIO
.package(url: "https://github.com/vapor/websocket-kit.git", from: "2.0.0"),
.package(url: "https://github.com/vapor/websocket-kit.git", from: "2.13.0"),

// MultipartKit, Multipart encoding and decoding
.package(url: "https://github.com/vapor/multipart-kit.git", from: "4.2.1"),
Expand Down Expand Up @@ -97,15 +98,7 @@ let package = Package(
dependencies: [
.target(name: "Vapor"),
],
resources: [.copy("Resources")],
swiftSettings: [
// Enable better optimizations when building in Release configuration. Despite the use of
// the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
// builds. See <https://github.com/swift-server/guides#building-for-production> for details.
.unsafeFlags([
"-cross-module-optimization"
], .when(configuration: .release)),
]
resources: [.copy("Resources")]
),

// Testing
Expand Down
144 changes: 144 additions & 0 deletions Package@swift-5.9.swift
@@ -0,0 +1,144 @@
// swift-tools-version:5.9
import PackageDescription
import Foundation

let package = Package(
name: "vapor",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
],
products: [
.library(name: "Vapor", targets: ["Vapor"]),
.library(name: "XCTVapor", targets: ["XCTVapor"]),
],
dependencies: [
// HTTP client library built on SwiftNIO
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.18.0"),

// Sugary extensions for the SwiftNIO library
.package(url: "https://github.com/vapor/async-kit.git", from: "1.15.0"),

// 💻 APIs for creating interactive CLI tools.
.package(url: "https://github.com/vapor/console-kit.git", from: "4.0.0"),

// 🔑 Hashing (SHA2, HMAC), encryption (AES), public-key (RSA), and random data generation.
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"),

// 🚍 High-performance trie-node router.
.package(url: "https://github.com/vapor/routing-kit.git", from: "4.5.0"),

// 💥 Backtraces for Swift on Linux
.package(url: "https://github.com/swift-server/swift-backtrace.git", from: "1.1.1"),

// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.56.0"),

// Bindings to OpenSSL-compatible libraries for TLS support in SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.8.0"),

// HTTP/2 support for SwiftNIO
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.20.0"),

// Useful code around SwiftNIO.
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.19.0"),

// Swift logging API
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),

// Swift metrics API
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.0.0"),

// Swift collection algorithms
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),

// WebSocket client library built on SwiftNIO
.package(url: "https://github.com/vapor/websocket-kit.git", from: "2.13.0"),

// MultipartKit, Multipart encoding and decoding
.package(url: "https://github.com/vapor/multipart-kit.git", from: "4.2.1"),
],
targets: [
// C helpers
.target(name: "CVaporBcrypt"),
.target(name: "CVaporURLParser"),

// Vapor
.target(
name: "Vapor",
dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "AsyncKit", package: "async-kit"),
.product(name: "Backtrace", package: "swift-backtrace"),
.target(name: "CVaporBcrypt"),
.target(name: "CVaporURLParser"),
.product(name: "ConsoleKit", package: "console-kit"),
.product(name: "Logging", package: "swift-log"),
.product(name: "Metrics", package: "swift-metrics"),
.product(name: "NIO", package: "swift-nio"),
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOExtras", package: "swift-nio-extras"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
.product(name: "NIOHTTPCompression", package: "swift-nio-extras"),
.product(name: "NIOHTTP1", package: "swift-nio"),
.product(name: "NIOHTTP2", package: "swift-nio-http2"),
.product(name: "NIOSSL", package: "swift-nio-ssl"),
.product(name: "NIOWebSocket", package: "swift-nio"),
.product(name: "Crypto", package: "swift-crypto"),
.product(name: "Algorithms", package: "swift-algorithms"),
.product(name: "RoutingKit", package: "routing-kit"),
.product(name: "WebSocketKit", package: "websocket-kit"),
.product(name: "MultipartKit", package: "multipart-kit"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),

// Development
.executableTarget(
name: "Development",
dependencies: [
.target(name: "Vapor"),
],
resources: [.copy("Resources")],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),

// Testing
.target(
name: "XCTVapor",
dependencies: [
.target(name: "Vapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.testTarget(
name: "VaporTests",
dependencies: [
.product(name: "NIOTestUtils", package: "swift-nio"),
.target(name: "XCTVapor"),
],
resources: [
.copy("Utilities/foo.txt"),
.copy("Utilities/index.html"),
.copy("Utilities/SubUtilities/"),
.copy("Utilities/foo bar.html"),
.copy("Utilities/test.env"),
.copy("Utilities/my-secret-env-content"),
.copy("Utilities/expired.crt"),
.copy("Utilities/expired.key"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.testTarget(
name: "AsyncTests",
dependencies: [
.product(name: "NIOTestUtils", package: "swift-nio"),
.target(name: "XCTVapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
]
)
14 changes: 8 additions & 6 deletions Sources/Development/routes.swift
Expand Up @@ -2,6 +2,7 @@ import class Foundation.Bundle
import Vapor
import NIOCore
import NIOHTTP1
import NIOConcurrencyHelpers

struct Creds: Content {
var email: String
Expand All @@ -18,21 +19,21 @@ public func routes(_ app: Application) throws {
app.on(.POST, "slow-stream", body: .stream) { req -> EventLoopFuture<String> in
let done = req.eventLoop.makePromise(of: String.self)

var total = 0
let totalBox = NIOLoopBoundBox(0, eventLoop: req.eventLoop)
req.body.drain { result in
let promise = req.eventLoop.makePromise(of: Void.self)

switch result {
case .buffer(let buffer):
req.eventLoop.scheduleTask(in: .milliseconds(1000)) {
total += buffer.readableBytes
totalBox.value += buffer.readableBytes
promise.succeed(())
}
case .error(let error):
done.fail(error)
case .end:
promise.succeed(())
done.succeed(total.description)
done.succeed(totalBox.value.description)
}

// manually return pre-completed future
Expand Down Expand Up @@ -202,17 +203,18 @@ public func routes(_ app: Application) throws {
eventLoop: req.eventLoop
).flatMap { fileHandle in
let promise = req.eventLoop.makePromise(of: HTTPStatus.self)
let fileHandleBox = NIOLoopBound(fileHandle, eventLoop: req.eventLoop)
req.body.drain { part in
switch part {
case .buffer(let buffer):
return req.application.fileio.write(
fileHandle: fileHandle,
fileHandle: fileHandleBox.value,
buffer: buffer,
eventLoop: req.eventLoop
)
case .error(let drainError):
do {
try fileHandle.close()
try fileHandleBox.value.close()
promise.fail(BodyStreamWritingToDiskError.streamFailure(drainError))
} catch {
promise.fail(BodyStreamWritingToDiskError.multipleFailures([
Expand All @@ -223,7 +225,7 @@ public func routes(_ app: Application) throws {
return req.eventLoop.makeSucceededFuture(())
case .end:
do {
try fileHandle.close()
try fileHandleBox.value.close()
promise.succeed(.ok)
} catch {
promise.fail(BodyStreamWritingToDiskError.fileHandleClosedFailure(error))
Expand Down
6 changes: 3 additions & 3 deletions Sources/Vapor/Authentication/RedirectMiddleware.swift
Expand Up @@ -13,7 +13,7 @@ extension Authenticatable {
///
/// - parameters:
/// - makePath: The closure that returns the redirect path based on the given `Request` object
public static func redirectMiddleware(makePath: @escaping (Request) -> String) -> Middleware {
@preconcurrency public static func redirectMiddleware(makePath: @Sendable @escaping (Request) -> String) -> Middleware {
RedirectMiddleware<Self>(Self.self, makePath: makePath)
}
}
Expand All @@ -22,9 +22,9 @@ extension Authenticatable {
private final class RedirectMiddleware<A>: Middleware
where A: Authenticatable
{
let makePath: (Request) -> String
let makePath: @Sendable (Request) -> String

init(_ authenticatableType: A.Type = A.self, makePath: @escaping (Request) -> String) {
@preconcurrency init(_ authenticatableType: A.Type = A.self, makePath: @Sendable @escaping (Request) -> String) {
self.makePath = makePath
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Concurrency/AsyncBasicResponder.swift
Expand Up @@ -3,7 +3,7 @@ import NIOCore
/// A basic, async closure-based `Responder`.
public struct AsyncBasicResponder: AsyncResponder {
/// The stored responder closure.
private let closure: (Request) async throws -> Response
private let closure: @Sendable (Request) async throws -> Response

/// Create a new `BasicResponder`.
///
Expand All @@ -15,7 +15,7 @@ public struct AsyncBasicResponder: AsyncResponder {
/// - parameters:
/// - closure: Responder closure.
public init(
closure: @escaping (Request) async throws -> Response
closure: @Sendable @escaping (Request) async throws -> Response
) {
self.closure = closure
}
Expand Down
Expand Up @@ -3,7 +3,7 @@ import Foundation

extension AsyncPasswordHasher {
public func hash<Password>(_ password: Password) async throws -> [UInt8]
where Password: DataProtocol
where Password: DataProtocol & Sendable
{
try await self.hash(password).get()
}
Expand All @@ -12,7 +12,7 @@ extension AsyncPasswordHasher {
_ password: Password,
created digest: Digest
) async throws -> Bool
where Password: DataProtocol, Digest: DataProtocol
where Password: DataProtocol & Sendable, Digest: DataProtocol & Sendable
{
try await self.verify(password, created: digest).get()
}
Expand Down

0 comments on commit 1bb4a2e

Please sign in to comment.