简体   繁体   中英

Is MariaDB JDBC driver ignoring connection timeout?

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:

  • have a SQL Server instance running on localhost:1433 (content does not matter)
  • have the MariaDB JDBC driver version 2.2.5. (latest) on your classpath.

Questions:

  • 1) Could this be a bug in the MariaDB JDBC driver? A google search revealed nothing in this regard.
  • 2) How should I work around this issue? I don't want my server to freeze when the user accidentally inserts an invalid JDBC URL.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM