Bug 60910 - PDE Container sometimes returs an empty array of classpath entries
Summary: PDE Container sometimes returs an empty array of classpath entries
Status: RESOLVED FIXED
Alias: None
Product: PDE
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 3.0   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 3.0 M9   Edit
Assignee: PDE-UI-Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2004-05-04 11:29 EDT by Wassim Melhem CLA
Modified: 2004-05-11 00:54 EDT (History)
6 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Wassim Melhem CLA 2004-05-04 11:29:34 EDT
Although I have not detected a definite pattern, upon starting the workbench, 
if you try to do something right away like open a plugin.xml file or Search 
for a Java type, the classpath container of some projects would resolve to 
nothing.

This indicates that the following method is return an empty array:
public IClasspathEntry[] getClasspathEntries() {
    if (model == null) return new IClasspathEntry[0];
    if (entries == null) {
	entries = ClasspathUtilCore.computePluginEntries(model);
	entries = verifyWithAttachmentManager(entries);
    }
    return entries;
}

So it could be that either:
1. the model is null.  This would occur if PDE has not created a plug-in model 
for the Java project when JDT/Core has asked it to resolve the container.  If 
PDE reacts to POST_CHANGE before JDT/Core, it would have a chance to 
initialize the model before JDT, and this problem would/should not occur.

2. entries = ClasspathUtilCore.computePluginEntries(model) is returning an 
empty array.  This would mean that PDE models are not initialized.  This seems 
unlikely because the particular model associated with this container is 
initialized, and all models are initialized at the same time.  However, if 
this is the culprit scenario, it is a dangerous one since the empty array is 
now cached in the PDE container.
Comment 1 Wassim Melhem CLA 2004-05-05 00:07:03 EDT
The method where the master table of plug-ins gets initialized was not thread-
safe, and a flawed boolean variable was being used to avoid reentrant code.

Here is what was happening:
The table of plug-in models is lazily initialized.
When one thread (namely the "All Types Caching" thread) enters the 
initializeTable() method below, it marks the model manager as locked with a 
variable and starts initializing models.  Now another thread ("Main") enters 
initializeTable() to resolve the classpath container, it will exit the method 
silently at line 1 because workspaceManager.isLocked() will return true, so 
the model for that particular project would be null, and we will give back to 
JDT/Core an empty classpath.


private void initializeTable() {
    if (workspaceManager == null || workspaceManager.isLocked()) return;
    entries = new HashMap();
    // now go into the workspace model manager, mark it as locked with a   
variable, initialize it, mark it as unlocked and return.
.....
}


Fixed by marking the intializer methods as synchronized.  Once a thread makes 
it into the method, if the hashtable containing the models is null (i.e. this 
is the first thread), it initializes the models.  Second thread blocks when 
thread 1 is executing the method.  Once the second thread enters the method, 
the hashtable is not null (since this thread was blocked until the first one 
finished initializing), the second thread in this case does nothing and 
returns.

private synchronized void initializeTable() {
    if (workspaceManager == null || entries != null) return;
    entries = new TreeMap();
.....
}

Before putting the change in, I was getting the problem very frequently in my 
runtime workbench.
After the change, the problem has not reoccurred.

So I will mark this as fixed and will release the code into the I-build.
Comment 2 Dejan Glozic CLA 2004-05-05 08:01:10 EDT
Man, I hate threads :-).
Comment 3 John Arthorne CLA 2004-05-05 10:09:58 EDT
If you want an extra set of eyes to look at the fix, I don't mind taking a
look... I've seen my share of threading issues over the past year :) Just point
me at the class...
Comment 4 Wassim Melhem CLA 2004-05-05 10:16:03 EDT
Thank you John.  Please do so.
The method in question is 
org.eclipse.pde.internal.core.PluginModelManager.initializeTable() in the 
org.eclipse.pde.core plug-in.
Comment 5 John Arthorne CLA 2004-05-05 11:58:46 EDT
I have a couple of recommendations:

1. Remove double-test around call to initializeTable. Say Thread 1 is inside
initializeTable, and has run past the second line:

private synchronized void initializeTable() {
	if (workspaceManager == null || entries != null) return;
	entries = new TreeMap();
	//execution of Thread 1 is here

Now say that Thread 2 calls the public getPlugins() method. Since "entries" is
non-null, the first two checks are skipped, and it never enters the
initializeTable method. It then proceeds to iterate over the table entries:

public IPluginModelBase[] getPlugins() {
	if (entries == null)
		initializeTable();
	if (entries == null)
		return new IPluginModelBase[0];
	Collection values = entries.values();//Thread 2 gets here

-> You now have Thread 1 writing to the "entries" map, and Thread 2 reading the
same map. This is not allowed according to spec of TreeMap (and will thus fail
in unspecified ways), and also I suspect it will cause other threads to read
partially initialized models.

I recommend removing the "entries==null" check and *always* calling
initializeTable() prior to any access to the table (perhaps enforced by a table
accessor method and avoiding all references to the field outside that accessor).

2. The "entries" map is modified during model updates, and it looks like this
can happen during resource change notification. If someone reads the "entries"
table while it is being updated, this is again not supported by TreeMap and
could fail in mysterious ways. I recommend making "entries" thread safe unless
you can guarantee that it will never be read during modification. The two
traditional techniques here are either wrapping "entries" in a synchronized map
(Collections.synchronizedMap), or creating a new copy of the table every time
you modify it. The second option is more efficient than a synchronized map if
the table is rarely modified. See for example ResourceInfo.setSessionProperty
for an example of a table copy-on-write pattern.

3. I know you guys hate source code comments, but I recommend putting some
source comments in whenever the code must maintain thread safety rules (i.e.,
"field x must never be accessed directly", "field y must never be written
outside method foo", "this method is synchronized to protect structure x", etc).
Threading bugs have a way of creeping back in despite best efforts otherwise. If
you later decide to synchronize a different method in the same class for a
different reason, you may expose yourself to unneccessary locking, deadlock, etc.
Comment 6 Wassim Melhem CLA 2004-05-05 13:33:25 EDT
Thanks John (aka. concurrency police :-) for your recommendations.
Will implement them all for the next I-build.
Comment 7 Wassim Melhem CLA 2004-05-05 16:23:31 EDT
Reopening...
Comment 8 Wassim Melhem CLA 2004-05-11 00:54:25 EDT
Implemented all recommendations made in comment #5.
Code will be in the 0511-0800 I-build.