Skip to content

Commit c79e779

Browse files
committed
Sendable checking
1 parent 42b0637 commit c79e779

11 files changed

+165
-57
lines changed

Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutableRequest.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,13 @@ protocol HTTPRequestExecutor {
201201
func cancelRequest(_ task: HTTPExecutableRequest)
202202
}
203203

204-
protocol HTTPExecutableRequest: AnyObject {
204+
#if swift(>=5.6)
205+
typealias _HTTPExecutableRequestSendable = Sendable
206+
#else
207+
typealias _HTTPExecutableRequestSendable = Any
208+
#endif
209+
210+
protocol HTTPExecutableRequest: AnyObject, _HTTPExecutableRequestSendable {
205211
/// The request's logger
206212
var logger: Logger { get }
207213

Sources/AsyncHTTPClient/HTTPClient.swift

+5
Original file line numberDiff line numberDiff line change
@@ -1025,3 +1025,8 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
10251025
@available(*, deprecated, message: "AsyncHTTPClient now correctly supports informational headers. For this reason `httpEndReceivedAfterHeadWith1xx` will not be thrown anymore.")
10261026
public static let httpEndReceivedAfterHeadWith1xx = HTTPClientError(code: .httpEndReceivedAfterHeadWith1xx)
10271027
}
1028+
1029+
#if swift(>=5.6)
1030+
/// HTTPClient is Sendable, since shared state is protected by the internal ``stateLock``.
1031+
extension HTTPClient: @unchecked Sendable {}
1032+
#endif

Sources/AsyncHTTPClient/HTTPHandler.swift

+17-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414

1515
import Foundation
1616
import Logging
17-
import NIOConcurrencyHelpers
17+
#if swift(>=5.6)
18+
@preconcurrency import NIOCore
19+
#else
1820
import NIOCore
21+
#endif
22+
import NIOConcurrencyHelpers
1923
import NIOHTTP1
2024
import NIOSSL
2125

@@ -385,6 +389,12 @@ public class ResponseAccumulator: HTTPClientResponseDelegate {
385389
}
386390
}
387391

392+
#if swift(>=5.6)
393+
@preconcurrency public protocol _HTTPClientResponseDelegate: Sendable {}
394+
#else
395+
public protocol _HTTPClientResponseDelegate {}
396+
#endif
397+
388398
/// `HTTPClientResponseDelegate` allows an implementation to receive notifications about request processing and to control how response parts are processed.
389399
/// You can implement this protocol if you need fine-grained control over an HTTP request/response, for example, if you want to inspect the response
390400
/// headers before deciding whether to accept a response body, or if you want to stream your request body. Pass an instance of your conforming
@@ -414,7 +424,7 @@ public class ResponseAccumulator: HTTPClientResponseDelegate {
414424
/// released together with the `HTTPTaskHandler` when channel is closed.
415425
/// Users of the library are not required to keep a reference to the
416426
/// object that implements this protocol, but may do so if needed.
417-
public protocol HTTPClientResponseDelegate: AnyObject {
427+
public protocol HTTPClientResponseDelegate: AnyObject, _HTTPClientResponseDelegate {
418428
associatedtype Response
419429

420430
/// Called when the request head is sent. Will be called once.
@@ -635,6 +645,11 @@ extension HTTPClient {
635645
}
636646
}
637647

648+
#if swift(>=5.6)
649+
// HTTPClient.Task is Sendable thanks to the internal lock.
650+
extension HTTPClient.Task: @unchecked Sendable {}
651+
#endif
652+
638653
internal struct TaskCancelEvent {}
639654

640655
// MARK: - RedirectHandler

Sources/AsyncHTTPClient/RequestBag.swift

+5
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,8 @@ extension RequestBag: HTTPClientTaskDelegate {
446446
}
447447
}
448448
}
449+
450+
#if swift(>=5.6)
451+
// RequestBag is Sendable because everything is dispatched onto the EL.
452+
extension RequestBag: @unchecked Sendable {}
453+
#endif

Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift

+17-12
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
@testable import AsyncHTTPClient
16+
#if swift(>=5.6)
17+
@preconcurrency import Logging
18+
#else
1619
import Logging
20+
#endif
1721
import NIOCore
1822
import NIOPosix
1923
import XCTest
@@ -327,13 +331,14 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
327331
let client = makeDefaultHTTPClient()
328332
defer { XCTAssertNoThrow(try client.syncShutdown()) }
329333
let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:))
330-
var request = HTTPClientRequest(url: "http://localhost:\(bin.port)/offline")
331-
request.method = .POST
332-
let streamWriter = AsyncSequenceWriter<ByteBuffer>()
333-
request.body = .stream(streamWriter, length: .unknown)
334334

335-
let task = Task<HTTPClientResponse, Error> { [request] in
336-
try await client.execute(request, deadline: .now() + .seconds(2), logger: logger)
335+
let task = Task<Void, Error> {
336+
var request = HTTPClientRequest(url: "http://localhost:\(bin.port)/offline")
337+
request.method = .POST
338+
let streamWriter = AsyncSequenceWriter<ByteBuffer>()
339+
request.body = .stream(streamWriter, length: .unknown)
340+
341+
_ = try await client.execute(request, deadline: .now() + .seconds(2), logger: logger)
337342
}
338343
task.cancel()
339344
await XCTAssertThrowsError(try await task.value) { error in
@@ -352,10 +357,10 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
352357
let client = makeDefaultHTTPClient()
353358
defer { XCTAssertNoThrow(try client.syncShutdown()) }
354359
let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:))
355-
let request = HTTPClientRequest(url: "https://localhost:\(bin.port)/wait")
356360

357-
let task = Task<HTTPClientResponse, Error> { [request] in
358-
try await client.execute(request, deadline: .now() + .milliseconds(100), logger: logger)
361+
let task = Task<Void, Error> {
362+
let request = HTTPClientRequest(url: "https://localhost:\(bin.port)/wait")
363+
_ = try await client.execute(request, deadline: .now() + .milliseconds(100), logger: logger)
359364
}
360365
await XCTAssertThrowsError(try await task.value) { error in
361366
guard let error = error as? HTTPClientError else {
@@ -377,10 +382,10 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
377382
let client = makeDefaultHTTPClient()
378383
defer { XCTAssertNoThrow(try client.syncShutdown()) }
379384
let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:))
380-
let request = HTTPClientRequest(url: "http://localhost:\(bin.port)/wait")
381385

382-
let task = Task<HTTPClientResponse, Error> { [request] in
383-
try await client.execute(request, deadline: .now(), logger: logger)
386+
let task = Task<Void, Error> {
387+
let request = HTTPClientRequest(url: "http://localhost:\(bin.port)/wait")
388+
_ = try await client.execute(request, deadline: .now(), logger: logger)
384389
}
385390
await XCTAssertThrowsError(try await task.value) { error in
386391
guard let error = error as? HTTPClientError else {

Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift

+4
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,10 @@ extension HTTPBin where RequestHandler == HTTPBinHandler {
591591
}
592592
}
593593

594+
#if swift(>=5.6)
595+
extension HTTPBin: @unchecked Sendable {}
596+
#endif
597+
594598
enum HTTPBinError: Error {
595599
case refusedConnection
596600
case invalidProxyRequest

Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift

+4
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,7 @@ private class MockScheduledRequest: HTTPSchedulableRequest {
137137
preconditionFailure("Unimplemented")
138138
}
139139
}
140+
141+
#if swift(>=5.6)
142+
extension MockScheduledRequest: @unchecked Sendable {}
143+
#endif

Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift

+8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
@testable import AsyncHTTPClient
1616
import Logging
17+
#if swift(>=5.6)
18+
@preconcurrency import NIOCore
19+
#else
1720
import NIOCore
21+
#endif
1822
import NIOHTTP1
1923
import NIOSSL
2024

@@ -747,3 +751,7 @@ class MockHTTPRequest: HTTPSchedulableRequest {
747751
preconditionFailure("Unimplemented")
748752
}
749753
}
754+
755+
#if swift(>=5.6)
756+
extension MockHTTPRequest: @unchecked Sendable {}
757+
#endif

Tests/AsyncHTTPClientTests/Mocks/MockRequestExecutor.swift

+10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
@testable import AsyncHTTPClient
1616
import NIOConcurrencyHelpers
17+
#if swift(>=5.6)
18+
@preconcurrency import NIOCore
19+
#else
1720
import NIOCore
21+
#endif
1822

1923
// This is a MockRequestExecutor, that is synchronized on its EventLoop.
2024
final class MockRequestExecutor {
@@ -273,3 +277,9 @@ extension MockRequestExecutor {
273277
}
274278
}
275279
}
280+
281+
#if swift(>=5.6)
282+
extension MockRequestExecutor: @unchecked Sendable {}
283+
extension MockRequestExecutor.RequestParts: Sendable {}
284+
extension MockRequestExecutor.BlockingQueue: @unchecked Sendable {}
285+
#endif

Tests/AsyncHTTPClientTests/RequestBagTests.swift

+12-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
@testable import AsyncHTTPClient
1616
import Logging
1717
import NIOCore
18+
import NIOConcurrencyHelpers
1819
import NIOEmbedded
1920
import NIOHTTP1
2021
import XCTest
@@ -522,16 +523,24 @@ class UploadCountingDelegate: HTTPClientResponseDelegate {
522523
}
523524
}
524525

525-
class MockTaskQueuer: HTTPRequestScheduler {
526-
private(set) var hitCancelCount = 0
526+
final class MockTaskQueuer: HTTPRequestScheduler {
527+
private let hitCancelCounter = NIOAtomic<Int>.makeAtomic(value: 0)
528+
529+
var hitCancelCount: Int {
530+
self.hitCancelCounter.load()
531+
}
527532

528533
init() {}
529534

530535
func cancelRequest(_: HTTPSchedulableRequest) {
531-
self.hitCancelCount += 1
536+
self.hitCancelCounter.add(1)
532537
}
533538
}
534539

540+
#if swift(>=5.6)
541+
extension MockTaskQueuer: @unchecked Sendable {}
542+
#endif
543+
535544
extension RequestOptions {
536545
static func forTests(idleReadTimeout: TimeAmount? = nil) -> Self {
537546
RequestOptions(

0 commit comments

Comments
 (0)