Author: remm Date: Thu Nov 8 10:29:24 2018 New Revision: 1846118 URL: http://svn.apache.org/viewvc?rev=1846118&view=rev Log: Refactor container level threads using the Service executor. Add additional monitoring of the main container background processor to reschedule it when needed.
Modified: tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties tomcat/trunk/webapps/docs/changelog.xml tomcat/trunk/webapps/docs/config/host.xml Modified: tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java?rev=1846118&r1=1846117&r2=1846118&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java Thu Nov 8 10:29:24 2018 @@ -24,16 +24,13 @@ import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -55,6 +52,7 @@ import org.apache.catalina.LifecycleStat import org.apache.catalina.Loader; import org.apache.catalina.Pipeline; import org.apache.catalina.Realm; +import org.apache.catalina.Service; import org.apache.catalina.Valve; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Request; @@ -172,6 +170,12 @@ public abstract class ContainerBase exte /** + * The future allowing control of the background processor. + */ + protected ScheduledFuture<?> backgroundProcessorFuture; + protected ScheduledFuture<?> monitorFuture; + + /** * The container event listeners for this Container. Implemented as a * CopyOnWriteArrayList since listeners may invoke methods to add/remove * themselves or other listeners and with a ReadWriteLock that would trigger @@ -254,18 +258,6 @@ public abstract class ContainerBase exte /** - * The background thread. - */ - private Thread thread = null; - - - /** - * The background thread completion semaphore. - */ - private volatile boolean threadDone = false; - - - /** * The access log to use for requests normally handled by this container * that have been handled earlier in the processing chain. */ @@ -288,27 +280,6 @@ public abstract class ContainerBase exte return startStopThreads; } - /** - * Handles the special values. - */ - private int getStartStopThreadsInternal() { - int result = getStartStopThreads(); - - // Positive values are unchanged - if (result > 0) { - return result; - } - - // Zero == Runtime.getRuntime().availableProcessors() - // -ve == Runtime.getRuntime().availableProcessors() + value - // These two are the same - result = Runtime.getRuntime().availableProcessors() + result; - if (result < 1) { - result = 1; - } - return result; - } - @Override public void setStartStopThreads(int startStopThreads) { int oldStartStopThreads = this.startStopThreads; @@ -316,7 +287,7 @@ public abstract class ContainerBase exte // Use local copies to ensure thread safety if (oldStartStopThreads != startStopThreads && startStopExecutor != null) { - reconfigureStartStopExecutor(getStartStopThreadsInternal()); + reconfigureStartStopExecutor(getStartStopThreads()); } } @@ -884,7 +855,7 @@ public abstract class ContainerBase exte @Override protected void initInternal() throws LifecycleException { - reconfigureStartStopExecutor(getStartStopThreadsInternal()); + reconfigureStartStopExecutor(getStartStopThreads()); super.initInternal(); } @@ -896,21 +867,15 @@ public abstract class ContainerBase exte */ private void reconfigureStartStopExecutor(int threads) { if (threads == 1) { + // Use a fake executor if (!(startStopExecutor instanceof InlineExecutorService)) { startStopExecutor = new InlineExecutorService(); } } else { - if (startStopExecutor instanceof ThreadPoolExecutor) { - ((ThreadPoolExecutor) startStopExecutor).setMaximumPoolSize(threads); - ((ThreadPoolExecutor) startStopExecutor).setCorePoolSize(threads); - } else { - BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>(); - ThreadPoolExecutor tpe = new ThreadPoolExecutor(threads, threads, 10, - TimeUnit.SECONDS, startStopQueue, - new StartStopThreadFactory(getName() + "-startStop-")); - tpe.allowCoreThreadTimeOut(true); - startStopExecutor = tpe; - } + // Delegate utility execution to the Service + Service service = Container.getService(this); + service.setUtilityThreads(threads); + startStopExecutor = Container.getService(this).getUtilityExecutor(); } } @@ -968,11 +933,15 @@ public abstract class ContainerBase exte ((Lifecycle) pipeline).start(); } - setState(LifecycleState.STARTING); // Start our thread threadStart(); + if (backgroundProcessorDelay > 0) { + monitorFuture = Container.getService(ContainerBase.this) + .getUtilityExecutor().scheduleWithFixedDelay( + new ContainerBackgroundProcessorMonitor(), 60, 60, TimeUnit.SECONDS); + } } @@ -987,6 +956,9 @@ public abstract class ContainerBase exte protected synchronized void stopInternal() throws LifecycleException { // Stop our thread + if (monitorFuture != null) { + monitorFuture.cancel(true); + } threadStop(); setState(LifecycleState.STOPPING); @@ -1295,18 +1267,21 @@ public abstract class ContainerBase exte * session timeouts. */ protected void threadStart() { - - if (thread != null) - return; - if (backgroundProcessorDelay <= 0) - return; - - threadDone = false; - String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; - thread = new Thread(new ContainerBackgroundProcessor(), threadName); - thread.setDaemon(true); - thread.start(); - + if (backgroundProcessorDelay > 0 && getState().isAvailable() + && (backgroundProcessorFuture == null || backgroundProcessorFuture.isDone())) { + if (backgroundProcessorFuture != null && backgroundProcessorFuture.isDone()) { + // There was an error executing the scheduled task, get it and log it + try { + backgroundProcessorFuture.get(); + } catch (InterruptedException | ExecutionException e) { + log.error(sm.getString("containerBase.backgroundProcess.error"), e); + } + } + backgroundProcessorFuture = Container.getService(this).getUtilityExecutor() + .scheduleWithFixedDelay(new ContainerBackgroundProcessor(), + backgroundProcessorDelay, backgroundProcessorDelay, + TimeUnit.SECONDS); + } } @@ -1315,20 +1290,10 @@ public abstract class ContainerBase exte * session timeouts. */ protected void threadStop() { - - if (thread == null) - return; - - threadDone = true; - thread.interrupt(); - try { - thread.join(); - } catch (InterruptedException e) { - // Ignore + if (backgroundProcessorFuture != null) { + backgroundProcessorFuture.cancel(true); + backgroundProcessorFuture = null; } - - thread = null; - } @@ -1347,40 +1312,26 @@ public abstract class ContainerBase exte return sb.toString(); } + // ------------------------------- ContainerBackgroundProcessor Inner Class - // -------------------------------------- ContainerExecuteDelay Inner Class + protected class ContainerBackgroundProcessorMonitor implements Runnable { + @Override + public void run() { + if (getState().isAvailable()) { + threadStart(); + } + } + } /** - * Private thread class to invoke the backgroundProcess method + * Private runnable class to invoke the backgroundProcess method * of this container and its children after a fixed delay. */ protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { - Throwable t = null; - String unexpectedDeathMessage = sm.getString( - "containerBase.backgroundProcess.unexpectedThreadDeath", - Thread.currentThread().getName()); - try { - while (!threadDone) { - try { - Thread.sleep(backgroundProcessorDelay * 1000L); - } catch (InterruptedException e) { - // Ignore - } - if (!threadDone) { - processChildren(ContainerBase.this); - } - } - } catch (RuntimeException|Error e) { - t = e; - throw e; - } finally { - if (!threadDone) { - log.error(unexpectedDeathMessage, t); - } - } + processChildren(ContainerBase.this); } protected void processChildren(Container container) { @@ -1407,17 +1358,17 @@ public abstract class ContainerBase exte } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); - log.error("Exception invoking periodic operation: ", t); + log.error(sm.getString("containerBase.backgroundProcess.error"), t); } finally { if (container instanceof Context) { ((Context) container).unbind(false, originalClassLoader); - } + } } } } - // ----------------------------- Inner classes used with start/stop Executor + // ---------------------------- Inner classes used with start/stop Executor private static class StartChild implements Callable<Void> { @@ -1451,22 +1402,4 @@ public abstract class ContainerBase exte } } - private static class StartStopThreadFactory implements ThreadFactory { - private final ThreadGroup group; - private final AtomicInteger threadNumber = new AtomicInteger(1); - private final String namePrefix; - - public StartStopThreadFactory(String namePrefix) { - SecurityManager s = System.getSecurityManager(); - group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); - this.namePrefix = namePrefix; - } - - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()); - thread.setDaemon(true); - return thread; - } - } } Modified: tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties?rev=1846118&r1=1846117&r2=1846118&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties [UTF-8] (original) +++ tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties [UTF-8] Thu Nov 8 10:29:24 2018 @@ -97,6 +97,7 @@ containerBase.backgroundProcess.cluster= containerBase.backgroundProcess.realm=Exception processing realm [{0}] background process containerBase.backgroundProcess.valve=Exception processing valve [{0}] background process containerBase.backgroundProcess.unexpectedThreadDeath=Unexpected death of background thread [{0}] +containerBase.backgroundProcess.error=Exception processing background thread filterChain.filter=Filter execution threw an exception filterChain.servlet=Servlet execution threw an exception jreLeakListener.gcDaemonFail=Failed to trigger creation of the GC Daemon thread during Tomcat start to prevent possible memory leaks. This is expected on non-Sun JVMs. Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1846118&r1=1846117&r2=1846118&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Thu Nov 8 10:29:24 2018 @@ -68,6 +68,13 @@ Add a scheduled executor to the Service, which can be used to process periodic utility tasks. (remm) </add> + <update> + Refactor container background processor using the Service executor, and + add monitoring to reschedule it in case of an unexpected error. (remm) + </update> + <update> + Refactor parallel deployment threads using the Service executor. (remm) + </update> </changelog> </subsection> </section> Modified: tomcat/trunk/webapps/docs/config/host.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/host.xml?rev=1846118&r1=1846117&r2=1846118&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/config/host.xml (original) +++ tomcat/trunk/webapps/docs/config/host.xml Thu Nov 8 10:29:24 2018 @@ -203,11 +203,10 @@ child <a href="context.html">Context</a> elements in parallel. The same thread pool will be used to deploy new <a href="context.html">Context</a>s if automatic deployment is being - used. The special value of 0 will result in the value of - <code>Runtime.getRuntime().availableProcessors()</code> being used. - Negative values will result in - <code>Runtime.getRuntime().availableProcessors() + value</code> being - used unless this is less than 1 in which case 1 thread will be used. If + used. + As the thread pool is shared at the service level, if more than one + host specifies this setting, only the maximum value will apply and will + be used for all except for the special value 1. If not specified, the default value of 1 will be used. If 1 thread is used then rather than using an <code>ExecutorService</code> the current thread will be used.</p> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org