Add error messages if DB becomes corrupted

This commit is contained in:
James Seibel
2026-06-02 21:42:26 -05:00
parent 18b0582152
commit 4ac56774fb
2 changed files with 70 additions and 1 deletions
@@ -0,0 +1,25 @@
package com.seibel.distanthorizons.core.sql;
import java.sql.SQLException;
/**
* Used to simplify handling when a database is corrupted
* since Java doesn't have a specific exception to handle corrupted databases
*/
public class DbCorruptedException extends SQLException
{
public DbCorruptedException(String message) { super(message); }
public DbCorruptedException(String message, Throwable cause) { super(message, cause); }
public DbCorruptedException(Throwable cause) { super(cause); }
// helper methods //
public static boolean isCorruptedException(SQLException e)
{
String message = e.getMessage().toLowerCase();
return message.contains("sqlite_corrupt")
|| message.contains("malformed");
}
}
@@ -19,10 +19,13 @@
package com.seibel.distanthorizons.core.sql.repo; package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException; import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
import com.seibel.distanthorizons.core.sql.DbCorruptedException;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO; import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper; import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.ExceptionUtil;
@@ -37,6 +40,7 @@ import java.io.IOException;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -55,6 +59,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
private static final ConcurrentHashMap<String, Connection> CONNECTIONS_BY_CONNECTION_STRING = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Connection> CONNECTIONS_BY_CONNECTION_STRING = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<AbstractDhRepo<?, ?>, String> ACTIVE_CONNECTION_STRINGS_BY_REPO = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<AbstractDhRepo<?, ?>, String> ACTIVE_CONNECTION_STRINGS_BY_REPO = new ConcurrentHashMap<>();
private static final Set<String> CORRUPTED_DB_PATHS = Collections.newSetFromMap(new ConcurrentHashMap<>());
@@ -70,6 +75,8 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
protected final KeyedLockContainer<TKey> saveLockContainer = new KeyedLockContainer<>(); protected final KeyedLockContainer<TKey> saveLockContainer = new KeyedLockContainer<>();
private final AtomicBoolean databaseCorruptedRef = new AtomicBoolean(false);
//=============// //=============//
@@ -227,7 +234,6 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
finally finally
{ {
saveLock.unlock(); saveLock.unlock();
//this.tryTriggerWalFlush();
} }
} }
private void insert(TDTO dto) private void insert(TDTO dto)
@@ -392,6 +398,13 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
@Nullable @Nullable
public ResultSet query(@Nullable PreparedStatement statement) throws RuntimeException public ResultSet query(@Nullable PreparedStatement statement) throws RuntimeException
{ {
// if the DB is corrupted act as if it's closed
// that should prevent further harm
if (this.databaseCorruptedRef.get())
{
return null;
}
// This is done so we don't have to add "if null" checks everywhere. // This is done so we don't have to add "if null" checks everywhere.
// Normally this should only happen once the DB has been closed. // Normally this should only happen once the DB has been closed.
if (statement == null) if (statement == null)
@@ -425,6 +438,33 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
{ {
return null; return null;
} }
else if (DbCorruptedException.isCorruptedException(e))
{
// error may trigger on multiple threads at once
synchronized (this)
{
// only trigger this error once per database
if (!this.databaseCorruptedRef.getAndSet(true)
// multiple repos may be open for the same path (client and server levels in singleplayer)
&& CORRUPTED_DB_PATHS.add(this.databaseFile.getPath()))
{
LOGGER.error("DH database file at [" + this.databaseFile.getPath() + "] is corrupted. \n" +
"All operations to this DB are disabled, DH may behave strangely if you continue playing. \n" +
"Please leave the world and delete the corrupted database file to fix. \n" +
"Error: [" + e.getMessage() + "]", e);
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.DARK_RED + MinecraftTextFormat.BOLD + "DH database is corrupted." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"DH will behave strangely if your continue playing. \n" +
"Please leave the world and delete the corrupted database file at: \n" +
MinecraftTextFormat.YELLOW + this.databaseFile.getPath() + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"to resolve the issue. \n"
);
}
}
return null;
}
else else
{ {
String message = "Unexpected Query error: [" + e.getMessage() + "], for prepared statement: [" + statement + "]."; String message = "Unexpected Query error: [" + e.getMessage() + "], for prepared statement: [" + statement + "].";
@@ -512,6 +552,10 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
LOGGER.error("Unable to close the connection ["+connectionString+"], error: ["+e.getMessage()+"]"); LOGGER.error("Unable to close the connection ["+connectionString+"], error: ["+e.getMessage()+"]");
} }
} }
// clear the errors so they can be re-fired if needed
CORRUPTED_DB_PATHS.clear();
} }
@Override @Override