Azure / Azure/azure-sdk-for-python

[ServiceBus] aio AutoLockRenewer never prunes completed futures from _futures, leaking memory over a long-running consumer

オープン
#48,366 コメント 2 件 リアクション 1 件 担当者 0 名 GitHub で見る
Client customer-reported needs-team-attention question Service Attention Service Bus
主要言語
Python
スター
5.6k
フォーク
3.4k
平均マージ
2日 2時間
マージ済み PR(30日)
202

説明

- **Package Name**: azure-servicebus
- **Package Version**: 7.14.3
- **Operating System**: Windows 11 Pro
- **Python Version**: 3.13.5

**Describe the bug**
`azure.servicebus.aio.AutoLockRenewer` grows its internal `_futures` list by one entry on every `register(...)` and does not release those entries as the corresponding messages are settled - the list is only drained when the renewer is closed. A long-running consumer using the **documented** one-renewer-per-app pattern therefore accumulates one entry per message processed for the whole lifetime of the renewer: unbounded memory growth, reclaimed only on `close()`.

This is the pattern the docs recommend (one long-lived renewer, `register` each message in the receive loop), so idiomatic usage hits it:

```python
lock_renewal = AutoLockRenewer()
async with servicebus_receiver:
async for message in servicebus_receiver:
lock_renewal.register(servicebus_receiver, message, max_lock_renewal_duration=60)
await process_message(message)
await servicebus_receiver.complete_message(message) # renewal completes here...
# ...but its Future stays in lock_renewal._futures forever
```

**To Reproduce**
1. Create a single long-lived `AutoLockRenewer()`.
2. Receive from a queue holding a few hundred messages; for each message, `register(receiver, msg, ...)` then settle it immediately with `complete_message(msg)` (so no renewal stays active).
3. Print `len(renewer._futures)` periodically.
4. Observe it climb to the all-time processed count and never shrink, even though every registered renewal has already completed; process RSS grows in lockstep. Only `await renewer.close()` releases them.

**Expected behavior**
A completed renewal's `Future` should be dropped from `_futures` when it finishes, so the list stays bounded by the number of *active* (unsettled) renewals rather than the process's all-time message count. In the repro above `len(_futures)` should hover near 0 (each message settles immediately), not grow to `processed`.

**Screenshots**
Not applicable

**Additional context**
The growth is proportional to the total number of messages handled over the process's uptime, not to how many renewals are active at once - the accumulated entries are only released when the renewer itself is closed, so a single app-lifetime renewer (the documented pattern) that stays open until shutdown holds them for the whole run. Higher throughput or longer uptime makes it more pronounced; a short-lived script that closes the renewer promptly won't notice.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

まず azure-servicebus パッケージ内の async AutoLockRenewer 実装を見つけ、register(...) が更新用 futures をどのように保存して完了させるかを追跡します。issue にある長時間稼働する consumer のシナリオを再現し、その後、完了した futures が削除される一方でアクティブな更新は追跡され続け、メッセージが settled された後もコレクションのサイズが上限内に収まることを確認します。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
azure, python
領域
api, backend
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
68/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。