From ac8d93d5b95a90c92f95b11fe46fc49beb565aae Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:00:28 -0500 Subject: [PATCH] Ensure exceptions thrown in chunk load events are not dropped --- .../chunk_deadlock/ChunkMapLoadMixin.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ChunkMapLoadMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ChunkMapLoadMixin.java index 4986358b..0cacb879 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ChunkMapLoadMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/ChunkMapLoadMixin.java @@ -4,6 +4,8 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.datafixers.util.Either; +import net.minecraft.CrashReport; +import net.minecraft.ReportedException; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.util.thread.BlockableEventLoop; @@ -19,10 +21,12 @@ import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.lang.reflect.Field; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.function.Function; @@ -39,6 +43,9 @@ public abstract class ChunkMapLoadMixin { @Unique private static final ThreadLocal>> MFIX_SURROGATE_FUTURE = new ThreadLocal<>(); + @Unique + private final ConcurrentLinkedQueue mfix$promotionExceptions = new ConcurrentLinkedQueue<>(); + /** * @author embeddedt * @reason This redirect makes several changes to how full chunk promotion works. First of all, promotion runs @@ -72,7 +79,14 @@ public abstract class ChunkMapLoadMixin { } }, this.mainThreadExecutor).whenComplete((either, throwable) -> { if (throwable != null) { - surrogate.completeExceptionally(throwable); + if (!surrogate.isDone()) { + surrogate.completeExceptionally(throwable); + } else { + // The chunk has already become visible at FULL status, so we + // track the exception ourselves and manually rethrow it at the right point + // to trigger a server crash + this.mfix$promotionExceptions.add(throwable); + } } else { surrogate.complete(either); } @@ -95,6 +109,19 @@ public abstract class ChunkMapLoadMixin { } } + @Inject(method = "tick()V", at = @At("HEAD")) + private void reportDeferredPromotionException(CallbackInfo ci) { + var throwable = this.mfix$promotionExceptions.poll(); + if (throwable == null) { + return; + } + if (throwable instanceof ReportedException e) { + throw e; + } else { + throw new ReportedException(CrashReport.forThrowable(throwable, "Exception during promotion of chunk to FULL status")); + } + } + // we also preserve the legacy currentlyLoading field to keep Forge parity