Bug #74072 [Dup]: Memory leaks with setter causing out of memory
| From: | nikic@php.net | Date: | Thu, 16 Feb 2017 12:57:26 +0000 |
| Subject: | Bug #74072 [Dup]: Memory leaks with setter causing out of memory | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-207410@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=74072&edit=1
ID: 74072
Updated by: nikic@php.net
Reported by: ealexs at gmail dot com
Summary: Memory leaks with setter causing out of memory
Status: Duplicate
Type: Bug
Package: Reproducible crash
Operating System: Debian 8
PHP Version: 7.0.15
Block user comment: N
Private report: N
New Comment:
On the limitations, quoting from the other bug report:
> A magic accessor should not trigger a magic accessor on a different property name on the same
> object. $a->__get('x') -> $a->__set('x') is fine.
> $a->__get('x') -> $b->__get('y') is fine. $a->__get('x')
> -> $a->__get('y') may still lead to unbounded memory growth, if this is done for
> many distinct property names.
It is not possible to apply this change to PHP 7.0.
As to the jumping memory usage between cycles, this is an artifact of passing "true" to
memory_get_usage(). If you do so, you will see internal details of the allocator. In this case, what
you observe is that when the memory limit is reached, an allocator GC is triggered, which may
release chunks, thus reducing memory usage below the previous value. Generally, I'd recommend
calling memory_get_usage() without argument, as the values become much harder to interpret
otherwise.
Previous Comments:
------------------------------------------------------------------------
[2017-02-16 12:30:51] ealexs at gmail dot com
@torben - less memory usage without XDEBUG, reduce memory or increase the cycles.
@nikic
Will the fix be applied for PHP 7.0.* also ?
Can you explain a bit the limitations of the fix ?
Why is the memory usage random between the cycles random ? (this worries me a bit)
------------------------------------------------------------------------
[2017-02-16 11:35:27] nikic@php.net
Duplicate of bug #65340.
------------------------------------------------------------------------
[2017-02-16 11:22:55] torben at dannhauer dot info
I can reproduce this bug with the below test script.
I have a error in a production server which seems to be the same but I'm not sure.
On my side, your testscript also fails with xdebug modul loaded, however my production error does
disappear with Xdebug module loaded.
------------------------------------------------------------------------
[2017-02-16 07:53:24] ealexs at gmail dot com
updated the "Package"
------------------------------------------------------------------------
[2017-02-11 12:03:29] ealexs at gmail dot com
I have managed to create a test script that reproduces the bug
by calling "test_set_by_method($max)" the execution works fine
Using test_set_by_magic_setter($max) it will run out of memory
<?php
header("Content-type: text");
ini_set('memory_limit', '32M');
// warm up
$obj = new TestGS();
$max = 200000;
// $t1 = microtime(true);
// this will finish ok
test_set_by_method($max);
// $set_time = microtime(true) - $t1;
// $t1 = microtime(true);
// this will cause Allowed memory size ... exhausted
test_set_by_magic_setter($max);
// $set_time = microtime(true) - $t1;
// end warm up
function test_set_by_method($max = 200000)
{
$t1 = microtime(true);
$val = 1;
$bag = [];
echo "START SET BY METHOD =======================================\n\n";
for ($i = 0; $i < $max; $i++)
{
$obj = new TestGS();
$obj->setProperty_001($val++);
$obj->setProperty_002($val++);
$obj->setProperty_003($val++);
$obj->setProperty_004($val++);
$obj->setProperty_005($val++);
$obj->setProperty_006($val++);
$obj->setProperty_007($val++);
$obj->setProperty_008($val++);
$obj->setProperty_009($val++);
$obj->setProperty_010($val++);
$obj->setProperty_011($val++);
$obj->setProperty_012($val++);
$obj->setProperty_013($val++);
$obj->setProperty_014($val++);
$obj->setProperty_015($val++);
$obj->setProperty_016($val++);
$obj->setProperty_017($val++);
$obj->setProperty_018($val++);
$obj->setProperty_019($val++);
$obj->setProperty_020($val++);
// every 10 k we dump
if ((int)(floor($i/10000)*10000) === (int)$i)
{
echo "Memory usage [{$i}]: ".(memory_get_usage(true)/1024)." KB\n";
}
$bag[] = $obj;
}
echo "Memory usage [{$i}]: ".(memory_get_usage(true)/1024)." KB\n";
echo "DONE SET BY METHOD =======================================\n\n";
return [$bag, $val];
}
function test_set_by_magic_setter($max = 200000)
{
$val = 1;
$bag = [];
echo "START SET BY MAGIC SETTER =======================================\n\n";
for ($i = 0; $i < $max; $i++)
{
$obj = new TestGS();
$obj->property_001 = $val++;
$obj->property_002 = $val++;
$obj->property_003 = $val++;
$obj->property_004 = $val++;
$obj->property_005 = $val++;
$obj->property_006 = $val++;
$obj->property_007 = $val++;
$obj->property_008 = $val++;
$obj->property_009 = $val++;
$obj->property_010 = $val++;
$obj->property_011 = $val++;
$obj->property_012 = $val++;
$obj->property_013 = $val++;
$obj->property_014 = $val++;
$obj->property_015 = $val++;
$obj->property_016 = $val++;
$obj->property_017 = $val++;
$obj->property_018 = $val++;
$obj->property_019 = $val++;
$obj->property_020 = $val++;
// every 10 k we dump
if ((int)(floor($i/10000)*10000) === (int)$i)
{
echo "Memory usage [{$i}]: ".(memory_get_usage(true)/1024)." KB\n";
}
$bag[] = $obj;
}
echo "Memory usage [{$i}]: ".(memory_get_usage(true)/1024)." KB\n";
echo "END SET BY MAGIC SETTER =======================================\n\n";
return [$bag, $val];
}
function test_get_by_method($bag)
{
// GET TEST
foreach ($bag as $obj)
{
if ($obj->getProperty_001()) $val++;
if ($obj->getProperty_002()) $val++;
if ($obj->getProperty_003()) $val++;
if ($obj->getProperty_004()) $val++;
if ($obj->getProperty_005()) $val++;
if ($obj->getProperty_006()) $val++;
if ($obj->getProperty_007()) $val++;
if ($obj->getProperty_008()) $val++;
if ($obj->getProperty_009()) $val++;
if ($obj->getProperty_010()) $val++;
if ($obj->getProperty_011()) $val++;
if ($obj->getProperty_012()) $val++;
if ($obj->getProperty_013()) $val++;
if ($obj->getProperty_014()) $val++;
if ($obj->getProperty_015()) $val++;
if ($obj->getProperty_016()) $val++;
if ($obj->getProperty_017()) $val++;
if ($obj->getProperty_018()) $val++;
if ($obj->getProperty_019()) $val++;
if ($obj->getProperty_020()) $val++;
/*
if ($obj->property_001) $val++;
if ($obj->property_002) $val++;
if ($obj->property_003) $val++;
if ($obj->property_004) $val++;
if ($obj->property_005) $val++;
if ($obj->property_006) $val++;
if ($obj->property_007) $val++;
if ($obj->property_008) $val++;
if ($obj->property_009) $val++;
if ($obj->property_010) $val++;
if ($obj->property_011) $val++;
if ($obj->property_012) $val++;
if ($obj->property_013) $val++;
if ($obj->property_014) $val++;
if ($obj->property_015) $val++;
if ($obj->property_016) $val++;
if ($obj->property_017) $val++;
if ($obj->property_018) $val++;
if ($obj->property_019) $val++;
if ($obj->property_020) $val++;*/
}
}
// 967 ms | 390 ms | 2.4 more time
// for compile / provisioning it's fine ... a lot of things will be internal
/*echo "\n\nGET time for {$max} objs : ", round($get_time*1000, 4), " ms";
echo "\n\nSET time for {$max} objs : ", round($set_time*1000, 4), " ms";
echo "\n\nTotal time: ", round((microtime(true) - $t1)*1000, 4), " ms";
*/
class TestGS
{
protected $property_001;
protected $property_002;
protected $property_003;
protected $property_004;
protected $property_005;
protected $property_006;
protected $property_007;
protected $property_008;
protected $property_009;
protected $property_010;
protected $property_011;
protected $property_012;
protected $property_013;
protected $property_014;
protected $property_015;
protected $property_016;
protected $property_017;
protected $property_018;
protected $property_019;
protected $property_020;
public function getProperty_001() { return $this->property_001; }
public function getProperty_002() { return $this->property_002; }
public function getProperty_003() { return $this->property_003; }
public function getProperty_004() { return $this->property_004; }
public function getProperty_005() { return $this->property_005; }
public function getProperty_006() { return $this->property_006; }
public function getProperty_007() { return $this->property_007; }
public function getProperty_008() { return $this->property_008; }
public function getProperty_009() { return $this->property_009; }
public function getProperty_010() { return $this->property_010; }
public function getProperty_011() { return $this->property_011; }
public function getProperty_012() { return $this->property_012; }
public function getProperty_013() { return $this->property_013; }
public function getProperty_014() { return $this->property_014; }
public function getProperty_015() { return $this->property_015; }
public function getProperty_016() { return $this->property_016; }
public function getProperty_017() { return $this->property_017; }
public function getProperty_018() { return $this->property_018; }
public function getProperty_019() { return $this->property_019; }
public function getProperty_020() { return $this->property_020; }
public function setProperty_001(int $value) { $this->property_001 = $value; }
public function setProperty_002(int $value) { $this->property_002 = $value; }
public function setProperty_003(int $value) { $this->property_003 = $value; }
public function setProperty_004(int $value) { $this->property_004 = $value; }
public function setProperty_005(int $value) { $this->property_005 = $value; }
public function setProperty_006(int $value) { $this->property_006 = $value; }
public function setProperty_007(int $value) { $this->property_007 = $value; }
public function setProperty_008(int $value) { $this->property_008 = $value; }
public function setProperty_009(int $value) { $this->property_009 = $value; }
public function setProperty_010(int $value) { $this->property_010 = $value; }
public function setProperty_011(int $value) { $this->property_011 = $value; }
public function setProperty_012(int $value) { $this->property_012 = $value; }
public function setProperty_013(int $value) { $this->property_013 = $value; }
public function setProperty_014(int $value) { $this->property_014 = $value; }
public function setProperty_015(int $value) { $this->property_015 = $value; }
public function setProperty_016(int $value) { $this->property_016 = $value; }
public function setProperty_017(int $value) { $this->property_017 = $value; }
public function setProperty_018(int $value) { $this->property_018 = $value; }
public function setProperty_019(int $value) { $this->property_019 = $value; }
public function setProperty_020(int $value) { $this->property_020 = $value; }
public function __set($name, $value)
{
// both of these fail
// this will cause Allowed memory size ... exhausted
// $this->{'set'.$name}($value);
// this will also cause Allowed memory size ... exhausted
$this->$name = $value;
}
}
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=74072
--
Edit this bug report at https://bugs.php.net/bug.php?id=74072&edit=1