Core Data concurrency issue and how to fix - ios

I use multiple contexts in my Core Data app, and have recently had some core data concurrency crashes. I have added -com.apple.CoreData.ConcurrencyDebug 1 to help track these down, but I am not understanding how to fix the issue that is shown.
Here is what I am doing:
- (void)getEvents:(void (^)(NSArray *fetchedItems))completionBlock {
// Initialize Fetch Request
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ZSSCDEvent"];
// Initialize Asynchronous Fetch Request
NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:request completionBlock:^(NSAsynchronousFetchResult *result) {
dispatch_async(dispatch_get_main_queue(), ^{
// Dismiss Progress HUD
[SVProgressHUD dismiss];
// Process Asynchronous Fetch Result
if (result.finalResult) {
NSArray *results = result.finalResult;
completionBlock(results);
// Reload Table View
[self.activityIndicator stopAnimating];
[self.tableViewList reloadData];
}
});
}];
// Execute Asynchronous Fetch Request
[self.managedObjectContext performBlock:^{
// Execute Asynchronous Fetch Request
NSError *asynchronousFetchRequestError = nil;
NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[self.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
if (asynchronousFetchRequestError) {
NSLog(#"Unable to execute asynchronous fetch result.");
NSLog(#"%#, %#", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
}
}];
}
This gives me a Enqueued from com.apple.main-thread (Thread 1) error. This is where I am confused, since I am running this on the main thread and didn't think I needed to use my private context here.
Any ideas on why I am getting a concurrency issue here?
EDIT: It looks like someone else had the exact issue and thinks it is an Xcode bug: CoreData asynchronous fetch causes concurrency debugger error

Every managedObject has a context. Each context has one and only one thread that it can run on. ManagedObjects are NOT thread safe - not even for reading. Passing managedObjects around with completions blocks is a bad practice. It can be hard to figure out which managedObjects are supposed to be on which thread. Also, even if you are passing it around on the correct thread it is still a bad practice. When you do a dispatch_async the entity may have deleted from the database in the interim and accessing the managedObject will cause a crash. A good practice is that any method that does a fetch should be explicitly told which context to use and return synchronously. In your code the method is using self.managedObjectContext, but I have no way to know looking at you code what thread that is related to. Furthermore the pointer to the context may change and that can cause bugs that are very hard to track down.
NSAsynchronousFetchResult contains managedObjects, so is not thread safe and can only be used inside that completion block (which is running on the correct thread for the objects). You cannot pass them to another thread. If you return them in a block then that code must also not pass them to another thread. You should just do whatever you need to do with them inside the block and then discard them.
If you need to display the information to the user, then generally it is better to just do the fetch on the main thread synchronously and get managedObjects that are associated with a main thread context. If your fetch is taking to long then you should fix that - there is no reason for a fetch to take so long. In your case you appear to be fetching ALL the items in you database. That is a mistake. You should use a predicate to only get the ones that you need.
Another possibility is to copy the values of managedObjects into a thread safe object (a dictionary or a NSObject subclass).
TL;DR You probably don't need a NSAsynchronousFetchRequest. Use a regular fetch request on the main thread and return synchronously. Use a predicate to limit the results only to the objects you are actually displaying.

Related

Know when Core Data has saved all entities using NSXMLParser

I am using NSXMLParser to read a large XML file. Once I get the data element that I want I create an NSManagedObject and save that on a background context. The imported file will have n number of imported items, it is never known.
NSManagedObjectContext *backgroundContext = [ZSSCoreDataManager sharedService].persistentContainer.newBackgroundContext;
[backgroundContext performBlock:^{
// Create and insert new entity here
[self createAndInsertWithDictionary:object];
// Save
if ([backgroundContext hasChanges]) {
NSError *error = nil;
if (![backgroundContext save:&error]) {
NSLog(#"%#", error);
}
// Save was successful, reset to release memory
[backgroundContext reset];
}
}];
Because parsing can happen faster than a save: can complete, how can I know when the XML file is done reading AND all my Core Data objects have completed saving? I know of the parserDidEndDocument: method, but this doesn't help to know when all of my object have completed saving to Core Data.
Ideas?
One obvious way would be to replace performBlock with performBlockAndWait, so that you know once the block has finished that the save has also finished.
If you don't want to wait, this sounds like a job for a dispatch group. That would allow scheduling a function to be called only after a bunch of asynchronous code had finished.
Create a dispatch group with dispatch_group_create
Every time you start one of these blocks, call dispatch_group_enter before doing anything else.
Every time a save completes, call dispatch_group_leave
Use dispatch_group_notify to schedule a block that will only be executed when every "enter" has been matched by a "leave".
Put code in the "notify" block that should run once every save has finished.

How to avoid deadlock with CoreData

I have a CoreData stack that is like this:
Persistent Store -> (private)writingContext -> (main)UIContext
-> (private)backgroundContext
This is a commonly suggested stack to use in various blogs and people who have mastered CoreData, except that others will have the backgroundContext a child of the main context. But thats not the issue I am seeing. What I have is
[[self masterManagedObjectContext] performBlock:^{
NSError *error = nil;
BOOL saved = [self.masterManagedObjectContext save:&error];
if (!saved) {
// do some real error handling
[EventLogger logError:#"CoreDataService" message:#"masterContext save ERROR" error:error];
}
}];
Now this is executed from a UIContext performBlock calls this master save within a block and because its not blocking the next code to run is a tableView reload where it gets the fetched objects, accesses the properties and puts them on the tableViewCells. I am getting a deadlock here because the save is still running but the UIContext is accessing the property values which are going into the master to pull the values into memory. This happens consistently.
From what I understand, the contexts don't operate but on a single thread with a queue, well with the UI being a child of the master if the master is doing something and the child is requesting something at the same time, its causing the deadlock. How do I avoid this? How can I async save whats in the UI and not deadlock on accessing a property of an NSManagedObject?

ios - sending data to NSOperation or should I use NSThread?

I have core data objects that are created/updated via HTTP. I also want to create a background thread to continuously receive timestamp and state info from the app and update the core data objects. Should I use NSOperation or GCD for this? Since it's not just a simple task, NSOperation seems better since I can loop within it, but I can't figure out how to pass information into the operation, from the main thread, while it's running. Is there a simple way of doing this? I have seen many threads/articles about sending messages to the main thread from the operation, but nothing about passing messages to it.
Does using NSOperation/GCD seem like a good solution?
Step back and relook at your architecture. You should be using a managed object context that itself uses a private dispatch queue (option NSPrivateQueueConcurrencyType).
You would then use NSURLConnections to get the data you want, and when you get the data in the delegate method you can asynchronously update the repository using performBlock.
Conversely you may want to retrieve data using performBlockAndWait, using block variables or mutable pre-defined objects to receive the results from the block.
I was looking at this all wrong (essentially wanting to subclass NSThread and pass data to/from it). I didn't think about using blocks appropriately.
Essentially, what I'm doing is:
loadStuff:(NSDictionary *)stuff {
// stuff is data from HTTP GET request
NSManagedObjectContext *context = // init context with NSPrivateQueueConcurrencyType
context.parentContext = // main context
[context performBlock:^{
// insert/update entities
// save context
}];
}
But then I need to loop in a background thread to update the entities:
//inside some method
dispatch_async(global_queue, ^{
// _currentTimestamp is updated by a delegate
NSInterval timestamp = _currentTimestamp;
while (run) {
if (timestamp != _currentTimestamp) {
// do calculation
// update entities if needed
// save context
}
}
});

Concurrency with core data

I am using multi-threading to get data, parse it, create objects and store them. And after this is all done, I want the window to be shown.
But now I have 2 issues:
I have a deadlock
My barrier does not act as a barrier.
I think the deadlock is because I am updating the managedObjectContext in several threads at once.
So I changed my managedObjectContext with the ConcurrencyType:
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
And created an importContext for the concurrency queue and assigned the parentContext:
NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
importContext.parentContext = self.managedObjectContext;
And put my operations in a performBlock for the importContext:
[importContext performBlock:^{
dispatch_async(backgroundQueue, ^{
[myObject methodAWithContext:importContext];
});
dispatch_async(backgroundQueue, ^{
[myObject methodBWithContext:importContext];
});
dispatch_async(backgroundQueue, ^{
[myObject methodCWithContext:importContext];
});
dispatch_barrier_async(backgroundQueueM, ^{
// create barrier to wait for the first 3 threads to be completed.
dispatch_async(dispatch_get_main_queue(), ^{
// Save the data from the importContext tot the main context on the main queue
NSError *importError = nil;
[importContext save:&importError];
[importContext.parentContext performBlock:^{
NSError *parentError = nil;
[importContext.parentContext save:&parentError];
}];
[self.window makeKeyAndVisible];
});
});
}];
Approach 1:
In each method, I select a subset of object, delete these and then create new objects and save this.
(I thought the delete was quicker than doing a fetch and check for the existence for every object to be created).
So:
In Method A I select all AObjects, delete them and create new AObjects.
In Method B I select all BObjects, delete them and create new BObjects.
In Method C I select all CObjects, delete them and create new CObjects.
But then I get an error "An NSManagedObjectContext cannot delete objects in other contexts".
So approach 2:
I removed the delete. But now I get various different errors.....
And the barrier does not wait for the other threads to be executed.
Q1: What am I doing wrong?
Q2: how do I get the barrier to wait for the 3 threads to be completed
Q3: how can I delete / purge objects on various threads?
(I have read the Apple release notes and doc's, but I can't find this a clear explanation on the combination for multithreading and managedContext.)
You cannot call dispatch_async within performBlock. A managed object context of type NSPrivateQueueConcurrencyType has it's own dispatch queue for executing the operations.
You try to do several operations in parallel by moving them to a different dispatch queue, but that is not possible.
If you really have to do multiple operations in parallel, you must create a private concurrency type MOC for each operation.
ADDED:
There are several ways to wait for all operations to complete:
You could increment a counter at the end of each performBlock: and check if it's value is (in your example) 3.
You could create a semaphore (dispatch_semaphore_create) for each operation with initial value zero, wait for all the semaphores (dispatch_semaphore_wait) and signal the semaphore at the end of each performBlock.
And I am sure that there are better/more elegant/more sophisticated ways to do this.
BUT: As I re-read your question, I see that you try to delay the
[self.window makeKeyAndVisible];
until all Core Data fetch operations have completed. This is not a good design, because the user will see nothing until your data import is done.
A better design is to show an initial view immediately, and update that view when the background operations have fetched data.

iOS - MagicalRecord / AFNetworking / NSFetchedResultsController - background resyncing process causes perpetual hang

So the objective I'm trying to achieve
is a syncing process that is supposed to be completed in the background using AFNetworking and Magical Record, but causes a perpetual hang when a view controller hooked up to a NSFetchedResultsController is currently open or has been opened (but popped).
The app syncs the first time the user opens the phone and then uses the data always in the Core Data persistance store via the Magical Record framework. Then when the user wants to make sure that there data is the most recent version, they go into settings and click "Re-Sync", which causes the following code to execute:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[[CoreDataOperator sharedOperator] commenceSync:self];
});
This starts the syncing process using the singleton CoreDataOperator (subclass of NSObject -- maybe it should be NSOperation?), which fires off the following code:
[[ApiClient sharedClient] getDataRequest];
which then then fires off this bad boy in the singleton AFHTTPClient subclass:
[[ApiClient sharedClient] postPath:url parameters:dict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[request.sender performSelector:request.succeeded withObject:response];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[request.sender performSelector:request.failed withObject:response];
}
];
which effectively says: AFHTTPClient post this infomation and when it succeeds, pass back the information to the provided selector (I understand that this is generic, but the request isn't the issue)
NOW, AFNetworking is coded such that all completion selectors (in this case specifically, success and failure) are called on the main thread; so in order to prevent blocking the main thread, the code that processes the response and prepares it for saving is sent back into the background thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
id responseData;
[self clearAndSaveGetData:responseData];
});
This then calls the function that causes the saving using the Magical Record framework (still in background thread):
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
[DATAENTITY truncateAllInContext:localContext];
<!--- PROCESS DATA INTO DATAENTITY -->
[localContext saveNestedContexts];
I chose saveNestedContexts because since I am working in the background, I wanted it pushed all the way up to the defaults contexts, which I'm assuming is a parent context? (but this so far hasn't been an issue).
Now, this data can become thousands and thousands of rows, so I'm using a NSFetchedResultsController to access this data safely and efficiently, and they are used in a different view controller than the settings or main page.
Here are three cases:
The ViewController containing the FRC has not been accessed (not visible and hasn't been visible before) - the background sync works flawlessly, minus a little lag due to the saving up the stack.
The ViewController containing the FRC has been accessed and is currently visible - background sync process HANGS due to what appears to be the FRC receiving context updates.
The ViewController containing the FRC has been previously access, but it isn't currently visible (the VC was popped using the following code: [self.navigationController popViewControllerAnimated:YES];) AND the FRC is set to nil in ViewDidUnload - background sync process HANGS due to what appears to be the FRC receiving context updates (just like in case 2).
[PS: after hanging for a few minutes, I kill the debugger and it gives my a SIGKILL on the following code, which is why I'm assuing the FRC is receiving context updates that causes it to hang:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates]; <---- SIGKILL
}
Other important information of note:
I am using 2 separate FRCs, one for normal ALL data and one for searching
I am using a cache for both the FRC and the separate search FRC, which is purged at the appropriate times (before this context update stuff)
The FRCs are fetching in the mainthread (which does cause a slight hang when there is THOUSANDS of rows of data) and I have been looking into fetching in the background, however that is currently not implemented.
The QUESTION(s):
Why is this hanging occuring, where the VC is visible or has been popped?
How can I make it such that the FRC doesn't listen for the save, but uses what it has until the save is completed and then it refreshes the data (unless this is what already occuring and causing the hanging)?
Would implementing a background fetch (because the lag when opening the VC with the FRC to access the thousands of rows of data creates a noticable lag, even with a cache and lessened predicates/section headers - of between 1 and 4 seconds) be viable? Too complex? How can it be done?
Thank you, hope I was detailed enough in my question.
I know this question is old but since this is a common gotcha with MagicalRecord maybe the following will help somebody.
The problem is caused by the fetchRequest of the FRC and the save operation deadlocking each other. The following solved it for me:
Update to the latest MagicalRecord release (2.1 at the time of writing).
Then do all your background saves using the following:
MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// create new objects, update existing objects. make sure you're only
// accessing objects inside localContext
[myObjectFromOutsideTheBlock MR_inContext:localContext]; //is your friend
}
completion:^(BOOL success, NSError *error) {
// this gets called in the main queue. safe to update UI.
}];

Resources