Gracefully close ReactPhp app
Asked Answered
S

1

6

The question is very related to Consume only N messages from RabbitMQ with react\stomp, ack them separately and then exit, but a bit more general.

For example, I have a slow I/O operation:

$port = 4000;

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$socket->on('connection', function ($conn) use ($loop){

    $conn->on('data', function ($data) use ($conn, $loop) {

        if ($data == 42) {
            // this instantly stop the loop
            $loop->stop();
        }

        $process = new React\ChildProcess\Process('sleep 5; echo ' . $data);

        $loop->addTimer(0.001, function($timer) use ($process, $conn) {
            $process->start($timer->getLoop());

            $process->stdout->on('data', function($output) use ($conn) {
                if ($output) {
                    $conn->write("> $output");
                }
            });
        });
    });
});
echo "Socket server listening on port $port.\n";
echo "You can connect to it by running: telnet localhost $port\n";
$socket->listen($port);
$loop->run();
echo "exited";

when I run $loop->run(); I want to stop it at some point, e.g. by timer, after accepting N requests, or any other event, like pcntl_signal, or data assertion, as in the example.

The challenge is to finish all started jobs before exit, and I cannot figure out how to achieve it.

In the server console I have:

Socket server listening on port 4000.
You can connect to it by running: telnet localhost 4000
exited

In the client console I have:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
1
2
3
4
5
> 1
> 2
42
Connection closed by foreign host.

where 1,2,3,4,5 were entered with 1 sec interval

Instead I would like to see something like this:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
1
2
3
4
5
> 1
> 2
42
> 3
6
> 4
> 5
Connection closed by foreign host.
Sphygmo answered 30/7, 2015 at 10:53 Comment(4)
Depending on how you handle this, you need a callback that has access to $loop, and then simply call $loop->stop(), I believeOkwu
@EliasVanOotegem: $loop->stop() stops the loop. I have updated the question to make it a bit more clear, that I need the loop to finish all started jobs before exit the script.Sphygmo
So disconnect the socket first then, and when the queue is empty - stop the loop and process will exit. However, where's the guarantee that you process won't hang?Welty
@Welty Thanks, that makes sense. Do you have any idea how to check if the queue is empty ? A process can hung at any time, it is not the subject. It is ok to exit immediately on SIGKILL. The challenge is to exit gracefully on SIGTERM.Sphygmo
H
1

As was mentioned in the comments, to exit gracefully you need to track running processes and stop the loop only when all processes are finished:

$socket->on('connection', function ($conn) use ($loop) {
    $processes = new SplObjectStorage();
    $stop = false;

    $conn->on('data', function ($data) use ($conn, $loop, $processes, &$stop) {
        if ('42' === trim($data)) {
            $stop = true;
            if (!$processes->count()) {
                $loop->stop();
            }
        }

        if ($stop) {
            return;
        }

        $process = new React\ChildProcess\Process('sleep 5; echo ' . $data);
        $processes->attach($process);

        $process->on('exit', function () use ($process, $processes, &$stop, $loop) {
            $processes->detach($process);

            if ($stop && !$processes->count()) {
                $loop->stop();
            }
        });

        $loop->addTimer(0.001, function($timer) use ($process, $conn) {
            $process->start($timer->getLoop());

            $process->stdout->on('data', function($output) use ($conn) {
                if ($output && '42' !== trim($output)) {
                    $conn->write("> $output");
                }
            });
        });
    });
});
Howes answered 6/10, 2015 at 10:22 Comment(2)
Thanks Eugene, it is exactly the workaround I ended up with. I don't believe data-handling function is the right place to keep loop management code, but it seems there is no better approach =(Sphygmo
Well, a better approach would be to emit your own 'finished' event, and attach a callback with loop management logic to it.Howes

© 2022 - 2024 — McMap. All rights reserved.