Skip to content

[servicebus] Async ServiceBusSender leaks AttributeError ('NoneType' has no attribute 'client_ready_async') after idle timeout, instead of ServiceBusConnectionError #48921

Description

@BeatBard

Related issues

Same root cause as #35618 and #36334 (internal _handler reset to None),
but a different trigger: those reproduce under concurrent sends and were
closed not_planned with an asyncio.Lock() workaround. This report is a
single-threaded, non-concurrent reproduction driven by Azure's ~10-minute
idle timeout — the lock workaround does not apply.

Describe the bug

A long-lived (singleton) async ServiceBusSender that is reused across sends
works fine until the connection sits idle past Azure's ~10-minute idle timeout.
The next send_messages() raises:

AttributeError: 'NoneType' object has no attribute 'client_ready_async'

Expected: a documented, catchable ServiceBusConnectionError (or transparent
implicit reconnect), so callers can distinguish "stale link, safe to recreate
and retry" from a genuine error (auth/quota/payload).

Because the leaked exception is a bare AttributeError, callers cannot reliably
tell it apart from an ordinary programming bug without string-matching on the
internal attribute name client_ready_async, which is fragile across releases.

Steps to reproduce

  1. Create an async ServiceBusClient, then one ServiceBusSender, and keep both.
  2. await sender.send_messages(...) once — succeeds.
  3. Leave the sender idle for > 10 minutes (Azure idle timeout drops the AMQP link).
  4. await sender.send_messages(...) again.

Result: AttributeError: 'NoneType' object has no attribute 'client_ready_async'
rather than a ServiceBusConnectionError.

Minimal sketch:

import asyncio
from azure.servicebus.aio import ServiceBusClient
from azure.servicebus import ServiceBusMessage

async def main():
    client = ServiceBusClient.from_connection_string(CONN_STR)
    sender = client.get_queue_sender(QUEUE)
    await sender.send_messages(ServiceBusMessage("first"))   # ok
    await asyncio.sleep(11 * 60)                              # exceed idle timeout
    await sender.send_messages(ServiceBusMessage("second"))  # AttributeError

asyncio.run(main())

Expected behavior
The idle-dropped link should surface as ServiceBusConnectionError (the SDK's
documented connection-loss exception), or the sender should reconnect implicitly.
Either way, no internal AttributeError should escape.

Environment
azure-servicebus: 7.14.3 (latest stable)
azure-core: 1.38.2
Python: 3.11.14
OS: Windows 11 (also observed in Linux containers)
Transport: async (azure.servicebus.aio), pure-Python AMQP
Impact / current workaround
We run one persistent sender per worker and must detect this failure to recreate
the sender and retry. Since no typed exception is raised, we string-match:

isinstance(exc, AttributeError) and "client_ready_async" in str(exc)
This is brittle — it depends on an internal attribute name. A typed exception
(or implicit reconnect) would let us delete the string match. Please consider
fixing the idle-timeout path specifically, independently of the concurrency
scenarios in
Azure/azure-sdk-for-python#35618 /
Azure/azure-sdk-for-python#36334.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions