9

This line of code is being called in my awakeFromFetch method located inside a custom managed object which implements NSManagedObject. This line in particular is making a call to my singleton network manager class called sharedManager.

[self setSync:(![[WKNetworkManager sharedManager] objectHasPendingRequests:self.objectID]) ];

The dispatch_once block will be hit as is shown below. Notice that it is implemented in a good way as is shown here:

enter image description here

The dispatch_once call then goes to once.h and here it freezes right on the highlighted line:

dipatch_once deeper

Here is the stack trace:

stack trace

All of this happens when trying to load a previously saved network queue file. The application is closed fully to save, and then launched again and then this freezing/locking occurs.

I even tried to use this code instead to solve the problem as is suggested here, and it did not work. But changing this would probably not matter anyway, as my original dispatch_once code has been working just fine for a long time. It's just in this particular case.

if ([NSThread isMainThread]) 
{
    dispatch_once(&onceToken, ^{
    stack = [[KACoreDataStack alloc] init];});
} 
else 
{
    dispatch_sync(dispatch_get_main_queue(), ^{
    dispatch_once(&onceToken, ^{
    stack = [[KACoreDataStack alloc] init];});
});
}

So far, these are my sources for troubleshooting this type of problem:

Thanks for your help!

Community
  • 1
  • 1
Marcel Gruber
  • 6,668
  • 6
  • 34
  • 60

2 Answers2

6

Damn son, that is one fine job of question-asking.

The problem here is that recursive calls to dispatch_once will deadlock. If you want exact details, you can find them with the source here. So you need to rewrite to avoid that.

My opinion is that there is an architectural misstep with whatever leads to this -loadQueueFromDisk call. You absolutely do not want to be doing anything that is as potentially unbounded in execution time as going off and loading something from disk inside a dispatch_once. What you want to be doing is the absolute minimum to create an addressable singleton and get back out. Then, give it an internal state of "Initializing", and ship the disk-loading stuff onto a non-blocking queue somewhere. That way everything that doesn't depend on the stuff loaded from disk can carry on, and everything that does depend on the stuff loaded from disk can add itself to the queue the disk loading is on, or check back every few hundred milliseconds to see if it's in an "Initialized" state yet, or something along those lines.

Community
  • 1
  • 1
Alex Curylo
  • 4,744
  • 1
  • 27
  • 37
  • Thanks! I think you're certainly on to something about architecture. This whole loading from disk thing is being added on after the app has been architected and obviously it's not playing nice with it. WKNetworkManager (sharedManager) calls `[self loadQueueFromDisk];`. So, in turn, by creating the decoded objects, those object initializations trigger calls to sharedManager. That could certainly be problematic. The linked solution looks bulky. Do you think making a block statement of loadQueueFromDisk to delegate the task to another thread would work? – Marcel Gruber Feb 19 '15 at 05:27
  • 1
    Well, Core Data and threading, that can get complicated quickly. My first inclination for disk-hitting Core Data initialization like this is to toss it into the low priority GCD queue `dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0 ), ^{ [self loadQueueFromDisk]; });` so it gets attended to once the high and normal priority queues are empty. As that's pretty likely to be the desired behaviour whilst starting up. – Alex Curylo Feb 19 '15 at 16:57
  • I actually got it working by creating a new void method containing `fileStrQueue = [[NSOperationQueue alloc] init];` _/*Create an operation which will invoke method loadTextFile*/_ `NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadQueueFromDisk) object:nil];` _/*Add this operation to the queue*/_ `[fileStrQueue addOperation:operation];` That said, which do you think is better? – Marcel Gruber Feb 19 '15 at 17:07
  • 1
    For initialization type stuff, absolutely going to the extra trouble to create an `NSOperationQueue` like you did is better, because then you can add whatever depends on the results of `loadQueueFromDisk` into it to be executed on a deterministic schedule, should that become necessary, and it probably will sooner or later :) – Alex Curylo Feb 19 '15 at 17:13
  • Thanks, again! I tried to add my solution code to your answer, but it was rejected... – Marcel Gruber Feb 20 '15 at 00:14
0

Thanks to the help of alexcurylo, queueing out the loadFromDisk job addressed the issue. In order so that people aren't confused, it just so happens that with this code below I am queuing a job that loads an array (which acts as a saved queue of user requests) from the disk. You could be queueing any job, such as loading images / etc. The following code in my sharedManager class (where load from disk is being called) worked, as is shown here:

-(void) loadQueueFromFileByQueueing
{
  //Create a Queue
  /* Note that fileStrQueue should be added to the .h file as NSOperationQueue *fileStrQueue;*/
  fileStrQueue = [[NSOperationQueue alloc] init];

  //Create an  operation which will invoke method loadQueueFromDisk
  NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadQueueFromDisk) object:nil];

  //Add this operation to the queue
  [fileStrQueue addOperation:operation];

  //Releasing the above operation as it is already retained by the queue.
  // [operation release]; // If you are using ARC, this won't be necessary
}
Marcel Gruber
  • 6,668
  • 6
  • 34
  • 60