I've made some pretty major changes to JspServlet.java to clean up lots of
thread synchroniztion problems.  The commit message summarizes most of the
changes.  Because this is a major change very late in the beta cycle I would
appreciate it if other developers would give this a through review.  Be
kind, my head is still spinning from all this synchronization stuff.

If no one has any complaints I'll cut a new (and hopfully final) beta
release for Tomcat 3.2.2 in a couple days.


> -----Original Message-----
> From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]]
> Sent: Wednesday, April 25, 2001 6:30 PM
> To: [EMAIL PROTECTED]
> Subject: cvs commit: jakarta-tomcat/src/share/org/apache/jasper/servlet
> JspServlet.java
>
>
> 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/mes
> sages.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.p
> roperties
>
>   Index: messages_es.properties
>   ===================================================================
>   RCS file:
> /home/cvs/jakarta-tomcat/src/share/org/apache/jasper/resources/mes
> sages_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.p
> roperties
>
>   Index: messages_fr.properties
>   ===================================================================
>   RCS file:
> /home/cvs/jakarta-tomcat/src/share/org/apache/jasper/resources/mes
> sages_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/JspSe
> rvlet.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