10

I have a problem with compatibility of my application with an iOS5 b7 and GM versions.

The issue occurs in the next lines of code:

do {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);

App crashes with signal EXC_BAD_ACCESS after some iterations.

The number of passed iterations is random (from 2 till 7).

Also everything works quite well on iOS4 and iOS3.

The same issue occurs in Apple's sample: XMLPerformance Sample.

What do you think about this?

October 12th thousands of users of my app will upgrade to iOS5 and I don't want my app to be with such a strange error in the AppStore.

Nekto
  • 17,837
  • 1
  • 55
  • 65

4 Answers4

10

4 hours passed and I've found the problem. I will describe how I've resolved the problem in XMLPerformance sample.

The problem was in NSAutoreleasePool. There is @property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;. When the app starts to download Top300 Paid Apps RSS new thread is created using [NSThread detachNewThreadSelector:@selector(downloadAndParse:) toTarget:self withObject:url];. So in that thread we should keep local autorelease pool. It is done in next way:

- (void)downloadAndParse:(NSURL *)url {
    self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];

    // initializing internet connection and libxml parser.
    if (rssConnection != nil) {
         do {
             [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
         } while (!done);
    }
    // Release resources used only in this thread.
    [downloadAndParsePool release]; 
    self.downloadAndParsePool = nil;
}

So in downloadAndParse: everything looks fine. Now let's look in one method that is called when an item from RSS is parsed:

- (void)finishedCurrentSong {
    // sending new item to delegate and other ...
    countOfParsedSongs++;
    // Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the 
    // size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but 
    // taking this action too frequently would be wasteful and reduce performance.
    if (countOfParsedSongs == kAutoreleasePoolPurgeFrequency) {
        [downloadAndParsePool release];
        self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
        countOfParsedSongs = 0;
    }
}

As you see there lines :

[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];

So exactly that lines causes the exception. If I comment them everything works great.

But I decided not only to comment that lines but also replace NSAutoreleasePool in - (void)downloadAndParse:(NSURL *)url with @autorelease block as it is said that it is more efficient:

- (void)downloadAndParse:(NSURL *)url {
@autoreleasepool {

    // initializing internet connection and libxml parser.
    if (rssConnection != nil) {
         do {
             [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
         } while (!done);
    }
    // Release resources used only in this thread.
    }
}

Now everything works fine. The only problem that I haven't resolved is:

// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the 
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but 
// taking this action too frequently would be wasteful and reduce performance.

So if anybody has any thoughts about this problem can post another one answer and may be try to explain more correctly the bug fix. I will be glad to accept that answer.

Thanks.

Nekto
  • 17,837
  • 1
  • 55
  • 65
  • Original code was using one common NSAutoreleasePool and trying to reset it, when estimated that it's "full". I would have replaced that code with simple [self.downloadAndParsePool drain], which should do same thing, but be more safe. You fix was to add a second pool inside the first one. Should be ok and definitely more safe. – JOM Oct 07 '11 at 04:50
  • Thanks, JOM, for your response. – Nekto Oct 07 '11 at 05:26
2

This looks like memory problem, please check Apple Technote QA1367 "Finding EXC_BAD_ACCESS bugs in a Cocoa project"

In your code, try this to crash as soon as possible:

[item release], item = nil;

It doesn't solve the problem, just makes the crash happen earlier and hopefully give you a more meaningful callstack to study.

If you're using multi-threading, well... You could try to print "current" thread id into console to verify that everything really is run in thread where you expect them to be running. Especially verify that all UI stuff is in main thread, even when such code is run as side-effect of other code (error popups, maybe).

#include <pthread.h>
- (void)myFunction
{
    NSLog(@"Thread (%d)",
        pthread_mach_thread_np(pthread_self()));
}

Run your app with Instruments, make sure to change memory verification to happen every 1 or 2 seconds. Slow, but yet again you want to get notified as close to the actual memory problem as possible.

JOM
  • 8,139
  • 6
  • 78
  • 111
  • Yes, it is running in separate thread. And no, there are no memory problems because of everything works fine in iOS4 and Apple won't publish sample with memory errors. – Nekto Oct 06 '11 at 09:42
  • I've had some bad sample code from Apple or maybe it's just the way I used it :) Potential threading problems: all UI stuff must be in mainthread. All ALAsset stuff at least in same thread, maybe even in main (not sure). Otherwise sharing data between threads maybe with NSLock or @synchronized – JOM Oct 06 '11 at 09:48
  • Everything works fine on iOS4... And there are no UI in that thread. – Nekto Oct 06 '11 at 09:49
  • iOS4 tools are less strict, so that doesn't really mean anything. Now debugging app which runs ok on iOS5GM but fails to even compile with iOS4 sdk :(You already did "analyze" and fixed all potential problems? – JOM Oct 06 '11 at 10:03
  • UI in non-main threads: could you have code, which wants to display UI in some cases e.g. NSURLConnection, ALAssetsLibrary, NSTimer? Delegate pointer running code in wrong thread, some self should be __block self? Well, guess I run out of ideas with threading. – JOM Oct 06 '11 at 10:11
  • Thanks for your help. I've tried to describe the issue in my answer. I've voted up your answer, thanks. If you will have any thoughts about the issue I would be glad to hear them. – Nekto Oct 06 '11 at 14:22
1

Looking at your code: where did that "done" variable come from and who changes it's value and when? Now it looks pretty magical.

Also you could check the return value of runMode:beforeDate to make sure it was run. If the return value is NO, runLoop was not run at all. Maybe some other part of your code cannot handle such case?

JOM
  • 8,139
  • 6
  • 78
  • 111
  • With `done` variable everything is ok. runMode:beforeDate works. I've found the problem and will answer soon. Check it, there are also some questions. – Nekto Oct 06 '11 at 14:05
0

Just my little contribution.

As I've got the same problem, I've discover that in iOS5, you don't need to have your own NSAutoreleasePool in a thread (used by performSelectorOnMainThread).

Then, in your code (a xml parser- same as me), I think you have to separate code from iOS4 and iOS5.

With iOS4, you need NSAutoreleasePool, but not with iOS5.

ze4
  • 33
  • 3