Bug #72011 [Com]: DateTime serializes as empty object

From: Date: Wed, 15 Jun 2016 00:53:06 +0000
Subject: Bug #72011 [Com]: DateTime serializes as empty object
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-201625@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=72011&edit=1 ID: 72011 Comment by: bill at zeroedin dot com Reported by: benjamin dot roth at jaumo dot com Summary: DateTime serializes as empty object Status: Open Type: Bug Package: Date/time related Operating System: Linux PHP Version: 7.0.5 Block user comment: N Private report: N New Comment: Ok, I have a test case that exhibits the broken datetime behavior on Windows, but I can't seem to get the conditions right on Linux because the gc seems to behave quite differently, happily freeing cycles without overrunning memory limits. I have no php-fpm to test on Windows, so I can't go all the way with the bug, but I think this does provide a further hint that under certain memory utilization conditions, the gc_active flag is getting set and not reset in php-fpm. Maybe on Thursday if anyone has any hints I can tinker with this test case a bit more and see if I can get it to zombie out php-fpm on linux. Test script: --------------- <?php $memoryLimit = 1024*1024*((int)ini_get("memory_limit")); // assuming your memory limit is megs... echo "Memory limit: $memoryLimit\n"; ini_set('display_errors', 1); error_reporting(E_ALL); echo sprintf("usage: %s real: %s peak: %s peak_real: %s\n", memory_get_usage(), memory_get_usage(true), memory_get_peak_usage(), memory_get_peak_usage(true)); $cycles = []; function shutdownFunc() { echo sprintf("usage: %s real: %s peak: %s peak_real: %s\n", memory_get_usage(), memory_get_usage(true), memory_get_peak_usage(), memory_get_peak_usage(true)); // Serialize goes wrong echo serialize(new DateTime); echo "\n"; // So does print_r print_r(new DateTime); // And createFromFormat. Just super borked. print_r(DateTime::createFromFormat('Y-m-d', '2004-01-01')); } register_shutdown_function('shutdownFunc'); class F { public $next; public function __destruct() { global $foo; //$foo[] = 'bar'; } } // Do this until allowed memory is full while(memory_get_usage(true) < $memoryLimit) { //echo sprintf("cycle: %d usage: %s real: %s peak: %s peak_real: %s\n", count($cycles), memory_get_usage(), memory_get_usage(true), memory_get_peak_usage(), memory_get_peak_usage(true)); $a = new F(); $cycles[] = $a; $a->next = $a; $last = $a; $i = 0; // Build a cycle. while($i++ < 500 && memory_get_usage(true) < $memoryLimit) { $b = new F(); $last->next = $b; $b->next = $a; $last = $b; } } // Orphan all the cycles echo "Orphaning " . count($cycles) . " cycles.\n"; echo sprintf("usage: %s real: %s peak: %s peak_real: %s\n", memory_get_usage(), memory_get_usage(true), memory_get_peak_usage(), memory_get_peak_usage(true)); unset($cycles); echo sprintf("usage: %s real: %s peak: %s peak_real: %s\n", memory_get_usage(), memory_get_usage(true), memory_get_peak_usage(), memory_get_peak_usage(true)); gc_collect_cycles(); echo sprintf("usage: %s real: %s peak: %s peak_real: %s\n", memory_get_usage(), memory_get_usage(true), memory_get_peak_usage(), memory_get_peak_usage(true)); Expected result: ---------------- Memory limit: 268435456 usage: 388040 real: 2097152 peak: 400640 peak_real: 2097152 --snip-- O:8:"DateTime":3:{s:4:"date";s:26:"2016-06-14 17:42:49.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:19:"America/Los_Angeles";} DateTime Object ( [date] => 2016-06-14 17:42:49.000000 [timezone_type] => 3 [timezone] => America/Los_Angeles ) DateTime Object ( [date] => 2004-01-01 17:42:49.000000 [timezone_type] => 3 [timezone] => America/Los_Angeles ) Actual result: -------------- Memory limit: 268435456 Orphaning 11505 cycles. usage: 264856032 real: 268435456 peak: 264856032 peak_real: 268435456 Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 4096 bytes) in E:\wwwroot\platform-git\segfault_linux.php on line 55 Call Stack: 0.2040 365312 1. {main}() E:\wwwroot\platform-git\segfault_linux.php:0 usage: 266926344 real: 268435456 peak: 266926920 peak_real: 268435456 O:8:"DateTime":0:{} DateTime Object ( ) DateTime Object ( ) Previous Comments: ------------------------------------------------------------------------ [2016-06-14 22:44:46] bill at zeroedin dot com Actually no, that is just straight up crashing, nothing DateTime involved there. I'll have to try again for a working test case... <?php $a = new stdClass(); $a->next = $a; $last = $a; $i = 0; while($i++ < 500000) { $b = new stdClass(); $last->next = $b; $b->next = $a; $last = $b; } ------------------------------------------------------------------------ [2016-06-14 21:20:06] bill at zeroedin dot com Basically that code just makes a lot of work for the garbage collector to do, then waits until the execution time is almost up, and triggers the garbage collector so that the execution time expires while the garbage collector is running. Or at least I *think* that's what it's doing. Who knows. ------------------------------------------------------------------------ [2016-06-14 21:16:05] bill at zeroedin dot com @nikic thanks! I'll be running with execution time and memory limits relaxed for now, but once the patch hits a release I'll bump things back down and retest. I still can't reliably reproduce the issue. Is it possible that the garbage collector is allocating just enough memory at some stage to trigger the memory exhaustion error? I managed to get a segfault out of it on both linux and windows, fpm and cli, 5.6.22 and 7.0.4, but not the broken DateTime behavior. <?php // Segfault this shizz $start = microtime(true); set_time_limit(3); ini_set('display_errors', 1); error_reporting(E_ALL); function shutdownFunc() { if (!is_null($e = error_get_last())) { echo serialize(new DateTime); } } register_shutdown_function('shutdownFunc'); $a = new stdClass(); $a->next = $a; $last = $a; $mem_start = memory_get_usage(); echo "Building cycle...\n"; while((microtime(true) - $start < 2.9) && (memory_get_usage() - $mem_start) < 1024*1024*128) { $b = new stdClass(); $last->next = $b; $b->next = $a; $last = $b; } $i=0; echo "Waiting...\n"; while ((microtime(true)-$start < 2.9)) { $i++; } unset ($last); unset ($a); echo "Explode!\n"; gc_collect_cycles(); ------------------------------------------------------------------------ [2016-06-14 20:46:41] nikic@php.net @bill: Thanks! I've now applied https://github.com/php/php-src/commit/248fdfcf7356f2c20ab1e6afd1e9f295d08331c7 to 5.6 and upwards, which will hopefully fix the problem. We should probably still either catch bailouts during GC and reset state, or at least reset the GC state for new requests. ------------------------------------------------------------------------ [2016-06-14 20:11:31] bill at zeroedin dot com Actually yes, this bug has consistently appeared after a maximum execution time or allowed memory exhaustion bailout. After increasing the timeout in our dev environment, the bug has not reappeared, but I haven't tested the allowed memory ceiling yet. ------------------------------------------------------------------------ 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=72011 -- Edit this bug report at https://bugs.php.net/bug.php?id=72011&edit=1

« previous php.bugs (#201625) next »