//------------------------------------------------------------------------------------------------// // // // O m r E x e c u t o r s // // // //------------------------------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Audiveris 2018. All rights reserved. // // This program is free software: you can redistribute it and/or modify it under the terms of the // GNU Affero General Public License as published by the Free Software Foundation, either version // 3 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License along with this // program. If not, see <http://www.gnu.org/licenses/>. //------------------------------------------------------------------------------------------------// // </editor-fold> package org.audiveris.omr.util; import org.audiveris.omr.constant.Constant; import org.audiveris.omr.constant.ConstantSet; import org.audiveris.omr.step.ProcessingCancellationException; import org.audiveris.omr.util.param.Param; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * Class {@code OmrExecutors} handles several pools of threads provided to Audiveris * application: * <ul> * <li>lowExecutor: a fixed nb (#cpu+1) of threads with low priority</li> * <li>highExecutor: a fixed nb (#cpu+1) of threads with high priority</li> * <li>cachedLowExecutor: a varying nb of threads with low priority</li> * </ul> * * @author Hervé Bitteur */ public class OmrExecutors { private static final Logger logger = LoggerFactory.getLogger(OmrExecutors.class); private static final Constants constants = new Constants(); /** Default parameter. */ public static final Param<Boolean> defaultParallelism = new Default(); /** Number of processors available. */ private static final int cpuCount = Runtime.getRuntime().availableProcessors(); // Specific pools private static final Pool highs = new Highs(); private static final Pool lows = new Lows(); private static final Pool cachedLows = new CachedLows(); /** To handle all the pools as a whole. */ private static final Collection<Pool> allPools = Arrays.asList(cachedLows, lows, highs); /** To prevent parallel creation of pools when closing. */ private static volatile boolean creationAllowed = true; static { if (constants.printEnvironment.isSet()) { logger.info( "Environment. CPU count: {}, Use of parallelism: {}", cpuCount, defaultParallelism.getValue()); } } /** * Not meant to be instantiated. */ private OmrExecutors () { } //----------------------// // getCachedLowExecutor // //----------------------// /** * Return the (single) pool of cached low priority threads * * @return the cached low pool, allocated if needed */ public static ExecutorService getCachedLowExecutor () { return cachedLows.getPool(); } //-----------------// // getHighExecutor // //-----------------// /** * Return the (single) pool of high priority threads * * @return the high pool, allocated if needed */ public static ExecutorService getHighExecutor () { return highs.getPool(); } //----------------// // getLowExecutor // //----------------// /** * Return the (single) pool of low priority threads * * @return the low pool, allocated if needed */ public static ExecutorService getLowExecutor () { return lows.getPool(); } //-----------------// // getNumberOfCpus // //-----------------// /** * Report the number of "processors" available * * @return the number of CPUs */ public static int getNumberOfCpus () { return cpuCount; } //---------// // restart // //---------// /** * (re-)Allow the creation of pools. */ public static void restart () { creationAllowed = true; logger.debug("OmrExecutors open"); } //----------// // shutdown // //----------// /** * Gracefully shut down all the executors launched * * @return true if OK, false if timeout */ public static boolean shutdown () { boolean result = true; logger.debug("Closing all pools ..."); // No creation of pools from now on! creationAllowed = false; for (Pool pool : allPools) { if (pool.isActive()) { if (!pool.close()) { result = false; } } else { logger.debug("Pool {} not active", pool.getName()); } } logger.debug("OmrExecutors closed"); return result; } //------// // Pool // //------// private abstract static class Pool { /** The underlying pool of threads. */ protected ExecutorService pool; /** * Name the pool. */ public abstract String getName (); /** * Terminate the pool. * <p> * BEWARE, doc on shutdownNow says: There are no guarantees beyond best-effort attempts to * stop processing actively executing tasks. For example, typical implementations will * cancel via {@link Thread#interrupt}, so any task that fails to respond to interrupts may * never terminate. * * @return true if OK, false if timed out */ public synchronized boolean close () { boolean result = true; if (!isActive()) { return result; } logger.debug("Closing pool {}", getName()); pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(constants.graceDelay.getValue(), TimeUnit.SECONDS)) { logger.warn("Pool {} did not terminate", getName()); result = false; // (Try to) cancel currently executing tasks. pool.shutdownNow(); } } catch (InterruptedException ie) { // (Re-)Try to cancel if current thread also got interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); } logger.debug("Pool {} closed.", getName()); // Let garbage collector work pool = null; return result; } /** * Get the pool ready to use. */ public synchronized ExecutorService getPool () { if (!creationAllowed) { logger.info("No longer allowed to create pool: {}", getName()); throw new ProcessingCancellationException("Executor closed"); } if (!isActive()) { logger.debug("Creating pool: {}", getName()); pool = createPool(); } return pool; } /** * Is the pool active?. */ public synchronized boolean isActive () { return (pool != null) && !pool.isShutdown(); } /** * Needed to create the concrete pool. */ protected abstract ExecutorService createPool (); } //-----------// // Constants // //-----------// private static class Constants extends ConstantSet { private final Constant.Boolean printEnvironment = new Constant.Boolean( false, "Should we print out current environment?"); private final Constant.Boolean useParallelism = new Constant.Boolean( false, //true, // Disabled for the time being "Should we use parallelism when we have several processors?"); private final Constant.Integer graceDelay = new Constant.Integer( "seconds", 60, "Time to wait for terminating tasks"); } //------------// // CachedLows // //------------// /** Cached pool with low priority. */ private static class CachedLows extends Pool { @Override public String getName () { return "cachedLow"; } @Override protected ExecutorService createPool () { return Executors.newCachedThreadPool(new Factory(getName(), Thread.MIN_PRIORITY, 0)); } } //---------// // Default // //---------// private static class Default extends Param<Boolean> { @Override public Boolean getSpecific () { if (constants.useParallelism.isSourceValue()) { return null; } else { return constants.useParallelism.getValue(); } } @Override public Boolean getValue () { return constants.useParallelism.getValue(); } @Override public boolean isSpecific () { return !constants.useParallelism.isSourceValue(); } @Override public boolean setSpecific (Boolean specific) { if (!getValue().equals(specific)) { constants.useParallelism.setValue(specific); logger.info("Parallelism is {} allowed", specific ? "now" : "no longer"); return true; } else { return false; } } } //---------// // Factory // //---------// private static class Factory implements ThreadFactory { private final ThreadGroup group; private final String threadPrefix; private final int threadPriority; private final long stackSize; private final AtomicInteger threadNumber = new AtomicInteger(0); Factory (String threadPrefix, int threadPriority, long stackSize) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); this.threadPrefix = threadPrefix; this.threadPriority = threadPriority; this.stackSize = stackSize; } @Override public Thread newThread (Runnable r) { Thread t = new Thread(group, r, getOneThreadName(), stackSize); if (t.isDaemon()) { t.setDaemon(false); } if (t.getPriority() != threadPriority) { t.setPriority(threadPriority); } return t; } private String getOneThreadName () { return threadPrefix + "-thread-" + threadNumber.incrementAndGet(); } } //-------// // Highs // //-------// /** Fixed pool with high priority. */ private static class Highs extends Pool { @Override public String getName () { return "high"; } @Override protected ExecutorService createPool () { return Executors.newFixedThreadPool( defaultParallelism.getValue() ? (cpuCount + 1) : 1, new Factory(getName(), Thread.NORM_PRIORITY, 0)); } } //------// // Lows // //------// /** Fixed pool with low priority. */ private static class Lows extends Pool { @Override public String getName () { return "low"; } @Override protected ExecutorService createPool () { return Executors.newFixedThreadPool( defaultParallelism.getValue() ? (cpuCount + 1) : 1, new Factory(getName(), Thread.MIN_PRIORITY, 0)); } } }