Classloading problems with ClassProxyFactory
brian.stansberry Apr 25, 2008 12:21 PMI'm seeing intermittent failures in the AS clustering testsuite tests of replicating aspectized web session attributes (aka FIELD granularity session replication.) These are tests where classes are prepared with aopc for use with PojoCache, and then inserted into PojoCache via a web session.
Problem is one of stale (i.e. undeployed) classloaders being used by ClassProxyFactory to create proxies. Specifically:
1) Test class A deploys http-scoped-field.war, classloader BaseClassLoader@17eb527{vfsfile:/home/bes/dev/jboss/trunk/testsuite/output/lib/http-scoped-field.war} is created.
2) During test, PojoCache uses ClassProxyFactory to create a proxy of java.util.ArrayList. TCCL in effect is the "http-scoped-field.war" classloader, so that is the classloader used to define the proxy class.
3) Test class A finishes, http-scoped-field.war undeploys; all refs to "http-scoped-field.war" classloader should be released.
4) Test class B deploys different war http-field.war".
5) Same as step 2, during test PojoCache uses ClassProxyFactory to create a proxy of java.util.ArrayList. Now TCCL in effect is the "http-field.war" classloader,
Step 5 fails, as ClassProxyFactory finds the cached proxy class from step 2 and tries to create an instance of it:
2008-04-24 16:19:42,799 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[localhost].[/http-field].[jsp]] (http-besdev%2F192.168.1.145-8080-2) Servlet.service() for servlet jsp threw exception java.lang.IllegalStateException: BaseClassLoader@17eb527{vfsfile:/home/bes/dev/jboss/trunk/testsuite/output/lib/http-scoped-field.war} classLoader is not connected to a domain (probably undeployed?) for class sun.reflect.ConstructorAccessorImpl at org.jboss.classloader.spi.base.BaseClassLoader.loadClassFromDomain(BaseClassLoader.java:723) at org.jboss.classloader.spi.base.BaseClassLoader.loadClass(BaseClassLoader.java:372) at java.lang.ClassLoader.loadClass(ClassLoader.java:299) at java.lang.ClassLoader.loadClass(ClassLoader.java:251) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319) at sun.misc.Unsafe.defineClass(Native Method) at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:45) at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:381) at java.security.AccessController.doPrivileged(Native Method) at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:377) at sun.reflect.MethodAccessorGenerator.generateConstructor(MethodAccessorGenerator.java:76) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:30) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:494) at java.lang.Class.newInstance0(Class.java:350) at java.lang.Class.newInstance(Class.java:303) at org.jboss.aop.proxy.ClassProxyFactory.newInstance(ClassProxyFactory.java:103) at org.jboss.aop.proxy.ClassProxyFactory.newInstance(ClassProxyFactory.java:71) at org.jboss.aop.proxy.ClassProxyFactory.newInstance(ClassProxyFactory.java:66) at org.jboss.cache.pojo.collection.CollectionInterceptorUtil.createProxy(CollectionInterceptorUtil.java:50) at org.jboss.cache.pojo.collection.CollectionInterceptorUtil.createListProxy(CollectionInterceptorUtil.java:97) at org.jboss.cache.pojo.impl.CollectionClassHandler.get(CollectionClassHandler.java:63) at org.jboss.cache.pojo.impl.PojoCacheDelegate.getObjectInternal(PojoCacheDelegate.java:354) at org.jboss.cache.pojo.impl.PojoCacheDelegate.getObject(PojoCacheDelegate.java:102) at org.jboss.cache.pojo.impl.PojoCacheImpl.getObject(PojoCacheImpl.java:209) at org.jboss.cache.pojo.impl.PojoCacheImpl.org$jboss$cache$pojo$impl$PojoCacheImpl$detach$aop(PojoCacheImpl.java:151) at org.jboss.cache.pojo.impl.PojoCacheImpl$PojoCacheImplAdvisor.detach_N_6302035201148273652(PojoCacheImpl$PojoCacheImplAdvisor.java) at org.jboss.cache.pojo.impl.PojoCacheImpl.detach(PojoCacheImpl.java) at org.jboss.cache.pojo.impl.AdvisedPojoHandler.remove(AdvisedPojoHandler.java:182) at org.jboss.cache.pojo.impl.PojoCacheDelegate.removeObject(PojoCacheDelegate.java:276) at org.jboss.cache.pojo.impl.PojoCacheImpl.removeObject(PojoCacheImpl.java:171) at org.jboss.cache.pojo.impl.PojoCacheImpl.org$jboss$cache$pojo$impl$PojoCacheImpl$detach$aop(PojoCacheImpl.java:154) at org.jboss.cache.pojo.impl.PojoCacheImpl$PojoCacheImplAdvisor.detach_N_6302035201148273652(PojoCacheImpl$PojoCacheImplAdvisor.java) at org.jboss.cache.pojo.impl.PojoCacheImpl.detach(PojoCacheImpl.java) at org.jboss.cache.pojo.impl.PojoCacheImpl.detach(PojoCacheImpl.java:165) at org.jboss.cache.pojo.impl.PojoCacheImpl.detach(PojoCacheImpl.java:143) at org.jboss.web.tomcat.service.session.FieldBasedJBossCacheService.removePojo(FieldBasedJBossCacheService.java:197) at org.jboss.web.tomcat.service.session.FieldBasedClusteredSession.removeJBossInternalAttribute(FieldBasedClusteredSession.java:285) at org.jboss.web.tomcat.service.session.JBossCacheClusteredSession.removeAttributeInternal(JBossCacheClusteredSession.java:146) at org.jboss.web.tomcat.service.session.ClusteredSession.removeAttributeInternal(ClusteredSession.java:1348) at org.jboss.web.tomcat.service.session.ClusteredSession.removeAttributeInternal(ClusteredSession.java:1325) at org.apache.catalina.session.StandardSession.removeAttribute(StandardSession.java:1207) at org.apache.catalina.session.StandardSession.removeAttribute(StandardSession.java:1179) at org.apache.catalina.session.StandardSessionFacade.removeAttribute(StandardSessionFacade.java:140) at org.apache.jsp.removeAttribute_jsp._jspService(removeAttribute_jsp.java:60) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:803) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:369) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:337) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:266) at javax.servlet.http.HttpServlet.service(HttpServlet.java:803) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:235) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:189) at org.jboss.web.tomcat.service.session.ClusteredSessionValve.invoke(ClusteredSessionValve.java:89) at org.jboss.web.tomcat.service.session.BatchReplicationClusteredSessionValve.invoke(BatchReplicationClusteredSessionValve.java:102) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:433) at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:90) at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:96) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:310) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:601) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Thread.java:595)
I've been searching high and low to try and figure out why the old proxy class is being used, since AOP is using a weak ref to cache it. I don't completely understand this (see below) but here's one thing I've seen:
MethodHashesByClass.methodHashesByClass
+ WeakHashMap.table
++ WeakHashMap$Entry.value
+++ HashMap.table
++++ HashMap$Entry.value
+++++ MethodPersistentReference.referencedObject
++++++ SoftReference.referent
+++++++ java.lang.reflect.Method.clazz
++++++++ class Person.classloader
+++++++++ "http-scoped-field.war" classloader classes
++++++++++ proxy to ArrayList
A chain from the proxy class running through a SoftReference. Basically, holding a SoftReference to a Method effectively converts the ClassProxyFactory.proxyCache's weak ref to the proxy class into a SoftReference.
This would help explain the intermittent nature of these failures; if the SoftReference happens to get released, the proxy class is gone and the test passes.
Now, the caveat: while trying to debug this I've developed some tests that load and instantiate AOP-prepared (via aopc) classes in a war, then undeploy the war, fill memory to force all soft references to be cleared, and see then test if the classloader was collected. These are variants on the tests discussed at http://wiki.jboss.org/auth/wiki/ClassloaderLeakUnitTestCase . These tests show the classloader not being released, but an analysis of the heap shows all refs to the classloader going through weak references -- i.e. the classloader should have been collected.
If I run the same test with the same classes, except I haven't run aopc first to prepare the classes, the classloader is released. Been tearing my hair out trying to understand this; using different profiling tools to examing heap, etc. So, there's still some mystery here. :(