From 8ad97160e90f47e5fffe0b8a8c65ee421fa0903c Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 22 Sep 2026 09:08:37 +0000
Subject: [PATCH] [#933] Read a catalog table another session created while this one was creating it (#1005)

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java |   35 +++++++++++++++++++++++++++--------
 1 files changed, 27 insertions(+), 8 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 b323570..55cb13c 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
@@ -4724,8 +4724,9 @@
 		 * <p>
 		 * Serialized on the storage, so that two transactions opening trees at the same time cannot both
 		 * find the table absent and both go on to create it. It serializes this storage and nothing
-		 * else, which is why the create tolerates a table that turned up while it was being made: an
-		 * offline tool beside a running server is a pair no lock of one process can order. The stamp -
+		 * else, which is why the create tolerates a table that turned up while it was being made - an
+		 * offline tool beside a running server is a pair no lock of one process can order - and why what
+		 * it tolerated is then read like any other catalog that was already there. The stamp -
 		 * the one thing under it that is nobody's dependency - is issued outside it.
 		 * <p>
 		 * Two things are kept out of the lock because they are the slow ones. The flag is read before
@@ -4760,11 +4761,24 @@
 				if (!lostTheRace) {
 					if (isExistsTable(catalog)) {
 						readEnrolledTrees(catalog);
-					} else {
-						createCatalogTable(catalog);
-						// nothing to read from a table that has just been created, and nothing this open
-						// enrols may be skipped as already recorded
+					} else if (!createCatalogTable(catalog)) {
+						// the table was there after all: another session created it while this one was
+						// creating it, and a table this session did not create is a catalog with rows in it
+						// like any other - the branch above is what those rows are for. The flag below is
+						// raised over what this storage knows the catalog records, and raised over none of
+						// it, every tree of this open is enrolled again against a catalog already naming
+						// them: one upsert and one commit each, and the same for every later write of this
+						// open that names a tree (#933). It reads on the session the failed create reset,
+						// which is a connection rolled back or, where even that failed, one given up for
+						// the read to establish again: either is a session a select may be asked of. That
+						// re-establish is the one connect of this class made under the lock: it needs a
+						// create cut by a dead catalog connection, a table another session made meanwhile,
+						// and a database refusing logins, and it is bounded by the deadline of a borrow
+						// like the connect made outside it
+						readEnrolledTrees(catalog);
 					}
+					// and where the create really did create it, there is nothing to read: a table just
+					// made holds no row, and nothing this open enrols may be skipped as already recorded
 					catalogTableOpened=true;
 				}
 			}
@@ -4856,8 +4870,12 @@
 		 * line. What is left is the privilege the account is missing. An engine this backend does not
 		 * know has no number a lock could be told by ({@code isLockTimeout()} answers false on a null
 		 * dialect), and gets the privilege line there as it did.
+		 *
+		 * @return whether this session created the table. {@code false} says another session created it
+		 *         while this one was creating it, which is a table full of rows this storage has not
+		 *         read: what {@link #openCatalog} does with that answer is read them (#933).
 		 */
-		void createCatalogTable(TreeName catalog) {
+		boolean createCatalogTable(TreeName catalog) {
 			final String tableName=getTableName(catalog);
 			Dialect dialect=null; // read inside the try, and asked again by the catch, which tells a lock by the engine's own number
 			try {
@@ -4878,6 +4896,7 @@
 					catalogCon.commit();
 					return null;
 				});
+				return true;
 			} catch (SQLException | RuntimeException e) {
 				// the unchecked one as well, for the reason enrolInCatalog() takes it: what the statement
 				// left behind has to be rolled back whatever class the failure arrived in, this connection
@@ -4901,7 +4920,7 @@
 				if (alreadyThere) {
 					logger.debug(LocalizableMessage.raw("jdbc: table %s was created by another session while this one was creating it: %s",
 						tableName, stackTraceToSingleLineString(e)));
-					return;
+					return false; // read by the caller, the rows being another session's and not this one's
 				}
 				// A lock another session holds is not a privilege the account lacks, whichever bound ended
 				// the wait for it: asked of the engine's own number on every road, since gaveUpOnTheLock()

--
Gitblit v1.10.0