KEYCLOAK-10262 DBLockTest.testLockConcurrently fails with MariaDB Galera 10.1
This commit is contained in:
parent
7654793713
commit
c124aec586
3 changed files with 60 additions and 25 deletions
|
@ -76,12 +76,22 @@ public class Retry {
|
|||
* @return Index of the first successful invocation, starting from 0.
|
||||
*/
|
||||
public static int executeWithBackoff(AdvancedRunnable runnable, int attemptsCount, int intervalBaseMillis) {
|
||||
return executeWithBackoff(runnable, null, attemptsCount, intervalBaseMillis);
|
||||
}
|
||||
|
||||
|
||||
public static int executeWithBackoff(AdvancedRunnable runnable, ThrowableCallback throwableCallback, int attemptsCount, int intervalBaseMillis) {
|
||||
int iteration = 0;
|
||||
while (true) {
|
||||
try {
|
||||
runnable.run(iteration);
|
||||
return iteration;
|
||||
} catch (RuntimeException | AssertionError e) {
|
||||
|
||||
if (throwableCallback != null) {
|
||||
throwableCallback.handleThrowable(iteration, e);
|
||||
}
|
||||
|
||||
attemptsCount--;
|
||||
iteration++;
|
||||
if (attemptsCount > 0) {
|
||||
|
@ -150,6 +160,17 @@ public class Retry {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed here because:
|
||||
* - java.util.function.BiConsumer defined from Java 8
|
||||
* - Adds some additional info (current iteration and called throwable
|
||||
*/
|
||||
public interface ThrowableCallback {
|
||||
|
||||
void handleThrowable(int iteration, Throwable t);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed here because:
|
||||
* - java.util.function.Supplier defined from Java 8
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.connections.jpa.updater.liquibase.lock;
|
|||
|
||||
import liquibase.database.core.DerbyDatabase;
|
||||
import liquibase.exception.DatabaseException;
|
||||
import liquibase.exception.UnexpectedLiquibaseException;
|
||||
import liquibase.executor.Executor;
|
||||
import liquibase.executor.ExecutorService;
|
||||
import liquibase.lockservice.StandardLockService;
|
||||
|
@ -78,24 +79,24 @@ public class CustomLockService extends StandardLockService {
|
|||
}
|
||||
|
||||
|
||||
if (!isDatabaseChangeLogLockTableInitialized(createdTable)) {
|
||||
try {
|
||||
try {
|
||||
if (!isDatabaseChangeLogLockTableInitialized(createdTable)) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Initialize Database Lock Table");
|
||||
}
|
||||
executor.execute(new InitializeDatabaseChangeLogLockTableStatement());
|
||||
database.commit();
|
||||
|
||||
} catch (DatabaseException de) {
|
||||
log.warn("Failed to insert first record to the lock table. Maybe other transaction inserted in the meantime. Retrying...");
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace(de.getMessage(), de); // Log details at trace level
|
||||
}
|
||||
database.rollback();
|
||||
throw new LockRetryException(de);
|
||||
log.debug("Initialized record in the database lock table");
|
||||
}
|
||||
|
||||
log.debug("Initialized record in the database lock table");
|
||||
} catch (DatabaseException de) {
|
||||
log.warn("Failed to insert first record to the lock table. Maybe other transaction inserted in the meantime. Retrying...");
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace(de.getMessage(), de); // Log details at trace level
|
||||
}
|
||||
database.rollback();
|
||||
throw new LockRetryException(de);
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,6 +113,20 @@ public class CustomLockService extends StandardLockService {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDatabaseChangeLogLockTableInitialized(boolean tableJustCreated) throws DatabaseException {
|
||||
try {
|
||||
return super.isDatabaseChangeLogLockTableInitialized(tableJustCreated);
|
||||
} catch (UnexpectedLiquibaseException ulie) {
|
||||
// It can happen with MariaDB Galera 10.1 that UnexpectedLiquibaseException is rethrown due the DB lock. It is sufficient to just rollback transaction and retry in that case.
|
||||
if (ulie.getCause() != null && ulie.getCause() instanceof DatabaseException) {
|
||||
throw (DatabaseException) ulie.getCause();
|
||||
} else {
|
||||
throw ulie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForLock() {
|
||||
boolean locked = false;
|
||||
|
|
|
@ -21,6 +21,7 @@ import liquibase.Liquibase;
|
|||
import liquibase.exception.DatabaseException;
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||
|
@ -38,8 +39,8 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(LiquibaseDBLockProvider.class);
|
||||
|
||||
// 3 should be sufficient (Potentially one failure for createTable and one for insert record)
|
||||
private int DEFAULT_MAX_ATTEMPTS = 3;
|
||||
// 10 should be sufficient
|
||||
private int DEFAULT_MAX_ATTEMPTS = 10;
|
||||
|
||||
|
||||
private final LiquibaseDBLockProviderFactory factory;
|
||||
|
@ -49,8 +50,6 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
|||
private Connection dbConnection;
|
||||
private boolean initialized = false;
|
||||
|
||||
private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
|
||||
|
||||
public LiquibaseDBLockProvider(LiquibaseDBLockProviderFactory factory, KeycloakSession session) {
|
||||
this.factory = factory;
|
||||
this.session = session;
|
||||
|
@ -96,23 +95,23 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
|||
|
||||
lazyInit();
|
||||
|
||||
while (maxAttempts > 0) {
|
||||
try {
|
||||
lockService.waitForLock();
|
||||
factory.setHasLock(true);
|
||||
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
|
||||
return;
|
||||
} catch (LockRetryException le) {
|
||||
Retry.executeWithBackoff((int iteration) -> {
|
||||
|
||||
lockService.waitForLock();
|
||||
factory.setHasLock(true);
|
||||
|
||||
}, (int iteration, Throwable e) -> {
|
||||
|
||||
if (e instanceof LockRetryException && iteration < (DEFAULT_MAX_ATTEMPTS - 1)) {
|
||||
// Indicates we should try to acquire lock again in different transaction
|
||||
safeRollbackConnection();
|
||||
restart();
|
||||
maxAttempts--;
|
||||
} catch (RuntimeException re) {
|
||||
} else {
|
||||
safeRollbackConnection();
|
||||
safeCloseConnection();
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
|
||||
}, DEFAULT_MAX_ATTEMPTS, 10);
|
||||
});
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue