I posted this a couple of days ago and haven't seen anything on it.  Do I need to log 
a bug or just assume it's not really important to the tomcat crowd in general?

Mike Anderson


In ServletWrapper.handleReload, there is a syncronization problem.  If tomcat us under 
a load and a servlet class file or jsp is updated, there is a potential for multiple 
request to try and reload the servlet simultaneously and step on each other causing a 
bad state.  The way it is currently coded, we've seen cases where when a class is 
updated, the first thread through checks to see if a reload needs to take place and 
starts the destroy.  The next thread comes in and also thinks it is supposed to do a 
reload and starts down the same branch.  Meanwhile, the first thread continues and 
creates the new loader (loader.reload) and finishes handleReload (including setting 
servlet=null and servletClass=null).  The second thread then continues it's 
processing, and gets to creates a new loader.  While the second thread is creating the 
new loader, the first thread runs the Handler.service method which runs the 
ServletWrapper.init method which calls loadServlet where servlet and servletClass are 
set appropriately, and eventually calls ServletWrapper.doInit which sets initialized 
to true.  Then the second thread continues and nulls out servlet and servletClass, but 
since initialized is true, it doesn't run the init method and from the on you get a 
null pointer exception trying to access that particular servlet.  

I've even tried just moving the lines that null out servlet and servletClass up to 
right after where initialized is set to false, but this causes another problem where 
the first thread creates a new loader and calls loadServlet, then the second thread 
creates a new loader but the servlet isn't reinitialized (since it doesn't need to be) 
and so the loader doesn't have it cached and always reports false when its 
shouldReload method is called.  This means that  the servlet never really gets 
reloaded because the loader doesn't have it cached.

Anyways, attached is a patch that fixes this problem.  I synchronized before I call 
load.shouldReload so that only the first thread will actually call loader.reload with 
subsequent threads getting a false back from the new loader so that they don't also 
try and create a new loader or null out the class variables.  I've tested this on 
Windoze (Win2K) and NetWare against the Tomcat HTTP handler and via Apache/Tomcat.  It 
does slow performance during the actually reload, but is minimal otherwise and if the 
context is set to not reload, the syncronized call is bypassed altogether.  I would 
like to get this in to 3.2.2 if at all possible since it does affect us heavily in our 
development and testing on NetWare.  Even though the diff seems large, all that was 
added was the synchronized call (and it's preceding comment).  The rest of the 
differences are just indenting the existing code.



Mike Anderson
Senior Software Engineer
Platform Services Group
[EMAIL PROTECTED]
Novell, Inc., the leading provider of Net services software
www.novell.com

Index: ServletWrapper.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat/src/share/org/apache/tomcat/core/Attic/ServletWrapper.java,v
retrieving revision 1.60.2.5
diff -u -r1.60.2.5 ServletWrapper.java
--- ServletWrapper.java 2001/01/12 04:39:05     1.60.2.5
+++ ServletWrapper.java 2001/05/02 22:42:27
@@ -422,38 +422,44 @@
        if( isReloadable ) {// && ! "invoker".equals( getServletName())) {
            ServletLoader loader=context.getServletLoader();
            if( loader!=null) {
-               // XXX no need to check after we remove the old loader
-               if( loader.shouldReload() ) {
-                   // workaround for destroy 
-                   try {
-                       destroy();
-                   } catch(Exception ex ) {
-                       context.log( "Error in destroy ", ex );
-                   }
-                   initialized=false;
-                   loader.reload();
-                   
-                   ContextManager cm=context.getContextManager();
-                   cm.doReload( req, context );
-                   
-                   servlet=null;
-                   servletClass=null;
-                   /* Initial attempt to shut down the context and sessions.
-                      
-                      String path=context.getPath();
-                      String docBase=context.getDocBase();
-                      // XXX all other properties need to be saved
-                      // or something else
-                      ContextManager cm=context.getContextManager();
-                      cm.removeContext(path);
-                      Context ctx=new Context();
-                      ctx.setPath( path );
-                      ctx.setDocBase( docBase );
-                      cm.addContext( ctx );
-                      context=ctx;
-                      // XXX shut down context, remove sessions, etc
-                   */
-               }
+                // We need to syncronize here so that multiple threads don't
+                // try and reload the class.  The first thread through will
+                // create the new loader which will make shouldReload return
+                // false for subsequent threads.
+                synchronized(this) {
+                    // XXX no need to check after we remove the old loader
+                    if( loader.shouldReload() ) {
+                        // workaround for destroy 
+                        try {
+                            destroy();
+                        } catch(Exception ex ) {
+                            context.log( "Error in destroy ", ex );
+                        }
+                        initialized=false;
+                        loader.reload();
+                        
+                        ContextManager cm=context.getContextManager();
+                        cm.doReload( req, context );
+                        
+                        servlet=null;
+                        servletClass=null;
+                        /* Initial attempt to shut down the context and sessions.
+                           
+                           String path=context.getPath();
+                           String docBase=context.getDocBase();
+                           // XXX all other properties need to be saved
+                           // or something else
+                           ContextManager cm=context.getContextManager();
+                           cm.removeContext(path);
+                           Context ctx=new Context();
+                           ctx.setPath( path );
+                           ctx.setDocBase( docBase );
+                           cm.addContext( ctx );
+                           context=ctx;
+                           // XXX shut down context, remove sessions, etc
+                        */
+                    }
+                }
            }
        }
     }


Reply via email to