4

I have a popular iOS app, but I get a handful of crash reports that are always on the same line. I can't reproduce the bug for the life of me, but I suspect it has to do with my 3rd-Party library that doesn't use ARC, and so something is getting released when it shouldn't be.

I've tried simulating a memory warning and I've tried taking random globs of memory using malloc, and I can't reproduce the bug. But it happens often enough for many people to email every day and complain about it.

I know that the OS does some "cleanup" that releases objects that need to be auto-released, but is there a way to force this in a simulator?

philipkd
  • 910
  • 7
  • 20
  • ARC and non-ARC compiled code can be combined perfectly without any problem. This is because ARC 'only' inserts `retain`s and `release`s for you. So there must be something else. Show the crash report! – meaning-matters Aug 30 '13 at 05:44
  • Here is the crash report: http://pastebin.com/UnVPh543 I get a couple of reports exactly ending on that same part of DBRequest.m every day and, but nobody can reproduce it reliably. – philipkd Aug 30 '13 at 05:49
  • This SO question has the same type of crash: http://stackoverflow.com/questions/12901031/segv-accerr-calling-nsnotificationcenter-defaultcenter-removeobserverself-i – trojanfoe Aug 30 '13 at 05:55
  • I tracked down `SEGV_ACCERR` and in the header file it says "invalid permission for mapped object" (as opposed to the other sub code `SEGV_MAPERR` which means "address not mapped to object"). This is interesting; it looks like the memory is valid, but is read-only perhaps and you are writing to it? Might have some impact... – trojanfoe Aug 30 '13 at 06:06

1 Answers1

1

A message is being sent to a deallocated object.

Either something is trying to talk to a deallocated DBRequest, or DBRequest is trying to talk to a deallocated object.

The most common cause of this is if you do something like:

[DBRequest setNetworkRequestDelegate:self];
DBRequest *myDBRequest = [DBRequest initWithURLRequest:request andInformTarget:self selector:@selector(doSomething)];

You then start some network activity, the user moves to another view, which deallocates self, the network activity finishes, and tries to inform self that it's completed.

Make sure you are calling [myDBRequest cancel]; in 100% of the cases where the object that would be notified is going to be deallocated. The dealloc method is usually a safe place for this.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • +1 Looks plausible to me. Can you explain why the subcode would be `SEGV_ACCERR` and not `SEGV_MAPERR` in this case? – trojanfoe Aug 30 '13 at 07:31
  • While this is a good answer, the issue might be more subtle: suppose the dealloc method is executed on the main thread (e.g. UI controllers). Suppose also that `cancel` will very likely have "asynchronous" style - whose effect, namely setting the delegate to nil in the receiver, may be "happen later", possibly synchronized - that is *serialized* with other tasks running on the receiver. So, there is a potential *race condition* - and the app may still crash. – CouchDeveloper Aug 30 '13 at 08:41
  • A workaround to this race condition is to retain self until after the receiver has _acknowledged cancellation_, e.g. when sending an error to the delegate. This requires of course, that the receiver will actually do send an error when cancelled. The best approach is certainly to make the delegate a weak reference and use ARC. – CouchDeveloper Aug 30 '13 at 08:47
  • @CouchDeveloper According to the DBRequest header files, the `cancel` method "cancels the request and prevents it from sending additional messages to the delegate." Although I agree with you that they should be using ARC. – Aaron Brager Aug 30 '13 at 12:56
  • @AaronBrager I do not have the source code. It's difficult to nil a delegate without a potential race if the receiver uses private threads and does not use weak references or retains the delegate as long as it needs it. Just using a mutex isn't sufficient. DBRecord would need a mutex AND each time when accessing the delegate send a balanced retain/release. NSURLConnection uses another approach: it retains the delegate for the duration of the connection. – CouchDeveloper Aug 30 '13 at 13:15
  • I couldn't resist and take a look into the sources: it seems the offending line is: `251 [dbNetworkRequestDelegate networkRequestStopped];` The "delegate" is not retained. The cancel message will also not touch the delegate pointer. So, it seems best to explicitly setting the delegate to `nil` when the delegate itself is deallocating. Depending on which thread this happens, and on which thread the delegate is accessed, this may not be thread safe, . – CouchDeveloper Aug 30 '13 at 13:34
  • The problem is that this class uses the same global instance variable to store the delegate for all requests. So if you set the delegate to `nil` that will stop other network requests elsewhere in the app from working. – Aaron Brager Aug 30 '13 at 16:16
  • Is there a way to force these race conditions to happen? Because even if I apply the suggested fixes, I need to reproduce the crash on my own machine, which I haven't done before. – philipkd Aug 30 '13 at 19:44
  • Here's the code for my DBRestClient wrapper: http://pastebin.com/RCDnubah and nobody on Dropbox developers has seen this crash, so maybe it's my wrapper. But still I would like to reproduce the crash first! – philipkd Aug 30 '13 at 19:46