Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leaks #1150

Closed
crystoy opened this issue Dec 17, 2019 · 14 comments
Closed

Memory leaks #1150

crystoy opened this issue Dec 17, 2019 · 14 comments

Comments

@crystoy
Copy link

crystoy commented Dec 17, 2019

Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? no

Steps required to reproduce the problem

  1. Run project which uses this serializer in swoole http server(or other CLI mode that can reproduce this case), memory_limit is set to 128M
    The simplified code looks like this:
<?php

...

$serializer = $this->getSerializer();
$server->handle('/', function($request, $response) use ($serializer) {
    $data = ['OK'];
    $response->send($serializer->serialize($data, 'json'));
});

...

public function getSerializer(): Serializer
{
    $builder = SerializerBuilder::create();
    $builder->setCacheDir(ROOT_DIR . '/temp/runtime/serializer');
    $builder->setSerializationContextFactory(function () {
        return SerializationContext::create()
            ->setSerializeNull(true);
    });
    $builder->configureHandlers(function (HandlerRegistry $registry) {
        $registry->registerSubscribingHandler(new DateTimeHandler());
    });

    return $builder->build();
}
  1. Run apache ab test: ab -n 100000 http://example.test/, "n" should be a number bigger enough

Expected Result

  • Memory usage should always at a low-level, 10~20M, etc.

Actual Result

  • Memory usage increases to 128M until the project crashes

I'm not sure if it is caused by this serializer, but when I remove it, everything goes well again.
Does anyone else encoutered this problem?
Any help would be appreciated, thanks.

@goetas
Copy link
Collaborator

goetas commented Dec 17, 2019

if you change:

$server->handle('/', function($request, $response) use ($serializer) {
    $data = ['OK'];
    $response->send($serializer->serialize($data, 'json'));
});

to

for ($i = 0, $i<1000000; $i++) {
    $data = ['OK'];
    $serializer->serialize($data, 'json');
};

and just run it as a script?

@crystoy
Copy link
Author

crystoy commented Dec 17, 2019

Thanks for your help.
I tried this script and it seems no memory leaks at all...
I will check other points to find the problem.

@goetas
Copy link
Collaborator

goetas commented Dec 17, 2019

Then is related on how your application is built.

@goetas goetas closed this as completed Dec 17, 2019
@goetas
Copy link
Collaborator

goetas commented Dec 17, 2019

Is this a reactphp application?

@crystoy
Copy link
Author

crystoy commented Dec 17, 2019

Then is related on how your application is built.

I think it is.

Is this a reactphp application?

No, it's an application based on swoole, an extreme fast asynchronous engine which is widely used in china.

@goetas
Copy link
Collaborator

goetas commented Dec 17, 2019

Consider that if you are using symfony, their compiled DI container leaks memory when executed in a loop. But I think here you will have to figure it out. (related to https://bugs.php.net/bug.php?id=75914)

@crystoy
Copy link
Author

crystoy commented Dec 17, 2019

Wow...I'm using symfony DI indeed!
I'll do some test to check it

@goetas
Copy link
Collaborator

goetas commented Dec 17, 2019

if you disable the feature that splits the DI container into multiple files, you should have no leaks.

@crystoy
Copy link
Author

crystoy commented Dec 17, 2019

Sorry I have no idea how to disable this feature...
And what makes me confused is that when I use a json_encode() instead of serializer, it also solve the problem...

@crystoy
Copy link
Author

crystoy commented Dec 18, 2019

After many times of try, I believe it's not symfony DI's fault, because I even rewrite the DI container but without any effects...
And last I find maybe it's related on symfony console component.
Here are some test cases without using symfony DI, it needs to run in CLI mode with swoole installed

<?php

use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\SerializerInterface;
use Swoole\Coroutine\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

require 'vendor/autoload.php';

$serializer = SerializerBuilder::create()->build();

class StartCommand extends Command
{

    /**
     * @var SerializerInterface
     */
    private $serializer = null;

    public function setSerializer(SerializerInterface $serializer)
    {
        $this->serializer= $serializer;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        go(function () {
            $server = new Server('127.0.0.1', 3000);
            $server->handle('/', function (Request $request, Response $response) {
                $data = ['OK'];
                if ($this->serializer) {
                    $response->end($this->serializer->serialize($data, 'json'));
                } else {
                    $response->end(json_encode($data));
                }
            });
            $server->start();
        });

        return 0;
    }

}

// serializer and symfony console
$test1 = function () use ($serializer) {
    $app = new Application();
    $command = new StartCommand('start');
    $command->setSerializer($serializer);
    $app->add($command);
    $app->run();
};

// json_encode and symfony console
$test2 = function () {
    $app = new Application();
    $app->add(new StartCommand('start'));
    $app->run();
};

// serializer and no symfony console
$test3 = function () use ($serializer) {
    go(function () use ($serializer) {
        $server = new Server('127.0.0.1', 3000);
        $server->handle('/', function (Request $request, Response $response) use ($serializer) {
            $data = ['OK'];
            $response->end($serializer->serialize($data, 'json'));
        });
        $server->start();
    });
};

//$test1(); // About 88M memory usage(in monitor) after 50,000 requests
//$test2(); // About 8.5M memory usage(in monitor) after 50,000 requests
//$test3(); // About 14.5M memory usage(in monitor) after 50,000 requests

@goetas

@goetas
Copy link
Collaborator

goetas commented Dec 18, 2019

What is the memory after a single iteration?

@crystoy
Copy link
Author

crystoy commented Dec 18, 2019

What is the memory after a single iteration?

I didn't test it.

BUT I find a usable solution ...
Just place the go() function at the top level, not in execute() method will solve the problem...
I don't know why, but it just works!
There maybe some conflicts between swoole and symfony console, I can't figure it out, hope someone can do..
Anyway, thanks a lot for your careful help

<?php

use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\SerializerInterface;
use Swoole\Coroutine\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

require 'vendor/autoload.php';

$serializer = SerializerBuilder::create()->build();

class StartCommand extends Command
{

    /**
     * @var SerializerInterface
     */
    private $serializer = null;

    public function setSerializer(SerializerInterface $serializer)
    {
        $this->serializer= $serializer;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $server = new Server('127.0.0.1', 3000);
        $server->handle('/', function (Request $request, Response $response) {
            $data = ['OK'];
            if ($this->serializer) {
                $response->end($this->serializer->serialize($data, 'json'));
            } else {
                $response->end(json_encode($data));
            }
        });
        $server->start();

        return 0;
    }

}

// This is correct
go(function () use ($serializer) {
    $app = new Application();
    $command = new StartCommand('start');
    $command->setSerializer($serializer);
    $app->add($command);
    $app->run();
});

@goetas
Copy link
Collaborator

goetas commented Dec 18, 2019 via email

@crystoy
Copy link
Author

crystoy commented Dec 18, 2019

OK, nice tool, I'll have a try later, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants