Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 316780 - [ds] ClassCircularityError
Summary: [ds] ClassCircularityError
Status: RESOLVED FIXED
Alias: None
Product: Equinox
Classification: Eclipse Project
Component: Compendium (show other bugs)
Version: 3.6   Edit
Hardware: All Mac OS X - Carbon (unsup.)
: P3 major (vote)
Target Milestone: 3.6.1   Edit
Assignee: Stoyan Boshev CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-06-14 11:34 EDT by Bryan Hunt CLA
Modified: 2010-07-27 09:41 EDT (History)
5 users (show)

See Also:
tjwatson: review+


Attachments
testcase (13.60 KB, application/zip)
2010-06-14 11:34 EDT, Bryan Hunt CLA
no flags Details
experiment patch (1.05 KB, patch)
2010-06-16 21:49 EDT, Thomas Watson CLA
no flags Details | Diff
experiment patch 2 (1.16 KB, text/plain)
2010-06-17 09:34 EDT, Thomas Watson CLA
no flags Details
proposed patch 3 (5.92 KB, text/plain)
2010-07-07 11:00 EDT, Stoyan Boshev CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Bryan Hunt CLA 2010-06-14 11:34:22 EDT
Created attachment 171842 [details]
testcase

Eclipse 3.6 build I20100608-0911

In trying to create a testcase to reproduce an EMF problem, I ran across a ClassCircularityError in DS.  I've reduced the testcase to eliminate EMF.  If you use the supplied launcher, you should get the following on the console:


osgi> Activating company Y's secret code
!SESSION 2010-06-14 10:24:15.980 -----------------------------------------------
eclipse.buildId=unknown
java.version=1.6.0_20
java.vendor=Apple Inc.
BootLoader constants: OS=macosx, ARCH=x86_64, WS=cocoa, NL=en_US
Command-line arguments:  -dev file:/Users/bhunt/Documents/Programming/Code/Workspaces/ds/.metadata/.plugins/org.eclipse.pde.core/ds/dev.properties -os macosx -ws cocoa -arch x86_64 -consoleLog -console

!ENTRY org.eclipse.equinox.ds 4 0 2010-06-14 10:24:17.682
!MESSAGE Error while building configuration of component Component[
	name = com.companyX.core.service
	activate = activate
	deactivate = deactivate
	modified = 
	configuration-policy = optional
	factory = null
	autoenable = true
	immediate = true
	implementation = com.companyX.internal.core.OurService
	state = Unsatisfied
	properties = 
	serviceFactory = false
	serviceInterface = null
	references = {
		Reference[name = IService, interface = com.companyY.core.IService, policy = static, cardinality = 1..1, target = null, bind = bindService, unbind = null]
	}
	located in bundle = com.companyX.core_1.0.0.qualifier [2]
]
!STACK 0
java.lang.ClassCircularityError: com/companyY/core/IService
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2427)
	at java.lang.Class.getDeclaredMethod(Class.java:1935)
	at org.eclipse.equinox.internal.ds.model.ComponentReference.getMethod(ComponentReference.java:96)
	at org.eclipse.equinox.internal.ds.model.ComponentReference.bind(ComponentReference.java:315)
	at org.eclipse.equinox.internal.ds.model.ServiceComponentProp.bindReference(ServiceComponentProp.java:413)
	at org.eclipse.equinox.internal.ds.model.ServiceComponentProp.bind(ServiceComponentProp.java:209)
	at org.eclipse.equinox.internal.ds.model.ServiceComponentProp.build(ServiceComponentProp.java:327)
	at org.eclipse.equinox.internal.ds.InstanceProcess.buildComponent(InstanceProcess.java:580)
	at org.eclipse.equinox.internal.ds.InstanceProcess.buildComponents(InstanceProcess.java:196)
	at org.eclipse.equinox.internal.ds.Resolver.getEligible(Resolver.java:323)
	at org.eclipse.equinox.internal.ds.SCRManager.serviceChanged(SCRManager.java:221)
	at org.eclipse.osgi.internal.serviceregistry.FilteredServiceListener.serviceChanged(FilteredServiceListener.java:104)
	at org.eclipse.osgi.framework.internal.core.BundleContextImpl.dispatchEvent(BundleContextImpl.java:933)
	at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:227)
	at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:149)
	at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEventPrivileged(ServiceRegistry.java:756)
	at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEvent(ServiceRegistry.java:711)
	at org.eclipse.osgi.internal.serviceregistry.ServiceRegistrationImpl.register(ServiceRegistrationImpl.java:130)
	at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.registerService(ServiceRegistry.java:206)
	at org.eclipse.osgi.framework.internal.core.BundleContextImpl.registerService(BundleContextImpl.java:507)
	at org.eclipse.equinox.internal.ds.InstanceProcess.registerService(InstanceProcess.java:496)
	at org.eclipse.equinox.internal.ds.InstanceProcess.buildComponents(InstanceProcess.java:212)
	at org.eclipse.equinox.internal.ds.Resolver.buildNewlySatisfied(Resolver.java:431)
	at org.eclipse.equinox.internal.ds.Resolver.enableComponents(Resolver.java:213)
	at org.eclipse.equinox.internal.ds.SCRManager.startedBundle(SCRManager.java:641)
	at org.eclipse.equinox.internal.ds.SCRManager.bundleChanged(SCRManager.java:234)
	at org.eclipse.osgi.framework.internal.core.BundleContextImpl.dispatchEvent(BundleContextImpl.java:919)
	at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:227)
	at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:149)
	at org.eclipse.osgi.framework.internal.core.Framework.publishBundleEventPrivileged(Framework.java:1349)
	at org.eclipse.osgi.framework.internal.core.Framework.publishBundleEvent(Framework.java:1300)
	at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:380)
	at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:284)
	at org.eclipse.osgi.framework.util.SecureAction.start(SecureAction.java:417)
	at org.eclipse.osgi.internal.loader.BundleLoader.setLazyTrigger(BundleLoader.java:265)
	at org.eclipse.core.runtime.internal.adaptor.EclipseLazyStarter.postFindLocalClass(EclipseLazyStarter.java:106)
	at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findLocalClass(ClasspathManager.java:453)
	at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.findLocalClass(DefaultClassLoader.java:216)
	at org.eclipse.osgi.internal.loader.BundleLoader.findLocalClass(BundleLoader.java:393)
	at org.eclipse.osgi.internal.loader.SingleSourcePackage.loadClass(SingleSourcePackage.java:33)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:466)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:422)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:410)
	at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
	at com.companyX.internal.core.Activator.start(Activator.java:15)
	at org.eclipse.osgi.framework.internal.core.BundleContextImpl$1.run(BundleContextImpl.java:783)
	at java.security.AccessController.doPrivileged(Native Method)
	at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:774)
	at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:755)
	at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:370)
	at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:284)
	at org.eclipse.osgi.framework.util.SecureAction.start(SecureAction.java:417)
	at org.eclipse.osgi.internal.loader.BundleLoader.setLazyTrigger(BundleLoader.java:265)
	at org.eclipse.core.runtime.internal.adaptor.EclipseLazyStarter.postFindLocalClass(EclipseLazyStarter.java:106)
	at org.eclipse.osgi.baseadaptor.loader.ClasspathManager.findLocalClass(ClasspathManager.java:453)
	at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.findLocalClass(DefaultClassLoader.java:216)
	at org.eclipse.osgi.internal.loader.BundleLoader.findLocalClass(BundleLoader.java:393)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:469)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:422)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:410)
	at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
	at org.eclipse.osgi.internal.loader.BundleLoader.loadClass(BundleLoader.java:338)
	at org.eclipse.osgi.framework.internal.core.BundleHost.loadClass(BundleHost.java:232)
	at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadClass(AbstractBundle.java:1197)
	at org.eclipse.equinox.internal.ds.model.ServiceComponent.createInstance(ServiceComponent.java:457)
	at org.eclipse.equinox.internal.ds.model.ServiceComponentProp.createInstance(ServiceComponentProp.java:254)
	at org.eclipse.equinox.internal.ds.model.ServiceComponentProp.build(ServiceComponentProp.java:315)
	at org.eclipse.equinox.internal.ds.InstanceProcess.buildComponent(InstanceProcess.java:580)
	at org.eclipse.equinox.internal.ds.InstanceProcess.buildComponents(InstanceProcess.java:196)
	at org.eclipse.equinox.internal.ds.Resolver.buildNewlySatisfied(Resolver.java:431)
	at org.eclipse.equinox.internal.ds.Resolver.enableComponents(Resolver.java:213)
	at org.eclipse.equinox.internal.ds.SCRManager.performWork(SCRManager.java:800)
	at org.eclipse.equinox.internal.ds.SCRManager$QueuedJob.dispatch(SCRManager.java:767)
	at org.eclipse.equinox.internal.ds.WorkThread.run(WorkThread.java:89)
	at org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor.run(Executor.java:70)
Activating company X's secret code
Comment 1 Pierre-Henry Perret CLA 2010-06-15 23:15:31 EDT
Why if you declare com.companyX.core.service as a local remote service ?
Comment 2 Pierre-Henry Perret CLA 2010-06-15 23:18:36 EDT
Sorry , I mean WHAT if com.companyX.core.service is declared as a remote local service ?
Comment 3 Bryan Hunt CLA 2010-06-15 23:50:18 EDT
(In reply to comment #2)
> Sorry , I mean WHAT if com.companyX.core.service is declared as a remote local
> service ?

I'm not sure what you mean by "remote local".  In my world remote services are related to RFC119 which has no relationship to this problem.
Comment 4 Stoyan Boshev CLA 2010-06-16 08:39:15 EDT
This looks like a duplicate of bug #308021.
Although this bug was evaluated and partially fixed in 3.6RC1 it was noted that there is no clean solution that would fully solve the ClassCircularityError issues.
A suitable workaround that would solve your ClassCircularityError issues if they appear is to remove the lazy activation policy for the bundle which DS components are involved in the ClassCircularityError error. Could you check if that works for you?
Comment 5 Bryan Hunt CLA 2010-06-16 08:49:45 EDT
I'll be glad to check the lazy activation policy.

What happens when the bundles really do come from two different companies and I don't have access to the source code?  OSGi just breaks?
Comment 6 Stoyan Boshev CLA 2010-06-16 08:57:16 EDT
(In reply to comment #5)
> What happens when the bundles really do come from two different companies and I
> don't have access to the source code?  OSGi just breaks?

Another workaround would be to change the start order of the bundles.
Comment 7 Bryan Hunt CLA 2010-06-16 09:00:09 EDT
> Another workaround would be to change the start order of the bundles.

That goes against OSGi best practices, but yes, that would be a workaround.
Comment 8 Thomas Watson CLA 2010-06-16 09:25:21 EDT
We are dealing with conflicting requirements here.  We want lazy activated bundles to make their services available synchronously at startup.  But this leads to firing more ServiceEvents that can lead to more components getting activated which then can lead to more lazy trigger classes getting loaded.

I agree with Stoyan, this is likely another variation on bug308021.  I don't think we can change the DS implementation to all of a sudden stop providing services from lazy activated bundles asynchronously.  That will undoubtedly cause many things to break in eclipse (p2 being one of them).

I wonder if a work around in DS is to catch the circularity error when building the component and moving the activation to another thread in that case.
Comment 9 Thomas Watson CLA 2010-06-16 21:49:44 EDT
Created attachment 172087 [details]
experiment patch

Here is a patch that is an attempt to do what I suggested in comment 8.  It seems to fix the issue from this testcase.  This is just an example of what I was suggesting, I am not confident or familiar enough with the DS code to suggest this as the real fix.  Stoyan, is this an appropriate approach to fixing this issue?

BTW, I know some older VMs throw LinkageErrors instead of ClassCircularityErrors but I think it would be a mistake to catch LinkageErrors.
Comment 10 Stoyan Boshev CLA 2010-06-17 05:35:57 EDT
(In reply to comment #9)
> Created an attachment (id=172087) [details]
> experiment patch
> 
> Here is a patch that is an attempt to do what I suggested in comment 8.  It
> seems to fix the issue from this testcase.  This is just an example of what I
> was suggesting, I am not confident or familiar enough with the DS code to
> suggest this as the real fix.  Stoyan, is this an appropriate approach to
> fixing this issue?

Thanks for the patch Tom. I had a quick look at it. It does not seems correct to me because the method buildComponent() must either return a built component instance or throw a ComponentException. In your case if ClassCircularityError is caught, the method will return null. Perhaps if you throw a ComponentException after the resolver.mgr.enqueueWork() call then it would be fine.
I will be able to make more analysis on this issue next week.
Comment 11 Thomas Watson CLA 2010-06-17 09:34:24 EDT
Created attachment 172116 [details]
experiment patch 2

(In reply to comment #10)
> Thanks for the patch Tom. I had a quick look at it. It does not seems correct
> to me because the method buildComponent() must either return a built component
> instance or throw a ComponentException. In your case if ClassCircularityError
> is caught, the method will return null. Perhaps if you throw a
> ComponentException after the resolver.mgr.enqueueWork() call then it would be
> fine.

Yeah, I was unsure of the consequences of throwing a ComponentException in this case.  But it appears it is caught and handled correctly.  I was worried that would bubble up to the client.

> I will be able to make more analysis on this issue next week.

Thanks, this probably will need to be considered for 3.6.1.  Bryan, is this blocking one of your products?  I am sure we can come up with a workaround for you in the mean time.

Here is an updated patch to throw a ComponentExcpetion in case of the circularity error.
Comment 12 Thomas Watson CLA 2010-06-17 09:39:41 EDT
Also see http://www.eclipse.org/forums/index.php?t=msg&th=169856&start=0&

It appears we need to handle the dynamic bind case as well.
Comment 13 Bryan Hunt CLA 2010-06-17 23:04:41 EDT
(In reply to comment #11)
> Thanks, this probably will need to be considered for 3.6.1.  Bryan, is this
> blocking one of your products?  I am sure we can come up with a workaround for
> you in the mean time.

Tom, this is not blocking a product until we move our target platform to 3.6.  Setting the start order may be an acceptable workaround, and if not, I'll bring the problem up again.
Comment 14 Thomas Watson CLA 2010-06-18 09:23:42 EDT
Reducing to major.
Comment 15 Stoyan Boshev CLA 2010-06-25 10:54:10 EDT
I think we cannot fix the problem with the proposed patch. The reason is that when the method InstanceProcess.buildComponent() is executed, in some cases we really count on the returned value by this method. These are the cases when:
- the user call ComponentFactory.newInstance()
- the user wants to get a service registered by a DS component. In this case the call stack will reach to ServiceReg.getService() or FactoryReg.getService().

In all these cases the current patch would cause to return null as a service or the user will get a ComponentException. This would surely not fit into the user's logic.

I still think there is no clean solution in this case. There are only workarounds available.
Probably the best workaround would be to change the launch configuration and start the bundle which contains DS components that are in the root of the ClassCircularityError. In the provided test case this would be the "com.companyX.core" bundle. If I change in the launch configuration its auto-start value from "Default" to "true" the test case is successfully run.
Comment 16 Bryan Hunt CLA 2010-06-25 11:01:14 EDT
My problem with using the workaround is that it violates the fundamental OSGi rule:  Your code must be independent of start order.
Comment 17 Stoyan Boshev CLA 2010-06-25 11:11:58 EDT
(In reply to comment #16)
> My problem with using the workaround is that it violates the fundamental OSGi
> rule:  Your code must be independent of start order.

My last suggested workaround does not suggest changing of the bundles start order. I just suggest starting of the affected bundle.
Comment 18 Thomas Watson CLA 2010-06-28 14:33:44 EDT
(In reply to comment #15)
> In all these cases the current patch would cause to return null as a service or
> the user will get a ComponentException. This would surely not fit into the
> user's logic.

I am not sure I agree.  The circularity error also does not fit into the user's logic, so which is better?  If the user is getting the service then I assume BundleContext.getService(ServiceReference) is ultimately getting called.  This method is always allowed to return null and clients should protect against that incases where the service may have been unregistered.  ServiceFactory service are also allowed to return null any time they want which would result in null being returned to the client.

At any rate, if that is a concern then is there any way to limit this to the case when the DS resolver is enabling a component?
Comment 19 Stoyan Boshev CLA 2010-07-07 11:00:01 EDT
Created attachment 173661 [details]
proposed patch 3

The proposed patch contains patch 2 changes. It also includes additional logic that handles the cases when dynamic binding fails. In these cases the failed to bound references will be asynchronously bound later.
This should cover all known cases where ClassCircularityError appear.
Comment 20 Thomas Watson CLA 2010-07-21 10:41:06 EDT
+1, thanks for providing a more complete patch Stoyan.  I think we should release this into both HEAD and 3.6.1 to get more testing.
Comment 21 Stoyan Boshev CLA 2010-07-27 09:41:38 EDT
Patch committed to HEAD and branch R3_6_maintenance