Bug #97824 NullPointerException in NativeProtocol
Submitted: 28 Nov 2019 7:42 Modified: 31 Aug 2020 22:29
Reporter: Roman Leventov Email Updates:
Status: No Feedback Impact on me:
None 
Category:Connector / J Severity:S3 (Non-critical)
Version:8.0.18 OS:MacOS
Assigned to: CPU Architecture:Any

[28 Nov 2019 7:42] Roman Leventov
Description:
There may be a NullPointerException in NativeProtocol.java in line 661:

        } finally {
            if (timeoutMillis != 0) {
                try {
                    this.socketConnection.getMysqlSocket().setSoTimeout(oldTimeout); // <- this line
                } catch (IOException e) {
                    throw ExceptionFactory.createCommunicationsException(this.propertySet, this.serverSession, this.getPacketSentTimeHolder(),
                            this.getPacketReceivedTimeHolder(), e, getExceptionInterceptor());
                }
            }
        }

this.socketConnection.getMysqlSocket() may return null.

How to repeat:
    private static Network network;
    private static MySQLContainer mysql;
    private static int mysqlPort;
    private static ToxiproxyContainer toxiproxy;
    private static ToxiproxyContainer.ContainerProxy proxy;
    private static String jdbcUrl;

    @BeforeClass
    public static void setupContainers() {
        network = Network.newNetwork();
        mysql = (MySQLContainer) new MySQLContainer("mysql:5.7").withNetwork(network);
        mysql.start();
        mysqlPort = mysql.getMappedPort(MySQLContainer.MYSQL_PORT);
        toxiproxy = new ToxiproxyContainer().withNetwork(network);
        toxiproxy.start();
        proxy = toxiproxy.getProxy(mysql, MySQLContainer.MYSQL_PORT);
        jdbcUrl = "jdbc:mysql://" + proxy.getContainerIpAddress() + ":" + proxy.getProxyPort() +
                "/" + mysql.getDatabaseName();
    }

    @AfterClass
    public static void shutdownContainers() {
        //noinspection EmptyTryBlock: let try-with-resources do the work
        try (final Network ignore1 = network;
             final MySQLContainer ignore2 = mysql;
             final ToxiproxyContainer ignore3 = toxiproxy) {
        }
    }

    @Test
    public void testHikariDbDisconnected() throws SQLException {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(jdbcUrl);
        config.setUsername(mysql.getUsername());
        config.setPassword(mysql.getPassword());
        try (HikariDataSource ds = new HikariDataSource(config)) {

            proxy.setConnectionCut(true); // Emulate db disconnection

            System.out.println(ds.getConnection().prepareCall("SELECT 1;").execute());
        }
    }
[31 Jul 2020 22:29] Filipe Silva
Hi Roman,

Thank you for this bug report and your interest in Connector/J.

I'm sorry for the late reply, but can you provide a full stack trace with the exception, please?

Thanks,
[1 Sep 2020 1:00] Bugs System
No feedback was provided for this bug for over a month, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
[3 Sep 2020 5:37] Marcle leo
Hi Filipe,
I had the same problem recently, and I have a reproducible test case here.

How to repeat:

import java.lang.reflect.Method;
import java.sql.*;

public class Demo {

    public static void main(String[] args) {
        Connection conn = null;
        Statement statement = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            // Set your connection parameters
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useSSL=false" +
                    "&allowPublicKeyRetrieval=true&serverTimezone=UTC","root","12345678");
            // Set the breakpoint here or sleep the thread
            // Next, close the MySQL service and continue.
            Thread.sleep(30 * 1000);
            Method pingInternal = conn.getClass().getMethod("pingInternal", boolean.class, int.class);
            pingInternal.invoke(conn, true, 1000);
        } catch (Exception e) {
            //
            e.printStackTrace();
        } finally {
            try{
                if(statement!=null) statement.close();
            }catch(SQLException se2){
            }
            try{
                if(conn!=null) conn.close();
            }catch(SQLException se){
                se.printStackTrace();
            }
        }
    }

}

Description:

My guess is that when the MySQL service was first shut down, the server-side TCP connection was in the FIN_WAIT_2/FIN_WAIT_2 state and could accept requests but not return data, so the following code is executing normally.
(The following code is taken from NativeProtocol#sendCommand.)

try {
	clearInputStream();
	this.packetSequence = -1;
	send(queryPacket, queryPacket.getPosition());

} catch (CJException ex) {
	// don't wrap CJExceptions
	throw ex;
} catch (Exception ex) {
	throw ExceptionFactory.createCommunicationsException(this.propertySet, this.serverSession, this.getPacketSentTimeHolder(),
			this.getPacketReceivedTimeHolder(), ex, getExceptionInterceptor());
}

But this part of the code has an internal exception and sets the current this.socketConnection.getMysqlSocket() to null.
(The following code is taken from NativeProtocol#sendCommand.)

if (!skipCheck) {
	if ((COMMAND == NativeConstants.COM_STMT_EXECUTE) || (COMMAND == NativeConstants.COM_STMT_RESET)) {
		this.packetReader.resetMessageSequence();
	}

	returnPacket = checkErrorMessage(command);

	if (this.queryInterceptors ! = null) {
		returnPacket = (NativePacketPayload) invokeQueryInterceptorsPost(queryPacket, returnPacket, false);
	}
}
			
When executing to the finally statement block, when satisfying timeoutMillis ! = 0 condition, the following code throws the NPE.
(The following code is taken from NativeProtocol#sendCommand.)

this.socketConnection.getMysqlSocket().setSoTimeout(oldTimeout);