From a2c7a1aafbd30c89e1c857ec8574080dcd83aa52 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Fri, 31 Jul 2026 17:01:00 +0000
Subject: [PATCH] [#794] Wait for the listen port to be open before returning from connection handler start (#796)

---
 opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java |   53 +++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 47 insertions(+), 6 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
index 27581c4..482b318 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
@@ -190,6 +190,13 @@
      */
     private final Object waitListen = new Object();
 
+    /**
+     * Condition predicate for {@link #waitListen}: set once the handler thread has attempted to open the listen socket
+     * (successfully or not). Guarded by the {@link #waitListen} monitor; protects the start method against spurious
+     * wakeups.
+     */
+    private boolean listenAttempted;
+
     /** The friendly name of this connection handler. */
     private String friendlyName;
 
@@ -680,6 +687,38 @@
     }
 
     /**
+     * {@inheritDoc}
+     * <p>
+     * Deliberately left unsynchronized, unlike the overridden {@link Thread#start()}: the state this method touches is
+     * guarded by the {@link #waitListen} monitor, and holding the thread's own monitor across {@code waitListen.wait()}
+     * would block {@link Thread#join()}.
+     */
+    @Override
+    public void start() {
+        // The Directory Server start process should only return when the connection handler port is fully opened
+        // and working. The start method therefore needs to wait for the created thread.
+        synchronized (waitListen) {
+            super.start();
+
+            final long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(LISTEN_TIMEOUT_MS);
+            try {
+                while (!listenAttempted) {
+                    final long remainingMs = TimeUnit.NANOSECONDS.toMillis(deadlineNanos - System.nanoTime());
+                    if (remainingMs <= 0) {
+                        logger.error(ERR_CONNHANDLER_TIMEOUT_WAITING_FOR_LISTENER, handlerName,
+                                TimeUnit.MILLISECONDS.toSeconds(LISTEN_TIMEOUT_MS));
+                        break;
+                    }
+                    waitListen.wait(remainingMs);
+                }
+            } catch (InterruptedException e) {
+                // If something interrupted the start its probably better to return ASAP.
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
      * Operates in a loop, accepting new connections and ensuring that requests on those connections are handled
      * properly.
      */
@@ -702,6 +741,7 @@
                     // so notify here to allow the server startup to complete.
                     synchronized (waitListen) {
                         starting = false;
+                        listenAttempted = true;
                         waitListen.notifyAll();
                     }
                 }
@@ -717,12 +757,6 @@
             }
 
             try {
-                // At this point, the connection Handler either started correctly or failed
-                // to start but the start process should be notified and resume its work in any cases.
-                synchronized (waitListen) {
-                    waitListen.notifyAll();
-                }
-
                 // If we have gotten here, then we are about to start listening
                 // for the first time since startup or since we were previously disabled.
                 startListener();
@@ -750,6 +784,13 @@
                 } else {
                     lastIterationFailed = true;
                 }
+            } finally {
+                // At this point, the connection handler either started listening or failed to do so,
+                // but the start process should be notified and resume its work in any cases.
+                synchronized (waitListen) {
+                    listenAttempted = true;
+                    waitListen.notifyAll();
+                }
             }
         }
 

--
Gitblit v1.10.0