package com.vaguehope.onosendai.util.exec; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.vaguehope.onosendai.util.LogWrapper; public final class ExecUtils { private static final long THREAD_LINGER_TIME_SECONDS = 30L; private ExecUtils () { throw new AssertionError(); } public static ExecutorService newBoundedCachedThreadPool (final int maxThreads, final LogWrapper log) { return newBoundedCachedThreadPool(maxThreads, log, null); } public static ExecutorService newBoundedCachedThreadPool (final int maxThreads, final LogWrapper log, final ExecutorEventListener eventListener) { final ThreadPoolExecutor tpe = new TrackingThreadPoolExecutor(eventListener, log, maxThreads, maxThreads, THREAD_LINGER_TIME_SECONDS, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new LoggingThreadFactory(new LoggingThreadGroup(Thread.currentThread().getThreadGroup(), log), log), new LoggingRejectionHandler(log)); tpe.allowCoreThreadTimeOut(true); return tpe; } private static class TrackingThreadPoolExecutor extends ThreadPoolExecutor { private final ExecutorEventListener eventListener; private final LogWrapper log; public TrackingThreadPoolExecutor (final ExecutorEventListener eventListener, final LogWrapper log, final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final TimeUnit unit, final BlockingQueue<Runnable> workQueue, final ThreadFactory threadFactory, final RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); this.eventListener = eventListener; this.log = log; } @Override public void execute (final Runnable command) { if (this.eventListener == null) { super.execute(command); } else { super.execute(new TrackingRunnable(this.eventListener, this.log, command)); } } private static class TrackingRunnable implements Runnable { private final ExecutorEventListener eventListener; private final LogWrapper log; private final Runnable command; public TrackingRunnable (final ExecutorEventListener eventListener, final LogWrapper log, final Runnable command) { this.eventListener = eventListener; this.log = log; this.command = command; } @Override public void run () { this.eventListener.execStart(this.log.getPrefix(), this.command); try { this.command.run(); } finally { this.eventListener.execEnd(this.command); } } } } private static class LoggingThreadGroup extends ThreadGroup { private final LogWrapper log; public LoggingThreadGroup (final ThreadGroup parent, final LogWrapper log) { super(parent, "tg-" + log.getPrefix()); this.log = log; } @Override public void uncaughtException (final Thread t, final Throwable e) { this.log.wtf("Thread died: " + t.toString(), e); } } private static class LoggingThreadFactory implements ThreadFactory { private final LogWrapper log; private final AtomicInteger counter = new AtomicInteger(0); private final ThreadGroup group; public LoggingThreadFactory (final ThreadGroup group, final LogWrapper log) { this.group = group; this.log = log; } @Override public Thread newThread (final Runnable r) { final Thread t = new Thread(this.group, r, "t-" + this.log.getPrefix() + this.counter.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); t.setPriority(Thread.NORM_PRIORITY - 1); return t; } } private static class LoggingRejectionHandler implements RejectedExecutionHandler { private final LogWrapper log; public LoggingRejectionHandler (final LogWrapper log) { this.log = log; } @Override public void rejectedExecution (final Runnable r, final ThreadPoolExecutor executor) { this.log.w("Rejected execution: '%s'.", r.toString()); } } }