pdezwart/php-amqp — pull request #12 by narkq (2012-10-24 17:19:12+00:00)
Title: AMQPQueue::cancel() is unusable

First of all, description of this method on the page http://www.php.net/manual/en/amqpqueue.cancel.php is really confusing, especially for people who haven't got a consistent understanding of AMQP protocol.

What this method should do is cancelling an active subscription that was created by `AMQPQueue::consume`. Let me give a really boring explanation how this should work.
1. Client calls `AMQPQueue::consume` in php code (the `basic.consume` method from the AMQP spec).
2. Broker creates a consumer for this queue and reports its consumer tag to the client via `basic.consume-ok` method.
3. Consumer on the broker side sends messages to the client. Client acknowledges or rejects each message.
4. Client decides to stop accepting messages and tells broker to destroy the consumer with `AMQPQueue::cancel` call (the `basic.cancel` method from the AMQP spec).
5. Broker destroys the consumer in question and this consumer doesn't take messages from the queue anymore.

The problem is that performing step 4 is impossible now, because `AMQPQueue::consume` discards information from `basic.consume-ok` response and there's no way to find out the consumer tag that was assigned to our newly created consumer. We could try to send `basic.cancel` with an empty tag and hope that broker will figure it out on its own, but popular broker implementations like RabbitMQ (as of 2.8.7 version) and Apache Qpid (as of 0.18 version) don't do that, so that hope is futile.

So, what happens now is that consumer stays alive on the broker side and holds messages from its queue in unacknowledged state from the moment when client decided to stop accepting messages until the moment when the channel (or the connection) is closed.

The "right" solution would be storing consumer tags inside the channel object, having a way to retrieve them when necessary and maybe sending `basic.cancel` explicitly on channel closure (or in general, when we know for sure that client won't be using that consumer anymore).
I checked other client implementations, and they do that.
http://hg.rabbitmq.com/rabbitmq-java-client/file/b9b002aef47a/src/com/rabbitmq/client/impl/ChannelN.java#l953
https://github.com/pika/pika/blob/master/pika/channel.py#L195
https://github.com/cooldaemon/AnyEvent-RabbitMQ/blob/master/lib/AnyEvent/RabbitMQ/Channel.pm#L375

This pull request contains an "easy" workaround that allows client to specify consumer tag explicitly. Also, it contains a test case ( https://github.com/narkq/php-amqp/blob/consumer_tag_parameter/tests/amqpqueue_cancel.phpt ) to illustrate the issue.

P. S. If you run the test case with the current master branch, you might get interested why both the second and the third message ("message 1" and "message 2") get processed by second consumer, despite the `setPrefetchCount(1)` setting and the fact that the first consumer hasn't been canceled. It happens because `read_message_from_channel()` doesn't respect `consumer_tag` parameter from `basic.deliver` method (and because of this message can get delivered to the wrong channel), but that's a different story.