Hi Chris,
I know what is going on here (it is quite clear in the debugger).  I'll
take you through it one step at a time in this email, but the issue is
complicated, so I'll try to be thorough.

Would you be willing to try the newly-minted 8.0.21, or 8.0.20, to see

if it makes a difference?

Yes, I would!  I just tested/debugged in 8.0.20, and the issue still
occurs.  I don't see 8.0.21 on the tomcat download site, so I checked out
trunk, and the code I believe is responsible for this bug (I'll explain
below) hasn't been modified since 8.0.20.

Are there any other log messages in either your application's log, the
> localhost_*.log, or catalina.out that come *before* this
> ClassNotFoundException?

Nope.

In your original message, did you post the full and complete stack trace?

I believe so, but I might as well post updated versions from 8.0.20.
26-Mar-2015 14:38:46.838 SEVERE [http-nio-8443-exec-5]
org.apache.catalina.core.StandardContext.listenerStop Exception sending
context destroyed event to listener instance of class
com.avadyne.TestcaseContextListener
 java.lang.NoClassDefFoundError: com/avadyne/ThisClassNotFound
at
com.avadyne.TestcaseContextListener.contextDestroyed(TestcaseContextListener.java:27)
at
org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:4775)
at
org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5385)
at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232)
at
org.apache.catalina.core.StandardContext.reload(StandardContext.java:3739)
at org.apache.catalina.startup.HostConfig.reload(HostConfig.java:1304)
at
org.apache.catalina.startup.HostConfig.checkResources(HostConfig.java:1236)
at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1491)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at
org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300)
at
com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at
org.apache.catalina.manager.ManagerServlet.check(ManagerServlet.java:1460)
at
org.apache.catalina.manager.ManagerServlet.deploy(ManagerServlet.java:906)
at org.apache.catalina.manager.ManagerServlet.doGet(ManagerServlet.java:344)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at
org.apache.catalina.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:108)
at
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:613)
at
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at
org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
at
org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1086)
at
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:659)
at
org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
at
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassNotFoundException: com.avadyne.ThisClassNotFound
at
org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1305)
at
org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1157)
... 45 more

Here is the stack where the ClassLoader gives up on loading the class
(8.0.20).  The WebResourceSet it would have used to load the class from (in
the method StandardRoot.getResourceInternal) is a DirResourceSet pointed to
the exploded directory path - e.g.
/path/to/tomcat/webapps/ClassNotFoundGenerator/.  I can see it in the
debugger in the allResources member variable.  The path it generates in
DirResourceSet.getResource
is 
/path/to/tomcat/webapps/ClassNotFoundGenerator/WEB-INF/classes/com/avadyne/ThisClassNotFound.class
- the path is correct.  The file doesn't exist, and it returns a new
EmptyResource(root, path, f).

Daemon Thread [http-nio-8443-exec-31] (Suspended)
owns: WebappClassLoader  (id=3483)
owns: TestcaseContextListener  (id=3488)
owns: StandardContext  (id=3324)
owns: HostConfig  (id=3323)
owns: SecureNioChannel  (id=3313)
StandardRoot.getResourceInternal(String, boolean) line: 302
Cache.getResource(String, boolean) line: 65
StandardRoot.getResource(String, boolean, boolean) line: 216
StandardRoot.getClassLoaderResource(String) line: 225
WebappClassLoader(WebappClassLoaderBase).findResourceInternal(String,
String) line: 2548
WebappClassLoader(WebappClassLoaderBase).findClassInternal(String) line:
2405
WebappClassLoader(WebappClassLoaderBase).findClass(String) line: 854
WebappClassLoader(WebappClassLoaderBase).loadClass(String, boolean) line:
1274
WebappClassLoader(WebappClassLoaderBase).loadClass(String) line: 1157
TestcaseContextListener.contextDestroyed(ServletContextEvent) line: 27
StandardContext.listenerStop() line: 4775
StandardContext.stopInternal() line: 5385
StandardContext(LifecycleBase).stop() line: 232
StandardContext.reload() line: 3739
HostConfig.reload(HostConfig$DeployedApplication) line: 1304
HostConfig.checkResources(HostConfig$DeployedApplication) line: 1236
HostConfig.check(String) line: 1491
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not
available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 483
BaseModelMBean.invoke(String, Object[], String[]) line: 300
DefaultMBeanServerInterceptor.invoke(ObjectName, String, Object[],
String[]) line: 819
JmxMBeanServer.invoke(ObjectName, String, Object[], String[]) line: 801
ManagerServlet.check(String) line: 1460
ManagerServlet.deploy(PrintWriter, String, ContextName, String, boolean,
StringManager) line: 906
ManagerServlet.doGet(HttpServletRequest, HttpServletResponse) line: 344
ManagerServlet(HttpServlet).service(HttpServletRequest,
HttpServletResponse) line: 618
ManagerServlet(HttpServlet).service(ServletRequest, ServletResponse) line:
725
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse)
line: 291
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
WsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 52
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse)
line: 239
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
SetCharacterEncodingFilter.doFilter(ServletRequest, ServletResponse,
FilterChain) line: 108
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse)
line: 239
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
StandardWrapperValve.invoke(Request, Response) line: 219
StandardContextValve.invoke(Request, Response) line: 106
BasicAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 613
StandardHostValve.invoke(Request, Response) line: 142
ErrorReportValve.invoke(Request, Response) line: 79
AccessLogValve(AbstractAccessLogValve).invoke(Request, Response) line: 610
StandardEngineValve.invoke(Request, Response) line: 88
CoyoteAdapter.service(Request, Response) line: 516
Http11NioProcessor(AbstractHttp11Processor).process(SocketWrapper<S>) line:
1086
Http11NioProtocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler).process(SocketWrapper<S>,
SocketStatus) line: 659
Http11NioProtocol$Http11ConnectionHandler.process(SocketWrapper<NioChannel>,
SocketStatus) line: 223
NioEndpoint$SocketProcessor.doRun(SelectionKey, NioEndpoint$KeyAttachment)
line: 1558
NioEndpoint$SocketProcessor.run() line: 1515
ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker)
line: 1142
ThreadPoolExecutor$Worker.run() line: 617
TaskThread$WrappingRunnable.run() line: 61
TaskThread(Thread).run() line: 745

Taking a look at Tomcat 8 trunk, this code has undergone a large
> refactoring since 8.0.12 so the line numbers aren't correct relative
> to your stack trace. I looked in 8.0.12 and the line in question
> (WebappClassLoader.java:1320) is at the end of the loadClass method,
> when all options for loading the class have been exhausted. It just
> gives up and says "sorry, I can't find the class".

Yes, that is what I saw in the debugger on 8.0.12.  It goes all the way
down to findClass, findClassInternal and findResourceInternal before giving
up.  8.0.12 takes a slightly different path than 8.0.20, but 8.0.20 still
fails to find the class and throws a ClassNotFoundError.

It's possible that Tomcat's reload process first notifies the
> WebappClassLoader that it shouldn't load any new classes during
> shutdown, but that sounds like it would be a recipe for disaster --
> the kind of disaster that you are experiencing, here.

That would trigger a ClassNotFoundError in
WebappClassLoader.checkStateForClassLoading or
checkStateForResourceLoading, right?  I can see in the debugger the
loadClass(String,boolean) method continues after
checkStateForClassLoading.  I have a better explanation, though, so keep
reading :)

In this case, I would expect that the class is actually missing. Are
> you sure it's there? Where is it located?

At first I was thinking that WCL was objecting to loading the class,
> but it's not: it's actually not finding it.

I believe I figured this part out in the debugger.  When the
WebappClassLoader attempts to load the class, the .class file (and the
entire exploded directory) is not on the disk.  First, let me quote a few
frames of the ClassNotFoundError stack trace (once again, 8.0.20).

 java.lang.NoClassDefFoundError: com/avadyne/ThisClassNotFound
...
  at
org.apache.catalina.core.StandardContext.reload(StandardContext.java:3739)
at org.apache.catalina.startup.HostConfig.reload(HostConfig.java:1304)
at
org.apache.catalina.startup.HostConfig.checkResources(HostConfig.java:1236)
...

HostConfig.checkResources:1231 deletes the exploded directory
HostConfig.checkResources:1236 calls into StandardContext.reload
StandardContext.reload:3739 eventually invokes the ServletContextListeners

Once the contextDestroyed listeners are executed, the .class file doesn't
exist (HostConfig.checkResources deleted it).

I have walked (in the debugger) all the way from HostConfig.checkResources
to the SevletContextListener/WebappClassLoader.loadClass call, and there
doesn't seem to be any other code that causes trouble.

It is definitely unsafe for HostConfig.checkResources to delete the
unpacked directory before the context is stopped.  However,
WebappClassLoader is correct to throw a ClassNotFoundError, since the class
really doesn't exist on disk.

Now, Tomcat 8 uses a more
> complicated resource-loading framework than previous Tomcats, and it's
> possible that the resource-loading framework is being taken down
> earlier than your code is running, which would make certain classes
> /libraries inaccessible. I haven't read enough code to know if that's
> the case or not. IMO it shouldn't be the case, and probably isn't
> because the smarter Tomcat devs tend to think about things like that
> before coding them ;)



Okay. IIRC, during ServletContextListener.contextDestroyed, the
> WebappClassLoader should be fully-functional. That is, it shouldn't
> have complained about anything.

I wouldn't blame anyone for not noticing this bug.  And, I dream of a fully
functional ClassLoader :D

The bug requires very specific conditions. Deploy a WAR through the Tomcat
manager, and configure it to explode the war, and create a listener that
references a class on shutdown that was never referenced prior...

Unfortunately, the impact (uncaught java.lang.Error leaving uncleaned
resources/threads) is pretty severe.  It is also unavoidable if you need to
execute lots of code in the shutdown handler, or you need to wait for
Threads to stop.  I need to do both...

> In my first example, the ClassNotFoundError is thrown when
> > enabled=false. When enabled=true, the error doesn't occur.
> When you say enabled=true, you mean that's what's showing in the
> Manager's web interface?

Don't worry too much about the enabled flag, it doesn't have anything to do
with the actual issue.  I was trying to explain why a class wouldn't be
loaded during contextInitialized (in this case - when initialization is
conditional).

The "new" WebappClassLoader shouldn't even be involved, yet, since the
> shutdown is evidently not completing successfully, so the reload never
> actually gets to the "load" part.

Well, yes and no.  The WebappClassLoader which throws the
ClassNotFoundError is definitely the one on the 'stop' side of the reload.
I can tell from the cached classes.

The ClassNotFoundError gets caught and logged by
StandardContext.listenerStop(), which catches Throwable.  Tomcat completes
the reload without any issue.

When you do the reload(), are you shutting-down a properly-running web
> application, or one that failed to load properly in the first place?

Yes, the app is up and running just fine.  All servlets/listeners are
mounted, and no errors were reported during startup.

> I actually have reworked my app's top-level initialization layer to
> > preload classes on startup.  I am considering preloading all
> > classes in WEB-INF/classes at server startup.  Unfortunately, my
> > init layer needs to call into code deeper in the application, which
> > may call into bundled JARs (WEB-INF/lib).  Including JARs, that
> > would be a lot of code to preload.



It also shouldn't be necessary. I think something else weird might be
> going on.

It's a temporary workaround, but it works OK.  I get occasional NPEs from
the ClassLoader when it needs to load from JARs in WEB-INF/lib, but I can
live with that until the Tomcat code is fixed.  Actually, Guava's ClassPath
API made preloading surprisingly easy.

Reply via email to