From b82a59ecbc5bd070eec4f22f808dceb837514f5e Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 14 Nov 2025 07:46:02 -0600 Subject: [PATCH] Speed up shutdown and reduce logging --- .../V2/FullDataSourceProviderV2.java | 65 ++++++++++--------- .../V2/FullDataUpdatePropagatorV2.java | 25 +++---- .../fullDatafile/V2/FullDataUpdaterV2.java | 21 ++++-- .../core/generation/WorldGenerationQueue.java | 18 ++--- .../core/level/DhServerLevel.java | 1 - .../core/sql/repo/AbstractDhRepo.java | 2 +- .../util/threading/PriorityTaskPicker.java | 4 -- .../core/world/AbstractDhServerWorld.java | 23 ++++++- .../core/world/DhClientServerWorld.java | 24 ++++++- .../core/world/DhClientWorld.java | 25 +++++-- .../core/world/DhServerWorld.java | 2 +- 11 files changed, 129 insertions(+), 81 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java index 22040a43b..5c2207212 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; /** @@ -76,14 +77,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable public static final byte LEAF_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; - - protected final ReentrantLock closeLock = new ReentrantLock(); - protected volatile boolean isShutdown = false; - - protected final File saveDir; - public final FullDataSourceV2Repo repo; + + protected final AtomicBoolean isShutdownRef = new AtomicBoolean(false); + protected final File saveDir; protected final IDhLevel level; protected final String levelId; @@ -174,6 +172,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable */ public CompletableFuture getAsync(long pos) { + if (this.isShutdownRef.get()) + { + return CompletableFuture.completedFuture(null); + } + AbstractExecutorService executor = ThreadPoolUtil.getFileHandlerExecutor(); if (executor == null || executor.isTerminated()) { @@ -199,6 +202,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable @Nullable public FullDataSourceV2 get(long pos) { + if (this.isShutdownRef.get()) + { + return null; + } + try(FullDataSourceV2DTO dto = this.repo.getByKey(pos)) { if (dto == null) @@ -267,6 +275,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable */ public FullDataSourceV2 getAdjForDirection(long pos, EDhDirection direction) { + if (this.isShutdownRef.get()) + { + return null; + } + try(FullDataSourceV2DTO dto = this.repo.getAdjByPosAndDirection(pos, direction)) { if (dto == null) @@ -386,7 +399,14 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable @Nullable public Long getTimestampForPos(long pos) - { return this.repo.getTimestampForPos(pos); } + { + if (this.isShutdownRef.get()) + { + return null; + } + + return this.repo.getTimestampForPos(pos); + } @@ -416,28 +436,15 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable @Override public void close() { - try - { - LOGGER.debug("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.levelId + "]."); - - this.closeLock.lock(); - this.isShutdown = true; - - this.dataUpdater.close(); - this.updatePropagator.close(); - this.dataMigratorV1.close(); - - // 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); - - this.repo.close(); - } - catch (InterruptedException ignore) { } - finally - { - this.closeLock.unlock(); - } + LOGGER.debug("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.levelId + "]."); + + this.isShutdownRef.set(true); + + this.dataUpdater.close(); + this.updatePropagator.close(); + this.dataMigratorV1.close(); + + this.repo.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java index 0d783cdbe..7bc8698d7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java @@ -23,6 +23,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseable @@ -41,9 +42,6 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab { return NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); } - private final FullDataSourceProviderV2 provider; - private final FullDataUpdaterV2 dataUpdater; - /** * Tracks which positions are currently being updated @@ -59,9 +57,14 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab @Nullable public final ThreadPoolExecutor updateQueueProcessor; + private final AtomicBoolean isShutdownRef = new AtomicBoolean(false); private final String levelId; + private final FullDataSourceProviderV2 provider; + private final FullDataUpdaterV2 dataUpdater; + + //=============// // constructor // @@ -125,8 +128,6 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab LOGGER.error("Unexpected error in the parent update queue thread. Error: " + e.getMessage(), e); } } - - LOGGER.info("Update thread ["+Thread.currentThread().getName()+"] terminated."); } /** will always apply updates */ private void runParentUpdates(PriorityTaskPicker.Executor executor, DhBlockPos targetBlockPos) @@ -387,20 +388,10 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab @Override public void close() { - try + if (this.updateQueueProcessor != null) { - //LOGGER.info("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.levelId + "]."); - - if (this.updateQueueProcessor != null) - { - this.updateQueueProcessor.shutdownNow(); - } - - // 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); + this.updateQueueProcessor.shutdownNow(); } - catch (InterruptedException ignore) { } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdaterV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdaterV2.java index eefb76c3b..50246c292 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdaterV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdaterV2.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Set; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; @@ -26,10 +27,6 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable { private static final DhLogger LOGGER = new DhLoggerBuilder().build(); - - private final FullDataSourceProviderV2 provider; - - protected final PositionalLockProvider updateLockProvider = new PositionalLockProvider(); /** * generally just used for debugging, @@ -41,6 +38,9 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable public final ArrayList> dateSourceUpdateListeners = new ArrayList<>(); private final String levelId; + private final AtomicBoolean isShutdownRef = new AtomicBoolean(false); + + private final FullDataSourceProviderV2 provider; @@ -67,6 +67,11 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable */ public CompletableFuture updateDataSourceAsync(@NotNull FullDataSourceV2 inputDataSource) { + if (this.isShutdownRef.get()) + { + return CompletableFuture.completedFuture(null); + } + AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); if (executor == null || executor.isTerminated()) { @@ -104,6 +109,12 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable /** After this method returns the inputData will be written to file. */ public void updateDataSource(@NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos) { + if (this.isShutdownRef.get()) + { + return; + } + + long updatePos = inputData.getPos(); boolean methodLocked = false; @@ -231,7 +242,7 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable @Override public void close() { - //LOGGER.info("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.levelId + "]."); + this.isShutdownRef.set(true); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index bc502a521..1e668bc3f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -621,19 +621,13 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb LodUtil.assertTrue(this.generatorClosingFuture != null); - LOGGER.info("Awaiting world generator thread pool termination..."); - try + LOGGER.info("Shutting down world generator thread pool..."); + + AbstractExecutorService executor = ThreadPoolUtil.getWorldGenExecutor(); + if (executor != null) { - int waitTimeInSeconds = 3; - AbstractExecutorService executor = ThreadPoolUtil.getWorldGenExecutor(); - if (executor != null && !executor.awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS)) - { - LOGGER.warn("World generator thread pool shutdown didn't complete after [" + waitTimeInSeconds + "] seconds. Some world generator requests may still be running."); - } - } - catch (InterruptedException e) - { - LOGGER.warn("World generator thread pool shutdown interrupted! Ignoring child threads...", e); + List tasks = executor.shutdownNow(); + LOGGER.info("World generator thread pool shutdown with [" + tasks.size() + "] incomplete tasks."); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index e230f09c7..2539acd5d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -107,7 +107,6 @@ public class DhServerLevel extends AbstractDhServerLevel { super.close(); this.serverside.close(); - LOGGER.info("Closed DHLevel for ["+this.getLevelWrapper()+"]."); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java index 76bcfb536..c015ac836 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java @@ -369,7 +369,7 @@ public abstract class AbstractDhRepo> implemen if (DbConnectionClosedException.isClosedException(e)) { - throw new DbConnectionClosedException(e); + return new ArrayList<>(); } else { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java index c2ed4adae..e55fa8679 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/PriorityTaskPicker.java @@ -98,10 +98,6 @@ public class PriorityTaskPicker // Clear this executor's tasks since we no longer expect anything to execute. executor.taskQueue.clear(); } - else - { - throw e; - } } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhServerWorld.java index 68384b1be..9d2a160ca 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhServerWorld.java @@ -10,8 +10,10 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; public abstract class AbstractDhServerWorld extends AbstractDhWorld implements IDhServerWorld @@ -134,10 +136,9 @@ public abstract class AbstractDhServerWorld> closeFutures = new ArrayList<>(); for (TDhServerLevel level : this.dhLevelByLevelWrapper.values()) { - LOGGER.info("Unloading level [" + level.getLevelWrapper().getDhIdentifier() + "]."); - // level wrapper shouldn't be null, but just in case IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); if (serverLevelWrapper != null) @@ -145,7 +146,23 @@ public abstract class AbstractDhServerWorld closeFuture = new CompletableFuture<>(); + Thread closeThread = new Thread(() -> + { + level.close(); + closeFuture.complete(null); + }, "level shutdown"); + closeThread.start(); + closeFutures.add(closeFuture); + } + + // wait for all the levels to finish closing + for (CompletableFuture future : closeFutures) + { + future.join(); } this.dhLevelByLevelWrapper.clear(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index a0dc1ab30..ce9693d0a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -28,9 +28,11 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; public class DhClientServerWorld extends AbstractDhServerWorld implements IDhClientWorld @@ -143,13 +145,13 @@ public class DhClientServerWorld extends AbstractDhServerWorld> closeFutures = new ArrayList<>(); + synchronized (this.dhLevels) { // close each level for (DhClientServerLevel level : this.dhLevels) { - LOGGER.info("Unloading level [" + level.getServerLevelWrapper().getDhIdentifier() + "]."); - // level wrapper shouldn't be null, but just in case IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); if (serverLevelWrapper != null) @@ -157,10 +159,26 @@ public class DhClientServerWorld extends AbstractDhServerWorld closeFuture = new CompletableFuture<>(); + Thread closeThread = new Thread(() -> + { + level.close(); + closeFuture.complete(null); + }, "level shutdown"); + closeThread.start(); + closeFutures.add(closeFuture); } } + // wait for all the levels to finish closing + for (CompletableFuture future : closeFutures) + { + future.join(); + } + + this.dhLevelByLevelWrapper.clear(); this.eventLoop.close(); LOGGER.info("Closed DhWorld of type " + this.environment); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index 624c5d82d..7ed347e80 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -29,7 +29,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; @@ -127,14 +129,11 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public void close() { this.networkState.close(); - this.dhTickerThread.shutdownNow(); - + ArrayList> closeFutures = new ArrayList<>(); for (DhClientLevel dhClientLevel : this.levels.values()) { - LOGGER.info("Unloading level [" + dhClientLevel.getLevelWrapper().getDhIdentifier() + "]."); - // level wrapper shouldn't be null, but just in case IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper(); if (clientLevelWrapper != null) @@ -142,7 +141,23 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld clientLevelWrapper.onUnload(); } - dhClientLevel.close(); + + // close levels asynchronously to speed up + // shutdown on servers with a lot of levels + CompletableFuture closeFuture = new CompletableFuture<>(); + Thread closeThread = new Thread(() -> + { + dhClientLevel.close(); + closeFuture.complete(null); + }, "level shutdown"); + closeThread.start(); + closeFutures.add(closeFuture); + } + + // wait for all the levels to finish closing + for (CompletableFuture future : closeFutures) + { + future.join(); } this.levels.clear(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index d638f3b5a..9871be809 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -64,7 +64,7 @@ public class DhServerWorld extends AbstractDhServerWorld if (this.dhLevelByLevelWrapper.containsKey(wrapper)) { - LOGGER.info("Unloading level {} ", this.dhLevelByLevelWrapper.get(wrapper)); + DhServerLevel level = this.dhLevelByLevelWrapper.get(wrapper); wrapper.onUnload(); this.dhLevelByLevelWrapper.remove(wrapper).close(); }