- 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:
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
- Create a single long-lived
AutoLockRenewer().
- 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).
- Print
len(renewer._futures) periodically.
- 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.
Describe the bug
azure.servicebus.aio.AutoLockRenewergrows its internal_futureslist by one entry on everyregister(...)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 onclose().This is the pattern the docs recommend (one long-lived renewer,
registereach message in the receive loop), so idiomatic usage hits it:To Reproduce
AutoLockRenewer().register(receiver, msg, ...)then settle it immediately withcomplete_message(msg)(so no renewal stays active).len(renewer._futures)periodically.await renewer.close()releases them.Expected behavior
A completed renewal's
Futureshould be dropped from_futureswhen 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 abovelen(_futures)should hover near 0 (each message settles immediately), not grow toprocessed.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.