doctrine / orm

Doctrine Object Relational Mapper (ORM)
https://www.doctrine-project.org/projects/orm.html
MIT License
9.86k stars 2.5k forks source link

Regression related to Improve lazy ghost performance by avoiding self-referencing closure #11455

Closed raziel057 closed 3 weeks ago

raziel057 commented 1 month ago

Bug Report

Q A
BC Break yes
Version 2.19.3

Summary

From the following commit of doctrine/orm 2.19.3 I got a OutOfMemoryError when trying to clone an entity that contains a blob value: https://github.com/doctrine/orm/commit/9d1a4973ae2d8ddedd6b66e958e32ebb58cdf3d6

If I revert this change I have no issue and the Memory is low: less than 4Mo but when I apply the change, even if I allocate 2G of memory I got the OutOfMemoryError .

Current behavior

Infinite memory rise which leads to an OutOfMemoryError.

How to reproduce

namespace App\Entity;

...

#[ORM\Entity(repositoryClass: ResourceFileRepository::class)]
class ResourceFile
{

    #[Groups(['read'])]
    #[ORM\Id]
    #[ORM\Column(name: 'ID', type: 'integer', nullable: false)]
    #[ORM\GeneratedValue(strategy: 'IDENTITY')]
    private ?int $id = null;

    ...

    /**
     * @var string|resource
     */
    #[ORM\Column(name: 'VALUE', type: 'blob', nullable: false)]
    private mixed $content = null;

    public function getCopy() : self
    {
        return clone $this;
    }

    public function __clone()
    {
        if ($this->id) {
            $this->id = null;
        }
    }
}

As soon as I call getCopy(); I got an OutOfMemoryError:

$copy = $formDefinition->getLogo()->getCopy();

image

Please note that I have no issue if I call $formDefinition->getLogo()->getContent() to load the resource before calling $formDefinition->getLogo()->getCopy() :


dump("Initial Memory Used: ".round(memory_get_usage() / (1024*1024), 1)." MB");

$formDefinition = $this->em->getRepository(FormDefinition::class)->find($id);

dump($formDefinition->getLogo()->getContent());
dump("Memory Used before getCopy: ".round(memory_get_usage() / (1024*1024), 1)." MB");

$copy = $formDefinition->getLogo()->getCopy();

dump("Memory Used after getCopy: ".round(memory_get_usage() / (1024*1024), 1)." MB");

image

Expected behavior

Just clone the entity.

raziel057 commented 1 month ago

@nicolas-grekas Can you please consider this issue as you created the commit https://github.com/doctrine/orm/commit/9d1a4973ae2d8ddedd6b66e958e32ebb58cdf3d6

If you need more information or to test something I'm available.

nicolas-grekas commented 1 month ago

A small repo with instructions to reproduce would help a lot :pray:

raziel057 commented 1 month ago

I tried to create a small / simple reproducer, but could notice the issue is also related to the number of elements I have in the ResourceFile table in database. If I reduce the number of elements I don't get the MemoryLimitError.

I also detected that I got the error if I load the parent entity. For example in the following sample The formDefinition entity contains a logo (ResourceFile id 24163). If I comment the first line I don't have any Memory issue, but if I uncomment it the memory issue happen.

$formDefinition = $entityManager->getRepository(FormDefinition::class)->find(3829);
$resourceFile = $entityManager->getRepository(ResourceFile::class)->find(24163);

$copy = clone $resourceFile;

Here is the debug:

image

And at some point a query is done without restricting on ID:

image

If in clone method of ResouyrceFile I remove the following code, I don't have any issue and no query are triggered:

    if ($this->creationDate) {
        $this->creationDate = null;
    }

image

image

The code is not very easy to read so I can't provide more information for now.

raziel057 commented 1 month ago

@nicolas-grekas Here is a reproducer:

git clone https://github.com/raziel057/demo.git -b blob-issue
cd demo/
composer install
vendor/bin/simple-phpunit

Result

image

Downgrade to doctrine/orm:2.19.2 and rerun phpunit:

composer require doctrine/orm:2.19.2
vendor/bin/simple-phpunit

image

As you can see in the first result the query doesn't contain the where restriction. That's why that was leading to an OutOfMemoryError in my case as I have a lot of entities in my table.

Can you please have a look now?

nicolas-grekas commented 1 month ago

Thanks for the reproducer. I already spotted a bug but not the one you reported :) Stay tuned.

raziel057 commented 1 month ago

Nice 👍 thanks

nicolas-grekas commented 1 month ago

This should be fixed by #11475