From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 9 Sep 2018 00:32:05 -0400
Subject: [PATCH] Remove deadlock risk in firing async events

The PluginManager incorrectly used synchronization on firing any event
that was marked as synchronous.

This synchronized did not even protect any concurrency risk as
handlers were already thread safe in terms of mutations during event
dispatch.

The way it was used, has commonly led to deadlocks on the server,
which results in a hard crash.

This change removes the synchronize and adds some protection around enable/disable

diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
index a8dbf282d0a8fb57f4719b9e95199894d8d3324c..b4069dbf31587786da39f5e387a71b7bc6a5d0ae 100644
--- a/src/main/java/org/bukkit/entity/Entity.java
+++ b/src/main/java/org/bukkit/entity/Entity.java
@@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable;
  */
 public interface Entity extends Metadatable, CommandSender, Nameable, PersistentDataHolder {
 
-    /**
+    /*
      * Gets the entity's current position
      *
      * @return a new copy of Location containing the position of this entity
diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
index 8bb24f734f0bbb5df37dff2f376a450b8be9be28..8355f9f0eb1b554bf6cfd3883afd2bdd2bf8c357 100644
--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
@@ -462,7 +462,7 @@ public final class SimplePluginManager implements PluginManager {
      * @return true if the plugin is enabled, otherwise false
      */
     @Override
-    public boolean isPluginEnabled(@Nullable Plugin plugin) {
+    public synchronized boolean isPluginEnabled(@Nullable Plugin plugin) { // Paper - synchronize
         if ((plugin != null) && (plugins.contains(plugin))) {
             return plugin.isEnabled();
         } else {
@@ -471,7 +471,7 @@ public final class SimplePluginManager implements PluginManager {
     }
 
     @Override
-    public void enablePlugin(@NotNull final Plugin plugin) {
+    public synchronized void enablePlugin(@NotNull final Plugin plugin) { // Paper - synchronize
         if (!plugin.isEnabled()) {
             List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin);
 
@@ -509,7 +509,7 @@ public final class SimplePluginManager implements PluginManager {
     }
 
     @Override
-    public void disablePlugin(@NotNull final Plugin plugin, boolean closeClassloader) {
+    public synchronized void disablePlugin(@NotNull final Plugin plugin, boolean closeClassloader) { // Paper - synchronize
         // Paper end - close Classloader on disable
         if (plugin.isEnabled()) {
             try {
@@ -579,6 +579,7 @@ public final class SimplePluginManager implements PluginManager {
             defaultPerms.get(false).clear();
         }
     }
+    private void fireEvent(Event event) { callEvent(event); } // Paper - support old method incase plugin uses reflection
 
     /**
      * Calls an event with the given details.
@@ -587,23 +588,13 @@ public final class SimplePluginManager implements PluginManager {
      */
     @Override
     public void callEvent(@NotNull Event event) {
-        if (event.isAsynchronous()) {
-            if (Thread.holdsLock(this)) {
-                throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
-            }
-            if (server.isPrimaryThread()) {
-                throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread.");
-            }
-        } else {
-            if (!server.isPrimaryThread()) {
-                throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from another thread.");
-            }
+        // Paper - replace callEvent by merging to below method
+        if (event.isAsynchronous() && server.isPrimaryThread()) {
+            throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
+        } else if (!event.isAsynchronous() && !server.isPrimaryThread()) {
+            throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
         }
 
-        fireEvent(event);
-    }
-
-    private void fireEvent(@NotNull Event event) {
         HandlerList handlers = event.getHandlers();
         RegisteredListener[] listeners = handlers.getRegisteredListeners();
 
diff --git a/src/test/java/org/bukkit/plugin/PluginManagerTest.java b/src/test/java/org/bukkit/plugin/PluginManagerTest.java
index f188cd4f3b07027c30d41f1162db77a506b7b6bb..1941c9f49e9514c1236c5f4ea9f7af47f7be85c5 100644
--- a/src/test/java/org/bukkit/plugin/PluginManagerTest.java
+++ b/src/test/java/org/bukkit/plugin/PluginManagerTest.java
@@ -17,7 +17,7 @@ public class PluginManagerTest {
     private static final PluginManager pm = TestServer.getInstance().getPluginManager();
 
     private final MutableObject store = new MutableObject();
-
+/* // Paper start - remove unneeded test
     @Test
     public void testAsyncSameThread() {
         final Event event = new TestEvent(true);
@@ -28,14 +28,14 @@ public class PluginManagerTest {
             return;
         }
         throw new IllegalStateException("No exception thrown");
-    }
+    }*/ // Paper end
 
     @Test
     public void testSyncSameThread() {
         final Event event = new TestEvent(false);
         pm.callEvent(event);
     }
-
+/* // Paper start - remove unneeded test
     @Test
     public void testAsyncLocked() throws InterruptedException {
         final Event event = new TestEvent(true);
@@ -129,7 +129,7 @@ public class PluginManagerTest {
         if (store.value == null) {
             throw new IllegalStateException("No exception thrown");
         }
-    }
+    } */ // Paper
 
     @Test
     public void testRemovePermissionByNameLower() {
