From 47769981c6fb596f892f0b2ab64c24c8e22b38e9 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 23 Sep 2026 07:38:47 +0000
Subject: [PATCH] [#915] Bound the wait of a write transaction for a row lock another session holds (#1010)

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java |   30 +++++++++++++++++++++++++++++-
 1 files changed, 29 insertions(+), 1 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java
index 516960e..d629d0f 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java
@@ -1325,6 +1325,14 @@
     private volatile boolean poolable;
 
     /**
+     * The session value the row lock bound of this backend displaces on this connection, read once and
+     * kept for its life: see {@link #rowLockBoundDisplaces()}. Volatile for the reason
+     * {@link #poolable} is - it is written by one borrower and read by the next, which is another
+     * thread, and the value is a plain field of an object the pool hands between them.
+     */
+    private volatile Long rowLockBoundDisplaces;
+
+    /**
      * When this connection last answered the database, as a {@link System#nanoTime()} reading:
      * established - the login and the two round trips that set it up have just answered - or
      * validated. It is never stamped on the way back into the pool, although that is where a
@@ -1369,7 +1377,7 @@
     /**
      * Keeps this connection out of the pool: it serves the borrower holding it and is closed rather
      * than pooled when that borrow ends. For a borrower that left something of its own on the session
-     * and could not take it off again - {@code JDBCStorage.restoreDdlLockBound()} is the one that
+     * and could not take it off again - {@code JDBCStorage.restoreLockBound()} is the one that
      * does (#885) - where the blast radius is then this one connection instead of every borrow it
      * would have served after this one.
      */
@@ -1377,6 +1385,26 @@
         poolable = false;
     }
 
+    /**
+     * What this session carried before the row lock bound of {@code JDBCStorage.write()} first
+     * displaced it, or null while that has not been read yet (#915).
+     * <p>
+     * Remembered for the life of the connection because that bound is on the hot path - one write of
+     * the server, one arming - while the readback it saves is a round trip. Only this backend writes
+     * that setting on a connection of this pool, every write puts the value back before the
+     * connection is released, and a connection whose restore failed is kept out of the pool by
+     * {@link #keepOutOfThePool} rather than handed on: so a value remembered here cannot outlive the
+     * session that answered it. Not reset on borrow for the same reason - it describes the session,
+     * which outlives every borrow of it.
+     */
+    Long rowLockBoundDisplaces() {
+        return rowLockBoundDisplaces;
+    }
+
+    void rowLockBoundDisplaces(Long previous) {
+        rowLockBoundDisplaces = previous;
+    }
+
     /** Gives back the right to hold this connection, once and only if it was taken. */
     void releasePermit() {
         if (metered && permitReleased.compareAndSet(false, true)) {

--
Gitblit v1.10.0