From c89e13a0de11e1ebe7dae75f401f7993a01c5e16 Mon Sep 17 00:00:00 2001 From: markt Date: Sun, 25 Apr 2010 12:19:19 +0000 Subject: [PATCH] Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=48358 Add the ability to limit the number of JSPs loaded at any one time. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@937787 13f79535-47bb-0310-9956-ffa450edef68 --- conf/web.xml | 8 ++ java/org/apache/jasper/EmbeddedServletOptions.java | 25 +++++ java/org/apache/jasper/JspC.java | 4 + java/org/apache/jasper/Options.java | 6 ++ .../apache/jasper/compiler/JspRuntimeContext.java | 75 ++++++++++++++- .../jasper/resources/LocalStrings.properties | 1 + java/org/apache/jasper/servlet/JspServlet.java | 1 + .../apache/jasper/servlet/JspServletWrapper.java | 19 +++- java/org/apache/jasper/util/Entry.java | 60 ++++++++++++ java/org/apache/jasper/util/JspQueue.java | 103 +++++++++++++++++++++ webapps/docs/changelog.xml | 4 + webapps/docs/jasper-howto.xml | 6 ++ 12 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 java/org/apache/jasper/util/Entry.java create mode 100644 java/org/apache/jasper/util/JspQueue.java diff --git a/conf/web.xml b/conf/web.xml index a61ab2475..c1bde1730 100644 --- a/conf/web.xml +++ b/conf/web.xml @@ -185,6 +185,14 @@ + + + + + + + + diff --git a/java/org/apache/jasper/EmbeddedServletOptions.java b/java/org/apache/jasper/EmbeddedServletOptions.java index 3fdb0653a..3fe5a8855 100644 --- a/java/org/apache/jasper/EmbeddedServletOptions.java +++ b/java/org/apache/jasper/EmbeddedServletOptions.java @@ -186,6 +186,12 @@ public final class EmbeddedServletOptions implements Options { private boolean displaySourceFragment = true; + /** + * The maxim number of loaded jsps per web-application. If there are more + * jsps loaded, they will be unloaded. + */ + private int maxLoadedJsps = -1; + public String getProperty(String name ) { return settings.getProperty( name ); } @@ -383,6 +389,14 @@ public final class EmbeddedServletOptions implements Options { } /** + * Should any jsps be unloaded? If set to a value greater than 0 eviction of jsps + * is started. Default: -1 + * */ + public int getMaxLoadedJsps() { + return maxLoadedJsps; + } + + /** * Create an EmbeddedServletOptions object using data available from * ServletConfig and ServletContext. */ @@ -663,6 +677,17 @@ public final class EmbeddedServletOptions implements Options { } } + String maxLoadedJsps = config.getInitParameter("maxLoadedJsps"); + if (maxLoadedJsps != null) { + try { + this.maxLoadedJsps = Integer.parseInt(maxLoadedJsps); + } catch(NumberFormatException ex) { + if (log.isWarnEnabled()) { + log.warn(Localizer.getMessage("jsp.warning.maxLoadedJsps", ""+this.maxLoadedJsps)); + } + } + } + // Setup the global Tag Libraries location cache for this // web-application. tldLocationsCache = new TldLocationsCache(context); diff --git a/java/org/apache/jasper/JspC.java b/java/org/apache/jasper/JspC.java index afca8cf9a..7eadb4863 100644 --- a/java/org/apache/jasper/JspC.java +++ b/java/org/apache/jasper/JspC.java @@ -444,6 +444,10 @@ public class JspC implements Options { return true; } + public int getMaxLoadedJsps() { + return -1; + } + /** * {@inheritDoc} */ diff --git a/java/org/apache/jasper/Options.java b/java/org/apache/jasper/Options.java index fcf011ced..f7372feb5 100644 --- a/java/org/apache/jasper/Options.java +++ b/java/org/apache/jasper/Options.java @@ -221,4 +221,10 @@ public interface Options { */ public Map getCache(); + /** + * The maxim number of loaded jsps per web-application. If there are more + * jsps loaded, they will be unloaded. If unset or less than 0, no jsps + * are unloaded. + */ + public int getMaxLoadedJsps(); } diff --git a/java/org/apache/jasper/compiler/JspRuntimeContext.java b/java/org/apache/jasper/compiler/JspRuntimeContext.java index 397e1bbd7..ab922fd66 100644 --- a/java/org/apache/jasper/compiler/JspRuntimeContext.java +++ b/java/org/apache/jasper/compiler/JspRuntimeContext.java @@ -40,9 +40,11 @@ import org.apache.jasper.Options; import org.apache.jasper.runtime.JspFactoryImpl; import org.apache.jasper.security.SecurityClassLoad; import org.apache.jasper.servlet.JspServletWrapper; +import org.apache.jasper.util.JspQueue; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; + /** * Class for tracking JSP compile time file dependencies when the * &060;%@include file="..."%&062; directive is used. @@ -171,7 +173,11 @@ public final class JspRuntimeContext { * Maps JSP pages to their JspServletWrapper's */ private Map jsps = new ConcurrentHashMap(); - + + /** + * Keeps JSP pages ordered by last access. + */ + private JspQueue jspQueue = new JspQueue(); // ------------------------------------------------------ Public Methods @@ -205,6 +211,30 @@ public final class JspRuntimeContext { } /** + * Push a newly compiled JspServletWrapper into the queue at first + * execution of jsp. + * + * @param jsw Servlet wrapper for jsp. + * @return a ticket that can be pushed to front of queue at later execution times. + * */ + public org.apache.jasper.util.Entry push(JspServletWrapper jsw) { + synchronized (jspQueue) { + return jspQueue.push(jsw); + } + } + + /** + * Push ticket for JspServletWrapper to front of the queue. + * + * @param ticket the ticket for the jsp. + * */ + public void makeFirst(org.apache.jasper.util.Entry ticket) { + synchronized( jspQueue ) { + jspQueue.makeYoungest(ticket); + } + } + + /** * Returns the number of JSPs for which JspServletWrappers exist, i.e., * the number of JSPs that have been loaded into the webapp. * @@ -468,5 +498,48 @@ public final class JspRuntimeContext { return new SecurityHolder(source, permissions); } + /** Returns a JspServletWrapper that should be destroyed. Default strategy: Least recently used. */ + public JspServletWrapper getJspForUnload(final int maxLoadedJsps) { + if( jsps.size() > maxLoadedJsps ) { + synchronized( jsps ) { + JspServletWrapper oldest; + synchronized( jspQueue) { + oldest = jspQueue.pop(); + } + if (oldest != null) { + removeWrapper(oldest.getJspUri()); + return oldest; + } + } + } + return null; + } + /** + * Method used by background thread to check if any JSP's should be destroyed. + * If JSP's to be unloaded are found, they will be destroyed. + * Uses the lastCheck time from background compiler to determine if it is time to unload JSP's. + */ + public void checkUnload() { + if (options.getMaxLoadedJsps() > 0) { + long now = System.currentTimeMillis(); + if (now > (lastCheck + (options.getCheckInterval() * 1000L))) { + while (unloadJsp()); + } + } + } + + /** + * Checks whether there is a jsp to unload, if one is found, it is destroyed. + * */ + public boolean unloadJsp() { + JspServletWrapper jsw = getJspForUnload(options.getMaxLoadedJsps()); + if( null != jsw ) { + synchronized(jsw) { + jsw.destroy(); + return true; + } + } + return false; + } } diff --git a/java/org/apache/jasper/resources/LocalStrings.properties b/java/org/apache/jasper/resources/LocalStrings.properties index 409da71f8..eb0273bb4 100644 --- a/java/org/apache/jasper/resources/LocalStrings.properties +++ b/java/org/apache/jasper/resources/LocalStrings.properties @@ -176,6 +176,7 @@ jsp.warning.dumpSmap=Warning: Invalid value for the initParam dumpSmap. Will use jsp.warning.genchararray=Warning: Invalid value for the initParam genStrAsCharArray. Will use the default value of \"false\" jsp.warning.suppressSmap=Warning: Invalid value for the initParam suppressSmap. Will use the default value of \"false\" jsp.warning.displaySourceFragment=Warning: Invalid value for the initParam displaySourceFragment. Will use the default value of \"true\" +jsp.warning.maxLoadedJsps=Warning: Invalid value for the initParam maxLoadedJsps. Will use the default value of \"-1\" jsp.error.badtaglib=Unable to open taglibrary {0} : {1} jsp.error.badGetReader=Cannot create a reader when the stream is not buffered jsp.warning.unknown.element.in.taglib=Unknown element ({0}) in taglib diff --git a/java/org/apache/jasper/servlet/JspServlet.java b/java/org/apache/jasper/servlet/JspServlet.java index 6c36333e5..95d9e7678 100644 --- a/java/org/apache/jasper/servlet/JspServlet.java +++ b/java/org/apache/jasper/servlet/JspServlet.java @@ -286,6 +286,7 @@ public class JspServlet extends HttpServlet implements PeriodicEventListener { public void periodicEvent() { + rctxt.checkUnload(); rctxt.checkCompile(); } diff --git a/java/org/apache/jasper/servlet/JspServletWrapper.java b/java/org/apache/jasper/servlet/JspServletWrapper.java index 8f16a574c..f4266d252 100644 --- a/java/org/apache/jasper/servlet/JspServletWrapper.java +++ b/java/org/apache/jasper/servlet/JspServletWrapper.java @@ -40,6 +40,7 @@ import org.apache.jasper.compiler.JspRuntimeContext; import org.apache.jasper.compiler.Localizer; import org.apache.jasper.runtime.InstanceManagerFactory; import org.apache.jasper.runtime.JspSourceDependent; +import org.apache.jasper.util.Entry; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.InstanceManager; @@ -81,6 +82,7 @@ public class JspServletWrapper { private JasperException compileException; private long servletClassLastModifiedTime; private long lastModificationTest = 0L; + private Entry ticket; /* * JspServletWrapper for JSP pages. @@ -273,6 +275,10 @@ public class JspServletWrapper { return tripCount--; } + public String getJspUri() { + return jspUri; + } + public void service(HttpServletRequest request, HttpServletResponse response, boolean precompile) @@ -306,6 +312,10 @@ public class JspServletWrapper { // The following sets reload to true, if necessary ctxt.compile(); + + if (options.getMaxLoadedJsps() > 0) { + ctxt.getRuntimeContext().unloadJsp(); + } } } else { if (compileException != null) { @@ -367,7 +377,14 @@ public class JspServletWrapper { } else { theServlet.service(request, response); } - + if (options.getMaxLoadedJsps() > 0) { + synchronized(this) { + if (ticket == null) + ticket = ctxt.getRuntimeContext().push(this); + else + ctxt.getRuntimeContext().makeFirst(ticket); + } + } } catch (UnavailableException ex) { String includeRequestUri = (String) request.getAttribute("javax.servlet.include.request_uri"); diff --git a/java/org/apache/jasper/util/Entry.java b/java/org/apache/jasper/util/Entry.java new file mode 100644 index 000000000..60d1d8e5e --- /dev/null +++ b/java/org/apache/jasper/util/Entry.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jasper.util; + +/** + * Implementation of a list entry. It exposes links to previous and next + * elements on package level only. + */ +public class Entry { + + /** The content this entry is valid for. */ + private final T content; + /** Pointer to next element in queue. */ + private Entry next; + /** Pointer to previous element in queue. */ + private Entry previous; + + public Entry(T object) { + content = object; + } + + protected void setNext(final Entry next) { + this.next = next; + } + + protected void setPrevious(final Entry previous) { + this.previous = previous; + } + + public T getContent() { + return content; + } + + public Entry getPrevious() { + return previous; + } + + public Entry getNext() { + return next; + } + + @Override + public String toString() { + return content.toString(); + } +} diff --git a/java/org/apache/jasper/util/JspQueue.java b/java/org/apache/jasper/util/JspQueue.java new file mode 100644 index 000000000..3a8a65b94 --- /dev/null +++ b/java/org/apache/jasper/util/JspQueue.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jasper.util; + +/** + * + * The JspQueue is supposed to hold a set of instances in sorted order. Sorting + * order is determined by the instances' content. As this content may change + * during instance lifetime, the Queue must be cheap to update - ideally in + * constant time. + * + * Access to the first element in the queue must happen in constant time. + * + * Only a minimal set of operations is implemented. + */ +public class JspQueue { + + /** Head of the queue. */ + private Entry head; + /** Last element of the queue. */ + private Entry last; + + /** Initialize empty queue. */ + public JspQueue() { + head = null; + last = null; + } + + /** + * Adds an object to the end of the queue and returns the entry created for + * said object. The entry can later be reused for moving the entry back to + * the front of the list. + * + * @param object + * the object to append to the end of the list. + * @return a ticket for use when the object should be moved back to the + * front. + * */ + public Entry push(final T object) { + Entry entry = new Entry(object); + if (head == null) { + head = last = entry; + } else { + last.setPrevious(entry); + entry.setNext(last); + last = entry; + } + + return entry; + } + + /** + * Removes the head of the queue and returns its content. + * + * @return the content of the head of the queue. + **/ + public T pop() { + T content = null; + if (head != null) { + content = head.getContent(); + if (head.getPrevious() != null) + head.getPrevious().setNext(null); + head = head.getPrevious(); + } + return content; + } + + /** + * Moves the candidate to the front of the queue. + * + * @param candidate + * the entry to move to the front of the queue. + * */ + public void makeYoungest(final Entry candidate) { + if (candidate.getPrevious() != null) { + Entry candidateNext = candidate.getNext(); + Entry candidatePrev = candidate.getPrevious(); + candidatePrev.setNext(candidateNext); + if (candidateNext != null) + candidateNext.setPrevious(candidatePrev); + else + head = candidatePrev; + candidate.setNext(last); + candidate.setPrevious(null); + last.setPrevious(candidate); + last = candidate; + } + } +} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 71904a8e3..7c5b81417 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -154,6 +154,10 @@ 787978 Use "1.6" as the default value for compilerSourceVM and compilerTargetVM options of Jasper. (kkolinko) + + 48358: Add support for limiting the number of JSPs that are + loaded at any one time. Based on a patch by Isabel Drost. (markt) + diff --git a/webapps/docs/jasper-howto.xml b/webapps/docs/jasper-howto.xml index 9b3c4f1a4..34474181c 100644 --- a/webapps/docs/jasper-howto.xml +++ b/webapps/docs/jasper-howto.xml @@ -157,6 +157,12 @@ code for each page instead of deleting it? true or print statement per input line, to ease debugging? true or false, default true. +
  • maxLoadedJsps - The maximum number of JSPs that will be +loaded for a web application. If more than this number of JSPs are loaded, the +least recently used JSPs will be unloaded so that the number of JSPs loaded at +any one time does not exceed this limit. A value of zero or less indicates no +limit. Default -1
  • +
  • modificationTestInterval - Causes a JSP (and its dependent files) to not be checked for modification during the specified time interval (in seconds) from the last time the JSP was checked for modification. A value of -- 2.11.0