Completed shutdown retry ownership

- Retain generation stages and world services until shutdown completes
- Preserve failed runtime cleanup across resource hash changes
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-06 04:09:34 -04:00
commit 0bfb4d6718
6 files changed
+230 -14

No files matched your search

@@ -45,7 +45,8 @@ final class EngineShutdownSequence {
private final IrisEngine engine;
private final Set<RuntimeAssembly> incompleteAssemblies = ConcurrentHashMap.newKeySet();
private final Set<EngineRuntime> unpublishedRuntimes = ConcurrentHashMap.newKeySet();
private final Set<EngineRuntime> unpublishedRuntimes = Collections.synchronizedSet(
Collections.newSetFromMap(new IdentityHashMap<>()));
private final Set<EngineTarget> incompleteTargets = Collections.synchronizedSet(
Collections.newSetFromMap(new IdentityHashMap<>()));
private boolean runtimeReleased;
@@ -267,7 +268,7 @@ final class EngineShutdownSequence {
return true;
}
}
for (EngineRuntime runtime : unpublishedRuntimes) {
for (EngineRuntime runtime : unpublishedRuntimes.toArray(new EngineRuntime[0])) {
if (runtime.generation().data() == data) {
return true;
}
@@ -351,7 +352,7 @@ final class EngineShutdownSequence {
new IllegalStateException("Iris runtime assembly still owns the detached target."));
}
}
for (EngineRuntime runtime : unpublishedRuntimes) {
for (EngineRuntime runtime : unpublishedRuntimes.toArray(new EngineRuntime[0])) {
if (runtime.generation().target() == target) {
return appendFailure(failure,
new IllegalStateException("Iris unpublished runtime still owns the detached target."));
@@ -277,26 +277,26 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
}
if (!looperStopped) {
try {
looperStopped = true;
if (looper != null) {
looper.interrupt();
joinQuietly(looper);
awaitLooper(looper);
}
looperStopped = true;
} catch (Throwable e) {
failure = appendCloseFailure(failure, e);
}
}
if (!cleanupServiceStopped) {
try {
cleanupServiceStopped = true;
if (cleanupService != null) {
for (Future<?> future : cleanup.values()) {
future.cancel(false);
}
cleanup.clear();
cleanupService.shutdownNow();
awaitQuietly(cleanupService);
awaitCleanupService(cleanupService);
}
cleanupServiceStopped = true;
} catch (Throwable e) {
failure = appendCloseFailure(failure, e);
}
@@ -306,28 +306,30 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
}
}
private void joinQuietly(Thread thread) {
private void awaitLooper(Thread thread) {
if (thread == Thread.currentThread()) {
return;
throw new IllegalStateException("Cannot finish world manager shutdown from its own loop thread.");
}
try {
thread.join(CLOSE_AWAIT_MS);
if (thread.isAlive()) {
IrisLogging.warn("Thread " + thread.getName() + " did not stop within " + CLOSE_AWAIT_MS + "ms.");
throw new IllegalStateException("Thread " + thread.getName() + " did not stop within " + CLOSE_AWAIT_MS + "ms.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while waiting for the world manager loop to stop.", e);
}
}
private void awaitQuietly(ScheduledExecutorService service) {
private void awaitCleanupService(ScheduledExecutorService service) {
try {
if (!service.awaitTermination(CLOSE_AWAIT_MS, TimeUnit.MILLISECONDS)) {
IrisLogging.warn("Mantle cleanup executor did not stop within " + CLOSE_AWAIT_MS + "ms.");
throw new IllegalStateException("Mantle cleanup executor did not stop within " + CLOSE_AWAIT_MS + "ms.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while waiting for mantle cleanup to stop.", e);
}
}
@@ -42,11 +42,11 @@ public abstract class IrisEngineMode implements EngineMode {
return;
}
closed = true;
dump();
terrainStages.forEach(EngineStage::close);
terrainStages.clear();
transitionStage.close();
closed = true;
}
@Override