@@ -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,118 @@ 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
+ self . state. withLockedValue {
595
+ switch $0. state {
596
+ case . idle:
597
+ if self . requestMethod != . HEAD,
598
+ let contentLength = head. headers. first ( name: " Content-Length " ) ,
599
+ let announcedBodySize = Int ( contentLength) ,
600
+ announcedBodySize > self . maxBodySize
601
+ {
602
+ let error = ResponseTooBigError ( maxBodySize: maxBodySize)
603
+ $0. state = . error( error)
604
+ return task. eventLoop. makeFailedFuture ( error)
605
+ }
598
606
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
607
+ $0. state = . head( head)
608
+ case . head:
609
+ preconditionFailure ( " head already set " )
610
+ case . body:
611
+ preconditionFailure ( " no head received before body " )
612
+ case . end:
613
+ preconditionFailure ( " request already processed " )
614
+ case . error:
615
+ break
616
+ }
617
+ return task. eventLoop. makeSucceededFuture ( ( ) )
608
618
}
609
- return task. eventLoop. makeSucceededFuture ( ( ) )
610
619
}
611
620
612
621
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
- }
622
+ self . state. withLockedValue {
623
+ switch $0. state {
624
+ case . idle:
625
+ preconditionFailure ( " no head received before body " )
626
+ case . head( let head) :
627
+ guard part. readableBytes <= self . maxBodySize else {
628
+ let error = ResponseTooBigError ( maxBodySize: self . maxBodySize)
629
+ $0. state = . error( error)
630
+ return task. eventLoop. makeFailedFuture ( error)
631
+ }
632
+ $0. state = . body( head, part)
633
+ case . body( let head, var body) :
634
+ let newBufferSize = body. writerIndex + part. readableBytes
635
+ guard newBufferSize <= self . maxBodySize else {
636
+ let error = ResponseTooBigError ( maxBodySize: self . maxBodySize)
637
+ $0. state = . error( error)
638
+ return task. eventLoop. makeFailedFuture ( error)
639
+ }
630
640
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
641
+ // The compiler can't prove that `self.state` is dead here (and it kinda isn't, there's
642
+ // a cross-module call in the way) so we need to drop the original reference to `body` in
643
+ // `self.state` or we'll get a CoW. To fix that we temporarily set the state to `.end` (which
644
+ // has no associated data). We'll fix it at the bottom of this block.
645
+ $0. state = . end
646
+ var part = part
647
+ body. writeBuffer ( & part)
648
+ $0. state = . body( head, body)
649
+ case . end:
650
+ preconditionFailure ( " request already processed " )
651
+ case . error:
652
+ break
653
+ }
654
+ return task. eventLoop. makeSucceededFuture ( ( ) )
643
655
}
644
- return task. eventLoop. makeSucceededFuture ( ( ) )
645
656
}
646
657
647
658
public func didReceiveError( task: HTTPClient . Task < Response > , _ error: Error ) {
648
- self . state = . error( error)
659
+ self . state. withLockedValue {
660
+ $0. state = . error( error)
661
+ }
649
662
}
650
663
651
664
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
665
+ try self . state. withLockedValue {
666
+ switch $0. state {
667
+ case . idle:
668
+ preconditionFailure ( " no head received before end " )
669
+ case . head( let head) :
670
+ return Response (
671
+ host: self . requestHost,
672
+ status: head. status,
673
+ version: head. version,
674
+ headers: head. headers,
675
+ body: nil ,
676
+ history: $0. history
677
+ )
678
+ case . body( let head, let body) :
679
+ return Response (
680
+ host: self . requestHost,
681
+ status: head. status,
682
+ version: head. version,
683
+ headers: head. headers,
684
+ body: body,
685
+ history: $0. history
686
+ )
687
+ case . end:
688
+ preconditionFailure ( " request already processed " )
689
+ case . error( let error) :
690
+ throw error
691
+ }
677
692
}
678
693
}
679
694
}
@@ -709,8 +724,9 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
709
724
/// released together with the `HTTPTaskHandler` when channel is closed.
710
725
/// Users of the library are not required to keep a reference to the
711
726
/// object that implements this protocol, but may do so if needed.
712
- public protocol HTTPClientResponseDelegate : AnyObject {
713
- associatedtype Response
727
+ @preconcurrency
728
+ public protocol HTTPClientResponseDelegate : AnyObject , Sendable {
729
+ associatedtype Response : Sendable
714
730
715
731
/// Called when the request head is sent. Will be called once.
716
732
///
@@ -885,7 +901,7 @@ extension URL {
885
901
}
886
902
}
887
903
888
- protocol HTTPClientTaskDelegate {
904
+ protocol HTTPClientTaskDelegate : Sendable {
889
905
func fail( _ error: Error )
890
906
}
891
907
@@ -894,30 +910,35 @@ extension HTTPClient {
894
910
///
895
911
/// Will be created by the library and could be used for obtaining
896
912
/// `EventLoopFuture<Response>` of the execution or cancellation of the execution.
897
- public final class Task < Response> {
913
+ @preconcurrency
914
+ public final class Task < Response: Sendable > {
898
915
/// The `EventLoop` the delegate will be executed on.
899
916
public let eventLoop : EventLoop
900
917
/// The `Logger` used by the `Task` for logging.
901
918
public let logger : Logger // We are okay to store the logger here because a Task is for only one request.
902
919
903
920
let promise : EventLoopPromise < Response >
904
921
922
+ struct State : Sendable {
923
+ var isCancelled : Bool
924
+ var taskDelegate : HTTPClientTaskDelegate ?
925
+ }
926
+
927
+ private let state : NIOLockedValueBox < State >
928
+
905
929
var isCancelled : Bool {
906
- self . lock . withLock { self . _isCancelled }
930
+ self . state . withLockedValue { $0 . isCancelled }
907
931
}
908
932
909
933
var taskDelegate : HTTPClientTaskDelegate ? {
910
934
get {
911
- self . lock . withLock { self . _taskDelegate }
935
+ self . state . withLockedValue { $0 . taskDelegate }
912
936
}
913
937
set {
914
- self . lock . withLock { self . _taskDelegate = newValue }
938
+ self . state . withLockedValue { $0 . taskDelegate = newValue }
915
939
}
916
940
}
917
941
918
- private var _isCancelled : Bool = false
919
- private var _taskDelegate : HTTPClientTaskDelegate ?
920
- private let lock = NIOLock ( )
921
942
private let makeOrGetFileIOThreadPool : ( ) -> NIOThreadPool
922
943
923
944
/// The shared thread pool of a ``HTTPClient`` used for file IO. It is lazily created on first access.
@@ -930,6 +951,7 @@ extension HTTPClient {
930
951
self . promise = eventLoop. makePromise ( )
931
952
self . logger = logger
932
953
self . makeOrGetFileIOThreadPool = makeOrGetFileIOThreadPool
954
+ self . state = NIOLockedValueBox ( State ( isCancelled: false , taskDelegate: nil ) )
933
955
}
934
956
935
957
static func failedTask(
@@ -957,7 +979,8 @@ extension HTTPClient {
957
979
/// - returns: The value of ``futureResult`` when it completes.
958
980
/// - throws: The error value of ``futureResult`` if it errors.
959
981
@available ( * , noasync, message: " wait() can block indefinitely, prefer get() " , renamed: " get() " )
960
- public func wait( ) throws -> Response {
982
+ @preconcurrency
983
+ public func wait( ) throws -> Response where Response: Sendable {
961
984
try self . promise. futureResult. wait ( )
962
985
}
963
986
@@ -968,7 +991,8 @@ extension HTTPClient {
968
991
/// - returns: The value of ``futureResult`` when it completes.
969
992
/// - throws: The error value of ``futureResult`` if it errors.
970
993
@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
971
- public func get( ) async throws -> Response {
994
+ @preconcurrency
995
+ public func get( ) async throws -> Response where Response: Sendable {
972
996
try await self . promise. futureResult. get ( )
973
997
}
974
998
@@ -985,9 +1009,9 @@ extension HTTPClient {
985
1009
///
986
1010
/// - Parameter error: the error that is used to fail the promise
987
1011
public func fail( reason error: Error ) {
988
- let taskDelegate = self . lock . withLock { ( ) -> HTTPClientTaskDelegate ? in
989
- self . _isCancelled = true
990
- return self . _taskDelegate
1012
+ let taskDelegate = self . state . withLockedValue { state in
1013
+ state . isCancelled = true
1014
+ return state . taskDelegate
991
1015
}
992
1016
993
1017
taskDelegate? . fail ( error)
@@ -1017,7 +1041,7 @@ internal struct TaskCancelEvent {}
1017
1041
1018
1042
// MARK: - RedirectHandler
1019
1043
1020
- internal struct RedirectHandler < ResponseType> {
1044
+ internal struct RedirectHandler < ResponseType: Sendable > {
1021
1045
let request : HTTPClient . Request
1022
1046
let redirectState : RedirectState
1023
1047
let execute : ( HTTPClient . Request , RedirectState ) -> HTTPClient . Task < ResponseType >
0 commit comments