Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core
This commit is contained in:
@@ -49,6 +49,6 @@ public final class ModInfo
|
||||
public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control";
|
||||
|
||||
/** All DH owned threads should start with this string to allow for easier debugging and profiling. */
|
||||
public static String THREAD_NAME_PREFIX = "DH-";
|
||||
public static final String THREAD_NAME_PREFIX = "DH-";
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPools;
|
||||
import com.seibel.distanthorizons.core.world.*;
|
||||
@@ -58,7 +59,7 @@ public class SharedApi
|
||||
private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 500;
|
||||
private static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 5_000;
|
||||
|
||||
private static final Timer CHUNK_UPDATE_TIMER = new Timer("DH-ChunkUpdateTimer", true);
|
||||
private static final Timer CHUNK_UPDATE_TIMER = TimerUtil.CreateTimer("ChunkUpdateTimer");
|
||||
|
||||
|
||||
private static AbstractDhWorld currentWorld;
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.enums.rendering.ETransparency;
|
||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
@@ -103,7 +104,7 @@ public class RenderCacheConfigEventHandler
|
||||
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
||||
}
|
||||
};
|
||||
this.cacheClearingTimer = new Timer("RenderCacheConfig-Timeout-Timer");
|
||||
this.cacheClearingTimer = TimerUtil.CreateTimer("RenderCacheClearConfigTimer");
|
||||
this.cacheClearingTimer.schedule(timerTask, TIMEOUT_IN_MS);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.ConfigBase;
|
||||
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
|
||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui;
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
@@ -119,7 +120,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
|
||||
{
|
||||
public void run() { AbstractPresetConfigEventHandler.this.applyPreset(); }
|
||||
};
|
||||
this.applyPresetTimer = new Timer("DH-ApplyPresetTimer", true);
|
||||
this.applyPresetTimer = TimerUtil.CreateTimer("ApplyConfigPresetTimer");
|
||||
this.applyPresetTimer.schedule(task, MS_DELAY_BEFORE_APPLYING_PRESET);
|
||||
|
||||
}
|
||||
|
||||
+112
-56
@@ -7,6 +7,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.sql.*;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPools;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -15,13 +16,13 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ClosedByInterruptException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -31,7 +32,7 @@ import java.util.zip.CheckedOutputStream;
|
||||
public abstract class AbstractDataSourceHandler<TDataSource extends IDataSource<TDhLevel>, TDhLevel extends IDhLevel> implements ISourceProvider<TDataSource, TDhLevel>
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final Timer DELAYED_SAVE_TIMER = new Timer("DH-DelayedSaveTimer", true);
|
||||
private static final Timer DELAYED_SAVE_TIMER = TimerUtil.CreateTimer("DataSourceSaveTimer");
|
||||
/** How long a data source must remain un-modified before being written to disk. */
|
||||
private static final int SAVE_DELAY_IN_MS = 4_000;
|
||||
|
||||
@@ -44,7 +45,11 @@ public abstract class AbstractDataSourceHandler<TDataSource extends IDataSource<
|
||||
|
||||
protected final ConcurrentHashMap<DhSectionPos, TDataSource> unsavedDataSourceBySectionPos = new ConcurrentHashMap<>();
|
||||
protected final ConcurrentHashMap<DhSectionPos, TimerTask> saveTimerTasksBySectionPos = new ConcurrentHashMap<>();
|
||||
|
||||
protected final ReentrantLock[] updateLockArray;
|
||||
protected final ReentrantLock[] queueSaveLockArray;
|
||||
protected final ReentrantLock closeLock = new ReentrantLock();
|
||||
protected volatile boolean isShutdown = false;
|
||||
|
||||
protected final TDhLevel level;
|
||||
protected final File saveDir;
|
||||
@@ -67,12 +72,15 @@ public abstract class AbstractDataSourceHandler<TDataSource extends IDataSource<
|
||||
LOGGER.warn("Unable to create full data folder, file saving may fail.");
|
||||
}
|
||||
|
||||
// the lock array's length is double the number of CPU cores so the number of collisions
|
||||
// the lock arrays' length is double the number of CPU cores so the number of collisions
|
||||
// should be relatively low without having too many extra locks
|
||||
this.updateLockArray = new ReentrantLock[Runtime.getRuntime().availableProcessors() * 2];
|
||||
for (int i = 0; i < this.updateLockArray.length; i++)
|
||||
int lockCount = Runtime.getRuntime().availableProcessors() * 2;
|
||||
this.updateLockArray = new ReentrantLock[lockCount];
|
||||
this.queueSaveLockArray = new ReentrantLock[lockCount];
|
||||
for (int i = 0; i < lockCount; i++)
|
||||
{
|
||||
this.updateLockArray[i] = new ReentrantLock();
|
||||
this.queueSaveLockArray[i] = new ReentrantLock();
|
||||
}
|
||||
|
||||
this.repo = repo;
|
||||
@@ -195,19 +203,23 @@ public abstract class AbstractDataSourceHandler<TDataSource extends IDataSource<
|
||||
}
|
||||
|
||||
|
||||
executor.execute(() ->
|
||||
try
|
||||
{
|
||||
DhSectionPos chunkSectionPos = chunkDataView.getSectionPos();
|
||||
LodUtil.assertTrue(chunkSectionPos.overlapsExactly(pos), "Update failed, chunk [" + chunkSectionPos + "] does not overlap section [" + pos + "].");
|
||||
|
||||
// update this pos
|
||||
this.updateDataSourceAtPos(pos, chunkDataView);
|
||||
|
||||
// recursively update the parent pos
|
||||
DhSectionPos parentPos = pos.getParentPos();
|
||||
executor.execute(() ->
|
||||
{
|
||||
DhSectionPos chunkSectionPos = chunkDataView.getSectionPos();
|
||||
LodUtil.assertTrue(chunkSectionPos.overlapsExactly(pos), "Update failed, chunk [" + chunkSectionPos + "] does not overlap section [" + pos + "].");
|
||||
|
||||
// update this pos
|
||||
this.updateDataSourceAtPos(pos, chunkDataView);
|
||||
|
||||
// recursively update the parent pos
|
||||
DhSectionPos parentPos = pos.getParentPos();
|
||||
this.recursivelyUpdateDataSourcesAsync(parentPos, chunkDataView);
|
||||
this.recursivelyUpdateDataSourcesAsync(parentPos, chunkDataView);
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (RejectedExecutionException ignore) { /* can happen if the executor was shutdown while this task was queued */ }
|
||||
}
|
||||
protected void updateDataSourceAtPos(DhSectionPos pos, ChunkSizedFullDataAccessor chunkData)
|
||||
{
|
||||
@@ -248,45 +260,72 @@ public abstract class AbstractDataSourceHandler<TDataSource extends IDataSource<
|
||||
*/
|
||||
protected void queueDelayedSave(TDataSource dataSource)
|
||||
{
|
||||
// a lock is necessary to prevent two threads from queuing a save at the same time,
|
||||
// which can cause the timer to queue canceled tasks
|
||||
DhSectionPos pos = dataSource.getSectionPos();
|
||||
ReentrantLock saveQueueLock = this.getSaveQueueLockForPos(pos);
|
||||
|
||||
// put the data source in memory until it can be flushed to disk
|
||||
this.unsavedDataSourceBySectionPos.put(pos, dataSource);
|
||||
|
||||
TimerTask task = new TimerTask()
|
||||
// done to prevent queueing saves while the current queue is being cleared
|
||||
if (this.isShutdown)
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
LOGGER.warn("Attempted to queue save for section ["+pos+"] while the handler is being shut down. Some data for that position may be lost.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
saveQueueLock.lock();
|
||||
|
||||
// put the data source in memory until it can be flushed to disk
|
||||
this.unsavedDataSourceBySectionPos.put(pos, dataSource);
|
||||
|
||||
TimerTask task = new TimerTask()
|
||||
{
|
||||
try
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
final TDataSource finalDataSource = AbstractDataSourceHandler.this.unsavedDataSourceBySectionPos.remove(pos);
|
||||
|
||||
// this can rarely happen due to imperfect concurrency handling,
|
||||
// if the data source is null that just means it has already been saved so nothing needs to be done
|
||||
if (finalDataSource != null)
|
||||
try
|
||||
{
|
||||
AbstractDataSourceHandler.this.writeDataSourceToFile(finalDataSource);
|
||||
final TDataSource finalDataSource = AbstractDataSourceHandler.this.unsavedDataSourceBySectionPos.remove(pos);
|
||||
|
||||
// this can rarely happen due to imperfect concurrency handling,
|
||||
// if the data source is null that just means it has already been saved so nothing needs to be done
|
||||
if (finalDataSource != null)
|
||||
{
|
||||
AbstractDataSourceHandler.this.writeDataSourceToFile(finalDataSource);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Failed to save updated data for section ["+pos+"], error: ["+e.getMessage()+"]", e);
|
||||
}
|
||||
}
|
||||
catch (ClosedByInterruptException e) // thrown by buffers that are interrupted
|
||||
{
|
||||
// expected if the file handler is shut down, the exception can be ignored
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("Failed to save updated data for section " + pos, e);
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
DELAYED_SAVE_TIMER.schedule(task, SAVE_DELAY_IN_MS);
|
||||
}
|
||||
};
|
||||
DELAYED_SAVE_TIMER.schedule(task, SAVE_DELAY_IN_MS);
|
||||
|
||||
// cancel the old save timer if present
|
||||
// (this is equivalent to restarting the timer)
|
||||
TimerTask oldTask = this.saveTimerTasksBySectionPos.put(pos, task);
|
||||
if (oldTask != null)
|
||||
catch (IllegalStateException ignore)
|
||||
{
|
||||
// James isn't sure why this is possible since this logic is inside a lock,
|
||||
// maybe the timer is just async enough that there can be problems?
|
||||
LOGGER.warn("Attempted to queue an already canceled task. Pos: ["+pos+"], task already queued for pos: ["+this.saveTimerTasksBySectionPos.containsKey(pos)+"]");
|
||||
}
|
||||
|
||||
|
||||
// cancel the old save timer if present
|
||||
// (this is equivalent to restarting the timer)
|
||||
TimerTask oldTask = this.saveTimerTasksBySectionPos.put(pos, task);
|
||||
if (oldTask != null)
|
||||
{
|
||||
oldTask.cancel();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
oldTask.cancel();
|
||||
saveQueueLock.unlock();
|
||||
}
|
||||
}
|
||||
protected void writeDataSourceToFile(TDataSource dataSource) throws IOException
|
||||
@@ -333,6 +372,7 @@ public abstract class AbstractDataSourceHandler<TDataSource extends IDataSource<
|
||||
|
||||
/** Based on the stack overflow post: https://stackoverflow.com/a/45909920 */
|
||||
protected ReentrantLock getUpdateLockForPos(DhSectionPos pos) { return this.updateLockArray[Math.abs(pos.hashCode()) % this.updateLockArray.length]; }
|
||||
protected ReentrantLock getSaveQueueLockForPos(DhSectionPos pos) { return this.queueSaveLockArray[Math.abs(pos.hashCode()) % this.queueSaveLockArray.length]; }
|
||||
|
||||
|
||||
|
||||
@@ -343,22 +383,38 @@ public abstract class AbstractDataSourceHandler<TDataSource extends IDataSource<
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
LOGGER.info("Closing ["+this.getClass().getSimpleName()+"] for level: ["+this.level+"], saving ["+this.saveTimerTasksBySectionPos.size()+"] positions.");
|
||||
|
||||
Enumeration<DhSectionPos> list = this.saveTimerTasksBySectionPos.keys();
|
||||
while (list.hasMoreElements())
|
||||
try
|
||||
{
|
||||
DhSectionPos pos = list.nextElement();
|
||||
TimerTask saveTask = this.saveTimerTasksBySectionPos.remove(pos);
|
||||
if (saveTask != null)
|
||||
this.closeLock.lock();
|
||||
this.isShutdown = true;
|
||||
|
||||
// wait a moment so any queued saves can finish queuing,
|
||||
// otherwise we might not see everything that needs saving and attempt to use a closed repo
|
||||
Thread.sleep(200);
|
||||
|
||||
LOGGER.info("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.level + "], saving [" + this.saveTimerTasksBySectionPos.size() + "] positions.");
|
||||
|
||||
|
||||
Enumeration<DhSectionPos> list = this.saveTimerTasksBySectionPos.keys();
|
||||
while (list.hasMoreElements())
|
||||
{
|
||||
saveTask.run();
|
||||
saveTask.cancel();
|
||||
DhSectionPos pos = list.nextElement();
|
||||
TimerTask saveTask = this.saveTimerTasksBySectionPos.remove(pos);
|
||||
if (saveTask != null)
|
||||
{
|
||||
saveTask.run();
|
||||
// canceling the task doesn't need to be done since the it has internal logic to prevent running more than once
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.info("[" + this.getClass().getSimpleName() + "] saving complete, closing repo.");
|
||||
this.repo.close();
|
||||
}
|
||||
catch (InterruptedException ignore) { }
|
||||
finally
|
||||
{
|
||||
this.closeLock.unlock();
|
||||
}
|
||||
|
||||
LOGGER.info("["+this.getClass().getSimpleName()+"] saving complete, closing repo.");
|
||||
this.repo.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -74,8 +74,9 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
IFullDataSource dataSource = super.get(pos);
|
||||
|
||||
// add world gen tasks for missing columns in the data source
|
||||
// if this position hasn't already been queued for generation
|
||||
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
|
||||
if (worldGenQueue != null)
|
||||
if (worldGenQueue != null && !this.generatingDataSourceByPos.containsKey(pos))
|
||||
{
|
||||
this.queueWorldGenForMissingColumnsInDataSource(worldGenQueue, pos, dataSource);
|
||||
}
|
||||
|
||||
+1
-7
@@ -190,19 +190,13 @@ public class DebugRenderer
|
||||
{
|
||||
this.transformThiFrame = transform;
|
||||
Vec3d camPos = MC_RENDER.getCameraExactPosition();
|
||||
camPosFloatThisFrame = new Vec3f((float) camPos.x, (float) camPos.y, (float) camPos.z);
|
||||
this.camPosFloatThisFrame = new Vec3f((float) camPos.x, (float) camPos.y, (float) camPos.z);
|
||||
|
||||
GLState glState = new GLState();
|
||||
this.init();
|
||||
|
||||
GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
|
||||
GL32.glViewport(0, 0, MC_RENDER.getTargetFrameBufferViewportWidth(), MC_RENDER.getTargetFrameBufferViewportHeight());
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
|
||||
//GL32.glLineWidth(2);
|
||||
GL32.glEnable(GL32.GL_DEPTH_TEST);
|
||||
GL32.glDisable(GL32.GL_STENCIL_TEST);
|
||||
GL32.glDisable(GL32.GL_BLEND);
|
||||
GL32.glDisable(GL32.GL_SCISSOR_TEST);
|
||||
|
||||
this.basicShader.bind();
|
||||
this.va.bind();
|
||||
|
||||
+12
-7
@@ -303,6 +303,12 @@ public class LodRenderer
|
||||
{
|
||||
this.setActiveColorTextureId(this.nullableColorTexture.getTextureId());
|
||||
}
|
||||
else
|
||||
{
|
||||
// get MC's color texture
|
||||
int mcColorTextureId = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
||||
this.setActiveColorTextureId(mcColorTextureId);
|
||||
}
|
||||
// Bind LOD frame buffer
|
||||
this.framebuffer.bind();
|
||||
|
||||
@@ -440,6 +446,12 @@ public class LodRenderer
|
||||
FogShader.INSTANCE.render(partialTicks);
|
||||
}
|
||||
|
||||
if (Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get())
|
||||
{
|
||||
profiler.popPush("Debug wireframes");
|
||||
// Note: this can be very slow if a lot of boxes are being rendered
|
||||
DebugRenderer.INSTANCE.render(modelViewProjectionMatrix);
|
||||
}
|
||||
|
||||
if (this.usingMcFrameBuffer)
|
||||
{
|
||||
@@ -482,13 +494,6 @@ public class LodRenderer
|
||||
|
||||
this.shaderProgram.unbind();
|
||||
|
||||
if (Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get())
|
||||
{
|
||||
profiler.popPush("Debug wireframes");
|
||||
// Note: this can be very slow if a lot of boxes are being rendered
|
||||
DebugRenderer.INSTANCE.render(modelViewProjectionMatrix);
|
||||
profiler.popPush("LOD cleanup");
|
||||
}
|
||||
|
||||
minecraftGlState.restore();
|
||||
drawCleanup.end("LodDrawCleanup");
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.junit.Assert;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Handles interfacing with SQL databases.
|
||||
@@ -37,8 +38,8 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
public static final int TIMEOUT_SECONDS = 30;
|
||||
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final HashMap<String, Connection> CONNECTIONS_BY_CONNECTION_STRING = new HashMap<>();
|
||||
private static final HashMap<AbstractDhRepo<?>, String> ACTIVE_CONNECTION_STRINGS_BY_REPO = new HashMap<>();
|
||||
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 final String connectionString;
|
||||
private final Connection connection;
|
||||
@@ -76,12 +77,24 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
// get or create the connection,
|
||||
// reusing existing connections reduces the chance of locking the database during trivial queries
|
||||
this.connectionString = this.databaseType+":"+this.databaseLocation;
|
||||
if (!CONNECTIONS_BY_CONNECTION_STRING.containsKey(this.connectionString))
|
||||
|
||||
|
||||
this.connection = CONNECTIONS_BY_CONNECTION_STRING.computeIfAbsent(this.connectionString, (connectionString) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return DriverManager.getConnection(connectionString);
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
LOGGER.error("Unable to connect to database with the connection string: ["+connectionString+"]");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (this.connection == null)
|
||||
{
|
||||
Connection connection = DriverManager.getConnection(this.connectionString);
|
||||
CONNECTIONS_BY_CONNECTION_STRING.put(this.connectionString, connection);
|
||||
throw new SQLException("Unable to get repo with connection string ["+this.connectionString+"]");
|
||||
}
|
||||
this.connection = CONNECTIONS_BY_CONNECTION_STRING.get(this.connectionString);
|
||||
|
||||
ACTIVE_CONNECTION_STRINGS_BY_REPO.put(this, this.connectionString);
|
||||
|
||||
@@ -126,8 +139,9 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
{
|
||||
this.query(statement);
|
||||
}
|
||||
catch (DbConnectionClosedException ignored)
|
||||
catch (DbConnectionClosedException ignored)
|
||||
{
|
||||
LOGGER.warn("Attempted to insert ["+this.dtoClass.getSimpleName()+"] with primary key ["+(dto != null ? dto.getPrimaryKeyString() : "NULL")+"] on closed repo ["+this.connectionString+"].");
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
@@ -142,8 +156,9 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
{
|
||||
this.query(statement);
|
||||
}
|
||||
catch (DbConnectionClosedException ignored)
|
||||
catch (DbConnectionClosedException e)
|
||||
{
|
||||
LOGGER.warn("Attempted to update ["+this.dtoClass.getSimpleName()+"] with primary key ["+(dto != null ? dto.getPrimaryKeyString() : "NULL")+"] on closed repo ["+this.connectionString+"].");
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
@@ -223,12 +238,16 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
// SQL exceptions generally only happen when something is wrong with
|
||||
// the database or the query and should cause the system to blow up to notify the developer
|
||||
|
||||
if (e.toString().equals("database connection closed"))
|
||||
if (DbConnectionClosedException.IsClosedException(e))
|
||||
{
|
||||
throw new DbConnectionClosedException(e);
|
||||
|
||||
String message = "Unexpected Query error: ["+e.getMessage()+"], for prepared statement: ["+statement+"].";
|
||||
LOGGER.error(message);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
String message = "Unexpected Query error: [" + e.getMessage() + "], for prepared statement: [" + statement + "].";
|
||||
LOGGER.error(message);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
/** note: this can only handle 1 command at a time */
|
||||
@@ -250,12 +269,16 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
// SQL exceptions generally only happen when something is wrong with
|
||||
// the database or the query and should cause the system to blow up to notify the developer
|
||||
|
||||
if (e.toString().equals("database connection closed"))
|
||||
if (DbConnectionClosedException.IsClosedException(e))
|
||||
{
|
||||
throw new DbConnectionClosedException(e);
|
||||
|
||||
String message = "Unexpected Query error: ["+e.getMessage()+"], for script: ["+sql+"].";
|
||||
LOGGER.error(message);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
String message = "Unexpected Query error: [" + e.getMessage() + "], for script: [" + sql + "].";
|
||||
LOGGER.error(message);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
private List<Map<String, Object>> parseQueryResult(ResultSet resultSet, boolean resultSetPresent) throws SQLException
|
||||
@@ -278,7 +301,7 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
}
|
||||
|
||||
|
||||
public PreparedStatement createPreparedStatement(String sql)
|
||||
public PreparedStatement createPreparedStatement(String sql) throws DbConnectionClosedException
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -288,9 +311,19 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
}
|
||||
catch(SQLException e)
|
||||
{
|
||||
// SQL exceptions generally only happen when something is wrong with
|
||||
// the database or the query and should cause the system to blow up to notify the developer
|
||||
throw new RuntimeException(e);
|
||||
if (DbConnectionClosedException.IsClosedException(e))
|
||||
{
|
||||
throw new DbConnectionClosedException(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
// SQL exceptions generally only happen when something is wrong with
|
||||
// the database or the query and should cause the system to blow up to notify the developer
|
||||
|
||||
String message = "Unexpected error: [" + e.getMessage() + "], preparing statement: [" + sql + "].";
|
||||
LOGGER.error(message);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +359,7 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
{
|
||||
if(this.connection != null)
|
||||
{
|
||||
LOGGER.info("Closing database connection ["+this.connectionString+"]");
|
||||
CONNECTIONS_BY_CONNECTION_STRING.remove(this.connectionString);
|
||||
this.connection.close();
|
||||
}
|
||||
@@ -335,7 +369,7 @@ public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
|
||||
catch(SQLException e)
|
||||
{
|
||||
// connection close failed.
|
||||
Assert.fail("Unable to close the connection: " + e.getMessage());
|
||||
LOGGER.error("Unable to close the connection ["+this.connectionString+"], error: ["+e.getMessage()+"]");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-14
@@ -1,20 +1,26 @@
|
||||
package com.seibel.distanthorizons.core.sql;
|
||||
|
||||
public class DbConnectionClosedException extends Exception
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used to simplify handling when a database has been closed
|
||||
* since Java doesn't have a specific exception to handle closed databases
|
||||
*/
|
||||
public class DbConnectionClosedException extends SQLException
|
||||
{
|
||||
public DbConnectionClosedException() {
|
||||
super("The database connection is closed.");
|
||||
public DbConnectionClosedException() { super("The database connection is closed."); }
|
||||
public DbConnectionClosedException(String message) { super(message); }
|
||||
public DbConnectionClosedException(String message, Throwable cause) { super(message, cause); }
|
||||
public DbConnectionClosedException(Throwable cause) { super(cause); }
|
||||
|
||||
|
||||
// helper methods //
|
||||
|
||||
public static boolean IsClosedException(SQLException e)
|
||||
{
|
||||
// TODO long term we should prevent using repos that are closed, but for now this is the easier solution
|
||||
String message = e.getMessage().toLowerCase();
|
||||
return message.contains("connection closed") || message.contains("pointer is closed");
|
||||
}
|
||||
|
||||
public DbConnectionClosedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DbConnectionClosedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DbConnectionClosedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,15 +34,13 @@ import java.util.concurrent.*;
|
||||
* Handles thread pool creation.
|
||||
*
|
||||
* @see ThreadPools
|
||||
* @see TimerUtil
|
||||
*/
|
||||
public class ThreadUtil
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
public static String THREAD_NAME_PREFIX = ModInfo.THREAD_NAME_PREFIX;
|
||||
|
||||
public static int MINIMUM_RELATIVE_PRIORITY = -4;
|
||||
public static int DEFAULT_RELATIVE_PRIORITY = 0;
|
||||
public static final String THREAD_NAME_PREFIX = ModInfo.THREAD_NAME_PREFIX;
|
||||
|
||||
/** used to track and remove old listeners for certain pools if the thread pool is recreated. */
|
||||
private static final ConcurrentHashMap<String, ConfigChangeListener<Double>> THREAD_CHANGE_LISTENERS_BY_THREAD_NAME = new ConcurrentHashMap<>();
|
||||
@@ -51,7 +49,9 @@ public class ThreadUtil
|
||||
|
||||
|
||||
|
||||
// rate limited thread pool //
|
||||
//===================//
|
||||
// rate limited pool //
|
||||
//===================//
|
||||
|
||||
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, DhThreadFactory threadFactory, ConfigEntry<Double> runTimeRatioConfigEntry, Semaphore activeThreadCountSemaphore)
|
||||
{
|
||||
@@ -90,9 +90,12 @@ public class ThreadUtil
|
||||
}
|
||||
|
||||
|
||||
// thread pool executor //
|
||||
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name, int relativePriority)
|
||||
//===============//
|
||||
// standard pool //
|
||||
//===============//
|
||||
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name, int priority)
|
||||
{
|
||||
// this is what was being internally used by Executors.newFixedThreadPool
|
||||
// I'm just calling it explicitly here so we can reference the more feature-rich
|
||||
@@ -100,20 +103,23 @@ public class ThreadUtil
|
||||
return new ThreadPoolExecutor(/*corePoolSize*/ poolSize, /*maxPoolSize*/ poolSize,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new DhThreadFactory(name, Thread.NORM_PRIORITY + relativePriority));
|
||||
new DhThreadFactory(name, priority));
|
||||
}
|
||||
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz, int relativePriority) { return makeThreadPool(poolSize, clazz.getSimpleName(), relativePriority); }
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name) { return makeThreadPool(poolSize, name, DEFAULT_RELATIVE_PRIORITY); }
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz) { return makeThreadPool(poolSize, clazz.getSimpleName(), DEFAULT_RELATIVE_PRIORITY); }
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz, int priority) { return makeThreadPool(poolSize, clazz.getSimpleName(), priority); }
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name) { return makeThreadPool(poolSize, name, Thread.NORM_PRIORITY); }
|
||||
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz) { return makeThreadPool(poolSize, clazz.getSimpleName(), Thread.NORM_PRIORITY); }
|
||||
|
||||
|
||||
// single thread pool executor //
|
||||
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name, int relativePriority) { return makeThreadPool(1, name, relativePriority); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz, int relativePriority) { return makeThreadPool(1, clazz.getSimpleName(), relativePriority); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name) { return makeThreadPool(1, name, DEFAULT_RELATIVE_PRIORITY); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz) { return makeThreadPool(1, clazz.getSimpleName(), DEFAULT_RELATIVE_PRIORITY); }
|
||||
//====================//
|
||||
// single thread pool //
|
||||
//====================//
|
||||
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name, int priority) { return makeThreadPool(1, name, priority); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz, int priority) { return makeThreadPool(1, clazz.getSimpleName(), priority); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(String name) { return makeThreadPool(1, name, Thread.NORM_PRIORITY); }
|
||||
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz) { return makeThreadPool(1, clazz.getSimpleName(), Thread.NORM_PRIORITY); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.seibel.distanthorizons.core.util;
|
||||
|
||||
import java.util.Timer;
|
||||
|
||||
/**
|
||||
* Handles creating timers.
|
||||
* Used to prevent accidentally creating timers with the wrong format.
|
||||
*
|
||||
* @see ThreadUtil
|
||||
*/
|
||||
public class TimerUtil
|
||||
{
|
||||
|
||||
public static Timer CreateTimer(String timerName)
|
||||
{
|
||||
// isDaemon = true is necessary to allow MC to stop running even if the timer hasn't finished
|
||||
return new Timer(ThreadUtil.THREAD_NAME_PREFIX+timerName, true);
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -45,6 +45,11 @@ public class DhThreadFactory implements ThreadFactory
|
||||
private final LinkedList<WeakReference<Thread>> threads = new LinkedList<>();
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public DhThreadFactory(String newThreadName, int priority)
|
||||
{
|
||||
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY)
|
||||
@@ -65,6 +70,12 @@ public class DhThreadFactory implements ThreadFactory
|
||||
return thread;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// debugging //
|
||||
//===========//
|
||||
|
||||
private static String StackTraceToString(StackTraceElement[] stackTraceArray)
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
Reference in New Issue
Block a user