Skip to content

Commit 180f4c0

Browse files
committed
Fix concurrent operations execution in OperationQueue
To be able to run operations concurrently OperationQueue needs concurrent underlying DispatchQueue. This matches with Darwin.
1 parent c285c96 commit 180f4c0

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

Sources/Foundation/Operation.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -938,15 +938,15 @@ open class OperationQueue : NSObject, ProgressReporting {
938938
let queue: DispatchQueue
939939
if let qos = _propertyQoS {
940940
if let name = __name {
941-
queue = DispatchQueue(label: name, qos: qos.qosClass)
941+
queue = DispatchQueue(label: name, qos: qos.qosClass, attributes: .concurrent)
942942
} else {
943-
queue = DispatchQueue(label: "NSOperationQueue \(Unmanaged.passUnretained(self).toOpaque())", qos: qos.qosClass)
943+
queue = DispatchQueue(label: "NSOperationQueue \(Unmanaged.passUnretained(self).toOpaque())", qos: qos.qosClass, attributes: .concurrent)
944944
}
945945
} else {
946946
if let name = __name {
947-
queue = DispatchQueue(label: name)
947+
queue = DispatchQueue(label: name, attributes: .concurrent)
948948
} else {
949-
queue = DispatchQueue(label: "NSOperationQueue \(Unmanaged.passUnretained(self).toOpaque())")
949+
queue = DispatchQueue(label: "NSOperationQueue \(Unmanaged.passUnretained(self).toOpaque())", attributes: .concurrent)
950950
}
951951
}
952952
__backingQueue = queue

Tests/Foundation/Tests/TestOperationQueue.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class TestOperationQueue : XCTestCase {
3939
("test_CustomOperationReady", test_CustomOperationReady),
4040
("test_DependencyCycleBreak", test_DependencyCycleBreak),
4141
("test_Lifecycle", test_Lifecycle),
42+
("test_ConcurrentOperations", test_ConcurrentOperations),
43+
("test_ConcurrentOperationsWithDependenciesAndCompletions", test_ConcurrentOperationsWithDependenciesAndCompletions),
4244
]
4345
}
4446

@@ -672,6 +674,58 @@ class TestOperationQueue : XCTestCase {
672674
Thread.sleep(forTimeInterval: 1) // Let queue to be deallocated
673675
XCTAssertNil(weakQueue, "Queue should be deallocated at this point")
674676
}
677+
678+
func test_ConcurrentOperations() {
679+
let queue = OperationQueue()
680+
queue.maxConcurrentOperationCount = 2
681+
682+
// Running several iterations helps to reveal use-after-dealloc crashes
683+
for _ in 0..<3 {
684+
let didRunOp1 = expectation(description: "Did run first operation")
685+
let didRunOp2 = expectation(description: "Did run second operation")
686+
687+
queue.addOperation {
688+
self.wait(for: [didRunOp2], timeout: 0.2)
689+
didRunOp1.fulfill()
690+
}
691+
queue.addOperation {
692+
didRunOp2.fulfill()
693+
}
694+
695+
self.wait(for: [didRunOp1], timeout: 0.3)
696+
}
697+
}
698+
699+
func test_ConcurrentOperationsWithDependenciesAndCompletions() {
700+
let queue = OperationQueue()
701+
queue.maxConcurrentOperationCount = 2
702+
703+
// Running several iterations helps to reveal use-after-dealloc crashes
704+
for _ in 0..<3 {
705+
let didRunOp1 = expectation(description: "Did run first operation")
706+
let didRunOp1Completion = expectation(description: "Did run first operation completion")
707+
let didRunOp1Dependency = expectation(description: "Did run first operation dependency")
708+
let didRunOp2 = expectation(description: "Did run second operation")
709+
710+
let op1 = BlockOperation {
711+
self.wait(for: [didRunOp1Dependency, didRunOp2], timeout: 0.2)
712+
didRunOp1.fulfill()
713+
}
714+
op1.completionBlock = {
715+
didRunOp1Completion.fulfill()
716+
}
717+
let op1Dependency = BlockOperation {
718+
didRunOp1Dependency.fulfill()
719+
}
720+
queue.addOperations([op1, op1Dependency], waitUntilFinished: false)
721+
queue.addOperation {
722+
didRunOp2.fulfill()
723+
}
724+
725+
self.wait(for: [didRunOp1, didRunOp1Completion], timeout: 0.3)
726+
}
727+
}
728+
675729
}
676730

677731
class AsyncOperation: Operation {

0 commit comments

Comments
 (0)