20

We've discovered our complex iPhone app (ObjC, C++, JavaScript/WebKit) is leaking file descriptors under unusual circumstances.

I need to know which files (by file path) we are leaving open.

I want something like the BSD command "lsof", which, of course, isn't available in iOS 4, at least not to me. Ideally a C or ObjC function. Or a tool, like shark or Instruments. Just need the files for our running app, not (as with lsof) for all apps/processes.

We do all sorts of things with files, and the code that is failing with "Too many open files" hasn't changed in ages, and since the circumstances are unusual, this could have crept in months ago. So there's no need to remind me to look at code that opens files and make sure I close them. I know that already. Would be nice to narrow it down with something lsof-esque. Thanks.

Tim James
  • 1,513
  • 1
  • 14
  • 15

7 Answers7

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

+(void) lsof
{
    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 ; 
    }
}
Rich Waters
  • 1,567
  • 16
  • 15
5

For future reference, I ran into a similar problem on an iPhone 11 with iOS 13; I was creating too many file descriptors (FDs) by creating too many files and sockets. My solution was to increase FDs at runtime with setrlimit().

First I got the FD limits on my iPhone 11, with the following code:

// This goes somewhere in your code
struct rlimit rlim;

if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
    std::cout << "Soft limit: " << rlim.rlim_cur << std::endl;
    std::cout << "Hard limit: " << rlim.rlim_max << std::endl;
} else {
    std::cout << "Unable to get file descriptor limits" << std::endl;
}

After running getrlimit(), I could confirm that on iOS 13, the soft limit is 256 FDs, and the hard limit is infinite FDs. Since I was creating > 300 FDs between files and sockets, my app was crashing.

In my case I couldn't decrease the number of FDs, so I decided to increase the FD soft limit instead, with this code:

// This goes somewhere in your code
struct rlimit rlim;

rlim.rlim_cur = NEW_SOFT_LIMIT;
rlim.rlim_max = NEW_HARD_LIMIT;

if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {
    std::cout << "Unable to set file descriptor limits" << std::endl;
}

NOTE: You can find more information on gettrlimit() and setrlimit() here and here.

Jaime Ivan Cervantes
  • 3,579
  • 1
  • 40
  • 38
3

Can't you just intercept all file opens with your own function, say my_fopen, and store the descriptors along with their names so that when you have too many files opened, you can go through your list to see what's taking all the descriptors?

Jim Buck
  • 20,482
  • 11
  • 57
  • 74
  • Yeah, I used this on a PSP game since I was coming across the exact same problem (leaked file handles until system said "sorry, dude, can't open any more files"). – Jim Buck Nov 04 '10 at 17:41
  • Do you mean call my_fopen instead of fopen? We could if we used fopen directly to open all files, but we open files from C++ (fstream), ObjC, and probably (indirectly) javascript/webkit. And sockets consume FDs too. Are you suggesting some low-level hook, or some linker trick to replace fopen? Thanks. – Tim James Nov 04 '10 at 18:46
  • Yeah, I meant only for your own code if you were calling fopen directly. With fstream, maybe create a child class? For other stuff, yeah, you'd have to get into low-level hacking if you still haven't tracked down the problem. – Jim Buck Nov 04 '10 at 21:14
  • 1
    Apparently with iOS if you are using custom fonts they use a file descriptor to include them in your app, even if you are not loading them to use yet. – Brad Moore Jul 11 '14 at 11:11
3

If Instruments isn't working well for your purposes, my next recommendation would be to run your app in the simulator, and use fs_usage on the command line to track the file descriptors you're opening and closing. Something like this:

  1. In Terminal, run "sudo fs_usage -f filesys MyAppName", replacing MyAppName with the name of your app.
  2. Launch your app.
  3. Look at the file descriptor open and close commands output by fs_usage to see which files you're leaving open.
Ryan
  • 16,626
  • 2
  • 23
  • 20
2

Can you reproduce the problem running in the simulator?

If so, then you could actually use "lsof"...


update:

Ok, if you can't use the simulator, then idea #2:

When you get the "too many open files" error, call a function that iterates through all open file descriptors and dumps some information about each (for example the length and the first few bytes).

David Gelhar
  • 27,873
  • 3
  • 67
  • 84
  • We have yet to reproduce in the simulator. And we cannot get Instruments to do the right thing against the device. Thanks. – Tim James Nov 03 '10 at 22:31
1

I suggest you run your app on a jailbroken iPhone, and use Backgrounder and MobileTerminal to take a look at the files currently open.

You can get an iPhone binary of lsof here: http://modmyi.com/cydia/package.php?id=6945

Coxy
  • 8,844
  • 4
  • 39
  • 62
  • Thanks. We have one last jailbroken device here. But it and it's jailbreaking are older and it was not tolerating the lsof install. – Tim James Nov 04 '10 at 19:06
1

Instruments.app might be able to help you (/Developer/Applications/Instruments.app). Run your app using the System Usage tool in Instruments, and it'll probably show you what you need to know. Best of all, it can be used while running the app on your device, without jailbreaking it.

Ryan
  • 16,626
  • 2
  • 23
  • 20