This is an automated email from the ASF dual-hosted git repository.
jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-java.git
The following commit(s) were added to refs/heads/main by this push:
new 7e61d462c GH-932: [JDBC] Fix memory leak on Connection#close due to
unclosed Statement(s) (#933)
7e61d462c is described below
commit 7e61d462c094ff9eb3a692176b040a08f81654fe
Author: Pedro Matias <[email protected]>
AuthorDate: Thu Jan 22 14:19:32 2026 +0000
GH-932: [JDBC] Fix memory leak on Connection#close due to unclosed
Statement(s) (#933)
## What's Changed
Closing a Connection when there was one or more ResultSet that matched
the following 2 conditions
1. hadn't been fully consumed
2. was obtained via a Statement instance of this Connection instance
would generate exceptions due to memory leaks.
Now, closing a Connection will first close all the Statement instances
obtained via that Connection,
which has a side effect of closing all the ResultSet, and then proceed
with the old closing logic. This
side effect is guaranteed by the JDBC Spec 4.3, chapter 13.1.4
The old closing logic was also slightly refactored to:
1. remove duplicate calls to ArrowFlightSqlClientHandler.close()
5. make sure that any exception generated during Connection.close()
would be wrapped in a SQLException.
Closes #932.
---
.../arrow/driver/jdbc/ArrowFlightConnection.java | 39 +++++++++++++++++-----
.../apache/arrow/driver/jdbc/ConnectionTest.java | 38 +++++++++++++++++++++
2 files changed, 68 insertions(+), 9 deletions(-)
diff --git
a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightConnection.java
b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightConnection.java
index f6f17770f..f81233ec3 100644
---
a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightConnection.java
+++
b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightConnection.java
@@ -20,6 +20,7 @@ import static
org.apache.arrow.driver.jdbc.utils.ArrowFlightConnectionConfigImpl
import io.netty.util.concurrent.DefaultThreadFactory;
import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -180,19 +181,39 @@ public final class ArrowFlightConnection extends
AvaticaConnection {
@Override
public void close() throws SQLException {
- clientHandler.close();
- if (executorService != null) {
- executorService.shutdown();
+ Exception topLevelException = null;
+ try {
+ if (executorService != null) {
+ executorService.shutdown();
+ }
+ } catch (final Exception e) {
+ topLevelException = e;
+ }
+ ArrayList<AutoCloseable> closeables = new
ArrayList<>(statementMap.values());
+ closeables.add(clientHandler);
+ closeables.addAll(allocator.getChildAllocators());
+ closeables.add(allocator);
+ try {
+ AutoCloseables.close(closeables);
+ } catch (final Exception e) {
+ if (topLevelException == null) {
+ topLevelException = e;
+ } else {
+ topLevelException.addSuppressed(e);
+ }
}
-
try {
- AutoCloseables.close(clientHandler);
- allocator.getChildAllocators().forEach(AutoCloseables::closeNoChecked);
- AutoCloseables.close(allocator);
-
super.close();
} catch (final Exception e) {
- throw AvaticaConnection.HELPER.createException(e.getMessage(), e);
+ if (topLevelException == null) {
+ topLevelException = e;
+ } else {
+ topLevelException.addSuppressed(e);
+ }
+ }
+ if (topLevelException != null) {
+ throw AvaticaConnection.HELPER.createException(
+ topLevelException.getMessage(), topLevelException);
}
}
diff --git
a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ConnectionTest.java
b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ConnectionTest.java
index 46762f331..dbedbe9d3 100644
---
a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ConnectionTest.java
+++
b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ConnectionTest.java
@@ -17,6 +17,7 @@
package org.apache.arrow.driver.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -27,6 +28,7 @@ import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
+import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import org.apache.arrow.driver.jdbc.authentication.UserPasswordAuthentication;
@@ -660,4 +662,40 @@ public class ConnectionTest {
assertEquals(catalog, actualCatalog);
}
}
+
+ @Test
+ public void testStatementsClosedOnConnectionClose() throws Exception {
+ // create a connection
+ final Properties properties = new Properties();
+ properties.put(ArrowFlightConnectionProperty.HOST.camelName(),
"localhost");
+ properties.put(
+ ArrowFlightConnectionProperty.PORT.camelName(),
FLIGHT_SERVER_TEST_EXTENSION.getPort());
+ properties.put(ArrowFlightConnectionProperty.USER.camelName(), userTest);
+ properties.put(ArrowFlightConnectionProperty.PASSWORD.camelName(),
passTest);
+ properties.put("useEncryption", false);
+
+ Connection connection =
+ DriverManager.getConnection(
+ "jdbc:arrow-flight-sql://"
+ + FLIGHT_SERVER_TEST_EXTENSION.getHost()
+ + ":"
+ + FLIGHT_SERVER_TEST_EXTENSION.getPort(),
+ properties);
+
+ // create some statements
+ int numStatements = 3;
+ Statement[] statements = new Statement[numStatements];
+ for (int i = 0; i < numStatements; i++) {
+ statements[i] = connection.createStatement();
+ assertFalse(statements[i].isClosed());
+ }
+
+ // close the connection
+ connection.close();
+
+ // assert the statements are closed
+ for (int i = 0; i < numStatements; i++) {
+ assertTrue(statements[i].isClosed());
+ }
+ }
}