Skip to content

Commit 51b6e66

Browse files
SR-8572: Rework OperationQueue.isSuspended to stop suspending underlying DispatchQueue
- Suspending the underlying DispatchQueue causes issues when an app needs to use the same DispatchQueue for scheduling work from multiple OperationQueue's and control which OperationQueue's are suspended or not
1 parent e65848b commit 51b6e66

File tree

2 files changed

+45
-19
lines changed

2 files changed

+45
-19
lines changed

Foundation/Operation.swift

+21-19
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ open class OperationQueue: NSObject {
326326
}
327327
}
328328
let queueGroup = DispatchGroup()
329+
var unscheduledWorkItems: [DispatchWorkItem] = []
329330
#endif
330331

331332
var _operations = _OperationList()
@@ -364,9 +365,6 @@ open class OperationQueue: NSObject {
364365
}
365366
}
366367
let queue = DispatchQueue(label: effectiveName, attributes: attr)
367-
if _suspended {
368-
queue.suspend()
369-
}
370368
__underlyingQueue = queue
371369
lock.unlock()
372370
return queue
@@ -432,13 +430,13 @@ open class OperationQueue: NSObject {
432430
_operations.insert(operation)
433431
}
434432
lock.unlock()
435-
ops.forEach { (operation: Operation) -> Void in
436433
#if DEPLOYMENT_ENABLE_LIBDISPATCH
434+
let items = ops.map { (operation: Operation) -> DispatchWorkItem in
437435
if let group = waitGroup {
438436
group.enter()
439437
}
440438

441-
let block = DispatchWorkItem(flags: .enforceQoS) { () -> Void in
439+
return DispatchWorkItem(flags: .enforceQoS) { () -> Void in
442440
if let sema = self._concurrencyGate {
443441
sema.wait()
444442
self._runOperation()
@@ -450,10 +448,17 @@ open class OperationQueue: NSObject {
450448
group.leave()
451449
}
452450
}
453-
_underlyingQueue.async(group: queueGroup, execute: block)
454-
#endif
455451
}
456-
#if DEPLOYMENT_ENABLE_LIBDISPATCH
452+
453+
let queue = _underlyingQueue
454+
lock.lock()
455+
if _suspended {
456+
unscheduledWorkItems += items
457+
} else {
458+
items.forEach { queue.async(group: queueGroup, execute: $0) }
459+
}
460+
lock.unlock()
461+
457462
if let group = waitGroup {
458463
group.wait()
459464
}
@@ -498,19 +503,16 @@ open class OperationQueue: NSObject {
498503
}
499504
set {
500505
lock.lock()
501-
if _suspended != newValue {
502-
_suspended = newValue
503-
#if DEPLOYMENT_ENABLE_LIBDISPATCH
504-
if let queue = __underlyingQueue {
505-
if newValue {
506-
queue.suspend()
507-
} else {
508-
queue.resume()
509-
}
506+
_suspended = newValue
507+
let items = unscheduledWorkItems
508+
unscheduledWorkItems.removeAll()
509+
lock.unlock()
510+
511+
if !newValue {
512+
items.forEach {
513+
_underlyingQueue.async(group: queueGroup, execute: $0)
510514
}
511-
#endif
512515
}
513-
lock.unlock()
514516
}
515517
}
516518

TestFoundation/TestOperationQueue.swift

+24
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class TestOperationQueue : XCTestCase {
2222
("test_CurrentQueueOnBackgroundQueueWithSelfCancel", test_CurrentQueueOnBackgroundQueueWithSelfCancel),
2323
("test_CurrentQueueWithCustomUnderlyingQueue", test_CurrentQueueWithCustomUnderlyingQueue),
2424
("test_CurrentQueueWithUnderlyingQueueResetToNil", test_CurrentQueueWithUnderlyingQueueResetToNil),
25+
("test_isSuspended", test_isSuspended),
2526
]
2627
}
2728

@@ -155,6 +156,29 @@ class TestOperationQueue : XCTestCase {
155156
waitForExpectations(timeout: 1)
156157
}
157158

159+
func test_isSuspended() {
160+
let expectation1 = self.expectation(description: "DispatchQueue execution")
161+
let expectation2 = self.expectation(description: "OperationQueue execution")
162+
163+
let dispatchQueue = DispatchQueue(label: "underlying_queue")
164+
let operationQueue = OperationQueue()
165+
operationQueue.maxConcurrentOperationCount = 1
166+
operationQueue.underlyingQueue = dispatchQueue
167+
operationQueue.isSuspended = true
168+
169+
operationQueue.addOperation {
170+
XCTAssert(OperationQueue.current?.underlyingQueue === dispatchQueue)
171+
expectation2.fulfill()
172+
}
173+
174+
dispatchQueue.async {
175+
operationQueue.isSuspended = false
176+
expectation1.fulfill()
177+
}
178+
179+
waitForExpectations(timeout: 1)
180+
}
181+
158182
func test_CurrentQueueWithUnderlyingQueueResetToNil() {
159183
let expectation = self.expectation(description: "Background execution")
160184

0 commit comments

Comments
 (0)