Skip to content

Commit 14f0c8a

Browse files
authored
Merge pull request #2779 from readdle/operation-queue-overrelease
Balance OperationQueue retain/release
2 parents c971e6f + 6257faa commit 14f0c8a

File tree

2 files changed

+26
-11
lines changed

2 files changed

+26
-11
lines changed

Sources/Foundation/Operation.swift

+8-3
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ open class Operation : NSObject {
125125
internal var _queue: OperationQueue? {
126126
_lock()
127127
defer { _unlock() }
128-
return __queue?.takeRetainedValue()
128+
return __queue?.takeUnretainedValue()
129129
}
130130

131131
internal func _adopt(queue: OperationQueue, schedule: DispatchWorkItem) {
@@ -230,7 +230,7 @@ open class Operation : NSObject {
230230
let kind: Transition?
231231
if keyPath == _NSOperationIsFinished || keyPath == _NSOperationIsFinishedAlternate {
232232
kind = .toFinished
233-
} else if keyPath == _NSOperationIsExecuting || keyPath == _NSOperationIsReadyAlternate {
233+
} else if keyPath == _NSOperationIsExecuting || keyPath == _NSOperationIsExecutingAlternate {
234234
kind = .toExecuting
235235
} else if keyPath == _NSOperationIsReady || keyPath == _NSOperationIsReadyAlternate {
236236
kind = .toReady
@@ -474,7 +474,7 @@ open class Operation : NSObject {
474474

475475
internal func changePriority(_ newPri: Operation.QueuePriority.RawValue) {
476476
_lock()
477-
guard let oq = __queue?.takeRetainedValue() else {
477+
guard let oq = __queue?.takeUnretainedValue() else {
478478
__priorityValue = newPri
479479
_unlock()
480480
return
@@ -963,6 +963,11 @@ open class OperationQueue : NSObject, ProgressReporting {
963963
OperationQueue._currentQueue.set(self)
964964
op.start()
965965
OperationQueue._currentQueue.clear()
966+
// We've just cleared _currentQueue storage.
967+
// NSThreadSpecific doesn't release stored value on clear.
968+
// This means `self` will leak if we don't release manually.
969+
Unmanaged.passUnretained(self).release()
970+
966971
// unset current tsd
967972
if op.isFinished && op._state.rawValue < Operation.__NSOperationState.finishing.rawValue {
968973
Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: op)

Tests/Foundation/Tests/TestOperationQueue.swift

+18-8
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,17 @@ class TestOperationQueue : XCTestCase {
2828
("test_isSuspended", test_isSuspended),
2929
("test_OperationDependencyCount", test_OperationDependencyCount),
3030
("test_CancelDependency", test_CancelDependency),
31-
/* ⚠️ */ ("test_Deadlock", testExpectedToFail(test_Deadlock, "Crashes due to overrelease of OperationQueue")),
31+
("test_Deadlock", test_Deadlock),
3232
("test_CancelOutOfQueue", test_CancelOutOfQueue),
33-
/* ⚠️ */ ("test_CrossQueueDependency", testExpectedToFail(test_CrossQueueDependency, "Crashes due to overrelease of OperationQueue")),
33+
("test_CrossQueueDependency", test_CrossQueueDependency),
3434
("test_CancelWhileSuspended", test_CancelWhileSuspended),
3535
("test_OperationOrder", test_OperationOrder),
3636
("test_OperationOrder2", test_OperationOrder2),
37-
/* ⚠️ */ ("test_WaitUntilFinished", testExpectedToFail(test_WaitUntilFinished, "Crashes due to overrelease of OperationQueue")),
37+
("test_WaitUntilFinished", test_WaitUntilFinished),
3838
("test_OperationWaitUntilFinished", test_OperationWaitUntilFinished),
39-
/* ⚠️ */ ("test_CustomOperationReady", testExpectedToFail(test_CustomOperationReady, "Crashes due to overrelease of OperationQueue")),
40-
/* ⚠️ */ ("test_DependencyCycleBreak", testExpectedToFail(test_DependencyCycleBreak, "Crashes due to overrelease of OperationQueue")),
41-
/* ⚠️ */ ("test_Lifecycle", testExpectedToFail(test_Lifecycle, "Crashes due to overrelease of OperationQueue")),
39+
("test_CustomOperationReady", test_CustomOperationReady),
40+
("test_DependencyCycleBreak", test_DependencyCycleBreak),
41+
("test_Lifecycle", test_Lifecycle),
4242
]
4343
}
4444

@@ -55,7 +55,12 @@ class TestOperationQueue : XCTestCase {
5555
queue.addOperation(op2)
5656
queue.addOperation(op3)
5757
XCTAssertEqual(queue.operationCount, 2)
58-
XCTAssertEqual(queue.operations.count, 2)
58+
let operations = queue.operations
59+
XCTAssertEqual(operations.count, 2)
60+
if (operations.count == 2) {
61+
XCTAssertEqual(operations[0], op2)
62+
XCTAssertEqual(operations[1], op3)
63+
}
5964
queue.waitUntilAllOperationsAreFinished()
6065
XCTAssertEqual(queue.operationCount, 0)
6166
XCTAssertEqual(queue.operations.count, 0)
@@ -547,6 +552,11 @@ class TestOperationQueue : XCTestCase {
547552
let op1 = BlockOperation { Thread.sleep(forTimeInterval: 1) }
548553
queue1.addOperation(op1)
549554
op1.waitUntilFinished()
555+
556+
// Operation is not removed from Queue simultaneously
557+
// with transitioning to "Finished" state. Wait a bit
558+
// to allow OperationQueue to deal with finished op.
559+
Thread.sleep(forTimeInterval: 0.1)
550560
XCTAssertEqual(queue1.operationCount, 0)
551561
}
552562

@@ -656,7 +666,7 @@ class TestOperationQueue : XCTestCase {
656666
}()
657667

658668
wait(for: [opStarted], timeout: 1)
659-
op2.cancel() // op2
669+
op2.cancel()
660670
wait(for: [opDone], timeout: 1)
661671

662672
Thread.sleep(forTimeInterval: 1) // Let queue to be deallocated

0 commit comments

Comments
 (0)