Fix temp file leak in batch metadata segment upload - #19377
Merged
yashmayya merged 1 commit intoAug 27, 2026
Merged
Conversation
The batch metadata upload path (`POST /segments/batchUpload`) creates three temporary files under `java.io.tmpdir` that were never deleted: - `segmentMetadata-<uuid>/` — the staging dir tarred up by `createSegmentFileFromSegmentMetadataInfo`. Only the resulting tar file had a `finally`; the source dir had none. One leaked dir per segment per push. - `allSegmentsMetadataTar-<uuid>.tar.gz` and `allSegmentsMetadataDir-<uuid>/` — created by `createSegmentsMetadataInfoMap` and never removed. `uploadSegments`'s existing `cleanupTempFiles` list only covered the three per-segment files under the controller's own upload/untar temp dirs. The leaked files live in `java.io.tmpdir` instead, so `ControllerFilePathProvider.initDir`'s startup `cleanDirectory` does not reclaim them either — they survive controller restarts. Each push leaks roughly 3x its metadata volume, which scales with column count, so wide tables on a frequent push cadence fill up /tmp. Changes: - `createSegmentFileFromSegmentMetadataInfo`: open the `try` before the staging work so the `finally` covers the staging dir as well as the tar file. This also handles a `createCompressedTarFile` failure leaving a partial tar behind. Use `deleteQuietly` rather than `forceDelete` so a cleanup failure cannot mask the exception that caused it. - `createSegmentsMetadataInfoMap`: register both request-scoped files with the caller's `tempFiles` list as soon as their paths are computed, so they are cleaned even when the untar or the mapping-file read fails part way. They are registered rather than deleted locally because the returned `SegmentMetadataInfo` values hold live `File` handles into that directory. - `uploadSegments`: move the `createSegmentsMetadataInfoMap` call inside the `try`. Previously a failure there leaked both files and skipped `multiPart.cleanup()` entirely. The equivalent client-side code in `SegmentPushUtils.createSegmentsMetadataTarFile` already cleans up its staging dir in a `finally`; the controller side had not been given the same treatment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #19377 +/- ##
============================================
- Coverage 67.54% 67.51% -0.04%
Complexity 1430 1430
============================================
Files 3486 3486
Lines 224044 224047 +3
Branches 35353 35353
============================================
- Hits 151339 151273 -66
- Misses 60677 60739 +62
- Partials 12028 12035 +7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The batch metadata segment upload path (
POST /segments/batchUpload) creates three temporary files underjava.io.tmpdirthat are never deleted:segmentMetadata-<uuid>/createSegmentFileFromSegmentMetadataInfoallSegmentsMetadataTar-<uuid>.tar.gzcreateSegmentsMetadataInfoMapallSegmentsMetadataDir-<uuid>/createSegmentsMetadataInfoMapuploadSegmentsalready has atempFileslist drained bycleanupTempFilesin afinally, but it only ever receives the three per-segment entries (tempEncryptedFile,tempDecryptedFile,tempSegmentDir) which live under the controller's owngetFileUploadTempDir()/getUntarredFileTempDir(). The three files above live injava.io.tmpdirinstead, soControllerFilePathProvider.initDir's startupcleanDirectorydoes not reclaim them either — they survive controller restarts and are only removed when the OS reaps/tmp.In
createSegmentFileFromSegmentMetadataInfo, only the intermediate tar file had afinally; the staging directory it was built from had none.Per push of N segments this leaves behind 1 tar + 1 directory holding 2N metadata files + N directories holding 2 files each — roughly 3x the metadata volume of every push.
metadata.propertiesscales with column count, so wide tables on a frequent push cadence will fill/tmpon the controller.Fix
createSegmentFileFromSegmentMetadataInfo— open thetrybefore the staging work so thefinallycovers the staging directory as well as the tar file. This also handles acreateCompressedTarFilefailure leaving a partial tar behind. Switched thefinallyfromforceDeletetodeleteQuietlyso a cleanup failure cannot mask the exception that caused it.createSegmentsMetadataInfoMap— takes the caller'stempFileslist and registers both request-scoped files as soon as their paths are computed, so they are cleaned even when the untar or the mapping-file read fails part way through. They are registered rather than deleted locally because the returnedSegmentMetadataInfovalues hold liveFilehandles into that directory, which the caller reads inside its loop.uploadSegments— moved thecreateSegmentsMetadataInfoMapcall inside thetry. Previously a failure there leaked both files and skippedmultiPart.cleanup()entirely.ControllerApplicationExceptionextendsWebApplicationException, so the catch block rethrows it unchanged; the only behavioral delta is a metric increment of 0 and the cleanup now running.The equivalent client-side code in
SegmentPushUtils.createSegmentsMetadataTarFilealready deletes its staging directory in afinally— the controller side had not been given the same treatment.Testing
testCreateSegmentFileFromSegmentMetadataInfoextended with a before/after snapshot ofjava.io.tmpdirentries matching the twosegmentMetadata*prefixes.testCreateSegmentsMetadataInfoMapRegistersTempFilesForCleanupbuilds a real uber tar and asserts the map is correct, that its file handles are still readable (cleanup correctly deferred to the caller), that both temp files landed intempFiles, and that draining the list leaves no residue.Both assert on a prefix-filtered diff of the temp directory rather than absolute emptiness, so unrelated entries cannot flake them.
Confirmed the tests are not vacuous — with the cleanup removed they fail with
expected [] but got [segmentMetadata-3cff66bd-…]andexpected [2] but found [0]respectively.createSegmentsMetadataInfoMapwas made package-private@VisibleForTestingto allow the new test; it has no callers outside this class.Backward compatibility
None affected — no wire format, serialization, or public API change.
createSegmentsMetadataInfoMapis an internal static helper.