From 920e61ce0d5502eb0e83225a2bdb586b1c61cf78 Mon Sep 17 00:00:00 2001 From: Dueris Date: Mon, 1 Jun 2026 19:34:04 -0700 Subject: [PATCH 1/2] More tick thread checks --- .../server/level/ChunkMap.java.patch | 8 ++++---- .../server/level/ServerLevel.java.patch | 2 +- .../server/level/ServerPlayer.java.patch | 20 +++++++++++++++---- .../block/state/BlockBehaviour.java.patch | 2 +- .../craftbukkit/entity/CraftHumanEntity.java | 9 ++++++++- .../craftbukkit/entity/CraftLivingEntity.java | 3 ++- .../craftbukkit/entity/CraftPlayer.java | 4 ++++ 7 files changed, 36 insertions(+), 12 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch index c2584b6efe61..999176fd2565 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -263,7 +263,7 @@ } public void addEntity(final Entity entity) { -+ org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot add entity to tracking async"); // Paper - block async calls + // Paper start - ignore and warn about illegal addEntity calls instead of crashing server + if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { + LOGGER.error("Illegal ChunkMap::addEntity for world " + io.papermc.paper.util.MCUtil.getLevelName(this.level) @@ -283,7 +283,7 @@ } protected void removeEntity(final Entity entity) { -+ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot remove entity async"); // Paper - block async calls if (entity instanceof ServerPlayer player) { this.updatePlayerStatus(player, false); @@ -304,7 +304,7 @@ } public void removePlayer(final ServerPlayer player) { -+ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot remove player async"); // Paper - block async calls if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); if (this.seenBy.isEmpty()) { @@ -312,7 +312,7 @@ } public void updatePlayer(final ServerPlayer player) { -+ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot update player async"); // Paper - block async calls if (player != this.entity) { - Vec3 deltaToPlayer = player.position().subtract(this.entity.position()); + // Paper start - remove allocation of Vec3D here diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch index c71e11b9d093..4fff585b3fef 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -644,7 +644,7 @@ - private boolean addEntity(final Entity entity) { + // CraftBukkit start + private boolean addEntity(final Entity entity, final org.bukkit.event.entity.CreatureSpawnEvent.@Nullable SpawnReason spawnReason) { -+ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot add entity async"); // Paper - block async calls + entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process + // Paper start - extra debug info + if (entity.valid) { diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index 36e80c0dc5b1..41c18d1ffe46 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -218,6 +218,14 @@ public void setExperiencePoints(final int amount) { float limit = this.getXpNeededForNextLevel(); float max = (limit - 1.0F) / limit; +@@ -540,6 +_,7 @@ + } + + public void initMenu(final AbstractContainerMenu container) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot init menu async"); // Paper - block async calls + container.addSlotListener(this.containerListener); + container.setSynchronizer(this.containerSynchronizer); + } @@ -572,6 +_,11 @@ @Override @@ -437,7 +445,7 @@ this.connection .send( new ClientboundPlayerCombatKillPacket(this.getId(), deathMessage), -@@ -889,6 +_,64 @@ +@@ -889,6 +_,65 @@ } ) ); @@ -448,6 +456,7 @@ + // Paper end - Expand PlayerDeathEvent API + @Override + public void die(final DamageSource source) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot die async"); // Paper - block async calls + // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check + boolean showDeathMessage = this.level().getGameRules().get(GameRules.SHOW_DEATH_MESSAGES); + // CraftBukkit start - fire PlayerDeathEvent @@ -964,7 +973,7 @@ } @Override -@@ -1308,8 +_,9 @@ +@@ -1308,22 +_,51 @@ this.connection.send(new ClientboundShowDialogPacket(dialog)); } @@ -975,7 +984,9 @@ } @Override -@@ -1318,12 +_,39 @@ + public OptionalInt openMenu(final @Nullable MenuProvider provider) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot open menu async"); // Paper - block async calls + if (provider == null) { return OptionalInt.empty(); } @@ -1088,7 +1099,7 @@ this.initMenu(this.containerMenu); } -@@ -1394,10 +_,30 @@ +@@ -1394,10 +_,31 @@ @Override public void closeContainer() { @@ -1097,6 +1108,7 @@ + } + @Override + public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot close container async"); // Paper - block async calls + org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit + // Paper end - Inventory close reason this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch index a7ddf93cc18d..11612d097cd7 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch @@ -4,7 +4,7 @@ } protected void onPlace(final BlockState state, final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { -+ org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, pos, "Cannot place block async"); // Paper - block async calls } protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index 0c98e3ec53b4..00ee87463d7e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import io.papermc.paper.adventure.PaperAdventure; @@ -501,6 +502,12 @@ private static int getMountInventoryColumns(final AbstractMountInventoryMenu mou @Override public InventoryView openMerchant(Merchant merchant, boolean force) { Preconditions.checkNotNull(merchant, "merchant cannot be null"); + // Paper start - block async calls + if (merchant instanceof CraftEntity craftEntity) { + TickThread.ensureTickThread(craftEntity.entity, "Cannot open merchant screen async from merchant"); + } + TickThread.ensureTickThread(this.entity, "Cannot open merchant screen async"); + // Paper end - block async calls if (!force && merchant.isTrading()) { return null; @@ -562,7 +569,7 @@ public InventoryView openStonecutter(Location location, boolean force) { } private InventoryView openInventory(Location location, boolean force, Material material) { - org.spigotmc.AsyncCatcher.catchOp("open" + material); + TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); // Paper - block async calls if (location == null) { location = this.getLocation(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index aefc65543fc1..f1a891ad407f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import java.util.ArrayList; @@ -508,7 +509,7 @@ public void setKiller(Player killer) { @Override public boolean addPotionEffect(PotionEffect effect, boolean force) { - org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper + TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); // Paper - block async calls this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon return true; } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 2e95fee39986..481763d0118e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -1340,6 +1341,7 @@ public void setSprinting(boolean sprinting) { @Override public void loadData() { + TickThread.ensureTickThread(this.entity, "Cannot load data async"); // Paper - block async calls this.server.getHandle().playerIo.load(this.getHandle().nameAndId()) .map(tag -> TagValueInput.create(ProblemReporter.DISCARDING, this.server.getServer().registryAccess(), tag)) .ifPresent(this.getHandle()::load); @@ -1347,6 +1349,7 @@ public void loadData() { @Override public void saveData() { + TickThread.ensureTickThread(this.entity, "Cannot save data async"); // Paper - block async calls this.server.getHandle().playerIo.save(this.getHandle()); } @@ -1663,6 +1666,7 @@ public void setGameMode(GameMode mode) { Preconditions.checkArgument(mode != null, "GameMode cannot be null"); if (this.getHandle().connection == null) return; + TickThread.ensureTickThread(this.entity, "Cannot set gamemode async"); // Paper - block async calls this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent } From 8167910566ee75cf762ad3c3cf764c7ea5257fbd Mon Sep 17 00:00:00 2001 From: Dueris Date: Mon, 1 Jun 2026 19:46:45 -0700 Subject: [PATCH 2/2] A few more replacement calls --- .../main/java/org/bukkit/craftbukkit/CraftWorld.java | 11 ++++++----- .../org/bukkit/craftbukkit/entity/CraftEntity.java | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index f77cb6d85c89..62798942ca75 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2,6 +2,7 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.Lists; @@ -1554,7 +1555,7 @@ public void playSound(Location loc, String sound, org.bukkit.SoundCategory categ @Override public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1566,7 +1567,7 @@ public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory catego @Override public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1589,8 +1590,8 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1610,8 +1611,8 @@ public void playSound(final net.kyori.adventure.sound.Sound sound) { @Override public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(Identifier.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1622,7 +1623,7 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + TickThread.ensureTickThread(this.getHandle(), ((int) x) >> 4, ((int) z) >> 4, "Cannot play sound async"); // Paper - block async calls io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, x, y, z, sound.seed().orElseGet(this.world.getRandom()::nextLong), this.playSound0(x, y, z)); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index 2f40476da09c..c12cb6df4f5f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; @@ -373,7 +374,7 @@ public static LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnch @Override public List getNearbyEntities(double x, double y, double z) { Preconditions.checkState(!this.entity.generation, "Cannot get nearby entities during world generation"); - org.spigotmc.AsyncCatcher.catchOp("getNearbyEntities"); // Spigot + TickThread.ensureTickThread(this.entity, "Cannot check nearby entities async"); // Paper - block async calls List entities = this.getHandle().level().getEntities(this.entity, this.entity.getBoundingBox().inflate(x, y, z), Predicates.alwaysTrue()); List result = new java.util.ArrayList<>(entities.size());