From cf2068420f92f25985c22a6cdb16c17d9ceb1efb Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Sat, 05 Sep 2026 18:16:09 +0000
Subject: [PATCH] [#878] Bound the JDBC connection pool and expire its connections one by one (#884)

---
 opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStatementBoundTestCase.java |   36 ++++++++++++++++++++++++++++++------
 1 files changed, 30 insertions(+), 6 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStatementBoundTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStatementBoundTestCase.java
index 7c0228b..32ad5a5 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStatementBoundTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStatementBoundTestCase.java
@@ -829,8 +829,22 @@
 		when(statement.executeQuery()).thenReturn(mock(ResultSet.class));
 		when(parent.prepareStatement(anyString())).thenReturn(statement);
 		storage.accessMode = AccessMode.READ_WRITE; // an import has the storage open for writing
-		final JDBCStorage.ImporterImpl importer =
-			storage.new ImporterImpl(new CachedConnection("jdbc:mock", parent), true);
+		// Borrowed through the seam rather than handed to the constructor: the importer takes its own
+		// connection now (#878). It is the same physical connection the entry read below runs on,
+		// which is what this test pins - the backstop is keyed on the connection, not on the storage.
+		final JDBCStorage importing = new JDBCStorage(mockCfg(JDBCBackendCfg.class), null) {
+			@Override
+			Connection getConnection(boolean trusted) {
+				return new CachedConnection("jdbc:mock", parent);
+			}
+
+			@Override
+			public StorageStatus getStorageStatus() {
+				return StorageStatus.working(); // open already, so the importer borrows and no more
+			}
+		};
+		importing.accessMode = AccessMode.READ_WRITE;
+		final JDBCStorage.ImporterImpl importer = importing.new ImporterImpl();
 		final TreeName tree = new TreeName("dc=example,dc=com", "id2entry");
 
 		// an entry read of a client arms the backstop on the very connection the import writes to,
@@ -1006,9 +1020,11 @@
 	@Test
 	public void testStartImportGivesTheConnectionBackWhenTheImporterCannotBeBuilt() throws Exception {
 		final Connection con = mock(Connection.class);
+		final AtomicInteger borrows = new AtomicInteger();
 		final JDBCStorage readOnly = new JDBCStorage(mockCfg(JDBCBackendCfg.class), null) {
 			@Override
 			Connection getConnection(boolean trusted) {
+				borrows.incrementAndGet();
 				return con;
 			}
 
@@ -1026,7 +1042,13 @@
 			// the designed path this test is about
 		}
 
-		verify(con).close();
+		// Nothing to give back. With the borrow inside the importer's constructor (#878) the refusal
+		// stands in front of it, so an import of a read-only storage takes no connection at all
+		// rather than taking one and returning it. Pinned as never borrowed rather than dropped: the
+		// leak this covers - a connection out of the pool for good, holding a transaction it had
+		// already begun - is the same one, and never taking it is the state that cannot leak it.
+		assertEquals(borrows.get(), 0);
+		verify(con, never()).close();
 	}
 
 	/**
@@ -1074,9 +1096,11 @@
 			// the build failing after this method opened the storage, which is the path under test
 		}
 
-		assertEquals(opens.get(), 1, "the storage was not opened by startImport(), so nothing was owed back");
-		verify(con).close();
-		assertEquals(closes.get(), 1, "the storage this method opened was left open");
+		assertEquals(opens.get(), 1, "the storage was not opened by the importer, so nothing was owed back");
+		// the connection is not owed back here either: the refusal stands in front of the borrow now
+		// (#878), so what this path has to give back is the storage alone
+		verify(con, never()).close();
+		assertEquals(closes.get(), 1, "the storage the importer opened was left open");
 	}
 
 	/**

--
Gitblit v1.10.0