82 lines
5.7 KiB
Java
82 lines
5.7 KiB
Java
package dev.modzuozhi;
|
|
|
|
import dev.modzuozhi.core.compat.*;
|
|
import dev.modzuozhi.core.dimthread.*;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import java.util.concurrent.*;
|
|
import java.util.concurrent.atomic.*;
|
|
import java.util.*;
|
|
|
|
public final class ReviewRegression {
|
|
static final List<String> failures=new ArrayList<>();
|
|
static void expect(boolean ok,String name){if(!ok){failures.add(name);ModZuozhi.LOGGER.error("REVIEW RED: {}",name);}}
|
|
public static void run(MinecraftServer server) throws Exception {
|
|
var manager=DimThreadCore.MANAGER;manager.setPaused(server,true);
|
|
var player=new net.neoforged.neoforge.common.util.FakePlayer(server.overworld(),
|
|
new com.mojang.authlib.GameProfile(UUID.randomUUID(),"ReviewTest"));
|
|
var disconnects=new AtomicInteger();
|
|
new net.minecraft.server.network.ServerGamePacketListenerImpl(server,
|
|
new net.minecraft.network.Connection(net.minecraft.network.protocol.PacketFlow.SERVERBOUND),player,
|
|
net.minecraft.server.network.CommonListenerCookie.createInitial(player.getGameProfile(),false)) {
|
|
@Override public void disconnect(net.minecraft.network.chat.Component reason){disconnects.incrementAndGet();}
|
|
};
|
|
var ctx=new net.neoforged.neoforge.network.handling.ServerPayloadContext(player.connection,ResourceLocation.parse("dimensionalripper:review"));
|
|
var result=new AtomicReference<CompletableFuture<Integer>>();
|
|
Thread rejected=new Thread(() -> result.set(ctx.enqueueWork(() -> {throw new RejectedExecutionException("handler executor closed");})));
|
|
rejected.start();rejected.join(5000);PayloadMainThreadDispatch.drain(server);PayloadMainThreadDispatch.drain(server);
|
|
expect(disconnects.get()==0,"handler rejection must not disconnect player");
|
|
try{expect(result.get().join()==null,"guard preserves handler rejection result");}
|
|
catch(CompletionException e){expect(false,"guard preserves handler rejection result");}
|
|
Thread cancelledHandler=new Thread(() -> result.set(ctx.enqueueWork(() -> {throw new CancellationException("handler cancelled its own computation");})));
|
|
cancelledHandler.start();cancelledHandler.join(5000);PayloadMainThreadDispatch.drain(server);
|
|
expect(result.get().join()==null,"guard preserves handler cancellation result");
|
|
var overflow=new AtomicReference<CompletableFuture<Integer>>();
|
|
Thread flood=new Thread(() -> {for(int i=0;i<257;i++)overflow.set(ctx.enqueueWork(() -> 1));});
|
|
flood.start();flood.join(5000);
|
|
expect(overflow.get().isCompletedExceptionally(),"capacity rejection remains exceptional");
|
|
PayloadMainThreadDispatch.drain(server);
|
|
expect(disconnects.get()==1,"genuine capacity rejection disconnects once");
|
|
for(int i=0;i<10;i++)PayloadMainThreadDispatch.drain(server);
|
|
String counters=PayloadDiagnostics.snapshot(server);
|
|
expect(counters.contains("failed=2") && counters.contains("rejected=1"),
|
|
"diagnostics distinguish handler failures from capacity rejection");
|
|
|
|
var other=server.getLevel(net.minecraft.world.level.Level.NETHER);
|
|
manager.removeLoop(other);
|
|
manager.runMainThreadPhase(server,"review-new-loop",() -> manager.getLoop(other).scheduleIfNeeded());
|
|
expect(manager.getLoop(other).isPaused(),"new loop inherits menu pause after phase exit");
|
|
manager.setPaused(server,true);
|
|
|
|
var loop=manager.getLoop(other);var entered=new CountDownLatch(1);var release=new CountDownLatch(1);
|
|
var finished=new AtomicBoolean();
|
|
loop.execute(() -> {
|
|
server.execute(() -> manager.removeLoop(other));entered.countDown();
|
|
while(release.getCount()!=0) try{release.await();}catch(InterruptedException ignored){}
|
|
finished.set(true);
|
|
});
|
|
manager.setPaused(server,false);PayloadRegression.check(entered.await(5,TimeUnit.SECONDS),"retiring loop fixture start");
|
|
Thread releaser=new Thread(() -> {try{Thread.sleep(250);}catch(InterruptedException ignored){}release.countDown();});
|
|
releaser.start();
|
|
try{manager.runMainThreadPhase(server,"review-retire",() -> expect(finished.get() && !loop.isTicking(),"retired loop must drain before exclusive phase"));}
|
|
finally{release.countDown();releaser.join(5000);}
|
|
manager.setPaused(server,true);
|
|
|
|
var labelled=new CountDownLatch(1);var labelRelease=new CountDownLatch(1);
|
|
var late=new AtomicReference<CompletableFuture<Integer>>();
|
|
Thread submitter=new Thread(() -> {
|
|
labelled.countDown();try{labelRelease.await(5,TimeUnit.SECONDS);}catch(InterruptedException e){throw new RuntimeException(e);}
|
|
// Exact late-registration boundary after the submitter had obtained its queue.
|
|
var ticket=PayloadDiagnostics.begin(server,"dimensionalripper:shutdown_race");
|
|
PayloadDiagnostics.end(server,ticket,new CancellationException("stopped"));
|
|
late.set(PayloadMainThreadDispatch.submit(player,ctx.payloadId(),() -> {throw new AssertionError("late action");}));
|
|
});
|
|
submitter.start();PayloadRegression.check(labelled.await(5,TimeUnit.SECONDS),"late submit fixture pause");
|
|
PayloadMainThreadDispatch.stop(server);PayloadMainThreadDispatch.clear(server);labelRelease.countDown();submitter.join(5000);
|
|
expect(late.get()!=null && late.get().isCompletedExceptionally(),"late future completed");
|
|
expect(PayloadDiagnostics.snapshot(server).equals("payloads: pending=0 batches=0"),"late submission must not recreate diagnostics after clear");
|
|
if(!failures.isEmpty())throw new AssertionError(String.join("; ",failures));
|
|
ModZuozhi.LOGGER.info("PAYLOAD REVIEW PASS");
|
|
}
|
|
}
|