agoragames / haigha

AMQP Python client
BSD 3-Clause "New" or "Revised" License
160 stars 41 forks source link

AttributeError: 'Channel' object has no attribute 'channel' when closing channel with gevent transport #24

Closed vitaly-krugl closed 12 years ago

vitaly-krugl commented 12 years ago

This happens every time with haigha 0.5.3 with gevent transport when running the following app. However, the app works fine with haigha 0.4.1. I am using gevent 1.0b2, but am pretty sure this failure is not related to gevent.

The traceback looks like this:

$ python haigha_channel_test.py 
WARNING: DEPLOYING GEVENT-FRIENDLINESS BUG WORK-AROUND FOR HAIGHA v0.5.3
Connecting...
/Users/vkrug/Packages/haigha/haigha-0.5.3/build/lib/haigha/transports/gevent_transport.py:12: DeprecationWarning: gevent.coros has been renamed to gevent.lock
  from gevent.coros import Semaphore
Starting message pump greenlet...
Closing channel1...
Traceback (most recent call last):
  File "/Users/vkrug/nta/current/lib/python2.6/site-packages/gevent/greenlet.py", line 328, in run
    result = self._run(*self.args, **self.kwargs)
  File "haigha_channel_test.py", line 57, in readFrames
    conn.read_frames()
  File "/Users/vkrug/Packages/haigha/haigha-0.5.3/build/lib/haigha/connection.py", line 354, in read_frames
    self._transport.process_channels( p_channels )
  File "/Users/vkrug/Packages/haigha/haigha-0.5.3/build/lib/haigha/transports/transport.py", line 38, in process_channels
    channel.process_frames()
  File "/Users/vkrug/Packages/haigha/haigha-0.5.3/build/lib/haigha/channel.py", line 208, in process_frames
    self.close( 500, "Failed to dispatch %s"%(str(frame)) )
  File "/Users/vkrug/Packages/haigha/haigha-0.5.3/build/lib/haigha/channel.py", line 156, in close
    self.channel.close(reply_code, reply_text, class_id, method_id)
AttributeError: 'Channel' object has no attribute 'channel'
<Greenlet at 0x1009d42f8: readFrames(conn=<haigha.connection.Connection object at 0x1009de45)> failed with AttributeError

This is the app that reproduces the failure with gevent 0.5.3

import sys

import gevent
from gevent.event import AsyncResult
from haigha.connection import Connection
from haigha.message import Message

# NOTE: Work around a bug in Haigha 0.5.1-0.5.3 that breaks gevent
#  compatibility
import haigha
try:
  haigha_version = haigha.__version__
except AttributeError:
  pass
else:
  from distutils import version
  if (version.StrictVersion(haigha_version) >=
      version.StrictVersion("0.5.1")
      and
      version.StrictVersion(haigha_version) <=
      version.StrictVersion("0.5.3")):
    print >>sys.stderr, \
    "WARNING: DEPLOYING GEVENT-FRIENDLINESS BUG WORK-AROUND FOR HAIGHA v%s" % (
      haigha_version)
    from haigha.transports import socket_transport
    import gevent.socket
  socket_transport.socket = gevent.socket

def test_haigha():
  """
  A simple test to check Haigha's connection/channel opening and closing.

  Note that Rabbit MQ must be running
  """

  channel1CloseWaiter = AsyncResult()
  connectionCloseWaiter = AsyncResult()

  def handleChannel1Closed(ch, channelImpl):
    print "CHANNEL1 CLOSED: %r" % (channelImpl.close_info,)
    channel1CloseWaiter.set()

  def handleConnectionClosed():
    print "CONNECTION CLOSED!"
    connectionCloseWaiter.set()

  print "Connecting..."
  connection = Connection(
    user='guest', password='guest',
    vhost='/', host='localhost',
   heartbeat=None, debug=True, transport="gevent",
   close_cb=handleConnectionClosed)

  def readFrames(conn):
    while True:
      conn.read_frames()
      if connectionCloseWaiter.ready():
        break

  # Start Haigha message pump
  print "Starting message pump greenlet..."
  g = gevent.spawn(readFrames, conn=connection)

  ch1 = connection.channel()
  channel1Impl = ch1.channel
  ch1.add_close_listener(lambda ch: handleChannel1Closed(ch, channel1Impl))

  # Close the channels and wait for close-done
  print "Closing channel1..."
  ch1.close()
  with gevent.Timeout(seconds=10) as myTimeout:
    channel1CloseWaiter.wait()

  # Close the connection and wait for close-done
  print "Closing connection..."
  connection.close()
  with gevent.Timeout(seconds=10) as myTimeout:
    connectionCloseWaiter.wait()

  print "Killing message pump..."
  sys.stdout.flush()

  g.kill()

if __name__ == '__main__':
  test_haigha()
awestendorf commented 12 years ago

There is a bug in your test.

  def handleChannel1Closed(ch, channelImpl):
    print "CHANNEL1 CLOSED: %r" % (channelImpl.close_info,)
    channel1CloseWaiter.set()

should be:

  def handleChannel1Closed(ch, channelImpl):
    print "CHANNEL1 CLOSED: %r" % (ch.close_info,)
    channel1CloseWaiter.set()

This results in a call to Channel.close after Channel._closed_cb has run. I can improve the safety in that situation so that the error is reraised and you'll see the bug.

awestendorf commented 12 years ago

Fixed in https://github.com/agoragames/haigha/commit/309eef26cd1886d333b5c08a2236a5be571ae9e3

vitaly-krugl commented 12 years ago

Hi Aaron, is it possible to have my code snippet that reproduced the failure added to haigha unit tests suite? I would do it myself, but I am baffled how to do this with Chai/mock.