PDA

View Full Version : NSTask crashing after 7 to 15 runs




MrFusion
Feb 4, 2009, 05:14 PM
I have a strange problem, and I was wondering if someone else has run into this as well.

I have a class that executes an NSTask. Each time I want to run an NSTask, I create a new instance of that class. Everything works as it is suppose to do. The output is as expected. There is no memory leak that I am aware of. I have stepped through the routines in question many times, the flow is what it should be.

An new instance of this class is created through some interface button, and each time I start it once or twice, it runs fine.

But the thing is, If I click the button too many times (between 7 and 15) after each other, the program will crash.

Did anyone experience something similar?



kpua
Feb 4, 2009, 08:44 PM
Although I haven't tried reproducing your problem, a backtrace might immediately indicate a problem. Could you provide one?

MrFusion
Feb 5, 2009, 12:49 AM
Although I haven't tried reproducing your problem, a backtrace might immediately indicate a problem. Could you provide one?

What is a backtrace, and how do you get one? Is that the upper left part in the debug window? thanks.

kainjow
Feb 5, 2009, 10:59 AM
It's a good chance it's a memory related problem and those are impossible to solve unless there is code, so maybe post some relevant code.

kpua
Feb 5, 2009, 11:16 AM
What is a backtrace, and how do you get one? Is that the upper left part in the debug window? thanks.

This is pretty basic knowledge a programmer needs for debugging...

If the app crashes in gdb, type 'bt' to show the entire stack at the point of the crash.

If it's not in gdb, you should see the crash reporter dialog come up. Click 'report' and show us the part after where it says "Thread X Crahsed:" followed by a series of numbered lines.

Krevnik
Feb 5, 2009, 12:01 PM
It's a good chance it's a memory related problem and those are impossible to solve unless there is code, so maybe post some relevant code.

It could also be a timing issue, but again, impossible to solve unless there is code.

And a huge point to the original poster: Almost never do two programmers hit the exact same issues, especially if the problem is in the code written by one of the programmers. This is why when people ask for help, they tend to post a code snippet of some kind about the problem area.

As for the backtrace, another term for the same thing is call stack. Basically what is being asked for is "What was the order of the method calls on this?"

MrFusion
Feb 5, 2009, 12:31 PM
here is the code.



#import "MyTask.h"

//see Apple code example moriaty

@implementation MyTask
-(id)initForController:(id) control {
self = [super init];
if (self != nil) {
controller = control;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(checkTaskStatus:)
name:NSTaskDidTerminateNotification
object:nil];
}
return self;
}

#pragma mark current state
- (void)checkTaskStatus:(NSNotification *)aNotification {
if(![myTask isRunning]) {
int status = [[aNotification object] terminationStatus];
if (status)
NSLog(@"Task succeeded.");
else
NSLog(@"Task stopped.");
}
}

#pragma mark task
-(BOOL) startTask:(NSString *) command inWorkingDirectory:(NSURL *) url withOptions:(NSArray *) options {
NSMutableArray *allOptions = [[NSMutableArray alloc] initWithCapacity:2+[options count]];
[allOptions addObject:command];
[allOptions addObject:url];
[allOptions addObjectsFromArray:options];
BOOL succes = [self startTask:allOptions];
[allOptions release];
return succes;
}
-(BOOL) startTask:(NSArray *) options{
if ([options count] < 2)
return NO;

//options:
//0, NSString = shell command
//1, NSURL = working directory
//2 - count-2 = arguments
NSString *command = [options objectAtIndex:0];
NSString *workingDirectory = [[options objectAtIndex:1] path];

BOOL succes = NO;
//check if file is valid path
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:workingDirectory isDirectory:&isDir] & (isDir)) {
[controller processStarted];
//create task
myTask = [[NSTask alloc] init];
[myTask setCurrentDirectoryPath:workingDirectory];
[myTask setLaunchPath:command];
//set arguments
NSArray *args = [options subarrayWithRange:NSMakeRange(2, [options count]-2)];
[myTask setArguments:args];
//pipes -> log
NSPipe *outputPipe = [NSPipe pipe];
[myTask setStandardOutput:outputPipe];
[myTask setStandardError:outputPipe];
NSPipe *inputPipe = [NSPipe pipe];
[myTask setStandardInput:inputPipe];
//pipes -> filehandles
readHandle = [outputPipe fileHandleForReading];
writeHandle = [inputPipe fileHandleForWriting];

//observe change to output of task, so that they can be collected
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(readFromTask:)
name:NSFileHandleReadCompletionNotification
object:readHandle];
// We tell the file handle to go ahead and read in the background asynchronously, and notify
// us via the callback registered above when we signed up as an observer. The file handle will
// send a NSFileHandleReadCompletionNotification when it has data that is available.
[readHandle readInBackgroundAndNotify];

// launch the task asynchronously
[myTask launch];

succes = YES;
}
return succes;
}

-(BOOL) stopTask {
if ([myTask isRunning]) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object:readHandle];
[myTask terminate];

NSData *data = nil;
while ((data = [readHandle availableData]) && [data length]) {
[controller appendOutput: [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]];
}
}
[myTask release];
[controller processFinished];
controller = nil;
return YES;
}

#pragma mark input/output
-(void) readFromTask: (NSNotification *)aNotification{
NSData *data = [[aNotification userInfo] objectForKey:NSFileHandleNotificationDataItem];
if ([data length]) {
// Send the data on to the controller; we can't just use +stringWithUTF8String: here because -[data bytes] is not necessarily a properly terminated string.
// -initWithData:encoding: on the other hand checks -[data length]
[controller appendOutput: [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]];
// we need to schedule the file handle go read more data in the background again.
[[aNotification object] readInBackgroundAndNotify];
} else {
[self stopTask]; //no more data
}
}

-(void) writeToTask:(NSString *) output{
[writeHandle writeData:[output dataUsingEncoding:NSUTF8StringEncoding]];
}

#pragma mark cleanup
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:nil];

[super dealloc];
}

MrFusion
Feb 5, 2009, 12:43 PM
This is pretty basic knowledge a programmer needs for debugging...

If the app crashes in gdb, type 'bt' to show the entire stack at the point of the crash.

If it's not in gdb, you should see the crash reporter dialog come up. Click 'report' and show us the part after where it says "Thread X Crahsed:" followed by a series of numbered lines.

Maybe that is basic knowledge for a programmer, but cs is not my background. 80% self taught, the other 20% is introduction to programming (using pascal).

I am looking around in the xCode menu's, but I can't find a 'report'.

--edit--
Maybe I can't find backtrace, but I did find instruments. I haven't used that tool before.
I found one memory leak, which is not in the code I posted above. It is in a different class, and was a retain function instead of an alloc. That is why I missed it before. I fixed it, but the program still crashes.

kainjow
Feb 5, 2009, 08:17 PM
You might be able to get a stack trace by first showing the debugger (Run > Debugger) and then test your code by running the app via Run > Debug.

MrFusion
Feb 6, 2009, 01:29 AM
You might be able to get a stack trace by first showing the debugger (Run > Debugger) and then test your code by running the app via Run > Debug.

or just cmd-Y to start debugging. Yep, add some breakpoints to see at which one it crashes, etc. That I know. Which button to click to get a stack report I don't know. I appreciate everyones help and patience. This thread is getting irritating for me, because it shows my ignorance about something basic about xCode and I can't get past it. I hate feeling stupid.

Anyway, here is a screenshot.

kpua
Feb 6, 2009, 02:18 AM
Try removing your object as an observer of the NSFileHandleReadCompletionNotification notification in -dealloc. You remove it for the other notification you registered for, but perhaps there's a chance you're not removing the notification before deallocate as you may have expected.

It's definitely crashing because a notification is getting sent to a deallocated object. The question is which one... It's possible to see what selector is getting called on the object, but that's too much to explain here...

Just try what I said above and see if that fixes the crash.

MrFusion
Feb 6, 2009, 03:55 AM
Try removing your object as an observer of the NSFileHandleReadCompletionNotification notification in -dealloc. You remove it for the other notification you registered for, but perhaps there's a chance you're not removing the notification before deallocate as you may have expected.

It's definitely crashing because a notification is getting sent to a deallocated object. The question is which one... It's possible to see what selector is getting called on the object, but that's too much to explain here...

Just try what I said above and see if that fixes the crash.

yihaa...
Thanks.
Moving both to the dealloc solves it.
I can see the notificationpost in the screenshot, but how did can you tell that it's being sent to a deallocated object?
I really thought it would be deeper down, beyond my control. Or that not all notifications would be received by my code (as in sending mouse events to a view, where some might be dropped). That's why I didn't post any code in the first place.

kpua
Feb 6, 2009, 09:07 AM
Another Objective-C debugging tip: A crash in objc_msgSend 99 times out of 100 is due to a message being sent to a deallocated object. It happens a lot with delegates too.

kalimba
Feb 6, 2009, 09:16 AM
What is a backtrace, and how do you get one? Is that the upper left part in the debug window? thanks.

This is pretty basic knowledge a programmer needs for debugging...

If the app crashes in gdb, type 'bt' to show the entire stack at the point of the crash.

If it's not in gdb, you should see the crash reporter dialog come up. Click 'report' and show us the part after where it says "Thread X Crahsed:" followed by a series of numbered lines.

Not to derail the thread, but I've been writing software for decades, and I've never even heard the term "backtrace". In my world, what you're describing has always been called a stack trace.

kainjow
Feb 6, 2009, 09:55 AM
or just cmd-Y to start debugging. Yep, add some breakpoints to see at which one it crashes, etc. That I know. Which button to click to get a stack report I don't know. I appreciate everyones help and patience. This thread is getting irritating for me, because it shows my ignorance about something basic about xCode and I can't get past it. I hate feeling stupid.

I think learning to debug code properly takes a long time. I'm no way an expert (don't really know gdb at all, only a few commands). It takes patience and persistence. The main problem I come across is debugging other people's code. Once you write a lot of code you learn where potential problems may come from, so you prepare your code in advance to avoid these situations. But not everyone does that so when you're stuck in situations like me fixing other's code, it can be annoying :)

Krevnik
Feb 6, 2009, 10:45 AM
Not to derail the thread, but I've been writing software for decades, and I've never even heard the term "backtrace". In my world, what you're describing has always been called a stack trace.

Backtrace is a fairly GDB-centric term. It is the only place I have seen it used.

MrFusion
Feb 6, 2009, 12:34 PM
Another Objective-C debugging tip: A crash in objc_msgSend 99 times out of 100 is due to a message being sent to a deallocated object. It happens a lot with delegates too.

Ok. I will keep this one in mind.