Drained pregen before shutdown

- Server shutdown advances native chunk tasks while waiting for pregeneration
- Native world close owns chunk saves during server shutdown
- Pending plugin flushes stop waiting when native shutdown begins
- Late eviction completion cannot schedule another shutdown flush
- Regression tests cover owner waits and shutdown save ownership
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-06 18:20:26 -04:00
1 parent 319f8aed47
commit 083e689864
6 files changed
+420 -4

No files matched your search

@@ -208,6 +208,28 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource {
return shutdownAndWait(inst, timeoutMs);
}
public static boolean shutdownAndWait(long timeoutMs, Runnable processPendingTasks) {
Objects.requireNonNull(processPendingTasks, "processPendingTasks");
PregeneratorJob inst = instance.get();
if (inst == null) {
return false;
}
inst.requestStop();
long started = System.nanoTime();
long timeoutNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(1L, timeoutMs));
try {
while (inst.worker.isAlive() && System.nanoTime() - started < timeoutNanos) {
processPendingTasks.run();
inst.worker.join(1L);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while stopping the Iris pregenerator.", e);
}
return finishShutdown(inst, timeoutMs);
}
private static boolean shutdownAndWait(PregeneratorJob inst, long timeoutMs) {
inst.requestStop();
try {
@@ -216,6 +238,10 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while stopping the Iris pregenerator.", e);
}
return finishShutdown(inst, timeoutMs);
}
private static boolean finishShutdown(PregeneratorJob inst, long timeoutMs) {
if (inst.worker.isAlive()) {
throw new IllegalStateException("Timed out while stopping the Iris pregenerator after "
+ Math.max(1L, timeoutMs) + "ms.");
@@ -357,6 +357,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
}
private CompletableFuture<Void> evictRegion(long c) {
if (IrisToolbelt.isServerStopping()) {
return CompletableFuture.completedFuture(null);
}
if (!evictedRegions.add(c)) {
return CompletableFuture.completedFuture(null);
}
@@ -441,6 +444,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
}
private void flushAllRemainingChunks() {
if (IrisToolbelt.isServerStopping()) {
return;
}
List<Long> keys = new ArrayList<>(regionChunks.keySet());
List<CompletableFuture<Void>> evictions = new ArrayList<>(keys.size() + pendingEvictions.size());
for (Long regionKey : keys) {
@@ -453,10 +459,27 @@ public class AsyncPregenMethod implements PregeneratorMethod {
CompletableFuture<Void> settledEvictions = evictionsComplete.handle((ignored, failure) -> null);
CompletableFuture<Void> flush = foliaRuntime
? settledEvictions
: settledEvictions.thenCompose(ignored -> J.sfut(() -> INMS.get().flushChunkIO(world)));
: settledEvictions.thenCompose(ignored -> IrisToolbelt.isServerStopping()
? CompletableFuture.completedFuture(null)
: J.sfut(() -> INMS.get().flushChunkIO(world)));
long started = System.nanoTime();
long timeoutNanos = TimeUnit.SECONDS.toNanos(FLUSH_TIMEOUT_SECONDS);
try {
flush.get(FLUSH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
while (!IrisToolbelt.isServerStopping()) {
long remaining = timeoutNanos - (System.nanoTime() - started);
if (remaining <= 0L) {
throw new TimeoutException();
}
try {
flush.get(Math.min(remaining, TimeUnit.MILLISECONDS.toNanos(100L)), TimeUnit.NANOSECONDS);
return;
} catch (TimeoutException e) {
if (System.nanoTime() - started >= timeoutNanos) {
throw e;
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
IrisLogging.warn("Interrupted while flushing pregen chunks for " + world.getName() + ".");