From 55f6ed0e1538f9cbb4466d51d1813072e964d518 Mon Sep 17 00:00:00 2001 From: tledkov Date: Mon, 6 May 2019 17:45:44 +0300 Subject: [PATCH] GG-17339 Backport [IGNITE-11524] Memory leak caused by executing a jdbc prepared statement --- .../jdbc/suite/IgniteJdbcDriverTestSuite.java | 9 ++- .../JdbcThinPreparedStatementLeakTest.java | 76 +++++++++++++++++++ .../jdbc/thin/JdbcThinConnection.java | 26 ++++++- .../internal/jdbc/thin/JdbcThinStatement.java | 4 +- 4 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinPreparedStatementLeakTest.java diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java index 4e9a83e132353..2d45c5d8ae054 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java @@ -1,12 +1,12 @@ /* * Copyright 2019 GridGain Systems, Inc. and Contributors. - * + * * Licensed under the GridGain Community Edition License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -70,6 +70,7 @@ import org.apache.ignite.jdbc.thin.JdbcThinMetadataSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinMissingLongArrayResultsTest; import org.apache.ignite.jdbc.thin.JdbcThinNoDefaultSchemaTest; +import org.apache.ignite.jdbc.thin.JdbcThinPreparedStatementLeakTest; import org.apache.ignite.jdbc.thin.JdbcThinPreparedStatementSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinResultSetSelfTest; import org.apache.ignite.jdbc.thin.JdbcThinSchemaCaseTest; @@ -223,6 +224,8 @@ public static TestSuite suite() { suite.addTest(new JUnit4TestAdapter(JdbcThinAuthenticateConnectionSelfTest.class)); suite.addTest(new JUnit4TestAdapter(JdbcThinTransactionsLeaksMvccTest.class)); + suite.addTest(new JUnit4TestAdapter(JdbcThinPreparedStatementLeakTest.class)); + return suite; } } diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinPreparedStatementLeakTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinPreparedStatementLeakTest.java new file mode 100644 index 0000000000000..9aa3144cc38c2 --- /dev/null +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinPreparedStatementLeakTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019 GridGain Systems, Inc. and Contributors. + * + * Licensed under the GridGain Community Edition License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.jdbc.thin; + +import org.apache.ignite.IgniteJdbcThinDriver; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Properties; +import java.util.Set; + +/** + * Prepared statement leaks test. + */ +@SuppressWarnings("ThrowableNotThrown") +public class JdbcThinPreparedStatementLeakTest extends JdbcThinAbstractSelfTest { + /** URL. */ + private static final String URL = "jdbc:ignite:thin://127.0.0.1/"; + + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + startGrid(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + stopAllGrids(); + + super.afterTest(); + } + + /** + * @throws Exception If failed. + */ + @SuppressWarnings("StatementWithEmptyBody") + @Test + public void test() throws Exception { + try (Connection conn = new IgniteJdbcThinDriver().connect(URL, new Properties())) { + for (int i = 0; i < 50000; ++i) { + try (PreparedStatement st = conn.prepareStatement("select 1")) { + ResultSet rs = st.executeQuery(); + + while (rs.next()) { + // No-op. + } + + rs.close(); + } + } + + Set stmts = U.field(conn, "stmts"); + + assertEquals(0, stmts.size()); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java index 04346dcf595d2..17e413d090ab3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java @@ -1,12 +1,12 @@ /* * Copyright 2019 GridGain Systems, Inc. and Contributors. - * + * * Licensed under the GridGain Community Edition License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -34,9 +34,12 @@ import java.sql.Statement; import java.sql.Struct; import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Semaphore; import java.util.logging.Level; @@ -114,7 +117,7 @@ public class JdbcThinConnection implements Connection { private boolean connected; /** Tracked statements to close on disconnect. */ - private final ArrayList stmts = new ArrayList<>(); + private final Set stmts = Collections.newSetFromMap(new IdentityHashMap<>()); /** * Creates new connection. @@ -380,6 +383,12 @@ private void doCommit() throws SQLException { streamState = null; } + synchronized (stmtsMux) { + stmts.clear(); + } + + SQLException err = null; + closed = true; cliIo.close(); @@ -834,6 +843,15 @@ private static String normalizeSchema(String schemaName) { return res; } + /** + * @param stmt Statement to close. + */ + void closeStatement(JdbcThinStatement stmt) { + synchronized (stmtsMux) { + stmts.remove(stmt); + } + } + /** * Streamer state and */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java index 388bc211c1d8b..3e16ab896c453 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java @@ -45,8 +45,6 @@ import org.apache.ignite.internal.processors.odbc.jdbc.JdbcResult; import org.apache.ignite.internal.processors.odbc.jdbc.JdbcResultInfo; import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType; -import org.apache.ignite.internal.processors.odbc.jdbc.JdbcBulkLoadBatchRequest; -import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType; import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.sql.SqlKeyword; import org.apache.ignite.internal.sql.SqlParseException; @@ -353,6 +351,8 @@ private JdbcResult sendFile(JdbcBulkLoadAckResult cmdRes) throws SQLException { try { closeResults(); + + conn.closeStatement(this); } finally { closed = true;