From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MartijnMuijsers Date: Sun, 27 Nov 2022 16:32:36 +0100 Subject: [PATCH] Variable main thread task delay License: AGPL-3.0 (https://www.gnu.org/licenses/agpl-3.0.html) diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java index dabd93c35bdbac6a8b668a82d5f3d4173a1baa4a..da07ef850f54f1e0225a48ccf24e28e170935a7d 100644 --- a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java @@ -21,6 +21,7 @@ import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.*; import org.bukkit.Bukkit; +import org.galemc.gale.concurrent.MinecraftServerBlockableEventLoop; import java.util.*; import java.util.concurrent.Executor; @@ -180,7 +181,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo if (!Bukkit.isPrimaryThread()) { // Plugins? - MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); + MinecraftServer.getServer().tell(() -> modifyBlocks(chunkPacket, chunkPacketInfo), MinecraftServerBlockableEventLoop.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY); // Gale - main thread tasks with variable delay return; } diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java index 8bc0cb9ad5bb4e76d962ff54305e2c08e279a17b..0ecccc1f6a17c9fb15c2ac1f33dcffc8d5a87cdd 100644 --- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java +++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java @@ -2,6 +2,7 @@ package net.minecraft.network.protocol; import com.mojang.logging.LogUtils; import net.minecraft.network.PacketListener; +import org.galemc.gale.concurrent.AbstractBlockableEventLoop; import org.slf4j.Logger; // CraftBukkit start @@ -36,10 +37,10 @@ public class PacketUtils { public PacketUtils() {} public static void ensureRunningOnSameThread(Packet packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException { - PacketUtils.ensureRunningOnSameThread(packet, listener, (BlockableEventLoop) world.getServer()); + PacketUtils.ensureRunningOnSameThread(packet, listener, world.getServer()); // Gale - main thread tasks with variable delay } - public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { + public static void ensureRunningOnSameThread(Packet packet, T listener, AbstractBlockableEventLoop engine) throws RunningOnDifferentThreadException { // Gale - main thread tasks with variable delay if (!engine.isSameThread()) { engine.executeIfPossible(() -> { packetProcessing.push(listener); // Paper - detailed watchdog information diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index ec5e28a633abe6932fd8c1da47625309bee2ca03..9045ae692d77f986542940334dd15edef6b2a9f4 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -40,7 +40,6 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -115,7 +114,6 @@ import net.minecraft.util.profiling.metrics.profiling.InactiveMetricsRecorder; import net.minecraft.util.profiling.metrics.profiling.MetricsRecorder; import net.minecraft.util.profiling.metrics.profiling.ServerMetricsSamplersProvider; import net.minecraft.util.profiling.metrics.storage.MetricsPersister; -import net.minecraft.util.thread.ReentrantBlockableEventLoop; import net.minecraft.world.Difficulty; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ai.village.VillageSiege; @@ -150,6 +148,7 @@ import net.minecraft.world.level.storage.loot.PredicateManager; import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.Validate; +import org.galemc.gale.concurrent.MinecraftServerBlockableEventLoop; import org.galemc.gale.configuration.GaleConfigurations; import org.galemc.gale.configuration.GaleGlobalConfiguration; import org.slf4j.Logger; @@ -174,9 +173,9 @@ import org.bukkit.event.server.ServerLoadEvent; import co.aikar.timings.MinecraftTimings; // Paper -public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements CommandSource, AutoCloseable { +public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop implements CommandSource, AutoCloseable { // Gale - main thread tasks with variable delay - private static MinecraftServer SERVER; // Paper + public static MinecraftServer SERVER; // Paper // Gale - main thread tasks with variable delay - private -> public public static final Logger LOGGER = LogUtils.getLogger(); public static final String VANILLA_BRAND = "vanilla"; private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F; @@ -246,7 +245,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public private final PackRepository packRepository; private final ServerScoreboard scoreboard; @Nullable @@ -314,7 +313,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop registryreadops, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) { - super("Server"); + super(); // Gale - main thread tasks with variable delay SERVER = this; // Paper - better singleton this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; this.profiler = this.metricsRecorder.getProfiler(); @@ -893,7 +892,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public public volatile boolean hasFullyShutdown = false; // Paper private boolean hasLoggedStop = false; // Paper private final Object stopLock = new Object(); @@ -922,8 +921,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public // Paper start if (this.forceTicks) { return true; @@ -1288,7 +1289,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; - } - // Paper end - return new TickTask(this.tickCount, runnable); - } - - protected boolean shouldRun(TickTask ticktask) { - return ticktask.getTick() + 3 < this.tickCount || this.haveTime(); - } - - @Override - public boolean pollTask() { - boolean flag = this.pollTaskInternal(); - - this.mayHaveDelayedTasks = flag; - return flag; - } - - private boolean pollTaskInternal() { - if (super.pollTask()) { - this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick - return true; - } else { - boolean ret = false; // Paper - force execution of all worlds, do not just bias the first - if (this.haveTime()) { - Iterator iterator = this.getAllLevels().iterator(); - - while (iterator.hasNext()) { - ServerLevel worldserver = (ServerLevel) iterator.next(); - - if (worldserver.getChunkSource().pollTask()) { - ret = true; // Paper - force execution of all worlds, do not just bias the first - } - } - } - - return ret; // Paper - force execution of all worlds, do not just bias the first - } - } - - public void doRunTask(TickTask ticktask) { // CraftBukkit - decompile error - this.getProfiler().incrementCounter("runTask"); - super.doRunTask(ticktask); - } - private void updateStatusIcon(ServerStatus metadata) { Optional optional = Optional.of(this.getFile("server-icon.png")).filter(File::isFile); @@ -1407,6 +1358,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= 5000000000L) { this.lastServerStatus = i; @@ -1455,7 +1407,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop CompletableFuture filterTextPacket(T text, BiFunction> filterer) { @@ -895,13 +896,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); // Paper - run this async // CraftBukkit start if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable - server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // Gale - JettPack - reduce array allocations + server.tell(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), MinecraftServerBlockableEventLoop.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - main thread tasks with variable delay return; } // Paper start String str = packet.getCommand(); int index = -1; if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { - server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // Gale - JettPack - reduce array allocations + server.tell(() -> this.disconnect(Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), MinecraftServerBlockableEventLoop.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - main thread tasks with variable delay return; } // Paper end @@ -926,7 +927,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (!event.isHandled()) { if (!event.isCancelled()) { - this.server.scheduleOnMain(() -> { // This needs to be on main + this.server.tell(() -> { // This needs to be on main // Gale - main thread tasks with variable delay ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { @@ -937,7 +938,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); // Paper end - Brigadier API }); - }); + }, MinecraftServerBlockableEventLoop.SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY); // Gale - main thread tasks with variable delay } } else if (!completions.isEmpty()) { final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(command, stringreader.getTotalLength()); @@ -1246,7 +1247,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; if (byteLength > 256 * 4) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); - server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + server.tell(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), MinecraftServerBlockableEventLoop.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - main thread tasks with variable delay return; } byteTotal += byteLength; @@ -1269,14 +1270,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (byteTotal > byteAllowed) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); - server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + server.tell(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), MinecraftServerBlockableEventLoop.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - main thread tasks with variable delay return; } } // Paper end // CraftBukkit start if (this.lastBookTick + 20 > MinecraftServer.currentTick) { - server.scheduleOnMain(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause // Paper - Also ensure this is called on main + server.tell(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), MinecraftServerBlockableEventLoop.KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY); // Paper - kick event cause // Paper - Also ensure this is called on main // Gale - main thread tasks with variable delay return; } this.lastBookTick = MinecraftServer.currentTick; @@ -2228,9 +2229,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } // CraftBukkit end if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { - this.server.scheduleOnMain(() -> { // Paper - push to main for event firing + this.server.tell(() -> { // Paper - push to main for event firing // Gale - main thread tasks with variable delay this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause - }); // Paper - push to main for event firing + }, MinecraftServerBlockableEventLoop.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - main thread tasks with variable delay } else { if (this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages())) { // this.server.submit(() -> { // CraftBukkit - async chat @@ -2258,9 +2259,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleChatCommand(ServerboundChatCommandPacket packet) { if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { - this.server.scheduleOnMain(() -> { // Paper - push to main for event firing + this.server.tell(() -> { // Paper - push to main for event firing // Gale - main thread tasks with variable delay this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - }); // Paper - push to main for event firing + }, MinecraftServerBlockableEventLoop.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main for event firing // Gale - main thread tasks with variable delay } else { if (this.tryHandleChat(packet.command(), packet.timeStamp(), packet.lastSeenMessages())) { this.server.submit(() -> { @@ -2357,9 +2358,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { if (!this.updateChatOrder(timestamp)) { ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}': {} > {}", this.player.getName().getString(), message, this.lastChatTimeStamp.get().getEpochSecond(), timestamp.getEpochSecond()); // Paper - this.server.scheduleOnMain(() -> { // Paper - push to main + this.server.tell(() -> { // Paper - push to main // Gale - main thread tasks with variable delay this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause - }); // Paper - push to main + }, MinecraftServerBlockableEventLoop.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY); // Paper - push to main // Gale - main thread tasks with variable delay return false; } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); @@ -3420,7 +3421,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic // Paper start if (!org.bukkit.Bukkit.isPrimaryThread()) { if (recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { - server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause // Gale - JettPack - reduce array allocations + server.tell(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", ArrayConstants.emptyObjectArray), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM), MinecraftServerBlockableEventLoop.KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY); // Paper - kick event cause // Gale - JettPack - reduce array allocations // Gale - main thread tasks with variable delay return; } } diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index a60db92d8c6afab40e12b3ac28241beac06bcf63..a9f5ce015e1f99391c8ba91cbe0c91de327c693f 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -100,6 +100,7 @@ import net.minecraft.world.scores.Objective; import net.minecraft.world.scores.PlayerTeam; import net.minecraft.world.scores.Scoreboard; // Paper import net.minecraft.world.scores.Team; +import org.galemc.gale.concurrent.MinecraftServerBlockableEventLoop; import org.galemc.gale.configuration.GaleGlobalConfiguration; import org.slf4j.Logger; @@ -306,7 +307,7 @@ public abstract class PlayerList { worldserver1, chunkX, chunkZ, net.minecraft.server.level.ChunkHolder.FullChunkStatus.ENTITY_TICKING, true, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHEST, (chunk) -> { - MinecraftServer.getServer().scheduleOnMain(() -> { + MinecraftServer.getServer().tell(() -> { // Gale - main thread tasks with variable delay try { if (!playerconnection.connection.isConnected()) { return; @@ -319,7 +320,7 @@ public abstract class PlayerList { } finally { finalWorldserver.pendingLogin.remove(player); } - }); + }, MinecraftServerBlockableEventLoop.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY); // Gale - main thread tasks with variable delay } ); } diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java index 83701fbfaa56a232593ee8f11a3afb8941238bfa..30e6da28c30b169763417b271dbe72cc6bdba1ea 100644 --- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java @@ -10,13 +10,16 @@ import java.util.concurrent.Executor; import java.util.concurrent.locks.LockSupport; import java.util.function.BooleanSupplier; import java.util.function.Supplier; + +import net.minecraft.server.MinecraftServer; import net.minecraft.util.profiling.metrics.MetricCategory; import net.minecraft.util.profiling.metrics.MetricSampler; import net.minecraft.util.profiling.metrics.MetricsRegistry; import net.minecraft.util.profiling.metrics.ProfilerMeasured; +import org.galemc.gale.concurrent.AbstractBlockableEventLoop; import org.slf4j.Logger; -public abstract class BlockableEventLoop implements ProfilerMeasured, ProcessorHandle, Executor { +public abstract class BlockableEventLoop implements ProfilerMeasured, ProcessorHandle, AbstractBlockableEventLoop { // Gale - main thread tasks with variable delay private final String name; private static final Logger LOGGER = LogUtils.getLogger(); private final Queue pendingRunnables = Queues.newConcurrentLinkedQueue(); @@ -31,6 +34,7 @@ public abstract class BlockableEventLoop implements Profiler protected abstract boolean shouldRun(R task); + @Override // Gale - main thread tasks with variable delay public boolean isSameThread() { return Thread.currentThread() == this.getRunningThread(); } @@ -102,6 +106,7 @@ public abstract class BlockableEventLoop implements Profiler } + @Override // Gale - main thread tasks with variable delay public void executeIfPossible(Runnable runnable) { this.execute(runnable); } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 55d83a9a691d11c9408d2c3260c3e77dfb51b97e..f8a5bac64d9877ba18d18cc341fd7a3f4a8a6a0d 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -135,6 +135,7 @@ import org.bukkit.util.Consumer; import org.bukkit.util.RayTraceResult; import org.bukkit.util.StructureSearchResult; import org.bukkit.util.Vector; +import org.galemc.gale.concurrent.MinecraftServerBlockableEventLoop; public class CraftWorld extends CraftRegionAccessor implements World { public static final int CUSTOM_DIMENSION_OFFSET = 10; @@ -2353,11 +2354,11 @@ public class CraftWorld extends CraftRegionAccessor implements World { java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { - net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { + net.minecraft.server.MinecraftServer.getServer().tell(() -> { // Gale - main thread tasks with variable delay net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; if (chunk != null) addTicket(x, z); // Paper ret.complete(chunk == null ? null : chunk.getBukkitChunk()); - }); + }, MinecraftServerBlockableEventLoop.COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY); // Gale - main thread tasks with variable delay }); return ret; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index 9368ec01e498f913bc5b7b3e77fe87659090d9b5..ee20bf57734bcd24f7c898cd52efea70b9a7d45f 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -189,6 +189,7 @@ import org.bukkit.plugin.Plugin; import org.bukkit.util.BoundingBox; import org.bukkit.util.NumberConversions; import org.bukkit.util.Vector; +import org.galemc.gale.concurrent.MinecraftServerBlockableEventLoop; public abstract class CraftEntity implements org.bukkit.entity.Entity { private static PermissibleBase perm; @@ -1260,7 +1261,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) { chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); } - net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { + net.minecraft.server.MinecraftServer.getServer().tell(() -> { // Gale - main thread tasks with variable delay try { ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE); } catch (Throwable throwable) { @@ -1270,7 +1271,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable); ret.completeExceptionally(throwable); } - }); + }, MinecraftServerBlockableEventLoop.TELEPORT_ASYNC_TASK_MAX_DELAY); // Gale - main thread tasks with variable delay }); return ret; diff --git a/src/main/java/org/galemc/gale/concurrent/AbstractBlockableEventLoop.java b/src/main/java/org/galemc/gale/concurrent/AbstractBlockableEventLoop.java new file mode 100644 index 0000000000000000000000000000000000000000..5c7f2159756a3de250b10b82922d69c6fab882ec --- /dev/null +++ b/src/main/java/org/galemc/gale/concurrent/AbstractBlockableEventLoop.java @@ -0,0 +1,20 @@ +// Gale - main thread tasks with variable delay + +package org.galemc.gale.concurrent; + +import net.minecraft.util.thread.BlockableEventLoop; + +import java.util.concurrent.Executor; + +/** + * An interface for the common functionality of {@link BlockableEventLoop} and {@link MinecraftServerBlockableEventLoop}. + * + * @author Martijn Muijsers + */ +public interface AbstractBlockableEventLoop extends Executor { + + boolean isSameThread(); + + void executeIfPossible(Runnable runnable); + +} diff --git a/src/main/java/org/galemc/gale/concurrent/MinecraftServerBlockableEventLoop.java b/src/main/java/org/galemc/gale/concurrent/MinecraftServerBlockableEventLoop.java new file mode 100644 index 0000000000000000000000000000000000000000..4b82aea23b99180f13c71a1797c4d829045872f5 --- /dev/null +++ b/src/main/java/org/galemc/gale/concurrent/MinecraftServerBlockableEventLoop.java @@ -0,0 +1,324 @@ +// Gale - main thread tasks with variable delay + +package org.galemc.gale.concurrent; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.util.TickThread; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.thread.BlockableEventLoop; +import net.minecraft.util.thread.ProcessorHandle; +import net.minecraft.util.thread.ReentrantBlockableEventLoop; +import org.slf4j.Logger; + +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +/** + * This is a base class for {@link MinecraftServer}, as a replacement of {@link BlockableEventLoop} + * (and the intermediary class {@link ReentrantBlockableEventLoop}. + * + * @author Martijn Muijsers + */ +public class MinecraftServerBlockableEventLoop implements ProcessorHandle, AbstractBlockableEventLoop { + + public static int DEFAULT_TASK_MAX_DELAY = 2; + public static int COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int TELEPORT_ASYNC_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + public static int HANDLE_DISCONNECT_TASK_MAX_DELAY = DEFAULT_TASK_MAX_DELAY; + + private static final String NAME = "Server"; + private static final Logger LOGGER = LogUtils.getLogger(); + + /** + * A number of queues, which the queue at index i being the queue to be used after another i ticks pass + * even when {@link MinecraftServer#haveTime()} is false. + */ + public static Queue[] pendingRunnables = new Queue[0]; + + public static volatile int firstQueueWithElementsIndex = 0; + public static volatile long stamp = 0; + + private static final ReadWriteLock pendingRunnablesLock = new ReentrantReadWriteLock(); + private static final Lock pendingRunnablesReadLock = pendingRunnablesLock.readLock(); + public static final Lock pendingRunnablesWriteLock = pendingRunnablesLock.writeLock(); + + private static int blockingCount; + private static int reentrantCount; + + public static boolean scheduleExecutables() { + return (reentrantCount != 0 || !(Thread.currentThread() instanceof TickThread)) && !MinecraftServer.SERVER.isStopped(); + } + + protected boolean runningTask() { + return reentrantCount != 0; + } + + /** + * This moves the queues in {@link #pendingRunnables}. + */ + public static void tickPendingRunnables() { + while (!pendingRunnablesWriteLock.tryLock()) {} + try { + stamp++; + // Move the queues to the preceding position + Queue firstQueue = pendingRunnables[0]; + for (int i = 1; i < pendingRunnables.length; i++) { + pendingRunnables[i - 1] = pendingRunnables[i]; + } + // Re-use the same instance that was the old first queue as the new last queue + pendingRunnables[pendingRunnables.length - 1] = firstQueue; + // Move any elements that were still present in the previous first queue to the new first queue + pendingRunnables[0].addAll(firstQueue); + firstQueue.clear(); + stamp++; + } finally { + pendingRunnablesWriteLock.unlock(); + } + } + + private static boolean hasPendingTasks() { + while (!pendingRunnablesReadLock.tryLock()); + try { + for (int i = 0; i < pendingRunnables.length; i++) { + if (!pendingRunnables[i].isEmpty()) { + return true; + } + } + return false; + } finally { + pendingRunnablesReadLock.unlock(); + } + } + + public int getPendingTasksCount() { + while (!pendingRunnablesReadLock.tryLock()); + try { + int count = 0; + for (int i = 0; i < pendingRunnables.length; i++) { + count += pendingRunnables[i].size(); + } + return count; + } finally { + pendingRunnablesReadLock.unlock(); + } + } + + public CompletableFuture submit(Supplier task) { + return scheduleExecutables() ? CompletableFuture.supplyAsync(task, this) : CompletableFuture.completedFuture(task.get()); + } + + private CompletableFuture submitAsync(Runnable runnable) { + return CompletableFuture.supplyAsync(() -> { + runnable.run(); + return null; + }, this); + } + + public CompletableFuture submit(Runnable task) { + if (scheduleExecutables()) { + return this.submitAsync(task); + } else { + task.run(); + return CompletableFuture.completedFuture((Void)null); + } + } + + public void executeBlocking(Runnable runnable) { + if (Thread.currentThread() != MinecraftServer.SERVER.serverThread) { + this.submitAsync(runnable).join(); + } else { + runnable.run(); + } + + } + + @Override + public void tell(Runnable runnable) { + this.tell(runnable, DEFAULT_TASK_MAX_DELAY); + } + + public void tell(Runnable runnable, int maxDelay) { + // Paper start - anything that does try to post to main during watchdog crash, run on watchdog + if (MinecraftServer.SERVER.hasStopped && Thread.currentThread() == MinecraftServer.SERVER.shutdownThread) { + runnable.run(); + return; + } + // Paper end + while (!pendingRunnablesReadLock.tryLock()) {} + try { + stamp++; + this.pendingRunnables[maxDelay].add(runnable); + if (maxDelay < firstQueueWithElementsIndex) { + firstQueueWithElementsIndex = maxDelay; + } + LockSupport.unpark(MinecraftServer.SERVER.serverThread); + stamp++; + } finally { + pendingRunnablesReadLock.unlock(); + } + } + + @Override + public void execute(Runnable runnable) { + if (scheduleExecutables()) { + this.tell(runnable); + } else { + runnable.run(); + } + + } + + @Override + public boolean isSameThread() { + return Thread.currentThread() instanceof TickThread; + } + + @Override + public void executeIfPossible(Runnable runnable) { + if (MinecraftServer.SERVER.isStopped()) { + throw new RejectedExecutionException("Server already shutting down"); + } else { + this.execute(runnable); + } + } + + /** + * Runs all tasks, regardless of which tick they must be finished in, or whether there is time. + */ + protected void runAllTasksForAllTicks() { + while (this.pollTask(false) || hasPendingTasks()) {} + } + + /** + * Runs all tasks while there is time. + * Runs at least all tasks that must be finished in the current tick, regardless of whether there is time. + */ + protected void runAllTasksWithinTimeOrForCurrentTick() { + while (this.pollTask(true) || !pendingRunnables[0].isEmpty()) {} + } + + public boolean pollTask(boolean mustBeWithinTimeOrForCurrentTick) { + if (this.pollTaskInternal(mustBeWithinTimeOrForCurrentTick)) { + MinecraftServer.SERVER.executeMidTickTasks(); // Paper - execute chunk tasks mid tick + MinecraftServer.SERVER.mayHaveDelayedTasks = true; + return true; + } else { + boolean ret = false; // Paper - force execution of all worlds, do not just bias the first + if (MinecraftServer.SERVER.haveTime()) { + Iterator iterator = MinecraftServer.SERVER.getAllLevels().iterator(); + + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); + + if (worldserver.getChunkSource().pollTask()) { + ret = true; // Paper - force execution of all worlds, do not just bias the first + } + } + } + + MinecraftServer.SERVER.mayHaveDelayedTasks = ret; + return ret; // Paper - force execution of all worlds, do not just bias the first + } + } + + public boolean pollTaskInternal(boolean mustBeWithinTimeOrForCurrentTick) { + mustBeWithinTimeOrForCurrentTick &= blockingCount == 0; // If we are in some way within a managedBlock call, we just want to run some tasks to fill up the time, regardless of whether there is tick time left or what moment a task is supposed to be finished by + while (true) { + // Make sure we use the first queue if it is non-empty + Runnable runnable = (Runnable) pendingRunnables[0].peek(); + int indexThatRunnableIsFrom = 0; + // Keep trying to get a runnable + if (runnable == null) { + tryGetRunnableUntilSuccessOrNothingChanged: while (true) { + int oldFirstQueueWithElementsIndex = firstQueueWithElementsIndex; + long oldStamp = stamp; + // First try from firstQueueWithElementsIndex until the end of pendingRunnables, then try all pendingRunnables again just in case + for (int attempt = 0; attempt < 2; attempt++) { + for (int i = attempt == 0 ? oldFirstQueueWithElementsIndex : 0; i < pendingRunnables.length; i++) { + runnable = (Runnable) pendingRunnables[i].peek(); + if (runnable != null) { + if (attempt == 0 ? i > firstQueueWithElementsIndex : i < firstQueueWithElementsIndex) { + firstQueueWithElementsIndex = i; + } + indexThatRunnableIsFrom = i; + break tryGetRunnableUntilSuccessOrNothingChanged; + } + } + } + if (oldFirstQueueWithElementsIndex == firstQueueWithElementsIndex && oldStamp == stamp) { // If we did not find an element and nothing has changed, we give up + return false; + } + } + } + if (mustBeWithinTimeOrForCurrentTick && !(indexThatRunnableIsFrom == 0 || MinecraftServer.SERVER.haveTime() || !pendingRunnables[0].isEmpty())) { + return false; + } else { + runnable = (Runnable) pendingRunnables[indexThatRunnableIsFrom].poll(); + if (runnable == null) { + // We lost a race condition between the peek() and poll() calls: just try again + continue; + } + doRunTask(runnable); + return true; + } + } + } + + public void managedBlock(BooleanSupplier stopCondition) { + ++blockingCount; + + try { + while(!stopCondition.getAsBoolean()) { + if (!this.pollTask(false)) { + this.waitForTasks(); + } + } + } finally { + --blockingCount; + } + + } + + private static void waitForTasks() { + Thread.yield(); + LockSupport.parkNanos("waiting for tasks", 100000L); + } + + private static void doRunTask(Runnable task) { + MinecraftServer.SERVER.getProfiler().incrementCounter("runTask"); + ++reentrantCount; + try { + task.run(); + } catch (Exception var3) { + if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper + LOGGER.error(LogUtils.FATAL_MARKER, "Error executing task on {}", NAME, var3); + } finally { + --reentrantCount; + } + } + + @Override + public String name() { + return NAME; + } + +} diff --git a/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java b/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java index 44c69a6104f19d799510e349448b8a15805c38c0..02e9a61635078af3d31a112984f4c20dddc8ac7b 100644 --- a/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java +++ b/src/main/java/org/galemc/gale/configuration/GaleGlobalConfiguration.java @@ -2,11 +2,14 @@ package org.galemc.gale.configuration; +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; import io.papermc.paper.configuration.Configuration; import io.papermc.paper.configuration.ConfigurationPart; import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece; +import org.galemc.gale.concurrent.MinecraftServerBlockableEventLoop; import org.spongepowered.configurate.objectmapping.meta.Setting; +import java.util.Arrays; import java.util.Locale; import java.util.function.Consumer; @@ -74,6 +77,223 @@ public class GaleGlobalConfiguration extends ConfigurationPart { } // Gale end - Pufferfish - SIMD support + // Gale start - main thread tasks with variable delay + public MainThreadTaskMaxDelay mainThreadTaskMaxDelay; + public class MainThreadTaskMaxDelay extends ConfigurationPart.Post { + + /** + * The default maximum delay for tasks. + * Given in ticks. + * Any value < 0 uses the vanilla maximum delay for tasks, which is currently 2. + *
    + *
  • Default: -1
  • + *
  • Vanilla: -1
  • + *
+ */ + @Setting("default") + public int defaultValue = -1; + + /** + * The default maximum delay for completing a {@link java.util.concurrent.CompletableFuture} + * for a chunk load, after the chunk has already finished loading. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: 0
  • + *
  • Vanilla: -1
  • + *
+ */ + public int completeChunkFuture = 0; + + /** + * The default maximum delay for completing the steps needed to take when a player is joining and the + * necessary chunk has been loaded. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: 19
  • + *
  • Vanilla: -1
  • + *
+ */ + public int postChunkLoadJoin = 19; + + /** + * The default maximum delay for chunk packets to be modified for anti-xray. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: 19
  • + *
  • Vanilla: -1
  • + *
+ */ + public int antiXrayModifyBlocks = 19; + + /** + * The default maximum delay for entities to be teleported when a teleport is started asynchronously. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: -1
  • + *
  • Vanilla: -1
  • + *
+ */ + public int teleportAsync = -1; + + /** + * The default maximum delay for command completion suggestions to be sent to the player. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: 9
  • + *
  • Vanilla: -1
  • + *
+ */ + public int sendCommandCompletionSuggestions = 9; + + /** + * The default maximum delay for players to get kicked for command packet spam. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: 0
  • + *
  • Vanilla: -1
  • + *
+ */ + public int kickForCommandPacketSpam = 0; + + /** + * The default maximum delay for players to get kicked for place-recipe packet spam. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: 0
  • + *
  • Vanilla: -1
  • + *
+ */ + public int kickForRecipePacketSpam = 0; + + /** + * The default maximum delay for players to get kicked for sending invalid packets trying to + * send book content that is too large, which usually indicates they are attempting to abuse an exploit. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: -1
  • + *
  • Vanilla: -1
  • + *
+ */ + public int kickForBookTooLargePacket = -1; + + /** + * The default maximum delay for players to get kicked for editing a book too quickly. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: -1
  • + *
  • Vanilla: -1
  • + *
+ */ + public int kickForEditingBookTooQuickly = -1; + + /** + * The default maximum delay for players to get kicked for sending a chat packet with illegal characters. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: -1
  • + *
  • Vanilla: -1
  • + *
+ */ + public int kickForIllegalCharactersInChatPacket = -1; + + /** + * The default maximum delay for players to get kicked for sending an out-of-order chat packet. + * Given in ticks. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: -1
  • + *
  • Vanilla: -1
  • + *
+ */ + public int kickForOutOfOrderChatPacket = -1; + + /** + * The default maximum delay for handling player disconnects. + * Any value < 0 uses {@link #defaultValue}. + *
    + *
  • Default: -1
  • + *
  • Vanilla: -1
  • + *
+ */ + public int handleDisconnect = -1; + + @Override + public void postProcess() { + while (!MinecraftServerBlockableEventLoop.pendingRunnablesWriteLock.tryLock()) {} + try { + // Update the values in MinecraftServerBlockableEventLoop for quick access + MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY = this.defaultValue >= 0 ? this.defaultValue : 2; + MinecraftServerBlockableEventLoop.COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY = this.completeChunkFuture >= 0 ? this.completeChunkFuture : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY = this.postChunkLoadJoin >= 0 ? this.postChunkLoadJoin : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY = this.antiXrayModifyBlocks >= 0 ? this.antiXrayModifyBlocks : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.TELEPORT_ASYNC_TASK_MAX_DELAY = this.teleportAsync >= 0 ? this.teleportAsync : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY = this.sendCommandCompletionSuggestions >= 0 ? this.sendCommandCompletionSuggestions : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY = this.kickForCommandPacketSpam >= 0 ? this.kickForCommandPacketSpam : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY = this.kickForRecipePacketSpam >= 0 ? this.kickForRecipePacketSpam : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY = this.kickForBookTooLargePacket >= 0 ? this.kickForBookTooLargePacket : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY = this.kickForEditingBookTooQuickly >= 0 ? this.kickForEditingBookTooQuickly : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY = this.kickForIllegalCharactersInChatPacket >= 0 ? this.kickForIllegalCharactersInChatPacket : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY = this.kickForOutOfOrderChatPacket >= 0 ? this.kickForOutOfOrderChatPacket : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + MinecraftServerBlockableEventLoop.HANDLE_DISCONNECT_TASK_MAX_DELAY = this.handleDisconnect >= 0 ? this.handleDisconnect : MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY; + // Change the length of the pendingRunnables array of queues + int maxDelay = 0; + for (int delay : new int[]{ + MinecraftServerBlockableEventLoop.DEFAULT_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.COMPLETE_CHUNK_FUTURE_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.POST_CHUNK_LOAD_JOIN_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.ANTI_XRAY_MODIFY_BLOCKS_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.TELEPORT_ASYNC_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.SEND_COMMAND_COMPLETION_SUGGESTIONS_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.KICK_FOR_COMMAND_PACKET_SPAM_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.KICK_FOR_RECIPE_PACKET_SPAM_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.KICK_FOR_EDITING_BOOK_TOO_QUICKLY_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.KICK_FOR_ILLEGAL_CHARACTERS_IN_CHAT_PACKET_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.KICK_FOR_OUT_OF_ORDER_CHAT_PACKET_TASK_MAX_DELAY, + MinecraftServerBlockableEventLoop.HANDLE_DISCONNECT_TASK_MAX_DELAY + }) { + if (delay > maxDelay) { + maxDelay = delay; + } + } + int newPendingRunnablesLength = maxDelay + 1; + int oldPendingRunnablesLength = MinecraftServerBlockableEventLoop.pendingRunnables.length; + if (oldPendingRunnablesLength != newPendingRunnablesLength) { + if (oldPendingRunnablesLength > newPendingRunnablesLength) { + // Move all tasks in queues that will be removed to the last queue + for (int i = newPendingRunnablesLength + 1; i < MinecraftServerBlockableEventLoop.pendingRunnables.length; i++) { + MinecraftServerBlockableEventLoop.pendingRunnables[maxDelay].addAll(MinecraftServerBlockableEventLoop.pendingRunnables[i]); + } + // Update the first queue with elements index + if (MinecraftServerBlockableEventLoop.firstQueueWithElementsIndex >= newPendingRunnablesLength) { + MinecraftServerBlockableEventLoop.firstQueueWithElementsIndex = maxDelay; + } + } + MinecraftServerBlockableEventLoop.pendingRunnables = Arrays.copyOf(MinecraftServerBlockableEventLoop.pendingRunnables, newPendingRunnablesLength); + if (newPendingRunnablesLength > oldPendingRunnablesLength) { + // Create new queues + for (int i = oldPendingRunnablesLength; i < newPendingRunnablesLength; i++) { + MinecraftServerBlockableEventLoop.pendingRunnables[i] = new MultiThreadedQueue<>(); + } + } + } + } finally { + MinecraftServerBlockableEventLoop.pendingRunnablesWriteLock.unlock(); + } + } + + } + // Gale end - main thread tasks with variable delay + } public LogToConsole logToConsole;