Skip to content

fix(flink): close CDC image spillable maps on failures - #19482

Merged
voonhous merged 5 commits into
apache:masterfrom
danny0405:resource-leak
Aug 4, 2026
Merged

fix(flink): close CDC image spillable maps on failures#19482
voonhous merged 5 commits into
apache:masterfrom
danny0405:resource-leak

Conversation

@danny0405

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

CDC image loading allocates spillable maps that were not closed when iterator creation or traversal failed, leaking disk and native resources. The LOG_FILE path also allocated a spillable map for an absent before-slice even though that cache remains empty.

Summary and Changelog

  • Close newly allocated CDC image maps when loading or cache registration fails, preserving cleanup failures as suppressed exceptions.
  • Generalize image-record helpers to the Map interface and use Collections.emptyMap() when no before-image slice exists.
  • Ensure the log-record iterator and image manager are both closed while preserving the primary close failure.
  • Add focused coverage for iterator-creation and iteration failure cleanup.

Impact

Prevents temporary disk and native resource leaks in Flink CDC reads. There are no public API, configuration, storage-format, or user-visible behavior changes.

Risk Level

Low. The changes are localized to Flink CDC image-cache lifecycle handling and are covered by focused unit tests.

Documentation Update

None.

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

@hudi-agent hudi-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR tightens resource cleanup in the Flink CDC image cache — closing newly-allocated spillable maps when iterator creation or traversal fails, replacing the unclosed empty spillable map in the absent-before-slice path with Collections.emptyMap(), and closing both the log-record iterator and image manager while preserving the primary failure. I traced the mutation, failure, and close paths (including that the immutable empty map is never written, since updateImageRecord is unreachable when removeImageRecord always returns null) and everything holds up. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here. One small naming nit in the new helper method; otherwise the change is clean and readable.

cc @yihua

@codecov-commenter

codecov-commenter commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.99%. Comparing base (d98f2f1) to head (f96df25).
⚠️ Report is 10 commits behind head on master.

Files with missing lines Patch % Lines
.../reader/function/HoodieCdcSplitReaderFunction.java 0.00% 6 Missing ⚠️
...g/apache/hudi/table/format/cdc/CdcInputFormat.java 57.14% 3 Missing ⚠️
...org/apache/hudi/table/format/cdc/CdcIterators.java 72.72% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19482      +/-   ##
============================================
+ Coverage     76.96%   76.99%   +0.02%     
- Complexity    33850    33866      +16     
============================================
  Files          2575     2576       +1     
  Lines        143372   143420      +48     
  Branches      17572    17645      +73     
============================================
+ Hits         110349   110423      +74     
+ Misses        24758    24742      -16     
+ Partials       8265     8255      -10     
Components Coverage Δ
hudi-common 82.30% <100.00%> (+0.02%) ⬆️
hudi-client 81.82% <ø> (+<0.01%) ⬆️
hudi-flink 83.96% <69.23%> (-0.01%) ⬇️
hudi-spark-datasource 75.11% <ø> (ø)
hudi-utilities 73.67% <ø> (+0.04%) ⬆️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.72% <ø> (+0.22%) ⬆️
hudi-sync 71.00% <ø> (+0.12%) ⬆️
hudi-io 79.60% <ø> (ø)
hudi-timeline-service 83.44% <ø> (-0.30%) ⬇️
hudi-cloud 64.00% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 49.55% <70.45%> (+0.02%) ⬆️
flink-integration-tests 48.80% <72.72%> (+<0.01%) ⬆️
hadoop-mr-java-client 43.75% <0.00%> (-0.02%) ⬇️
integration-tests 13.58% <0.00%> (+<0.01%) ⬆️
spark-client-hadoop-common 48.70% <0.00%> (+0.01%) ⬆️
spark-java-tests 51.35% <60.00%> (+0.01%) ⬆️
spark-scala-tests 47.42% <0.00%> (-0.02%) ⬇️
utilities 36.60% <0.00%> (+0.01%) ⬆️

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

Files with missing lines Coverage Δ
...va/org/apache/hudi/common/util/CloseableUtils.java 100.00% <100.00%> (ø)
...rce/reader/function/HoodieSplitReaderFunction.java 100.00% <ø> (ø)
.../apache/hudi/table/format/cdc/CdcImageManager.java 92.77% <100.00%> (+1.46%) ⬆️
...g/apache/hudi/table/format/cdc/CdcInputFormat.java 73.68% <57.14%> (-3.03%) ⬇️
...org/apache/hudi/table/format/cdc/CdcIterators.java 82.01% <72.72%> (-0.54%) ⬇️
.../reader/function/HoodieCdcSplitReaderFunction.java 74.52% <0.00%> (-2.18%) ⬇️

... and 27 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added the size:S PR with lines of changes in (10, 100] label Aug 3, 2026

@voonhous voonhous left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the lifecycle changes against how CdcImageManager is actually shared across splits, not just the diff.

The core fix in loadImageRecords is right, and the Collections.emptyMap() swap is a genuine improvement -- I verified on JDK 8/17/21 that Collections.emptyMap().remove(k) returns null through the inherited AbstractMap.remove (EmptyMap does not override the 1-arg form), so the unreachability argument for updateImageRecord holds, and the old else-branch map was never written to and never registered in the cache, i.e. a real per-split leak since #11490. No accidental revert: #19402 (46cd13434308) and #19202 (d10b868d90c9) are both intact on this branch.

Two things I would want resolved before merge, both inline: the close() in DataLogFileIterator is silently load-bearing for correctness and needs a comment, and the suppressed-exception behavior the PR body advertises has no test. Also inline: two more instances of this PR's own leak class in the surrounding constructors, and CdcImageManager.close() itself. The rest are cleanliness.

@github-actions github-actions Bot added size:M PR with lines of changes in (100, 300] and removed size:S PR with lines of changes in (10, 100] labels Aug 4, 2026

@hudi-agent hudi-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR tightens resource cleanup on the Flink CDC read path: it closes newly-allocated spillable maps and log-record iterators when image loading or iterator construction fails, moves CdcImageManager ownership entirely to CdcFileSplitsIterator, and uses Collections.emptyMap() for the absent before-slice. I traced the ownership/lifecycle model and failure paths in detail: removing imageManager.close() from DataLogFileIterator.close() correctly fixes a premature-close of the shared manager, the Collections.emptyMap() path is safe (the updateImageRecord mutation branch is unreachable when the before-slice is absent), and the suppressed-exception cleanup is idempotent. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here.
. One duplication suggestion around the repeated closeSuppressing helper; otherwise clean.

cc @yihua

@github-actions github-actions Bot added size:L PR with lines of changes in (300, 1000] and removed size:M PR with lines of changes in (100, 300] labels Aug 4, 2026

@voonhous voonhous left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@hudi-bot

hudi-bot commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

@voonhous voonhous closed this Aug 4, 2026
@voonhous voonhous reopened this Aug 4, 2026
@voonhous
voonhous enabled auto-merge (squash) August 4, 2026 09:15
@voonhous voonhous closed this Aug 4, 2026
auto-merge was automatically disabled August 4, 2026 09:40

Pull request was closed

@voonhous voonhous reopened this Aug 4, 2026
@voonhous
voonhous enabled auto-merge (squash) August 4, 2026 09:40
@voonhous
voonhous merged commit f41e8e3 into apache:master Aug 4, 2026
149 of 154 checks passed
voonhous pushed a commit that referenced this pull request Aug 6, 2026
CDC iterators leaked ExternalSpillableMap instances when construction or
iteration failed: the image manager was never closed on the failure path,
leaving spill files behind. Route cleanup through a shared
CloseableUtils.closeSuppressing helper, close the image manager via
try-with-resources, and retain CDC images across child splits.

(cherry picked from commit f41e8e3)

Cherry-pick adaptations:
- HoodieSplitReaderFunction: the private closeSuppressing helper being deleted
  is typed HoodieFileGroupReader<RowData> here rather than
  HoodieRecordReader<RowData>, since the LSM reader refactor (#18987, #19079,
  #19307) is not on this branch. Same deletion, different pre-existing
  signature. The shared replacement takes AutoCloseable and
  HoodieFileGroupReader implements Closeable, so call sites are unchanged.
- CdcIterators: upstream drops the FormatUtils import and keeps
  HoodieRowDataFileReader / InternalSchemaManager as context. This branch's
  copy of the file never imported the latter two and does not reference them,
  so only the FormatUtils import is dropped. Adding the other two would be
  unused imports and fail checkstyle. The static import of
  FormatUtils.buildAvroRecordBySchema is unaffected.
- Dropped the TestCdcImageManager and TestCdcIterators changes. Both test
  classes arrive with #19402, which is not on this branch, and both assert
  behavior from that commit's production half: TestCdcImageManager expects
  skipBytesToRead to throw EOFException, which without #19402 loops forever
  rather than failing. Importing them would add a hanging test.

The fix therefore lands without its CDC test coverage. TestCloseableUtils is
included, so the shared helper itself is covered.
voonhous pushed a commit that referenced this pull request Aug 7, 2026
CDC iterators leaked ExternalSpillableMap instances when construction or
iteration failed: the image manager was never closed on the failure path,
leaving spill files behind. Route cleanup through a shared
CloseableUtils.closeSuppressing helper, close the image manager via
try-with-resources, and retain CDC images across child splits.

(cherry picked from commit f41e8e3)

Cherry-pick adaptations:
- HoodieSplitReaderFunction: the private closeSuppressing helper being deleted
  is typed HoodieFileGroupReader<RowData> here rather than
  HoodieRecordReader<RowData>, since the LSM reader refactor (#18987, #19079,
  #19307) is not on this branch. Same deletion, different pre-existing
  signature. The shared replacement takes AutoCloseable and
  HoodieFileGroupReader implements Closeable, so call sites are unchanged.
- CdcIterators: upstream drops the FormatUtils import and keeps
  HoodieRowDataFileReader / InternalSchemaManager as context. This branch's
  copy of the file never imported the latter two and does not reference them,
  so only the FormatUtils import is dropped. Adding the other two would be
  unused imports and fail checkstyle. The static import of
  FormatUtils.buildAvroRecordBySchema is unaffected.
- Dropped the TestCdcImageManager and TestCdcIterators changes. Both test
  classes arrive with #19402, which is not on this branch, and both assert
  behavior from that commit's production half: TestCdcImageManager expects
  skipBytesToRead to throw EOFException, which without #19402 loops forever
  rather than failing. Importing them would add a hanging test.

The fix therefore lands without its CDC test coverage. TestCloseableUtils is
included, so the shared helper itself is covered.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L PR with lines of changes in (300, 1000]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants