Re: [PHP-DEV] Memory leak in eval()'d code

This is only part of a thread. view whole thread
  106093
June 28, 2019 16:49 benjamin.morel@gmail.com (Benjamin Morel)
> Theoretical it is thinkable we could either detect equal classes (but > unlikely to happen ... if it's the same why do it in eval, zthus cost > likely is higher than benefit in most cases?)
Thanks for your reply. I do it in eval() because schema.org has many types (more than 600 at the moment), and each object ("Thing") can be an instance of one or more of these types. This creates an unmanageable number of possible combinations that I cannot pre-compute, so I need to dynamically create a definition for an object that implements an arbitrary number of interfaces.
> or unload the class once > it's not needed anymore (which is hard to detect with reflection and > other mechanisms which aren't bound to an instance's zval and also not > cheap)
AFAIK, unloading a class is not possible from userland, I guess you're talking about detecting this in PHP itself? Anyway, that's an edge case, I found a workaround which is to keep a cache of freshly created objects, indexed by interface name(s). Anytime I request a combination of types that's already been handled in the past, I return a clone of the cached object. Even though the possible combinations of types are huge and impossible to predict in advance, there are never more than a handful of such combinations used in a single document, and I'd be surprised if there are actually that many combinations actually used in the wild. So this should hardly be a problem. Thank you, Ben On Fri, 28 Jun 2019 at 17:59, Johannes Schlüter <johannes@schlueters.de> wrote:
> On Fri, 2019-06-28 at 02:41 +0200, Benjamin Morel wrote: > > Hi internals, > > > > I've tracked down a memory leak to an anonymous class created within > > eval(): > > > > ``` > > for ($i = 0; $i < 1000000; $i++) { > > $object = eval('return new class {};'); > > > > if ($i % 1000 == 0) { > > echo memory_get_usage() . "\n"; > > } > > } > > > > ``` > > > > The memory usage quickly ramps up and memory_limit is reached in > > seconds. > > Without eval(), the memory usage stays flat. I'm on 7.3.6. > > > > *Is this a bug?* Or is this some inherent limitation of eval() that > > cannot > > be fixed? > > I case this is non-trivial to fix. Each invocation recompile a new > piece of code, which creates a new class. > > Similar to > > for ($i = 0; $i < 1000000; $i++) { > eval("function f$i(){}"); > } > f1(); > f2(); > ... > > or > > for ($i = 0; $i < 1000000; $i++) { > file_put_contents("c$i.php", 'return new class {};'); > $object = include "c$i.php"; > } > > Theoretical it is thinkable we could either detect equal classes (but > unlikely to happen ... if it's the same why do it in eval, zthus cost > likely is higher than benefit in most cases?) or unload the class once > it's not needed anymore (which is hard to detect with reflection and > other mechanisms which aren't bound to an instance's zval and also not > cheap) > > johannes > >
  106094
June 28, 2019 17:48 johannes@schlueters.de (=?ISO-8859-1?Q?Johannes_Schl=FCter?=)
On June 28, 2019 6:49:49 PM GMT+02:00, Benjamin Morel morel@gmail.com> wrote:
> >> or unload the class once >> it's not needed anymore (which is hard to detect with reflection and >> other mechanisms which aren't bound to an instance's zval and also >not >> cheap) > >AFAIK, unloading a class is not possible from userland, I guess you're >talking about detecting this in PHP itself?
Correct.
>Anyway, that's an edge case, I found a workaround which is to keep a >cache >of freshly created objects, indexed by interface name(s). >Anytime I request a combination of types that's already been handled in >the >past, I return a clone of the cached object. > >Even though the possible combinations of types are huge and impossible >to >predict in advance, there are never more than a handful of such >combinations used in a single document, and I'd be surprised if there >are >actually that many combinations actually used in the wild. So this >should >hardly be a problem.
This is going to be off-topic for his list: you could rethink your approach. Maybe you can generate the classes only on demand (if you can derive the information from a class name an evil way is `function __autoload($classname) { /* figure out what is needed */ eval("class $classname { ...}")}` ... absolutely evil, but hides the machinery) or you could rethink whether you really need distinct classes or whether a single type with accessor routines (either some custom or __call/__set/__get) isn't sufficient. Or something else ... johannes