diff --git a/src/engine/server/login/LoginServer.java b/src/engine/server/login/LoginServer.java index e0ab95ee..4802652d 100644 --- a/src/engine/server/login/LoginServer.java +++ b/src/engine/server/login/LoginServer.java @@ -150,29 +150,42 @@ public class LoginServer { } + /** + * Returns true when something is already listening on the given TCP port. + * + * This used to shell out to `lsof -i tcp:` through /bin/bash and read the + * child's output until EOF. With no timeout and no waitFor() a child that blocks + * is fatal: lsof has been observed stuck in uninterruptible I/O (D state) while + * scanning the busy world server inside the container, which left the login + * server permanently inside its startup path. LoginServer.exec() never returned + * from checkServerHealth(), the accept loop never started, and clients could open + * a TCP connection to the port and then wait forever for the 100 byte DH + * handshake - surfacing as "Failed to open a server connection" after the + * client's own timeout. + * + * A bind probe is equivalent for this purpose, needs no external tool and cannot + * hang. SO_REUSEADDR does not allow stealing a port that is actively listening, + * so a successful bind still means the port is free. + */ public static boolean isPortInUse(int port) { - ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "lsof -i tcp:" + port + " | tail -n +2 | awk '{print $2}'"); - builder.redirectErrorStream(true); - Process process = null; - String line = null; - boolean portInUse = false; + java.net.ServerSocket probe = null; try { - process = builder.start(); - - InputStream is = process.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - - while ((line = reader.readLine()) != null) { - portInUse = true; - } - + probe = new java.net.ServerSocket(); + probe.setReuseAddress(true); + probe.bind(new java.net.InetSocketAddress(port)); + return false; } catch (IOException e) { - e.printStackTrace(); + return true; + } finally { + if (probe != null) + try { + probe.close(); + } catch (IOException e) { + // the bind result is what matters; closing cannot change it + } } - - return portInUse; } private void exec() {