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 c584f27c0c..37a3256532 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 @@ public void run() */ 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 @@ public void start() 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 @@ public void run() synchronized (waitListen) { starting = false; + listenAttempted = true; waitListen.notify(); } } @@ -882,6 +896,7 @@ public void run() // 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 c7a835bf32..9f220fa7e8 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 @@ public static void startServer() throws Exception setupLoggers(); writeBuildInfoFile(); server.start(); + waitForLdapListener(); assertTrue(InvocationCounterPlugin.startupCalled()); // Save config.ldif for when we restart the server backupServerConfigLdif(); @@ -607,6 +610,7 @@ public static synchronized void restartServer() restoreServerConfigLdif(); server.start(); + waitForLdapListener(); clearJEBackends(); initializeTestBackend(true); @@ -625,6 +629,38 @@ public static synchronized void restartServer() } } + /** + * 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. *