Core Data multi-threading assertion #64

Closed
opened 2025-12-29 15:23:26 +01:00 by adam · 8 comments
Owner

Originally created by @ruslanskorb on GitHub (Jun 15, 2016).

Hi John,

I have noticed that if you add the argument -com.apple.CoreData.ConcurrencyDebug 1 to Arguments Passed On Launch and try to invoke the code I provided below NSManagedObjectContext will throw the exception +[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__].

CoreStore.beginSynchronous { (transaction) -> Void in
    let place = transaction.create(Into(Place))
    // ...
}

Apparently, this is because you are creating an object directly in your own transaction queue, not using neither the performBlockAndWait(_:) method nor the performBlock(_:) method of the managed object context.

To fix it you can update the performAndWait() method of SynchronousDataTransaction with the following code:

internal func performAndWait() -> SaveResult? {

    self.transactionQueue.sync {

        self.context.performBlockAndWait({ 

            self.closure(transaction: self)
        })

        if !self.isCommitted && self.hasChanges {

            CoreStore.log(
                .Warning,
                message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
            )
        }
    }
    return self.result
}

And commit(_:), perform() and performAndWait() methods of AsynchronousDataTransaction with the following code:

public func commit(completion: (result: SaveResult) -> Void = { _ in }) {

    CoreStore.assert(
        self.transactionQueue.isCurrentExecutionContext(),
        "Attempted to commit a \(typeName(self)) outside its designated queue."
    )
    CoreStore.assert(
        !self.isCommitted,
        "Attempted to commit a \(typeName(self)) more than once."
    )

    self.isCommitted = true

    self.context.saveAsynchronouslyWithCompletion { (result) -> Void in

        self.result = result
        completion(result: result)
    }
}
internal func perform() {

    self.transactionQueue.async {

        self.context.performBlock({

            self.closure(transaction: self)
            if !self.isCommitted && self.hasChanges {

            CoreStore.log(
                .Warning,
                message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
            )
        }
        })
    }
}
internal func performAndWait() -> SaveResult? {

    self.transactionQueue.sync {

        self.context.performBlockAndWait({ 

            self.closure(transaction: self)
        })

        if !self.isCommitted && self.hasChanges {

            CoreStore.log(
                .Warning,
                message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
            )
        }
    }
    return self.result
}
Originally created by @ruslanskorb on GitHub (Jun 15, 2016). Hi John, I have noticed that if you add the argument `-com.apple.CoreData.ConcurrencyDebug 1` to **Arguments Passed On Launch** and try to invoke the code I provided below `NSManagedObjectContext` will throw the exception `+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__]`. ``` swift CoreStore.beginSynchronous { (transaction) -> Void in let place = transaction.create(Into(Place)) // ... } ``` Apparently, this is because you are creating an object directly in your own transaction queue, not using neither the `performBlockAndWait(_:)` method nor the `performBlock(_:)` method of the managed object context. To fix it you can update the `performAndWait()` method of `SynchronousDataTransaction` with the following code: ``` swift internal func performAndWait() -> SaveResult? { self.transactionQueue.sync { self.context.performBlockAndWait({ self.closure(transaction: self) }) if !self.isCommitted && self.hasChanges { CoreStore.log( .Warning, message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded." ) } } return self.result } ``` And `commit(_:)`, `perform()` and `performAndWait()` methods of `AsynchronousDataTransaction` with the following code: ``` swift public func commit(completion: (result: SaveResult) -> Void = { _ in }) { CoreStore.assert( self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue." ) CoreStore.assert( !self.isCommitted, "Attempted to commit a \(typeName(self)) more than once." ) self.isCommitted = true self.context.saveAsynchronouslyWithCompletion { (result) -> Void in self.result = result completion(result: result) } } ``` ``` swift internal func perform() { self.transactionQueue.async { self.context.performBlock({ self.closure(transaction: self) if !self.isCommitted && self.hasChanges { CoreStore.log( .Warning, message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded." ) } }) } } ``` ``` swift internal func performAndWait() -> SaveResult? { self.transactionQueue.sync { self.context.performBlockAndWait({ self.closure(transaction: self) }) if !self.isCommitted && self.hasChanges { CoreStore.log( .Warning, message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded." ) } } return self.result } ```
adam closed this issue 2025-12-29 15:23:26 +01:00
Author
Owner

@ruslanskorb commented on GitHub (Jun 15, 2016):

If I'm right about that I'll be happy to create a PR :-]

@ruslanskorb commented on GitHub (Jun 15, 2016): If I'm right about that I'll be happy to create a PR :-]
Author
Owner

@JohnEstropia commented on GitHub (Jun 15, 2016):

This has been reported here and here.

As I mentioned in those threads, you don't need to turn the com.apple.CoreData.ConcurrencyDebug argument on because CoreStore is strict with its own queueing mechanism.

@JohnEstropia commented on GitHub (Jun 15, 2016): This has been reported [here](https://github.com/JohnEstropia/CoreStore/issues/28) and [here](https://github.com/JohnEstropia/CoreStore/issues/60). As I mentioned in those threads, you don't need to turn the `com.apple.CoreData.ConcurrencyDebug` argument on because CoreStore is strict with its own queueing mechanism.
Author
Owner

@ruslanskorb commented on GitHub (Jun 15, 2016):

@JohnEstropia Sorry that I did not look for it in the already closed tasks.
But can you please explain why you don't want to use performBlock(_:) / performBlockAndWait(_:) in your own perform() and performAndWait() methods that will allow to turn the com.apple.CoreData.ConcurrencyDebug argument on?
As they say Better safe than sorry :-]

@ruslanskorb commented on GitHub (Jun 15, 2016): @JohnEstropia Sorry that I did not look for it in the already closed tasks. But can you please explain why you don't want to use `performBlock(_:)` / `performBlockAndWait(_:)` in your own `perform()` and `performAndWait()` methods that will allow to turn the `com.apple.CoreData.ConcurrencyDebug` argument on? As they say **Better safe than sorry** :-]
Author
Owner

@JohnEstropia commented on GitHub (Jun 15, 2016):

CoreStore queues transactions serially, so it manages transactions through children contexts in such a way that they will never interleave (with the exception of UnsafeDataTransactions).

In effect, there really is no point in using performBlock() because there is no other context to protect against. In fact, enclosing performBlock() from within the transaction queue actually introduces opportunities to deadlock. The context will be bound within multiple queues, and any attempt to cross another queue (e.g. fetching objects, which taps the parent context and/or the persistent store) becomes a deadlock risk.

@JohnEstropia commented on GitHub (Jun 15, 2016): CoreStore queues transactions serially, so it manages transactions through children contexts in such a way that they will never interleave (with the exception of `UnsafeDataTransaction`s). In effect, there really is no point in using `performBlock()` because there is no other context to protect against. In fact, enclosing `performBlock()` from within the transaction queue actually introduces opportunities to deadlock. The context will be bound within multiple queues, and any attempt to cross another queue (e.g. fetching objects, which taps the parent context and/or the persistent store) becomes a deadlock risk.
Author
Owner

@ruslanskorb commented on GitHub (Jun 15, 2016):

@JohnEstropia Thanks for the explanation and special thanks for the library! 👍

One thing I would like to suggest is to add a small note about com.apple.CoreData.ConcurrencyDebug in the README.

And the issue can be closed :-]

@ruslanskorb commented on GitHub (Jun 15, 2016): @JohnEstropia Thanks for the explanation and special thanks for the library! :thumbsup: One thing I would like to suggest is to add a small note about `com.apple.CoreData.ConcurrencyDebug` in the README. And the issue can be closed :-]
Author
Owner

@JohnEstropia commented on GitHub (Jun 16, 2016):

You're right, this should be explained in the documentation. I'll try to print a warning log when com.apple.CoreData.ConcurrencyDebug is turned on as well.

Thanks for the feedback! Really appreciate it :)

@JohnEstropia commented on GitHub (Jun 16, 2016): You're right, this should be explained in the documentation. I'll try to print a warning log when `com.apple.CoreData.ConcurrencyDebug` is turned on as well. Thanks for the feedback! Really appreciate it :)
Author
Owner

@JohnEstropia commented on GitHub (Jun 16, 2016):

I'll add a note in the Installation section of CoreStore 2.0's README regarding com.apple.CoreData.ConcurrencyDebug. (corestore2_develop branch)

@JohnEstropia commented on GitHub (Jun 16, 2016): I'll add a note in the `Installation` section of CoreStore 2.0's README regarding `com.apple.CoreData.ConcurrencyDebug`. (corestore2_develop branch)
Author
Owner

@ruslanskorb commented on GitHub (Jun 16, 2016):

@JohnEstropia 👍

@ruslanskorb commented on GitHub (Jun 16, 2016): @JohnEstropia :thumbsup:
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#64