forked from MagicBane/Server
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:
@@ -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) {
|
||||
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user