Question about NSFileHandle

Discussion in 'Mac Programming' started by DannySmurf, Jul 9, 2005.

  1. DannySmurf macrumors 6502a

    Joined:
    Jul 7, 2005
    #1
    First let me say that yes, I know that there are probably better ways to read a file than this. But the file I need to read in this case is a data file that must be shared with Windows (so I need access to the actual bytes to do endian conversions on OSX), and I need to jump to random locations in the file (as many as 1000 every time it's open).

    Now, my question:

    My app has been crashing at a specific location. I've managed to narrow the line it's crashing on down. If I take everything out of my function except these three lines, I see the crash:

    Code:
    NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:somepath];
    [handle closeFile];
    return @"somevalue"; //crash here
    
    If I remove the call to closeFile, the app no longer crashes. Now, I don't want to leave the file dangling open, or leave the handle unreleased, so my question is: Is handle going to be autoreleased when this function returns? And if so, will the file automatically be closed? If it will, I'm more than happy to leave the crashing closeFile call a mystery and move on.
     
  2. HiRez macrumors 603

    HiRez

    Joined:
    Jan 6, 2004
    Location:
    Western US
    #2
    According to Apple's docs, when an NSFileHandle is deallocated, it'll automatically call closeFile, so you shouldn't have to explicitly if you expect your object to be deallocated in a timely manner. However, it should not crash your app to do so. I'd suspect some sort of reference counting culprit here. I'd expect the method you use to return an autoreleased object, which should get deallocated after the method exits at the next event loop. What's the method you're calling this one from look like?
     
  3. mj_1903 macrumors 6502a

    mj_1903

    Joined:
    Feb 3, 2003
    Location:
    Sydney, Australia
    #3
    I think you are possibly along the right lines. The file handle that is created is an autoreleased object and the auto release pool may be destroyed when you return. Therefore you will be calling for an already released object to be destroyed which is not always pretty.

    If that is not the case, and I have a funny feeling it isn't, then some of the other code you have in that function would be beneficial.
     
  4. DannySmurf thread starter macrumors 6502a

    Joined:
    Jul 7, 2005
    #4
    Well, I whittled the crashing function down to just the three lines of code that I posted, but I'll post all the code I'm actually using. Even just opening the file and then closing it causes the crash. And although it's crashing on the return statement, it actually doesn't matter what I return. Even if it's an int, as long as I've opened and tried to close the file, the crash still happens.

    The code that calls that function is just as simple:

    (the crashing function is dataStore:getItemWithFolder:atIndex:)

    Code:
    /*********************
     * MainWindowController 
     **********************/
    
    - (id)tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn*)aColumn row:(int)rowIndex
    {
    	if (rowIndex == -1)
    		return nil;
    		
    	if (aTableView == folderListBox)
    	{
    		Folder *f = [folderList folderAtIndex:rowIndex];
    		NSString *name = [f folderName];
    		return name;
    	}
    	else if (aTableView == mainListBox)
    	{
    		DataObject *m = (DataObject*)[dataStore getItemWithFolder:[self selectedFolder] atIndex:rowIndex];
    		NSString *summary = [m summary];
    		return summary;
    	}
    		
    	return nil;
    }
    
    - (NSString*)selectedFolder
    {
    	NSString *ret = [folderList folderAtIndex:[folderListBox selectedRow]];
    	if (ret == nil)
    		return @"";
    	else
    		return ret;
    }
    
    /*****************
     * DataStore
     *****************/
    
    - (id)getItemWithFolder:(NSString*)folder atIndex:(int)index
    {
    	IndexedItem *ii = nil;
    	int nCounter;
    	int nCurrentIndex = -1;
    	for (nCounter = 0; nCounter < [items count]; nCounter++)
    		if ([[items objectAtIndex:nCounter] existsInFolder:folder] == YES)
    		{
    			nCurrentIndex++;
    			if (nCurrentIndex == index)
    			{
    				ii = [items objectAtIndex:nCounter];
    				break;
    			}
    		}
    		
    	if (ii == nil)
    		return nil;
    		
    	NSFileHandle *theFile = [NSFileHandle fileHandleForReadingAtPath:@"/Volumes/Data/DataStore.dat"];
    	[theFile seekToFileOffset:[ii fileOffset]];
    	NSData *dCount = [theFile readDataOfLength:4];
    	UInt8 bCount[4];
    	[dCount getBytes:bCount];
    	int nCount = [BitConverter GetInt:bCount];
    	
    	NSData *dData = [theFile readDataOfLength:nCount];
    	DataObject *o = [[DataObject alloc] init];
    		
    	[o deserializeData:dData];
    
          [theFile closeFile]; // If this is here, crash on the return statement, otherwise return properly.
    	return o;
    }
    
     
  5. whooleytoo macrumors 603

    whooleytoo

    Joined:
    Aug 2, 2002
    Location:
    Cork, Ireland.
    #5
    Not really sure why that isn't working, the code looks ok.

    It seems as if when the NSFileHandle is being released on return, it may be attempting to close the file descriptor again, causing a crash.

    Out of interest, if you send a releaseCount message to the NSFileHandle before returning, does it return 1? If so, you're safe enough not closing the file explicitly, as the NSFileHandle will be de-allocated on return. (Although, that's a workaround rather than a fix).

    Incidentally, you should be autoreleasing the returned DataObject, since that method is responsible for allocating it. Then the calling method should retain it if it needs to.
     
  6. DannySmurf thread starter macrumors 6502a

    Joined:
    Jul 7, 2005
    #6
    Not sure what you mean here. releaseCount doesn't seem to be a valid message, and I can't find it anywhere in the documentation.However...

    I just tried calling retain on the NSFileHandle before the call to closeFile, which stops the crashing, so it would seem that that indeed is the problem.

    Thanks for the advice. I'll make that change.
     
  7. whooleytoo macrumors 603

    whooleytoo

    Joined:
    Aug 2, 2002
    Location:
    Cork, Ireland.
    #7
    Ahem! My mistake, I should have said retainCount!

    Though that of course isn't a fix either, as it would leak the NSFileHandle each time (you probably knew that, but it's better to play safe and mention it, just in case!)
     
  8. DannySmurf thread starter macrumors 6502a

    Joined:
    Jul 7, 2005
    #8
    I did know that, yeah. Right now I'm MOST concerned with making sure these files are closed at the appropriate time (this has been drummed into me on Windows since in Win32 if you leave a dangling file, the contents will get completely wiped if your app closes unexpectedly and I just won't feel right without knowing that the file is properly closed).

    Anyhoo, here's the retainCount results:

    Code:
    	[o deserializeData:dData];
    	[o autorelease];
    	int n = [theFile retainCount]; // retainCount is 1
    	[theFile closeFile];
    	int n2 = [theFile retainCount]; // Crash
    
    Code:
    	[o deserializeData:dData];
    	[o autorelease];
    	int n = [theFile retainCount]; // retainCount is 1
    	[theFile retain];
    	[theFile closeFile];
    	int n2 = [theFile retainCount]; // Crash here regardless of retain
    
     
  9. HiRez macrumors 603

    HiRez

    Joined:
    Jan 6, 2004
    Location:
    Western US
    #9
    This is very strange. Based on your test code, it almost seems like calling closeFile causes a deallocation of the file handle object and nulls out its reference (causing a crash even if you try to get a retain count from it). One thing that might help: what are the exact crash messages you're getting in the original code in the run log? SIGBUS or what? Also, does the crash occur immediately after the value is returned, or is there a small delay? In my experience a small delay is usually an retain count / autorelease problem (the crash occurs when the event loop gets around to clearing the autorelease pool). You could also try to retain a single NSFileHandle object in your document instead of creating it multiple times. According to Apple's docs, closeFile will close the file but the handle will remain valid for future use (reassignment).
     
  10. DannySmurf thread starter macrumors 6502a

    Joined:
    Jul 7, 2005
    #10
    The error I'm getting in the debugger is "Cannot access memory at location 0x445fc008." I don't know where to look for a run log. Everything stops as soon as I try to step the debugger past the return statement.

    Do you mean leaving the file open for the duration of execution, or just keeping a pointer around to be constantly allocated/released? The former's not an option, but I'm sure it wouldn't be a problem just holding a pointer around for when I need it.

    You're right, this is strange, since closeFile works for me elsewhere. In fact, I'm using this exact same behaviour (open file, read into an NSData that gets passed around and then close the file) elsewhere without a problem.
     
  11. HiRez macrumors 603

    HiRez

    Joined:
    Jan 6, 2004
    Location:
    Western US
    #11
    First I would find out what object it thinks is supposed to be at that memory location, as that's probably where your problem lies. Look at the debugger offsets to see which object matches that value.

    I think whooleytoo is also correct: you should be sending that DataObject in your getItemWithFolder:atIndex: method an autorelease message before you return it, since you created it with [[... alloc] init]. Otherwise, it'll never get released and you have a memory leak.
     

Share This Page