Press "Enter" to skip to content

Please Release Your Resources Manually

I never thought this was a problem, until yesterday.
Last night, I submitted an RFC about introducing finally into PHP. The motivation for implementing this feature is simple: I saw a lot of people's need for it, and also Stas said that all he'd ever seen was discussion, never anyone actually implementing it. So I went and implemented it.
After posting it to the mailing list, a fellow developer Nikita Popov (nikic) strongly opposed this RFC. Of course, he made a lot of initial arguments, and when we finally discussed it online, he expressed one of his views:

"PHP releases all resources at the end of a request, so we have no need to call fclose, or mysql_close to release resources — PHP does it for us."

And he said he never calls fclose, believing fclose exists only for the sake of inheriting the C function family.
I was surprised, and I have no idea how many others share his view, so I decided to write this article.
Before PHP 5.2, PHP used reference counting to manage resources: when a zval's reference count reaches 0, it gets released. Although cycle references exist, this design is fine for developing web scripts, because the characteristic of web scripts — and their goal — is a short execution time; they don't run for a long time. The resource leaks caused by cycle references are released at the end of the request. In other words, releasing resources at the end of a request is a fallback (backup) measure.
However, as more and more people use PHP, a lot of people started using PHP for some background scripts. The characteristic of these scripts is that they run for a long time. If cycle references exist, causing the reference count to be unable to release unused resources in time, then that script will eventually exhaust memory and exit.
So after PHP 5.3, we introduced GC — that is, we introduced GC to solve a problem users couldn't solve themselves.
That's the history, I've introduced it briefly. Now let's go back to the question at the start: just because PHP releases all resources at the end of a request, does that mean we don't have to release them manually?
Let's look at an example:

Mysql max connections (mysql.max_connections)

 <?php
    $db = mysql_connect() ;
    $resut = mysql_query();
    // process result...
    usleep(500);
    //mysql_close($db);  let's say, you didn't call to this
    // other logic, assuming it costs 5s
    sleep(5);
    exit(0); //finish

In the example above, we hold a connection to Mysql for 5 seconds. Such a script is no big deal for a normal application, but for a script with a very high request volume, it leads to a fatal problem:
For example, a busy application that has to handle 1000 requests from users every second — how many requests in 5 seconds? 5 * 1000 = 5000. And Mysql has a max-connections limit (mysql.max_connections); this number generally doesn't exceed 2000, and the default is even lower: (mysql.max_connections).
So, code like this can make your application completely unable to serve normally. But if we close the connection right after finishing processing with Mysql, this problem won't be triggered.
In practice, we ran into an even more real problem. Look at the example below:

<?PHP
$mmc = new Memcached();
$mysql = mysql_connect();
//process
mysql_close($mysql);
$mmc->close();

This is a real lesson. The code is as above. One day, suddenly our Mysql had a problem, causing the time to connect to Mysql to increase, which then led to a script holding the Memcached connection too long, and finally Memcache, because of too many connections, started refusing service..
So, we must always initialize the resource with the highest connection cost first.

System max handles (/proc/sys/fs/file-max)

This is simple: if you keep opening handles without releasing them, you may hit the system's max-handle limit. For a process, it also has its own per-process open-handle limit (ulimit -n).

System calls are expensive (System call is expensive)

The reason PHP correctly releases all resources/memory at the end of a request is this: when we use new memory in a script, PHP requests a big chunk of memory from the OS (of size ZEND_MM_SEG_SIZE), then carves out and hands you a suitably small piece of what you need.
When you no longer use that small piece, PHP doesn't return it to the OS either; it keeps it around for subsequent use.
We know that malloc(3) leads to a system call (brk(2)) (it could also be mmap — we won't consider this detail here, thanks to 华裔), and system calls are expensive.
So, if you use a resource and don't release it in time, then if subsequent logic requests memory, PHP finds that the big chunk it requested earlier has already been fully carved up, and it has no choice but to issue another malloc call to the OS to get a new big chunk of memory. And it still needs to do some marking work on that big chunk of memory..
Whereas if you use a resource and release it in time, then the next time the script requests memory, the memory block you previously returned can be reused, and maybe your entire script only has to request memory from the OS once.

Memory peak usage

This is somewhat related to the above. When you release a resource right after using it, and then use such a resource again afterwards, PHP's memory usage is:
resource+1 -> resource-1 -> resource+1 -> resource-1 (peak is 1)
Whereas if you wait until the PHP request ends to release:
resource+1 -> resource + 1 .... -> resource -1 -> resource - 1 (peak is 2)
That is to say, a well-written script may save a lot of peak memory compared to a sloppily written one..
Consider an extreme case: on a very busy server, say there are 10 PHP processes, each PHP process up to 1G of memory, but the server only has 8G of memory.

Conclusion (conclusion)

The conclusion is obvious. As I said at the start, I never thought this was a problem.
Let me add a note: if you bought a PHP book, and it tells you "you don't need to actively release resources in PHP, because PHP will release them for you," I'd suggest you burn it.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.