Description:
If Connection.changeUser is called after DriverManager.loginTimeout has elapsed since the connection was started, the following error occurs and changeUser cannot be performed.
We want to use one connection with switching users to limit the number of connections.
## Error
Exception in thread "main" com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:175)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64)
at com.mysql.cj.jdbc.ConnectionImpl.changeUser(ConnectionImpl.java:561)
at test.TestMainKt.main(TestMain.kt:16)
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:62)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:150)
at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:166)
at com.mysql.cj.protocol.a.NativeProtocol.afterHandshake(NativeProtocol.java:451)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.proceedHandshakeWithPluggableAuthentication(NativeAuthenticationProvider.java:535)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.changeUser(NativeAuthenticationProvider.java:614)
at com.mysql.cj.protocol.a.NativeProtocol.changeUser(NativeProtocol.java:1385)
at com.mysql.cj.CoreSession.changeUser(CoreSession.java:103)
at com.mysql.cj.jdbc.ConnectionImpl.changeUser(ConnectionImpl.java:544)
... 1 more
Caused by: java.net.SocketException: Connection attempt exceeded defined timeout.
at com.mysql.cj.protocol.StandardSocketFactory.resetLoginTimeCountdown(StandardSocketFactory.java:212)
at com.mysql.cj.protocol.StandardSocketFactory.afterHandshake(StandardSocketFactory.java:197)
at com.mysql.cj.protocol.a.NativeProtocol.afterHandshake(NativeProtocol.java:449)
... 6 more
How to repeat:
1. Set the loginTimeout of DriverManager.
DriverManager.setLoginTimeout(3) // Set to 3 seconds
2. Establish a connection.
val conn = DriverManager.getConnection(jdbcUrl, "test_user", "")
3. Wait until the loginTimeout of DriverManager is reached.
4. Call Connection.changeUser.
val mysqlConn = conn.unwrap(com.mysql.cj.jdbc.JdbcConnection::class.java)
mysqlConn.changeUser("test_user", "")
You can reproduce it with the following code.
---
import java.sql.Connection;
import java.sql.DriverManager;
import java.time.LocalTime;
public class LoginTimeoutIssue {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/test";
DriverManager.setLoginTimeout(3);
LocalTime limit = LocalTime.now().plusSeconds(10);
Connection conn = DriverManager.getConnection(url, "test_user", "");
com.mysql.cj.jdbc.JdbcConnection mysqlConn = conn.unwrap(com.mysql.cj.jdbc.JdbcConnection.class);
while (LocalTime.now().isBefore(limit)) {
mysqlConn.changeUser("test_user", "");
System.out.println("changeUser success");
Thread.sleep(1000);
}
conn.close();
}
}
---
Suggested fix:
MySQL is checking the elapsed time since the initialization of the connection in StandardSocketFactory.afterHandshake.
Although afterHandshake is also executed during the changeUser calling, I believe it is unnecessary at that time.