Description:
The DriverManager.setLoginTimeout method is not honored, making it very difficult to handle situations where the MySQL Server gets in a bad state and accepts new connections but never process the handshake. I experienced this just recently, and the end result was that all my Connection threads became hung trying to connect.
I tried using DriverManager.setLoginTimeout, but after looking through the code, I can its not used.
I then tried using connectTimeout, but that only works if the socket creation takes longer than the timeout, its not used during the handshake.
Finally, I tried socketTimeout, which worked, but the problem with this solution is that its set for the lifetime of the socket. Any further commands, such as long running queries will also results in timeouts occurring, unfortunately negating this solution.
How to repeat:
Point the MySQL JDBC client to a process that just accepts sockets, but does nothing with the socket, such as this:
public class DummyServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(4444);
Socket socket;
while ((socket = serverSocket.accept()) != null)
System.out.println("Accepted socket: " + socket.toString());
}
catch (IOException e) {
System.exit(-1);
}
}
}
And you will see that the JDBC client will not timeout, except when using the socketTimeout .
Suggested fix:
Ideally the connectTimeout should also be used for any socket operations during the handshake. I made a SocketFactory that does exactly this here, but it would be ideal if this solution was part of the driver itself (and not requiring a special socket factory):
/**
* This class will temporarily set the socketTimeout to the connectTimeout just for the handshake. Post handshake, the
* socketTimeout will be reset to the configured value. A potential JDBC URL will look like this:
* <p/>
* jdbc:mysql://localhost:5002?connectTimeout=2000&socketFactory=MySQLConnectionTimeoutSocketFactory
* <p/>
* User: ckingsbu
* Date: 6/26/13
* Time: 12:41 PM
*/
public class MySQLConnectionTimeoutSocketFactory extends StandardSocketFactory {
private Properties props;
private int originalSocketTimeout;
public Socket connect(String hostname, int portNumber, Properties props)
throws SocketException, IOException {
this.props = props;
return super.connect(hostname, portNumber, props);
}
private int getConnectTimeout() {
String connectTimeoutStr = props.getProperty("connectTimeout");
boolean wantsTimeout = (connectTimeoutStr != null
&& connectTimeoutStr.length() > 0 && !connectTimeoutStr
.equals("0"));
return wantsTimeout ? Integer.parseInt(connectTimeoutStr) : 0;
}
public Socket beforeHandshake() throws SocketException, IOException {
originalSocketTimeout = rawSocket.getSoTimeout();
rawSocket.setSoTimeout(getConnectTimeout());
return rawSocket;
}
public Socket afterHandshake() throws SocketException, IOException {
rawSocket.setSoTimeout(originalSocketTimeout);
return this.rawSocket;
}
}