zenstruck / foundry

A model factory library for creating expressive, auto-completable, on-demand dev/test fixtures with Symfony and Doctrine.
https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html
MIT License
670 stars 75 forks source link

Memory leak issue after upgrading to v2 #725

Open nathan-de-pachtere opened 3 days ago

nathan-de-pachtere commented 3 days ago

Hi,

I’m encountering an issue with my fixtures while using zenstruck/foundry. I’m migrating to Symfony 7.1 and updating Zenstruck to v2. After the migration, my fixtures started crashing, even though I haven’t changed the data creation logic. Everything works fine with v1.

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /app/vendor/symfony/serializer/Serializer.php on line 149

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /app/vendor/symfony/serializer/Serializer.php on line 149
PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in Unknown on line 0

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in Unknown on line 0

What I’ve tried:

Setting memory_limit to -1 ⇾ This causes my computer (32GB of memory) to crash. Reducing the number of objects handled by my fixtures ⇾ No changes. Setting profiling: false in the Doctrine configuration ⇾ No changes.

Observations:

The crash happens at the first Story::load() encountered in my fixtures. image

class InvoiceFixtures extends Fixture implements DependentFixtureInterface
{
    public function load(ObjectManager $manager): void
    {
        InvoiceFactory::new(
            function () {
                return ['customer' => CustomerProfessionalFactory::random()];
            }
        )->range(10, 20)->create();

        InvoiceFactory::new(
            function () {
                return ['customer' => CustomerIndividualFactory::random()];
            }
        )->range(10, 20)->create();

        InvoiceLineFactory::new(function () {
            return ['invoice' => InvoiceFactory::random()];
        })->range(10, 15)->create();

        // No elements
        InvoiceFactory::new(function () {
            return ['customer' => CustomerProfessionalFactory::random()];
        })->range(10, 20)->create();

        EmittedInvoiceStory::load(); //CRASH HERE
        PaidInvoiceStory::load();
        CanceledInvoiceStory::load();
    }

    public function getDependencies(): array
    {
        return [
            CustomerFixtures::class,
        ];
    }
}
final class EmittedInvoiceStory extends Story
{
    public function __construct(private readonly WorkflowInterface $invoiceLifecycleStateMachine)
    {
    }

    public function build(): void
    {
        $invoices = InvoiceFactory::new(function () {
            return ['customer' => faker()->boolean() ? CustomerIndividualFactory::random() : CustomerProfessionalFactory::random()];
        })->range(2, 5)->create();

        foreach ($invoices as $invoice) {
            InvoiceLineFactory::new(['invoice' => $invoice])->range(1, 2)->create(); //CRASH HERE
            $this->invoiceLifecycleStateMachine->apply($invoice->_real(), InvoiceLifecycleWorkflow::TRANSITION_EMIT);
        }
    }
}

It’s not an immediate crash. Instead, I see my Docker container’s memory usage spike until my computer crashes.

This issue is challenging to debug. I’m happy to provide access to my private repo if that would help solve the problem.

nikophil commented 3 days ago

Hi, did you try to add a dump in your fixture / story / factory to ensure you're not facing an Infinite loop?

nathan-de-pachtere commented 2 days ago

@nikophil Yes, already done to track where it crashes. And didn't catch any infinite loop.

And the code logic didn't change, it's working with v1.x of Zenstruck. Process not going more than 190MiB of MEM USAGE.