Bug 215885 - Deadlock between indexing and classloading
Summary: Deadlock between indexing and classloading
Status: RESOLVED WONTFIX
Alias: None
Product: Equinox
Classification: Eclipse Project
Component: Framework (show other bugs)
Version: 3.3.1   Edit
Hardware: Other Linux
: P3 major (vote)
Target Milestone: ---   Edit
Assignee: equinox.framework-inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: needinfo
Depends on:
Blocks:
 
Reported: 2008-01-19 09:15 EST by Stephan Herrmann CLA
Modified: 2009-05-21 17:21 EDT (History)
3 users (show)

See Also:


Attachments
corrected stacktrace (4.28 KB, text/plain)
2008-01-21 14:27 EST, Stephan Herrmann CLA
no flags Details
stacktrace showing reproducable deadlock in jdt.debug.tests (13.48 KB, text/plain)
2008-01-28 19:05 EST, Stephan Herrmann CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Stephan Herrmann CLA 2008-01-19 09:15:59 EST
Build ID: M20071023-1652

I could reproduce a deadlock during Eclipse startup.
From my analysis this is what happened:

1) JDT/Core had just spawned the indexer (which was still "warming-up")
2) the Activator of a third-party plug-in, here com.soyatec.uml.core_2.1.0, called JavaCore.setOptions(Hashtable)

Eventually the indexer thread needed to load a class (here: Index).
Concurrently JavaCore had to wait for the indexes to be ready.

Since I need to run Eclipse with the global classloading lock (osgi.classloader.singleThreadLoads=true, see bug 121737) 
to avoid other deadlocks, the Activator of the uml plug-in blocks any
classloading by other classloaders.

=> Indexer is waiting for class loading, 
=> class loader waits for bundle activator waits for indexer.

I paste the relevant stacktraces below.

Since this deadlock involves three parties (JDT/Core, equinox singleThreadLoads, and the uml plug-in) it's not me to decide, who's wrong.

Since doing heavy work in an Activator is bad style, I feel that JavaCore
should communicate to clients, that seemingly harmless methods like setOptions
may indeed perform heavy work, which should never be invoked from an Activator.

My workaround was simply to disable the uml plug-in (which I wasn't using very frequently any way, see the old version number 2.1.0 ;-)

Finally, here are the stacks:

Daemon Thread [Java indexing] (Suspended)       
        owns: IndexManager  (id=78)     
        waiting for: DefaultClassLoader  (id=80)        
        Object.wait(long) line: not available [native method]   
        DefaultClassLoader(Object).wait() line: 485     
        BundleLoader.lock(Object) line: 1089    
        BundleLoader.findClass(String, boolean) line: 383       
        BundleLoader.findClass(String) line: 372        
        DefaultClassLoader.loadClass(String, boolean) line: 83  
        DefaultClassLoader(ClassLoader).loadClass(String) line: 251     
        DefaultClassLoader(ClassLoader).loadClassInternal(String) line: 319     
        IndexManager.getIndex(IPath, IPath, boolean, boolean) line: 257 
        IndexManager.getIndexForUpdate(IPath, boolean, boolean) line: 277       
        AddJarFileToIndex.execute(IProgressMonitor) line: 75    
        IndexManager(JobManager).run() line: 392        
        Thread.run() line: 619 

Thread [main] (Suspended)       
        owns: RunnableLock  (id=79)     
        owns: RunnableLock  (id=77)     
        waiting for: IndexManager  (id=78)      
        IndexManager(JobManager).discardJobs(String) line: 83   
        ClasspathChange.requestIndexing() line: 452     
        SetContainerOperation(ChangeClasspathOperation).classpathChanged(JavaProject) line: 66  
        SetContainerOperation.executeOperation() line: 112      
        SetContainerOperation(JavaModelOperation).run(IProgressMonitor) line: 720       
        SetContainerOperation(JavaModelOperation).runOperation(IProgressMonitor) line: 780      
        JavaCore.java line: 4538        
        RequiredPluginsInitializer.initialize(IPath, IJavaProject) line: 33     
        JavaModelManager.initializeContainer(IJavaProject, IPath) line: 2218    
        JavaModelManager$7.run(IProgressMonitor) line: 2148     
        JavaModelManager.initializeAllContainers(IJavaProject, IPath) line: 2162        
        JavaModelManager.getClasspathContainer(IPath, IJavaProject) line: 1544  
        JavaCore.java line: 1590        
        JavaProject.resolveClasspath(JavaModelManager$PerProjectInfo) line: 2570        
        JavaProject.getResolvedClasspath() line: 1860   
        DeltaProcessingState.initializeRoots() line: 202        
        SetContainerOperation(JavaModelOperation).run(IProgressMonitor) line: 717       
        SetContainerOperation(JavaModelOperation).runOperation(IProgressMonitor) line: 780      
        JavaCore.java line: 4538        
        JREContainerInitializer.initialize(IPath, IJavaProject) line: 69        
        JavaModelManager.initializeContainer(IJavaProject, IPath) line: 2218    
        JavaModelManager.getClasspathContainer(IPath, IJavaProject) line: 1546  
        JavaCore.java line: 1590        
        JavaProject.resolveClasspath(JavaModelManager$PerProjectInfo) line: 2570        
        JavaProject.getResolvedClasspath() line: 1860   
        DeltaProcessor.createExternalArchiveDelta(IProgressMonitor) line: 802   
        DeltaProcessor.resourceChanged(IResourceChangeEvent) line: 1897 
        DeltaProcessingState.resourceChanged(IResourceChangeEvent) line: 368    
        NotificationManager$2.run() line: 282   
        SafeRunner.run(ISafeRunnable) line: 37  
        NotificationManager.notify(ResourceChangeListenerList$ListenerEntry[], IResourceChangeEvent, boolean) line: 276 
        NotificationManager.broadcastChanges(ElementTree, ResourceChangeEvent, boolean) line: 148       
        Workspace.broadcastPostChange() line: 311       
        Workspace.endOperation(ISchedulingRule, boolean, IProgressMonitor) line: 1018   
        Project.touch(IProgressMonitor) line: 1042      
        JavaModelManager$EclipsePreferencesListener.preferenceChange(IEclipsePreferences$PreferenceChangeEvent) line: 1276      
        EclipsePreferences$2.run() line: 746    
        SafeRunner.run(ISafeRunnable) line: 37  
        InstancePreferences(EclipsePreferences).firePreferenceEvent(String, Object, Object) line: 749   
        InstancePreferences(EclipsePreferences).put(String, String) line: 762   
        JavaModelManager.setOptions(Hashtable) line: 4112       
        JavaCore.java line: 4708        
        UMLPlugin.a() line: 84 [local variables unavailable]    
        UMLPlugin.start(BundleContext) line: 149        
        BundleContextImpl$2.run() line: 999     
        AccessController.doPrivileged(PrivilegedExceptionAction<T>) line: not available [native method] 
        BundleContextImpl.startActivator(BundleActivator) line: 993
... more ...
Comment 1 Frederic Fusier CLA 2008-01-21 11:24:05 EST
I tried to reproduce this issue and never was able to.

Two questions about the given stack trace:
1) it seems to have strange lines:
   - JavaCore.java line: 4708
   - JavaCore.java line: 4538        
   - JavaCore.java line: 1590        
   and none of these lines matches java code in JavaCore.java R3_3_1 version...
   How did you get it and are your sure you were using the referred build ID?
2) I do not see another thread requesting a class loader which may explain
   the lock. Can you attach to this bug the complete stack trace?
Comment 2 Stephan Herrmann CLA 2008-01-21 14:27:44 EST
Created attachment 87434 [details]
corrected stacktrace

I'm sorry about incomplete/incorrect information!

I forgot that we do have a few source level changes in classes JavaCore,
JavaProject and JavaModelManager. I strongly believe that these changes
are unrelated to the observed deadlock, but line numbers are broken
wrt. any version in your CVS. Sorry (plus the debugger swallowed some 
method names/signatures due to our use of JSR 045, but that's not a bug..)

I currently sit at a different computer, so I cannot easily reproduce,
but I tried to translate broken line number information back to a clean
state as of v_780.
Please excuse if I made mistakes during this manual correction.

> 2) I do not see another thread requesting a class loader which may explain
>   the lock. Can you attach to this bug the complete stack trace?

Sorry again for the incomplete stacktrace. Let me try to explain better
(for the lack of a more complete stacktrace, currently):

Due to the global locking strategy, thread [Java indexing] tries to
acquire the global classloading lock using BundleLoader.lock(Object).

However, this global lock is owned by thread [main] because startActivator
at the bottom of stack [main] is called from within class-loading.
Stackframes of thread [main] mentioning BundleLoader omitted at 
"... more ..." 
:( 


I did hope you could quickly confirm that calling JavaCore-methods from
within an Activator might be dangerous. If you do want to reproduce I 
suggest the following (perhaps not telling much news here):
+ In the launch config pass option "-Dosgi.classloader.singleThreadLoads=true"
  as a vmarg.
+ have a workspace with some indexing to be done a startup
+ set a breakpoint at the first line of IndexManager.reset()
  => once hit, we have the lock on IndexManager
+ set another breakpoint at UMLPlugin.start() 
  (or whatever client you use to trigger setOptions from)
  => once hit, this thread has the classloading lock
+ Now launch to run up-to these two breakpoints
+ now set a breakpoint at JobManager.discardJobs(String) and resume [main],
  which should eventually hit the new breakpoint, but should not be
  able to enter the "synchronized(this)" block, if "this" is the IndexManager
+ resume thread [Java indexing] and hope it needs to do some class loading.

Do you see it?
Comment 3 Stephan Herrmann CLA 2008-01-28 19:05:11 EST
Created attachment 88076 [details]
stacktrace showing reproducable deadlock in jdt.debug.tests

I attach a stacktrace that I get reproducably when running jdt.debug.tests
in an unmodified Eclipse 3.3.0. Slightly different situation by symptoms
appear to be the same as before.
Comment 4 Stephan Herrmann CLA 2008-01-28 19:22:33 EST
Steps for reproducing the stacktrace I just attached:

1. Clean Eclipse 3.3 with two projects in the workspace:
     org.eclipse.test.performance
     org.eclipse.jdt.debug.tests

2. Launch configuration (JUnit Plugin Tests) with these settings
     Project: org.eclipse.jdt.debug.tests
     Test class: org.eclipse.jdt.debug.tests.AutomatedSuite
     Run an application: org.eclipse.ui.ide.Workbench
     VM Arguments: -Dosgi.classloader.singleThreadLoads=true

Everything else at defaults.

3. Set the following line breakpoints (maybe not all are needed, but
   this set is dead-safe):
     ContentDescriptionManager [line: 438] - setCacheState(byte)
     IndexManager [line: 255] - getIndex(IPath, IPath, boolean, boolean)
     LaunchingPlugin [line: 479] - start(BundleContext)
     SourceIndexer [line: 45] - indexDocument()

Debug the above launch configuration, see the debugger stop at breakpoints,
after waiting a little, resume, and watch the process getting locked
in a state, where some threads are actively looping a dead loop, i.e.,
waiting for an unreachable state.

Stacktraces are a little different from previous stacks, but I see these
common ingredients:
+ Indexer is working, trying to load a class, blocked
+ Other plugin's Activator (here LaunchingPlugin) calls JavaCore.setOptions,
  which tries to schedule a Job, blocked while blocking class loading.

The part I don't see is how Job scheduling contributes to the dead state.



Comment 5 Frederic Fusier CLA 2008-01-29 05:32:30 EST
I can reproduce with 3.3 but not with 3.4M4. So, some changes in the Equinox framework seemed to have fix this issue => move to Equinox/Framework component to identify which one and set as duplicate...
Comment 6 Thomas Watson CLA 2008-02-13 12:01:09 EST
So far I have not been able to reproduce on either 3.3 or 3.4.  I'm not aware of a fix that would have helped with this deadlock.
Comment 7 Stephan Herrmann CLA 2008-04-05 10:17:39 EDT
OK, I found a significant difference in HEAD: three lines deleted
from org.eclipse.jdt.debug.tests.AbstractDebugTest (at the end of <init>):

  if (!(this.getClass() == ProjectCreationDecorator.class) && !getJavaProject().exists()) {
	new TestSuite(ProjectCreationDecorator.class).run(new TestResult());
  }

If you re-add these lines, the deadlock can still be reproduced in 3.4M6.

Run to the following breakpoints:
thread main: LaunchingPlugin.start(BundleContext) line: 472 
thread Java indexing: IndexManager.getIndex(IPath, IPath, boolean, boolean) line: 265	

Try to step the main thread past the invocation of JavaCore.setOptions(optionsMap);
This thread will hang while trying to schedule an indexer job.
Now continue stepping the indexer thread, which will cause loading
some more classes 
(I see org.eclipse.jdt.internal.core.search.indexing.ReadWriteMonitor) 
which blocks in BundleLoader.lock(Object) line: 1158	
(Note that I'm running with singleThreadLoads=true).

Does this help you reproduce in 3.4?
Comment 8 Stephan Herrmann CLA 2008-06-07 16:07:31 EDT
Given that osgi.classloader.singleThreadLoads is mostly dead (cf. bug 227587),
I assume this issue became irrelevant, too, since the deadlock was observed
only with osgi.classloader.singleThreadLoads=true

Should it then be closed as WONTFIX?
Comment 9 Stephan Herrmann CLA 2009-05-21 17:21:11 EDT
closing as per previous comment.