Bug 150505 - ModelManagerImpl should not synchronize model creation
Summary: ModelManagerImpl should not synchronize model creation
Status: RESOLVED FIXED
Alias: None
Product: WTP Source Editing
Classification: WebTools
Component: wst.sse (show other bugs)
Version: 1.5   Edit
Hardware: PC Windows XP
: P2 major (vote)
Target Milestone: 3.0   Edit
Assignee: David Williams CLA
QA Contact: David Williams CLA
URL:
Whiteboard:
Keywords: performance
Depends on:
Blocks:
 
Reported: 2006-07-13 09:32 EDT by David Slubicki CLA
Modified: 2008-02-27 22:21 EST (History)
7 users (show)

See Also:


Attachments
prototype with ModelProxy implementation (77.70 KB, patch)
2006-07-14 00:22 EDT, David Slubicki CLA
no flags Details | Diff
slight change in setId(String) to make this prototype work (47.81 KB, patch)
2006-07-14 00:28 EDT, David Slubicki CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description David Slubicki CLA 2006-07-13 09:32:46 EDT
Background
     ModelManagerImpl, a singleton, provides access to models via its various "get*Model*" methods, all of which are synchronized.  I understand the need to synchronize the retrieving/releasing of models, given that ModelManagerImpl manages the lifecycle of the shared models (keeping reference counts, etc).  The problem with the implementation (as I see it) is that most of the "get*Model*" methods will actually create the requested model if one does not exist, and creating a model is, potentially, very time consuming.  Various background threads (builders, validators, indexers, etc) competing with the main thread for access to ModelManagerImpl becomes, in certain scenarios, a performance bottleneck.
     
Potential Fix
     With limited knowledge of ModelManagerImpl, I just want to put this idea out there to see what is thought about its feasibility.
     My thought is that we could introduce an internal,  light-weight "model proxy" class (ModelProxy), instances of which would represent structured models.  ModelManagerImpl would synchronize solely on retrieving/releasing ModelProxy instances (which should be very fast) as opposed to instantiating and managing the actual IStructuredModel objects.  An individual ModelProxy instance would then provide synchronized access to its underlying model.  The first attempt to acquire the model from the proxy would result in the model being created.  As only the individual proxy instance associated with a specific model would be locked (via synchronization) during the creation of the underlying model, other threads attempting to acquire different models would not be blocked.  The proposed changes would be completely internal to ModelManagerImpl, with no API changes.
     

Test Implementation
     To test this idea, I decided to create a prototype (and I emphasize "prototype") by modifying the existing ModelManagerImpl.  This turned out to be far more work than I anticipated (the original class is around 2000 lines).  To manage the volume of changes I had to make, I first created an abstract inner class, ModelProxy, which defines the synchronized #getModel() method.  For each relevant public synchronized method I tried to apply the following pattern:
     - remove "synchronized" from the signature
     - create a corresponding private synchronized method with a return type of ModelProxy.   This method has the same name as the public method, with "_Proxy" appended to the end.  (note:  in some cases, a common "_Proxy"  method is used)
     - create a concrete subclass of ModelProxy for this method, overriding #getModel().  To this implementation of #getModel() I moved the existing logic for creating a model via this ModelManagerImpl method.
note:  I'm not suggesting we use this implementation - I wrote it solely to demonstrate the design.


Example Code
Logically, this is what model access currently looks like.  I condensed several methods down to one for clarity (note that it is synchronized):

public synchronized IStructuredModel getModelForEdit(IFile iFile) {
     String id = calculateId(iFile);
     IStructuredModel model= getModel(id);
     if (model == null) {
          model = createModel(iFile, EDIT);
          fManagedModels.put(id, model);
     } else
          _incrCount(model, EDIT);
}



Redesigned, this method would be broken into two  - looking something like this (note that only the private method is synchronized):

public getModelForEdit(IFile iFile) {
     IStructuredModel model = null;
     ModelProxy proxy = getModel_Proxy(iFile, EDIT);
     if (proxy != null)
          model = proxy.getModel();
     return model;
}

private synchronized ModelProxy getModel_Proxy(IFile iFile, ReadEditType rwType) {
     String id = calculateId(iFile);
     ModelProxy proxy= getModelProxy(id); //retrieves it from the dictionary
     if (proxy == null) {
          proxy = new ModelProxyByFile(id, iFile, rwType);
          fManagedProxies.put(id, proxy);
     } else
          _incrCount(proxy, rwType);

     return proxy;
}


The ModelProxyForFile class would look something like this:

ModelProxyByFile extends ModelProxy {
     ModelProxyByFile(String id, IFile file, ReadEditType rwType) {
          super(id, rwType);   // super will increment the reference count 
          ...  // store the file 
     }
     
     synchronized IStructuredModel getModel() {
          if (fModel == null) {
               // create the model
               // clear any data (passed in to the constructor) used in creating the model
          }                
          return fModel;
     }
}


I'll attach my prototype implementation, though I'm sure there must be many problems with it.  Also, I didn't know exactly what would be required to add this same sort of support for 
methods used to access/create IStructuredDocument objects, but I attempted to use a similar design.  A very small change was also needed in AbstractStructuredModel, which I'll attach.
Comment 1 David Slubicki CLA 2006-07-14 00:22:40 EDT
Created attachment 46281 [details]
prototype with ModelProxy implementation
Comment 2 David Slubicki CLA 2006-07-14 00:28:16 EDT
Created attachment 46282 [details]
slight change in setId(String) to make this prototype work
Comment 3 David Williams CLA 2006-07-29 18:38:35 EDT
Thanks for this detailed suggestion. See also bug 126133. 

Off hand, I don't think a proxy object is needed, but will keep this design in mind as we loosen ModelManager's synchronization. (Its definitely on the radar for 1.5.1, if we can do it safely, without breaking anyone, or changing "API" (psuedo API)). 

The main goal, that I think addresses your concern, is that we don't want one model being created (which, as you say, might take a while) to block other threads where the model might already exist, and could be returned quickly, and its UI updated, or, what ever is needs the model for. This is what's needed for UI Responsiveness. Responsiveness is the whole issue here, I think, as changing this threading behavior wouldn't change overall performance, per se. I just state this here in case I'm missing something and, in your scenerios, it does somehow make an overall performance difference, so you can explain it to me. :) 

Thanks again. 




Comment 4 David Williams CLA 2006-08-02 16:34:19 EDT
Hot bug request (and accepted) from IBM, as this problem greatly interferes with 
UI responsiveness (in some cases). 

Comment 5 Jeffrey Liu CLA 2006-08-31 13:49:32 EDT
Adding the performance keyword and jeffliu@ca.ibm.com to the CC list
Comment 6 David Williams CLA 2006-09-10 12:09:23 EDT
I've confimred with originator that "it would be insanity to make such a fundamental change to ModelManagerImpl at this point in the development cycle". 

Plus, luckily, the magnitude of this problem has lessened with other bug fixes (for example, previously, some models were much more expensive to create, due to some net access related bugs). 

Thus, I'm removing from hot list and untargetting. 

I've reduced the severity in keeping with our defined project defineded meanings 
https://bugs.eclipse.org/bugs/page.cgi?id=fields.html#bug_severity
Critical: crashes, loss of data, severe memory leak
Major: major loss of function
The loss of function in this case is that ModelManager is not an effective caching mechanism for existing models (models that have already been created, that is). 

BTW, just to document it, bug 126115 sort of "goes with" this one. They should both be fixed at the same time. 


Comment 7 Nitin Dahyabhai CLA 2008-02-27 22:21:00 EST
Resolved by bug 126133.