GraphQLBundle icon indicating copy to clipboard operation
GraphQLBundle copied to clipboard

add kernel.reset tag to allow clearing memory between request

Open Nyholm opened this issue 8 months ago • 4 comments

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Documented? no
Fixed tickets
License MIT

When you run your application in an environment like FrankenPHP, RoadRunner etc, then you need to clear memory from one request to another. Using tag kernel.reset is the best way to tell Symfony to run the reset() method between requests.

Nyholm avatar May 27 '25 08:05 Nyholm

~~I spent all night debugging to find the memory leak and finally came across your PR. Thanks, it works perfectly ❤️. I hope this PR will be accepted soon.~~

It doesn't work

currently using swoole

image

MGDSoft avatar May 31 '25 01:05 MGDSoft

@Nyholm, ~I attempted to simulate this using the Terminate event. However, in the second response, it states that "At least one schema should be declared." How is it possible for reset tags to work but not with terminate?~

~Here is my current code:~

<?php

#[AsEventListener(event: KernelEvents::TERMINATE, method: 'onTerminate', priority: -100)]
class MemoryClearAfterEachRequest implements ServiceSubscriberInterface
{
    public function __construct(
        private ?ContainerInterface $locator,
    ) {
    }

    public function onTerminate(): void
    {
        if (!\extension_loaded('openswoole')) {
            return;
        }

        // Overblog GraphQL Bundle
        if ($this->locator->has('Overblog\GraphQLBundle\Resolver\TypeResolver')) {
            $graphQLTypeResolver = $this->locator->get('Overblog\GraphQLBundle\Resolver\TypeResolver');
            (new \ReflectionProperty($graphQLTypeResolver, 'cache'))->setValue($graphQLTypeResolver, []);
        }

        if ($this->locator->has('Overblog\GraphQLBundle\Request\Executor')) {
            $graphQLExecutor = $this->locator->get('Overblog\GraphQLBundle\Request\Executor');
            (new \ReflectionProperty($graphQLExecutor, 'schemas'))->setValue($graphQLExecutor, []);
        }
    }
    
    public static function getSubscribedServices(): array
    {
        return [
            'Overblog\GraphQLBundle\Request\Executor' => '?Overblog\GraphQLBundle\Request\Executor',
            'Overblog\GraphQLBundle\Resolver\TypeResolver' => '?Overblog\GraphQLBundle\Resolver\TypeResolver',
        ];
    }
}

MGDSoft avatar Jun 02 '25 15:06 MGDSoft

I tried again with a reset event, but it didn't work. The same error appeared as with the terminate event.

Changing the service Overblog\GraphQLBundle\Request\Executor to lazy and resetting it during termination reduces memory usage. However, this approach requires recreating the entire schema for each request.

MGDSoft avatar Jun 02 '25 19:06 MGDSoft

Finally, I couldn't locate the memory leak, so I decided to reduce max_request from 3000 to 1000...

here is my metrics file from swoole. Maybe someone can spot a clue there...

{
    "up": 1,
    "version": "OpenSwoole-25.2.0",
    "master_pid": 6,
    "manager_pid": 7,
    "worker_id": 2,
    "reactor_threads_num": 4,
    "workers_total": 12,
    "workers_idle": 11,
    "task_workers_total": 0,
    "task_workers_idle": 0,
    "tasking_num": 0,
    "user_workers_total": 0,
    "dispatch_total": 5692,
    "requests_total": 5691,
    "start_time": 1748951309,
    "start_seconds": 2813,
    "max_conn": 100000,
    "connections_accepted": 676,
    "connections_active": 5,
    "connections_closed": 671,
    "reload_count": 0,
    "reload_last_time": 1748951309,
    "worker_memory_usage": 29360128,
    "worker_vm_object_num": 9393,
    "worker_vm_resource_num": 4,
    "coroutine_num": 1,
    "event_workers": [
        {
            "worker_id": 0,
            "pid": 12,
            "start_time": 1748951309,
            "start_seconds": 2813,
            "request_count": 0,
            "dispatch_count": 0
        },
        {
            "worker_id": 1,
            "pid": 13,
            "start_time": 1748951309,
            "start_seconds": 2813,
            "request_count": 1063,
            "dispatch_count": 1063
        },
        {
            "worker_id": 2,
            "pid": 14,
            "start_time": 1748951309,
            "start_seconds": 2813,
            "request_count": 1008,
            "dispatch_count": 1009
        },
        {
            "worker_id": 3,
            "pid": 15,
            "start_time": 1748951309,
            "start_seconds": 2813,
            "request_count": 927,
            "dispatch_count": 927
        }, // ...
    ],
    "task_workers": [],
    "user_workers": [],
    "top_classes": {
        "GraphQL\\Language\\Token": 5588,
        "Symfony\\Component\\VarDumper\\Cloner\\Data": 1720,
        "Closure": 502,
        "Doctrine\\ORM\\Mapping\\FieldMapping": 79,
        "Doctrine\\ORM\\Query\\TokenType": 79,
        "GraphQL\\Type\\Definition\\NonNull": 69,
        "Symfony\\Component\\Serializer\\Mapping\\AttributeMetadata": 56,
        "GraphQL\\Type\\Definition\\FieldDefinition": 55,
        "Doctrine\\Persistence\\Reflection\\TypedNoDefaultReflectionProperty": 48,
        "GraphQL\\Language\\AST\\NodeList": 44,
        "GraphQL\\Language\\AST\\Location": 43,
        "Doctrine\\Persistence\\Reflection\\RuntimeReflectionProperty": 33
    }
}

MGDSoft avatar Jun 03 '25 12:06 MGDSoft

When running the latest update, I get the error I mentioned. Executed with symfonyruntime & runtime/swoole

{
  "code": "RuntimeException",
  "message": "At least one schema should be declare.",
  "trace": [[
}
image

MGDSoft avatar Oct 03 '25 18:10 MGDSoft