There aren't enough locks in the world to make it safe to update a table's bound model on a background thread. If you have a UI element bound to a another object, that object must never send KVO notifications on a bacgeohnd thread.

Sent from my iPhone

On Feb 20, 2009, at 11:57, Luke Evans <l...@eversosoft.com> wrote:

Hi list,

I have a nasty deadlock caused by the condition lock used by - [NSViewHierarachyLock _lockForWriting:handler:] never getting acquired on a non-main thread. This is on 10.5.6 BTW.

I can sort of see what's going on, but I'm not sure how one is supposed to fix it.

Here's what's happening:

My app has a table whose content shows files. The files that get shown here are stored in Core Data, the table is bound to the Core Data model to show the appropriate stuff. A background thread is started to spider through the file system looking for appropriate files. When a new candidate file is spotted, the managed object context is locked, and the file is added to the 'store'.

Naturally, when a new item arrives in the MOC, the table that is bound to it tries to update. This is when the deadlock occurs.
The state of the two threads when things lock up appears as follows:

The background thread has acquired the MOC lock, which minimally wraps a call to -[NSManagedObjectContext executeFetchRequest:error:]. It is doing a fetch request to see if a file that is being processed by the file system walker is already in the MOC.
Here's this thread's stack:

--------------------
#0    0x94fef3ae in __semwait_signal
#1    0x9501a326 in _pthread_cond_wait
#2    0x95019d0d in pthread_cond_wait$UNIX2003
#3    0x937e6c77 in -[NSViewHierarchyLock _lockForWriting:handler:]
#4 0x937e6827 in -[NSViewHierarchyLock lockForWritingWithExceptionHandler:]
#5    0x931b7b79 in -[NSView setFrameSize:]
#6    0x931ae00a in -[NSControl setFrameSize:]
#7    0x932adcff in -[NSTableView setFrameSize:]
#8    0x932ad0c7 in -[NSTableView tile]
#9    0x932c6b05 in -[NSTableView rowsInRect:]
#10    0x933d8138 in -[NSTableBinder _visibleRowIndexesForObject:]
#11 0x933d79e7 in -[NSTableBinder observeValueForKeyPath:ofObject:change:context:]
#12    0x96e00b0e in NSKVONotify
#13 0x96d910a5 in -[NSObject(NSKeyValueObservingPrivate) _notifyObserversForKeyPath:change:] #14 0x931b670a in -[NSController _notifyObserversForKeyPath:change:]
#15    0x931b660b in -[NSController didChangeValueForKey:]
#16 0x933d2920 in -[NSArrayController didChangeValuesForArrangedKeys:objectKeys:indexKeys:] #17 0x933d5780 in -[NSArrayController _arrangeObjectsWithSelectedObjects:avoidsEmptySelection:operationsMask:useBasis: ]
#18    0x933d06b6 in -[NSArrayController setContent:]
#19 0x936cf1a9 in -[_NSManagedProxy _managedObjectsChangedInContext:]
#20    0x96d7fe1a in _nsnote_callback
#21    0x94f078da in __CFXNotificationPost
#22    0x94f07bb3 in _CFXNotificationPostNotification
#23 0x96d7d080 in -[NSNotificationCenter postNotificationName:object:userInfo:] #24 0x96595439 in - [NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] #25 0x96595302 in - [NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes: ] #26 0x9659415b in - [NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] #27 0x96592e70 in -[NSManagedObjectContext executeFetchRequest:error:]
--------------------

I note here that while processing the fetch request on the MOC, the MOC chooses to notify its observers, which includes the NSTableBinder for table that shows the files.
Apparently, the table is unable to refresh its view because...

Over on the main thread, back at the ranch...

...the table is drawing, and guess what: it want to access the MOC to get the name of the file it's drawing. Because accessing this attribute is a potentially faulting operation, this too is protected by a lock on the MOC, which is never acquired.

Here's the main thread stack:

---------------------
#0    0x94fe8202 in semaphore_wait_trap
#1    0x94fefcf2 in pthread_mutex_lock
#2    0x9658bdf5 in -[_PFLock lock]
#3    0x9658bdca in -[NSManagedObjectContext lock]
#4 0x00007849 in -[MYFileTracker managerForFile:] at MYFileTracker.m:261 #5 0x00008831 in -[MYFileTracker identifyingDisplayTextForFile:] at MYFileTracker.m:523
#6    0x0000da26 in -[MYFile identifyingDisplayText] at MYFile.m:78
#7    0x96dd15fa in -[NSObject(NSKeyValueCoding) valueForKeyPath:]
#8 0x933947f0 in -[NSBinder _valueForKeyPath:ofObject:mode:raisesForNotApplicableKeys:] #9 0x9346caed in -[NSBinder valueForBinding:atIndex:resolveMarkersToPlaceholders:]
#10    0x9346c91d in -[NSValueBinder _referenceBindingValueAtIndex:]
#11 0x933c98d3 in -[NSValueBinder _adjustObject:mode:observedController:observedKeyPath:context:editableState:adjustState: ] #12 0x9346c78c in -[NSValueBinder updateTableColumnDataCell:forDisplayAtIndex:] #13 0x9346c69d in -[NSTextValueBinder updateTableColumnDataCell:forDisplayAtIndex:] #14 0x9346c639 in -[_NSBindingAdaptor tableColumn:willDisplayCell:row:] #15 0x932e17a1 in -[NSTableView _sendBindingAdapterWillDisplayCell:forColumn:row:]
#16    0x93263ab3 in -[NSTableView preparedCellAtColumn:row:]
#17 0x9326387c in -[NSTableView _drawContentsAtRow:column:withCellFrame:]
#18    0x93262db2 in -[NSTableView drawRow:clipRect:]
#19    0x932081a0 in -[NSTableView drawRowIndexes:clipRect:]
#20    0x93206c84 in -[NSTableView drawRect:]
#21    0x9329729c in -[NSView _drawRect:clip:]
#22 0x93295d93 in -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] #23 0x9329612a in -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] #24 0x9329612a in -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] #25 0x932946e9 in -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView: ] #26 0x93295543 in -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView: ] #27 0x93295543 in -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView: ] #28 0x9329402b in -[NSThemeFrame _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView: ] #29 0x93290b4f in -[NSView _displayRectIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:]
#30    0x931d1523 in -[NSView displayIfNeeded]
#31    0x931d10d1 in -[NSWindow displayIfNeeded]
#32    0x931d0ef4 in _handleWindowNeedsDisplay
#33    0x94f249a2 in __CFRunLoopDoObservers
#34    0x94f25cfc in CFRunLoopRunSpecific
#35    0x94f26cd8 in CFRunLoopRunInMode
#36    0x955d12c0 in RunCurrentEventLoopInMode
#37    0x955d10d9 in ReceiveNextEventCommon
#38    0x955d0f4d in BlockUntilNextEventMatchingListInMode
#39    0x931ced7d in _DPSNextEvent
#40 0x931ce630 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]
#41    0x931c766b in -[NSApplication run]
#42    0x931948a4 in NSApplicationMain
#43    0x00003844 in main at main.m:13
--------------------

So, in a nutshell, we have the following dual-lock deadlock:
- The table is drawing in the main thread and therefore holds some internal lock protecting view hierarchy modification. It is trying to acquire the managed object context lock to obtain some data to render. - The 'spider' thread is simply checking if a file already exists in the managed object context. It acquires what it thinks is a very shortly-held lock on the MOC to bracket a fetch request, but unexpectedly, the act of simply interrogating the managed object context (executing a fetch request), causes a notification to the table informing it of some recent change, which in turn requires the view hierarchy lock being held by the main thread.

I was a little surprised at this turn of events having conformed (so I thought) to one of the models multi-threaded code using Core Data. The docs suggest locking a single MOC is an acceptable usage in a multi-threaded environment so long as you are completely diligent with the locks. I figure from the docs that the preferred method is "MOC per thread", but opted for the locking.

Clearly, if the MOC locks had not been there, the app would not have deadlocked in this case. However, the real 'culprit' here (or maybe I'll just say 'surprise') is that executing a fetch request on the MOC causes the table to require its view hierarchy lock.

Questions that come to mind:
- Is there any way to suppress the table notification in the non- main thread when I'll issuing the fetch request? - Can I force it to happen before I lock the MOC to execute the fetch request? Seems like that wouldn't be a strong guarantee of proper functioning, but it might considerably reduce the potential for the NSKVONotify to be posted to the table right in the middle of my fetch request! Is this something that -commitEditing might help with... but then surely I'm supposed to lock the MOC around that operation too, and that would likely produce the same result. - What methods have people found to solve this? I don't think I'm missing anything pertinent from the docs... but you never know ;-) - Does this suggest that the MOC-locking approach to Core Data in a multi-threaded environment isn't, actually, practical (or at least not in the presence of UI binding to the MOC)?

I can certainly look at changing over to a MOC-per-thread approach (though reading the docs that sounds like a bit of a pain). However, right now I'd prefer to continue with the locking method if it can be made to work in the presence of UI binding to core data - which the documentation gives no guidance on, while suggesting locking as a viable approach in general.

Suggestions greatly appreciated!!

-- lwe



_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/clarkcox3%40gmail.com

This email sent to clarkc...@gmail.com
_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to