From b960e40edc450c3fca4070669d44540e4334e73b Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 22 Sep 2026 15:15:01 +0000
Subject: [PATCH] [#929] Establish a catalog connection the way the pool establishes its own (#1009)
---
opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java | 92 ++++++++++++++++++++++------------------------
1 files changed, 44 insertions(+), 48 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
index 02a41a8..70e45f8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
@@ -1629,9 +1629,10 @@
// transaction.
con.commit();
}catch (SQLException e) { // nothing else holds this connection yet: it would leak
- try {
- con.close();
- }catch (SQLException e2) {}
+ // and a driver that will not close is said so on the failure being unwound, as it is on
+ // the two roads CachedConnection.establish() holds (#929): this connection is gone either
+ // way, and the failure of the set-up is the one report this attempt makes
+ CachedConnection.closeQuietly(con, e);
throw e;
}
return con;
@@ -1678,8 +1679,9 @@
* thread that is waiting. It is the pool's retry that is wanted here and not its queue, which is
* why the loop below is its own rather than a borrow of {@link CachedConnection#getConnection}.
* What one attempt is, is the login and the set-up behind it, exactly as an attempt of a borrow is
- * ({@code CachedConnection.connect}): a session the server takes and then kills off answers the
- * first statement of the set-up rather than the login, and it is the same refusal either way.
+ * - the same code, {@link CachedConnection#establish} (#929): a session the server takes and then
+ * kills off answers the first statement of the set-up rather than the login, and it is the same
+ * refusal either way.
* <p>
* That is also what this wait is weaker than a borrow at, and it is worth writing down rather than
* leaving to be discovered: a borrow can be answered by a peer handing a connection back, while
@@ -1812,7 +1814,11 @@
deadline==budgetDeadline, now-startedAt, attempts, e);
}
CachedConnection.warnStallOutsidePool(connectionString, "tree catalog", attempts, startedAt, e);
- backoffMs=Math.min(backoffMs==0 ? 1 : backoffMs*2, CachedConnection.MAX_BACKOFF_MS);
+ // the schedule of the pool, from the one place that holds it (#929). The wait is slept
+ // out rather than handed to the deque of the pool, which is the difference this loop
+ // exists for: nothing here can be answered by a peer returning a connection, so there
+ // is no queue to wait on - see the head of this method
+ backoffMs=CachedConnection.nextBackoffMs(backoffMs);
try {
Thread.sleep(Math.min(backoffMs, remaining));
}catch (InterruptedException interrupted) {
@@ -1881,57 +1887,47 @@
return timeout;
}
+ /** How this connection is named where the read bound of its login would not come off. */
+ static String catalogConnectionNamed(String backendId) {
+ return "the catalog connection of backend "+backendId;
+ }
+
+ /**
+ * And what becomes of it, which is the whole of what this road does not share with the pool's:
+ * the pool closes such a connection rather than pooling it, having a borrower to hand another
+ * to, while this backend has one catalog connection and nothing behind it.
+ */
+ static final String CATALOG_CONNECTION_FATE=
+ "it is kept as it is: this backend has one catalog connection and no second to fall back to";
+
/**
* One attempt of {@link #newCatalogConnection}, established and set up or left holding nothing.
* Failures leave here as the driver reported them, checked and unchecked alike: what a retry is
* decided on is the chain of the original, and the redaction is the caller's - a redacted copy is
* rebuilt link by link, so redacting an attempt that is about to be retried would pay for a
* failure nobody ever sees.
+ * <p>
+ * The login, the transaction it is set up for and the read bound it carries afterwards are the
+ * pool's own, from the one place that holds them ({@link CachedConnection#establish}, #929):
+ * written out here as well they drifted inside a single round, the standing read bound of #885
+ * reaching the pooled half alone and leaving this connection with nothing bounding the reads
+ * between its statements - its {@code commit()}, the {@code rollback()} of a session given up and
+ * the {@code close()} of one that lost the race to another thread.
+ * <p>
+ * What is this connection's own is what becomes of it where the read bound of the login will not
+ * come off. A driver that refuses to take it back leaves it in force for the life of the
+ * connection, and that bound is the one this attempt was given - near the end of the deadline of
+ * the retry, a second. The connection is kept all the same, where the pool closes such a
+ * connection rather than pooling it: this backend has one catalog connection and no borrower
+ * behind it to hand another to, and failing here instead would stop the backend opening on a
+ * driver whose setNetworkTimeout is not implemented at all, where the pooled connection beside it
+ * works. There is no state to fail with that {@code write()} does not read as a connection the
+ * database dropped, either. So it is reported and the connection is used, at the bound in force.
*/
private Connection connectCatalog(String connectionString, CachedConnection.ConnectDialect dialect,
long timeoutSeconds) throws SQLException {
- // A driver is free to write into the map it is handed, so every attempt gets one of its own.
- final Properties properties=new Properties();
- final boolean readBoundSet=dialect!=null && timeoutSeconds>0
- && dialect.bound(connectionString, properties, timeoutSeconds);
- final Connection con=DriverManager.getConnection(connectionString, properties);
- try {
- con.setAutoCommit(false);
- con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
- }catch (SQLException | RuntimeException e) { // nothing else holds this connection yet: it would leak
- closeQuietly(con, e);
- throw e;
- }
- if (readBoundSet) {
- try {
- // only where this code set one: a read bound of the connection string is the
- // administrator's and is not lifted along with it, exactly as the pool leaves it
- con.setNetworkTimeout(Runnable::run, 0);
- }catch (SQLException | RuntimeException e) {
- // A driver that will not take the bound back leaves it in force for the life of the
- // connection, and that bound is the one this attempt was given - near the end of the
- // deadline of the retry, a second. The connection is kept all the same, which is the
- // pool's own answer to this failure: it stops pooling such a connection and still hands
- // it to the borrower that is waiting. Failing here instead would stop the backend opening
- // on a driver whose setNetworkTimeout is not implemented at all, where the pooled
- // connection beside it works - and there is no state to fail with that write() does not
- // read as a connection the database dropped. So it is reported, at the bound in force.
- logger.warn(LocalizableMessage.raw("jdbc: the catalog connection of backend %s keeps the %ds read bound its login was given, so a statement of the catalog slower than that fails on it: %s",
- config.getBackendId(), timeoutSeconds, stackTraceToSingleLineString(e)));
- }
- }
- return con;
- }
-
- /** Closes a connection nothing holds yet, reporting the failure of the close on the one being unwound. */
- private static void closeQuietly(Connection con, Throwable unwinding) {
- try {
- con.close();
- }catch (SQLException | RuntimeException e) {
- // the unchecked one as well: this runs from the catch of a failure it must not replace
- // (JLS 14.20.2), which is the rule every close of this class keeps
- unwinding.addSuppressed(e);
- }
+ return CachedConnection.establish(connectionString, dialect, timeoutSeconds,
+ catalogConnectionNamed(config.getBackendId()), CATALOG_CONNECTION_FATE).con;
}
/**
--
Gitblit v1.10.0