/** * Licensed 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.aurora.scheduler.base; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.aurora.common.application.Lifecycle; import org.apache.aurora.common.stats.Stats; import org.slf4j.Logger; import static java.util.Objects.requireNonNull; /** * Utility class for facilitating async scheduling. */ public final class AsyncUtil { private AsyncUtil() { // Utility class. } private static final AtomicLong UNCAUGHT_EXCEPTIONS = Stats.exportLong("async_executor_uncaught_exceptions"); /** * Creates a {@link ScheduledThreadPoolExecutor} that logs unhandled errors. * * @param poolSize Thread pool size. * @param nameFormat Thread naming format. * @param logger Logger instance. * @return instance of {@link ScheduledThreadPoolExecutor} enabled to log unhandled exceptions. */ public static ScheduledThreadPoolExecutor loggingScheduledExecutor( int poolSize, String nameFormat, final Logger logger) { requireNonNull(nameFormat); return new ScheduledThreadPoolExecutor( poolSize, new ThreadFactoryBuilder().setDaemon(true).setNameFormat(nameFormat).build()) { @Override protected void afterExecute(Runnable runnable, Throwable throwable) { super.afterExecute(runnable, throwable); evaluateResult(runnable, throwable, logger); } }; } /** * Creates a single-threaded {@link ScheduledThreadPoolExecutor} that logs unhandled errors. * * @param nameFormat Thread naming format. * @param logger Logger instance. * @return instance of {@link ScheduledThreadPoolExecutor} enabled to log unhandled exceptions. */ public static ScheduledThreadPoolExecutor singleThreadLoggingScheduledExecutor( String nameFormat, Logger logger) { return loggingScheduledExecutor(1, nameFormat, logger); } /** * Creates a {@link ThreadPoolExecutor} that logs unhandled errors. * * @param corePoolSize see {@link ThreadPoolExecutor}. * @param maxPoolSize see {@link ThreadPoolExecutor}. * @param workQueue see {@link ThreadPoolExecutor}. * @param nameFormat Thread naming format. * @param logger Logger instance. * @return instance of {@link ThreadPoolExecutor} enabled to log unhandled exceptions. */ public static ThreadPoolExecutor loggingExecutor( int corePoolSize, int maxPoolSize, BlockingQueue<Runnable> workQueue, String nameFormat, final Logger logger) { return new ThreadPoolExecutor( corePoolSize, maxPoolSize, 0L, TimeUnit.MILLISECONDS, workQueue, new ThreadFactoryBuilder().setDaemon(true).setNameFormat(nameFormat).build()) { @Override protected void afterExecute(Runnable runnable, Throwable throwable) { super.afterExecute(runnable, throwable); evaluateResult(runnable, throwable, logger); } }; } /** * Helper wrapper to call the provided {@link Lifecycle} on unhandled error. * * @param lifecycle {@link Lifecycle} instance. * @param logger Logger instance. * @param message message to log. * @param runnable {@link Runnable} to wrap. * @return A new {@link Runnable} logging an error and calling {@link Lifecycle#shutdown()}. */ public static Runnable shutdownOnError( Lifecycle lifecycle, Logger logger, String message, Runnable runnable) { requireNonNull(lifecycle); requireNonNull(logger); requireNonNull(message); requireNonNull(runnable); return () -> { try { runnable.run(); } catch (Throwable t) { logger.error(message, t); lifecycle.shutdown(); } }; } private static void evaluateResult(Runnable runnable, Throwable throwable, Logger logger) { // See java.util.concurrent.ThreadPoolExecutor#afterExecute(Runnable, Throwable) // for more details and an implementation example. if (throwable == null) { if (runnable instanceof Future) { try { Future<?> future = (Future<?>) runnable; if (future.isDone()) { future.get(); } } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } catch (ExecutionException ee) { logger.error(ee.toString(), ee); UNCAUGHT_EXCEPTIONS.incrementAndGet(); } } } else { logger.error(throwable.toString(), throwable); UNCAUGHT_EXCEPTIONS.incrementAndGet(); } } }