login: probe the world port with a bind test instead of lsof
LoginServer.exec() -> checkServerHealth() -> isPortInUse() shelled out to `lsof -i tcp:<port>` through /bin/bash and read the child's stdout until EOF. There is no timeout and no waitFor(), so a child that blocks is fatal: in the container lsof was observed stuck in uninterruptible I/O (D state) while it was scanning the busy world server. The login server then never returned from its startup path, the accept loop never started, and clients could open a TCP connection to 6000 and wait forever for the 100 byte DH handshake, surfacing as "Failed to open a server connection" after the client's own timeout. A container restart was the only way out. Replace the external probe with a bind probe: it cannot hang, needs no external tool, and SO_REUSEADDR still does not allow stealing a port that is actively listening, so a successful bind keeps meaning "free".
This commit is contained in:
1 parent
9f3ec7c553
commit
97eff6517e
1 file changed
+30
-17
@@ -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:<port>` 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) {
|
public static boolean isPortInUse(int port) {
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "lsof -i tcp:" + port + " | tail -n +2 | awk '{print $2}'");
|
java.net.ServerSocket probe = null;
|
||||||
builder.redirectErrorStream(true);
|
|
||||||
Process process = null;
|
|
||||||
String line = null;
|
|
||||||
boolean portInUse = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
process = builder.start();
|
probe = new java.net.ServerSocket();
|
||||||
|
probe.setReuseAddress(true);
|
||||||
InputStream is = process.getInputStream();
|
probe.bind(new java.net.InetSocketAddress(port));
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
return false;
|
||||||
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
portInUse = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
} 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() {
|
private void exec() {
|
||||||
|
|||||||
Reference in new issue
Block a user