Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why might releasing a managed object crash in -[_PFManagedObjectReferenceQueue _queueForDealloc:]?

I am occasionally seeing crashes with a stack trace like this:

0 libobjc.A.dylib 0x97dc0edb objc_msgSend + 27
1 com.apple.CoreData 0x97edcdc2 -[_PFManagedObjectReferenceQueue _queueForDealloc:] + 162
2 com.apple.CoreData 0x97edccbe -[NSManagedObject release] + 94
3 com.apple.CoreFoundation 0x9318ef38 CFRelease + 152
4 com.apple.CoreFoundation 0x931a7460 __CFBasicHashStandardCallback + 384
5 com.apple.CoreFoundation 0x931a706e __CFBasicHashDrain + 478
6 com.apple.CoreFoundation 0x9318f101 _CFRelease + 353
7 com.apple.CoreFoundation 0x931bbc6d _CFAutoreleasePoolPop + 253
8 com.apple.Foundation 0x973270aa NSPopAutoreleasePool + 76
9 com.apple.Foundation 0x97326fd2 -[NSAutoreleasePool drain] + 130
10 com.apple.AppKit 0x95087185 -[NSApplication run] + 627
11 com.apple.AppKit 0x9507f2d9 NSApplicationMain + 574
12 com.karelia.Sandvox 0x70001ef6 start + 54

Unfortunately, it's rather random to reproduce. Does anyone have any ideas what could cause such a crash? Doesn't help that no-one seems to have mentioned -_queueForDealloc: on the internet before!

I have a vague memory of a similar problem in the past where this was a symptom of deallocating a managed object while it still had KVO observers attached. Anyone concur?

like image 932
Mike Abdullah Avatar asked Mar 08 '11 14:03

Mike Abdullah


3 Answers

Having finally been able to reproduce the problem on a development machine, it seems this crash is a side-effect of an earlier exception during context teardown.

The sequence of events is something like:

  1. The MOC is being deallocated, so it's time to tear down its contents
  2. To do so, all registered MOs are turned into faults*
  3. The act of turning a MO into a fault sends KVO-notifications
  4. An observer receives the notification and tries to act upon it, hitting a now invalid MO in the graph
  5. Core Data throws an exception from the invalid access
  6. For reasons unknown, that exception is not passed to my exception reporter
  7. The MOs get released, but the exception left Core Data in an unexpected state, so the MO deallocation crashes

In short the real problem is that observers outlive the context; don't allow them to! Any object observing a MO should probably also have a strong reference to the MOC, like NSObjectController and friends do.

*I found in testing that Core Data often does this on a background thread, presumably to avoid blocking the main thread

MOC – managed object context
MO – managed object

like image 62
Mike Abdullah Avatar answered Nov 17 '22 14:11

Mike Abdullah


-_queueForDealloc: is an undocumented internal method. It shows up in stacks from time to time but it's nothing we deal with directly.

Your problem is most likely caused by over-releasing a managedObject. A managedObject will be strongly retained by a context that inserts, updates or changes the object so if you micromanage the objects own retention, you can over-release it prior to the context releasing it. This cause the managed object to disappear at seemingly at random. Conversely, you can over-retain causing an object to persist after the context has deleted it.

I encourage people to avoid retaining managed objects but when you do, put them a class property or a collection like an array or set. That way, the retention is handled for you.

like image 22
TechZen Avatar answered Nov 17 '22 14:11

TechZen


We hit into a a similar issue when using a private managed object context inside an NSOperation and we ended up working around it by weakifying any parameters and using a private @autoreleasepool. I'll elaborate further below.

Our current set up has an NSOperationQueue which has a long running calculation we do in the background. The operation first creates a private managed object context with the parent set as the main object context and goes and fetches its objects.

In the mean time, we have a separate NSOperationQueue elsewhere that syncs down new data from our server, potentially adding, updating, or removing objects used by our calculation operation.

We first saw a bunch of these crashes out in the wild and the only way to repro it locally is to have both calculation and sync operations run continuously and after 5-10 minutes, we would see a crash similar to one of the below:

Thread : Crashed: background queue :: NSOperation 0x18f43c90
0  libobjc.A.dylib                0x36f11f46 objc_msgSend + 5
1  CoreData                       0x2928408f -[NSManagedObject release] + 166
2  CoreData                       0x2927b4d7 -[_PFArray dealloc] + 94
3  libobjc.A.dylib                0x36f201a9 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 404
4  CoreFoundation                 0x294713a9 _CFAutoreleasePoolPop + 16
5  Foundation                     0x2a1b6453 -[__NSOperationInternal _start:] + 1058
6  Foundation                     0x2a25b44b __NSOQSchedule_f + 186
7  libdispatch.dylib              0x3746d651 _dispatch_queue_drain + 952
8  libdispatch.dylib              0x3746809d _dispatch_queue_invoke + 84
9  libdispatch.dylib              0x3746eba1 _dispatch_root_queue_drain + 320
10 libdispatch.dylib              0x3746fcd7 _dispatch_worker_thread3 + 94
11 libsystem_pthread.dylib        0x375c6e31 _pthread_wqthread + 668


Thread : Crashed: background queue :: NSOperation 0x1db59e80
0  libsystem_kernel.dylib         0x3722edfc __pthread_kill + 8
1  libsystem_pthread.dylib        0x372acd37 pthread_kill + 62
2  libsystem_c.dylib              0x371ce909 abort + 76
3  libsystem_malloc.dylib         0x37258331 szone_size
4  libobjc.A.dylib                0x36bf1621 object_dispose + 20
5  CoreData                       0x28ec571d -[_PFManagedObjectReferenceQueue dealloc] + 80
6  CoreData                       0x28e5630f -[NSManagedObject dealloc] + 166
7  CoreData                       0x28e55217 -[_PFManagedObjectReferenceQueue _queueForDealloc:] + 246
8  CoreData                       0x28e5508f -[NSManagedObject release] + 166
9  CoreData                       0x28e4c4d7 -[_PFArray dealloc] + 94
10 libobjc.A.dylib                0x36c031a9 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 404
11 CoreFoundation                 0x29042149 _CFAutoreleasePoolPop + 16
12 Foundation                     0x29d88c23 -[__NSOperationInternal _start:] + 1058
13 Foundation                     0x29e2dc1b __NSOQSchedule_f + 186
14 libdispatch.dylib              0x371505b1 _dispatch_queue_drain + 952
15 libdispatch.dylib              0x3714af85 _dispatch_queue_invoke + 84
16 libdispatch.dylib              0x37151b9b _dispatch_root_queue_drain + 338
17 libdispatch.dylib              0x37152cd7 _dispatch_worker_thread3 + 94
18 libsystem_pthread.dylib        0x372a9e31 _pthread_wqthread + 668


Thread : Crashed: NSOperationQueue Serial Queue
0  libsystem_kernel.dylib         0x396871f0 __pthread_kill + 8
1  libsystem_pthread.dylib        0x396ef7b7 pthread_kill + 58
2  libsystem_c.dylib              0x39637ff9 abort + 76
3  libsystem_malloc.dylib         0x396aed25 szone_size
4  libobjc.A.dylib                0x390d93a9 object_dispose + 20
5  CoreData                       0x2e3d4081 -[_PFManagedObjectReferenceQueue dealloc] + 80
6  CoreData                       0x2e3655b7 -[NSManagedObject dealloc] + 166
7  CoreData                       0x2e364501 -[_PFManagedObjectReferenceQueue _queueForDealloc:] + 244
8  CoreData                       0x2e36437d -[NSManagedObject release] + 164
9  CoreData                       0x2e35b867 -[_PFArray dealloc] + 94
10 libobjc.A.dylib                0x390e20d3 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 358
11 CoreFoundation                 0x2e5294c1 _CFAutoreleasePoolPop + 16
12 Foundation                     0x2ef29999 -[__NSOperationInternal _start:] + 1064
13 Foundation                     0x2efcd745 __NSOQSchedule_f + 60
14 libdispatch.dylib              0x395c0cbd _dispatch_queue_drain + 488
15 libdispatch.dylib              0x395bdc6f _dispatch_queue_invoke + 42
16 libdispatch.dylib              0x395c15f1 _dispatch_root_queue_drain + 76
17 libdispatch.dylib              0x395c18dd _dispatch_worker_thread2 + 56
18 libsystem_pthread.dylib        0x396ecc17 _pthread_wqthread + 298

We reviewed the code multiple times and was not able to determine why it was crashing. We tried enabling NSZombies, but would run out of memory long before we could get a repro.

What we ended up doing is the following 2 things:

@autoreleasepool

Inside our [privateObjectContext performBlockAndWait:^{…}] which resides inside our NSOperationBlock, we wrapped all the code inside an @autoreleasepool{…}. That way all NSManagedObjects retrieved during that block of code will be mark for release before leaving the performBlockAndWait.

weakify/strongify

Any parameters that include NSManagedObjects were weakify before passing it into the block, and strongify once in the block. This way since we no longer have a strong reference to them, they can be released if they become out of date while we wait for the NSOperation to start. Here's a good article on how weakify/strongify works: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

like image 29
Toland Hon Avatar answered Nov 17 '22 15:11

Toland Hon