Skip to content

Commit d63ea81

Browse files
committed
Merge pull request #33 from ReactKit/feature/thread-safety
Add _RecursiveLock & _Atomic for thread safety.
2 parents 583b19d + c210b51 commit d63ea81

File tree

5 files changed

+259
-119
lines changed

5 files changed

+259
-119
lines changed

SwiftTask.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
1F4C76A41AD8CF40004E47C1 /* AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5FA35619A374E600975FB9 /* AlamofireTests.swift */; };
1717
1F4C76A51AD8CF41004E47C1 /* AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5FA35619A374E600975FB9 /* AlamofireTests.swift */; };
1818
1F6A8CA319A4E4F200369A5D /* SwiftTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEE3199EDF1000F97868 /* SwiftTaskTests.swift */; };
19+
1FB3B8F31B0A194F00F78900 /* _Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB3B8F21B0A194F00F78900 /* _Atomic.swift */; };
20+
1FB3B8F41B0A194F00F78900 /* _Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB3B8F21B0A194F00F78900 /* _Atomic.swift */; };
1921
1FCF71121AD8CD2B007079C2 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71111AD8CD2B007079C2 /* Alamofire.framework */; };
2022
1FCF71141AD8CD2F007079C2 /* Async.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71131AD8CD2F007079C2 /* Async.framework */; };
2123
1FCF71161AD8CD38007079C2 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71151AD8CD38007079C2 /* Alamofire.framework */; };
@@ -31,6 +33,8 @@
3133
48511C5B19C17563002FE03C /* RetainCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48511C5A19C17563002FE03C /* RetainCycleTests.swift */; };
3234
485C31F11A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */; };
3335
485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */; };
36+
487858241B09701F0022E56A /* _RecursiveLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487858231B09701F0022E56A /* _RecursiveLock.swift */; };
37+
487858251B09701F0022E56A /* _RecursiveLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487858231B09701F0022E56A /* _RecursiveLock.swift */; };
3438
48A1E8221A366F9C007619EB /* SwiftTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F46DED4199EDF1000F97868 /* SwiftTask.framework */; };
3539
48A1E8231A366FA8007619EB /* SwiftTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48CD5A0C19AEE3570042B9F1 /* SwiftTask.framework */; };
3640
48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */; };
@@ -51,6 +55,7 @@
5155
1F46DEFA199EDF8100F97868 /* SwiftTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftTask.swift; sourceTree = "<group>"; };
5256
1F46DEFC199EE2C200F97868 /* _TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _TestCase.swift; sourceTree = "<group>"; };
5357
1F5FA35619A374E600975FB9 /* AlamofireTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlamofireTests.swift; sourceTree = "<group>"; };
58+
1FB3B8F21B0A194F00F78900 /* _Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Atomic.swift; sourceTree = "<group>"; };
5459
1FCF71111AD8CD2B007079C2 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "../Carthage/Checkouts/Alamofire/build/Debug-iphoneos/Alamofire.framework"; sourceTree = "<group>"; };
5560
1FCF71131AD8CD2F007079C2 /* Async.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Async.framework; path = "../Carthage/Checkouts/Async/build/Debug-iphoneos/Async.framework"; sourceTree = "<group>"; };
5661
1FCF71151AD8CD38007079C2 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = ../Carthage/Checkouts/Alamofire/build/Debug/Alamofire.framework; sourceTree = "<group>"; };
@@ -60,6 +65,7 @@
6065
4822F0D019D00ABF00F5F572 /* SwiftTask-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftTask-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
6166
48511C5A19C17563002FE03C /* RetainCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetainCycleTests.swift; sourceTree = "<group>"; };
6267
485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeInferenceTests.swift; sourceTree = "<group>"; };
68+
487858231B09701F0022E56A /* _RecursiveLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _RecursiveLock.swift; sourceTree = "<group>"; };
6369
48B58D7A1A6F255E0068E18C /* _StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _StateMachine.swift; sourceTree = "<group>"; };
6470
48CD5A0C19AEE3570042B9F1 /* SwiftTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftTask.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6571
/* End PBXFileReference section */
@@ -129,6 +135,8 @@
129135
1F46DEFA199EDF8100F97868 /* SwiftTask.swift */,
130136
1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */,
131137
48B58D7A1A6F255E0068E18C /* _StateMachine.swift */,
138+
487858231B09701F0022E56A /* _RecursiveLock.swift */,
139+
1FB3B8F21B0A194F00F78900 /* _Atomic.swift */,
132140
1F46DED7199EDF1000F97868 /* Supporting Files */,
133141
);
134142
path = SwiftTask;
@@ -345,6 +353,8 @@
345353
1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */,
346354
1FD7197B1AFE387C00BC38C4 /* Cancellable.swift in Sources */,
347355
48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */,
356+
1FB3B8F31B0A194F00F78900 /* _Atomic.swift in Sources */,
357+
487858241B09701F0022E56A /* _RecursiveLock.swift in Sources */,
348358
);
349359
runOnlyForDeploymentPostprocessing = 0;
350360
};
@@ -385,6 +395,8 @@
385395
48CD5A3C19AEEBDF0042B9F1 /* SwiftTask.swift in Sources */,
386396
1FD7197C1AFE387C00BC38C4 /* Cancellable.swift in Sources */,
387397
48B58D7C1A6F255E0068E18C /* _StateMachine.swift in Sources */,
398+
1FB3B8F41B0A194F00F78900 /* _Atomic.swift in Sources */,
399+
487858251B09701F0022E56A /* _RecursiveLock.swift in Sources */,
388400
);
389401
runOnlyForDeploymentPostprocessing = 0;
390402
};

SwiftTask/SwiftTask.swift

Lines changed: 62 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
8484
internal let _paused: Bool
8585
internal var _initClosure: _InitClosure! // retained throughout task's lifetime
8686

87-
public var state: TaskState { return self._machine.state }
87+
public var state: TaskState { return self._machine.state.rawValue }
8888

8989
/// progress value (NOTE: always nil when `weakified = true`)
90-
public var progress: Progress? { return self._machine.progress }
90+
public var progress: Progress? { return self._machine.progress.rawValue }
9191

9292
/// fulfilled value
93-
public var value: Value? { return self._machine.value }
93+
public var value: Value? { return self._machine.value.rawValue }
9494

9595
/// rejected/cancelled tuple info
96-
public var errorInfo: ErrorInfo? { return self._machine.errorInfo }
96+
public var errorInfo: ErrorInfo? { return self._machine.errorInfo.rawValue }
9797

9898
public var name: String = "DefaultTask"
9999

@@ -424,7 +424,7 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
424424
let selfMachine = self._machine
425425

426426
self._then(&canceller) {
427-
let innerTask = thenClosure(selfMachine.value, selfMachine.errorInfo)
427+
let innerTask = thenClosure(selfMachine.value.rawValue, selfMachine.errorInfo.rawValue)
428428
_bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure)
429429
}
430430

@@ -484,11 +484,11 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
484484

485485
// NOTE: using `self._then()` + `selfMachine` instead of `self.then()` will reduce Task allocation
486486
self._then(&canceller) {
487-
if let value = selfMachine.value {
487+
if let value = selfMachine.value.rawValue {
488488
let innerTask = successClosure(value)
489489
_bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure)
490490
}
491-
else if let errorInfo = selfMachine.errorInfo {
491+
else if let errorInfo = selfMachine.errorInfo.rawValue {
492492
_reject(errorInfo)
493493
}
494494
}
@@ -534,10 +534,10 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
534534
let selfMachine = self._machine
535535

536536
self._then(&canceller) {
537-
if let value = selfMachine.value {
537+
if let value = selfMachine.value.rawValue {
538538
fulfill(value)
539539
}
540-
else if let errorInfo = selfMachine.errorInfo {
540+
else if let errorInfo = selfMachine.errorInfo.rawValue {
541541
let innerTask = failureClosure(errorInfo)
542542
_bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure)
543543
}
@@ -617,10 +617,10 @@ internal func _bindInnerTask<Progress2, Value2, Error>(
617617
configure.cancel = { innerTask.cancel(); return }
618618

619619
// pause/cancel innerTask if descendant task is already paused/cancelled
620-
if newMachine.state == .Paused {
620+
if newMachine.state.rawValue == .Paused {
621621
innerTask.pause()
622622
}
623-
else if newMachine.state == .Cancelled {
623+
else if newMachine.state.rawValue == .Cancelled {
624624
innerTask.cancel()
625625
}
626626
}
@@ -637,36 +637,37 @@ extension Task
637637

638638
var completedCount = 0
639639
let totalCount = tasks.count
640+
let lock = _RecursiveLock()
640641

641642
for task in tasks {
642643
task.success { (value: Value) -> Void in
643644

644-
synchronized(self) {
645-
completedCount++
646-
647-
let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount)
648-
progress(progressTuple)
645+
lock.lock()
646+
completedCount++
647+
648+
let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount)
649+
progress(progressTuple)
650+
651+
if completedCount == totalCount {
652+
var values: [Value] = Array()
649653

650-
if completedCount == totalCount {
651-
var values: [Value] = Array()
652-
653-
for task in tasks {
654-
values.append(task.value!)
655-
}
656-
657-
fulfill(values)
654+
for task in tasks {
655+
values.append(task.value!)
658656
}
657+
658+
fulfill(values)
659659
}
660+
lock.unlock()
660661

661662
}.failure { (errorInfo: ErrorInfo) -> Void in
662663

663-
synchronized(self) {
664-
_reject(errorInfo)
665-
666-
for task in tasks {
667-
task.cancel()
668-
}
664+
lock.lock()
665+
_reject(errorInfo)
666+
667+
for task in tasks {
668+
task.cancel()
669669
}
670+
lock.unlock()
670671
}
671672
}
672673

@@ -684,32 +685,33 @@ extension Task
684685
var completedCount = 0
685686
var rejectedCount = 0
686687
let totalCount = tasks.count
688+
let lock = _RecursiveLock()
687689

688690
for task in tasks {
689691
task.success { (value: Value) -> Void in
690692

691-
synchronized(self) {
692-
completedCount++
693+
lock.lock()
694+
completedCount++
695+
696+
if completedCount == 1 {
697+
fulfill(value)
693698

694-
if completedCount == 1 {
695-
fulfill(value)
696-
697-
self.cancelAll(tasks)
698-
}
699+
self.cancelAll(tasks)
699700
}
701+
lock.unlock()
700702

701703
}.failure { (errorInfo: ErrorInfo) -> Void in
702704

703-
synchronized(self) {
704-
rejectedCount++
705+
lock.lock()
706+
rejectedCount++
707+
708+
if rejectedCount == totalCount {
709+
var isAnyCancelled = (tasks.filter { task in task.state == .Cancelled }.count > 0)
705710

706-
if rejectedCount == totalCount {
707-
var isAnyCancelled = (tasks.filter { task in task.state == .Cancelled }.count > 0)
708-
709-
let errorInfo = ErrorInfo(error: nil, isCancelled: isAnyCancelled) // NOTE: Task.any error returns nil (spec)
710-
_reject(errorInfo)
711-
}
711+
let errorInfo = ErrorInfo(error: nil, isCancelled: isAnyCancelled) // NOTE: Task.any error returns nil (spec)
712+
_reject(errorInfo)
712713
}
714+
lock.unlock()
713715
}
714716
}
715717

@@ -728,28 +730,29 @@ extension Task
728730

729731
var completedCount = 0
730732
let totalCount = tasks.count
733+
let lock = _RecursiveLock()
731734

732735
for task in tasks {
733736
task.then { (value: Value?, errorInfo: ErrorInfo?) -> Void in
734737

735-
synchronized(self) {
736-
completedCount++
737-
738-
let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount)
739-
progress(progressTuple)
738+
lock.lock()
739+
completedCount++
740+
741+
let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount)
742+
progress(progressTuple)
743+
744+
if completedCount == totalCount {
745+
var values: [Value] = Array()
740746

741-
if completedCount == totalCount {
742-
var values: [Value] = Array()
743-
744-
for task in tasks {
745-
if task.state == .Fulfilled {
746-
values.append(task.value!)
747-
}
747+
for task in tasks {
748+
if task.state == .Fulfilled {
749+
values.append(task.value!)
748750
}
749-
750-
fulfill(values)
751751
}
752+
753+
fulfill(values)
752754
}
755+
lock.unlock()
753756

754757
}
755758
}
@@ -795,15 +798,4 @@ infix operator ~ { associativity left }
795798
public func ~ <P, V, E>(task: Task<P, V, E>, tryCount: Int) -> Task<P, V, E>
796799
{
797800
return task.try(tryCount)
798-
}
799-
800-
//--------------------------------------------------
801-
// MARK: - Utility
802-
//--------------------------------------------------
803-
804-
internal func synchronized(object: AnyObject, closure: Void -> Void)
805-
{
806-
objc_sync_enter(object)
807-
closure()
808-
objc_sync_exit(object)
809801
}

SwiftTask/_Atomic.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// _Atomic.swift
3+
// SwiftTask
4+
//
5+
// Created by Yasuhiro Inami on 2015/05/18.
6+
// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved.
7+
//
8+
9+
import Darwin
10+
11+
internal final class _Atomic<T>
12+
{
13+
private var spinlock = OS_SPINLOCK_INIT
14+
private var _rawValue: T
15+
16+
internal var rawValue: T
17+
{
18+
get {
19+
lock()
20+
let rawValue = self._rawValue
21+
unlock()
22+
23+
return rawValue
24+
}
25+
26+
set(newValue) {
27+
lock()
28+
self._rawValue = newValue
29+
unlock()
30+
}
31+
}
32+
33+
init(_ rawValue: T)
34+
{
35+
self._rawValue = rawValue
36+
}
37+
38+
private func lock()
39+
{
40+
withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock)
41+
}
42+
43+
private func unlock()
44+
{
45+
withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock)
46+
}
47+
}

SwiftTask/_RecursiveLock.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// _RecursiveLock.swift
3+
// SwiftTask
4+
//
5+
// Created by Yasuhiro Inami on 2015/05/18.
6+
// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved.
7+
//
8+
9+
import Darwin
10+
11+
internal final class _RecursiveLock
12+
{
13+
private let mutex: UnsafeMutablePointer<pthread_mutex_t>
14+
private let attribute: UnsafeMutablePointer<pthread_mutexattr_t>
15+
16+
internal init()
17+
{
18+
self.mutex = UnsafeMutablePointer.alloc(sizeof(pthread_mutex_t))
19+
self.attribute = UnsafeMutablePointer.alloc(sizeof(pthread_mutexattr_t))
20+
21+
pthread_mutexattr_init(self.attribute)
22+
pthread_mutexattr_settype(self.attribute, PTHREAD_MUTEX_RECURSIVE)
23+
pthread_mutex_init(self.mutex, self.attribute)
24+
}
25+
26+
deinit
27+
{
28+
pthread_mutexattr_destroy(self.attribute)
29+
pthread_mutex_destroy(self.mutex)
30+
}
31+
32+
internal func lock()
33+
{
34+
pthread_mutex_lock(self.mutex)
35+
}
36+
37+
internal func unlock()
38+
{
39+
pthread_mutex_unlock(self.mutex)
40+
}
41+
}

0 commit comments

Comments
 (0)