diff --git a/src/main/java/com/hbm/commands/CommandPacketInfo.java b/src/main/java/com/hbm/commands/CommandPacketInfo.java index 97d465f0c..d4d3cb0f3 100644 --- a/src/main/java/com/hbm/commands/CommandPacketInfo.java +++ b/src/main/java/com/hbm/commands/CommandPacketInfo.java @@ -1,7 +1,6 @@ package com.hbm.commands; import com.hbm.config.GeneralConfig; -import com.hbm.handler.radiation.ChunkRadiationManager; import com.hbm.handler.threading.PacketThreading; import com.hbm.util.BobMathUtil; import net.minecraft.command.CommandBase; @@ -9,7 +8,9 @@ import net.minecraft.command.ICommandSender; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; -import static com.hbm.handler.threading.PacketThreading.processedCnt; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; + import static com.hbm.handler.threading.PacketThreading.totalCnt; public class CommandPacketInfo extends CommandBase { @@ -20,59 +21,45 @@ public class CommandPacketInfo extends CommandBase { @Override public String getCommandUsage(ICommandSender sender) { - return "/ntmpackets "; + return "/ntmpackets"; } @Override public void processCommand(ICommandSender sender, String[] args) { - if(args.length == 1 && "infoall".equals(args[0])) { - sender.addChatMessage(new ChatComponentText("NTM Packet Debugger")); - if(GeneralConfig.enablePacketThreading) - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GREEN + "Packet Threading Active")); - else - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Packet Threading Inactive")); - - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread Pool Info")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread pool size: " + PacketThreading.threadPool.getPoolSize())); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread pool queue: " + PacketThreading.threadPool.getQueue().size())); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Packet Info: " + PacketThreading.threadPool.getPoolSize())); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Amount Processed: " + processedCnt)); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Amount Remaining: " + totalCnt)); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "% Processed: " + BobMathUtil.roundDecimal(((double) processedCnt / totalCnt) * 100, 2))); - return; + if(args.length > 0 && args[0].equals("resetState")) { + PacketThreading.hasTriggered = false; + PacketThreading.clearCnt = 0; } - if(args.length == 1 && "threadpool".equals(args[0])) { - sender.addChatMessage(new ChatComponentText("NTM Packet Debugger")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GOLD + "NTM Packet Debugger v1.2")); - if(GeneralConfig.enablePacketThreading) - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GREEN + "Packet Threading Active")); - else - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Packet Threading Inactive")); + if(PacketThreading.isTriggered() && GeneralConfig.enablePacketThreading) + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Packet Threading Errored, check log.")); + else if(GeneralConfig.enablePacketThreading) + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GREEN + "Packet Threading Active")); + else + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Packet Threading Inactive")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread Pool Info")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread pool size: " + PacketThreading.threadPool.getPoolSize())); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread pool queue: " + PacketThreading.threadPool.getQueue().size())); - return; - } + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread Pool Info")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "# Threads (total): " + PacketThreading.threadPool.getPoolSize())); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "# Threads (core): " + PacketThreading.threadPool.getCorePoolSize())); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "# Threads (idle): " + (PacketThreading.threadPool.getPoolSize() - PacketThreading.threadPool.getActiveCount()))); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "# Threads (maximum): " + PacketThreading.threadPool.getMaximumPoolSize())); - if(args.length == 1 && "packets".equals(args[0])) { - sender.addChatMessage(new ChatComponentText("NTM Packet Debugger")); + for(ThreadInfo thread : ManagementFactory.getThreadMXBean().dumpAllThreads(false, false)) + if(thread.getThreadName().startsWith("NTM-Packet-Thread-")) { + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GOLD + "Thread Name: " + thread.getThreadName())); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread ID: " + thread.getThreadId())); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Thread state: " + thread.getThreadState())); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Locked by: " + (thread.getLockOwnerName() == null ? "None" : thread.getLockName()))); + } - if(GeneralConfig.enablePacketThreading) - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GREEN + "Packet Threading Active")); - else - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Packet Threading Inactive")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GOLD + "Packet Info: ")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Amount total: " + totalCnt)); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Amount remaining: " + PacketThreading.threadPool.getQueue().size())); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "% Remaining to process: " + BobMathUtil.roundDecimal(((double) PacketThreading.threadPool.getQueue().size() / totalCnt) * 100, 2) + "%")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Time spent waiting on thread(s) last tick: " + BobMathUtil.roundDecimal(PacketThreading.nanoTimeWaited / 1000000d, 4) + "ms")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Packet Info: " + PacketThreading.threadPool.getPoolSize())); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Amount Processed: " + processedCnt)); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Amount Remaining: " + totalCnt)); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "% Processed: " + BobMathUtil.roundDecimal(((double) processedCnt / totalCnt) * 100, 2))); - - return; - } - - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + getCommandUsage(sender))); } } diff --git a/src/main/java/com/hbm/config/GeneralConfig.java b/src/main/java/com/hbm/config/GeneralConfig.java index 41ad6735e..916bd9e9c 100644 --- a/src/main/java/com/hbm/config/GeneralConfig.java +++ b/src/main/java/com/hbm/config/GeneralConfig.java @@ -7,6 +7,9 @@ public class GeneralConfig { public static boolean enableThermosPreventer = true; public static boolean enablePacketThreading = true; + public static int packetThreadingCoreCount = 1; + public static int packetThreadingMaxCount = 1; + public static boolean packetThreadingErrorBypass = false; public static boolean enableDebugMode = true; public static boolean enableMycelium = false; @@ -78,6 +81,10 @@ public class GeneralConfig { enablePacketThreading = config.get(CATEGORY_GENERAL, "0.01_enablePacketThreading", true, "Enables creation of a separate thread to increase packet processing speed on servers. Disable this if you are having anomalous crashes related to memory connections.").getBoolean(true); + packetThreadingCoreCount = config.get(CATEGORY_GENERAL, "0.02_packetThreadingCoreCount", 1, "Number of core threads to create for packets (recommended 1).").getInt(1); + packetThreadingMaxCount = config.get(CATEGORY_GENERAL, "0.03_packetThreadingMaxCount", 1, "Maximum number of threads to create for packet threading. Must be greater than or equal to 0.02_packetThreadingCoreCount.").getInt(1); + packetThreadingErrorBypass = config.get(CATEGORY_GENERAL, "0.04_packetThreadingErrorBypass", false, "Forces the bypassing of most packet threading errors, only enable this if directed to or if you know what you're doing.").getBoolean(false); + enableDebugMode = config.get(CATEGORY_GENERAL, "1.00_enableDebugMode", false, "Enable debugging mode").getBoolean(false); enableMycelium = config.get(CATEGORY_GENERAL, "1.01_enableMyceliumSpread", false, "Allows glowing mycelium to spread").getBoolean(false); enablePlutoniumOre = config.get(CATEGORY_GENERAL, "1.02_enablePlutoniumNetherOre", false, "Enables plutonium ore generation in the nether").getBoolean(false); diff --git a/src/main/java/com/hbm/handler/threading/PacketThreading.java b/src/main/java/com/hbm/handler/threading/PacketThreading.java index 9e2e4762a..7392e59d8 100644 --- a/src/main/java/com/hbm/handler/threading/PacketThreading.java +++ b/src/main/java/com/hbm/handler/threading/PacketThreading.java @@ -20,7 +20,7 @@ public class PacketThreading { public static int totalCnt = 0; - public static int processedCnt = 0; + public static long nanoTimeWaited = 0; public static final List> futureList = new ArrayList<>(); @@ -29,7 +29,26 @@ public class PacketThreading { */ public static void init() { threadPool.setKeepAliveTime(50, TimeUnit.MILLISECONDS); - threadPool.allowCoreThreadTimeOut(true); + if(GeneralConfig.enablePacketThreading) { + if(GeneralConfig.packetThreadingCoreCount < 0 || GeneralConfig.packetThreadingMaxCount <= 0) { + MainRegistry.logger.error("0.02_packetThreadingCoreCount < 0 or 0.03_packetThreadingMaxCount is <= 0, defaulting to 1 each."); + threadPool.setCorePoolSize(1); // beugh + threadPool.setMaximumPoolSize(1); + } else if(GeneralConfig.packetThreadingMaxCount > GeneralConfig.packetThreadingCoreCount) { + MainRegistry.logger.error("0.03_packetThreadingMaxCount is > 0.02_packetThreadingCoreCount, defaulting to 1 each."); + threadPool.setCorePoolSize(1); + threadPool.setMaximumPoolSize(1); + } else { + threadPool.setCorePoolSize(GeneralConfig.packetThreadingCoreCount); + threadPool.setMaximumPoolSize(GeneralConfig.packetThreadingMaxCount); + } + threadPool.allowCoreThreadTimeOut(false); + } else { + threadPool.allowCoreThreadTimeOut(true); + for(Runnable task : threadPool.getQueue()) { + task.run(); // Run all tasks async just in-case there *are* tasks left to run. + } + } } /** @@ -49,10 +68,11 @@ public class PacketThreading { PacketDispatcher.wrapper.sendToAllAround(message, target); if(message instanceof PrecompiledPacket) ((PrecompiledPacket) message).getPreBuf().release(); - processedCnt++; }; - if(GeneralConfig.enablePacketThreading) + if(isTriggered()) + task.run(); + else if(GeneralConfig.enablePacketThreading) futureList.add(threadPool.submit(task)); // Thread it else task.run(); // no threading :( @@ -62,8 +82,9 @@ public class PacketThreading { * Wait until the packet thread is finished processing. */ public static void waitUntilThreadFinished() { + long startTime = System.nanoTime(); try { - if (!(processedCnt >= totalCnt) && !GeneralConfig.enablePacketThreading) { + if (GeneralConfig.enablePacketThreading && (!GeneralConfig.packetThreadingErrorBypass && !hasTriggered)) { for (Future future : futureList) { future.get(50, TimeUnit.MILLISECONDS); // I HATE EVERYTHING } @@ -71,14 +92,61 @@ public class PacketThreading { } catch (ExecutionException ignored) { // impossible } catch (TimeoutException e) { - MainRegistry.logger.warn("A packet has taken >50ms to process, discarding {}/{} packets to prevent pausing of main thread ({} total futures).", totalCnt-processedCnt, totalCnt, futureList.size()); - threadPool.getQueue().clear(); + if(!GeneralConfig.packetThreadingErrorBypass && !hasTriggered) + MainRegistry.logger.warn("A packet has taken >50ms to process, discarding {}/{} packets to prevent pausing of main thread ({} total futures).", threadPool.getQueue().size(), totalCnt, futureList.size()); + clearThreadPoolTasks(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // maybe not the best thing but it's gotta be here } finally { futureList.clear(); - processedCnt = 0; + nanoTimeWaited = System.nanoTime() - startTime; + if(!threadPool.getQueue().isEmpty()) { + if(!GeneralConfig.packetThreadingErrorBypass && !hasTriggered) + MainRegistry.logger.warn("Residual packets in packet threading queue detected, discarding {}/{} packets.", threadPool.getQueue().size(), totalCnt); + clearThreadPoolTasks(); // Just in case the thread somehow doesn't process all the tasks, we don't want this backing up too far. + } + totalCnt = 0; } } + + public static int clearCnt = 0; + + public static boolean hasTriggered = false; + + public static void clearThreadPoolTasks() { + + if(threadPool.getQueue().isEmpty()) { + clearCnt = 0; + return; + } + + threadPool.getQueue().clear(); + + if(!GeneralConfig.packetThreadingErrorBypass && !hasTriggered) + MainRegistry.logger.warn("Packet work queue cleared forcefully (clear count: {}).", clearCnt); + + clearCnt++; + + + if(clearCnt > 5 && !isTriggered()) { + // If it's been cleared 5 times in a row, something may have gone really wrong. + // Best case scenario here, the server is lagging terribly, has a bad CPU, or has a poor network connection + // Worst case scenario, the entire packet thread is dead. (very not good) + // So just log it with a special message and only once. + MainRegistry.logger.error( + "Something has gone wrong and the packet pool has cleared 5 times (or more) in a row. " + + "This can indicate that the thread has been killed, suspended, or is otherwise non-functioning. " + + "This message will only be logged once, further packet operations will continue on the main thread. " + + "If this message is a common occurrence and is *completely expected*, then it can be bypassed permanently by setting " + + "the \"0.04_packetThreadingErrorBypass\" config option to true. This can lead to adverse effects, so do this at your own risk. " + + "Running \"/ntmpacket resetState\" resets this trigger as a temporary fix." + ); + hasTriggered = true; + } + } + + public static boolean isTriggered() { + return hasTriggered && !GeneralConfig.packetThreadingErrorBypass; + } } diff --git a/src/main/java/com/hbm/main/MainRegistry.java b/src/main/java/com/hbm/main/MainRegistry.java index ddf7ce8a6..06d07cb69 100644 --- a/src/main/java/com/hbm/main/MainRegistry.java +++ b/src/main/java/com/hbm/main/MainRegistry.java @@ -905,8 +905,6 @@ public class MainRegistry { PacketDispatcher.registerPackets(); - PacketThreading.init(); - NeutronHandler neutronHandler = new NeutronHandler(); MinecraftForge.EVENT_BUS.register(neutronHandler); FMLCommonHandler.instance().bus().register(neutronHandler); @@ -969,6 +967,8 @@ public class MainRegistry { config.save(); + PacketThreading.init(); + try { if(GeneralConfig.enableThermosPreventer && Class.forName("thermos.ThermosClassTransformer") != null) { throw new IllegalStateException("The mod tried to start on a Thermos or its fork server and therefore stopped. To allow the server to start on Thermos, change the appropriate " diff --git a/src/main/java/com/hbm/tileentity/TileEntityLoadedBase.java b/src/main/java/com/hbm/tileentity/TileEntityLoadedBase.java index 583a3cb9d..c151824a9 100644 --- a/src/main/java/com/hbm/tileentity/TileEntityLoadedBase.java +++ b/src/main/java/com/hbm/tileentity/TileEntityLoadedBase.java @@ -84,7 +84,7 @@ public class TileEntityLoadedBase extends TileEntity implements ILoadedTile, IBu return; } - this.lastPackedBuf = preBuf; + this.lastPackedBuf = preBuf.copy(); PacketThreading.createThreadedPacket(packet, new NetworkRegistry.TargetPoint(this.worldObj.provider.dimensionId, xCoord, yCoord, zCoord, range)); }