Hello All, I've been struggling with a rather obscure PHP memory issue for the last few days. Here's my setup: FreeBSD 5.5 Apache2.0, PHP 5.1.6, and MySQL 4.1.x compiled from scratch (that is, we're not using FreeBSD ports) PHP is compiled with --enable-memory-limit and the limit is set to 8MB We're developing a web application that involves traversal of a hierarchical database structure (MySQL, PEAR::DB, and PEAR::DB::DataObject). Currently that traversal is done recursively, and involves visiting thousands of nodes in the tree. However, the tree is relatively flat, and the recursion never gets more than 4 or 5 calls deep. A severely truncated but illustrative version of the code of interest is: <?php // ... setup database connection and other infrastructure ... trigger_error(memory_get_usage()); $result = traverse_hierarchy(); trigger_error(memory_get_usage()); // ... do something with $result ?> Exactly what the traverse_hierarchy function does is mostly irrelevant to my question, as will become clear in a moment. In practice, the first trigger_error outputs ~2.5MB (mostly in $GLOBALS['_DB_DATAOBJECT']...), and the 2nd, after the traversal, outputs ~8MB. This puts us dangerously near our limit, and frequently causes the application to quit as it reaches the 8MB limit. The problem is this: the amount of data gathered is nowhere near the ~5.5 MB that PHP claims has been allocated during execution of traverse_hierarchy, and attempts to figure out WHERE that memory has been assigned have failed. Consider the following alterations to the code: <?php // ... setup database connection and other infrastructure ... trigger_error(memory_get_usage()); $result = traverse_hierarchy(); trigger_error(memory_get_usage()); clear_globals(); trigger_error(memory_get_usage()); // function to iteratively unset all globals function clear_globals() { foreach ($GLOBALS as $k => &$v) { if ($k === 'GLOBALS') continue; $before = memory_get_usage(); unset($v); unset($$k); unset($GLOBALS[$k]); $after = memory_get_usage(); trigger_error($k.':'.($before-$after)); unset($k); } unset($GLOBALS); } ?> The above code outputs the memory allocated before and after the traversal, and before and after a function call (clear_globals) that iteratively 'unset's all members of the $GLOBALS array. It also outputs the amount of memory reclaimed after each unset of the members of $GLOBALS. When I wrote this I expected something like the following (ignoring the superfluous error text generated by trigger_error): 2500000 8000000 (key):(memory released) . . . 40000 Where 40000 is about the minimum memory footprint of an empty PHP script as reported by memory_get_usage(), and where one of the (key):(memory released) outputs would identify the memory hogging culprit. However, what I got instead was something like: 2500000 8000000 (key):(memory released) . . . 5500000 After unsetting ALL globals within the global context (there are no stack frames present, and therefore no non-global variables), PHP still claimed my script was holding onto 5.5MB!! Note that traverse_hierarchy does not do anything that results in additional code being evaluated. There are no new function definitions, includes, or evals. So the additional memory is not being used to store any new function or class definitions. All non-global memory allocated by the function (theoretically) goes away when the function exits, and all globals allocated by the function get unset by the clear_globals function. I'm leaking memory somewhere -- the question is where? I've scoured my own code -- and even tentatively looked at DB_DataObject -- for cases of circular references, and have found none. The question is this: Given the following assumptions: 1) PHP's memory manager reclaims memory when all references to that memory are gone. 2) A reference is 'gone' when it goes out of scope or is 'unset'. 3) The only references that remain in the global context are references to globals (all non-global variables have gone out of scope and that memory reclaimed) 4) $GLOBALS is a PHP special associative array that contains the name and value of all global variables. 5) By doing unset($GLOBALS[$varname]) and unset($$varname), where $varname is each key of the $GLOBALS array, I am effectively eliminating all remaining references, and all allocated memory should be reclaimed by the memory manager (except perhaps for memory associated with function and class definitions). 6) Resources (think database resources) are automatically freed by garbage collection when there are no more references to them 7) No additional code is being evaluated within traverse_hierarchy 8) I'm correct that there aren't any circular references in my code nor in any PEAR module code Are there any other ways that user code can result in this apparent memory leak situation? If so, what are they? Or, are any of my first 6 assumptions incorrect? Thanks for any help, -- Matthew H. North mailto:mnorth@xxxxxxx -- PHP General Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php