Skip to content

Adopt Sendable #621

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

Merged
merged 1 commit into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ let package = Package(
.library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.38.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.14.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.41.1"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.22.0"),
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.19.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.10.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.13.0"),
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
],
Expand Down
8 changes: 4 additions & 4 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ let package = Package(
.library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.38.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.14.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.41.1"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.22.0"),
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.19.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.10.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.13.0"),
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
],
targets: [
Expand Down
8 changes: 4 additions & 4 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ let package = Package(
.library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.38.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.14.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.41.1"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.22.0"),
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.19.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.10.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.13.0"),
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
],
targets: [
Expand Down
168 changes: 161 additions & 7 deletions Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import NIOHTTP1
///
/// This object is similar to ``HTTPClient/Request``, but used for the Swift Concurrency API.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public struct HTTPClientRequest {
public struct HTTPClientRequest: Sendable {
/// The request URL, including scheme, hostname, and optionally port.
public var url: String

Expand All @@ -47,18 +47,18 @@ extension HTTPClientRequest {
///
/// This object encapsulates the difference between streamed HTTP request bodies and those bodies that
/// are already entirely in memory.
public struct Body {
public struct Body: Sendable {
@usableFromInline
internal enum Mode {
internal enum Mode: Sendable {
/// - parameters:
/// - length: complete body length.
/// If `length` is `.known`, `nextBodyPart` is not allowed to produce more bytes than `length` defines.
/// - makeAsyncIterator: Creates a new async iterator under the hood and returns a function which will call `next()` on it.
/// The returned function then produce the next body buffer asynchronously.
/// We use a closure as abstraction instead of an existential to enable specialization.
/// We use a closure as an abstraction instead of an existential to enable specialization.
case asyncSequence(
length: RequestBodyLength,
makeAsyncIterator: () -> ((ByteBufferAllocator) async throws -> ByteBuffer?)
makeAsyncIterator: @Sendable () -> ((ByteBufferAllocator) async throws -> ByteBuffer?)
)
/// - parameters:
/// - length: complete body length.
Expand All @@ -68,7 +68,7 @@ extension HTTPClientRequest {
case sequence(
length: RequestBodyLength,
canBeConsumedMultipleTimes: Bool,
makeCompleteBody: (ByteBufferAllocator) -> ByteBuffer
makeCompleteBody: @Sendable (ByteBufferAllocator) -> ByteBuffer
)
case byteBuffer(ByteBuffer)
}
Expand All @@ -92,6 +92,22 @@ extension HTTPClientRequest.Body {
self.init(.byteBuffer(byteBuffer))
}

#if swift(>=5.6)
/// Create an ``HTTPClientRequest/Body-swift.struct`` from a `RandomAccessCollection` of bytes.
///
/// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory
/// usage of this construction will be double the size of the original collection. The construction
/// of the `ByteBuffer` will be delayed until it's needed.
///
/// - parameter bytes: The bytes of the request body.
@inlinable
@preconcurrency
public static func bytes<Bytes: RandomAccessCollection & Sendable>(
_ bytes: Bytes
) -> Self where Bytes.Element == UInt8 {
Self._bytes(bytes)
}
#else
/// Create an ``HTTPClientRequest/Body-swift.struct`` from a `RandomAccessCollection` of bytes.
///
/// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory
Expand All @@ -102,6 +118,14 @@ extension HTTPClientRequest.Body {
@inlinable
public static func bytes<Bytes: RandomAccessCollection>(
_ bytes: Bytes
) -> Self where Bytes.Element == UInt8 {
Self._bytes(bytes)
}
#endif

@inlinable
static func _bytes<Bytes: RandomAccessCollection>(
_ bytes: Bytes
) -> Self where Bytes.Element == UInt8 {
self.init(.sequence(
length: .known(bytes.count),
Expand All @@ -116,6 +140,33 @@ extension HTTPClientRequest.Body {
})
}

#if swift(>=5.6)
/// Create an ``HTTPClientRequest/Body-swift.struct`` from a `Sequence` of bytes.
///
/// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory
/// usage of this construction will be double the size of the original collection. The construction
/// of the `ByteBuffer` will be delayed until it's needed.
///
/// Unlike ``bytes(_:)-1uns7``, this construction does not assume that the body can be replayed. As a result,
/// if a redirect is encountered that would need us to replay the request body, the redirect will instead
/// not be followed. Prefer ``bytes(_:)-1uns7`` wherever possible.
///
/// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths
/// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload
/// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`.
///
/// - parameters:
/// - bytes: The bytes of the request body.
/// - length: The length of the request body.
@inlinable
@preconcurrency
public static func bytes<Bytes: Sequence & Sendable>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
Self._bytes(bytes, length: length)
}
#else
/// Create an ``HTTPClientRequest/Body-swift.struct`` from a `Sequence` of bytes.
///
/// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory
Expand All @@ -137,6 +188,15 @@ extension HTTPClientRequest.Body {
public static func bytes<Bytes: Sequence>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
Self._bytes(bytes, length: length)
}
#endif

@inlinable
static func _bytes<Bytes: Sequence>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
self.init(.sequence(
length: length.storage,
Expand All @@ -151,6 +211,29 @@ extension HTTPClientRequest.Body {
})
}

#if swift(>=5.6)
/// Create an ``HTTPClientRequest/Body-swift.struct`` from a `Collection` of bytes.
///
/// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory
/// usage of this construction will be double the size of the original collection. The construction
/// of the `ByteBuffer` will be delayed until it's needed.
///
/// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths
/// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload
/// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`.
///
/// - parameters:
/// - bytes: The bytes of the request body.
/// - length: The length of the request body.
@inlinable
@preconcurrency
public static func bytes<Bytes: Collection & Sendable>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
Self._bytes(bytes, length: length)
}
#else
/// Create an ``HTTPClientRequest/Body-swift.struct`` from a `Collection` of bytes.
///
/// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory
Expand All @@ -168,6 +251,15 @@ extension HTTPClientRequest.Body {
public static func bytes<Bytes: Collection>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
Self._bytes(bytes, length: length)
}
#endif

@inlinable
static func _bytes<Bytes: Collection>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
self.init(.sequence(
length: length.storage,
Expand All @@ -182,6 +274,27 @@ extension HTTPClientRequest.Body {
})
}

#if swift(>=5.6)
/// Create an ``HTTPClientRequest/Body-swift.struct`` from an `AsyncSequence` of `ByteBuffer`s.
///
/// This construction will stream the upload one `ByteBuffer` at a time.
///
/// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths
/// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload
/// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`.
///
/// - parameters:
/// - sequenceOfBytes: The bytes of the request body.
/// - length: The length of the request body.
@inlinable
@preconcurrency
public static func stream<SequenceOfBytes: AsyncSequence & Sendable>(
_ sequenceOfBytes: SequenceOfBytes,
length: Length
) -> Self where SequenceOfBytes.Element == ByteBuffer {
Self._stream(sequenceOfBytes, length: length)
}
#else
/// Create an ``HTTPClientRequest/Body-swift.struct`` from an `AsyncSequence` of `ByteBuffer`s.
///
/// This construction will stream the upload one `ByteBuffer` at a time.
Expand All @@ -197,6 +310,15 @@ extension HTTPClientRequest.Body {
public static func stream<SequenceOfBytes: AsyncSequence>(
_ sequenceOfBytes: SequenceOfBytes,
length: Length
) -> Self where SequenceOfBytes.Element == ByteBuffer {
Self._stream(sequenceOfBytes, length: length)
}
#endif

@inlinable
static func _stream<SequenceOfBytes: AsyncSequence>(
_ sequenceOfBytes: SequenceOfBytes,
length: Length
) -> Self where SequenceOfBytes.Element == ByteBuffer {
let body = self.init(.asyncSequence(length: length.storage) {
var iterator = sequenceOfBytes.makeAsyncIterator()
Expand All @@ -207,6 +329,29 @@ extension HTTPClientRequest.Body {
return body
}

#if swift(>=5.6)
/// Create an ``HTTPClientRequest/Body-swift.struct`` from an `AsyncSequence` of bytes.
///
/// This construction will consume 1kB chunks from the `Bytes` and send them at once. This optimizes for
/// `AsyncSequence`s where larger chunks are buffered up and available without actually suspending, such
/// as those provided by `FileHandle`.
///
/// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths
/// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload
/// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`.
///
/// - parameters:
/// - bytes: The bytes of the request body.
/// - length: The length of the request body.
@inlinable
@preconcurrency
public static func stream<Bytes: AsyncSequence & Sendable>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
Self._stream(bytes, length: length)
}
#else
/// Create an ``HTTPClientRequest/Body-swift.struct`` from an `AsyncSequence` of bytes.
///
/// This construction will consume 1kB chunks from the `Bytes` and send them at once. This optimizes for
Expand All @@ -224,6 +369,15 @@ extension HTTPClientRequest.Body {
public static func stream<Bytes: AsyncSequence>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
Self._stream(bytes, length: length)
}
#endif

@inlinable
static func _stream<Bytes: AsyncSequence>(
_ bytes: Bytes,
length: Length
) -> Self where Bytes.Element == UInt8 {
let body = self.init(.asyncSequence(length: length.storage) {
var iterator = bytes.makeAsyncIterator()
Expand Down Expand Up @@ -257,7 +411,7 @@ extension Optional where Wrapped == HTTPClientRequest.Body {
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTPClientRequest.Body {
/// The length of a HTTP request body.
public struct Length {
public struct Length: Sendable {
/// The size of the request body is not known before starting the request
public static let unknown: Self = .init(storage: .unknown)

Expand Down
6 changes: 3 additions & 3 deletions Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import NIOHTTP1
///
/// This object is similar to ``HTTPClient/Response``, but used for the Swift Concurrency API.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public struct HTTPClientResponse {
public struct HTTPClientResponse: Sendable {
/// The HTTP version on which the response was received.
public var version: HTTPVersion

Expand All @@ -38,7 +38,7 @@ public struct HTTPClientResponse {
/// The body is streamed as an `AsyncSequence` of `ByteBuffer`, where each `ByteBuffer` contains
/// an arbitrarily large chunk of data. The boundaries between `ByteBuffer` objects in the sequence
/// are entirely synthetic and have no semantic meaning.
public struct Body {
public struct Body: Sendable {
private let bag: Transaction
private let reference: ResponseRef

Expand Down Expand Up @@ -87,7 +87,7 @@ extension HTTPClientResponse.Body {
/// The purpose of this object is to inform the transaction about the response body being deinitialized.
/// If the users has not called `makeAsyncIterator` on the body, before it is deinited, the http
/// request needs to be cancelled.
fileprivate class ResponseRef {
fileprivate final class ResponseRef: Sendable {
private let transaction: Transaction

init(transaction: Transaction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ extension HTTPConnectionPool.Manager: HTTPConnectionPoolDelegate {
}

extension HTTPConnectionPool.Connection.ID {
static var globalGenerator = Generator()
static let globalGenerator = Generator()

struct Generator {
private let atomic: ManagedAtomic<Int>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
//
//===----------------------------------------------------------------------===//

import NIOCore

/// - Note: use `HTTPClientRequest.Body.Length` if you want to expose `RequestBodyLength` publicly
@usableFromInline
internal enum RequestBodyLength: Hashable {
internal enum RequestBodyLength: Hashable, NIOSendable {
/// size of the request body is not known before starting the request
case unknown
/// size of the request body is fixed and exactly `count` bytes
Expand Down
3 changes: 2 additions & 1 deletion Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import Darwin
import Glibc
#endif
import CAsyncHTTPClient
import NIOCore

extension HTTPClient {
/// A representation of an HTTP cookie.
public struct Cookie {
public struct Cookie: NIOSendable {
/// The name of the cookie.
public var name: String
/// The cookie's string value.
Expand Down
Loading