aioamqp icon indicating copy to clipboard operation
aioamqp copied to clipboard

Heartbeat timeout expires directly after connection is opened

Open mwfrojdman opened this issue 9 years ago • 4 comments

Steps to reproduce on aioamqp 0.8.2:

  • Have AMQP server which sends connection.tune method frame with parameter heartbeat = 0 (meaning turn heartbeats off)
  • Do not pass heartbeat > 0 to aioamqp.connect()

Result:

  • AmqpProtocol.start_connection() waits for connection.tune() to arrive on line 171
  • On receiving connection.tune, AmqpProtocol.tune() sets self.server_heartbeat = decoder.read_short()
  • start_connection proceeds to set key heartbeat to the tune_ok dictionary on line 176, but because user didn't pass heartbeat != None, it gets set to what the server sent (= 0)
  • Client confirms heartbeat 0 with tune_ok() and self.server_heartbeat gets set to the same value 0 on line 184
  • Line 186 skips calling _heartbeat_timer_recv_reset if self.server_heartbeat == 0, still OK
  • Server replies with an connection.open-ok frame, and data_received calls self._heartbeat_timer_recv_reset() unconditionally on line 108
  • which checks if self.server_heartbeat is None (which is never true, it's 0)
  • and _heartbeat_timer_recv_timeout is set to be called in 0 * 2, ie. 0 seconds
  • which then silently just closes the writer on line 318
  • which leads to connection_lost being called with no exception
  • which leads to general kaboom.

Here's a traceback. Because connection.open-ok is the last frame the broker sends on establishing the connection, my code got to calling AmqpProtocol.channel() before getting an exception:

Traceback (most recent call last):
  File "reproduce.py", line 123, in run
    channel = yield from connection.channel()
  File "/usr/lib/python3/dist-packages/aioamqp/protocol.py", line 444, in channel
    yield from channel.open()
  File "/usr/lib/python3/dist-packages/aioamqp/channel.py", line 134, in open
    yield from fut
  File "/usr/lib/python3.4/asyncio/futures.py", line 388, in __iter__
    yield self  # This tells Task to wait for completion.
  File "/usr/lib/python3.4/asyncio/tasks.py", line 286, in _wakeup
    value = future.result()
  File "/usr/lib/python3.4/asyncio/futures.py", line 277, in result
    raise self._exception
aioamqp.exceptions.ChannelClosed: (None, None)

mwfrojdman avatar Nov 14 '16 17:11 mwfrojdman

Haven't tested locally, but that sounds about right. And it sucks!

Thanks for reporting! A PR would be much appreciated or we'll try to fix it as time allows.

Cheers

RemiCardona avatar Nov 15 '16 15:11 RemiCardona

Experiencing the same thing, any news on the bugfix ? Thanks.

nemiliani avatar May 18 '17 15:05 nemiliani

Sorry, no news unfortunately. Been busy with other work. A PR would be most appreciated.

Thanks

RemiCardona avatar May 23 '17 09:05 RemiCardona

Hi @RemiCardona @mwfrojdman, I've made a simple workaround to solve this problem, it was so simple it sounds suspicious ... We need this feature for our work so any suggestion is welcome. Saludos!

clodo avatar May 25 '17 19:05 clodo