From f70033e6e7a8456a8a892c4b48663cde7de82712 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 21 Jul 2026 07:25:00 +0000
Subject: [PATCH] [#755] wait for the LDAP listener after in-process server (re)start (#756)

---
 opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java                        |   38 +++++++++++++++++++++++++++++++++++++-
 opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java |   17 ++++++++++++++++-
 2 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java
index c584f27..37a3256 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyrighted 2026 3A Systems, LLC.
  */
 package org.opends.server.protocols.ldap;
 
@@ -183,6 +184,14 @@
    */
   private final Object waitListen = new Object();
 
+  /**
+   * Condition predicate for {@link #waitListen}: set once the selector thread
+   * has attempted to open the listen channels (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;
 
@@ -814,12 +823,16 @@
 
       try
       {
-        waitListen.wait();
+        while (!listenAttempted)
+        {
+          waitListen.wait();
+        }
       }
       catch (InterruptedException e)
       {
         // If something interrupted the start its probably better
         // to return ASAP.
+        Thread.currentThread().interrupt();
       }
     }
   }
@@ -859,6 +872,7 @@
           synchronized (waitListen)
           {
             starting = false;
+            listenAttempted = true;
             waitListen.notify();
           }
         }
@@ -882,6 +896,7 @@
         // should be notified and resume its work in any cases.
         synchronized (waitListen)
         {
+          listenAttempted = true;
           waitListen.notify();
         }
 
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
index c7a835b..9f220fa 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
@@ -14,7 +14,7 @@
  * Copyright 2006-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
  * Portions Copyright 2013 Manuel Gaupp
- * Portions Copyright 2018-2025 3A Systems, LLC
+ * Portions Copyright 2018-2026 3A Systems, LLC
  */
 package org.opends.server;
 
@@ -68,6 +68,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Handler;
 import java.util.logging.LogManager;
@@ -119,6 +120,7 @@
 import org.opends.server.util.BuildVersion;
 import org.opends.server.util.DynamicConstants;
 import org.opends.server.util.LDIFReader;
+import org.opends.server.util.TestTimer;
 
 import com.forgerock.opendj.util.OperatingSystem;
 
@@ -327,6 +329,7 @@
       setupLoggers();
       writeBuildInfoFile();
       server.start();
+      waitForLdapListener();
       assertTrue(InvocationCounterPlugin.startupCalled());
       // Save config.ldif for when we restart the server
       backupServerConfigLdif();
@@ -607,6 +610,7 @@
       restoreServerConfigLdif();
 
       server.start();
+      waitForLdapListener();
 
       clearJEBackends();
       initializeTestBackend(true);
@@ -626,6 +630,38 @@
   }
 
   /**
+   * Waits until the LDAP connection handler actually accepts TCP connections.
+   * {@code LDAPConnectionHandler.start()} may return before the listen
+   * channel is bound (e.g. after an early wakeup of its wait-for-listen
+   * handshake), so the very first connection made right after a server
+   * (re)start could otherwise be refused.
+   */
+  private static void waitForLdapListener() throws Exception
+  {
+    new TestTimer.Builder()
+        .maxSleep(10, TimeUnit.SECONDS)
+        .sleepTimes(100, TimeUnit.MILLISECONDS)
+        .toTimer()
+        .repeatUntilSuccess(new TestTimer.CallableVoid()
+        {
+          @Override
+          public void call() throws Exception
+          {
+            try (Socket s = new Socket())
+            {
+              s.connect(new InetSocketAddress("127.0.0.1", getServerLdapPort()), 500);
+            }
+            catch (IOException e)
+            {
+              // repeatUntilSuccess() only retries on assertion errors.
+              throw new AssertionError("LDAP port " + getServerLdapPort()
+                  + " is not accepting connections yet", e);
+            }
+          }
+        });
+  }
+
+  /**
    * Returns the embedded server used for tests.
    *
    * @return the embedded server.

--
Gitblit v1.10.0