From 3b56f00b825d9da1ccb935c585c6ede04287460c Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 12 Apr 2023 14:42:11 -0400 Subject: [PATCH 1/5] Fix world load freezing if mods access the previous server world --- .../core/config/ModernFixEarlyConfig.java | 1 + .../chunk_deadlock/ServerChunkCacheMixin.java | 32 +++++++++++++++++++ src/main/resources/modernfix.mixins.json | 1 + 3 files changed, 34 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 9bf69c7e..d4f7a59c 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -49,6 +49,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("bugfix.packet_leak", false); this.addMixinRule("bugfix.structure_manager_crash", true); this.addMixinRule("bugfix.mc218112", true); + this.addMixinRule("bugfix.chunk_deadlock", true); this.addMixinRule("bugfix.tf_cme_on_load", modPresent("twilightforest")); this.addMixinRule("bugfix.refinedstorage", modPresent("refinedstorage")); this.addMixinRule("perf.async_jei", modPresent("jei")); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java new file mode 100644 index 00000000..9ebcac80 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java @@ -0,0 +1,32 @@ +package org.embeddedt.modernfix.mixin.bugfix.chunk_deadlock; + +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.EmptyLevelChunk; +import org.embeddedt.modernfix.ModernFix; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ServerChunkCache.class, priority = 1100) +public abstract class ServerChunkCacheMixin { + @Shadow @Final private Thread mainThread; + @Shadow @Final public ServerLevel level; + private final boolean debugDeadServerAccess = Boolean.getBoolean("modernfix.debugBadChunkloading"); + @Inject(method = "getChunk", at = @At("HEAD"), cancellable = true) + private void bailIfServerDead(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable cir) { + if(!this.mainThread.isAlive()) { + ModernFix.LOGGER.fatal("A mod is accessing chunks from a stopped server (this will also cause memory leaks)"); + if(debugDeadServerAccess) { + new Exception().printStackTrace(); + } + cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ))); + } + } +} diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index ae37e36b..78067ad7 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -15,6 +15,7 @@ "bugfix.refinedstorage.te_bug.FluidExternalStorageMixin", "bugfix.refinedstorage.te_bug.ItemExternalStorageCacheMixin", "bugfix.refinedstorage.te_bug.ItemExternalStorageMixin", + "bugfix.chunk_deadlock.ServerChunkCacheMixin", "perf.remove_biome_temperature_cache.BiomeMixin", "perf.resourcepacks.ModFileResourcePackMixin", "perf.resourcepacks.VanillaPackMixin", From 10149e9f87bd59ea2791f8f898334c7b148d9275 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:05:08 -0400 Subject: [PATCH 2/5] Add integrated server watchdog --- .../embeddedt/modernfix/ModernFixClient.java | 13 ++++ .../core/config/ModernFixConfig.java | 4 ++ .../modernfix/world/IntegratedWatchdog.java | 61 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java diff --git a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index 3247ea38..a81291e2 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -20,11 +20,15 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.event.server.FMLServerStartedEvent; +import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.network.NetworkEvent; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.embeddedt.modernfix.core.config.ModernFixConfig; import org.embeddedt.modernfix.load.LoadEvents; import org.embeddedt.modernfix.packet.EntityIDSyncPacket; import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen; +import org.embeddedt.modernfix.world.IntegratedWatchdog; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; @@ -219,4 +223,13 @@ public class ModernFixClient { context.get().setPacketHandled(true); } + + @SubscribeEvent + public void onServerStarted(FMLServerStartingEvent event) { + if(ModernFixConfig.INTEGRATED_SERVER_WATCHDOG.get()) { + IntegratedWatchdog watchdog = new IntegratedWatchdog(event.getServer()); + watchdog.start(); + } + + } } diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java index 92500015..56142410 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java @@ -26,6 +26,7 @@ public class ModernFixConfig { public static ForgeConfigSpec.BooleanValue ENABLE_DEBUG_RELOADER; public static ForgeConfigSpec.BooleanValue REBUILD_BLOCKSTATES_ASYNC; + public static ForgeConfigSpec.BooleanValue INTEGRATED_SERVER_WATCHDOG; public static Set jeiPluginBlacklist; @@ -43,6 +44,9 @@ public class ModernFixConfig { REBUILD_BLOCKSTATES_ASYNC = COMMON_BUILDER .comment("Rebuild blockstate cache asynchronously. Should work with most mods, but can be disabled.") .define("rebuild_blockstate_cache_async", true); + INTEGRATED_SERVER_WATCHDOG = COMMON_BUILDER + .comment("Automatically output a thread dump if the integrated server spends too long on one tick") + .define("integrated_server_watchdog", true); } static { diff --git a/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java new file mode 100644 index 00000000..d2d8b4df --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java @@ -0,0 +1,61 @@ +package org.embeddedt.modernfix.world; + +import net.minecraft.DefaultUncaughtExceptionHandlerWithName; +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.concurrent.TimeUnit; + +public class IntegratedWatchdog extends Thread { + private static final Logger LOGGER = LogManager.getLogger(); + + private final MinecraftServer server; + + private static final long MAX_TICK_DELTA = 40*1000; + + public IntegratedWatchdog(MinecraftServer server) { + this.server = server; + this.setDaemon(true); + this.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER)); + this.setName("ModernFix integrated server watchdog"); + } + + public void run() { + while(server.isRunning()) { + long nextTick = this.server.getNextTickTime(); + long curTime = Util.getMillis(); + if((curTime - nextTick) > MAX_TICK_DELTA) { + LOGGER.error("A single server tick has taken {}, more than {} milliseconds", (nextTick - curTime), MAX_TICK_DELTA); + ThreadMXBean threadmxbean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] athreadinfo = threadmxbean.dumpAllThreads(true, true); + StringBuilder sb = new StringBuilder(); + sb.append("Thread Dump:\n"); + for(ThreadInfo threadinfo : athreadinfo) { + sb.append(threadinfo); + StackTraceElement[] elements = threadinfo.getStackTrace(); + if(elements.length > 8) { + sb.append("extended trace:\n"); + for(int i = 8; i < elements.length; i++) { + sb.append("\tat "); + sb.append(elements[i]); + sb.append('\n'); + } + } + sb.append('\n'); + } + LOGGER.error(sb); + nextTick = 0; + curTime = 0; + } + try { + Thread.sleep(nextTick + MAX_TICK_DELTA - curTime); + } catch(InterruptedException ignored) { + } + } + } +} From e977fcdfcebcbd12c87bafdd7ec2c39f0ab294cb Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 12 Apr 2023 19:06:40 -0400 Subject: [PATCH 3/5] Improved chunk deadlock detection system + patch Valhelsia Structures when installed --- build.gradle | 1 + .../modernfix/core/ModernFixMixinPlugin.java | 8 +++++ .../core/config/ModernFixEarlyConfig.java | 1 + .../chunk_deadlock/ServerChunkCacheMixin.java | 36 +++++++++++++++++++ .../valhesia/BlockStateBaseMixin.java | 28 +++++++++++++++ .../modernfix/world/IntegratedWatchdog.java | 5 +-- .../resources/META-INF/accesstransformer.cfg | 3 +- src/main/resources/modernfix.mixins.json | 1 + 8 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/valhesia/BlockStateBaseMixin.java diff --git a/build.gradle b/build.gradle index cc83e53a..80727ea5 100644 --- a/build.gradle +++ b/build.gradle @@ -98,6 +98,7 @@ dependencies { modRuntimeOnly("curse.maven:ferritecore-429235:4074330") modCompileOnly("team.chisel.ctm:CTM:${ctm_version}") modCompileOnly("curse.maven:supermartijncore-454372:4455378") + modCompileOnly("curse.maven:valhesiastructures-347488:3476252") } tasks.withType(JavaCompile) { diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index be713fa6..3ce635a5 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -251,6 +251,14 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { } } } + } else if(mixinClassName.equals("org.embeddedt.modernfix.mixin.bugfix.chunk_deadlock.valhesia.BlockStateBaseMixin")) { + // We need to destroy Valhelsia's callback so it can never run getBlockState + for(MethodNode m : targetClass.methods) { + if(m.name.contains("valhelsia_placeDousedTorch")) { + m.instructions.clear(); + m.instructions.add(new InsnNode(Opcodes.RETURN)); + } + } } } } \ No newline at end of file diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index d4f7a59c..d3cbb02d 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -50,6 +50,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("bugfix.structure_manager_crash", true); this.addMixinRule("bugfix.mc218112", true); this.addMixinRule("bugfix.chunk_deadlock", true); + this.addMixinRule("bugfix.chunk_deadlock.valhesia", modPresent("valhelsia_structures")); this.addMixinRule("bugfix.tf_cme_on_load", modPresent("twilightforest")); this.addMixinRule("bugfix.refinedstorage", modPresent("refinedstorage")); this.addMixinRule("perf.async_jei", modPresent("jei")); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java index 9ebcac80..4b675a62 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/ServerChunkCacheMixin.java @@ -1,5 +1,7 @@ package org.embeddedt.modernfix.mixin.bugfix.chunk_deadlock; +import com.mojang.datafixers.util.Either; +import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; @@ -13,12 +15,18 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.concurrent.*; @Mixin(value = ServerChunkCache.class, priority = 1100) public abstract class ServerChunkCacheMixin { @Shadow @Final private Thread mainThread; @Shadow @Final public ServerLevel level; + + @Shadow protected abstract CompletableFuture> getChunkFutureMainThread(int k, int l, ChunkStatus arg, boolean bl); + + @Shadow @Final private ServerChunkCache.MainThreadExecutor mainThreadProcessor; private final boolean debugDeadServerAccess = Boolean.getBoolean("modernfix.debugBadChunkloading"); + @Inject(method = "getChunk", at = @At("HEAD"), cancellable = true) private void bailIfServerDead(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable cir) { if(!this.mainThread.isAlive()) { @@ -27,6 +35,34 @@ public abstract class ServerChunkCacheMixin { new Exception().printStackTrace(); } cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ))); + } else if(Thread.currentThread() != this.mainThread) { + CompletableFuture> future = CompletableFuture.supplyAsync(() -> this.getChunkFutureMainThread(chunkX, chunkZ, requiredStatus, false), this.mainThreadProcessor).join(); + if(!future.isDone()) { + // Wait at least 500 milliseconds before printing anything + Either resultingChunk = null; + try { + resultingChunk = future.get(500, TimeUnit.MILLISECONDS); + } catch(InterruptedException | ExecutionException | TimeoutException ignored) { + } + if(resultingChunk != null && resultingChunk.left().isPresent()) { + cir.setReturnValue(resultingChunk.left().get()); + return; + } + if(debugDeadServerAccess) + ModernFix.LOGGER.warn("Async loading of a chunk was requested, this might not be desirable", new Exception()); + else + ModernFix.LOGGER.warn("Suspicious async chunkload, pass -Dmodernfix.debugBadChunkloading=true for more details"); + try { + resultingChunk = future.get(10, TimeUnit.SECONDS); + if(resultingChunk.left().isPresent()) { + cir.setReturnValue(resultingChunk.left().get()); + return; + } + } catch(InterruptedException | ExecutionException | TimeoutException e) { + ModernFix.LOGGER.error("Async chunk load took way too long, this needs to be reported to the appropriate mod.", e); + } + //cir.setReturnValue(new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ))); + } } } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/valhesia/BlockStateBaseMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/valhesia/BlockStateBaseMixin.java new file mode 100644 index 00000000..b66d9ed4 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/chunk_deadlock/valhesia/BlockStateBaseMixin.java @@ -0,0 +1,28 @@ +package org.embeddedt.modernfix.mixin.bugfix.chunk_deadlock.valhesia; + +import com.stal111.valhelsia_structures.init.ModBlocks; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = BlockBehaviour.BlockStateBase.class, priority = 900) +public abstract class BlockStateBaseMixin { + @Shadow public abstract Block getBlock(); + + /** + * Do not call getBlockState here; this can cause deadlocks during world gen/lighting. + */ + @Inject(method = "getOffset", at = @At("HEAD"), cancellable = true) + private void useThisBlock(BlockGetter getter, BlockPos pos, CallbackInfoReturnable cir) { + if(this.getBlock() == ModBlocks.BONE_PILE.get()) + cir.setReturnValue(new Vec3(0.0, -0.46875, 0.0)); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java index d2d8b4df..643712ed 100644 --- a/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java +++ b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java @@ -29,8 +29,9 @@ public class IntegratedWatchdog extends Thread { while(server.isRunning()) { long nextTick = this.server.getNextTickTime(); long curTime = Util.getMillis(); - if((curTime - nextTick) > MAX_TICK_DELTA) { - LOGGER.error("A single server tick has taken {}, more than {} milliseconds", (nextTick - curTime), MAX_TICK_DELTA); + long delta = curTime - nextTick; + if(delta > MAX_TICK_DELTA) { + LOGGER.error("A single server tick has taken {}, more than {} milliseconds", delta, MAX_TICK_DELTA); ThreadMXBean threadmxbean = ManagementFactory.getThreadMXBean(); ThreadInfo[] athreadinfo = threadmxbean.dumpAllThreads(true, true); StringBuilder sb = new StringBuilder(); diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 6c8b87f3..a12ea313 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -22,4 +22,5 @@ public net.minecraft.block.AbstractBlock$Properties field_235817_s_ # isViewBloc public net.minecraft.block.AbstractBlock$Properties field_235818_t_ # hasPostProcess public net.minecraft.block.AbstractBlock$Properties field_235819_u_ # emissiveRendering public net.minecraft.block.AbstractBlock$Properties field_235806_h_ # requiresCorrectToolForDrops -public net.minecraft.block.AbstractBlock$Properties field_200959_g # destroyTime \ No newline at end of file +public net.minecraft.block.AbstractBlock$Properties field_200959_g # destroyTime +public net.minecraft.world.server.ServerChunkProvider$ChunkExecutor \ No newline at end of file diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 78067ad7..a0e39552 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -16,6 +16,7 @@ "bugfix.refinedstorage.te_bug.ItemExternalStorageCacheMixin", "bugfix.refinedstorage.te_bug.ItemExternalStorageMixin", "bugfix.chunk_deadlock.ServerChunkCacheMixin", + "bugfix.chunk_deadlock.valhesia.BlockStateBaseMixin", "perf.remove_biome_temperature_cache.BiomeMixin", "perf.resourcepacks.ModFileResourcePackMixin", "perf.resourcepacks.VanillaPackMixin", From 9443c41273ffd4232032c833714199d9f52b4e90 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 13 Apr 2023 12:01:41 -0400 Subject: [PATCH 4/5] Optimize access transformers --- .../FastAccessTransformerList.java | 132 ++++++++++++++++++ .../modernfix/core/ModernFixMixinPlugin.java | 3 + 2 files changed, 135 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/classloading/FastAccessTransformerList.java diff --git a/src/main/java/org/embeddedt/modernfix/classloading/FastAccessTransformerList.java b/src/main/java/org/embeddedt/modernfix/classloading/FastAccessTransformerList.java new file mode 100644 index 00000000..21717f24 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/classloading/FastAccessTransformerList.java @@ -0,0 +1,132 @@ +package org.embeddedt.modernfix.classloading; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraftforge.accesstransformer.AccessTransformer; +import net.minecraftforge.accesstransformer.AccessTransformerEngine; +import net.minecraftforge.accesstransformer.INameHandler; +import net.minecraftforge.accesstransformer.Target; +import net.minecraftforge.accesstransformer.parser.AccessTransformerList; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import org.objectweb.asm.Type; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public class FastAccessTransformerList extends AccessTransformerList { + private FastATMap accessTransformerMap; + + public static void attemptReplace() { + AccessTransformerList masterList; + FastAccessTransformerList myList = new FastAccessTransformerList(); + try { + Field master = AccessTransformerEngine.class.getDeclaredField("masterList"); + master.setAccessible(true); + masterList = (AccessTransformerList)master.get(AccessTransformerEngine.INSTANCE); + Field transfomersMap = AccessTransformerList.class.getDeclaredField("accessTransformers"); + transfomersMap.setAccessible(true); + Map, AccessTransformer> map = (Map, AccessTransformer>)transfomersMap.get(masterList); + INameHandler nameHandler = ObfuscationReflectionHelper.getPrivateValue(AccessTransformerList.class, masterList, "nameHandler"); + myList.setNameHandler(nameHandler); + myList.accessTransformerMap = new FastATMap(map); + ObfuscationReflectionHelper.setPrivateValue(AccessTransformerList.class, myList, myList.accessTransformerMap, "accessTransformers"); + master.set(AccessTransformerEngine.INSTANCE, myList); + } catch(ReflectiveOperationException e) { + e.printStackTrace(); + } + } + + @Override + public boolean containsClassTarget(Type type) { + return this.accessTransformerMap.containsType(type); + } + + private static class FastATMap implements Map, AccessTransformer> { + private final Map, AccessTransformer> delegate; + private final Set allContainedTypes; + + public FastATMap(Map, AccessTransformer> delegate) { + this.delegate = delegate; + this.allContainedTypes = new ObjectOpenHashSet<>(); + } + + @Override + public int size() { + return this.delegate.size(); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object o) { + return this.delegate.containsKey(o); + } + + @Override + public boolean containsValue(Object o) { + return this.delegate.containsValue(o); + } + + @Override + public AccessTransformer get(Object o) { + return this.delegate.get(o); + } + + @Nullable + @Override + public AccessTransformer put(Target target, AccessTransformer accessTransformer) { + this.allContainedTypes.add(target.getASMType()); + return this.delegate.put(target, accessTransformer); + } + + @Override + public AccessTransformer remove(Object o) { + if(o instanceof Target) { + this.allContainedTypes.remove(((Target)o).getASMType()); + } + return this.delegate.remove(o); + } + + @Override + public void putAll(@NotNull Map, ? extends AccessTransformer> map) { + for(Target key : map.keySet()) { + this.allContainedTypes.add(key.getASMType()); + } + this.delegate.putAll(map); + } + + @Override + public void clear() { + this.allContainedTypes.clear(); + this.delegate.clear(); + } + + @NotNull + @Override + public Set> keySet() { + return this.delegate.keySet(); + } + + @NotNull + @Override + public Collection values() { + return this.delegate.values(); + } + + @NotNull + @Override + public Set, AccessTransformer>> entrySet() { + return this.delegate.entrySet(); + } + + public boolean containsType(Type type) { + return this.allContainedTypes.contains(type); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index 3ce635a5..c6d2a883 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -9,6 +9,7 @@ import net.minecraftforge.fml.common.ObfuscationReflectionHelper; import net.minecraftforge.fml.loading.LoadingModList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.embeddedt.modernfix.classloading.FastAccessTransformerList; import org.embeddedt.modernfix.classloading.ModernFixResourceFinder; import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig; import org.embeddedt.modernfix.core.config.Option; @@ -81,6 +82,8 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { logger.error("Failed to make classloading changes", e); } + FastAccessTransformerList.attemptReplace(); + /* https://github.com/FabricMC/Mixin/pull/99 */ try { Field groupMembersField = InjectorGroupInfo.class.getDeclaredField("members"); From c4fbde015ebe41673267eb8f14e172503acae598 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 13 Apr 2023 13:00:43 -0400 Subject: [PATCH 5/5] Use reflection instead of mixins for SyncExecutor patch --- .../modernfix/core/ModernFixMixinPlugin.java | 2 + .../core/config/ModernFixEarlyConfig.java | 1 - .../modernfix/load/ModWorkManagerQueue.java | 60 +++++++++++++++++++ .../SyncExecutorMixin.java | 38 ------------ src/main/resources/modernfix.mixins.json | 1 - 5 files changed, 62 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/load/ModWorkManagerQueue.java delete mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/sync_executor_sleep/SyncExecutorMixin.java diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index c6d2a883..9255011f 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -13,6 +13,7 @@ import org.embeddedt.modernfix.classloading.FastAccessTransformerList; import org.embeddedt.modernfix.classloading.ModernFixResourceFinder; import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig; import org.embeddedt.modernfix.core.config.Option; +import org.embeddedt.modernfix.load.ModWorkManagerQueue; import org.embeddedt.modernfix.util.DummyList; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; @@ -83,6 +84,7 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { } FastAccessTransformerList.attemptReplace(); + ModWorkManagerQueue.replace(); /* https://github.com/FabricMC/Mixin/pull/99 */ try { diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index d3cbb02d..83080de9 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -56,7 +56,6 @@ public class ModernFixEarlyConfig { this.addMixinRule("perf.async_jei", modPresent("jei")); this.addMixinRule("perf.thread_priorities", true); this.addMixinRule("perf.preload_block_classes", false); - this.addMixinRule("perf.sync_executor_sleep", true); this.addMixinRule("perf.scan_cache", true); this.addMixinRule("perf.compress_biome_container", true); this.addMixinRule("perf.nuke_empty_chunk_sections", true); diff --git a/src/main/java/org/embeddedt/modernfix/load/ModWorkManagerQueue.java b/src/main/java/org/embeddedt/modernfix/load/ModWorkManagerQueue.java new file mode 100644 index 00000000..e6a870b0 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/load/ModWorkManagerQueue.java @@ -0,0 +1,60 @@ +package org.embeddedt.modernfix.load; + +import net.minecraftforge.fml.ModWorkManager; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; + +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.LockSupport; + +public class ModWorkManagerQueue extends ConcurrentLinkedDeque { + private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(25); + + private static final Runnable DUMMY_TASK = () -> {}; + + private boolean shouldReturnDummyTask = false; + + /** + * Sleep for a bit if there are no tasks. + */ + @Override + public Runnable pollFirst() { + Runnable r = super.pollFirst(); + if(r == null) { + LockSupport.parkNanos(PARK_TIME); + boolean isReturning = shouldReturnDummyTask; + shouldReturnDummyTask = !shouldReturnDummyTask; + /* + * We need to kick FML to redraw the loading screen periodically, + * but also allow actually exiting the executor loop, so that + * loading can complete if async work is done. + * + * This is accomplished by alternating between returning a dummy + * task and nothing. + */ + return isReturning ? DUMMY_TASK : null; + } else { + return r; + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void replace() { + try { + Class syncExecutorClass = Class.forName("net.minecraftforge.fml.ModWorkManager$SyncExecutor"); + ConcurrentLinkedDeque taskQueue = (ConcurrentLinkedDeque)ObfuscationReflectionHelper.getPrivateValue((Class)syncExecutorClass, (Object)ModWorkManager.syncExecutor(), "tasks"); + ModWorkManagerQueue q = new ModWorkManagerQueue(); + Runnable task; + do { + task = taskQueue.pollFirst(); + if(task != null) + q.push(task); + } while(task != null); + ObfuscationReflectionHelper.setPrivateValue((Class)syncExecutorClass, (Object)ModWorkManager.syncExecutor(), q, "tasks"); + } catch(ReflectiveOperationException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/sync_executor_sleep/SyncExecutorMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/sync_executor_sleep/SyncExecutorMixin.java deleted file mode 100644 index 81d59b09..00000000 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/sync_executor_sleep/SyncExecutorMixin.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.embeddedt.modernfix.mixin.perf.sync_executor_sleep; - -import net.minecraftforge.fml.ModWorkManager; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -@Mixin(targets = "net/minecraftforge/fml/ModWorkManager$SyncExecutor") -public abstract class SyncExecutorMixin { - @Shadow(remap = false) public abstract boolean driveOne(); - - private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(50); - - /** - * Currently FML spins in driveOne while waiting for the modloading workers. We can improve this - * by sleeping for 50ms at a time. - * - * Also, render FML splash screen regardless of task availability. - * @author embeddedt - * @reason improve CPU efficiency - */ - public void drive(Runnable ticker) { - int executions = 0; - do { - executions++; - ticker.run(); - } while(driveOne()); - if(executions < 2) - LockSupport.parkNanos(PARK_TIME); - } -} diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index a0e39552..58795e9f 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -30,7 +30,6 @@ "perf.reduce_blockstate_cache_rebuilds.BlocksMixin", "perf.reduce_blockstate_cache_rebuilds.BlockStateBaseMixin", "perf.deduplicate_location.MixinResourceLocation", - "perf.sync_executor_sleep.SyncExecutorMixin", "perf.compress_biome_container.MixinBiomeContainer", "perf.nuke_empty_chunk_sections.MixinChunk", "perf.cache_blockstate_cache_arrays.AbstractBlockStateCacheMixin",