@@ -538,8 +538,12 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
538
538
}
539
539
}
540
540
541
- var history = [ HTTPClient . RequestResponse] ( )
542
- var state = State . idle
541
+ private struct MutableState : Sendable {
542
+ var history = [ HTTPClient . RequestResponse] ( )
543
+ var state = State . idle
544
+ }
545
+
546
+ private let state : NIOLockedValueBox < MutableState >
543
547
let requestMethod : HTTPMethod
544
548
let requestHost : String
545
549
@@ -573,107 +577,126 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
573
577
self . requestMethod = request. method
574
578
self . requestHost = request. host
575
579
self . maxBodySize = maxBodySize
580
+ self . state = NIOLockedValueBox ( MutableState ( ) )
576
581
}
577
582
578
583
public func didVisitURL(
579
584
task: HTTPClient . Task < HTTPClient . Response > ,
580
585
_ request: HTTPClient . Request ,
581
586
_ head: HTTPResponseHead
582
587
) {
583
- self . history. append ( . init( request: request, responseHead: head) )
588
+ self . state. withLockedValue {
589
+ $0. history. append ( . init( request: request, responseHead: head) )
590
+ }
584
591
}
585
592
586
593
public func didReceiveHead( task: HTTPClient . Task < Response > , _ head: HTTPResponseHead ) -> EventLoopFuture < Void > {
587
- switch self . state {
588
- case . idle:
589
- if self . requestMethod != . HEAD,
590
- let contentLength = head. headers. first ( name: " Content-Length " ) ,
591
- let announcedBodySize = Int ( contentLength) ,
592
- announcedBodySize > self . maxBodySize
593
- {
594
- let error = ResponseTooBigError ( maxBodySize: maxBodySize)
595
- self . state = . error( error)
596
- return task. eventLoop. makeFailedFuture ( error)
597
- }
594
+ let responseTooBig : Bool
598
595
599
- self . state = . head( head)
600
- case . head:
601
- preconditionFailure ( " head already set " )
602
- case . body:
603
- preconditionFailure ( " no head received before body " )
604
- case . end:
605
- preconditionFailure ( " request already processed " )
606
- case . error:
607
- break
596
+ if self . requestMethod != . HEAD,
597
+ let contentLength = head. headers. first ( name: " Content-Length " ) ,
598
+ let announcedBodySize = Int ( contentLength) ,
599
+ announcedBodySize > self . maxBodySize
600
+ {
601
+ responseTooBig = true
602
+ } else {
603
+ responseTooBig = false
604
+ }
605
+
606
+ return self . state. withLockedValue {
607
+ switch $0. state {
608
+ case . idle:
609
+ if responseTooBig {
610
+ let error = ResponseTooBigError ( maxBodySize: self . maxBodySize)
611
+ $0. state = . error( error)
612
+ return task. eventLoop. makeFailedFuture ( error)
613
+ }
614
+
615
+ $0. state = . head( head)
616
+ case . head:
617
+ preconditionFailure ( " head already set " )
618
+ case . body:
619
+ preconditionFailure ( " no head received before body " )
620
+ case . end:
621
+ preconditionFailure ( " request already processed " )
622
+ case . error:
623
+ break
624
+ }
625
+ return task. eventLoop. makeSucceededFuture ( ( ) )
608
626
}
609
- return task. eventLoop. makeSucceededFuture ( ( ) )
610
627
}
611
628
612
629
public func didReceiveBodyPart( task: HTTPClient . Task < Response > , _ part: ByteBuffer ) -> EventLoopFuture < Void > {
613
- switch self . state {
614
- case . idle:
615
- preconditionFailure ( " no head received before body " )
616
- case . head( let head) :
617
- guard part. readableBytes <= self . maxBodySize else {
618
- let error = ResponseTooBigError ( maxBodySize: self . maxBodySize)
619
- self . state = . error( error)
620
- return task. eventLoop. makeFailedFuture ( error)
621
- }
622
- self . state = . body( head, part)
623
- case . body( let head, var body) :
624
- let newBufferSize = body. writerIndex + part. readableBytes
625
- guard newBufferSize <= self . maxBodySize else {
626
- let error = ResponseTooBigError ( maxBodySize: self . maxBodySize)
627
- self . state = . error( error)
628
- return task. eventLoop. makeFailedFuture ( error)
629
- }
630
+ self . state. withLockedValue {
631
+ switch $0. state {
632
+ case . idle:
633
+ preconditionFailure ( " no head received before body " )
634
+ case . head( let head) :
635
+ guard part. readableBytes <= self . maxBodySize else {
636
+ let error = ResponseTooBigError ( maxBodySize: self . maxBodySize)
637
+ $0. state = . error( error)
638
+ return task. eventLoop. makeFailedFuture ( error)
639
+ }
640
+ $0. state = . body( head, part)
641
+ case . body( let head, var body) :
642
+ let newBufferSize = body. writerIndex + part. readableBytes
643
+ guard newBufferSize <= self . maxBodySize else {
644
+ let error = ResponseTooBigError ( maxBodySize: self . maxBodySize)
645
+ $0. state = . error( error)
646
+ return task. eventLoop. makeFailedFuture ( error)
647
+ }
630
648
631
- // The compiler can't prove that `self.state` is dead here (and it kinda isn't, there's
632
- // a cross-module call in the way) so we need to drop the original reference to `body` in
633
- // `self.state` or we'll get a CoW. To fix that we temporarily set the state to `.end` (which
634
- // has no associated data). We'll fix it at the bottom of this block.
635
- self . state = . end
636
- var part = part
637
- body. writeBuffer ( & part)
638
- self . state = . body( head, body)
639
- case . end:
640
- preconditionFailure ( " request already processed " )
641
- case . error:
642
- break
649
+ // The compiler can't prove that `self.state` is dead here (and it kinda isn't, there's
650
+ // a cross-module call in the way) so we need to drop the original reference to `body` in
651
+ // `self.state` or we'll get a CoW. To fix that we temporarily set the state to `.end` (which
652
+ // has no associated data). We'll fix it at the bottom of this block.
653
+ $0. state = . end
654
+ var part = part
655
+ body. writeBuffer ( & part)
656
+ $0. state = . body( head, body)
657
+ case . end:
658
+ preconditionFailure ( " request already processed " )
659
+ case . error:
660
+ break
661
+ }
662
+ return task. eventLoop. makeSucceededFuture ( ( ) )
643
663
}
644
- return task. eventLoop. makeSucceededFuture ( ( ) )
645
664
}
646
665
647
666
public func didReceiveError( task: HTTPClient . Task < Response > , _ error: Error ) {
648
- self . state = . error( error)
667
+ self . state. withLockedValue {
668
+ $0. state = . error( error)
669
+ }
649
670
}
650
671
651
672
public func didFinishRequest( task: HTTPClient . Task < Response > ) throws -> Response {
652
- switch self . state {
653
- case . idle:
654
- preconditionFailure ( " no head received before end " )
655
- case . head( let head) :
656
- return Response (
657
- host: self . requestHost,
658
- status: head. status,
659
- version: head. version,
660
- headers: head. headers,
661
- body: nil ,
662
- history: self . history
663
- )
664
- case . body( let head, let body) :
665
- return Response (
666
- host: self . requestHost,
667
- status: head. status,
668
- version: head. version,
669
- headers: head. headers,
670
- body: body,
671
- history: self . history
672
- )
673
- case . end:
674
- preconditionFailure ( " request already processed " )
675
- case . error( let error) :
676
- throw error
673
+ try self . state. withLockedValue {
674
+ switch $0. state {
675
+ case . idle:
676
+ preconditionFailure ( " no head received before end " )
677
+ case . head( let head) :
678
+ return Response (
679
+ host: self . requestHost,
680
+ status: head. status,
681
+ version: head. version,
682
+ headers: head. headers,
683
+ body: nil ,
684
+ history: $0. history
685
+ )
686
+ case . body( let head, let body) :
687
+ return Response (
688
+ host: self . requestHost,
689
+ status: head. status,
690
+ version: head. version,
691
+ headers: head. headers,
692
+ body: body,
693
+ history: $0. history
694
+ )
695
+ case . end:
696
+ preconditionFailure ( " request already processed " )
697
+ case . error( let error) :
698
+ throw error
699
+ }
677
700
}
678
701
}
679
702
}
@@ -709,8 +732,9 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
709
732
/// released together with the `HTTPTaskHandler` when channel is closed.
710
733
/// Users of the library are not required to keep a reference to the
711
734
/// object that implements this protocol, but may do so if needed.
712
- public protocol HTTPClientResponseDelegate : AnyObject {
713
- associatedtype Response
735
+ @preconcurrency
736
+ public protocol HTTPClientResponseDelegate : AnyObject , Sendable {
737
+ associatedtype Response : Sendable
714
738
715
739
/// Called when the request head is sent. Will be called once.
716
740
///
@@ -885,7 +909,7 @@ extension URL {
885
909
}
886
910
}
887
911
888
- protocol HTTPClientTaskDelegate {
912
+ protocol HTTPClientTaskDelegate : Sendable {
889
913
func fail( _ error: Error )
890
914
}
891
915
@@ -894,49 +918,54 @@ extension HTTPClient {
894
918
///
895
919
/// Will be created by the library and could be used for obtaining
896
920
/// `EventLoopFuture<Response>` of the execution or cancellation of the execution.
897
- public final class Task < Response> {
921
+ public final class Task < Response> : Sendable {
898
922
/// The `EventLoop` the delegate will be executed on.
899
923
public let eventLoop : EventLoop
900
924
/// The `Logger` used by the `Task` for logging.
901
925
public let logger : Logger // We are okay to store the logger here because a Task is for only one request.
902
926
903
927
let promise : EventLoopPromise < Response >
904
928
929
+ struct State : Sendable {
930
+ var isCancelled : Bool
931
+ var taskDelegate : HTTPClientTaskDelegate ?
932
+ }
933
+
934
+ private let state : NIOLockedValueBox < State >
935
+
905
936
var isCancelled : Bool {
906
- self . lock . withLock { self . _isCancelled }
937
+ self . state . withLockedValue { $0 . isCancelled }
907
938
}
908
939
909
940
var taskDelegate : HTTPClientTaskDelegate ? {
910
941
get {
911
- self . lock . withLock { self . _taskDelegate }
942
+ self . state . withLockedValue { $0 . taskDelegate }
912
943
}
913
944
set {
914
- self . lock . withLock { self . _taskDelegate = newValue }
945
+ self . state . withLockedValue { $0 . taskDelegate = newValue }
915
946
}
916
947
}
917
948
918
- private var _isCancelled : Bool = false
919
- private var _taskDelegate : HTTPClientTaskDelegate ?
920
- private let lock = NIOLock ( )
921
- private let makeOrGetFileIOThreadPool : ( ) -> NIOThreadPool
949
+ private let makeOrGetFileIOThreadPool : @Sendable ( ) -> NIOThreadPool
922
950
923
951
/// The shared thread pool of a ``HTTPClient`` used for file IO. It is lazily created on first access.
924
952
internal var fileIOThreadPool : NIOThreadPool {
925
953
self . makeOrGetFileIOThreadPool ( )
926
954
}
927
955
928
- init ( eventLoop: EventLoop , logger: Logger , makeOrGetFileIOThreadPool: @escaping ( ) -> NIOThreadPool ) {
956
+ init ( eventLoop: EventLoop , logger: Logger , makeOrGetFileIOThreadPool: @escaping @ Sendable ( ) -> NIOThreadPool ) {
929
957
self . eventLoop = eventLoop
930
958
self . promise = eventLoop. makePromise ( )
931
959
self . logger = logger
932
960
self . makeOrGetFileIOThreadPool = makeOrGetFileIOThreadPool
961
+ self . state = NIOLockedValueBox ( State ( isCancelled: false , taskDelegate: nil ) )
933
962
}
934
963
935
964
static func failedTask(
936
965
eventLoop: EventLoop ,
937
966
error: Error ,
938
967
logger: Logger ,
939
- makeOrGetFileIOThreadPool: @escaping ( ) -> NIOThreadPool
968
+ makeOrGetFileIOThreadPool: @escaping @ Sendable ( ) -> NIOThreadPool
940
969
) -> Task < Response > {
941
970
let task = self . init (
942
971
eventLoop: eventLoop,
@@ -957,7 +986,8 @@ extension HTTPClient {
957
986
/// - returns: The value of ``futureResult`` when it completes.
958
987
/// - throws: The error value of ``futureResult`` if it errors.
959
988
@available ( * , noasync, message: " wait() can block indefinitely, prefer get() " , renamed: " get() " )
960
- public func wait( ) throws -> Response {
989
+ @preconcurrency
990
+ public func wait( ) throws -> Response where Response: Sendable {
961
991
try self . promise. futureResult. wait ( )
962
992
}
963
993
@@ -968,7 +998,8 @@ extension HTTPClient {
968
998
/// - returns: The value of ``futureResult`` when it completes.
969
999
/// - throws: The error value of ``futureResult`` if it errors.
970
1000
@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
971
- public func get( ) async throws -> Response {
1001
+ @preconcurrency
1002
+ public func get( ) async throws -> Response where Response: Sendable {
972
1003
try await self . promise. futureResult. get ( )
973
1004
}
974
1005
@@ -985,23 +1016,14 @@ extension HTTPClient {
985
1016
///
986
1017
/// - Parameter error: the error that is used to fail the promise
987
1018
public func fail( reason error: Error ) {
988
- let taskDelegate = self . lock . withLock { ( ) -> HTTPClientTaskDelegate ? in
989
- self . _isCancelled = true
990
- return self . _taskDelegate
1019
+ let taskDelegate = self . state . withLockedValue { state in
1020
+ state . isCancelled = true
1021
+ return state . taskDelegate
991
1022
}
992
1023
993
1024
taskDelegate? . fail ( error)
994
1025
}
995
1026
996
- func succeed< Delegate: HTTPClientResponseDelegate > (
997
- promise: EventLoopPromise < Response > ? ,
998
- with value: Response ,
999
- delegateType: Delegate . Type ,
1000
- closing: Bool
1001
- ) {
1002
- promise? . succeed ( value)
1003
- }
1004
-
1005
1027
func fail< Delegate: HTTPClientResponseDelegate > (
1006
1028
with error: Error ,
1007
1029
delegateType: Delegate . Type
@@ -1011,13 +1033,11 @@ extension HTTPClient {
1011
1033
}
1012
1034
}
1013
1035
1014
- extension HTTPClient . Task : @unchecked Sendable { }
1015
-
1016
1036
internal struct TaskCancelEvent { }
1017
1037
1018
1038
// MARK: - RedirectHandler
1019
1039
1020
- internal struct RedirectHandler < ResponseType> {
1040
+ internal struct RedirectHandler < ResponseType: Sendable > {
1021
1041
let request : HTTPClient . Request
1022
1042
let redirectState : RedirectState
1023
1043
let execute : ( HTTPClient . Request , RedirectState ) -> HTTPClient . Task < ResponseType >
0 commit comments