mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Modded teardown fallback
- Closes engines, pools and scheduler a previous server left open when the next server starts - Flushes them at JVM exit once the stopping server thread has ended - Corrects the scheduler shutdown comment
This commit is contained in:
1 parent
ea516b4a59
commit
782a85c26a
3 files changed
+82
-1
No files matched your search
+58
@@ -79,6 +79,8 @@ import art.arcane.iris.spi.IrisServices;
|
|||||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public final class ModdedEngineBootstrap {
|
public final class ModdedEngineBootstrap {
|
||||||
private static final String[] CORE_SELF_TEST_CLASSES = {
|
private static final String[] CORE_SELF_TEST_CLASSES = {
|
||||||
@@ -87,12 +89,17 @@ public final class ModdedEngineBootstrap {
|
|||||||
"art.arcane.iris.pack.loading.IrisData"
|
"art.arcane.iris.pack.loading.IrisData"
|
||||||
};
|
};
|
||||||
private static final Object LOCK = new Object();
|
private static final Object LOCK = new Object();
|
||||||
|
private static final Object STOP_LOCK = new Object();
|
||||||
|
private static final AtomicBoolean EXIT_FLUSH_REGISTERED = new AtomicBoolean();
|
||||||
|
private static final long EXIT_FLUSH_SERVER_WAIT_SECONDS = 30L;
|
||||||
private static final ModdedServiceManager UNBOUND_SERVICE_MANAGER = new ModdedServiceManager();
|
private static final ModdedServiceManager UNBOUND_SERVICE_MANAGER = new ModdedServiceManager();
|
||||||
private static volatile NativeModdedLoader loader;
|
private static volatile NativeModdedLoader loader;
|
||||||
private static volatile BoundRuntime runtime;
|
private static volatile BoundRuntime runtime;
|
||||||
private static volatile NativeModdedServer currentServer;
|
private static volatile NativeModdedServer currentServer;
|
||||||
private static volatile NativeModdedServer spawnCaptureServer;
|
private static volatile NativeModdedServer spawnCaptureServer;
|
||||||
private static volatile boolean initialSpawnWasDefault;
|
private static volatile boolean initialSpawnWasDefault;
|
||||||
|
private static volatile boolean stoppedPending;
|
||||||
|
private static volatile Thread stoppingThread;
|
||||||
|
|
||||||
private ModdedEngineBootstrap() {
|
private ModdedEngineBootstrap() {
|
||||||
}
|
}
|
||||||
@@ -119,6 +126,8 @@ public final class ModdedEngineBootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void start(NativeModdedServer server) {
|
public static void start(NativeModdedServer server) {
|
||||||
|
finishPreviousServer("server start");
|
||||||
|
registerExitFlush();
|
||||||
captureInitialSpawn(server);
|
captureInitialSpawn(server);
|
||||||
currentServer = server;
|
currentServer = server;
|
||||||
bind();
|
bind();
|
||||||
@@ -150,6 +159,7 @@ public final class ModdedEngineBootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void serverAboutToStart(NativeModdedServer server) {
|
public static void serverAboutToStart(NativeModdedServer server) {
|
||||||
|
finishPreviousServer("server start");
|
||||||
captureInitialSpawn(server);
|
captureInitialSpawn(server);
|
||||||
ModdedStartup.prepareForStartup();
|
ModdedStartup.prepareForStartup();
|
||||||
}
|
}
|
||||||
@@ -188,6 +198,8 @@ public final class ModdedEngineBootstrap {
|
|||||||
|
|
||||||
public static void stop() {
|
public static void stop() {
|
||||||
NativeModdedServer stoppingServer = currentServer;
|
NativeModdedServer stoppingServer = currentServer;
|
||||||
|
stoppingThread = Thread.currentThread();
|
||||||
|
stoppedPending = true;
|
||||||
Throwable failure = null;
|
Throwable failure = null;
|
||||||
failure = runStopStage(failure, "pack downloads", IrisModdedCommands::shutdownDownloads);
|
failure = runStopStage(failure, "pack downloads", IrisModdedCommands::shutdownDownloads);
|
||||||
failure = runStopStage(failure, "world check", () -> ModdedWorldCheck.serverStopped(stoppingServer));
|
failure = runStopStage(failure, "world check", () -> ModdedWorldCheck.serverStopped(stoppingServer));
|
||||||
@@ -206,8 +218,54 @@ public final class ModdedEngineBootstrap {
|
|||||||
* The loaders still drain queued chunk generation after server stopping and only close levels afterwards, so
|
* The loaders still drain queued chunk generation after server stopping and only close levels afterwards, so
|
||||||
* engines, their mantle and generation history, and the generation pools stay open until the server stopped.
|
* engines, their mantle and generation history, and the generation pools stay open until the server stopped.
|
||||||
* Engines of unloaded levels are already closed by {@link #levelUnloaded}; this closes whatever remains.
|
* Engines of unloaded levels are already closed by {@link #levelUnloaded}; this closes whatever remains.
|
||||||
|
* Fabric fires it at the tail of stopServer, so a throw there skips it: the next server start or the JVM
|
||||||
|
* exit runs it instead.
|
||||||
*/
|
*/
|
||||||
public static void stopped() {
|
public static void stopped() {
|
||||||
|
synchronized (STOP_LOCK) {
|
||||||
|
stoppedPending = false;
|
||||||
|
stoppingThread = null;
|
||||||
|
closeStoppedServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void finishPreviousServer(String trigger) {
|
||||||
|
synchronized (STOP_LOCK) {
|
||||||
|
if (!stoppedPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ModdedIrisLog.warn("Iris closing the engines the previous server left open ({})", trigger);
|
||||||
|
stopped();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerExitFlush() {
|
||||||
|
if (EXIT_FLUSH_REGISTERED.compareAndSet(false, true)) {
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(ModdedEngineBootstrap::flushAtExit, "Iris Exit Flush"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void flushAtExit() {
|
||||||
|
if (!stoppedPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Thread serverThread = stoppingThread;
|
||||||
|
if (serverThread != null && serverThread != Thread.currentThread()) {
|
||||||
|
try {
|
||||||
|
serverThread.join(TimeUnit.SECONDS.toMillis(EXIT_FLUSH_SERVER_WAIT_SECONDS));
|
||||||
|
} catch (InterruptedException interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (serverThread.isAlive()) {
|
||||||
|
ModdedIrisLog.warn("Iris skipped its exit flush: the server thread is still stopping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finishPreviousServer("JVM exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void closeStoppedServer() {
|
||||||
Throwable failure = null;
|
Throwable failure = null;
|
||||||
failure = runStopStage(failure, "world engines", ModdedWorldEngines::shutdown);
|
failure = runStopStage(failure, "world engines", ModdedWorldEngines::shutdown);
|
||||||
failure = runStopStage(failure, "primary world router", ModdedPrimaryWorldRouter::clear);
|
failure = runStopStage(failure, "primary world router", ModdedPrimaryWorldRouter::clear);
|
||||||
|
|||||||
@@ -195,7 +195,8 @@ public final class ModdedScheduler implements PlatformScheduler {
|
|||||||
mainQueue.clear();
|
mainQueue.clear();
|
||||||
delayedQueue.clear();
|
delayedQueue.clear();
|
||||||
mainThread = null;
|
mainThread = null;
|
||||||
// Shutdown stage that always runs (ModdedEngineBootstrap.stopped): release the level snapshot with it.
|
// Runs from ModdedEngineBootstrap.stopped, or its fallback at the next server start or JVM exit when the
|
||||||
|
// loader skipped it: release the level snapshot with it.
|
||||||
ModdedServerLevels.forget();
|
ModdedServerLevels.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
@@ -4,6 +4,8 @@ import art.arcane.iris.configuration.IrisSettings;
|
|||||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.modded.command.IrisModdedCommands;
|
import art.arcane.iris.modded.command.IrisModdedCommands;
|
||||||
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||||
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeServerSpawn;
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.server.Bootstrap;
|
import net.minecraft.server.Bootstrap;
|
||||||
@@ -14,6 +16,8 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.MockedConstruction;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -23,6 +27,8 @@ import static org.junit.Assert.assertNotSame;
|
|||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockConstruction;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -70,6 +76,22 @@ public class ModdedEngineBootstrapShutdownTest {
|
|||||||
assertSame(Thread.currentThread(), MultiBurst.burst.submit(worker).get(10L, TimeUnit.SECONDS));
|
assertSame(Thread.currentThread(), MultiBurst.burst.submit(worker).get(10L, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nextServerStartClosesTheEnginesOfAServerThatNeverReachedStopped() {
|
||||||
|
NativeWorld level = level();
|
||||||
|
Engine engine = mock(Engine.class);
|
||||||
|
ModdedWorldEngines.installReplacement(level, engine);
|
||||||
|
ModdedEngineBootstrap.stop();
|
||||||
|
|
||||||
|
try (MockedStatic<ModdedStartup> startup = mockStatic(ModdedStartup.class);
|
||||||
|
MockedConstruction<NativeServerSpawn> spawn = mockConstruction(NativeServerSpawn.class)) {
|
||||||
|
ModdedEngineBootstrap.serverAboutToStart(mock(NativeModdedServer.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(engine).close();
|
||||||
|
assertFalse(ModdedWorldEngines.activeEngines().contains(engine));
|
||||||
|
}
|
||||||
|
|
||||||
private static NativeWorld level() {
|
private static NativeWorld level() {
|
||||||
ServerLevel serverLevel = mock(ServerLevel.class);
|
ServerLevel serverLevel = mock(ServerLevel.class);
|
||||||
ServerChunkCache chunks = mock(ServerChunkCache.class);
|
ServerChunkCache chunks = mock(ServerChunkCache.class);
|
||||||
|
|||||||
Reference in new issue
Block a user