Skip to content

插件同步清理实体时并发异常导致崩服 #830

Description

@AisakaUwU

自我检查表

  • 我已经浏览过 Issues 和 Leaf 文档,并确保汇报的不是已知问题
  • 我勾选了所有框,因为我根本就没仔细看他们是什么
  • 我正在使用从 https://www.leafmc.one/zh/download 下载的最新 Leaf 构建
  • 我无法在 最新 PaperPurpur 构建上复现此问题

问题描述

已开启PWT

崩溃日志

[14:35:50] [Server thread/WARN]: 检测到同区块同种掉落物 FLINT 超过限制,已清除 368 个,位于 world -10176 ~ -14768
[14:35:50] [Server thread/WARN]: 检测到同区块同种掉落物 IRON_NUGGET 超过限制,已清除 238 个,位于 world -10176 ~ -14768
[14:35:50] [Server thread/WARN]: 检测到同区块同种掉落物 CLAY_BALL 超过限制,已清除 333 个,位于 world -10176 ~ -14768
[14:35:50] [Server thread/WARN]: 检测到同区块TNT堆积超过限制,已清除 600 个,位于 world -10176 ~ -14800
[14:35:50] [Server thread/WARN]: 掉落物堆积检测完成,用时 12ms
[14:35:50] [Server thread/ERROR]: Encountered an unexpected exception
java.util.ConcurrentModificationException
	at java.base/java.util.HashMap.forEach(HashMap.java:1433) ~[?:?]
	at net.minecraft.world.level.storage.DimensionDataStorage.collectDirtyTagsToSave(DimensionDataStorage.java:182) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.world.level.storage.DimensionDataStorage.scheduleSave(DimensionDataStorage.java:142) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.server.level.ServerLevel.saveLevelData(ServerLevel.java:1797) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.server.level.ServerLevel.saveIncrementally(ServerLevel.java:1724) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.server.MinecraftServer.tickServer(MinecraftServer.java:1736) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.server.dedicated.DedicatedServer.tickServer(DedicatedServer.java:492) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.server.MinecraftServer.processPacketsAndTick(MinecraftServer.java:1771) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1435) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at net.minecraft.server.MinecraftServer.lambda$spin$2(MinecraftServer.java:430) ~[leaf-1.21.11.jar:1.21.11-170-4ca6916]
	at java.base/java.lang.Thread.run(Thread.java:1474) ~[?:?]
[14:35:51] [Server thread/ERROR]: This crash report has been saved to: F:\MC_Server_1_21_11\crash-reports\crash-2026-07-23_14.35.51-server.txt
[14:35:51] [Server thread/INFO]: Stopping server

同步任务清理代码

            new BukkitRunnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis(); // 记录任务开始时间

                    // 循环世界
                    for (World world : Bukkit.getWorlds()) {
                        // 循环世界内所有区块
                        for (Chunk chunk : world.getLoadedChunks()) {

                            Chunk.LoadLevel level = chunk.getLoadLevel();
                            if (level != Chunk.LoadLevel.TICKING && level != Chunk.LoadLevel.BORDER) continue;

                            Entity[] entities = chunk.getEntities();
                            if (entities.length < 200) continue; // 直接跳过少量实体的区块

                            Map<Material, Integer> countMap = new HashMap<>();
                            Map<Material, List<Item>> itemMap = new HashMap<>();

                            List<TNTPrimed> tntList = new ArrayList<>();

                            String chunkWorldName = chunk.getWorld().getName();
                            int blockX = chunk.getX() << 4; // chunk.getX() * 16
                            int blockZ = chunk.getZ() << 4;

                            // 循环区块内某一种实体
                            for (Entity entity: entities) {
                                // 如果是掉落物
                                if (entity instanceof Item item) {
                                    // 掉落物类型
                                    Material material = item.getItemStack().getType();
                                    // 统计实体数量(不是 ItemStack 数量)
                                    countMap.merge(material, 1, Integer::sum);
                                    // 保存实体,方便后面删除
                                    itemMap.computeIfAbsent(material, k -> new ArrayList<>()).add(item);
                                } else if (entity instanceof TNTPrimed tnt) {
                                    tntList.add(tnt);
                                }

                            }

                            // 删除堆积的TNT
                            if (tntList.size() >= 200) {
                                tntList.forEach(Entity::remove);
                                Bukkit.getLogger().warning(
                                        "检测到同区块TNT堆积超过限制,已清除 "
                                                + tntList.size()
                                                + " 个,位于 "
                                                + chunkWorldName
                                                + " "
                                                + blockX
                                                + " ~ "
                                                + blockZ
                                );
                            }

                            // 删除超过阈值的物品
                            for (Map.Entry<Material, Integer> entry : countMap.entrySet()) {
                                if (entry.getValue() >= 200) {
                                    for (Item item : itemMap.get(entry.getKey())) {
                                        item.remove();
                                    }
                                    Bukkit.getLogger().warning(
                                            "检测到同区块同种掉落物 "
                                                    + entry.getKey()
                                                    + " 超过限制,已清除 "
                                                    + entry.getValue()
                                                    + " 个,位于 "
                                                    + chunkWorldName
                                                    + " "
                                                    + blockX
                                                    + " ~ "
                                                    + blockZ
                                    );
                                }
                            }
                        }
                    }

                    long elapsed = System.currentTimeMillis() - startTime; // 计算到当前耗时
                    Bukkit.getLogger().warning("掉落物堆积检测完成,用时 " + elapsed + "ms");
                }
            }.runTaskTimer(this, 20L * 60 * 15, 20L * 60 * 15); // 15分钟扫描一次

所期望的行为

不再崩服

如何复现?

1.采用相同的清理代码
2.等待清理任务某一天与服务器的任务重叠
3.概率较小,可能半个月碰巧触发一次

Spark profiler 链接

错误日志 (如存在)

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions