Community
Participate
Working Groups
Build: 2.1 A deadlock can occur if a Plugin.startup() method starts another thread, and then both threads attempt to load classes with the same classloader. Here is an example stack trace: Thread [Thread-1] (Suspended) FileWatcherPlugin.watchResource(IResource) line: 136 FileWatcherPlugin.watchProjects() line: 112 FileWatcherPlugin.startup() line: 78 PluginDescriptor$1.run() line: 728 InternalPlatform.run(ISafeRunnable) line: 889 PluginDescriptor.internalDoPluginActivation() line: 740 PluginDescriptor.doPluginActivation() line: 188 PluginDescriptor.getPlugin() line: 301 Workbench$15.run() line: 1325 InternalPlatform.run(ISafeRunnable) line: 889 Platform.run(ISafeRunnable) line: 413 Workbench$14.run() line: 1334 Thread.run() line: 534 [local variables unavailable] Thread [Thread-2] (Suspended) PluginDescriptor.isPluginActivated() line: 758 PluginClassLoader.internalFindClassParentsSelf(String, boolean, DelegatingURLClassLoader, boolean) line: 164 PluginClassLoader(DelegatingURLClassLoader).findClassParentsSelf(String, boolean, DelegatingURLClassLoader, boolean) line: 485 PluginClassLoader(DelegatingURLClassLoader).loadClass(String, boolean, DelegatingURLClassLoader, Vector, boolean) line: 882 PluginClassLoader(DelegatingURLClassLoader).loadClass(String, boolean) line: 862 PluginClassLoader(ClassLoader).loadClass(String) line: 235 [local variables unavailable] PluginClassLoader(ClassLoader).loadClassInternal(String) line: 302 [local variables unavailable] ResourceUpdateQueue$UpdateTimerTask.run() line: 25 TimerThread.mainLoop() line: 432 [local variables unavailable] TimerThread.run() line: 382 [local variables unavailable] Thread-1 is trying to load a class, but the class loader is busy Thread-2 is waiting on the PluginDescriptor lock, which is held by Thread-1
I understand what is happening. Usually, plugin activation happens as a side-effect of class loading. In this case, the plugin is being activated eagerly because it's a participant in the startup extension point. This means the flag PluginClassLoader.pluginActivationInProgress = false because the PluginClassLoader does not know that an activation is in progress. So, when Thread-2 tries to load classes, it gets past this flag and calls descriptor.isPluginActivated(). This method is synchronized, and Thread-1 holds the lock. When Thread-1 tries to do classloading, it cannot because Thread-2 is in a classloading sync block. I believe the solution should be to notify the PluginClassLoader that an activation is in progress in the explicit activation case. I.e., PluginDescriptor should set the flag pluginActivationInProgress to true, so that the class loader in Thread-2 does not block unnecessarily. Matt, even after we fix this, I should advise you that your code is a bit dangerous. It would be safer if you fork that thread in the very last line of your startup() method. That would ensure there is no contention between that thread's activity and the activity happening in your startup method. Since startup() already happens in a sync block, it would only take one more monitor to get deadlock. There is a comment in Plugin.startup() javadoc that mentions this possibility.
Created attachment 4540 [details] deadlock.zip, Simplified plugin to reproduce problem Attached is a plugin that reproduces the problem. It implements the early startup extension point. In startup(), it simply forks a thread that tries to load a class, and then tries to load another class.
Created attachment 4542 [details] Patch file on org.eclipse.core.runtime This patch is a proposed fix. It adds a method isActivationInProgress() to PluginDescriptor. The PluginClassLoader checks if activation is in progress before attempting to activate the plugin: if (pluginActivationInProgress || descriptor.isActivationInProgress() || descriptor.isPluginActivated()) { Also, I've made the "activePending" field volatile. This ensures the variable will have the same value in multiple threads.
Released the fix to HEAD