Azure / Azure/azure-sdk-for-python

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

Open
#48,921 1 comment 1 reaction 0 assignees View on GitHub
Messaging Service Attention Service Bus
Dominant language
Python
Stars
5.6k
Forks
3.4k
Avg merge
1d 21h
Merged PRs (30d)
193

Description

### 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:

```python
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](https://github.com/Azure/azure-sdk-for-python/issues/35618) /
[Azure/azure-sdk-for-python#36334](https://github.com/Azure/azure-sdk-for-python/issues/36334).

Contributor guide

Open the contributing guide

Research direction

Start at the async ServiceBusSender send_messages path and trace how an idle-disconnected link leaves the internal _handler as None. Add a regression test for a single-threaded send after the idle-timeout condition, then verify that it raises ServiceBusConnectionError rather than AttributeError or reconnects transparently.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, python
Domain
cloud, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.