From 5f34434fa40844de25664f35a007db3c880b43b5 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 21 Oct 2026 11:30:35 -0400 Subject: [PATCH 1/4] one logger per class --- java-bigquery-jdbc/pom.xml | 7 +- .../jdbc/BigQueryCallableStatement.java | 3 +- .../bigquery/jdbc/BigQueryConnection.java | 3 +- .../jdbc/BigQueryDatabaseMetaData.java | 3 +- .../jdbc/BigQueryPooledConnection.java | 6 +- .../bigquery/jdbc/BigQueryStatement.java | 3 +- .../cloud/bigquery/jdbc/DataSource.java | 3 +- .../jdbc/PooledConnectionListener.java | 3 +- .../jdbc/BigQueryJdbcLoggerLeakTest.java | 99 +++++++++++++++++++ .../bigquery/jdbc/it/ITBigQueryJDBCTest.java | 2 + 10 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java diff --git a/java-bigquery-jdbc/pom.xml b/java-bigquery-jdbc/pom.xml index 2f344bd29c0a..c791277e678f 100644 --- a/java-bigquery-jdbc/pom.xml +++ b/java-bigquery-jdbc/pom.xml @@ -506,7 +506,12 @@ org.apache.maven.plugins maven-surefire-plugin - --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED + + --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED --add-opens=java.logging/java.util.logging=ALL-UNNAMED diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java index e9ad6fa1292d..e1ad8925eb0a 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java @@ -43,7 +43,8 @@ import java.util.Map; class BigQueryCallableStatement extends BigQueryPreparedStatement implements CallableStatement { - private final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + private static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(BigQueryCallableStatement.class.getName()); private boolean lastReadWasNull = false; private Object getParameterAndTrackNull(int parameterIndex) throws SQLException { diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java index 1e1c84c8e88c..6189f7ea75be 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java @@ -92,7 +92,8 @@ */ public class BigQueryConnection extends BigQueryNoOpsConnection { - private final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + private static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(BigQueryConnection.class.getName()); String connectionClassName = this.toString(); private final String connectionId; private static final String DEFAULT_JDBC_TOKEN_VALUE = "Google-BigQuery-JDBC-Driver"; diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java index a24522482d6e..01ca547234ae 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java @@ -91,7 +91,8 @@ * @see BigQueryStatement */ class BigQueryDatabaseMetaData implements DatabaseMetaData { - final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(BigQueryDatabaseMetaData.class.getName()); private static final String DATABASE_PRODUCT_NAME = "Google BigQuery"; private static final String DATABASE_PRODUCT_VERSION = "2.0"; private static final String DRIVER_NAME = "GoogleJDBCDriverForGoogleBigQuery"; diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPooledConnection.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPooledConnection.java index ad62ee76bcdb..6b90421d5d3d 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPooledConnection.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPooledConnection.java @@ -31,7 +31,8 @@ import javax.sql.StatementEventListener; class BigQueryPooledConnection implements PooledConnection { - private final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + private static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(BigQueryPooledConnection.class.getName()); private String id; // Mainly for internal use private Connection bqConnection; @@ -165,7 +166,8 @@ public void removeStatementEventListener(StatementEventListener arg0) { // This class notifies the listeners or calls the listner notification methods // provided by the pooled connection. static class BigQueryPooledConnectionWrapper implements Connection { - private final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + private static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(BigQueryPooledConnectionWrapper.class.getName()); private Connection bqConnectionDelegate; private BigQueryPooledConnection pooledConnection; private boolean closed = false; diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java index af50bf8b8df3..ab2e2186e07f 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java @@ -102,7 +102,8 @@ */ public class BigQueryStatement extends BigQueryNoOpsStatement { - private final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + private static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(BigQueryStatement.class.getName()); public static final int DEFAULT_BUFFER_SIZE = BigQuerySettings.DEFAULT_NUM_BUFFERED_ROWS * 2; private static final String DEFAULT_DATASET_NAME = "_google_jdbc"; private static final String DEFAULT_TABLE_NAME = "temp_table_"; diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java index d0485584e15d..66034dc1a35d 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java @@ -43,7 +43,8 @@ * registered with a naming service based on the Java™ Naming and Directory (JNDI) API. */ public class DataSource implements javax.sql.DataSource { - private final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + private static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(DataSource.class.getName()); private String URL; static final ImmutableSet VALID_JOB_CREATION_MODES = ImmutableSet.of(1, 2); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/PooledConnectionListener.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/PooledConnectionListener.java index 1534a6a0b798..86bc9626a08b 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/PooledConnectionListener.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/PooledConnectionListener.java @@ -23,7 +23,8 @@ import javax.sql.PooledConnection; public class PooledConnectionListener implements ConnectionEventListener { - private final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(this.toString()); + private static final BigQueryJdbcCustomLogger LOG = + new BigQueryJdbcCustomLogger(PooledConnectionListener.class.getName()); private String id; // Mainly for internal use private LinkedBlockingDeque connectionPool; private Long connectionPoolSize = 0L; diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java new file mode 100644 index 000000000000..5353d791e24a --- /dev/null +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 com.google.cloud.bigquery.jdbc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.logging.Logger; +import org.junit.jupiter.api.Test; + +/** + * Regression tests for logger registration leaks. + * + *

Every {@code java.util.logging.Logger} keeps a list of its child loggers so that level changes + * on a parent propagate downwards. {@code Logger.setParent} appends to that list under a single + * JVM-wide lock, and the entry is never removed when the child becomes garbage unless something + * else happens to call {@code LogManager.addLogger}. A JDBC object that creates its own logger + * therefore leaves a permanent entry on the shared driver root logger and makes every later + * creation scan a longer list. These tests assert that constructing driver objects does not grow + * that list. + */ +public class BigQueryJdbcLoggerLeakTest { + + private static final int OBJECT_COUNT = 1_000; + + @Test + public void testCreatingStatementsDoesNotRegisterChildLoggers() throws Exception { + BigQueryConnection connection = mock(BigQueryConnection.class); + doReturn("leak-test-connection").when(connection).getConnectionId(); + doReturn(BigQueryConnection.SessionState.empty()).when(connection).getSessionStateSnapshot(); + + Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger(); + // Warm up so that any one-time, class-level logger is registered before we measure. + new BigQueryStatement(connection); + int childrenBefore = childLoggerCount(rootLogger); + + for (int i = 0; i < OBJECT_COUNT; i++) { + new BigQueryStatement(connection); + } + + assertEquals( + childrenBefore, + childLoggerCount(rootLogger), + "Creating " + + OBJECT_COUNT + + " statements must not register additional child loggers on the driver root logger"); + } + + @Test + public void testCreatingDatabaseMetaDataDoesNotRegisterChildLoggers() + throws ReflectiveOperationException { + BigQueryConnection connection = mock(BigQueryConnection.class); + doReturn("leak-test-connection").when(connection).getConnectionId(); + + Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger(); + new BigQueryDatabaseMetaData(connection); + int childrenBefore = childLoggerCount(rootLogger); + + for (int i = 0; i < OBJECT_COUNT; i++) { + new BigQueryDatabaseMetaData(connection); + } + + assertEquals( + childrenBefore, + childLoggerCount(rootLogger), + "Creating " + + OBJECT_COUNT + + " DatabaseMetaData objects must not register additional child loggers on the driver" + + " root logger"); + } + + /** + * Reads the JDK-internal {@code Logger.kids} list. This field is private to {@code java.logging}, + * so the surefire {@code argLine} opens {@code java.util.logging} to the test classpath. + */ + private static int childLoggerCount(Logger logger) throws ReflectiveOperationException { + Field kids = Logger.class.getDeclaredField("kids"); + kids.setAccessible(true); + List children = (List) kids.get(logger); + return children == null ? 0 : children.size(); + } +} diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java index 366e95b70d46..75dd454d729c 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java @@ -230,6 +230,8 @@ public void testReadAPIPathLarge() throws SQLException { withReadApi.setProperty("HighThroughputActivationRatio", "2"); withReadApi.setProperty("HighThroughputMinTableSize", "1000"); withReadApi.setProperty("MaxResults", "300"); + withReadApi.setProperty("LogLevel", "6"); + withReadApi.setProperty("LogPath", "./logs"); Connection connection = DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG), withReadApi); From b7c9cfa2488be753843ce9b57b5cc51f1a5be5ca Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 21 Oct 2026 11:54:24 -0400 Subject: [PATCH 2/4] remove duplicate setParent --- java-bigquery-jdbc/pom.xml | 7 +------ .../cloud/bigquery/jdbc/BigQueryJdbcCustomLogger.java | 1 - .../cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java | 7 +++++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/java-bigquery-jdbc/pom.xml b/java-bigquery-jdbc/pom.xml index c791277e678f..2f344bd29c0a 100644 --- a/java-bigquery-jdbc/pom.xml +++ b/java-bigquery-jdbc/pom.xml @@ -506,12 +506,7 @@ org.apache.maven.plugins maven-surefire-plugin - - --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED --add-opens=java.logging/java.util.logging=ALL-UNNAMED + --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLogger.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLogger.java index c4bc39f7ee2a..0acea77e5bf6 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLogger.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLogger.java @@ -30,7 +30,6 @@ protected BigQueryJdbcCustomLogger(String name, String resourceBundleName) { public BigQueryJdbcCustomLogger(String name) { this(name, null); - this.setParent(BigQueryJdbcRootLogger.getRootLogger()); } private void logWithCaller(Level level, Supplier msgSupplier) { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java index 38a5df969406..2f567ccfd908 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java @@ -579,4 +579,11 @@ public void testNestedArrayInJsonStructConnectionIdPropagation() throws Exceptio "Log from Nested JSON Array", () -> nestedArray.LOG.fine("Log from Nested JSON Array")); } + + @Test + public void testLoggerIsParentedToDriverRootLogger() { + assertSame( + BigQueryJdbcRootLogger.getRootLogger(), + new BigQueryJdbcCustomLogger("ParentCheck").getParent()); + } } From 702cc5cec5925fc38da964b4ce5c1b2b06ca90d0 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 21 Oct 2026 12:19:13 -0400 Subject: [PATCH 3/4] add tests --- java-bigquery-jdbc/pom.xml | 7 +- .../jdbc/BigQueryJdbcCustomLoggerTest.java | 63 ++++++++++++ .../jdbc/BigQueryJdbcLoggerLeakTest.java | 99 ------------------- 3 files changed, 69 insertions(+), 100 deletions(-) delete mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java diff --git a/java-bigquery-jdbc/pom.xml b/java-bigquery-jdbc/pom.xml index 2f344bd29c0a..844a9876605f 100644 --- a/java-bigquery-jdbc/pom.xml +++ b/java-bigquery-jdbc/pom.xml @@ -506,7 +506,12 @@ org.apache.maven.plugins maven-surefire-plugin - --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED + + --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED --add-opens=java.logging/java.util.logging=ALL-UNNAMED diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java index 2f567ccfd908..ab6fa432b770 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcCustomLoggerTest.java @@ -20,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.FieldList; @@ -37,6 +39,7 @@ import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; +import java.util.logging.Logger; import org.apache.arrow.vector.util.JsonStringArrayList; import org.apache.arrow.vector.util.JsonStringHashMap; import org.junit.jupiter.api.AfterEach; @@ -49,6 +52,8 @@ public class BigQueryJdbcCustomLoggerTest extends BigQueryJdbcLoggingBaseTest { private BigQueryJdbcCustomLogger logger; private TestHandler testHandler; + private static final int OBJECT_COUNT = 1_000; + private static class TestHandler extends Handler { private final List records = new ArrayList<>(); @@ -586,4 +591,62 @@ public void testLoggerIsParentedToDriverRootLogger() { BigQueryJdbcRootLogger.getRootLogger(), new BigQueryJdbcCustomLogger("ParentCheck").getParent()); } + + @Test + public void testCreatingStatementsDoesNotRegisterChildLoggers() throws Exception { + BigQueryConnection connection = mock(BigQueryConnection.class); + doReturn("leak-test-connection").when(connection).getConnectionId(); + doReturn(BigQueryConnection.SessionState.empty()).when(connection).getSessionStateSnapshot(); + + Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger(); + // Warm up so that any one-time, class-level logger is registered before we measure. + new BigQueryStatement(connection); + int childrenBefore = childLoggerCount(rootLogger); + + for (int i = 0; i < OBJECT_COUNT; i++) { + new BigQueryStatement(connection); + } + + assertEquals( + childrenBefore, + childLoggerCount(rootLogger), + "Creating " + + OBJECT_COUNT + + " statements must not register additional child loggers on the driver root logger"); + } + + @Test + public void testCreatingDatabaseMetaDataDoesNotRegisterChildLoggers() + throws ReflectiveOperationException { + BigQueryConnection connection = mock(BigQueryConnection.class); + doReturn("leak-test-connection").when(connection).getConnectionId(); + + Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger(); + new BigQueryDatabaseMetaData(connection); + int childrenBefore = childLoggerCount(rootLogger); + + for (int i = 0; i < OBJECT_COUNT; i++) { + new BigQueryDatabaseMetaData(connection); + } + + assertEquals( + childrenBefore, + childLoggerCount(rootLogger), + "Creating " + + OBJECT_COUNT + + " DatabaseMetaData objects must not register additional child loggers on the driver" + + " root logger"); + } + + /** + * Reads the JDK-internal {@code Logger.kids} list. This field is private to {@code java.logging}, + * so the surefire {@code argLine} opens {@code java.util.logging} to the test classpath. + */ + private static int childLoggerCount(Logger logger) throws ReflectiveOperationException { + // Qualified because this file already imports com.google.cloud.bigquery.Field. + java.lang.reflect.Field kids = Logger.class.getDeclaredField("kids"); + kids.setAccessible(true); + List children = (List) kids.get(logger); + return children == null ? 0 : children.size(); + } } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java deleted file mode 100644 index 5353d791e24a..000000000000 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcLoggerLeakTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2026 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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 com.google.cloud.bigquery.jdbc; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.logging.Logger; -import org.junit.jupiter.api.Test; - -/** - * Regression tests for logger registration leaks. - * - *

Every {@code java.util.logging.Logger} keeps a list of its child loggers so that level changes - * on a parent propagate downwards. {@code Logger.setParent} appends to that list under a single - * JVM-wide lock, and the entry is never removed when the child becomes garbage unless something - * else happens to call {@code LogManager.addLogger}. A JDBC object that creates its own logger - * therefore leaves a permanent entry on the shared driver root logger and makes every later - * creation scan a longer list. These tests assert that constructing driver objects does not grow - * that list. - */ -public class BigQueryJdbcLoggerLeakTest { - - private static final int OBJECT_COUNT = 1_000; - - @Test - public void testCreatingStatementsDoesNotRegisterChildLoggers() throws Exception { - BigQueryConnection connection = mock(BigQueryConnection.class); - doReturn("leak-test-connection").when(connection).getConnectionId(); - doReturn(BigQueryConnection.SessionState.empty()).when(connection).getSessionStateSnapshot(); - - Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger(); - // Warm up so that any one-time, class-level logger is registered before we measure. - new BigQueryStatement(connection); - int childrenBefore = childLoggerCount(rootLogger); - - for (int i = 0; i < OBJECT_COUNT; i++) { - new BigQueryStatement(connection); - } - - assertEquals( - childrenBefore, - childLoggerCount(rootLogger), - "Creating " - + OBJECT_COUNT - + " statements must not register additional child loggers on the driver root logger"); - } - - @Test - public void testCreatingDatabaseMetaDataDoesNotRegisterChildLoggers() - throws ReflectiveOperationException { - BigQueryConnection connection = mock(BigQueryConnection.class); - doReturn("leak-test-connection").when(connection).getConnectionId(); - - Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger(); - new BigQueryDatabaseMetaData(connection); - int childrenBefore = childLoggerCount(rootLogger); - - for (int i = 0; i < OBJECT_COUNT; i++) { - new BigQueryDatabaseMetaData(connection); - } - - assertEquals( - childrenBefore, - childLoggerCount(rootLogger), - "Creating " - + OBJECT_COUNT - + " DatabaseMetaData objects must not register additional child loggers on the driver" - + " root logger"); - } - - /** - * Reads the JDK-internal {@code Logger.kids} list. This field is private to {@code java.logging}, - * so the surefire {@code argLine} opens {@code java.util.logging} to the test classpath. - */ - private static int childLoggerCount(Logger logger) throws ReflectiveOperationException { - Field kids = Logger.class.getDeclaredField("kids"); - kids.setAccessible(true); - List children = (List) kids.get(logger); - return children == null ? 0 : children.size(); - } -} From 73e5d7382cae731933b64ba55be06b7234b27f18 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 21 Oct 2026 12:26:36 -0400 Subject: [PATCH 4/4] cleanup --- .../com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java index 75dd454d729c..366e95b70d46 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java @@ -230,8 +230,6 @@ public void testReadAPIPathLarge() throws SQLException { withReadApi.setProperty("HighThroughputActivationRatio", "2"); withReadApi.setProperty("HighThroughputMinTableSize", "1000"); withReadApi.setProperty("MaxResults", "300"); - withReadApi.setProperty("LogLevel", "6"); - withReadApi.setProperty("LogPath", "./logs"); Connection connection = DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG), withReadApi);