Skip to content

Fix protobuf temp directory leak in ProtoBufUtils - #19378

Open
mayankshriv wants to merge 1 commit into
apache:masterfrom
mayankshriv:fix-protobuf-temp-dir-leak
Open

Fix protobuf temp directory leak in ProtoBufUtils#19378
mayankshriv wants to merge 1 commit into
apache:masterfrom
mayankshriv:fix-protobuf-temp-dir-leak

Conversation

@mayankshriv

Copy link
Copy Markdown
Contributor

Problem

ProtoBufUtils.getFileCopiedToLocal() created temporary directories with prefix pinot-protobuf that were never cleaned up. Every call to ProtoBufMessageDecoder.init(), ProtoBufRecordReader.init(), or ProtoBufCodeGenMessageDecoder.init() leaked one directory under java.io.tmpdir.

Fix

Descriptor file callers (ProtoBufMessageDecoder, ProtoBufRecordReader): replaced the copy-to-temp-then-open pattern with readDescriptorFileBytes() which streams bytes directly via PinotFS.open() - no temp files at all.

JAR caller (ProtoBufCodeGenMessageDecoder): resolve the JAR to a local File inline. Local JARs are used directly without copying. Remote JARs are copied to a temp directory that intentionally persists for the decoder's lifetime because the JVM may lazily resolve classes from the JAR at decode time via the URLClassLoader chain. Since StreamMessageDecoder does not extend Closeable, there is no lifecycle hook to clean up, but this is a one-time-per-consumer-init cost and consumers are long-lived.

Removed dead methods: getFileCopiedToLocal(), getDescriptorFileInputStream(), createLocalFile(), withLocalFile(), and FileAction from ProtoBufUtils. Renamed loadClass(File) to createClassLoader(File) for clarity.

Testing

Added ProtoBufTempFileLeakTest with 5 tests covering all three code paths. Each test snapshots pinot-protobuf* directories in java.io.tmpdir before the operation, performs the operation with functional correctness assertions (decoding a message and checking field values), then asserts no new temp directories remain afterward.

Test Path covered
testMessageDecoderInitDoesNotLeakTempDir Simple descriptor via ProtoBufMessageDecoder
testMessageDecoderComplexDescriptorDoesNotLeakTempDir Complex nested descriptor via ProtoBufMessageDecoder
testCodeGenDecoderInitDoesNotLeakTempDir Simple JAR via ProtoBufCodeGenMessageDecoder
testCodeGenDecoderComplexJarDoesNotLeakTempDir Complex JAR with nested/repeated/map types
testRecordReaderLifecycleDoesNotLeakTempDir Full ProtoBufRecordReader lifecycle (init, read, close)

All 5 tests confirmed to fail before the fix (each leaking 1 temp directory) and pass after. Full module suite: 172/172 tests pass.

@codecov-commenter

codecov-commenter commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.96296% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.55%. Comparing base (5e914c9) to head (d3a58b5).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...format/protobuf/ProtoBufCodeGenMessageDecoder.java 44.44% 9 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master   #19378   +/-   ##
=========================================
  Coverage     67.55%   67.55%           
  Complexity     1430     1430           
=========================================
  Files          3486     3486           
  Lines        224100   224130   +30     
  Branches      35370    35376    +6     
=========================================
+ Hits         151392   151420   +28     
  Misses        60678    60678           
- Partials      12030    12032    +2     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 67.55% <62.96%> (+<0.01%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 67.55% <62.96%> (+<0.01%) ⬆️
unittests 67.55% <62.96%> (+<0.01%) ⬆️
unittests1 57.66% <ø> (+0.01%) ⬆️
unittests2 39.31% <62.96%> (-0.02%) ⬇️

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

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

ProtoBufUtils.getFileCopiedToLocal() created temporary directories
with prefix "pinot-protobuf" that were never cleaned up. Every call
to ProtoBufMessageDecoder.init(), ProtoBufRecordReader.init(), or
ProtoBufCodeGenMessageDecoder.init() leaked one directory under
java.io.tmpdir.

For descriptor file callers (ProtoBufMessageDecoder, ProtoBufRecordReader):
replace the copy-to-temp-then-open pattern with readDescriptorFileBytes()
which streams bytes directly via PinotFS.open() - no temp files at all.

For the JAR caller (ProtoBufCodeGenMessageDecoder): resolve the JAR
to a local File inline. Local JARs are used directly without copying.
Remote JARs are copied to a temp directory that intentionally persists
for the decoder's lifetime because the JVM may lazily resolve classes
from the JAR at decode time via the URLClassLoader chain. Since
StreamMessageDecoder does not extend Closeable, there is no lifecycle
hook to clean up, but this is a one-time-per-consumer-init cost and
consumers are long-lived.

Removed dead methods: getFileCopiedToLocal(), getDescriptorFileInputStream(),
createLocalFile(), withLocalFile(), and FileAction from ProtoBufUtils.
Renamed loadClass(File) to createClassLoader(File) for clarity.

Testing:
- Added ProtoBufTempFileLeakTest with 5 tests covering all three code
  paths. Each test snapshots pinot-protobuf* directories in java.io.tmpdir
  before the operation, performs the operation with functional correctness
  assertions (decoding a message and checking field values), then asserts
  no new temp directories remain afterward.
  - testMessageDecoderInitDoesNotLeakTempDir: simple descriptor via
    ProtoBufMessageDecoder, verifies decode of sample record fields.
  - testMessageDecoderComplexDescriptorDoesNotLeakTempDir: complex
    nested descriptor via ProtoBufMessageDecoder.
  - testCodeGenDecoderInitDoesNotLeakTempDir: simple JAR via
    ProtoBufCodeGenMessageDecoder, verifies decode after init cleanup.
  - testCodeGenDecoderComplexJarDoesNotLeakTempDir: complex JAR with
    nested/repeated/map types via ProtoBufCodeGenMessageDecoder.
  - testRecordReaderLifecycleDoesNotLeakTempDir: full ProtoBufRecordReader
    lifecycle (init, read, close) with delimited protobuf data file.
- All 5 tests confirmed to fail before the fix (each leaking 1 temp
  directory) and pass after. Full module suite: 172/172 tests pass.
@mayankshriv
mayankshriv force-pushed the fix-protobuf-temp-dir-leak branch from 8a225a4 to d3a58b5 Compare August 28, 2026 14:23
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.

2 participants