@@ -493,6 +493,9 @@ public class NSURLSessionTask : NSObject, NSCopying {
493
493
if oldValue. isEasyHandlePaused && !internalState. isEasyHandlePaused {
494
494
fatalError ( " Need to solve pausing receive. " )
495
495
}
496
+ if case . taskCompleted = internalState {
497
+ updateTaskState ( )
498
+ }
496
499
}
497
500
}
498
501
private let workQueue : dispatch_queue_t
@@ -559,9 +562,7 @@ public class NSURLSessionTask : NSObject, NSCopying {
559
562
dispatch_sync ( taskAttributesIsolation) { r = self . _currentRequest }
560
563
return r
561
564
}
562
- set {
563
- dispatch_barrier_async ( taskAttributesIsolation) { self . _currentRequest = newValue }
564
- }
565
+ set { dispatch_barrier_async ( taskAttributesIsolation) { self . _currentRequest = newValue } }
565
566
}
566
567
private var _currentRequest : NSURLRequest ? = nil
567
568
/*@NSCopying*/ public private( set) var response : NSURLResponse ? {
@@ -570,9 +571,7 @@ public class NSURLSessionTask : NSObject, NSCopying {
570
571
dispatch_sync ( taskAttributesIsolation) { r = self . _response }
571
572
return r
572
573
}
573
- set {
574
- dispatch_barrier_async ( taskAttributesIsolation) { self . _response = newValue }
575
- }
574
+ set { dispatch_barrier_async ( taskAttributesIsolation) { self . _response = newValue } }
576
575
}
577
576
private var _response : NSURLResponse ? = nil
578
577
@@ -588,9 +587,7 @@ public class NSURLSessionTask : NSObject, NSCopying {
588
587
dispatch_sync ( taskAttributesIsolation) { r = self . _countOfBytesReceived }
589
588
return r
590
589
}
591
- set {
592
- dispatch_barrier_async ( taskAttributesIsolation) { self . _countOfBytesReceived = newValue }
593
- }
590
+ set { dispatch_barrier_async ( taskAttributesIsolation) { self . _countOfBytesReceived = newValue } }
594
591
}
595
592
private var _countOfBytesReceived : Int64 = 0
596
593
@@ -601,9 +598,7 @@ public class NSURLSessionTask : NSObject, NSCopying {
601
598
dispatch_sync ( taskAttributesIsolation) { r = self . _countOfBytesSent }
602
599
return r
603
600
}
604
- set {
605
- dispatch_barrier_async ( taskAttributesIsolation) { self . _countOfBytesSent = newValue }
606
- }
601
+ set { dispatch_barrier_async ( taskAttributesIsolation) { self . _countOfBytesSent = newValue } }
607
602
}
608
603
private var _countOfBytesSent : Int64 = 0
609
604
@@ -629,10 +624,14 @@ public class NSURLSessionTask : NSObject, NSCopying {
629
624
* The current state of the task within the session.
630
625
*/
631
626
public var state : NSURLSessionTaskState {
632
- //TODO: Make this thread-safe
633
- guard suspendCount == 0 else { return . Suspended }
634
- NSUnimplemented ( )
627
+ get {
628
+ var r : NSURLSessionTaskState = . Suspended
629
+ dispatch_sync ( taskAttributesIsolation) { r = self . _state }
630
+ return r
631
+ }
632
+ set { dispatch_barrier_async ( taskAttributesIsolation) { self . _state = newValue } }
635
633
}
634
+ private var _state : NSURLSessionTaskState = . Suspended
636
635
637
636
/*
638
637
* The error, if any, delivered via -URLSession:task:didCompleteWithError:
@@ -659,25 +658,38 @@ public class NSURLSessionTask : NSObject, NSCopying {
659
658
//
660
659
// TODO: It may be worth looking into starting over a task that gets
661
660
// resumed. The Darwin Foundation documentation states that that's what
662
- // it down for anything but download tasks.
663
- //
664
- // TODO: is `suspend` / `resume` supposed to be thread safe? If so, we'd
665
- // need to use atomic increment / decrement of the suspend count.
666
- dispatch_async ( workQueue) {
661
+ // it does for anything but download tasks.
662
+
663
+ // We perform the increment and call to `updateTaskState()`
664
+ // synchronous, to make sure the `state` is updated when this method
665
+ // returns, but the actual suspend will be done asynchronous to avoid
666
+ // dead-locks.
667
+ dispatch_sync ( workQueue) {
667
668
self . suspendCount += 1
668
- guard self . suspendCount == 1 else { return }
669
- self . performSuspend ( )
669
+ guard self . suspendCount < Int . max else { fatalError ( " Task suspended too many times \( Int . max) . " ) }
670
+ self . updateTaskState ( )
671
+
672
+ if self . suspendCount == 1 {
673
+ dispatch_async ( self . workQueue) {
674
+ self . performSuspend ( )
675
+ }
676
+ }
670
677
}
671
678
}
672
679
/// Resume the task.
673
680
///
674
681
/// - SeeAlso: `suspend()`
675
682
public func resume( ) {
676
- dispatch_async ( workQueue) {
683
+ dispatch_sync ( workQueue) {
677
684
self . suspendCount -= 1
678
685
guard 0 <= self . suspendCount else { fatalError ( " Resuming a task that's not suspended. Calls to resume() / suspend() need to be matched. " ) }
679
- guard self . suspendCount == 0 else { return }
680
- self . performResume ( )
686
+ self . updateTaskState ( )
687
+
688
+ if self . suspendCount == 0 {
689
+ dispatch_async ( self . workQueue) {
690
+ self . performResume ( )
691
+ }
692
+ }
681
693
}
682
694
}
683
695
@@ -709,6 +721,27 @@ public class NSURLSessionTask : NSObject, NSCopying {
709
721
private var _priority : Float = NSURLSessionTaskPriorityDefault
710
722
}
711
723
724
+ private extension NSURLSessionTask {
725
+ /// The calls to `suspend` can be nested. This one is only called when the
726
+ /// task is not suspended and needs to go into suspended state.
727
+ func performSuspend( ) {
728
+ if case . transferInProgress( let transferState) = internalState {
729
+ internalState = . transferReady( transferState)
730
+ }
731
+ }
732
+ /// The calls to `resume` can be nested. This one is only called when the
733
+ /// task is suspended and needs to go out of suspended state.
734
+ func performResume( ) {
735
+ if case . initial = internalState {
736
+ guard let r = originalRequest else { fatalError ( " Task has no original request. " ) }
737
+ startNewTransfer ( r)
738
+ }
739
+ if case . transferReady( let transferState) = internalState {
740
+ internalState = . transferInProgress( transferState)
741
+ }
742
+ }
743
+ }
744
+
712
745
internal extension NSURLSessionTask {
713
746
/// The is independent of the public `state: NSURLSessionTaskState`.
714
747
enum InternalState {
@@ -744,28 +777,11 @@ internal extension NSURLSessionTask {
744
777
case waitingForResponseCompletionHandler( TransferState )
745
778
/// The task is completed
746
779
///
747
- /// Contrast this with `.transferComplted `.
780
+ /// Contrast this with `.transferCompleted `.
748
781
case taskCompleted
749
782
}
750
- /// The calls to `suspend` can be nested. This one is only called when the
751
- /// task is not suspended and needs to go into suspended state.
752
- private func performSuspend( ) {
753
- if case . transferInProgress( let transferState) = internalState {
754
- internalState = . transferReady( transferState)
755
- }
756
- }
757
- /// The calls to `resume` can be nested. This one is only called when the
758
- /// task is suspended and needs to go out of suspended state.
759
- private func performResume( ) {
760
- if case . initial = internalState {
761
- guard let r = originalRequest else { fatalError ( " Task has no original request. " ) }
762
- startNewTransfer ( r)
763
- }
764
- if case . transferReady( let transferState) = internalState {
765
- internalState = . transferInProgress( transferState)
766
- }
767
- }
768
783
}
784
+
769
785
private extension NSURLSessionTask . InternalState {
770
786
var isEasyHandleAddedToMultiHandle : Bool {
771
787
switch self {
@@ -793,6 +809,25 @@ private extension NSURLSessionTask.InternalState {
793
809
}
794
810
}
795
811
812
+ internal extension NSURLSessionTask {
813
+ /// Updates the (public) state based on private / internal state.
814
+ ///
815
+ /// - Note: This must be called on the `workQueue`.
816
+ private func updateTaskState( ) {
817
+ func calculateState( ) -> NSURLSessionTaskState {
818
+ if case . taskCompleted = internalState {
819
+ return . Completed
820
+ }
821
+ if suspendCount == 0 {
822
+ return . Running
823
+ } else {
824
+ return . Suspended
825
+ }
826
+ }
827
+ state = calculateState ( )
828
+ }
829
+ }
830
+
796
831
private extension NSURLSessionTask {
797
832
enum Body {
798
833
case none
0 commit comments