Skip to content

Commit fdb1118

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 b1ed91d commit fdb1118

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
@@ -40,6 +40,8 @@ class TestOperationQueue : XCTestCase {
4040
("test_CustomOperationReady", test_CustomOperationReady),
4141
("test_DependencyCycleBreak", test_DependencyCycleBreak),
4242
("test_Lifecycle", test_Lifecycle),
43+
("test_ConcurrentOperations", test_ConcurrentOperations),
44+
("test_ConcurrentOperationsWithDependenciesAndCompletions", test_ConcurrentOperationsWithDependenciesAndCompletions),
4345
]
4446
}
4547

@@ -699,6 +701,58 @@ class TestOperationQueue : XCTestCase {
699701
Thread.sleep(forTimeInterval: 1) // Let queue to be deallocated
700702
XCTAssertNil(weakQueue, "Queue should be deallocated at this point")
701703
}
704+
705+
func test_ConcurrentOperations() {
706+
let queue = OperationQueue()
707+
queue.maxConcurrentOperationCount = 2
708+
709+
// Running several iterations helps to reveal use-after-dealloc crashes
710+
for _ in 0..<3 {
711+
let didRunOp1 = expectation(description: "Did run first operation")
712+
let didRunOp2 = expectation(description: "Did run second operation")
713+
714+
queue.addOperation {
715+
self.wait(for: [didRunOp2], timeout: 0.2)
716+
didRunOp1.fulfill()
717+
}
718+
queue.addOperation {
719+
didRunOp2.fulfill()
720+
}
721+
722+
self.wait(for: [didRunOp1], timeout: 0.3)
723+
}
724+
}
725+
726+
func test_ConcurrentOperationsWithDependenciesAndCompletions() {
727+
let queue = OperationQueue()
728+
queue.maxConcurrentOperationCount = 2
729+
730+
// Running several iterations helps to reveal use-after-dealloc crashes
731+
for _ in 0..<3 {
732+
let didRunOp1 = expectation(description: "Did run first operation")
733+
let didRunOp1Completion = expectation(description: "Did run first operation completion")
734+
let didRunOp1Dependency = expectation(description: "Did run first operation dependency")
735+
let didRunOp2 = expectation(description: "Did run second operation")
736+
737+
let op1 = BlockOperation {
738+
self.wait(for: [didRunOp1Dependency, didRunOp2], timeout: 0.2)
739+
didRunOp1.fulfill()
740+
}
741+
op1.completionBlock = {
742+
didRunOp1Completion.fulfill()
743+
}
744+
let op1Dependency = BlockOperation {
745+
didRunOp1Dependency.fulfill()
746+
}
747+
queue.addOperations([op1, op1Dependency], waitUntilFinished: false)
748+
queue.addOperation {
749+
didRunOp2.fulfill()
750+
}
751+
752+
self.wait(for: [didRunOp1, didRunOp1Completion], timeout: 0.3)
753+
}
754+
}
755+
702756
}
703757

704758
class AsyncOperation: Operation {

0 commit comments

Comments
 (0)