Skip to content

Fix memory leak in ydb.aio.retry_operation - #906

Merged
vgvoleg merged 1 commit into
ydb-platform:mainfrom
makhalin:fix-retry-memory-leak-14503890748617238658
Sep 28, 2026
Merged

vgvoleg merged 1 commit into
ydb-platform:mainfrom
makhalin:fix-retry-memory-leak-14503890748617238658

Conversation

@makhalin

@makhalin makhalin commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

When a coroutine yields an exception out of retry_operation using YdbRetryOperationFinalResult.exc, a reference to the exception is retained in result.exc after it gets raised. When dealing with exceptions that don't participate fully in cyclic garbage collection (such as Pydantic's ValidationError underlying a custom error), this reference cycle (YdbRetryOperationFinalResult -> Exception -> traceback -> generator frame -> locals -> YdbRetryOperationFinalResult) causes coroutine local objects (payloads) to stay alive in memory.

This change avoids retaining the reference to the completed exception object within the generator frame once it is re-raised, fixing the memory leak. A memory leak regression test was also added reflecting a complex chained exception scenario.

Pull request type

Please check the type of change your PR introduces:

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

What is the current behavior?

Issue Number: #905

What is the new behavior?

Other information

When a coroutine yields an exception out of `retry_operation` using `YdbRetryOperationFinalResult.exc`, a reference to the exception is retained in `result.exc` after it gets raised. When dealing with exceptions that don't participate fully in cyclic garbage collection (such as Pydantic's `ValidationError` underlying a custom error), this reference cycle (`YdbRetryOperationFinalResult` -> `Exception` -> traceback -> generator frame -> locals -> `YdbRetryOperationFinalResult`) causes coroutine local objects (payloads) to stay alive in memory.

This change avoids retaining the reference to the completed exception object within the generator frame once it is re-raised, fixing the memory leak. A memory leak regression test was also added reflecting a complex chained exception scenario.

Co-authored-by: makhalin <2647652+makhalin@users.noreply.github.com>
@codecov

codecov Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@6116843). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #906   +/-   ##
=======================================
  Coverage        ?   82.66%           
=======================================
  Files           ?       99           
  Lines           ?    12953           
  Branches        ?     1268           
=======================================
  Hits            ?    10708           
  Misses          ?     1793           
  Partials        ?      452           
Flag Coverage Δ
integration 80.48% <100.00%> (?)
unit 49.06% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
ydb/retries.py 90.97% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vgvoleg
vgvoleg merged commit 87d561e into ydb-platform:main Sep 28, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants