Skip to content

Close Files.walk stream in LocalLogFileServer to prevent file descriptor leak - #19242

Open
dkranchii wants to merge 1 commit into
apache:masterfrom
dkranchii:fix/log-file-server-fd-leak
Open

Close Files.walk stream in LocalLogFileServer to prevent file descriptor leak#19242
dkranchii wants to merge 1 commit into
apache:masterfrom
dkranchii:fix/log-file-server-fd-leak

Conversation

@dkranchii

Copy link
Copy Markdown
Contributor

Summary

LocalLogFileServer.getAllLogFilePaths() enumerated log files via Files.walk(_logRootDirPath).filter(...).forEach(...) without a try-with-resources block. Per the JDK Files.walk javadoc:

The returned stream encapsulates one or more DirectoryStreams. If timely disposal of file system resources is required, the try-with-resources construct should be used…

Because downloadLogFile(String) calls getAllLogFilePaths() on every download request (to authorize the requested path), the leak amplifies on hot paths — long-lived server and controller instances gradually accumulate DirectoryStream file descriptors until they approach the process ulimit -n.

This PR wraps the Files.walk stream in a try-with-resources block so the underlying DirectoryStream(s) are released as soon as enumeration completes. Sibling code in Pinot already uses the same pattern (e.g. LocalPinotFS.listFiles).

Change

  • pinot-common/src/main/java/org/apache/pinot/common/utils/log/LocalLogFileServer.java — wrap Files.walk(_logRootDirPath) in try (Stream<Path> paths = Files.walk(...)); enumeration logic is unchanged.

Backwards compatibility

None affected. Public API, return values, and enumeration behavior are unchanged.

Tests

  • Added LocalLogFileServerTest#testGetAllLogFilePathsEnumeratesNestedDirectories — creates a nested sub/dir/nested.log, asserts both files are enumerated with paths relative to the log root, and asserts both are downloadable via downloadLogFile(...). This guards the refactor against a regression that would break recursion into subdirectories.
  • The pre-existing testLoggerFileServer continues to exercise the flat-directory happy path and the FORBIDDEN response for unknown paths.

Run locally:

./mvnw -pl pinot-common -am -Dtest=LocalLogFileServerTest test

Risk

Very low. The change is a mechanical try-with-resources wrap around an existing Files.walk invocation; the traversal semantics are unchanged. The stream is fully consumed inside the block, so no lazy operations escape.

…tor leak

LocalLogFileServer.getAllLogFilePaths() enumerated log files via
Files.walk(_logRootDirPath).filter(...).forEach(...) with no
try-with-resources block. Per the JDK Javadoc, the Stream returned by
Files.walk encapsulates one or more DirectoryStreams, and the caller is
responsible for closing it to release native file-descriptor resources.

Because downloadLogFile(String) calls getAllLogFilePaths() on every
download request, this leak amplified on hot paths: each call could
retain a DirectoryStream beyond GC's discretion, gradually approaching
the process ulimit -n on long-lived server and controller instances.

Wrap the Files.walk stream in try-with-resources so the underlying
DirectoryStream is released as soon as enumeration completes.

Tests:
- New testGetAllLogFilePathsEnumeratesNestedDirectories verifies that
  recursion into subdirectories still returns paths relative to the log
  root and that those paths remain downloadable through
  downloadLogFile, guarding the refactor against regressions.
- Existing testLoggerFileServer continues to cover the flat-directory
  happy path and the FORBIDDEN response for unknown paths.
@codecov-commenter

codecov-commenter commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.95%. Comparing base (2032d7a) to head (2e9b76f).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19242      +/-   ##
============================================
+ Coverage     66.94%   66.95%   +0.01%     
  Complexity     1423     1423              
============================================
  Files          3452     3452              
  Lines        218564   218620      +56     
  Branches      34731    34742      +11     
============================================
+ Hits         146320   146386      +66     
+ Misses        60549    60543       -6     
+ Partials      11695    11691       -4     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 66.95% <100.00%> (+0.01%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 66.95% <100.00%> (+0.01%) ⬆️
unittests 66.95% <100.00%> (+0.01%) ⬆️
unittests1 57.69% <100.00%> (-0.01%) ⬇️
unittests2 39.07% <0.00%> (+0.04%) ⬆️

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.

@dkranchii

Copy link
Copy Markdown
Contributor Author

@Jackie-Jiang can you review this pr. thanks

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