Skip to content

Commit

Permalink
Clear MySQL prepared statements cache on connection reset (#1428)
Browse files Browse the repository at this point in the history
* Clear MySQL prepared statements cache on connection reset

See #1424

Otherwise, the client emits an error such as:

io.vertx.mysqlclient.MySQLException: {errorMessage=Unknown prepared statement handler (1) given to mysql_stmt_precheck, errorCode=1243, sqlState=HY000}

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>

* Add LruCacheTest#testCacheCleared

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>

---------

Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
  • Loading branch information
tsegismont committed Mar 13, 2024
1 parent ca64aca commit acb02de
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.vertx.mysqlclient.MySQLAuthenticationPlugin;
import io.vertx.mysqlclient.MySQLConnectOptions;
import io.vertx.mysqlclient.SslMode;
import io.vertx.mysqlclient.impl.codec.ClearCachedStatementsEvent;
import io.vertx.mysqlclient.impl.codec.MySQLCodec;
import io.vertx.mysqlclient.impl.codec.MySQLPacketDecoder;
import io.vertx.mysqlclient.impl.command.InitialHandshakeCommand;
Expand Down Expand Up @@ -114,6 +115,21 @@ protected <R> void doSchedule(CommandBase<R> cmd, Handler<AsyncResult<R>> handle
}
}

@Override
protected void handleMessage(Object msg) {
if (msg == ClearCachedStatementsEvent.INSTANCE) {
clearCachedStatements();
} else {
super.handleMessage(msg);
}
}

private void clearCachedStatements() {
if (this.psCache != null) {
this.psCache.clear();
}
}

public Future<Void> upgradeToSsl(ClientSSLOptions sslOptions) {
return socket.upgradeToSsl(sslOptions);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.vertx.mysqlclient.impl.codec;

/**
* An event that signals all cached statements must be cleared from the cache.
*/
public class ClearCachedStatementsEvent {

public static final ClearCachedStatementsEvent INSTANCE = new ClearCachedStatementsEvent();

private ClearCachedStatementsEvent() {
// Singleton
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ void encode(MySQLEncoder encoder) {

@Override
void decodePayload(ByteBuf payload, int payloadLength) {
encoder.chctx.fireChannelRead(ClearCachedStatementsEvent.INSTANCE);
handleOkPacketOrErrorPacketPayload(payload);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.vertx.ext.unit.junit.VertxUnitRunner;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.Tuple;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
Expand Down Expand Up @@ -160,6 +161,19 @@ public void testResetConnection(TestContext ctx) {
}));
}

@Test
public void testResetConnectionClearsPreparedStatementCache(TestContext ctx) {
Assume.assumeFalse(rule.isUsingMySQL5_6());
MySQLConnectOptions connectOptions = new MySQLConnectOptions(options).setCachePreparedStatements(true);
MySQLConnection.connect(vertx, connectOptions).onComplete(ctx.asyncAssertSuccess(conn -> {
conn.preparedQuery("SELECT 1").execute(Tuple.tuple()).onComplete(ctx.asyncAssertSuccess(res1 -> {
conn.resetConnection().onComplete(ctx.asyncAssertSuccess(rst -> {
conn.preparedQuery("SELECT 1").execute(Tuple.tuple()).onComplete(ctx.asyncAssertSuccess());
}));
}));
}));
}

@Test
public void testChangeUser(TestContext ctx) {
MySQLConnection.connect(vertx, options).onComplete( ctx.asyncAssertSuccess(conn -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,10 @@

package io.vertx.sqlclient.impl.cache;

import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.sqlclient.impl.PreparedStatement;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

/**
* A LRU replacement strategy cache based on {@link java.util.LinkedHashMap} for prepared statements.
* An LRU replacement strategy cache based on {@link java.util.LinkedHashMap} for prepared statements.
*/
public class LruCache<K, V> extends LinkedHashMap<K, V> {

Expand Down Expand Up @@ -75,4 +66,10 @@ protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return false;
}
}

@Override
public void clear() {
super.clear();
removed = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,13 @@ public boolean isFull() {
public int size() {
return cache.size();
}

/**
* Clears the cache.
* <p>
* This method must be called only when the cached prepared statements have been released (e.g. with a connection reset).
*/
public void clear() {
cache.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;

public class LruCacheTest {

Expand Down Expand Up @@ -37,4 +37,14 @@ public void testEvict() {
assertEquals("value-0", evicted);
assertEquals(1023, cache.size());
}

@Test
public void testCacheCleared() {
LruCache<String, String> cache = new LruCache<>(42);
List<String> evicted = cache.cache("foo", "bar");
assertTrue(evicted.isEmpty());
assertNotNull(cache.get("foo"));
cache.clear();
assertNull(cache.get("foo"));
}
}

0 comments on commit acb02de

Please sign in to comment.