When a PHP extension has a memory leak, mysterious crashes can result, forcing users to restart the web server for relief.
As official maintainers of the ibm_db2 and PDO_IBM extensions, we’ve been on a quest to find and eliminate any memory leaks from these popular PHP modules.
With such a comprehensive goal, we needed a strategy. For extensions that have comprehensive test suites we decided that, in addition to reviewing the usual regression tests, why not also use the tests to detect leaks?
What follows is a technical look at how we do it.
Building PHP in debug mode
A PHP executable built for debug mode will report leaks found at the runtime level, including extensions. You can enable the debug mode by passing
--enable-debug to the configure script when building PHP. It prints out a message when it does find a leak, and the location of the offending allocation:
[Mon Dec 21 12:43:55 2020] Script: '/home/calvin/src/pecl-database-pdo_ibm/tests/fvt_66620_V6_stored_proc_io_int.php' /QOpenSys/pkgs/include/php/Zend/zend_string.h(225) : Freeing 0x0700000000001a00 (48 bytes), script=/home/calvin/src/pecl-database-pdo_ibm/tests/fvt_66620_V6_stored_proc_io_int.php === Total 1 memory leaks detected ===
How memory leaks occur
Everything allocated must be freed, and freed only once. Thus, you need to keep track of where you allocate memory, and how long that memory needs to last. Otherwise, the data leaks and becomes unaccounted for even if all references to it disappear. Once you find the line of code that the leak occurred in, you can try to follow the code backwards (how it got there), or use a debugger and place a breakpoint. With that, then you can think about where it should be freed (the lifetime of the data).
If it finds multiple leaks, but one is a duplicate of another, debug mode will also report the duplication. Tests will often test the same thing in different ways, so duplicates can happen a lot.
[Wed Dec 16 11:01:27 2020] Script: '/home/calvin/src/pecl-database-pdo_ibm/tests/fvt_017c_SelectLOBs.php' /home/calvin/src/pecl-database-pdo_ibm/ibm_statement.c(149) : Freeing 0x070000000009b050 (36 bytes), script=/home/calvin/src/pecl-database-pdo_ibm/tests/fvt_017c_SelectLOBs.php Last leak repeated 1 time === Total 2 memory leaks detected ===
Your test suite will tell you
How does your test suite know there was a memory leak?
PHP’s test suite compares the output of the test with known good output. Optionally, tests can use format strings or regular expressions for fancier comparisons. If actual output does not match expected output, it’ll report the problematic test.
Because PHP’s leak reports append text to the output, the reports will be extraneous output that the test didn’t expect. This will cause the test to fail. Thus, if you have a test suite that passes, you can identify what tests are causing leaks. And because the tests are minimal viable examples containing very few lines of code, it’ll be easier to trace the source of the leak, too.