Apache process doesn't die after disconnection with RabbitMQ

php apache docker rabbitmq amqp

103 观看

1回复

156 作者的声誉

I was trying to use Server Side Events mechanics in my project. (This is like Long Polling on steroids)

Example from "Sending events from the server" subtitle works beautifully. After few seconds, from disconnection, the apache process is killed. This method works fine.

BUT! If I try to use RabbitMQ, Apache does't get the process killed after browser disconnects from server (es.close()). And process leaves as is and gets killed only after the docker container restarts.

connection_aborted and connection_status don't work at all. connection_aborted returns only 0 and connection_status returns CONNECTION_NORMAL even after disconnect. It happens only when I use RabbitMQ. Without RMQ this functions works well.

ignore_user_abort(false) doesn't work either.

Code example:

<?php
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AbstractConnection;
use PhpAmqpLib\Exception\AMQPTimeoutException;
use PhpAmqpLib\Message\AMQPMessage;

class RequestsRabbit
{
    protected $rabbit;

    /** @var AMQPChannel */
    protected $channel;

    public $exchange = 'requests.events';

    public function __construct(AbstractConnection $rabbit)
    {
        $this->rabbit = $rabbit;
    }

    public function getChannel()
    {
        if ($this->channel === null) {
            $channel = $this->rabbit->channel();

            $channel->exchange_declare($this->exchange, 'fanout', false, false, false);

            $this->channel = $channel;
        }

        return $this->channel;
    }

    public function send($message)
    {
        $channel = $this->getChannel();

        $message = json_encode($message);

        $channel->basic_publish(new AMQPMessage($message), $this->exchange);
    }

    public function subscribe(callable $callable)
    {
        $channel = $this->getChannel();

        list($queue_name) = $channel->queue_declare('', false, false, true, false);

        $channel->queue_bind($queue_name, $this->exchange);

        $callback = function (AMQPMessage $msg) use ($callable) {
            call_user_func($callable, json_decode($msg->body));
        };

        $channel->basic_consume($queue_name, '', false, true, false, false, $callback);

        while (count($channel->callbacks)) {
            if (connection_aborted()) {
                break;
            }

            try {
                $channel->wait(null, true, 5);
            } catch (AMQPTimeoutException $exception) {
            }
        }

        $channel->close();
        $this->rabbit->close();
    }
}

What happens:

  • Browser establishes SSE connection to the server. var es = new EventSource(url);
  • Apache2 spawns new process to handle this request.
  • PHP generates a new Queue and connects to it.
  • Browser closes connection es.close()
  • Apache2 doesn't kill process and it stays as is. Queue of RabbitMQ will not be deleted. If I do some reconnections, it spawns a bunch of processes and a bunch of queues (1 reconnection = 1 process = 1 queue).
  • I close all tabs -- processes alive. I close browser -- the same situation.

Looks line some kind of PHP bug. Or of Apach2?

What I use:

Some screenshots:

RabbitMQ queues

Processes

Please, help me to figure out what's going on...

P.S. Sorry for my English. If you can find a mistake or typo, point to it in the comments. I'll be very grateful :)

作者: Виталий Заславский 的来源 发布者: 2017 年 12 月 27 日

回应 1


0

11886 作者的声誉

You don't say if you're using send() or subscribe() (or both) during your server-side events. Assuming you're using subscribe() there is no bug. This loop:

while (count($channel->callbacks)) {
    if (connection_aborted()) {
        break;
    }

    try {
        $channel->wait(null, true, 5);
    } catch (AMQPTimeoutException $exception) {
    }
}

Will run until the process is killed or the connection is remotely closed from RabbitMQ. This is normal when listening for queued messages. If you need to stop the loop at some point you can set a variable to check in the loop or throw an exception when the SSE is ended (although I find this awkward).

作者: Matt S 发布者: 2018 年 2 月 4 日
32x32