event-loop: Memory leak if callbacks suspend
reproduce:
- create
script.phpwith the following content:
<?php
declare(strict_types=1);
use Revolt\EventLoop;
require __DIR__ . '/../../vendor/autoload.php';
$write_line = static fn(string $m, ...$args) => printf($m . "\n", ...$args);
$write_line('Server is listening on http://localhost:3030');
// Error reporting suppressed since stream_socket_server() emits an E_WARNING on failure (checked below).
$server = @stream_socket_server('tcp://localhost:3030', $errno, $_, flags: STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, context: stream_context_create([
'socket' => [
'ipv6_v6only' => true,
'so_reuseaddr' => false,
'so_reuseport' => false,
'so_broadcast' => false,
'tcp_nodelay' => false,
]
]));
if (!$server || $errno) {
throw new RuntimeException('Failed to listen localhost 3030.', $errno);
}
$watcher = null;
EventLoop::unreference(EventLoop::onSignal(SIGINT, static function () use ($server, &$watcher) {
EventLoop::cancel((string) $watcher);
fclose($server);
}));
$watcher = EventLoop::onReadable($server, function ($watcher, $resource) {
$stream = @stream_socket_accept($resource, timeout: 0.0);
if (false === $stream) {
EventLoop::cancel($watcher);
return;
}
stream_set_read_buffer($stream, 0);
stream_set_blocking($stream, false);
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onReadable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
$request = stream_get_contents($stream);
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, "HTTP/1.1 200 OK\n");
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, "Server: TCP Server\n");
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, "Connection: close\n");
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, "Content-Type: text/html; charset=utf-8\n\n");
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, "<h3>Hello, World!</h3>");
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, "<pre><code>" . htmlentities($request) . "</code></pre>");
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, sprintf('memory usage: %dMiB<br />', round(memory_get_usage() / 1024 / 1024, 1)));
$suspension = EventLoop::createSuspension();
$watcher = EventLoop::onWritable($stream, function () use ($suspension) {
$suspension->resume();
});
$suspension->suspend();
EventLoop::cancel($watcher);
fwrite($stream, sprintf('peak memory usage: %dMiB<br />', round(\memory_get_peak_usage() / 1024 / 1024, 1)));
@fclose($stream);
});
EventLoop::run();
$write_line('');
$write_line('Goodbye š');
- run
php script.php
memory keeps going up with each request ( noticeable with a request batch of 10k )
It seems suspensions are not being destructed properly and taking space in memory?
Iām not really sure what is happening, i first noticed the leak in https://github.com/azjezz/hack-php-async-io/blob/main/src/server.php, and went on simplifying the code to isolate it, until i reached this step, where no PSL code was involved, which means the leak is happening in revolt, or Iām doing something wrong.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 16 (15 by maintainers)
The memory leak in PSL is unrelated, this issue seems to be fixed.
Thank you @trowski and @kelunik š
I have the same issue on Linux/Ubuntu. if you comment out
readHeadersit is not leaking. Here is my example: