I'll have also some patches to port back in TC 3.2 mod_jk/ajp13.... - Henri Gomez ___[_]____ EMAIL : [EMAIL PROTECTED] (. .) PGP KEY : 697ECEDD ...oOOo..(_)..oOOo... PGP Fingerprint : 9DF8 1EA8 ED53 2F39 DC9B 904A 364F 80E6 >-----Original Message----- >From: Marc Saegesser [mailto:[EMAIL PROTECTED]] >Sent: Thursday, April 26, 2001 1:40 AM >To: [EMAIL PROTECTED] >Subject: Tomcat 3.2.2 and Thread synchronization > > >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.p >roperties >> >> 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.parentCla >ssLoader); >> + } >> + >> + // 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; >> >> >> >