gmr / rabbitpy

A pure python, thread-safe, minimalistic and pythonic RabbitMQ client library
http://rabbitpy.readthedocs.org
BSD 3-Clause "New" or "Revised" License
242 stars 58 forks source link

Exception not raised outside with block #119

Closed r01k closed 5 years ago

r01k commented 5 years ago

Publishing to a non-existing exchange does not raise exception if the channel is not used as a context manager:

conn = rabbitpy.Connection('amqp://LenelRabbit:Secur1ty%23@ms7:5672/LenelHost') ch = conn.channel() msg = rabbitpy.Message(ch, body_value='hello') msg.publish(exchange='invalid')

If the channel is inside a with block the exception does come up:

conn = rabbitpy.Connection('amqp://myuser:mypass@ms7:5672/myhost') with conn.channel() as ch: ... msg = rabbitpy.Message(ch, body_value='hello') ... msg.publish(exchange='invalid') ... Traceback (most recent call last): File "", line 3, in File "D:\Programming\mqclient\env36x64\lib\site-packages\rabbitpy\channel.py", line 101, in exit self.close() File "D:\Programming\mqclient\env36x64\lib\site-packages\rabbitpy\channel.py", line 147, in close super(Channel, self).close() File "D:\Programming\mqclient\env36x64\lib\site-packages\rabbitpy\base.py", line 204, in close self._check_for_pending_frames() File "D:\Programming\mqclient\env36x64\lib\site-packages\rabbitpy\base.py", line 318, in _check_forpending frames self._check_for_rpc_request(value) File "D:\Programming\mqclient\env36x64\lib\site-packages\rabbitpy\channel.py", line 271, in _check_for_rpc_r equest super(Channel, self)._check_for_rpc_request(value) File "D:\Programming\mqclient\env36x64\lib\site-packages\rabbitpy\base.py", line 328, in _check_for_rpc_requ est self._on_remote_close(value) File "D:\Programming\mqclient\env36x64\lib\site-packages\rabbitpy\base.py", line 380, in _on_remote_close raise exceptions.AMQPvalue.reply_code rabbitpy.exceptions.AMQPNotFound: <pamqp.specification.Channel.Close object at 0x333e438>

gmr commented 5 years ago

The reason why this is happening here is because without publisher confirmations, Message.publish is fire and forget at the protocol level. The exception is raised when RabbitMQ closes the channel remotely. It's an awkward side-effect of the protocol.

If you enable publisher confirmations and perform mandatory publishing, it raises where you would expect it. The following test passes:

class Issue119TestCase(unittest.TestCase):

    def test_exception_is_raised(self):
        conn = rabbitpy.Connection(os.environ['RABBITMQ_URL'])
        channel = conn.channel()
        channel.enable_publisher_confirms()
        msg = rabbitpy.Message(channel, body_value='hello')
        with self.assertRaises(exceptions.AMQPNotFound):
            msg.publish(exchange='invalid', mandatory=True)

If I purposefully pause and wait for a potential channel close after publishing, non mandatory publishing without publisher confirmations will suffer a pretty big performance degradation.

gmr commented 5 years ago

Unfortunately I can't think of a good way to force the behavior you are expecting with negatively impacting performance. It's not a bug in the library as much as it is a behavior in the protocol.

Using publisher confirmations will result in the behavior you expect, but will come with a performance tradeoff.