Close transaction queue after connection wait on SQLite

Affects issues:
- #3436
This commit is contained in:
Aurora Lahtela 2024-02-11 15:47:39 +02:00
parent bf3bdb599d
commit e041e193fc
2 changed files with 42 additions and 15 deletions

View File

@ -148,7 +148,7 @@ public abstract class SQLDB extends AbstractDatabase {
@Override @Override
public void init() { public void init() {
List<Runnable> unfinishedTransactions = closeTransactionExecutor(transactionExecutor); List<Runnable> unfinishedTransactions = forceCloseTransactionExecutor();
this.transactionExecutor = transactionExecutorServiceProvider.get(); this.transactionExecutor = transactionExecutorServiceProvider.get();
setState(State.PATCHING); setState(State.PATCHING);
@ -167,9 +167,9 @@ public abstract class SQLDB extends AbstractDatabase {
} }
} }
private List<Runnable> closeTransactionExecutor(ExecutorService transactionExecutor) { protected boolean attemptToCloseTransactionExecutor() {
if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) { if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) {
return Collections.emptyList(); return true;
} }
transactionExecutor.shutdown(); transactionExecutor.shutdown();
try { try {
@ -179,20 +179,27 @@ public abstract class SQLDB extends AbstractDatabase {
logger.warn(TimeSettings.DB_TRANSACTION_FINISH_WAIT_DELAY.getPath() + " was set to over 5 minutes, using 5 min instead."); logger.warn(TimeSettings.DB_TRANSACTION_FINISH_WAIT_DELAY.getPath() + " was set to over 5 minutes, using 5 min instead.");
waitMs = TimeUnit.MINUTES.toMillis(5L); waitMs = TimeUnit.MINUTES.toMillis(5L);
} }
if (!transactionExecutor.awaitTermination(waitMs, TimeUnit.MILLISECONDS)) { return transactionExecutor.awaitTermination(waitMs, TimeUnit.MILLISECONDS);
List<Runnable> unfinished = transactionExecutor.shutdownNow();
int unfinishedCount = unfinished.size();
if (unfinishedCount > 0) {
logger.warn(unfinishedCount + " unfinished database transactions were not executed.");
}
return unfinished;
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
}
return true;
}
protected List<Runnable> forceCloseTransactionExecutor() {
if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) {
return Collections.emptyList();
}
try {
List<Runnable> unfinished = transactionExecutor.shutdownNow();
int unfinishedCount = unfinished.size();
if (unfinishedCount > 0) {
logger.warn(unfinishedCount + " unfinished database transactions were not executed.");
}
return unfinished;
} finally { } finally {
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE)); logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
} }
return Collections.emptyList();
} }
Patch[] patches() { Patch[] patches() {
@ -307,13 +314,18 @@ public abstract class SQLDB extends AbstractDatabase {
@Override @Override
public void close() { public void close() {
// SQLiteDB Overrides this, so any additions to this should also be reflected there.
if (getState() == State.OPEN) setState(State.CLOSING); if (getState() == State.OPEN) setState(State.CLOSING);
closeTransactionExecutor(transactionExecutor); if (attemptToCloseTransactionExecutor()) {
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
} else {
forceCloseTransactionExecutor();
}
unloadDriverClassloader(); unloadDriverClassloader();
setState(State.CLOSED); setState(State.CLOSED);
} }
private void unloadDriverClassloader() { protected void unloadDriverClassloader() {
// Unloading class loader using close() causes issues when reloading. // Unloading class loader using close() causes issues when reloading.
// It is better to leak this memory than crash the plugin on reload. // It is better to leak this memory than crash the plugin on reload.
@ -349,6 +361,8 @@ public abstract class SQLDB extends AbstractDatabase {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
if (getState() == State.CLOSED) return CompletableFuture.completedFuture(null);
accessLock.performDatabaseOperation(() -> { accessLock.performDatabaseOperation(() -> {
if (!ranIntoFatalError.get()) {transaction.executeTransaction(this);} if (!ranIntoFatalError.get()) {transaction.executeTransaction(this);}
}, transaction); }, transaction);
@ -360,6 +374,9 @@ public abstract class SQLDB extends AbstractDatabase {
} }
private boolean determineIfShouldDropUnimportantTransactions(int queueSize) { private boolean determineIfShouldDropUnimportantTransactions(int queueSize) {
if (getState() == State.CLOSING) {
return true;
}
boolean dropTransactions = dropUnimportantTransactions.get(); boolean dropTransactions = dropUnimportantTransactions.get();
if (queueSize >= 500 && !dropTransactions) { if (queueSize >= 500 && !dropTransactions) {
logger.warn("Database queue size: " + queueSize + ", dropping some unimportant transactions. If this keeps happening disable some extensions or optimize MySQL."); logger.warn("Database queue size: " + queueSize + ", dropping some unimportant transactions. If this keeps happening disable some extensions or optimize MySQL.");

View File

@ -201,11 +201,21 @@ public class SQLiteDB extends SQLDB {
@Override @Override
public void close() { public void close() {
super.close(); if (getState() == State.OPEN) setState(State.CLOSING);
boolean transactionQueueClosed = attemptToCloseTransactionExecutor();
if (transactionQueueClosed) logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
unloadDriverClassloader();
setState(State.CLOSED);
stopConnectionPingTask(); stopConnectionPingTask();
logger.info(locale.getString(PluginLang.DISABLED_WAITING_SQLITE)); logger.info(locale.getString(PluginLang.DISABLED_WAITING_SQLITE));
connectionLock.waitUntilNothingAccessing(); connectionLock.waitUntilNothingAccessing();
// Transaction queue can't be force-closed before all connections have terminated.
if (!transactionQueueClosed) forceCloseTransactionExecutor();
if (connection != null) { if (connection != null) {
MiscUtils.close(connection); MiscUtils.close(connection);
} }