Skip to content

Commit

Permalink
Make HBRequest/HBResponse Sendable (hummingbird-project#153)
Browse files Browse the repository at this point in the history
* Made HBRequest Sendable

* Add UnsafeTransfer and use in HBRequest

* Make HBResponse Sendable

* Added _HBSendableProtocol

* format

* Make PerformanceTest an executableTarget

* Update FlatDictionary.swift

* Fix compile error

* Make MediaType Sendable
  • Loading branch information
adam-fowler committed Nov 23, 2022
1 parent 9c9ad92 commit 4bf0119
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 17 deletions.
19 changes: 19 additions & 0 deletions Sources/Hummingbird/AsyncAwaitSupport/Sendable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2022 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

#if compiler(>=5.6)
@preconcurrency public protocol _HBSendableProtocol: Sendable {}
#else
public protocol _HBSendableProtocol {}
#endif
66 changes: 66 additions & 0 deletions Sources/Hummingbird/AsyncAwaitSupport/UnsafeTransfer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2022 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2021-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// ``HBUnsafeTransfer`` can be used to make non-`Sendable` values `Sendable`.
/// As the name implies, the usage of this is unsafe because it disables the sendable checking of the compiler.
/// It can be used similar to `@unsafe Sendable` but for values instead of types.
@usableFromInline
struct HBUnsafeTransfer<Wrapped> {
@usableFromInline
var wrappedValue: Wrapped

@inlinable
init(_ wrappedValue: Wrapped) {
self.wrappedValue = wrappedValue
}
}

#if swift(>=5.5) && canImport(_Concurrency)
extension HBUnsafeTransfer: @unchecked Sendable {}
#endif

extension HBUnsafeTransfer: Equatable where Wrapped: Equatable {}
extension HBUnsafeTransfer: Hashable where Wrapped: Hashable {}

/// ``HBUnsafeMutableTransferBox`` can be used to make non-`Sendable` values `Sendable` and mutable.
/// It can be used to capture local mutable values in a `@Sendable` closure and mutate them from within the closure.
/// As the name implies, the usage of this is unsafe because it disables the sendable checking of the compiler and does not add any synchronisation.
@usableFromInline
final class HBUnsafeMutableTransferBox<Wrapped> {
@usableFromInline
var wrappedValue: Wrapped

@inlinable
init(_ wrappedValue: Wrapped) {
self.wrappedValue = wrappedValue
}
}

#if swift(>=5.5) && canImport(_Concurrency)
extension HBUnsafeMutableTransferBox: @unchecked Sendable {}
#endif
54 changes: 54 additions & 0 deletions Sources/Hummingbird/Extensions/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import HummingbirdCore
import Logging

/// Extend objects with additional member variables
Expand Down Expand Up @@ -91,3 +92,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 Sendable {}
#endif
5 changes: 5 additions & 0 deletions Sources/Hummingbird/HTTP/MediaType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,8 @@ extension HBMediaType {
"7z": .application7z,
]
}

#if swift(>=5.6)
extension HBMediaType: Sendable {}
extension HBMediaType.Category: Sendable {}
#endif
4 changes: 4 additions & 0 deletions Sources/Hummingbird/Router/Parameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,7 @@ extension HBParameters: CustomStringConvertible {
String(describing: self.parameters)
}
}

#if swift(>=5.6)
extension HBParameters: Sendable {}
#endif
30 changes: 18 additions & 12 deletions Sources/Hummingbird/Server/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2021-2021 the Hummingbird authors
// Copyright (c) 2021-2022 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand All @@ -20,7 +20,7 @@ import NIOCore
import NIOHTTP1

/// Holds all the values required to process a request
public struct HBRequest: HBExtensible {
public struct HBRequest: HBSendableExtensible {
// MARK: Member variables

/// URI path
Expand All @@ -36,9 +36,9 @@ public struct HBRequest: HBExtensible {
/// Logger to use
public var logger: Logger
/// reference to application
public var application: HBApplication { self._internal.application }
public var application: HBApplication { self._internal.application.wrappedValue }
/// Request extensions
public var extensions: HBExtensions<HBRequest>
public var extensions: HBSendableExtensions<HBRequest>
/// Request context (eventLoop, bytebuffer allocator and remote address)
public var context: HBRequestContext { self._internal.context }
/// EventLoop request is running on
Expand All @@ -61,8 +61,8 @@ public struct HBRequest: HBExtensible {

/// endpoint that services this request.
internal var endpointPath: String? {
get { self._internal.endpointPath }
set { self._internal.endpointPath = newValue }
get { self._internal.endpointPath.wrappedValue }
set { self._internal.endpointPath.wrappedValue = newValue }
}

// MARK: Initialization
Expand Down Expand Up @@ -90,7 +90,7 @@ public struct HBRequest: HBExtensible {
)
self.body = body
self.logger = application.logger.with(metadataKey: "hb_id", value: .stringConvertible(Self.globalRequestID.loadThenWrappingIncrement(by: 1, ordering: .relaxed)))
self.extensions = HBExtensions()
self.extensions = .init()
}

// MARK: Methods
Expand Down Expand Up @@ -145,9 +145,9 @@ public struct HBRequest: HBExtensible {
self.version = version
self.method = method
self.headers = headers
self.application = application
self.application = .init(application)
self.context = context
self.endpointPath = endpointPath
self.endpointPath = .init(endpointPath)
}

/// URI path
Expand All @@ -158,13 +158,14 @@ public struct HBRequest: HBExtensible {
let method: HTTPMethod
/// Request HTTP headers
let headers: HTTPHeaders
/// reference to application
let application: HBApplication
/// reference to application. Currently wrapped in HBUnsafeTransfer to make Sendable.
/// Hope to make it Sendable in the future
let application: HBUnsafeTransfer<HBApplication>
/// request context
let context: HBRequestContext
/// Endpoint path. This is stored a var so it can be edited by the router. In theory this could
/// be accessed on multiple thread/tasks at the same point but it is only ever edited by router
var endpointPath: String?
let endpointPath: HBUnsafeMutableTransferBox<String?>
}

private var _internal: _Internal
Expand All @@ -190,3 +191,8 @@ extension HBRequest: CustomStringConvertible {
"uri: \(self.uri), version: \(self.version), method: \(self.method), headers: \(self.headers), body: \(self.body)"
}
}

#if compiler(>=5.6)
extension HBRequest: Sendable {}
extension HBRequest._Internal: Sendable {}
#endif
4 changes: 2 additions & 2 deletions Sources/Hummingbird/Server/RequestContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2021-2021 the Hummingbird authors
// Copyright (c) 2021-2022 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand All @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//

/// Context that created HBRequest.
public protocol HBRequestContext {
public protocol HBRequestContext: _HBSendableProtocol {
/// EventLoop request is running on
var eventLoop: EventLoop { get }
/// ByteBuffer allocator used by request
Expand Down
10 changes: 7 additions & 3 deletions Sources/Hummingbird/Server/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import HummingbirdCore
import NIOHTTP1

/// Holds all the required to generate a HTTP Response
public struct HBResponse: HBExtensible {
public struct HBResponse: HBSendableExtensible {
/// response status
public var status: HTTPResponseStatus
/// response headers
public var headers: HTTPHeaders
/// response body
public var body: HBResponseBody
/// Response extensions
public var extensions: HBExtensions<HBResponse>
public var extensions: HBSendableExtensions<HBResponse>

/// Create an `HBResponse`
///
Expand All @@ -36,7 +36,7 @@ public struct HBResponse: HBExtensible {
self.status = status
self.headers = headers
self.body = body
self.extensions = HBExtensions()
self.extensions = .init()
}
}

Expand All @@ -45,3 +45,7 @@ extension HBResponse: CustomStringConvertible {
"status: \(self.status), headers: \(self.headers), body: \(self.body)"
}
}

#if compiler(>=5.6)
extension HBResponse: Sendable {}
#endif
5 changes: 5 additions & 0 deletions Sources/Hummingbird/Utils/FlatDictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,8 @@ public struct FlatDictionary<Key: Hashable, Value>: Collection {
private var elements: [Element]
private var hashKeys: [Int]
}

#if compiler(>=5.6)
// FlatDictionary is Sendable when Key and Value are Sendable
extension FlatDictionary: Sendable where Key: Sendable, Value: Sendable {}
#endif

0 comments on commit 4bf0119

Please sign in to comment.