124 lines
		
	
	
	
		
			5.3 KiB
			
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			124 lines
		
	
	
	
		
			5.3 KiB
			
		
	
	
	
		
			Diff
		
	
	
	
	
	
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/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
 | 
						|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
						|
--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
 | 
						|
+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
 | 
						|
@@ -0,0 +0,0 @@ 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 {
 | 
						|
@@ -0,0 +0,0 @@ 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);
 | 
						|
 
 | 
						|
@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager {
 | 
						|
     // Paper end
 | 
						|
 
 | 
						|
     @Override
 | 
						|
-    public void disablePlugin(@NotNull final Plugin plugin) {
 | 
						|
+    public synchronized void disablePlugin(@NotNull final Plugin plugin) { // Paper - synchronize
 | 
						|
         if (plugin.isEnabled()) {
 | 
						|
             try {
 | 
						|
                 plugin.getPluginLoader().disablePlugin(plugin);
 | 
						|
@@ -0,0 +0,0 @@ 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.
 | 
						|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
						|
--- a/src/test/java/org/bukkit/plugin/PluginManagerTest.java
 | 
						|
+++ b/src/test/java/org/bukkit/plugin/PluginManagerTest.java
 | 
						|
@@ -0,0 +0,0 @@ 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);
 | 
						|
@@ -0,0 +0,0 @@ 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);
 | 
						|
@@ -0,0 +0,0 @@ public class PluginManagerTest {
 | 
						|
         if (store.value == null) {
 | 
						|
             throw new IllegalStateException("No exception thrown");
 | 
						|
         }
 | 
						|
-    }
 | 
						|
+    } */ // Paper
 | 
						|
 
 | 
						|
     @Test
 | 
						|
     public void testRemovePermissionByNameLower() {
 |