PHP: wrong for long-running processes (wrong for America?)

Yesterday at work, we heard from a client for whom we’d written a web application two years ago. Although his call had nothing to do with this project, I was reminded of a problem with PHP that we’d found out about when we wrote this program: PHP doesn’t reuse freed file descriptors and thus can run out of them in a long-running process.

For this particular project, we wrote a small PHP service that needs to be running 24/7. I won’t get into details of why we needed such a service, NDA and whatnot. One thing I can tell you is that it crashes every 3-4 weeks (wow, that’s a painful thing to say…) and needs to be restarted. The client doesn’t really mind, and sees little value in paying to fix this bug. So we never got to fix the problem. Nevertheless, let’s look at it.

As I said in the first paragraph, PHP can run out of file descriptors. Here is a simple script to demonstrate the problem.

<?php
$fd = fopen('/etc/passwd', 'r');
echo "$fd\n";
fclose($fd);

$fd = fopen('/etc/fstab', 'r');
echo "$fd\n";
fclose($fd);
?>

And the program’s output:

$ php fds.php
Resource id #5
Resource id #6

Here’s the equivalent Python program for comparison purposes:

f = open('/etc/passwd')
print f.fileno()
f.close()

f = open('/etc/fstab')
print f.fileno()
f.close()

And the output:

$ python fds.py
3
3

As you can see, in the PHP program, although we clearly close the first file, the interpreter used the next file descriptor, while the same was reused in Python. If you wrap the code of these scripts inside an infinite loop, you will see that Python prints the same file descriptor over and over again while the one in PHP keeps incrementing.

And that’s the problem we bumped into; after weeks of opening and closing sockets, files and other types of resources, the program reached the last possible file descriptor and then crashed.

For the vast majority of PHP scripts out there, this is not a real problem since the interpreter is usually launched for small periods of time to generate an HTML document. It does, however, render PHP completely unsuitable for developing any sort of long-running program.

Update: I got some flack for calling resources file descriptors. First, I would like to say that when you observe the /proc//fd/ directory on Linux when the script is running, the fd #3 is indeed reused over and over again.

However, resources are not. I ran the script in an infinite loop print every 200,000th line. It went up to 2^31-1 then it wrapped into negative numbers. And finally, it reached back positive numbers. Here are the last lines of output.

Resource id #-1000000
Resource id #-800000
Resource id #-600000
Resource id #-400000
Resource id #-200000
Resource id #0
real: 105074.820s; user: 59353.445s; sys: 45313.772s; CPU: 99.61%
~/php$

There we go, PHP just exited.

7 Responses to “PHP: wrong for long-running processes (wrong for America?)”

  1. Kint Says:

    YOU’RE SO CLEVAR, WILL YOU MARRY ME?!

  2. kub4r Says:

    lolurgay

  3. Smokinn Says:

    Good to know. Tim is doing something similar right now so I’ll make sure to give him the link tomorrow morning in case he doesn’t know about this.

  4. Felix Says:

    you’re misinterpreting the php output.
    “Resource id #5″ does not mean file descriptor 5.
    the 5 is a serial number for php’s internal file objects.
    strace or ktrace of php should show that file descriptors are being re-used.
    the serial numbers don’t get reused, even when after a file object is freed.

  5. Chris Goffinet Says:

    I have to call nay on this blog post. If you run strace like Felix says, you can clearly see close() is freeing the FD on both Python and PHP.

  6. g2-9e263681488308e5e5d5e548b2f9bc99 Says:

    I opened a bug report for your problem:
    http://bugs.php.net/bug.php?id=47396

  7. Thijs de Zoete Says:

    So is there a way to fix this? Can’t you just let the daemon reload itself every week? For example say in the weekends you can automaticly start a new daemon and shut down the old one?

    Or am I overseeing some Parent/Child/Child/Child problem?
    Would be nice to test this I guess.. Maybe I will this weekend

Leave a Reply