I'm getting random crashes in a background thread in my document-based app.
NSDocument(NSDocumentSaving)
causes EXC_BAD_ACCESS (SIGSEGV)
/ KERN_INVALID_ADDRESS
Thread 10 Crashed:: Dispatch queue: com.apple.root.default-qos
0 libobjc.A.dylib 0x00007fff2020581d objc_msgSend + 29
1 com.apple.AppKit 0x00007fff237b1909 __85-[NSDocument(NSDocumentSaving) _saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke_4.860 + 245
2 libdispatch.dylib 0x00007fff201bc623 _dispatch_call_block_and_release + 12
3 libdispatch.dylib 0x00007fff201bd806 _dispatch_client_callout + 8
4 libdispatch.dylib 0x00007fff201bfe37 _dispatch_queue_override_invoke + 775
5 libdispatch.dylib 0x00007fff201cc818 _dispatch_root_queue_drain + 326
6 libdispatch.dylib 0x00007fff201ccf70 _dispatch_worker_thread2 + 92
7 libsystem_pthread.dylib 0x00007fff20364417 _pthread_wqthread + 244
8 libsystem_pthread.dylib 0x00007fff2036342f start_wqthread + 15
I'm wondering how I could start debugging the issue, and need some help with deciphering the crash log.
Am I correct, that based on the error log, the NSDocument
which is being saved does exist?
In that case, it would be just some other variable which has disappeared somewhere, meaning either the NSURL
or completionHandler
? Or is the whole NSDocument
a corpse at this point?
The crash occurs very randomly, so it's pretty hard to reproduce reliably.
EDIT: I found a GitHub issue with the exact same problem. Their research points to this being a race condition, which occurs when asynchronous autosave is in progress and an edit to NSDocument
is being performed.
However, if I disallow asynchronous autosaves (canAsynchronouslyWriteToURL
), drafts don't seem to get saved at all.
Is there anything I can do to prevent the asynchronous race condition?
Asynchronous saving seems to result in weird thread safety problems that are not really covered by NSDocument
documentation, or even discussed anywhere. So, for anyone else struggling with this:
In your dataOfType:
method, be careful not to fetch anything, which could be mutating while an edit is made. This is the basic solution, but apparently it's more complicated than that.
I still haven't resolved the issue completely, and have to go through thousands of lines of code to find possible culprits for this thread un-safety. I tried to make a buffer/cache which only gets updated when an edit has been made, and in dataOfType:
I also made sure to make copies of that data. This doesn't help, because it's still completely possible that the data was altered on the same millisecond, causing a new race condition.