ProcedureKit: Completion block isn't called in ~0.1% of cases when a condition is attached.

If you have a operation and prevent it from running by a failed condition, the completion callback isn’t always called. Try the following with an iOS test target:

    func test__multiple_operations_completion_blocks() {

        let q = OperationQueue()
        (0...5000).forEach { i in
            let e = expectationWithDescription("wait\(i)")
            let operation = BlockOperation(block: { _ in XCTFail() })
            operation.addCompletionBlock({ e.fulfill() })
            let condition = BlockCondition(block: { false })
            operation.addCondition(condition)
            q.addOperation(operation)
        }
        waitForExpectationsWithTimeout(8, handler: nil)

    }

I see about 1-10 expections unfulfilled. Do I use conditions the wrong way?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 18 (17 by maintainers)

Commits related to this issue

Most upvoted comments

This is now fixed in development 😀

A massive thanks to @pronebird who did the bulk of the work in actually figuring out the issue was due to isReady getting overridden.

The change, which refactors operation conditions as Condition an Operation and executed as a dependency may require some refactoring. Operation will accept the old style OperationCondition but you will find that dependencies for the condition are added / queried when the condition is attached, which is different than from before.

@pronebird okay, so, it looks like adding some asynchronous logic around ready causes issues - either due to conditions (asynchronous evaluator) or mutual exclusivity (asynchronous exclusion lock). Will try to find the time to debug it more this week.

I made a sample project that somewhat resembles a very simple version of Operations.

https://github.com/pronebird/NSOperationReproducingRaceCondition

There is no problem as long as operation transitions to .Ready once super.isReady turns true. However, everything breaks if operation transitions to .Ready anytime later (think evaluator).

That seems to be the case with Operations too, because Operations switches to .Ready from within isReady if there are no any conditions set. I just ran a long test without any conditions and everything works fine.

Besides, I looked at __NSOperationInternal internals and I don’t see any thread synchronization there except when they extract the reference to NSOperationQueue assigned to operation.