Skip to content

Fix #798: Close File/Path stream if closing of wrapper fails in IonFactory - #799

Merged
cowtowncoder merged 3 commits into
3.xfrom
tatu-claude/3.x/798-ion-raw-fallback-close
Sep 19, 2026
Merged

cowtowncoder merged 3 commits into
3.xfrom
tatu-claude/3.x/798-ion-raw-fallback-close

Conversation

@cowtowncoder

Copy link
Copy Markdown
Member

Fixes #798. Follow-up to #780 / #791, and the Ion counterpart of FasterXML/jackson-core#1711 + FasterXML/jackson-core#1718.

Closing the outermost resource on failed construction normally cascades down to the stream Jackson opened from the File/Path. If that close() throws, it does not — and IonFactory, which implements those create methods itself, had no fallback. The base factories got one in jackson-core#1711/#1718; this brings IonFactory in line, now that FasterXML/jackson-core#1722 has made the 2-argument _closeOnFailedConstruction(toClose, rawFallback, failure) protected.

Changes

  • createParser(File) / createParser(Path) keep the stream Jackson opened (rawIn) separate from the decorated one instead of overwriting the reference, and pass it down.
  • The two _createParser() overloads that own their input take that raw source in place of the old closeInputOnFailedConstruction boolean (null = caller-provided, leave alone), and close via _closeOnFailedConstruction(outermost, rawIn, e). The outermost is the IonReader once it exists, else the decorated stream.
  • _createGenerator(ObjectWriteContext, OutputStream, JsonEncoding, boolean) does the same with out as fallback behind the IonWriter / UTF8Writer.
  • Drops the private static _releaseOnFailedConstruction() copy that Fix IonFactory resource cleanup on failed construction #791 had to add, in favor of the inherited one (also protected as of jackson-core#1722).

Requires jackson-core 3.3.0-SNAPSHOT build 55 or later.

Tests

Three added to IonFactoryFailedConstructionTest, all exercising a decorator wrapper / IonWriter whose close() throws without closing what it wraps:

  • closesFileInputStreamWhenDecoratedStreamCloseFails — failure after IonReader exists
  • closesFileInputStreamWhenDecoratedStreamCloseFailsBeforeIonReader — failure before it does
  • closesFileOutputStreamWhenIonWriterCloseFails — generator side

Each asserts the tracked raw stream is closed exactly once and the close failure is recorded as suppressed. Reverting IonFactory.java fails all three (expected: <1> but was: <0>), leaving the other 17 green; with the fix, ./mvnw verify is green across all modules (ion: 286 tests).

🤖 Generated with Claude Code

…IonFactory`

`IonFactory` implements its own `File`/`Path` create methods (it extends
`DecorableTSFactory` directly), so it did not get the second level of cleanup
that jackson-core#1711 / #1718 added to the base factories: closing of the
outermost resource is expected to cascade down, but if that `close()` throws,
the stream Jackson opened leaks anyway.

Now tracks the raw stream alongside the decorated/wrapping one and passes it
as fallback to `_closeOnFailedConstruction(toClose, rawFallback, failure)`,
in all three places that close on failed construction: both `_createParser()`
overloads that own their input, and `_createGenerator(OutputStream)`.

Also drops the private `_releaseOnFailedConstruction()` copy in favor of the
inherited one, made `protected` by jackson-core#1722.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown

🧪 Code Coverage Report

Coverage Type Coverage Change
📝 Instructions 78.21% 📈 +0.02%
🔀 Branches 70.28% 📈 +0.01%

@cowtowncoder cowtowncoder added this to the 3.3.0 milestone Sep 19, 2026
cowtowncoder and others added 2 commits September 18, 2026 21:09
`inputToClose` could never be observed holding the decorated stream: the
delegation flag is set on the very next statement, and the catch body only
runs when that flag is still false. Cleanup of the decorated stream belongs
to `_createParser()`, which has the raw stream as fallback; the outer handler
only ever needs `rawIn`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cowtowncoder
cowtowncoder merged commit 84c5474 into 3.x Sep 19, 2026
3 checks passed
@cowtowncoder
cowtowncoder deleted the tatu-claude/3.x/798-ion-raw-fallback-close branch September 19, 2026 04:29
cowtowncoder added a commit that referenced this pull request Sep 19, 2026
`IonFactory` conflicted throughout parser creation: `3.x` has the failed-construction
cleanup from #798/#799, which `3.2` does not, and #805 touches the same methods.
Resolved by combining rather than picking a side -- each `_createParser()` helper
keeps its `try`/`catch` cleanup and gains the document length check:

- source is wrapped before the `try`, so the wrapper is what gets closed on failure
  (and cascades to what it wraps), with `rawIn` / `rawR` still the fallback
- `Reader` helper takes a `checkLength` flag, since the `char[]` path validates
  length exactly up front and must not also count
- `_newReader()` and the two counting wrappers came across unchanged

Verified both features still hold: `DocumentLengthIonReadTest` (9), `DeeplyNestedIonReadTest`
(8) and `IonFactoryFailedConstructionTest` (20) all pass, 303 tests in the module.

No release notes entry: #805 is recorded under 3.1.7 and 3.2.3, which came with the merge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IonFactory leaks File/Path stream on failed construction if closing of wrapping resource fails

1 participant