Community
Participate
Working Groups
We (Dali) adapt the platform interface IProject to the Dali interface ProjectResourceLocator. This Dali interface and its sole implementation, SimpleProjectResourceLocator, are in the plug-in o.e.jpt.common.core. The IAdapterFactory that implements this "adaptation" is in the same plug-in. And plugin.xml defining the corresponding extension to the o.e.core.runtime.adapters extension point is also in the same plug-in. The problem: Intermittently, during start-up, a call to IProject.getAdapter(ProjectResourceLocator.class) will return null with the first call but not null with subsequent calls. The adapter factory is implemented in such a way that it will never return null; so, apparently, the adapter factory is not invoked by the platform adapter manager. This seems to be a bug, as the plug-in must have been loaded for the class ProjectResourceLocator to be resolved. Unfortunately, in a test workspace where this happens with every start-up, it never happens when executing under the debugger (even with no breakpoints whatsoever). Is this expected behavior (i.e. that even though a plug-in is loaded, its adapter factories are not necessarily loaded)? If not, can anyone offer any hints as to how we can investigate, sans the debugger, why the platform adapter manager does not invoke the appropriate adapter factory?
This is not expected behaviour to me. It sounds like some kind of race condition where the bundle is not yet active at the time the adapter request comes in, although I don't see how that is possible if you are referencing a class from the plugin providing the adapter before-hand. If suspending in debugger alters the timing, the only debugging technique I can think of is stdout messages in plugin startup, factory instantiation, and adapter lookup to start with.
Brian and I have been looking at this issue, and after some println debugging were able to identify the cause of the race condition. The root of this issue is in AdapterFactoryProxy and its use of the 'factoryLoaded' flag. The summary is that 'factoryLoaded' can be set to true and accessed by another thread as true before the actual 'factory' has been assigned in the proxy. This results in null being returned for an adapter factory that could be successfully loaded by has not yet been loaded. Details: During workbench start-up, depending on the what the UI context is for a given workbench session, multiple Dali "JPA projects" may end up being built concurrently. In this situation, multiple threads end up making a call to IProject.getAdatper(...) around the same time to retrieve a particular adapter that is needed to build a JPA project. What ends up happening in the case where this fails (which is 50-75% of the time depending on the number and size of projects in the workspace) is: - a call is made to IProject.getAdatper(...) on Thread A - the AdapterFactoryProxy in question has already been pre-built, but the internal 'factory' has not yet been loaded (it is lazily loaded) - AdapterFactoryProxy.getAdapter(...) calls loadFactory(...) to load the real AdapterFactory, as 'factoryLoaded' is set to false - 'factoryLoaded' flag is set to true in loadFactory(...) before the 'factory' instance var has been assigned - Thread B has also called IProject.getAdatper(...), which has gone to the proxy, where the 'factoryLoaded' check in getAdapter(...) returns true but the 'factory' itself has not yet been assigned and therefore not actually loaded - As a result, Thread B's call to getAdapter(...) returns a factory proxy that has a null internal factory, which results in a return value of null from AdapterManager.getAdapter(...) Proposed solution: I have attached a patch that solves the issue by changing when the 'factoryLoaded' flag is set to true. The intended behavior of this flag should stay the same and avoid the race condition by setting the 'factoryLoaded' flag in a finally block. Since the 'factoryLoaded' flag is double-checked, it should prevent any redundant loading of the proxy's 'factory', given that the loadFactory(...) method is synchronized. Other notes: The AdapterFactoryProxy.loadFactory(...) method is synchronized, but the AdapterFactoryProxy.getAdapter(...) method is not, so we cannot rely on the synchronized loadFactory(...) method to prevent a race condition in relation to the 'factoryLoaded' flag.
Created attachment 231487 [details] Proposed patch
Great find!
Reviewing this now. Neil to get the formalities out of the way, can you state here that your contribution complies with the CoO: http://www.eclipse.org/legal/CoO.php
(In reply to comment #5) > Reviewing this now. Neil to get the formalities out of the way, can you > state here that your contribution complies with the CoO: > > http://www.eclipse.org/legal/CoO.php I confirm that the contribution complies with the CoO. In fact, I just now submitted my CLA for Eclipse.
Released to master yesterday: http://git.eclipse.org/c/equinox/rt.equinox.bundles.git/commit/?id=003ae109666066cce1aa56c25b39b6edc162926a
Also released in Kepler maintenance branch: http://git.eclipse.org/c/equinox/rt.equinox.bundles.git/commit/?h=R3_9_maintenance&id=0dcfc65cad9dbe82f62eea6820b52e2967ab6e1d