0

I have a game with a menu scene and a game play scene. When I switch back and forth eventually I get a crash,

error = 24 (Too many open files)

and it's always on a line that is trying to access a resource, example, creating a sprite,

spriteNodeWithImageNamed:

Using the I/O Activity Instrument I can see that any sound action created with playSoundFileNamed is opening the sound file and it is never closed. Going back and forth between my game scene and menu scene is building up open instances of these files. (Note: My sound actions are all properties)

I'm pretty sure what's happening is that i'm running out of file descriptors. I'd like to close these files to free up the descriptors. Does anyone know a way to do this? Or does this sound like a bug?

Thanks for the help!

Brad
  • 104
  • 8
  • Brad, I may be fighting the exact same problem. I have a similar setup. My game crashes somewhere between the 3rd - 5th switch back & forth. Where would I find the error code to check it? – Scott Jan 14 '15 at 07:32
  • Hi Scott sorry for the delay... haven't been on.... Check out section 2 of my solution below. There's a code snippet linked. Use that to monitor your file descriptors (run it once at the end of the init of a scene). If you have the same issue as me you'll see the list getting bigger every time the scene gets init'ed until you run out of FDs. I fixed it using the view controller as stated below but since doing this I've thought that maybe next app I will implement a singleton to take care of all my sounds and hold the references that way. – Brad Feb 23 '15 at 20:28

1 Answers1

0
  1. First i'm going to tell you how I kept the file descriptors from leaking (this is the solution I implemented).
  2. Then i'm going to tell you a way to force the file descriptors to be released.

1. I noticed in all my testing that when a sound file is opened using,

self.soundAction = [SKAction playSoundFileNamed:@"Song1.mp3"]

that another call of playSoundFile:@"Song1.mp3" does not open the file again as long as the reference to the first call is still valid. Neither deallocating the scene nor the action causes the file to be closed and does not release the file descriptor. However these deallocs do cause the reference to be lost. At which point you've burned a file descriptor.

The worst thing I saw in my app was a block that was run. Let's say pressing a button performs this action,

-(instancetype) init {
 .
 .
 .
self.playSound2 = [SKAction runBlock:(dispatch_block_t)^(){
    if (!weakSelf.condition1.hidden)
    {
        [weakSelf runAction:[SKAction playSoundFileNamed:@"Song2.mp3" waitForCompletion:NO]];
    }
}];
 .
 .
 .
}

Every time the button was pressed, self.playSound2 was run. So self.playSound2 would open the file and then at the end of the action the reference to playSoundFileNamed: was lost. Every time the button was pressed it burned a file descriptor. I changed the code to this,

-(instancetype) init {
 .
 .
 .
self.sound2 = [SKAction playSoundFileNamed:@"Song2.mp3" waitForCompletion:NO];

self.playSound2 = [SKAction runBlock:(dispatch_block_t)^(){
    if (!weakSelf.condition1.hidden)
    {
        [weakSelf runAction:self.sound2];
    }
}];
 .
 .
 .
}

Then self.sound2 holds the reference until the scene is deallocated and I only burn one file descriptor per scene presentation.

So... My game has one view controller which presents scene after scene. The view controller is not deallocated until the app terminates. So I created a reference in the VC to every sound. This means that every sound file is open all the time. So any future calls to it do not open the file again, they already have access to it, and these references are never lost. I do this by creating a property in the VC for each sound,

ViewController.h

@property (nonatomic, strong)SKAction *sound001;
@property (nonatomic, strong)SKAction *sound002;
 .
 .
 .

ViewController.m

self.sound001 = [SKAction playSoundFileNamed:@"Bang.mp3" waitForCompletion:NO];
self.sound002 = [SKAction playSoundFileNamed:@"Boing.mp3" waitForCompletion:NO];
 .
 .
 .

This meant that I had about 55 file descriptors used at any given time (including some that the system uses up). 39 of these were the open sound files. They do not leak anymore.

2. So I figured out a work around for forcing the file descriptors to close but I still feel like ARC should be taking care of this. I modified a code snippet found here

The code snippet lists all the file descriptors used.

This is my modified version...

#import <sys/types.h>
#import <fcntl.h>
#import <errno.h>
#import <sys/param.h>

-(void)closeFileDescriptorsForSounds
{
    int flags;
    int fd;
    char buf[MAXPATHLEN+1] ;
    int n = 1 ;

    for (fd = 0; fd < (int) FD_SETSIZE; fd++) {
        errno = 0;
        flags = fcntl(fd, F_GETFD, 0);
        if (flags == -1 && errno) {
            if (errno != EBADF) {
                return ;
            }
            else
                continue;
        }
        fcntl(fd , F_GETPATH, buf ) ;
        NSLog( @"File Descriptor %d number %d in use for: %s",fd,n , buf ) ;
        ++n ;

// My modifications to the snippet...

        NSString *str = [NSString stringWithUTF8String:buf];
        NSString *theFileName = [str lastPathComponent];

        if ([theFileName isEqualToString:@"LoudBang.mp3"])
        {
            NSLog(@"FD is LoudBang");
            NSFileHandle *loudBangHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
        }
        else if ([theFileName isEqualToString:@"QuietBang.mp3"])
        {
            NSLog(@"FD is QuietBang");
            NSFileHandle *quietBangHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
        }
    }
}

I converted buf to a string and then isolated the file name. Then I compared the file name to a known file I want to close. When a file I want to close is found the code creates an NSFileHandle around the file descriptor using the following,

NSFileHandle *loudBangHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];

This init specifically closes the file descriptor on dealloc of the NSFileHandle (which will be almost immediately). The sounds still play every time I press their associated buttons. The file descriptors are freed and used by other files. The modified code snippet isn't intended to be cut and paste into someone's project, rather to show the steps taken to force a file descriptor to be released.

Hope this helps.

Community
  • 1
  • 1
Brad
  • 104
  • 8