Bug #74072 [Com]: Memory leaks with setter causing out of memory

From: Date: Sat, 11 Feb 2017 12:03:37 +0000
Subject: Bug #74072 [Com]: Memory leaks with setter causing out of memory
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-207268@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 Comment by: ealexs at gmail dot com Reported by: ealexs at gmail dot com Summary: Memory leaks with setter causing out of memory Status: Open Type: Bug Package: *General Issues Operating System: Debian 8 PHP Version: 7.0.15 Block user comment: N Private report: N New Comment: 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; } } Previous Comments: ------------------------------------------------------------------------ [2017-02-10 10:39:00] ealexs at gmail dot com Description: ------------ We have a framework with an ORM within. The MODEL object has a setter in it that causes the problems. We pull ~100.000 records from a DB (mysqli) in batches of 1000. I use "memory_get_usage(true)" to monitor the memory usage after each batch. The behaiviour is random. After the first batches memory_get_usage(true) = 2 MB. Then it starts to grow until it runs out of memory (set at 32MB atm). In rare cases (refreshing) it finishes. It always does the same thing! (no data changed - devel DB). If the setter (public function __set) is removed, or we avoid beeing called, then it always runs and memory_get_usage(true) stays at 2 MB. Always. <?php public function __set($property, $value) { return $this->set($property, $value); } public function set($property, $value = null, $transform_with = null, $requester = null, $reason = null) { if ($property{0} === "_") { $this->$property = $value; return; } // manage saving the old property if ((!(isset($this->_ols[$property]) || ($this->_ols && array_key_exists($property, $this->_ols)))) && (($value === null) || ($this->$property !== $value))) $this->_ols[$property] = $this->$property; // end old $set = $this->getModelType()->properties[$property]->setter; if ($set) $this->$set($value); else $this->$property = $value; } ?> I was unable to isolate the bug outside our framework atm. Here is Valgrind's output (if the setter is removed or not called these elements vanishes) ==6720== ==6720== 2,299,968 bytes in 7,986 blocks are possibly lost in loss record 9,815 of 9,817 ==6720== at 0x4C28C20: malloc (vg_replace_malloc.c:296) ==6720== by 0x831CD8: __zend_malloc (zend_alloc.c:2864) ==6720== by 0x863DF6: zend_hash_real_init_ex (zend_hash.c:140) ==6720== by 0x863DF6: zend_hash_check_init (zend_hash.c:163) ==6720== by 0x863DF6: _zend_hash_add_or_update_i (zend_hash.c:563) ==6720== by 0x863DF6: _zend_hash_add (zend_hash.c:638) ==6720== by 0x88D53C: zend_hash_add_mem (zend_hash.h:570) ==6720== by 0x88D53C: zend_get_property_guard (zend_object_handlers.c:503) ==6720== by 0x88FB76: zend_std_write_property (zend_object_handlers.c:667) ==6720== by 0x8B71E3: zend_assign_to_object (zend_execute.c:1234) ==6720== by 0x8B71E3: ZEND_ASSIGN_OBJ_SPEC_CV_CONST_HANDLER (zend_vm_execute.h:32028) ==6720== by 0x89584A: execute_ex (zend_vm_execute.h:414) ==6720== by 0x8E9566: zend_execute (zend_vm_execute.h:458) ==6720== by 0x857EF3: zend_execute_scripts (zend.c:1437) ==6720== by 0x7FB23F: php_execute_script (main.c:2492) ==6720== by 0x8EB1F9: do_cli (php_cli.c:977) ==6720== by 0x449796: main (php_cli.c:1347) ==6720== ==6720== ==6720== 18,452,736 bytes in 8,009 blocks are possibly lost in loss record 9,817 of 9,817 ==6720== at 0x4C28C20: malloc (vg_replace_malloc.c:296) ==6720== by 0x831CD8: __zend_malloc (zend_alloc.c:2864) ==6720== by 0x862C3F: zend_hash_real_init_ex (zend_hash.c:140) ==6720== by 0x862C3F: zend_hash_real_init (zend_hash.c:205) ==6720== by 0x88E192: rebuild_object_properties (zend_object_handlers.c:82) ==6720== by 0x8900B2: zend_std_write_property (zend_object_handlers.c:709) ==6720== by 0x8BCAF2: zend_assign_to_object (zend_execute.c:1234) ==6720== by 0x8BCAF2: ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_HANDLER (zend_vm_execute.h:26390) ==6720== by 0x89584A: execute_ex (zend_vm_execute.h:414) ==6720== by 0x849650: zend_call_function (zend_execute_API.c:858) ==6720== by 0x874A32: zend_call_method (zend_interfaces.c:104) ==6720== by 0x88FBD9: zend_std_call_setter (zend_object_handlers.c:216) ==6720== by 0x88FBD9: zend_std_write_property (zend_object_handlers.c:674) ==6720== by 0x8B71E3: zend_assign_to_object (zend_execute.c:1234) ==6720== by 0x8B71E3: ZEND_ASSIGN_OBJ_SPEC_CV_CONST_HANDLER (zend_vm_execute.h:32028) ==6720== by 0x89584A: execute_ex (zend_vm_execute.h:414) ==6720== by 0x8E9566: zend_execute (zend_vm_execute.h:458) ==6720== by 0x857EF3: zend_execute_scripts (zend.c:1437) ==6720== by 0x7FB23F: php_execute_script (main.c:2492) ==6720== by 0x8EB1F9: do_cli (php_cli.c:977) ==6720== by 0x449796: main (php_cli.c:1347) ==6720== ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=74072&edit=1

« previous php.bugs (#207268) next »