Bug #69579 DriverManager.setLoginTimeout not honored
Submitted: 26 Jun 2013 6:00 Modified: 25 Nov 2013 23:06
Reporter: Christopher Kingsbury Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:5.1.25 OS:Any
Assigned to: Filipe Silva CPU Architecture:Any
Tags: Connection, timeout

[26 Jun 2013 6:00] Christopher Kingsbury
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;
    }
}
[26 Jun 2013 6:41] Christopher Kingsbury
Patch to allow connectionTime parameter to apply to handshake

Attachment: mysql-jdbc-connection-timeout-patch.patch (application/octet-stream, text), 1.52 KiB.

[26 Jun 2013 9:01] Alexander Soklakov
Hi Christopher,

Thanks for interesting bug report. Verified by code review.
[25 Nov 2013 23:06] Daniel So
Added the following entry to the Connector/J 5.1.28 changelog:

The timeout limit set by DriverManager.setLoginTimeout() was not honored during a handshake attempt. This fix adds the DriverManager.setLoginTimeout() control to the function NonRegisteringDriver.connect(): if the defined timeout is reached, the connection creation is cancelled and an exception is thrown.