201 lines
14 KiB
Java
201 lines
14 KiB
Java
package dev.modzuozhi;
|
|
|
|
import dev.modzuozhi.core.dimthread.DimThreadCore;
|
|
import dev.modzuozhi.core.compat.SableMainThreadBridge;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.neoforged.bus.api.SubscribeEvent;
|
|
import net.neoforged.fml.common.EventBusSubscriber;
|
|
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
|
import java.util.concurrent.*;
|
|
import java.util.concurrent.atomic.*;
|
|
|
|
@EventBusSubscriber(modid="dimensionalripper")
|
|
public final class PayloadRegression {
|
|
static boolean ran;
|
|
static void check(boolean value, String reason) { if(!value) throw new AssertionError(reason); }
|
|
@SubscribeEvent
|
|
public static void tick(ServerTickEvent.Post event) throws Exception {
|
|
var server=event.getServer();
|
|
if(ran || server.getTickCount()<20) return;
|
|
ran=true;
|
|
if(Boolean.getBoolean("dimensionalripper.reviewRegression")) {
|
|
try {ReviewRegression.run(server);} finally {DimThreadCore.MANAGER.setPaused(server,true);server.halt(false);}
|
|
return;
|
|
}
|
|
var manager=DimThreadCore.MANAGER; var level=server.overworld();
|
|
var release=new CountDownLatch(1); var entered=new CountDownLatch(1);
|
|
var failure=new AtomicReference<Throwable>();
|
|
try {
|
|
String idleStats=dev.modzuozhi.core.compat.PayloadDiagnostics.snapshot(server);
|
|
for(int i=0;i<100;i++) dev.modzuozhi.core.compat.PayloadMainThreadDispatch.drain(server);
|
|
check(idleStats.equals(dev.modzuozhi.core.compat.PayloadDiagnostics.snapshot(server)),"empty drain created payload barrier/statistics");
|
|
ModZuozhi.LOGGER.info("PAYLOAD IDLE PASS: 100 empty drains created no payload batches");
|
|
manager.setPaused(server,true); level.setChunkForced(0,0,true); level.getChunk(0,0);
|
|
var pos=new BlockPos(8,115,8); level.setBlockAndUpdate(pos,Blocks.CHEST.defaultBlockState());
|
|
var chest=level.getBlockEntity(pos); check(chest!=null,"fixture chest");
|
|
manager.getLoop(level).execute(() -> {
|
|
server.execute(release::countDown); entered.countDown();
|
|
try {
|
|
check(release.await(5,TimeUnit.SECONDS),"callback not pumped");
|
|
SableMainThreadBridge.runForLevel(level, () -> check(level.getBlockEntity(pos)==chest,"handoff lookup"));
|
|
} catch(Throwable e) { failure.set(e); }
|
|
});
|
|
manager.setPaused(server,false); check(entered.await(5,TimeUnit.SECONDS),"worker start");
|
|
check(level.getBlockEntity(pos)==null,"must reproduce guarded null before phase");
|
|
manager.runMainThreadPhase(server,"payload-test",() -> {
|
|
check(server.isSameThread() && level.getBlockEntity(pos)==chest,"exclusive lookup");
|
|
manager.runMainThreadPhase(server,"nested",() -> check(level.getBlockEntity(pos)==chest,"nested lookup"));
|
|
// Regression: a normal resume during exclusive handling must not drop its lease.
|
|
manager.setPaused(server,false);
|
|
manager.getLoop(level).scheduleIfNeeded();
|
|
try { Thread.sleep(100); } catch(InterruptedException e) { throw new RuntimeException(e); }
|
|
check(!manager.getLoop(level).isTicking() && level.getBlockEntity(pos)==chest,"menu resume broke exclusive lease");
|
|
});
|
|
check(failure.get()==null,"Sable callback failed: "+failure.get());
|
|
manager.setPaused(server,true);
|
|
manager.runMainThreadPhase(server,"pre-paused",() -> {});
|
|
check(manager.getLoop(level).isPaused(),"pre-pause lost");
|
|
var expected=new IllegalStateException("sentinel");
|
|
try { manager.runMainThreadPhase(server,"failure",() -> {throw expected;}); throw new AssertionError("swallowed"); }
|
|
catch(IllegalStateException e) {check(e==expected,"exception identity");}
|
|
check(manager.getLoop(level).isPaused(),"exception lost pre-pause");
|
|
ModZuozhi.LOGGER.info("PAYLOAD PHASE PASS");
|
|
entry(server, pos, chest);
|
|
} finally { release.countDown(); manager.setPaused(server,true); server.halt(false); }
|
|
}
|
|
static void entry(net.minecraft.server.MinecraftServer server, BlockPos pos,
|
|
net.minecraft.world.level.block.entity.BlockEntity chest) throws Exception {
|
|
var manager=DimThreadCore.MANAGER; var level=server.overworld();
|
|
var player=new net.neoforged.neoforge.common.util.FakePlayer(level,
|
|
new com.mojang.authlib.GameProfile(java.util.UUID.randomUUID(), "PayloadTest"));
|
|
var ctx=new net.neoforged.neoforge.network.handling.ServerPayloadContext(player.connection,
|
|
net.minecraft.resources.ResourceLocation.parse("dimensionalripper:test"));
|
|
var entered=new CountDownLatch(1); var release=new CountDownLatch(1);
|
|
manager.getLoop(level).execute(() -> {
|
|
server.execute(release::countDown); entered.countDown();
|
|
try { check(release.await(5,TimeUnit.SECONDS),"entry callback pump"); }
|
|
catch(InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); }
|
|
});
|
|
manager.setPaused(server,false); check(entered.await(5,TimeUnit.SECONDS),"entry worker start");
|
|
try {
|
|
var future=ctx.enqueueWork(() -> {
|
|
check(server.isSameThread(),"payload ran off main");
|
|
check(level.getBlockEntity(pos)==chest,"public payload entry returned guarded null");
|
|
return ctx.enqueueWork(() -> 42).join();
|
|
});
|
|
check(future.join()==42,"nested supplier result");
|
|
var expected=new IllegalArgumentException("payload sentinel");
|
|
try { ctx.enqueueWork((Runnable) () -> { throw expected; }); throw new AssertionError("main exception hidden"); }
|
|
catch(IllegalArgumentException e) { check(e==expected,"main exception identity"); }
|
|
ModZuozhi.LOGGER.info("PAYLOAD ENTRY PASS");
|
|
asyncEntry(server,ctx,player);
|
|
manager.setPaused(server,false);
|
|
var assembled=new AtomicInteger();
|
|
dev.modzuozhi.core.compat.SableAssemblyDispatch.dispatch(player,() -> {
|
|
check(server.isSameThread(),"legacy assembly not main");
|
|
check(manager.loopsSnapshot().values().stream().noneMatch(loop -> loop.isTicking()),"legacy assembly lacks global ownership");
|
|
assembled.incrementAndGet();
|
|
});
|
|
check(assembled.get()==1,"legacy dispatch did not use common phase");
|
|
manager.setPaused(server,true);
|
|
ModInteractionRegression.run(server,ctx);
|
|
SimulatedHandleRegression.run(server,ctx);
|
|
var lifecyclePlayer=new net.neoforged.neoforge.common.util.FakePlayer(level,
|
|
new com.mojang.authlib.GameProfile(java.util.UUID.randomUUID(),"LifecycleTest"));
|
|
lifecycle(server,new net.neoforged.neoforge.network.handling.ServerPayloadContext(lifecyclePlayer.connection,ctx.payloadId()),lifecyclePlayer);
|
|
} finally { release.countDown(); manager.setPaused(server,true); }
|
|
}
|
|
static void asyncEntry(net.minecraft.server.MinecraftServer server,
|
|
net.neoforged.neoforge.network.handling.ServerPayloadContext ctx,
|
|
net.minecraft.server.level.ServerPlayer player) throws Exception {
|
|
var seen=new java.util.ArrayList<Integer>();
|
|
var fs=new java.util.ArrayList<CompletableFuture<Integer>>();
|
|
Thread producer=new Thread(() -> {
|
|
for(int i=0;i<100;i++) {int n=i; fs.add(ctx.enqueueWork(() -> {check(server.isSameThread(),"async handler main");seen.add(n);return n;}));}
|
|
},"payload-network-fixture");
|
|
producer.start(); producer.join(5000); check(!producer.isAlive(),"network submit blocks");
|
|
check(seen.isEmpty(),"off-thread handler executed inline");
|
|
while(fs.stream().anyMatch(f -> !f.isDone())) dev.modzuozhi.core.compat.PayloadMainThreadDispatch.drain(server);
|
|
check(seen.size()==100,"async handler count");
|
|
for(int i=0;i<100;i++) check(seen.get(i)==i && fs.get(i).join()==i,"async FIFO/result");
|
|
var cancelled=new AtomicReference<CompletableFuture<Integer>>();var changed=new AtomicInteger();
|
|
Thread cancelProducer=new Thread(() -> cancelled.set(ctx.enqueueWork(changed::incrementAndGet)));
|
|
cancelProducer.start();cancelProducer.join(5000);cancelled.get().cancel(false);
|
|
dev.modzuozhi.core.compat.PayloadMainThreadDispatch.drain(server);
|
|
check(changed.get()==0,"cancelled public future still mutated world");
|
|
var configListener=new net.minecraft.server.network.ServerConfigurationPacketListenerImpl(server,
|
|
new net.minecraft.network.Connection(net.minecraft.network.protocol.PacketFlow.SERVERBOUND),
|
|
net.minecraft.server.network.CommonListenerCookie.createInitial(player.getGameProfile(),false));
|
|
var configCtx=new net.neoforged.neoforge.network.handling.ServerPayloadContext(configListener,ctx.payloadId());
|
|
check(configCtx.enqueueWork(() -> 17).join()==17,"configuration phase not preserved");
|
|
var guarded=new AtomicReference<CompletableFuture<Integer>>();
|
|
Thread failing=new Thread(() -> guarded.set(ctx.enqueueWork(() -> {throw new IllegalArgumentException("expected test failure");})));
|
|
failing.start();failing.join(5000);dev.modzuozhi.core.compat.PayloadMainThreadDispatch.drain(server);
|
|
check(guarded.get().join()==null,"NeoForge async handler failure contract changed");
|
|
var raw=new AtomicReference<CompletableFuture<Integer>>();
|
|
Thread stale=new Thread(() -> raw.set(dev.modzuozhi.core.compat.PayloadMainThreadDispatch.submit(player,ctx.payloadId(),() -> {throw new AssertionError("stale handler executed");})));
|
|
stale.start();stale.join(5000);
|
|
var old=player.serverLevel(); player.setServerLevel(server.getLevel(net.minecraft.world.level.Level.NETHER));
|
|
dev.modzuozhi.core.compat.PayloadMainThreadDispatch.drain(server);
|
|
check(raw.get().isCompletedExceptionally(),"stale future pending"); player.setServerLevel(old);
|
|
check(dev.modzuozhi.core.compat.PayloadDiagnostics.snapshot(server).contains("expired=1"),"stale work not counted separately");
|
|
var nested=new AtomicReference<Throwable>(); var entered=new CountDownLatch(1);
|
|
var manager=DimThreadCore.MANAGER;
|
|
manager.setPaused(server,true);
|
|
manager.getLoop(old).execute(() -> {
|
|
entered.countDown();
|
|
SableMainThreadBridge.runForLevel(old,() -> {
|
|
try {ctx.enqueueWork(() -> 1); nested.set(new AssertionError("unsafe handoff reentry accepted"));}
|
|
catch(IllegalStateException expected) {}
|
|
});
|
|
});
|
|
manager.setPaused(server,false);check(entered.await(5,TimeUnit.SECONDS),"handoff fixture start");
|
|
manager.runMainThreadPhase(server,"handoff-test",() -> {});
|
|
check(nested.get()==null,"handoff reentry result");
|
|
manager.setPaused(server,true);
|
|
var blockedFuture=new AtomicReference<CompletableFuture<Integer>>();var waitDetected=new AtomicBoolean();
|
|
var waiting=new CountDownLatch(1);
|
|
manager.getLoop(old).execute(() -> {
|
|
CompletableFuture<Integer> f=dev.modzuozhi.core.compat.PayloadMainThreadDispatch.submit(player,ctx.payloadId(),() -> {throw new AssertionError("cancelled worker wait action ran");});
|
|
blockedFuture.set(f);waiting.countDown();
|
|
try { f.get(250,TimeUnit.MILLISECONDS); }
|
|
catch(TimeoutException expected) {waitDetected.set(true);f.cancel(false);}
|
|
catch(Exception e) {throw new RuntimeException(e);}
|
|
});
|
|
manager.setPaused(server,false);check(waiting.await(5,TimeUnit.SECONDS),"worker wait fixture start");
|
|
manager.runMainThreadPhase(server,"unsupported-worker-wait",() -> {});
|
|
check(waitDetected.get() && blockedFuture.get().isCancelled(),"worker blocking cycle not detected");
|
|
manager.setPaused(server,true);
|
|
ModZuozhi.LOGGER.info("PAYLOAD ASYNC PASS");
|
|
}
|
|
static void lifecycle(net.minecraft.server.MinecraftServer server,
|
|
net.neoforged.neoforge.network.handling.ServerPayloadContext ctx,
|
|
net.minecraft.server.level.ServerPlayer player) throws Exception {
|
|
var manager=DimThreadCore.MANAGER; var other=server.getLevel(net.minecraft.world.level.Level.NETHER);
|
|
manager.setPaused(server,true); manager.removeLoop(other);
|
|
manager.runMainThreadPhase(server,"new-loop",() -> {
|
|
var created=manager.getLoop(other);created.scheduleIfNeeded();
|
|
check(created.isPaused() && !created.isTicking(),"new loop escaped phase");
|
|
});
|
|
manager.setPaused(server,true);
|
|
// Bounded ID labels, not an unbounded record for every packet.
|
|
for(int i=0;i<1000;i++) {
|
|
dev.modzuozhi.core.compat.PayloadMainThreadDispatch.submit(player,
|
|
net.minecraft.resources.ResourceLocation.parse("dimensionalripper:test_"+i),() -> 1).join();
|
|
}
|
|
String stats=dev.modzuozhi.core.compat.PayloadDiagnostics.snapshot(server);
|
|
check(stats.contains("ids=128") && stats.contains("other="),"diagnostics ID cap: "+stats);
|
|
check(stats.contains("pending=0"),"diagnostic pending count: "+stats);
|
|
var pending=new AtomicReference<CompletableFuture<Integer>>();
|
|
Thread t=new Thread(() -> pending.set(dev.modzuozhi.core.compat.PayloadMainThreadDispatch.submit(player,ctx.payloadId(),() -> {throw new AssertionError("work after stop");})));
|
|
t.start();t.join(5000);
|
|
manager.runMainThreadPhase(server,"stop-inside-phase",() -> dev.modzuozhi.core.compat.PayloadMainThreadDispatch.stop(server));
|
|
check(pending.get().isDone() && pending.get().isCompletedExceptionally(),"stop leaves future pending");
|
|
check(manager.getLoop(other).isPaused(),"stop restored worker");
|
|
check(dev.modzuozhi.core.compat.PayloadMainThreadDispatch.submit(player,ctx.payloadId(),() -> 1).isCompletedExceptionally(),"stop accepts new work");
|
|
ModZuozhi.LOGGER.info("PAYLOAD LIFECYCLE PASS");
|
|
ModZuozhi.LOGGER.info("PAYLOAD DIAGNOSTICS PASS");
|
|
}
|
|
}
|