Skip to content

fix(jni): release the schema export in VortexWriter.build() - #18

Merged
ChristopherWirt merged 2 commits into
developfrom
rasheed/jni-writer-schema-release
Nov 2, 2026
Merged

ChristopherWirt merged 2 commits into
developfrom
rasheed/jni-writer-schema-release

Conversation

@gitRasheed

@gitRasheed gitRasheed commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

VortexWriter.build() exports an Arrow schema that native creation borrows. Closing only the schema struct leaks the child allocations. Release the export before closing the struct, for both URI and stream writers.

The cleanup uses separate try-with-resources entries: the release callback runs before the struct closes, both cleanup steps are attempted, and cleanup failures are suppressed onto an existing export or creation failure. The release callback is checked before invocation so an export that did not install one can still be cleaned up.

Review follow-up

  • Parameterize the allocation regression across URI/stream writers, flat/nested schemas, and successful/rejected creation (eight cases).
  • Check that native metadata validation errors retain their cause and leave no Arrow allocations.
  • Use a child of the shared root allocator for isolated leak assertions.
  • Keep native schema borrowing unchanged. The separate address-based batch-schema leak from the review is tracked in JNI address-based writer callers leak exported batch schemas #53.

Validation

The original revision was checked with ./gradlew --parallel --continue javadoc check. The latest cleanup and test changes have not been run; build, test, lint, and formatting execution remain the user's choice under repository guidance.

I used AI tools to help write and review this change.

build() closed the exported ArrowSchema without calling its release
callback. Native code only borrows the schema, so the child structs the
export allocated were never freed, and every writer's allocator closed
with outstanding memory.

Signed-off-by: gitRasheed <[email protected]>

@ChristopherWirt ChristopherWirt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks correct: Schema::try_from(&*ptr) copies the schema, so releasing after create/createStream is safe, and the release != 0 guard covers a failed export.

One gap outside the diff: native writeBatch also only borrows the schema (&*(arrow_schema_addr as *const FFI_ArrowSchema)), and the documented usage (e.g. JNIWriterTest L264-268, NativeIOBridgeTest) closes it without calling release(). That leaks the schema's children on every batch.

}
return new VortexWriter(pointer);
} finally {
// Native code only borrows the schema, so the children the export allocated are ours to release.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative: create/createStream use the schema once, so native could take ownership with FFI_ArrowSchema::from_raw (as writeBatch already does for the array). Then plain close() on the Java side is always correct, and new call sites can't reintroduce this leak.

return new VortexWriter(pointer);
} finally {
// Native code only borrows the schema, so the children the export allocated are ours to release.
if (ffi.snapshot().release != 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VortexDataWriter.closeArrowResources does the same thing but calls release() unguarded, which breaks if exportSchema threw. Worth a shared releaseAndClose(ArrowSchema) helper used in both places?

} finally {
// Native code only borrows the schema, so the children the export allocated are ours to release.
if (ffi.snapshot().release != 0) {
ffi.release();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If release() throws, close() is skipped and the original exception from create is lost. Suggest try { ffi.release(); } finally { ffi.close(); }.

}

@Test
public void testCreateWriterReleasesSchemaExport() throws IOException {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only covers the URI path with a flat schema. Consider adding createStream, a nested schema, and a writeBatch call. The last one would fail today because of the writeBatch schema leak.

public void testCreateWriterReleasesSchemaExport() throws IOException {
String writePath = tempDir.resolve("test_schema_release.vortex").toUri().toString();
Session session = Session.create();
try (BufferAllocator allocator = new RootAllocator()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ArrowAllocation.rootAllocator().newChildAllocator("test", 0, Long.MAX_VALUE) gives the same zero-leak check without a second root allocator.

@ChristopherWirt ChristopherWirt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Ran JNIWriterTest locally on 124affa: all 8 schema-release cases pass (16 passed, 1 pre-existing skip). The writeBatch schema leak is tracked in #53.

@ChristopherWirt
ChristopherWirt merged commit 7ad32dd into develop Nov 2, 2026
1 check passed
ChristopherWirt added a commit that referenced this pull request Nov 2, 2026
Preserve the generic Arrow field writers from #17 and exception-safe schema cleanup and regression coverage from #18 alongside independent JNI buffer ownership.

Signed-off-by: Christopher Wirt <[email protected]>
gitRasheed added a commit that referenced this pull request Nov 2, 2026
Develop changed old-connector files that this branch removes (#17) and
the JNI writer files this branch already carries with the new writer port
(#18, #49). The resolution keeps this branch's versions and takes the
broader schema release test from #18.

Signed-off-by: gitRasheed <[email protected]>
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