amphp / socket

Non-blocking socket and TLS functionality for PHP based on Amp.

Home Page:https://amphp.org/socket

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Too much RAM is used

opened this issue · comments

\Amp\Loop::run(function () {
    \Amp\Loop::repeat(5 * 1000, function () {
        echo "current RAM: " . round((memory_get_usage() / 1024 / 1024), 2) . "MB, pick RAM: " . round((memory_get_peak_usage() / 1024 / 1024), 2) . "MB\n";
    });

    $server = \Amp\Socket\listen("127.0.0.1:12001");

    \Amp\call(function () use ($server) {
        /**
         * @var $client \Amp\Socket\ServerSocket
         */
        while ($client = yield $server->accept()) {
            \Amp\call(function () use ($client) {
                echo "client connected" . PHP_EOL;

                while (($chunk = yield $client->read()) !== null) {
                    echo "client data -> " . strlen($chunk) . PHP_EOL;
                }

                echo "client disonnected" . PHP_EOL;
            });
        }
    });
});

What I am doing wrong here ?
If I connect 1000 users I get 100MB of RAM used
Some time ago I already have connected 30K and I have ~100MB of RAM

UPDATE

\Amp\Loop::run(function () {
    \Amp\Loop::repeat(5 * 1000, function () {
        echo "current RAM: " . round((memory_get_usage() / 1024 / 1024), 2) . "MB, pick RAM: " . round((memory_get_peak_usage() / 1024 / 1024), 2) . "MB\n";
    });

    $clientHandler = asyncCoroutine(function (ServerSocket $client) {
        echo "client connected" . PHP_EOL;

        while (($chunk = yield $client->read()) !== null) {
            echo "client data -> " . strlen($chunk) . PHP_EOL;
        }

        echo "client disonnected" . PHP_EOL;
    });

    $server = \Amp\Socket\listen("127.0.0.1:12001");

    while ($client = yield $server->accept()) {
        $clientHandler($client);
    }
});

I try to use code from example, and I get the same problem

I think problem is here:

        while (($chunk = yield $client->read()) !== null) {
            echo "client data -> " . strlen($chunk) . PHP_EOL;
        }

or I am doing something wrong ?

Could you also provide a script that provides the 1000 clients part? I'm currently very busy, but maybe I can look into it sooner then.

\Amp\Loop::run(function () {
    echo "Driver -> " . get_class(\Amp\Loop::get()) . "\n";

    \Amp\Loop::repeat(5 * 1000, function () {
        echo "current RAM: " . round((memory_get_usage() / 1024 / 1024), 2) . "MB, pick RAM: " . round((memory_get_peak_usage() / 1024 / 1024), 2) . "MB\n";
    });

    for ($i = 0; $i < 1000; $i++) {
        client($i);
    }
});

/**
 * @param $i
 *
 * @return \Amp\Promise<\Amp\Socket\ClientSocket>
 */
function client($i)
{
    return \Amp\call(function () use ($i) {
        /**
         * @var $client \Amp\Socket\ClientSocket
         */
        $client = yield \Amp\Socket\connect("127.0.0.1:12001");

        \Amp\Loop::repeat(1000, function () use ($client, $i) {
            $data = "test";

            echo "write $i data -> " . strlen($data) . PHP_EOL;

            $client->write($data);
        });

        \Amp\call(function () use ($i, $client) {
            echo "client connected -> $i" . PHP_EOL;

            while (($chunk = yield $client->read()) !== null) {
                echo "read $i data -> " . strlen($chunk) . PHP_EOL;
            }

            echo "client disonnected -> $i" . PHP_EOL;
        });

        return $client;
    });
}

@kelunik Sorry for delay, weekend :)

Observations: If no message is written, then no problems with RAM

I have test this with 7.1.3 with UvDriver & EvDriver, i will update my php and try with last 7.1.9 and report here

Using EventDriver I currently get current RAM: 73.71MB, pick RAM: 73.78MB for 1000 clients.

Don't worry about any such short delay, I'm anyway busy. :-)

When accepting the connection, but not reading from it, I currently get an InvalidWatcherError, see amphp/byte-stream#20.

UPDATE: Tested on 7.1.9 fresh install with UvDriver & EvDriver I get the same, ~75MB RAM on 1K connected users

How much ram do you think is acceptable?

Right now I have a running server with some old version of amp_v2 and ~30K connected users use ~300MB of RAM, I think not more then 10M for 1K

I am rewriting that old server using all new features from amphp and so why I think this is a bug, old version (some commits ago) use much less RAM

A client really shouldn't use much more then 5-10 KB RAM. Needing 50+ KB for a client is definitely far too much.

Sorry @bwoebi I didn't mention! There is some other logic in my old implementation, and when measure RAM I don't really spend a lot of time, I just memory_get_usage() with all that logic, but now with simple server, there are much more RAM consumption

@umbri What's the commit ID used in the old implementation?

@kelunik I am using this one 2139b91

I'm getting below 5MB when I keep references to the clients, but don't read anything.

@kelunik same, problem is definitely somewhere in read()

This is caused by the new default chunk size of 65k instead of 8k. Currently there's no way to change that why using Amp\Socket\listen() unfortunately.

@trowski Should we just add another parameter to listen()?

@bwoebi It needs to be freed here: https://lxr.room11.org/xref/php-src%40master/ext/standard/file.c#1813

It should probably only do that if the read size is less than half the allocated space or something like that.

@kelunik not sure. Given the Zend allocator, it will typically lead to an extra copy. Often enough the data will be small enough to fit into the bucket allocator (<= 3072 bytes), causing it to allocate a new chunk of memory and copy the whole data into that newly allocated memory chunk.

Not sure whether I can generally advocate copying the whole read string in that case. However we may realloc on a page (4 KB) boundary here.

Additionally, it should be simple enough to fix the reproduce case, just set $chunk = null; at the end of the while loop.

@bwoebi It should probably just truncate the string as stream_get_contents does, see https://lxr.room11.org/xref/php-src%40master/main/streams/streams.c#1453

We might just want to switch to stream_get_contents.

You can drastically improve the RAM usage by explicitly setting the $chunk to null in the reading coroutine. We can also add a chunkSize option to ServerListenContext.

I'm closing this, as $chunk = null solves it.