py-amqp icon indicating copy to clipboard operation
py-amqp copied to clipboard

Channel hangs on subsequent publish after an `AccessRefused` error (403)

Open matias-martini opened this issue 11 months ago • 2 comments

Description

When attempting to publish a message to an exchange that the user does not have permission to write to, the channel raises an AccessRefused error (403) as expected. However, after this error occurs, subsequent attempts to publish on the same channel hang indefinitely instead of returning an error or otherwise recovering gracefully.

According to the AMQP 0-9-1 protocol, when a 403 (“AccessRefused”) is triggered, the broker typically sends a channel.close method. The client library should then close that channel and raise an exception, allowing the user to handle the error and open a new channel if needed. In some cases, RabbitMQ may also set certain flags (e.g., connection.blocked frames or channel-blocked signals) depending on how the broker is configured and how the error is propagated.

In this scenario, it appears that py-amqp either does not fully close or reset the channel after receiving the AccessRefused, leaving the channel in a “blocked” or “bad” state. Any subsequent publish calls on this channel remain stuck because the broker has effectively closed or blocked the channel from further use.

Below is a minimal reproducible example demonstrating the issue:

from amqp import Connection, Message

# Establish connection and open channel
con = Connection(host='rabbitmq', userid='example-user', password='example-user', virtual_host='/')
con.connect()
chan = con.channel()

# 1) This line triggers an AccessRefused exception because the user lacks permission:
#    AccessRefused: Basic.publish: (403) ACCESS_REFUSED - write access to exchange 'system-configuration'
#    in vhost '/' refused for user 'example-user'
chan.basic_publish_confirm(Message(b''), 'system-configuration', 'a.config.c')

# 2) After the above error occurs, attempting to reuse the same channel hangs forever:
chan.basic_publish_confirm(Message(b''), 'example-exchange', 'a.config.c')

Steps to Reproduce

  1. Set up a RabbitMQ user (example-user) with permission to publish on some exchanges, but not on system-configuration.
  2. Run the above code.
  3. Observe that the first publish call fails with AccessRefused (403) as expected.
  4. Note that the second publish call never returns, causing the application to hang indefinitely.

Expected Behavior

  • After encountering AccessRefused, the channel should close or become invalid, and the library should raise a clear exception if further operations are attempted.
  • Future calls on the same channel should either fail fast with an error indicating the channel is closed, or allow the application to recover by reopening or recreating the channel.

Actual Behavior

  • The channel appears to enter a “blocked” or otherwise bad state after the first failed publish.
  • Subsequent calls to basic_publish_confirm with valid exchanges hang and never return.
  • The library does not appear to handle the channel close gracefully.

Environment

  • py-amqp version: 5.3.1
  • Python version: 3.13
  • RabbitMQ version: 3.13
  • OS: Debian 12.5-slim (based on python:3.13-slim Docker image)

Additional Notes

  • This behavior suggests that py-amqp is not handling the server’s channel.close (or related block signals) properly in response to the 403 error.
  • A workaround is to catch the AccessRefused exception explicitly, discard the failed channel, and open a new one for subsequent operations. However, the more intuitive behavior would be for py-amqp to automatically detect the channel is closed and raise an error on further usage, rather than hanging.
  • Please let me know if any additional logs or information would help diagnose this issue further.

matias-martini avatar Jan 08 '25 02:01 matias-martini

Thanks for the detailed report. would you mind come up with a prospective fix?

auvipy avatar Mar 19 '25 09:03 auvipy

Sure, @auvipy! I’ll submit a draft PR for your review as soon as I find some free time 🚀

matias-martini avatar Mar 19 '25 13:03 matias-martini