Source is not closed when a JSON tester read fails - #51384
Conversation
AbstractJsonMarshalTester.read(Resource) and read(Reader) close their source only after readObject has returned. When the delegate throws, for example on malformed JSON, closeQuietly is skipped and the source is left open. Whether this leaks depends on the delegate: Jackson and JSON-B close the source themselves, but Gson does not, so GsonTester leaks a file handle for every failed read from a Resource, File or classpath path. Close in a finally block so the tester honours its own contract regardless of the delegate. See spring-projectsgh-51384 Signed-off-by: dlwldn30 <dlwldn30@naver.com>
dcd57da to
4d8db1b
Compare
| T object = readObject(reader, getTypeNotNull()); | ||
| return new ObjectContent<>(this.type, object); | ||
| } | ||
| finally { |
There was a problem hiding this comment.
Could we add a test where close() itself throws an IOException? This would verify that closeQuietly suppresses the close failure and does not affect the result of the read operation.
There was a problem hiding this comment.
Thanks for taking a look.
That case is worth noting, though it is pre-existing behaviour rather than something this change introduces: closeQuietly already swallowed close failures before this PR, on the paths where it was reached.
What this PR does change is that the source is now closed when readObject throws. readResourceWhenReadFailsShouldCloseInputStream covers that, and it fails without the production change.
The finally block plus closeQuietly is also deliberate over try-with-resources: it keeps a failing close() from propagating on the success path, and from being attached as a suppressed exception on the failure path, so the read outcome is always what the caller sees.
Happy to add a test pinning that down if a maintainer would like it in this PR.
What Problem This Solves
AbstractJsonMarshalTester.read(Resource)andread(Reader)open or takeownership of a source, hand it to
readObject(...), and then close it:closeQuietlyis only reached whenreadObjectreturns normally. When itthrows — most commonly on malformed JSON, but also on any binding failure —
the source is left open.
Whether that actually leaks depends on whether the delegate closes the source
itself, which the base class cannot control. I measured this per tester with
the tests below rather than assuming it:
JacksonTester/Jackson2TesterAUTO_CLOSE_SOURCE)JsonbTesterGsonTesterSo
GsonTester.read(...)leaks a file handle on every failed read. Itreaches
read(Resource)through four public entry points, three of whichopen a real file:
read(String resourcePath)viaClassPathResource,read(File)viaFileSystemResource, andread(Resource)itself.Negative-path assertions are ordinary in test suites, so this accumulates
over a run rather than happening once.
Evidence
Red — the fix reverted, the tests present:
Only
GsonTesterTestsfails, which is what makes the point: the other threepass by accident, because their delegate happens to close the source. The
tester should not depend on that.
Green — same command with the fix applied:
Summary
Close the source in a
finallyblock in both methods, so the tester honoursits own contract regardless of the delegate. Nothing else changes:
ObjectContentis constructed before the close in both the old and new code,and
closeQuietlystill swallowsIOExceptionfromclose.JsonLoader, used byBasicJsonTester, is not affected — it delegates toFileCopyUtils.copyToString, which already closes in afinallyblock.Related
Same leak class as prior cleanups, all merged:
JarFile is not closed when finding main class from archiveClose FileOutputStream delegate in InspectingOutputStreamClose URLClassLoader in ArchitectureCheckNo open PR touches
AbstractJsonMarshalTester, and I found no existing issuefor this.
Test plan
readResourceWhenReadFailsShouldCloseInputStreamandreadReaderWhenReadFailsShouldCloseReaderfail forGsonTesterTestswithout the fix
:core:spring-boot-test:test— 755 tests, 0 failures, 1 skipped:core:spring-boot-test-autoconfigure:test— 22 tests, 0 failurescheckFormatMain,checkFormatTest,checkstyleMain,checkstyleTestThe tests were added to
AbstractJsonMarshalTesterTests, so they run forJacksonTester,Jackson2Tester,GsonTesterandJsonbTester.Contributed on behalf of Goatshave.