Skip to content

fix: close streams in LoggerContextAdmin.setConfigLocationUri - #4218

Open
SebTardif wants to merge 3 commits into
apache:2.xfrom
SebTardif:fix/f005-jmx-config-stream-leak
Open

fix: close streams in LoggerContextAdmin.setConfigLocationUri#4218
SebTardif wants to merge 3 commits into
apache:2.xfrom
SebTardif:fix/f005-jmx-config-stream-leak

Conversation

@SebTardif

@SebTardif SebTardif commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

LoggerContextAdmin.setConfigLocationUri opens a FileInputStream or URL stream and builds ConfigurationSource(InputStream, File/URL). That constructor documents that the caller owns the stream.

Built-in factories (XmlConfiguration, JsonConfiguration, PropertiesConfigurationFactory) close getInputStream() when they consume it. That is factory-side cleanup, not a substitute for the caller contract: if a factory path never reads the stream (or returns before consuming it), the descriptor can leak. Same ownership theme as #4127.

Evidence

Leak path (why close is still needed)

Xml/JSON/properties factories close streams they consume. The remaining risk is caller ownership: open stream → hand to ConfigurationSource → factory that never calls getInputStream() (or fails before consume) → stream stays open. Defensive fix is try-with-resources on the caller side while keeping a stream-backed source so resetInputStream() / monitorInterval still re-open the file (buffering into data would short-circuit that and keep stale config; thanks @ramanathan1504).

Fix

try (final InputStream in = new FileInputStream(configFile)) {
  final ConfigurationSource configSource = new ConfigurationSource(in, configFile);
  final Configuration config = ConfigurationFactory.getInstance().getConfiguration(loggerContext, configSource);
  loggerContext.start(config);
}

Same pattern for the URL branch. Double-close after XmlConfiguration's finally is safe.

Red-green

export JAVA_HOME=$(/usr/libexec/java_home -v 17)
./mvnw -pl log4j-core,log4j-core-test -am test \
  -Dtest=LoggerContextAdminSetConfigLocationUriTest \
  -Dsurefire.failIfNoSpecifiedTests=false

Red (production try-with-resources removed, factory that never consumes stream):

AssertionFailedError: expected: <173> but was: <177>  // open FD count

Green (with try-with-resources): 4 tests, 0 failures.

Summary

  • Keep stream-backed ConfigurationSource (no full buffer)
  • try-with-resources around load for file and URL
  • Regression test with non-consuming factory + FD/delete check
  • Changelog entry

ConfigurationSource(InputStream, File/URL) leaves stream ownership with the
caller. Buffer configuration bytes into a Source-backed ConfigurationSource
and close the FileInputStream/URL stream before ConfigurationFactory runs so
failed or successful JMX reconfigure paths do not leak descriptors.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

@ramanathan1504 ramanathan1504 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.

@SebTardif thanks for this one, the missing close() is real, I could follow it in the code.

I pulled it down and ran it locally (./mvnw test -pl log4j-core,log4j-core-test -am -Dtest=LoggerContextAdminSetConfigLocationUriTest), two things came up:

  1. I reverted LoggerContextAdmin.java back to 2.x and kept your tests, all 3 still pass. They only check the config loads, not that anything is closed. Can you make at least one of them fail without the production change?

  2. The buffering looks like it changes more than the close. ConfigurationSource.resetInputStream() returns early when data != null and gives back the same bytes, and that is the method reconfigure() uses. getFile() is still not null so initializeWatchers still registers the file watcher, so a config set over JMX with monitorInterval would keep seeing the file change but keep loading the old config. Same idea as your #4125 — can you keep the stream-backed source and just widen the try-with-resources around the getConfiguration call? Smaller diff and no behaviour change.

One more, XmlConfiguration, JsonConfiguration and PropertiesConfigurationFactory all close getInputStream() in a finally already, so which path is the one that actually leaks? Good to have it written down so the changelog says the right thing.

Let me know when it's updated and I will review it again.

@github-project-automation github-project-automation Bot moved this to Changes requested in Log4j pull request tracker Aug 2, 2026
Address review on apache#4218:
- Drop full buffering so ConfigurationSource remains stream/file backed
  (monitorInterval / resetInputStream still re-read the file).
- Close the caller-owned InputStream with try-with-resources around
  getConfiguration and start.
- Add a regression test that installs a factory which never consumes the
  stream; red fails on open FD count without the try-with-resources, green
  closes cleanly.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SebTardif

Copy link
Copy Markdown
Contributor Author

@ramanathan1504 thanks for the detailed review — updated.

  1. Red-green test: setConfigLocationUri_closesCallerOwnedStreamWhenFactoryDoesNotConsumeIt installs a ConfigurationFactory that returns DefaultConfiguration without reading the stream. Without the try-with-resources, open FD count rises (expected: 173 but was: 177 locally). With the fix, FD count holds and the temp file can be deleted (Windows lock path). Happy-path tests still load real XML.

  2. No full buffering: dropped the toByteArray / Source+byte[] approach. Source stays stream/file-backed so resetInputStream() still re-opens the file for monitorInterval watchers.

  3. Leak note: Xml/JSON/Properties factories already close when they consume the stream. The gap is the caller-owns-stream contract when a factory path never consumes (or fails before consume). try-with-resources on the JMX method covers that without changing reconfigure/watch behavior.

Ready for another look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Changes requested

Development

Successfully merging this pull request may close these issues.

2 participants