Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sendable conformance #110

Closed
wants to merge 11 commits into from
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-metrics.git", "1.0.0"..<"3.0.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.33.0"),
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "1.0.0-alpha.9"),
.package(url: "https://github.com/hummingbird-project/hummingbird-core.git", .upToNextMinor(from: "0.13.5")),
.package(url: "https://github.com/hummingbird-project/hummingbird-core.git", .branch("sendable")),
],
targets: [
.target(name: "Hummingbird", dependencies: [
Expand Down
4 changes: 2 additions & 2 deletions Sources/Hummingbird/AsyncAwaitSupport/AsyncResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import NIO
/// Responder that calls supplied closure
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
public struct HBAsyncCallbackResponder: HBResponder {
let callback: (HBRequest) async throws -> HBResponse
let callback: @Sendable (HBRequest) async throws -> HBResponse

public init(callback: @escaping (HBRequest) async throws -> HBResponse) {
public init(callback: @escaping @Sendable (HBRequest) async throws -> HBResponse) {
self.callback = callback
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@

#if compiler(>=5.5) && canImport(_Concurrency)

#if compiler(>=5.6)
@preconcurrency import Logging
@preconcurrency import NIOCore
#else
import Logging
import NIO
import NIOCore
#endif

@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
extension HBConnectionPool {
/// Request a connection, run a process and then release the connection
/// - Parameters:
/// -logger: Logger used for logging
/// - process: Closure to run while we have the connection
public func lease<NewValue>(logger: Logger, process: @escaping (Source.Connection) async throws -> NewValue) async throws -> NewValue {
public func lease<NewValue>(logger: Logger, process: @Sendable @escaping (Source.Connection) async throws -> NewValue) async throws -> NewValue {
return try await self.lease(logger: logger) { connection in
let promise = self.eventLoop.makePromise(of: NewValue.self)
promise.completeWithTask {
Expand Down Expand Up @@ -58,7 +63,7 @@ extension HBConnectionPoolGroup {
public func lease<NewValue>(
on eventLoop: EventLoop,
logger: Logger,
process: @escaping (Source.Connection) async throws -> NewValue
process: @Sendable @escaping (Source.Connection) async throws -> NewValue
) async throws -> NewValue {
let pool = self.getConnectionPool(on: eventLoop)
return try await pool.lease(logger: logger, process: process)
Expand Down Expand Up @@ -97,7 +102,7 @@ public extension HBAsyncConnection {
}

@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
public protocol HBAsyncConnectionSource: HBConnectionSource where Connection: HBAsyncConnection {
public protocol HBAsyncConnectionSource: HBConnectionSource, HBSendable where Connection: HBAsyncConnection {
func makeConnection(on eventLoop: EventLoop, logger: Logger) async throws -> Connection
}

Expand Down
18 changes: 9 additions & 9 deletions Sources/Hummingbird/AsyncAwaitSupport/Router+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension HBRouterMethods {
@discardableResult public func get<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest) async throws -> Output
use handler: @escaping AsyncHandler<Output>
) -> Self {
return on(path, method: .GET, options: options, use: handler)
}
Expand All @@ -31,7 +31,7 @@ extension HBRouterMethods {
@discardableResult public func put<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest) async throws -> Output
use handler: @escaping AsyncHandler<Output>
) -> Self {
return on(path, method: .PUT, options: options, use: handler)
}
Expand All @@ -40,7 +40,7 @@ extension HBRouterMethods {
@discardableResult public func delete<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest) async throws -> Output
use handler: @escaping AsyncHandler<Output>
) -> Self {
return on(path, method: .DELETE, options: options, use: handler)
}
Expand All @@ -49,7 +49,7 @@ extension HBRouterMethods {
@discardableResult public func head<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest) async throws -> Output
use handler: @escaping AsyncHandler<Output>
) -> Self {
return on(path, method: .HEAD, options: options, use: handler)
}
Expand All @@ -58,7 +58,7 @@ extension HBRouterMethods {
@discardableResult public func post<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest) async throws -> Output
use handler: @escaping AsyncHandler<Output>
) -> Self {
return on(path, method: .POST, options: options, use: handler)
}
Expand All @@ -67,14 +67,14 @@ extension HBRouterMethods {
@discardableResult public func patch<Output: HBResponseGenerator>(
_ path: String = "",
options: HBRouterMethodOptions = [],
use handler: @escaping (HBRequest) async throws -> Output
use handler: @escaping AsyncHandler<Output>
) -> Self {
return on(path, method: .PATCH, options: options, use: handler)
}

func constructResponder<Output: HBResponseGenerator>(
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest) async throws -> Output
use closure: @escaping AsyncHandler<Output>
) -> HBResponder {
return HBAsyncCallbackResponder { request in
var request = request
Expand All @@ -99,7 +99,7 @@ extension HBRouter {
_ path: String,
method: HTTPMethod,
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest) async throws -> Output
use closure: @escaping AsyncHandler<Output>
) -> Self {
let responder = constructResponder(options: options, use: closure)
add(path, method: method, responder: responder)
Expand All @@ -114,7 +114,7 @@ extension HBRouterGroup {
_ path: String = "",
method: HTTPMethod,
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest) async throws -> Output
use closure: @escaping AsyncHandler<Output>
) -> Self {
let responder = constructResponder(options: options, use: closure)
let path = self.combinePaths(self.path, path)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/ConnectionPool/ConnectionPool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public enum HBConnectionPoolError: Error {
}

/// Protocol describing a single connection
public protocol HBConnection: AnyObject {
public protocol HBConnection: AnyObject, HBSendable {
/// Close connection.
///
/// This should not be called directly. Instead connection should be closed via `HBConnectionPool.release`
Expand Down
8 changes: 8 additions & 0 deletions Sources/Hummingbird/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@
//
//===----------------------------------------------------------------------===//

#if compiler(>=5.6)
#if os(Linux)
@preconcurrency import Glibc
#else
@preconcurrency import Darwin.C
#endif
#else
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
#endif

/// Access environment variables
public struct HBEnvironment: Decodable, ExpressibleByDictionaryLiteral {
Expand Down
53 changes: 53 additions & 0 deletions Sources/Hummingbird/Extensions/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,56 @@ public struct HBExtensions<ParentObject> {
public protocol HBExtensible {
var extensions: HBExtensions<Self> { get set }
}

/// Version of `HBExtensions` that requires all extensions are sendable
public struct HBSendableExtensions<ParentObject> {
/// Initialize extensions
public init() {
self.items = [:]
}

/// Get optional extension from a `KeyPath`
public func get<Type: HBSendable>(_ key: KeyPath<ParentObject, Type>) -> Type? {
self.items[key]?.value as? Type
}

/// Get extension from a `KeyPath`
public func get<Type: HBSendable>(_ key: KeyPath<ParentObject, Type>, error: StaticString? = nil) -> Type {
guard let value = items[key]?.value as? Type else {
preconditionFailure(error?.description ?? "Cannot get extension of type \(Type.self) without having set it")
}
return value
}

/// Return if extension has been set
public func exists<Type: HBSendable>(_ key: KeyPath<ParentObject, Type>) -> Bool {
self.items[key]?.value != nil
}

/// Set extension for a `KeyPath`
/// - Parameters:
/// - key: KeyPath
/// - value: value to store in extension
/// - shutdownCallback: closure to call when extensions are shutsdown
public mutating func set<Type: HBSendable>(_ key: KeyPath<ParentObject, Type>, value: Type) {
self.items[key] = .init(
value: value
)
}

struct Item {
let value: Any
}

var items: [PartialKeyPath<ParentObject>: Item]
}

/// Protocol for extensible classes
public protocol HBSendableExtensible {
var extensions: HBSendableExtensions<Self> { get set }
}

#if compiler(>=5.6)
/// Conform to @unchecked Sendable as the PartialKeyPath in the item dictionary is not Sendable
extension HBSendableExtensions: @unchecked HBSendable {}
#endif
25 changes: 18 additions & 7 deletions Sources/Hummingbird/HTTP/MediaType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@
//===----------------------------------------------------------------------===//

/// Define media type of file
public struct HBMediaType: CustomStringConvertible {
public struct HBMediaType: CustomStringConvertible, HBSendable {
/// general category
public let type: Category
/// exact kind of data specified
public let subType: String
/// optional parameter
public let parameter: (name: String, value: String)?
public let parameter: Parameter?

/// Initialize `HBMediaType`
/// - Parameters:
/// - type: category
/// - subType: specific kind of data
/// - parameter: additional parameter
public init(type: Category, subType: String = "*", parameter: (String, String)? = nil) {
public init(type: Category, subType: String = "*", parameter: Parameter? = nil) {
self.type = type
self.subType = subType
self.parameter = parameter
Expand All @@ -46,7 +46,7 @@ public struct HBMediaType: CustomStringConvertible {

var category: Category?
var subCategory: String?
var parameter: (String, String)?
var parameter: Parameter?

while state != .finished {
switch state {
Expand Down Expand Up @@ -90,7 +90,7 @@ public struct HBMediaType: CustomStringConvertible {
} else {
value = parser.readUntilTheEnd().string
}
parameter = (key, value)
parameter = .init(name: key, value: value)
state = .finished

case .finished:
Expand All @@ -110,7 +110,7 @@ public struct HBMediaType: CustomStringConvertible {

/// Return media type with new parameter
public func withParameter(name: String, value: String) -> HBMediaType {
return .init(type: self.type, subType: self.subType, parameter: (name, value))
return .init(type: self.type, subType: self.subType, parameter: .init(name: name, value: value))
}

/// Output
Expand Down Expand Up @@ -141,7 +141,7 @@ public struct HBMediaType: CustomStringConvertible {
}

/// Media type categories
public enum Category: String, Equatable {
public enum Category: String, Equatable, HBSendable {
case application
case audio
case example
Expand All @@ -164,6 +164,17 @@ public struct HBMediaType: CustomStringConvertible {
}
}

/// Media type parameter
public struct Parameter: HBSendable {
public let name: String
public let value: String

public init(name: String, value: String) {
self.name = name
self.value = value
}
}

static let tSpecial = Set<Unicode.Scalar>(["(", ")", "<", ">", "@", ",", ";", ":", "\\", "\"", "/", "[", "]", "?", ".", "="])
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/HTTP/URL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//

public struct HBURL: CustomStringConvertible, ExpressibleByStringLiteral {
public struct HBURL: CustomStringConvertible, ExpressibleByStringLiteral, HBSendable {
public struct Scheme: RawRepresentable, Equatable {
public let rawValue: String

Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Middleware/CORSMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import NIOCore
/// "access-control-allow-origin" header
public struct HBCORSMiddleware: HBMiddleware {
/// Defines what origins are allowed
public enum AllowOrigin {
public enum AllowOrigin: HBSendable {
case none
case all
case originBased
Expand Down
4 changes: 4 additions & 0 deletions Sources/Hummingbird/Middleware/LogRequestMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
//
//===----------------------------------------------------------------------===//

#if compiler(>=5.6)
@preconcurrency import Logging
#else
import Logging
#endif

/// Middleware outputting to log for every call to server
public struct HBLogRequestsMiddleware: HBMiddleware {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Middleware/Middleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import NIOCore
/// }
/// }
/// ```
public protocol HBMiddleware {
public protocol HBMiddleware: HBSendable {
func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture<HBResponse>
}

Expand Down
10 changes: 8 additions & 2 deletions Sources/Hummingbird/Router/EndpointResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ final class HBEndpointResponder: HBResponder {
self.methods[method.rawValue] = responder
}

var methods: [String: HBResponder]
var path: String
private var methods: [String: HBResponder]
private(set) var path: String
}

#if compiler(>=5.6)
/// Construction of HBEndpointResponder occurs in non-concurrent code. After that they are
/// read-only
extension HBEndpointResponder: @unchecked HBSendable {}
#endif
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Router/Parameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//

/// Store for parameters key, value pairs extracted from URI
public struct HBParameters {
public struct HBParameters: HBSendable {
internal var parameters: [Substring: Substring]

init() {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Router/RouteHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
/// }
/// }
/// ```
public protocol HBRouteHandler {
public protocol HBRouteHandler: HBSendable {
associatedtype _Output
init(from: HBRequest) throws
func handle(request: HBRequest) throws -> _Output
Expand Down
4 changes: 2 additions & 2 deletions Sources/Hummingbird/Router/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ extension HBRouter {
_ path: String,
method: HTTPMethod,
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest) throws -> Output
use closure: @escaping Handler<Output>
) -> Self {
let responder = constructResponder(options: options, use: closure)
add(path, method: method, responder: responder)
Expand All @@ -71,7 +71,7 @@ extension HBRouter {
_ path: String,
method: HTTPMethod,
options: HBRouterMethodOptions = [],
use closure: @escaping (HBRequest) -> EventLoopFuture<Output>
use closure: @escaping FutureHandler<Output>
) -> Self {
let responder = constructResponder(options: options, use: closure)
add(path, method: method, responder: responder)
Expand Down
Loading