Skip to content

fix(jni): free QueryResult on Arrow entry-point failure - #14

Merged
adsharma merged 1 commit into
mainfrom
fix/arrow-jni-leak-on-failure
Aug 1, 2026
Merged

fix(jni): free QueryResult on Arrow entry-point failure#14
adsharma merged 1 commit into
mainfrom
fix/arrow-jni-leak-on-failure

Conversation

@adsharma

@adsharma adsharma commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #13

Problem

Four JNI entry points free only their wrapper when the C API reports failure, leaving the QueryResult the out-param now owns unfreed.

The C API's setQueryResult (in ladybug/src/c_api/connection.cpp:77) releases the C++ QueryResult into the out-param before checking success:

auto queryResultPtr = queryResult.release();
outQueryResult->_query_result = queryResultPtr;
outQueryResult->_is_owned_by_cpp = false;
if (!queryResultPtr->isSuccess()) {
    return LbugError;
}

The JNI then does delete queryResult; on the failure branch — that only frees the lbug_query_result struct, not the QueryResult it points at. lbug_query_result_destroy is the function that frees it, and it is not called. Linear leak of ~432 bytes per failed call, measured by RSS in the issue.

Sites

lbug_java.cpp C API function
lbugConnectionCreateArrowTable lbug_connection_create_arrow_table
lbugConnectionCreateArrowRelTable lbug_connection_create_arrow_rel_table
lbugConnectionCreateArrowRelTableCSR lbug_connection_create_arrow_rel_table_csr
lbugConnectionDropArrowTable lbug_connection_drop_arrow_table

The fourth is reachable without any Arrow data: dropArrowTable("no_such_table") goes through setQueryResult.

Fix

Call lbug_query_result_destroy(queryResult) before delete queryResult on the failure branch of all four. lbug_query_result_destroy is null-safe (it returns on null lbug_query_result) and a no-op when the C API's catch block returned (in which case _query_result is null), so this is safe for both the setQueryResult and the catch-block failure modes.

This is the "smaller change" from the issue: it keeps the existing throwing behavior. The alternative — adopting the discriminator pattern from lbugConnectionQuery (line 717) and lbugConnectionExecute (line 774) and returning the QueryResult so Java can call getErrorMessage() — would also fix the leak and additionally resolve the diagnostic half (the generic throwLastError fallback when lbug_get_last_error() is empty, ladybug#754), but it is a behavior change. Happy to follow up with that as a separate PR if desired.

Verified

  • Applied to the four sites listed above; each diff is identical in shape (one new line and three comment lines, before the existing delete queryResult;).
  • lbug_query_result_destroy (ladybug/src/c_api/query_result.cpp:10) is null-safe and a no-op when _query_result is null, so this is safe for both the setQueryResult failure path (where _query_result is non-null and _is_owned_by_cpp is false — the function will delete the underlying QueryResult) and the catch-block path (where _query_result is null).

The four Arrow JNI entry points (createArrowTable, createArrowRelTable,
createArrowRelTableCSR, dropArrowTable) freed only their wrapper on
failure. The C API's setQueryResult (ladybug/src/c_api/connection.cpp:77)
releases the C++ QueryResult into the out-param *before* checking success,
so on the setQueryResult failure branch the out-param owned a QueryResult
that the wrapper's delete did not touch and lbug_query_result_destroy
was not called.

Linear leak of ~432 bytes per failed call, verified by RSS measurement in
issue #13.

Call lbug_query_result_destroy(queryResult) before delete on the failure
branch of all four. lbug_query_result_destroy is null-safe and a no-op
when setQueryResult did not run, so this is safe for both the
setQueryResult and the catch-block failure modes.

Closes #13
@adsharma
adsharma merged commit 9adfa99 into main Aug 1, 2026
3 checks passed
@adsharma
adsharma deleted the fix/arrow-jni-leak-on-failure branch August 1, 2026 01:21
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.

Arrow JNI entry points leak a QueryResult on the failure path

1 participant