In the application we are working on, the user can connect to external RDBMSs by entering an arbitrary JDBC connection URL into a text field. One of our customers reported that our application server freezes (indefinitly) at 0% CPU when he accidentally tried to connect to a Microsoft SQL Server with a MySQL JDBC URL.
The following Java snippet illustrates the situation:
public static void main(String[] args){
// note: the application running on localhost:1433 is ACTUALLY
// an MS SQL Server instance!
String jdbcUrl = "jdbc:mysql://localhost:1433/my-db";
// enable JDBC Driver Manager logging
DriverManager.setLogWriter(new PrintWriter(System.err));
// set a timeout of 5 seconds for connecting (which is blissfully ignored!)
DriverManager.setLoginTimeout(5);
// open the connection (which should fail, but freezes instead)
try (Connection c = DriverManager.getConnection(jdbcUrl)){
System.out.println("This should never be reached due to wrong JDBC URL.");
}catch(Exception e){
System.out.println("This is expected (but never printed).");
}
System.out.println("This is never printed either.");
}
To run the snippet:
Questions:
I tried several other JDBC drivers (MySQL, DB2, Oracle...) and they all handle this issue gracefully, only the MariaDB JDBC driver freezes the JVM.
Here's what I did to resolve the issue. The trick is to add a socketTimeout
to the connection. To fix the program in the question, it is enough to modify the JDBC URL to be:
jdbc:mysql://localhost:1433/my-db?socketTimeout=2000
This answer to a related question was the hint I needed.
Answer 1: Yes, it is a bug. They missed to use the login timeout in the implementation of mariadb jdbc driver.
Answer 2: I worked around by using a task that that wraps the getConnection method. This task is stopped after a defined login time if it hasn't finished. Here is my implementation.
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import org.mariadb.jdbc.util.DefaultOptions;
public class ConnectionTest {
private static final String CONNECTION_STRING = "jdbc:mariadb://localhost:3306/test";
private static final String USER = "root";
private static final String PW = "";
private static final int LOGIN_TIMEOUT_SEC = 2;
public static void main(String[] args) throws Exception {
var test = new ConnectionTest();
Connection connection = test.getConnection();
if(connection != null && connection.isValid(LOGIN_TIMEOUT_SEC)) {
System.out.println("Connected!");
}
}
private Connection getConnection() throws Exception {
ConnEstablishSync sync = new ConnEstablishSync();
Properties conProps = new Properties();
conProps.setProperty(DefaultOptions.USER.getOptionName(), USER);
conProps.setProperty(DefaultOptions.PASSWORD.getOptionName(), PW);
FutureTask<Connection> task = new FutureTask<>(() -> {
Connection c = DriverManager.getConnection(CONNECTION_STRING, conProps);
if(sync.canceled && c != null) {
c.close();
c = null;
}
return c;
});
Connection connection = null;
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.submit(task);
connection = task.get(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);
} finally {
sync.canceled = true;
task.cancel(true);
executor.shutdown();
}
return connection;
}
private static class ConnEstablishSync {
private volatile boolean canceled = false;
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.