From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 3 Mar 2016 01:17:12 -0600
Subject: [PATCH] Ensure commands are not ran async

Plugins calling Player.chat("/foo") or Server.dispatchCommand() could
trigger the server to execute a command while on another thread.

These commands would then process EXPECTING to be on the main thread, leaving to
very hard to trace concurrency issues.

This change will synchronize the command execution back to the main thread, causing a
big slowdown in execution but throwing an exception at same time to raise awareness
that it is happening so that plugin authors can fix their code to stop executing commands async.

diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
index 914366afcdffe616d1403de4143205033860dbcb..5e8739462eface8b3bbc7ef8c0e08763e45c5b2e 100644
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
@@ -1570,6 +1570,29 @@ public class PlayerConnection implements PacketListenerPlayIn {
         }
 
         if (!async && s.startsWith("/")) {
+            // Paper Start
+            if (!org.spigotmc.AsyncCatcher.shuttingDown && !org.bukkit.Bukkit.isPrimaryThread()) {
+                final String fCommandLine = s;
+                MinecraftServer.LOGGER.log(org.apache.logging.log4j.Level.ERROR, "Command Dispatched Async: " + fCommandLine);
+                MinecraftServer.LOGGER.log(org.apache.logging.log4j.Level.ERROR, "Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable());
+                Waitable wait = new Waitable() {
+                    @Override
+                    protected Object evaluate() {
+                        chat(fCommandLine, false);
+                        return null;
+                    }
+                };
+                minecraftServer.processQueue.add(wait);
+                try {
+                    wait.get();
+                    return;
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on!
+                } catch (Exception e) {
+                    throw new RuntimeException("Exception processing chat command", e.getCause());
+                }
+            }
+            // Paper End
             this.handleCommand(s);
         } else if (this.player.getChatFlags() == EnumChatVisibility.SYSTEM) {
             // Do nothing, this is coming from a plugin
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 99083a1f36f95a4fb3d67f903d02b84d6ebae3d2..697246492a4bf31e84ce3d9f4e35c46f54b7eb7d 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -720,6 +720,29 @@ public final class CraftServer implements Server {
         Validate.notNull(commandLine, "CommandLine cannot be null");
         org.spigotmc.AsyncCatcher.catchOp("command dispatch"); // Spigot
 
+        // Paper Start
+        if (!org.spigotmc.AsyncCatcher.shuttingDown && !Bukkit.isPrimaryThread()) {
+            final CommandSender fSender = sender;
+            final String fCommandLine = commandLine;
+            Bukkit.getLogger().log(Level.SEVERE, "Command Dispatched Async: " + commandLine);
+            Bukkit.getLogger().log(Level.SEVERE, "Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable());
+            org.bukkit.craftbukkit.util.Waitable<Boolean> wait = new org.bukkit.craftbukkit.util.Waitable<Boolean>() {
+                @Override
+                protected Boolean evaluate() {
+                    return dispatchCommand(fSender, fCommandLine);
+                }
+            };
+            net.minecraft.server.MinecraftServer.getServer().processQueue.add(wait);
+            try {
+                return wait.get();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on!
+            } catch (Exception e) {
+                throw new RuntimeException("Exception processing dispatch command", e.getCause());
+            }
+        }
+        // Paper End
+
         if (commandMap.dispatch(sender, commandLine)) {
             return true;
         }
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
index ddef523ea8762c927f37f7d16d581e43367e8c6b..70f8d42992aa348ef7b2d03d22cdd59d7c73f0fe 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -13,6 +13,7 @@ public class ServerShutdownThread extends Thread {
     public void run() {
         try {
             org.spigotmc.AsyncCatcher.enabled = false; // Spigot
+            org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper
             server.close();
         } finally {
             try {
diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java
index aeed7697254af17ffefe8e578353ad216e15f9f3..9f7d2ef932ab41cef5d3d0736d20a7c7e4a2c888 100644
--- a/src/main/java/org/spigotmc/AsyncCatcher.java
+++ b/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -6,6 +6,7 @@ public class AsyncCatcher
 {
 
     public static boolean enabled = true;
+    public static boolean shuttingDown = false; // Paper
 
     public static void catchOp(String reason)
     {
diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
index e7b953ca312533f7ede12cdab42b2289aefc3b95..ccea803f58e09067cc998c62ffa134d6604878ff 100644
--- a/src/main/java/org/spigotmc/RestartCommand.java
+++ b/src/main/java/org/spigotmc/RestartCommand.java
@@ -43,6 +43,7 @@ public class RestartCommand extends Command
     private static void restart(final String restartScript)
     {
         AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
+        org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper
         try
         {
             String[] split = restartScript.split( " " );
