Details
- 
    Bug 
- 
    Status: Closed (View Workflow)
- 
    Major 
- 
    Resolution: Fixed
- 
    2.4.0
- 
    None
Description
Happens when running in sequential HA mode with Client Prepared Statement in a transaction.
Create prepared statement and bind parameter to non-serializable object:
| try(PreparedStatement statement = connection.prepareStatement("SELECT * FROM test WHERE column = ?")) | 
| { | 
| statement.setObject(1, new Object()); | 
|     statement.execute(); | 
| }
 | 
Executing such query and writing parameters into the OutputStream (i.e. when calling "ComQuery.sendSubCmd"), results in "java.io.NotSerializableException: java.lang.Object".
Stack trace:
| java.io.NotSerializableException: java.lang.Object | 
| 	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) | 
| 	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) | 
| 	at org.mariadb.jdbc.internal.com.send.parameters.SerializableParameter.writeObjectToBytes(SerializableParameter.java:91) | 
| 	at org.mariadb.jdbc.internal.com.send.parameters.SerializableParameter.writeTo(SerializableParameter.java:80) | 
| 	at org.mariadb.jdbc.internal.com.send.ComQuery.sendSubCmd(ComQuery.java:85) | 
| 	at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.executeQuery(AbstractQueryProtocol.java:285) | 
| 	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) | 
| 	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) | 
| 	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) | 
| 	at java.lang.reflect.Method.invoke(Method.java:498) | 
| 	at org.mariadb.jdbc.internal.failover.AbstractMastersListener.invoke(AbstractMastersListener.java:398) | 
| 	at org.mariadb.jdbc.internal.failover.FailoverProxy.executeInvocation(FailoverProxy.java:278) | 
| 	at org.mariadb.jdbc.internal.failover.FailoverProxy.invoke(FailoverProxy.java:270) | 
| 	at com.sun.proxy.$Proxy8.executeQuery(Unknown Source) | 
| 	at org.mariadb.jdbc.ClientSidePreparedStatement.executeInternal(ClientSidePreparedStatement.java:221) | 
| 	at org.mariadb.jdbc.ClientSidePreparedStatement.execute(ClientSidePreparedStatement.java:157) | 
| 	at org.mariadb.jdbc.ClientSidePreparedStatement.executeUpdate(ClientSidePreparedStatement.java:192)
 | 
What is happening:
- "AbstractQueryProtocol.handleIoException" method will mark the protocol as not connected, via: "connected = false;"
- FailoverProxy.executeInvocation catches the exception and enters the block:
if (hasToHandleFailover(queryException)) { return handleFailOver(queryException, method, args, protocol); }
- MastersFailoverLustener.primaryFail will reconnect with a new protocol, but because the protocol returns false on "currentProtocol.isConnected()", existing protocol is not closed.
@Override public HandleErrorResult primaryFail(Method method, Object[] args, boolean killCmd) { boolean alreadyClosed = !currentProtocol.isConnected(); boolean inTransaction = currentProtocol != null && currentProtocol.inTransaction(); if (currentProtocol.isConnected()) { currentProtocol.close();}try { reconnectFailedConnection(new SearchFilter(true, false)); 
- During reconnection in "reconnectFailedConnection" method, "MastersFailoverListener.foundActiveMaster" method is called, which override current protocol's reference, without closing existing one, as "currentProtocol.isClosed()" returns true
if (currentProtocol != null && !currentProtocol.isClosed()) { currentProtocol.close();}currentProtocol = protocol;
Results in hanging open connection to database every time you run into above scenario. Calling "close" on the Connection, does not close the old protocol.
I would expect that "AbstractQueryProtocol.handleIoException" method to not assume that IOExceptions is due to connection being broken and actually close the protocol and not just flag it as "closed".