ID:               48781
 Comment by:       nightstorm at tlen dot pl
 Reported By:      nate at frickenate dot com
 Status:           No Feedback
 Bug Type:         Scripting Engine problem
 Operating System: Debian 5.0 kernel 2.6.24-23-xen
 PHP Version:      5.3.0
 New Comment:

I confirm, there is a true memory leak. Consider the following script,
where the references are unmounted manually with an extra dispose()
method:

~~~~
<?php

/*
How to use:
    a) run test 1 by running code as-is
    b) run test 2 by commenting out test 1, uncomment test 2
    c) run test 3 by commenting out test 1 & 2, uncomment test 3
*/

class User {
    protected
        $profile;

    public function __construct () {
        $this->profile = new UserProfile($this);
    }
    
    public function dispose()
    {
                $this->profile->dispose();
                $this->profile = null;
    } // end dispose();
}

class UserProfile {
    private
        $user;

    public function __construct ($user) {
        $this->user = $user;
    }
    
    public function dispose()
    {
                $this->user = null;
    } // end dispose();
}

for ($userid = 1; ; $userid++) {
        if(isset($user))
        {
                $user->dispose();
        }
    $user = new User;

    if ($userid % 100000 == 0)
        printf("memory usage after %s users: %s MB.\n",
number_format($userid), number_format(memory_get_usage() / 1024 /
1024,
2));

    continue;
}
~~~~~

In this case the result is still the same (PHP 5.3.1):

~~~~
memory usage after 100,000 users: 0.61 MB.
memory usage after 200,000 users: 0.61 MB.
memory usage after 300,000 users: 0.61 MB.
memory usage after 400,000 users: 0.61 MB.
memory usage after 500,000 users: 0.61 MB.
memory usage after 600,000 users: 0.61 MB.
memory usage after 700,000 users: 0.61 MB.
memory usage after 800,000 users: 0.61 MB.
memory usage after 900,000 users: 0.61 MB.
memory usage after 1,000,000 users: 0.61 MB.
memory usage after 1,100,000 users: 0.61 MB.
memory usage after 1,200,000 users: 0.61 MB.
~~~~

If we enable the garbage collector and remove the dispose() method, the
used memory level begins to increase, and if we call gc_collect_cycles()
after creating a new object, the used memory increases even much faster.
On my PC, it is not able to perform display even a single control
message after 100000 iterations. It stopped after approx. 33000 users:

~~~~
memory usage after 30,000 users: 25.37 MB.
memory usage after 31,000 users: 26.07 MB.
memory usage after 32,000 users: 26.76 MB.
memory usage after 33,000 users: 31.46 MB.

Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to
allocate 89 bytes) in /home/me/test/memleak.php on line 17
~~~~


Previous Comments:
------------------------------------------------------------------------

[2009-12-15 01:00:00] php-bugs at lists dot php dot net

No feedback was provided for this bug for over a week, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".

------------------------------------------------------------------------

[2009-12-08 15:23:16] nate at frickenate dot com

No change, same problem. Considering this bug hasn't even been looked
at or assigned, no surprise there.

------------------------------------------------------------------------

[2009-12-07 09:12:35] j...@php.net

Please try using this snapshot:

  http://snaps.php.net/php5.3-latest.tar.gz
 
For Windows:

  http://windows.php.net/snapshots/



------------------------------------------------------------------------

[2009-07-02 18:35:42] nate at frickenate dot com

Description:
------------
The new cyclical garbage collector isn't collecting everything it
should be - somewhere there is a memory leak. The issue I am bringing up
is not about delayed collection - this is a true memory leak with memory
that is never reclaimed.

When a variable (in this example, $user) contains an object with a
cyclical reference, there is a small amount of memory that is leaked/not
reclaimed when you set $user to another object (of any class). If you
set $user to an int/string/boolean/null before setting $user to another
object, all memory is collected properly.

I came to the conclusion that this must be a problem with the garbage
collector, since if you replace the "$this->user = $user;" with
"$this->user = new StdClass;" (thus preventing the creation of a
cyclical reference), the leak goes away.

So basically right now:

<?php

while ($user = get_next_user()) {
    // do stuff with $user

    // php bug: have to unset before next iteration
    // to prevent memory leak caused by buggy gc
    unset($user);
}
?>

Reproduce code:
---------------
<?php

/*
How to use:
    a) run test 1 by running code as-is
    b) run test 2 by commenting out test 1, uncomment test 2
    c) run test 3 by commenting out test 1 & 2, uncomment test 3
*/

class User {
    protected
        $profile;

    public function __construct () {
        $this->profile = new UserProfile($this);
    }
}

class UserProfile {
    private
        $user;

    public function __construct ($user) {
        $this->user = $user;
    }
}

for ($userid = 1; ; $userid++) {
    $user = new User;

    if ($userid % 100000 == 0)
        printf("memory usage after %s users: %s MB.\n",
number_format($userid), number_format(memory_get_usage() / 1024 / 1024,
2));

    // TEST 1 - do nothing before we set the
    // new User on next iteration of the loop).
    // RESULT: leaks memory (see "Actual result" section)
    continue;

    // TEST 2 - set $user to an empty object, before
    // we set the new User on next iteration
    // RESULT: leaks memory (see "Actual result" section)
    //$user = new StdClass; continue;

    // TEST 3 - set $user to anything other than an object,
    // before we set the new User on next iteration.
    // RESULT: does NOT leak memory (see "Expected result" section)
    //$user = 'not an object'; continue;
}

?>


Expected result:
----------------
memory usage after 100,000 users: 1.54 MB.
memory usage after 200,000 users: 1.54 MB.
memory usage after 300,000 users: 1.54 MB.
memory usage after 400,000 users: 1.54 MB.
memory usage after 500,000 users: 1.54 MB.
memory usage after 600,000 users: 1.54 MB.
memory usage after 700,000 users: 1.54 MB.
memory usage after 800,000 users: 1.54 MB.
memory usage after 900,000 users: 1.54 MB.
memory usage after 1,000,000 users: 1.54 MB.

[snip]

memory usage after 99,000,000 users: 1.54 MB.
memory usage after 99,100,000 users: 1.54 MB.
memory usage after 99,200,000 users: 1.54 MB.
memory usage after 99,300,000 users: 1.54 MB.
memory usage after 99,400,000 users: 1.54 MB.
memory usage after 99,500,000 users: 1.54 MB.
memory usage after 99,600,000 users: 1.54 MB.
memory usage after 99,700,000 users: 1.54 MB.
memory usage after 99,800,000 users: 1.54 MB.
memory usage after 99,900,000 users: 1.54 MB.
memory usage after 100,000,000 users: 1.54 MB.

Actual result:
--------------
memory usage after 100,000 users: 1.55 MB.
memory usage after 200,000 users: 1.57 MB.
memory usage after 300,000 users: 1.58 MB.
memory usage after 400,000 users: 1.59 MB.
memory usage after 500,000 users: 1.61 MB.
memory usage after 600,000 users: 1.62 MB.
memory usage after 700,000 users: 1.64 MB.
memory usage after 800,000 users: 1.65 MB.
memory usage after 900,000 users: 1.66 MB.
memory usage after 1,000,000 users: 1.68 MB.

[snip]

memory usage after 99,000,000 users: 18.43 MB.
memory usage after 99,100,000 users: 18.44 MB.
memory usage after 99,200,000 users: 18.46 MB.
memory usage after 99,300,000 users: 18.47 MB.
memory usage after 99,400,000 users: 18.48 MB.
memory usage after 99,500,000 users: 18.50 MB.
memory usage after 99,600,000 users: 18.51 MB.
memory usage after 99,700,000 users: 18.53 MB.
memory usage after 99,800,000 users: 18.54 MB.
memory usage after 99,900,000 users: 18.55 MB.
memory usage after 100,000,000 users: 18.57 MB.


------------------------------------------------------------------------


-- 
Edit this bug report at http://bugs.php.net/?id=48781&edit=1

Reply via email to