Description:
Connection.prepareStatement uses the ServerPreparedStatement constructor, which registers the preparedStatement in the connections openStatements map. If the serverPrepare() call fails (in my case this happens because I use a LIMIT ? in my sql) then the prepared statement is not unregistered from the connection. This causes a memory leak, as the openStatements map grows continuously.
This behaviour is hard to find, because the connection.prepareStatement() method catches the exception and emulates the unsupported prepared statement with a clientPrepareStatement call.
How to repeat:
Use a prepared statement with a LIMIT ? clause. prepare this statement multiple times against the same connection. The connection's openStatements map will grow and the ServerPreparedStatement instances will be kept on the heap.
Suggested fix:
Proper error handling in ServerPreparedStatement constructor - if it throws an exception, it should leave things the way it found them.
I have implemented this, and it fixes the problem for me.
In the Statement constructor, move:
if (!this.connection.getDontTrackOpenResources()) {
this.connection.registerStatement(this);
}
to the end of the method - so if anything in the constructor fails, the statement is not registered with the connection.
the ServerPreparedStatement reads:
public ServerPreparedStatement(Connection conn, String sql, String catalog)
throws SQLException {
super(conn, catalog);
try
{
checkNullOrEmptyQuery(sql);
this.isSelectQuery = StringUtils.startsWithIgnoreCaseAndWs(sql, "SELECT"); //$NON-NLS-1$
this.useTrueBoolean = this.connection.versionMeetsMinimum(3,
21, 23);
this.hasLimitClause = (StringUtils.indexOfIgnoreCase(sql, "LIMIT") != -1); //$NON-NLS-1$
this.firstCharOfStmt = StringUtils.firstNonWsCharUc(sql);
this.originalSql = sql;
if (this.connection.versionMeetsMinimum(4, 1, 2)) {
this.stringTypeCode = MysqlDefs.FIELD_TYPE_VAR_STRING;
} else {
this.stringTypeCode = MysqlDefs.FIELD_TYPE_STRING;
}
serverPrepare(sql);
}
catch(Exception ex)
{
try
{
realClose(false);
}
catch(SQLException e)
{
conn.getLog().logWarn("Prepare statement failed and then couldn't clean up server prepared statement",e);
}
if(ex instanceof SQLException)
throw (SQLException)ex;
throw new SQLException(ex.toString(),
SQLError.SQL_STATE_GENERAL_ERROR);
}
}