marcsaeg    01/04/25 16:29:30

  Modified:    src/share/org/apache/jasper/resources Tag: tomcat_32
                        messages.properties messages_es.properties
                        messages_fr.properties
               src/share/org/apache/jasper/servlet Tag: tomcat_32
                        JspServlet.java
  Log:
  This is a fairly major update.  There were numerous thread synchronization
  problems involving compiling JSP files.  These problems were most apparent
  on systems running under load in which page re-compilations might
  happen while pages are being executed and where new requests arrive while
  a page compilation is underway.
  
  I've made lots of changes so I'll just summarize the major differences
  here.
  
  1) The thread synchronization in doLoadJSP() is quite different.  The
  comments before that method describe the synchronization model in detail.
  
  2) The synchronization in doLoadJSP() is now based on the
  JspServletWrapper object instead of the JspServlet object.
  
  3) The class loading and the object instantiation used to be split between
  doLoadJSP() and the JspServletWrapper.  They are now combined into a
  single synchronization block inside doLoadJSP().
  
  4) In JspServletWrapper, the data member theServlet was used directly by
  several methods.  Access to this is now provided only through a
  synchronized accessor method().  Any method that needs to obtain the
  servlet must call getServlet() and store the result in a stack variable
  (or other per-thread storage).  Multiple threads of execution through
  JspServletWrapper may be executing different servlet classes if a page
  compilation happened while other requests were being processed.
  
  5) When a new JSP implementation class replaces an existing class, the
  original classes destroy() method should be called so that any
  jspDestroy() method on the page will be executed.  However, the destroy()
  method can't be invoked until we know that there are no more requests
  being processed on that instance.  To support this, the JSP implementation
  class is wrapped inside a new class called JspCountedServlet.  This class
  provides simple reference counting in the service() method.  If the
  classes destroy method is called it flags the class for destruction but
  does not call the servlet's destroy() method until it determines that the
  service method has completed on all threads running in the class.
  
  PR:  1280
  
  Revision  Changes    Path
  No                   revision
  
  
  No                   revision
  
  
  1.17.2.9  +3 -1      
jakarta-tomcat/src/share/org/apache/jasper/resources/messages.properties
  
  Index: messages.properties
  ===================================================================
  RCS file: 
/home/cvs/jakarta-tomcat/src/share/org/apache/jasper/resources/messages.properties,v
  retrieving revision 1.17.2.8
  retrieving revision 1.17.2.9
  diff -u -r1.17.2.8 -r1.17.2.9
  --- messages.properties       2001/01/12 04:47:00     1.17.2.8
  +++ messages.properties       2001/04/25 23:29:28     1.17.2.9
  @@ -1,4 +1,4 @@
  -# $Id: messages.properties,v 1.17.2.8 2001/01/12 04:47:00 larryi Exp $
  +# $Id: messages.properties,v 1.17.2.9 2001/04/25 23:29:28 marcsaeg Exp $
   #
   # Default localized string information
   # Localized this the Default Locale as is en_US
  @@ -214,3 +214,5 @@
   jsp.error.unterminated.user.tag=Unterminated user-defined tag: ending tag {0} not 
found or incorrectly nested 
   jsp.error.invalid.javaEncoding=Invalid java encodings. Tried {0} and then {1}. Both 
failed.
   jsp.error.needAlternateJavaEncoding=Default java encoding {0} is invalid on your 
java platform. An alternate can be specified via the 'javaEncoding' parameter of 
JspServlet.
  +jsp.error.badcount=Internal error.  Attempted to decrement the reference count for 
an unreferenced JSP.
  +
  
  
  
  1.3.2.7   +3 -1      
jakarta-tomcat/src/share/org/apache/jasper/resources/messages_es.properties
  
  Index: messages_es.properties
  ===================================================================
  RCS file: 
/home/cvs/jakarta-tomcat/src/share/org/apache/jasper/resources/messages_es.properties,v
  retrieving revision 1.3.2.6
  retrieving revision 1.3.2.7
  diff -u -r1.3.2.6 -r1.3.2.7
  --- messages_es.properties    2001/01/12 04:47:00     1.3.2.6
  +++ messages_es.properties    2001/04/25 23:29:29     1.3.2.7
  @@ -1,4 +1,4 @@
  -# $Id: messages_es.properties,v 1.3.2.6 2001/01/12 04:47:00 larryi Exp $
  +# $Id: messages_es.properties,v 1.3.2.7 2001/04/25 23:29:29 marcsaeg Exp $
   #
   # Default localized string information
   # Localized para Locale es_ES
  @@ -204,3 +204,5 @@
   jspc.error.emptyWebApp=-webapp necesita un argumento de archivo
   jsp.error.no.more.content=Final del contenido alcanzado mientras se requeria mas 
entrada: anidamiento de tag s erroneo o tag no terminado
   jsp.error.unterminated.user.tag=Tag definida por el usuario no terminada: tag de 
finalizacion {0} no encontrado o incorrectamente anidado
  +jsp.error.badcount=Internal error.  Attempted to decrement the reference count for 
an unreferenced JSP.
  +
  
  
  
  1.1.2.3   +3 -1      
jakarta-tomcat/src/share/org/apache/jasper/resources/messages_fr.properties
  
  Index: messages_fr.properties
  ===================================================================
  RCS file: 
/home/cvs/jakarta-tomcat/src/share/org/apache/jasper/resources/messages_fr.properties,v
  retrieving revision 1.1.2.2
  retrieving revision 1.1.2.3
  diff -u -r1.1.2.2 -r1.1.2.3
  --- messages_fr.properties    2001/01/12 04:47:00     1.1.2.2
  +++ messages_fr.properties    2001/04/25 23:29:29     1.1.2.3
  @@ -1,4 +1,4 @@
  -# $Id: messages_fr.properties,v 1.1.2.2 2001/01/12 04:47:00 larryi Exp $
  +# $Id: messages_fr.properties,v 1.1.2.3 2001/04/25 23:29:29 marcsaeg Exp $
   #
   # Default localized string information
   # Localized this the Default Locale as is fr_FR
  @@ -208,3 +208,5 @@
   jspc.error.emptyWebApp=-webapp nécessite un argument suivant de type fichier
   jsp.error.no.more.content=Fin de contenu atteinte alors que plus d''analyse est 
nécessaire: Un tag non terminé ou une erreur de tag imbriqué?
   jsp.error.unterminated.user.tag=Tag utilisateur non terminé: Le tag de fermeture 
{0} est introuvable ou incorrectement imbriqué
  +jsp.error.badcount=Internal error.  Attempted to decrement the reference count for 
an unreferenced JSP.
  +
  
  
  
  No                   revision
  
  
  No                   revision
  
  
  1.3.2.6   +181 -51   
jakarta-tomcat/src/share/org/apache/jasper/servlet/JspServlet.java
  
  Index: JspServlet.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-tomcat/src/share/org/apache/jasper/servlet/JspServlet.java,v
  retrieving revision 1.3.2.5
  retrieving revision 1.3.2.6
  diff -u -r1.3.2.5 -r1.3.2.6
  --- JspServlet.java   2001/04/25 21:58:41     1.3.2.5
  +++ JspServlet.java   2001/04/25 23:29:30     1.3.2.6
  @@ -91,14 +91,97 @@
    *
    * @author Anil K. Vijendran
    * @author Harish Prabandham
  + * @author Marc A. Saegesser
    */
   public class JspServlet extends HttpServlet {
   
  +    /**
  +     * Adds reference counting to the JSP implementation servlet.  This
  +     * is required to handle the case where a JSP implementation servlet
  +     * is executing requests on several threads when a new implementation
  +     * arrives (the JSP source was updated).  We need to wait until all
  +     * the requests complete before calling the implementation servlet's
  +     * destroy() method.
  +     */
  +    class JspCountedServlet extends HttpServlet
  +    {
  +        private Servlet servlet = null;
  +        private int threadCount = 0;
  +        private boolean destroyed = false;
  +
  +        public JspCountedServlet(Servlet servlet)
  +        {
  +            this.servlet = servlet;
  +        }
  +
  +        public void init(ServletConfig config) throws ServletException, 
JasperException
  +        {
  +            try{
  +                servlet.init(config);
  +            }catch(NullPointerException e){
  +                throw new JasperException(e);
  +            }
  +        }
  +
  +        public void service(HttpServletRequest req, HttpServletResponse res) throws 
ServletException, IOException, JasperException
  +        {
  +            try{
  +                incrementCount();
  +                servlet.service(req, res);
  +            }catch(NullPointerException e){
  +                throw new JasperException(e);
  +            }finally{
  +                decrementCount();
  +            }
  +        }
  +
  +        /*
  +         * Flags this servlet for destrction once all active requests have 
completed.
  +         * After calling this method it is invalid to call service().
  +         */
  +        public void destroy()
  +        {
  +            destroyed = true;
  +            if(getCount() == 0)
  +                doDestroy();
  +        }
  +
  +        private void doDestroy()
  +        {
  +            try{
  +                servlet.destroy();
  +                servlet = null;
  +            }catch(NullPointerException e){
  +            }
  +        }
  +
  +        private synchronized void incrementCount()
  +        {
  +            threadCount++;
  +        }
  +
  +        private synchronized void decrementCount()
  +        {
  +            if(threadCount <= 0){
  +                Constants.message("jsp.error.badcount", Logger.ERROR);
  +                return;
  +            }
  +
  +            --threadCount;
  +            if(threadCount == 0 && destroyed)
  +                doDestroy();
  +        }
  +
  +        private synchronized int getCount()
  +        {
  +            return threadCount;
  +        }
  +    }
  +
       class JspServletWrapper {
  -        Servlet theServlet;
  +        JspCountedServlet theServlet;
           String jspUri;
           boolean isErrorPage;
  -        // ServletWrapper will set this 
           Class servletClass;
   
           JspServletWrapper(String jspUri, boolean isErrorPage) {
  @@ -107,23 +190,46 @@
               this.theServlet = null;
           }
   
  -        private void load() throws JasperException, ServletException {
  +        public synchronized void instantiateServlet(Class servletClass) throws 
JasperException, ServletException
  +        {
               try {
  -                // Class servletClass = (Class) loadedJSPs.get(jspUri);
  -                // This is to maintain the original protocol.
  -                destroy();
  +                this.servletClass = servletClass;
  +
  +                // If we're replacing an existing JSP Implementation class, then
  +                // schedule it for destruction
  +                if(theServlet != null)
  +                    theServlet.destroy();
  +
  +                // Create an instance of the JSP implementation class
  +                Servlet servlet = (Servlet) servletClass.newInstance();
  +                // Set the class loader
  +                if(servlet instanceof HttpJspBase) {
  +                    
((HttpJspBase)servlet).setClassLoader(JspServlet.this.parentClassLoader);
  +                }
  +
  +                // Wrap this servlet in a counted servlet
  +                theServlet = new JspCountedServlet(servlet);
  +
  +                // Call the JSP Implementation servlet's init() method.  This
  +                // will cause the page's jspInit() method to be invoked if one 
exists.
  +                theServlet.init(JspServlet.this.config);
   
  -                theServlet = (Servlet) servletClass.newInstance();
               } catch(Exception ex) {
                   throw new JasperException(ex);
               }
  -            theServlet.init(JspServlet.this.config);
  -            if(theServlet instanceof HttpJspBase) {
  -                HttpJspBase h = (HttpJspBase) theServlet;
  -                h.setClassLoader(JspServlet.this.parentClassLoader);
  -            }
  +
           }
   
  +        public synchronized Servlet getServlet()
  +        {
  +            return theServlet;
  +        }
  +
  +        public synchronized boolean isInstantiated()
  +        {
  +            return theServlet != null;
  +        }
  +
           private void loadIfNecessary(HttpServletRequest req, HttpServletResponse 
res) 
           throws JasperException, ServletException, FileNotFoundException 
           {
  @@ -149,10 +255,7 @@
                                 }, 
                                 Logger.INFORMATION);
   
  -            if(loadJSP(jspUri, cp, isErrorPage, req, res) 
  -               || theServlet == null) {
  -                load();
  -            }
  +            loadJSP(jspUri, cp, isErrorPage, req, res);
           }
   
           public void service(HttpServletRequest request, 
  @@ -160,21 +263,23 @@
                               boolean precompile)
           throws ServletException, IOException, FileNotFoundException 
           {
  +            Servlet servlet = null;
               try {
                   loadIfNecessary(request, response);
  +                servlet = getServlet();
   
                   // If a page is to only to be precompiled return.
                   if(precompile)
                       return;
   
  -                if(theServlet instanceof SingleThreadModel) {
  +                if(servlet instanceof SingleThreadModel) {
                       // sync on the wrapper so that the freshness
                       // of the page is determined right before servicing
                       synchronized (this) {
  -                        theServlet.service(request, response);
  +                        servlet.service(request, response);
                       }
                   } else {
  -                    theServlet.service(request, response);
  +                    servlet.service(request, response);
                   }
   
               } catch(FileNotFoundException ex) {
  @@ -216,7 +321,8 @@
               }
           }
   
  -        public void destroy() {
  +        public void destroy()
  +        {
               if(theServlet != null)
                   theServlet.destroy();
           }
  @@ -272,7 +378,6 @@
                                     "<none>"
                                 }, Logger.DEBUG);
           }
  -        //   System.out.println("JspServlet: init " + config.getServletName() );
           if( loader==null ) {
               if( jdk12 ) {
                   try {
  @@ -307,11 +412,18 @@
       throws ServletException, IOException
       {
           boolean isErrorPage = exception != null;
  +        JspServletWrapper wrapper = null;
   
  -        JspServletWrapper wrapper = (JspServletWrapper) jsps.get(jspUri);
  -        if(wrapper == null) {
  -            wrapper = new JspServletWrapper(jspUri, isErrorPage);
  -            jsps.put(jspUri, wrapper);
  +        /*
  +         * Several threads may be handling requests for the same jspUri.
  +         * Only one of them is allowed to create the JspServletWrapper.
  +         */
  +        synchronized(this){
  +            wrapper = (JspServletWrapper) jsps.get(jspUri);
  +            if(wrapper == null) {
  +                wrapper = new JspServletWrapper(jspUri, isErrorPage);
  +                jsps.put(jspUri, wrapper);
  +            }
           }
   
           wrapper.service(request, response, precompile);
  @@ -437,6 +549,36 @@
        *  @param classpath explicitly set the JSP compilation path.
        *  @return true if JSP files is newer
        */
  +
  +    /*
  +     * A word about the thread synchronization below.  The call to 
  +     * compiler.isOutDated() is outside the synchronization block on purpose.  
  +     * The expectation is that for the vast majority of cases the JSP source file 
  +     * will not have changed and there will be no need to recompile the 
  +     * implementation class.  For those cases when a compile is required, we 
  +     * enter a block that is synchronized on the JspServletWrapper object for 
  +     * this JSP page.  Because the initial out dated check is unsynchronized, it 
  +     * is possible for more than one request to attempt to enter the synchronized 
  +     * compile block.  The compile() method contains performs an outdated check 
  +     * of its own.  The first thread into the block will cause a compile, the 
  +     * subsequent threads will essentially skip the compilation and instantiation 
  +     * steps.  
  +     * 
  +     * One other thing to note is that there is a window of time between the 
  +     * compiler.compile() call and the end of the synchronized block where a new 
  +     * thread entering doLoadJSP() will be told that that implementation class is 
  +     * up to date even though the code in the synchronized block has not 
  +     * completed loading and instantiating the class.  In this case doLoadJSP() 
  +     * will return false without attempting to compile the class.  This is OK 
  +     * because the JspServletWrapper.getServlet() method used by the 
  +     * JspServletWrapper.service() method is synchronized.  Thus, the service 
  +     * method will not receive the servlet class until it has been completely 
loaded and 
  +     * instantiated.  
  +     * 
  +     * The bottom line is that we avoid synchronizing a fairly expensive 
  +     * operation (isOutDated) but pay a small price of some unnecessary 
  +     * compilation attempts in the atypical case of a modified JSP file.  
  +     */
       protected boolean doLoadJSP(String jspUri, String classpath, 
                                   boolean isErrorPage, HttpServletRequest req, 
HttpServletResponse res) 
       throws JasperException, FileNotFoundException 
  @@ -445,25 +587,26 @@
           if( jsw==null ) {
               throw new JasperException("Can't happen - JspServletWrapper=null");
           }
  -        //   Class jspClass = (Class) loadedJSPs.get(jspUri);
  -        boolean firstTime = jsw.servletClass == null;
           JspCompilationContext ctxt = new JspEngineContext(loader, classpath,
                                                             context, jspUri, 
                                                             isErrorPage, options,
                                                             req, res);
           boolean outDated = false; 
   
  -        Compiler compiler = null;
  -        synchronized(this){
  -            compiler = ctxt.createCompiler();
  -        }
  +        Compiler compiler = ctxt.createCompiler();
   
           try {
  +
  +
               outDated = compiler.isOutDated();
  -            if( (jsw.servletClass == null) || outDated ) {
  -                synchronized ( this ) {
  -                    if((jsw.servletClass == null) || (compiler.isOutDated() )) {
  -                        outDated = compiler.compile();
  +            if(!jsw.isInstantiated() || outDated ) {
  +                synchronized(jsw){
  +                    outDated = compiler.compile();
  +                    if(!jsw.isInstantiated() || outDated) {
  +                        if( null ==ctxt.getServletClassName() ) {
  +                            compiler.computeServletClassName();
  +                        }
  +                        
jsw.instantiateServlet(loader.loadClass(ctxt.getFullClassName()));
                       }
                   }
               }
  @@ -472,25 +615,12 @@
               throw ex;
           } catch(JasperException ex) {
               throw ex;
  +        } catch(ClassNotFoundException cex) {
  +                throw new 
JasperException(Constants.getString("jsp.error.unable.load"), 
  +                                          cex);
           } catch(Exception ex) {
               throw new 
JasperException(Constants.getString("jsp.error.unable.compile"),
                                         ex);
  -        }
  -
  -        // Reload only if it's outdated
  -        if((jsw.servletClass == null) || outDated) {
  -            try {
  -                if( null ==ctxt.getServletClassName() ) {
  -                    compiler.computeServletClassName();
  -                }
  -                jsw.servletClass = loader.loadClass(ctxt.getFullClassName());
  -                //loadClass(ctxt.getFullClassName(), true);
  -            } catch(ClassNotFoundException cex) {
  -                throw new 
JasperException(Constants.getString("jsp.error.unable.load"), 
  -                                          cex);
  -            }
  -
  -            //           loadedJSPs.put(jspUri, jspClass);
           }
   
           return outDated;
  
  
  

Reply via email to