Skip to content

Commit

Permalink
Fix OperationQueue scheduling logic to prevent disruption of operatio…
Browse files Browse the repository at this point in the history
…n lists

I believe the original intention was to adust `__nextPriorityOperation`.
Doing so for `__nextOperation` has no sense and leads to crosslinking
of the main operation list and corresponding priority operation list.
OperationQueue in such state would crash or hang while attempting
to schedule subsequent operation.
  • Loading branch information
lxbndr committed Nov 27, 2020
1 parent 7d521e4 commit 9f44ed3
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Sources/Foundation/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@ open class OperationQueue : NSObject, ProgressReporting {
// if the cached state is possibly not valid then the isReady value needs to be re-updated
if Operation.__NSOperationState.enqueued == operation._state && operation._fetchCachedIsReady(&retest) {
if let previous = prev?.takeUnretainedValue() {
previous.__nextOperation = next
previous.__nextPriorityOperation = next
} else {
_setFirstPriorityOperation(prio, next)
}
Expand Down
27 changes: 27 additions & 0 deletions Tests/Foundation/Tests/TestOperationQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class TestOperationQueue : XCTestCase {
("test_CancelWhileSuspended", test_CancelWhileSuspended),
("test_OperationOrder", test_OperationOrder),
("test_OperationOrder2", test_OperationOrder2),
("test_ExecutionOrder", test_ExecutionOrder),
("test_WaitUntilFinished", test_WaitUntilFinished),
("test_OperationWaitUntilFinished", test_OperationWaitUntilFinished),
("test_CustomOperationReady", test_CustomOperationReady),
Expand Down Expand Up @@ -531,6 +532,32 @@ class TestOperationQueue : XCTestCase {
XCTAssertEqual(array, [5, 4, 3, 2, 1])
}

func test_ExecutionOrder() {
let queue = OperationQueue()

let didRunOp1 = expectation(description: "Did run first operation")
let didRunOp1Dependency = expectation(description: "Did run first operation dependency")
let didRunOp2 = expectation(description: "Did run second operation")
var didRunOp1DependencyFirst = false

let op1 = BlockOperation {
didRunOp1.fulfill()
XCTAssertTrue(didRunOp1DependencyFirst, "Dependency should be executed first")
}
let op1Dependency = BlockOperation {
didRunOp1Dependency.fulfill()
didRunOp1DependencyFirst = true
}
op1.addDependency(op1Dependency)
queue.addOperations([op1, op1Dependency], waitUntilFinished: false)

queue.addOperation {
didRunOp2.fulfill()
}

waitForExpectations(timeout: 1.0)
}

func test_WaitUntilFinished() {
let queue1 = OperationQueue()
let queue2 = OperationQueue()
Expand Down

0 comments on commit 9f44ed3

Please sign in to comment.