Spigot插件异步踢人报错java.lang.IllegalStateException问题解决
Spigot插件异步踢人异常修复方案
我开发的Spigot插件中,当玩家发送特定数据包累计20次违规时,本该触发踢人操作,但实际执行时抛出java.lang.IllegalStateException: Asynchronous player kick!错误。目前前19次违规提示正常,仅第20次无法完成踢人。
错误堆栈信息
[TenKAC] Unhandled exception number 64 occurred in onPacketReceiving(PacketEvent) for TenKAC java.lang.IllegalStateException: Asynchronous player kick! at org.spigotmc.AsyncCatcher.catchOp(AsyncCatcher.java:14) ~[spigot.jar:git-Spigot-21fe707-741a1bd] at org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer.kickPlayer(CraftPlayer.java:214) ~[spigot.jar:git-Spigot-21fe707-741a1bd] at cc.funkemunky.tenkac.checks.Check.flag(Check.java:57) ~[tenkac.jar:?] at cc.funkemunky.tenkac.checks.movement.Killaura.access$000(Killaura.java:23) ~[tenkac.jar:?] at cc.funkemunky.tenkac.checks.movement.Killaura$1.onPacketReceiving(Killaura.java:33) ~[tenkac.jar:?] at com.comphenix.protocol.injector.SortedPacketListenerList.invokeReceivingListener(SortedPacketListenerList.java:122) [ProtocolLib.jar:?] at com.comphenix.protocol.injector.SortedPacketListenerList.invokePacketRecieving(SortedPacketListenerList.java:75) [ProtocolLib.jar:?] at com.comphenix.protocol.injector.PacketFilterManager.postPacketToListeners(PacketFilterManager.java:557) [ProtocolLib.jar:?] at com.comphenix.protocol.injector.PacketFilterManager.invokePacketReceiving(PacketFilterManager.java:521) [ProtocolLib.jar:?] at com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector.onPacketReceiving(NetworkManagerInjector.java:118) [ProtocolLib.jar:?] at com.comphenix.protocol.injector.netty.channel.NettyChannelInjector.processInboundPacket(NettyChannelInjector.java:500) [ProtocolLib.jar:?] at com.comphenix.protocol.injector.netty.channel.InboundPacketInterceptor.channelRead(InboundPacketInterceptor.java:33) [ProtocolLib.jar:?] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:163) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:163) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:163) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.handler.timeout.ReadTimeoutHandler.channelRead(ReadTimeoutHandler.java:150) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:130) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354) [spigot.jar:git-Spigot-21fe707-741a1bd] at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116) [spigot.jar:git-Spigot-21fe707-741a1bd] at java.lang.Thread.run(Unknown Source) [?:1.8.0_301]
原实现代码
Killaura类
package cc.funkemunky.tenkac.checks.movement; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; import cc.funkemunky.tenkac.AntiCheat; import cc.funkemunky.tenkac.checks.Check; import cc.funkemunky.tenkac.checks.CheckType; import cc.funkemunky.tenkac.data.DataPlayer; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import static org.bukkit.Bukkit.getScheduler; import static org.bukkit.Bukkit.getServer; public class Killaura extends Check { private int buffer = 0; public Killaura(String name, CheckType type, boolean enabled, boolean punishable, int max) { super(name, type, enabled, punishable, max); ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter(AntiCheat.getInstance(), PacketType.Play.Client.POSITION, PacketType.Play.Client.POSITION_LOOK, PacketType.Play.Client.LOOK, PacketType.Play.Client.FLYING, PacketType.Play.Client.USE_ENTITY) { @Override public void onPacketReceiving(PacketEvent event) { DataPlayer data = AntiCheat.getInstance().getDataManager().getDataPlayer(event.getPlayer()); if (event.getPacketType().equals(PacketType.Play.Client.USE_ENTITY)) { flag(event.getPlayer(), "Player sent flying packet too late: " + Math.abs(System.currentTimeMillis() - data.lastFlying) + "ms!", "Flagged player may be using: &3♦ &l&f&lF&e&ll&6&ly &3♦ &l&9&lK&1&li&5&ll&d&ll&b&la&3&lu&9&lr&1&la"); if ((Math.abs(System.currentTimeMillis() - data.lastFlying) == 0) || (Math.abs(data.lastFlying) == 0) || (Math.abs(System.currentTimeMillis()) == 0)) { } else if (Math.abs(System.currentTimeMillis() - data.lastFlying) < 5) { ++buffer; getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "A player sent a fly packet too late! The packet was: " + (5 - (Math.abs(System.currentTimeMillis() - data.lastFlying))) + "ms too late!"); if (buffer > 1) { flag(event.getPlayer(), "Player sent flying packet too late: " + Math.abs(System.currentTimeMillis() - data.lastFlying) + "ms!", "Flagged player may be using: &3♦ &l&f&lF&e&ll&6&ly &3♦ &l&9&lK&1&li&5&ll&d&ll&b&la&3&lu&9&lr&1&la"); } } else { if (buffer > 0) { getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "Player's late fly packet has been buffered!"); } buffer = 0; } } else { data.lastFlying = System.currentTimeMillis(); } } }); } }
Check类
package cc.funkemunky.tenkac.checks; import cc.funkemunky.tenkac.AntiCheat; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.Listener; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import static org.bukkit.Bukkit.getServer; public abstract class Check implements Listener { protected String name; protected CheckType type; protected boolean enabled; protected boolean punishable; protected int max; public Map<Player, Integer> violations = new WeakHashMap<>(); public Check(String name, CheckType type, boolean enabled, boolean punishable, int max) { this.name = name; this.type = type; this.enabled = enabled; this.punishable = punishable; this.max = max; Bukkit.getPluginManager().registerEvents(this, AntiCheat.getInstance()); } synchronized protected void flag(Player player, String... information) { int violations = this.violations.getOrDefault(player, 0) + 1; if (information != null) { StringBuilder formattedInfo = new StringBuilder(); for (String string : information) { formattedInfo.append(string).append(", "); } for (Player staff : Bukkit.getOnlinePlayers()) { if (staff.hasPermission("anticheat.staff")) { staff.sendMessage(ChatColor.translateAlternateColorCodes('&', "&1[&b&lPvPCamp&1] &9" + player.getName() + " &7has been detected for &9" + name + " &1[&3" + formattedInfo.toString() + "&1]" + "&1(&b" + violations + "&1)")); } } } else { } if (violations > max) { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { } player.kickPlayer("You have been kicked for " + name); } this.violations.put(player, violations); if (violations > 0) { getServer().getConsoleSender().sendMessage(ChatColor.DARK_PURPLE + player.getName() + " has " + violations + " violations!"); } } }
错误原因与修复方案
错误原因
ProtocolLib的onPacketReceiving回调运行在Netty异步线程中,而Spigot的Player#kickPlayer()属于Bukkit核心API操作,必须在服务器主线程执行。直接在异步线程调用会触发Spigot的AsyncCatcher安全检查,抛出异常。此外,原代码中TimeUnit.MILLISECONDS.sleep(50);完全无用,还会阻塞异步线程,影响网络处理效率。
修复方案
修改Check类的flag方法,将踢人操作通过Bukkit调度器提交到主线程执行:
修改后的Check类代码
package cc.funkemunky.tenkac.checks; import cc.funkemunky.tenkac.AntiCheat; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.Listener; import java.util.Map; import java.util.WeakHashMap; import static org.bukkit.Bukkit.getServer; public abstract class Check implements Listener { protected String name; protected CheckType type; protected boolean enabled; protected boolean punishable; protected int max; public Map<Player, Integer> violations = new WeakHashMap<>(); public Check(String name, CheckType type, boolean enabled, boolean punishable, int max) { this.name = name; this.type = type; this.enabled = enabled; this.punishable = punishable; this.max = max; Bukkit.getPluginManager().registerEvents(this, AntiCheat.getInstance()); } synchronized protected void flag(Player player, String... information) { int violations = this.violations.getOrDefault(player, 0) + 1; if (information != null) { StringBuilder formattedInfo = new StringBuilder(); for (String string : information) { formattedInfo.append(string).append(", "); } for (Player staff : Bukkit.getOnlinePlayers()) { if (staff.hasPermission("anticheat.staff")) { staff.sendMessage(ChatColor.translateAlternateColorCodes('&', "&1[&b&lPvPCamp&1] &9" + player.getName() + " &7has been detected for &9" + name + " &1[&3" + formattedInfo.toString() + "&1]" + "&1(&b" + violations + "&1)")); } } } if (violations > max) { // 将踢人操作提交到主线程执行 Bukkit.getScheduler().runTask(AntiCheat.getInstance(), () -> { player.kickPlayer("You have been kicked for " + name); }); } this.violations.put(player, violations); if (violations > 0) { getServer().getConsoleSender().sendMessage(ChatColor.DARK_PURPLE + player.getName() + " has " + violations + " violations!"); } } }
关键修改点
- 移除了无用的
sleep代码块,避免阻塞异步线程 - 使用
Bukkit.getScheduler().runTask()将踢人逻辑包装到主线程执行,符合Spigot线程安全要求 - 简化空分支代码,保持代码整洁
内容的提问来源于stack exchange,提问作者user23249491
相关产品推荐
相关产品推荐

