fix: close streams in LoggerContextAdmin.setConfigLocationUri - #4218
fix: close streams in LoggerContextAdmin.setConfigLocationUri#4218SebTardif wants to merge 6 commits into
Conversation
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
left a comment
There was a problem hiding this comment.
@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:
-
I reverted
LoggerContextAdmin.javaback to2.xand 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? -
The buffering looks like it changes more than the close.
ConfigurationSource.resetInputStream()returns early whendata != nulland gives back the same bytes, and that is the methodreconfigure()uses.getFile()is still not null soinitializeWatchersstill registers the file watcher, so a config set over JMX withmonitorIntervalwould 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 thegetConfigurationcall? 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.
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>
|
@ramanathan1504 thanks for the detailed review — updated.
Ready for another look. |
Supersedes `./bench redgreen` from the previous commit. The red-green gates were half a rebuild of `knowledge-creator/log4j-pr-review.sh`, which owned this job first and did the build/spotless half better — full-reactor triggers for build-wide changes, `--offline`, GraphQL feedback that catches resolved and outdated threads, the port/changelog precedent rule, numbered evidence files. This merges the two into eleven steps under one command, per the split: oss-cli knows, workout runs, knowledge-creator remembers. The pollution check moves ahead of red-green. It asks whether the tests dirtied the tree, and red-green works by checking files in and out of that same tree, so in the original order it would report pollution it had created itself. Step 8 no longer claims a PR is broken when the module is. It runs the whole module suite — 8729 tests for log4j-core-test, with 6 failures and 9 errors on this machine in XmlLayoutTest, JsonLayoutTest, MemoryMappedFile*Test and friends, none of them touched by #4218. The summary now splits failures into classes the PR touches and classes it does not, and 08-test-failures.md prints the base-branch command that settles it. Untouched failures are probably pre-existing; "probably" is not a review finding. `bench` is now installable on PATH. ROOT was `dirname "$BASH_SOURCE"`, which through a symlink resolves to ~/.local/bin where configs/ and scripts/ do not exist — and `bench list --apps` still passes from there, because APPS is a literal array in the file, so the breakage looks like a working install until a command touches disk. It now walks the symlink chain by hand, since macOS ships bash 3.2 with no `readlink -f`. Verified through a single and a chained symlink. Also fixes a latent bug carried in from the original: `die` was called at line 69 to handle an unresolvable --out but not defined until line 72, so that error path would have died with "command not found". Verified end to end against apache/logging-log4j2#4218: build PASS, spotless clean, tree clean, RED "Tests run: 4, Failures: 1", GREEN PASS. Failure classification checked under bash against the real 8729-test log — 0 mine / 6 others, and 1/5 when a failing class is spliced into the touched set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ommand (#74) * Add ./bench redgreen — run a PR's tests without its fix Automates the hand-check that caught #4218: revert the production change, keep the tests, and see whether anything actually goes red. That PR's first revision shipped three tests which all passed without the fix, and only running them said so. Four gates in a throwaway git worktree, so the Log4j clone stays on 2.x and ~/.m2 is never overwritten — no `mvn install`, so no later bench run silently measures the PR: BUILD the PR branch compiles and its own tests pass SPOTLESS formatted the way the project enforces RED base + the PR's test files only -> must fail GREEN base + tests + the PR's main files -> must pass RED reports four outcomes, not two. An early version ran under JDK 22, Log4j's enforcer rejected it with [17,18) in log4j-bom before compiling anything, and the non-zero exit reported a green tick on RED — the same shape as the commons-compress repro that passed on four versions having compressed nothing. So RED now demands a surefire "Tests run: ... Failures:" line or a test-source COMPILATION ERROR, reports "inconclusive" otherwise, and a failed BUILD aborts rather than letting later gates report on a broken tree. The JDK is checked by asking the JVM its own java.specification.version, because java_home -v answers an unknown version with the newest JDK and exit 0. Verified against #4218: BUILD pass, SPOTLESS pass, RED "Tests run: 4, Failures: 1", GREEN pass. That output now backs the paste-ready comment in docs/pr-reviews/ instead of the author's word for it. PR-REVIEW.md §3 leads with the gates and spells out how to read each RED outcome; CLAUDE.md's command count follows the dispatch table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fold the knowledge-creator harness in: ./bench review Supersedes `./bench redgreen` from the previous commit. The red-green gates were half a rebuild of `knowledge-creator/log4j-pr-review.sh`, which owned this job first and did the build/spotless half better — full-reactor triggers for build-wide changes, `--offline`, GraphQL feedback that catches resolved and outdated threads, the port/changelog precedent rule, numbered evidence files. This merges the two into eleven steps under one command, per the split: oss-cli knows, workout runs, knowledge-creator remembers. The pollution check moves ahead of red-green. It asks whether the tests dirtied the tree, and red-green works by checking files in and out of that same tree, so in the original order it would report pollution it had created itself. Step 8 no longer claims a PR is broken when the module is. It runs the whole module suite — 8729 tests for log4j-core-test, with 6 failures and 9 errors on this machine in XmlLayoutTest, JsonLayoutTest, MemoryMappedFile*Test and friends, none of them touched by #4218. The summary now splits failures into classes the PR touches and classes it does not, and 08-test-failures.md prints the base-branch command that settles it. Untouched failures are probably pre-existing; "probably" is not a review finding. `bench` is now installable on PATH. ROOT was `dirname "$BASH_SOURCE"`, which through a symlink resolves to ~/.local/bin where configs/ and scripts/ do not exist — and `bench list --apps` still passes from there, because APPS is a literal array in the file, so the breakage looks like a working install until a command touches disk. It now walks the symlink chain by hand, since macOS ships bash 3.2 with no `readlink -f`. Verified through a single and a chained symlink. Also fixes a latent bug carried in from the original: `die` was called at line 69 to handle an unresolvable --out but not defined until line 72, so that error path would have died with "command not found". Verified end to end against apache/logging-log4j2#4218: build PASS, spotless clean, tree clean, RED "Tests run: 4, Failures: 1", GREEN PASS. Failure classification checked under bash against the real 8729-test log — 0 mine / 6 others, and 1/5 when a failing class is spliced into the touched set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add `bench hub`, the `--file` hand-off, and the cross-references Three repos overlapped on PR review and none of them mentioned the others, which is how `redgreen` came to be written on top of a harness that already existed. This closes that: each README now states the split — oss-cli knows, workout runs, knowledge-creator remembers — and the one test that decides where new work belongs (does it need to execute code against a real app?). `bench review <n> --file` hands the finished write-up to knowledge-creator's pr-review-file.py, which files it under Projects/<topic>/pr-reviews/ and indexes it. That is the only cross-repo call at runtime, and it goes in the right direction: the evidence under .bench/ is disposable, the reasoning is not, and a harvester cannot reconstruct the parts of a review that were deliberately never posted. `bench hub` serves all three repos as one local page on :8787 — live git state, the combined PR-review walkthrough, and every doc from all three rendered in place. Regenerated per request from the working trees, so there is no build step, no cache and no watcher to drift; a reload is the refresh. It only reads: never fetches, pulls or writes, and "behind" is measured against the last fetch you ran rather than the network. Stdlib only, including a markdown subset renderer, because this machine has no markdown library and the page has to work with no network. The first version of that renderer hung instead of failing: every branch guard can be true of the line the loop is standing on — a `|` row whose table has no separator is the common case — leaving the paragraph fallback consuming nothing and never advancing. It now always consumes the current line first. Checked against the inputs that hung it, plus injection: 23 sections, 58 tables, no unbalanced tags, `<script>` escaped. Verified: generation, the renderer, and `bench review --file` dry-run against the real 4218 write-up. NOT verified: the HTTP serving path — start, status.json, the reload poll — so treat `bench hub` as unproven until it has been run once by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * bench hub: open the browser by default, and fix a malformed CSS rule `bench hub` printed a URL and did nothing else unless you also passed --open, which reads as broken for a command whose entire job is to show you a page. Opening is now the default; --no-open serves without a browser, and --open is kept as a no-op so anything already passing it still works. A browser that cannot be launched now says so and repeats the URL, rather than leaving a silent server behind. Also drops a leftover line from the stylesheet — `:root:not([data-theme=light]) @media (prefers-color-scheme:dark){}` — which is not valid CSS. Browsers discarded it through error recovery, so the theme worked by luck rather than by construction. Braces now balance with no empty rules. Serving is verified this time, which the previous commit could not claim: HTTP 200 and 343KB through the installed `bench` symlink from an unrelated directory, status.json returning the three-repo stamp, and a taken port reporting "cannot bind ... Address already in use" rather than dying silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Address vy review: drop OS FD-count/delete assertions in favor of an AtomicReference + ConfigurationFactory capture of source.getInputStream(), then assertThrows(IOException) on read after setConfigLocationUri. Also switch the file path to Files.newInputStream as requested.
|
@vy Follow-ups from your review are on the tip (
Could you take another look when you have a moment? |
What Problem This Solves
LoggerContextAdmin.setConfigLocationUriopens aFileInputStreamor URL stream and buildsConfigurationSource(InputStream, File/URL). That constructor documents that the caller owns the stream.Built-in factories (
XmlConfiguration,JsonConfiguration,PropertiesConfigurationFactory) closegetInputStream()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 callsgetInputStream()(or fails before consume) → stream stays open. Defensive fix is try-with-resources on the caller side while keeping a stream-backed source soresetInputStream()/monitorIntervalstill re-open the file (buffering intodatawould short-circuit that and keep stale config; thanks @ramanathan1504).Fix
Same pattern for the URL branch. Double-close after XmlConfiguration's finally is safe.
Red-green
Red (production try-with-resources removed, factory that never consumes stream):
Green (with try-with-resources): 4 tests, 0 failures.
Summary
ConfigurationSource(no full buffer)