PDA

View Full Version : Unloading NSBundle causing application crash.




Marimuthu
May 8, 2012, 12:30 AM
Dear All,

I am a developer working on an application developed on Lion using Xcode 4.3.2.

One of the modules of the app is to load an NSBundle dynamically and use the same to query some information.
I find that when I try to unload the bundle, the application crashes a few seconds later and the calls tack in crash report is as provided below.

I believe this type of error happens when user tries to release the same object twice. It tried running the NSZombieEnabled technique along with Xcode but to no avail.



Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x000000000396d310

VM Regions Near 0x396d310:
mapped file 000000000395a000-0000000003960000 [ 24K] r--/rwx SM=COW /Applications/########.app/Contents/Resources/information.png
-->
MALLOC metadata 0000000003974000-0000000003975000 [ 4K] r--/rwx SM=ZER

Application Specific Information:
objc[717]: garbage collection is OFF

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 com.apple.CoreFoundation 0x9c120fc1 CFRelease + 49
1 com.apple.CoreFoundation 0x9c1698e6 -[__NSArrayI dealloc] + 246
2 libobjc.A.dylib 0x9367754e _objc_rootRelease + 47
3 libobjc.A.dylib 0x93678c58 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 404
4 com.apple.CoreFoundation 0x9c14be05 _CFAutoreleasePoolPop + 53
5 com.apple.Foundation 0x95e4476e -[NSAutoreleasePool drain] + 131
6 com.apple.AppKit 0x9c370cbd -[NSApplication run] + 791
7 com.apple.AppKit 0x9c601c59 NSApplicationMain + 1054
8 com.App.MyApp 0x0000323d start + 53

In my application which is multithreaded, I load the bundle in a different thread (not the main thread) and unload it in a different thread (this too is not the main thread). Could this possibly be resulting in the crash? Should the bundle load and unload operation be performed on the same thread?

Can you guys also please let me know as to what info you guys can extract from the call stack above?

Thanks & Regards.



Sydde
May 8, 2012, 01:50 AM
The documentation says that before you unload a NSBundle, you must make sure that there are no objects in existence that reference any classes defined by the bundle, since the bundle contains the code for those classes. Since the crash is occurring on the main thread, while draining its autorelease pool, this may be what is happening. Make sure your secondary and tertiary threads can keep track of any such resources and either dispose of them properly or wait until they have been disposed of before unloading the bundle. If this is what is happening, you could consider translating what you get from the bundle into a different class before passing it to the main thread.

Marimuthu
May 8, 2012, 03:12 AM
Hi Sydde,

Thanks for the reply.

I am pretty sure that any object that is created is also being released within the bundle before the bundle is unloaded. I have run the Leak test using Instruments tool to plug all the memory leaks that were present in the code.

Is it possible that trying to release an object twice could result in such kind of a crash? (though Zombie did not show any such instance).

Sydde
May 8, 2012, 11:38 AM
Here is what I see: on the main thread, an object named ArrayI is causing the problem when the autorelease pool is drained. Find out how you are handling this object and what it contains, then see what you can do to properly synchronize its lifespan.

If you create an autoreleased object, add it to an array, then release the object, it will be deallocated while it is still in the array when the pool is drained, causing a crash like this when the array is released.

gnasher729
May 8, 2012, 11:52 AM
Here is what I see: on the main thread, an object named ArrayI is causing the problem when the autorelease pool is drained. Find out how you are handling this object and what it contains, then see what you can do to properly synchronize its lifespan.

If you create an autoreleased object, add it to an array, then release the object, it will be deallocated while it is still in the array when the pool is drained, causing a crash like this when the array is released.

The array is probably created with [NSArray arrayWithObject:anObject]

Catfish_Man
May 8, 2012, 12:21 PM
Dear All,

I am a developer working on an application developed on Lion using Xcode 4.3.2.

One of the modules of the app is to load an NSBundle dynamically and use the same to query some information.
I find that when I try to unload the bundle, the application crashes a few seconds later and the calls tack in crash report is as provided below.

I believe this type of error happens when user tries to release the same object twice. It tried running the NSZombieEnabled technique along with Xcode but to no avail.



Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x000000000396d310

VM Regions Near 0x396d310:
mapped file 000000000395a000-0000000003960000 [ 24K] r--/rwx SM=COW /Applications/########.app/Contents/Resources/information.png
-->
MALLOC metadata 0000000003974000-0000000003975000 [ 4K] r--/rwx SM=ZER

Application Specific Information:
objc[717]: garbage collection is OFF

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 com.apple.CoreFoundation 0x9c120fc1 CFRelease + 49
1 com.apple.CoreFoundation 0x9c1698e6 -[__NSArrayI dealloc] + 246
2 libobjc.A.dylib 0x9367754e _objc_rootRelease + 47
3 libobjc.A.dylib 0x93678c58 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 404
4 com.apple.CoreFoundation 0x9c14be05 _CFAutoreleasePoolPop + 53
5 com.apple.Foundation 0x95e4476e -[NSAutoreleasePool drain] + 131
6 com.apple.AppKit 0x9c370cbd -[NSApplication run] + 791
7 com.apple.AppKit 0x9c601c59 NSApplicationMain + 1054
8 com.App.MyApp 0x0000323d start + 53

In my application which is multithreaded, I load the bundle in a different thread (not the main thread) and unload it in a different thread (this too is not the main thread). Could this possibly be resulting in the crash? Should the bundle load and unload operation be performed on the same thread?

Can you guys also please let me know as to what info you guys can extract from the call stack above?

Thanks & Regards.


I would *strongly* recommend never unloading bundles. Just don't do it, it's incredibly difficult to do safely.

Specifically, if you ever use a constant NSString (@"foo"), and pass that to any other part of your program, you're pretty much doomed. Calling -copy will not actually create a copy of the string, so even if you try to guard yourself that way, it won't work. When the bundle is unloaded, the binary that the constant string is in goes with it, and anything that uses that string in the future will crash.

Sydde
May 8, 2012, 04:16 PM
The array is probably created with [NSArray arrayWithObject:anObject]

Most likely, but that means the array is either being created on the main thread or the main thread is adding it to its autorelease pool without first retaining it. The latter case would exhibit random crashes, because the other thread would release the array at some unknown time, possibly making it invalid when the main thread wants to use it.

If I were faced with a situation like this and absolutely did have to unload the bundle, I think I might use -performSelectorOnMainThread:withObject:waitUntilDone:YES to make sure everything was properly cleaned up first (in which case, the main thread's method should be autorelease pool bracketed).

Marimuthu
May 9, 2012, 02:16 AM
Thanks all for providing valuable guidance.

I would *strongly* recommend never unloading bundles. Just don't do it, it's incredibly difficult to do safely.

In my application which detects an inserted Mobile broadband/data device where each card is represented by an unique bundle (only one data card can be used hence only 1 device bundle will be loaded at any given time) I dynamically load the bundle corresponding to the device inserted into the machine and unload the bundle when the user plugs out the device. If the user inserts the device again, I again load the same bundle and unload the bundle when device is removed.

In such a scenario, will not calling unload on the bundle (but directly calling release on the bundle object) not cause any kind of an unexpected behaviour (like memory leaks etc) as in my case, the same bundle might needed to be loaded multiple number of times during the same application instance.

Catfish_Man
May 9, 2012, 12:01 PM
Thanks all for providing valuable guidance.



In my application which detects an inserted Mobile broadband/data device where each card is represented by an unique bundle (only one data card can be used hence only 1 device bundle will be loaded at any given time) I dynamically load the bundle corresponding to the device inserted into the machine and unload the bundle when the user plugs out the device. If the user inserts the device again, I again load the same bundle and unload the bundle when device is removed.

In such a scenario, will not calling unload on the bundle (but directly calling release on the bundle object) not cause any kind of an unexpected behaviour (like memory leaks etc) as in my case, the same bundle might needed to be loaded multiple number of times during the same application instance.

Memory leaks are not the issue I was concerned about; I'm concerned about crashes. In order to do this safely you will need to audit *all constant string usage in all the bundles*, or compile them with the flag to not use constant strings. I don't think you understand how much pain you're signing up for by using bundle unloading.

Sydde
May 9, 2012, 01:38 PM
The two questions I would have are: are the bundles built by someone else (or might they be), and does the reason for unloading them have something to do with conflicting class definitions? If each bundle, as it should, defines unique classes/subclasses, there really is no need to unload them, they do not take up that much space. And if there is a potential class conflict in code that you have written, you really ought to fix that.

Marimuthu
Jun 20, 2012, 01:35 AM
Hi Sydde,

Sorry for the delay in replying.

The two questions I would have are: are the bundles built by someone else (or might they be)
No. The bundles is build and owned by me.

does the reason for unloading them have something to do with conflicting class definitions?
Each bundle is giving a different class name. I dont think there is any conflicting class definition.

Marimuthu
Jun 25, 2012, 02:09 AM
After slogging for many days, finally figured out the code that was causing when the unload was called on the bundle.


//infoDict is an NSDictionary with some objects in it.

//line 1
NSArray *arrayKey = [NSArray arrayWithObjects: @"Key1", @"Key2", @"Key3", nil];
//line 2
NSArray *arrayValues = [infoDict objectsForKeys:arrayKey notFoundMarker:@""];
//line 3
NSString *valueOfKey1 = [arrayValues objectAtIndex:0];


When I commented code in line 1 ,2 and 3 and accessed the value for Key1 using NSDictionary directly, I found that there was no crash.

From this I could conclude that using an NSArray as illustrated above (atleast for me) in an Bundle was causing an crash.

Can you guys from your experience in Cocoa programming figure out as to what is wrong above for it to cause a crash when the bundle was unloaded?

gnasher729
Jun 25, 2012, 07:44 AM
After slogging for many days, finally figured out the code that was causing when the unload was called on the bundle.


//infoDict is an NSDictionary with some objects in it.

//line 1
NSArray *arrayKey = [NSArray arrayWithObjects: @"Key1", @"Key2", @"Key3", nil];
//line 2
NSArray *arrayValues = [infoDict objectsForKeys:arrayKey notFoundMarker:@""];
//line 3
NSString *valueOfKey1 = [arrayValues objectAtIndex:0];


When I commented code in line 1 ,2 and 3 and accessed the value for Key1 using NSDictionary directly, I found that there was no crash.

From this I could conclude that using an NSArray as illustrated above (atleast for me) in an Bundle was causing an crash.

Can you guys from your experience in Cocoa programming figure out as to what is wrong above for it to cause a crash when the bundle was unloaded?

objectAtIndex:0 will crash if the array contains no objects. In many cases nil objects are harmless (sending any message to them does nothing), but asking for an object at a non-existing index isn't. That's possibly the difference. You might just sprinkle a few NSLog statements in your code. Like printing the contents of arrayKey, infoDict, arrayValues.

Sydde
Jun 25, 2012, 09:58 AM
Can you guys from your experience in Cocoa programming figure out as to what is wrong above for it to cause a crash when the bundle was unloaded?

Seems kind of unimportant. You decrufted your code and it works correctly now, time to move on.

arrayValues clearly must have contained a reference to a constant string that existed within the bundle. The bundle was unloaded before the main thread cycled its runloop, causing the string constant to evaporate as a valid object. Then, when the main thread drained its autorelease pool, it tried to release an invalid object, causing the crash.

If you were to bracket the troublesome code with an autorelease pool that was drained at a time when the bundle was certain to still be present, the crash would not happen. Any time you have a crash related to autoreleasing (as indicated by your crash report above), squeezing a pool bracket in around the problem code can help you locate the problem. But making your code cleaner as you have is usually a better choice.