package org.flywaydb.core.internal.database;
import org.flywaydb.core.api.logging.Log;
import org.flywaydb.core.api.logging.LogFactory;
import org.flywaydb.core.internal.jdbc.JdbcTemplate;
import org.flywaydb.core.internal.jdbc.Results;
import java.math.BigInteger;
import java.sql.SQLException;
import java.time.Duration;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
public class InsertRowLock {
private static final Log LOG = LogFactory.getLog(InsertRowLock.class);
private static final Random random = new Random();
private final String tableLockString = getNextRandomString();
private final JdbcTemplate jdbcTemplate;
public final int lockTimeoutMins;
private Timer timer;
public InsertRowLock(JdbcTemplate jdbcTemplate, int lockTimeoutMins) {
this.jdbcTemplate = jdbcTemplate;
this.lockTimeoutMins = lockTimeoutMins;
}
public void doLock(String insertStatementTemplate, String updateLockStatement, String deleteExpiredLockStatement, String booleanTrue) throws SQLException {
int retryCount = 0;
while (true) {
try {
jdbcTemplate.execute(deleteExpiredLockStatement);
if (insertLockingRow(insertStatementTemplate, booleanTrue)) {
startLockWatchingThread(String.format(updateLockStatement.replace("?", "%s"), tableLockString));
return;
}
if (retryCount < 50) {
retryCount++;
LOG.debug("Waiting for lock on Flyway schema history table");
} else {
LOG.error("Waiting for lock on Flyway schema history table. Application may be deadlocked. Lock row may require manual removal " +
"from the schema history table.");
}
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
}
private boolean insertLockingRow(String insertStatementTemplate, String booleanTrue) {
String insertStatement = String.format(insertStatementTemplate.replace("?", "%s"),
-100,
"'" + tableLockString + "'",
"'flyway-lock'",
"''",
"''",
0,
"''",
0,
booleanTrue
);
Results results = jdbcTemplate.executeStatement(insertStatement);
return results.getException() == null;
}
public void doUnlock(String deleteLockTemplate) throws SQLException {
stopLockWatchingThread();
String deleteLock = String.format(deleteLockTemplate.replace("?", "%s"), tableLockString);
jdbcTemplate.execute(deleteLock);
}
private String getNextRandomString(){
BigInteger bInt = new BigInteger(128, random);
return bInt.toString(16);
}
private void startLockWatchingThread(String updateLockStatement) {
TimerTask lockWatcherTask = new TimerTask() {
@Override
public void run() {
LOG.debug("Updating lock in Flyway schema history table");
jdbcTemplate.executeStatement(updateLockStatement);
}
};
timer = new Timer();
timer.schedule(lockWatcherTask, 0, Duration.ofMinutes(lockTimeoutMins / 2).toMillis());
}
private void stopLockWatchingThread() {
timer.cancel();
}
}