/*******************************************************************************
* Copyright (c) 2015 Bruno Medeiros and other Contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bruno Medeiros - initial API and implementation
*******************************************************************************/
package melnorme.utilbox.concurrency;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import static melnorme.utilbox.misc.MiscUtil.isUncheckedException;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* An extension to {@link ThreadPoolExecutor}:
* - Safer handling of uncaught exceptions, they must be handled by given UncaughtExceptionHandler.
* - Has a few minor utils.
*/
public class ThreadPoolExecutorExt extends ThreadPoolExecutor implements ExecutorService, ICommonExecutor {
public static interface UncaughtExceptionHandler extends Consumer<Throwable> { }
protected final String name;
protected final UncaughtExceptionHandler uncaughtExceptionHandler;
public ThreadPoolExecutorExt(String name, UncaughtExceptionHandler ueHandler) {
this(1, 1, new LinkedBlockingQueue<Runnable>(), name, ueHandler);
}
public ThreadPoolExecutorExt(int corePoolSize, int maximumPoolSize,
BlockingQueue<Runnable> workQueue, String name, UncaughtExceptionHandler ueHandler) {
this(corePoolSize, maximumPoolSize, 60L, TimeUnit.SECONDS, workQueue, name, ueHandler);
}
public ThreadPoolExecutorExt(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, String name, UncaughtExceptionHandler ueHandler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
new ExecutorThreadFactory(name, ueHandler));
this.name = assertNotNull(name);
this.uncaughtExceptionHandler = assertNotNull(ueHandler);
}
@Override
public String getName() {
return name;
}
protected final AtomicInteger executeCount = new AtomicInteger(0);
@Override
public long getSubmittedTaskCount() {
return executeCount.get();
}
@Override
public void execute(Runnable command) {
executeCount.incrementAndGet();
// Check this assertion contract early, and in submitter's thread.
execute_checkPreConditions(command);
super.execute(command);
}
public static void execute_checkPreConditions(Runnable command) {
if(command instanceof ICancellableTask) {
ICancellableTask cancellable = (ICancellableTask) command;
assertTrue(cancellable.canExecute());
}
}
@Override
public void submitTask(ICancellableTask runnableTask) {
this.execute(runnableTask);
}
/* ----------------- ----------------- */
@Override
public List<Runnable> shutdownNowAndCancelAll() {
List<Runnable> remaining = super.shutdownNow();
for (Runnable runnable : remaining) {
if(runnable instanceof FutureTask<?>) {
FutureTask<?> futureTask = (FutureTask<?>) runnable;
futureTask.cancel(true);
} else if(runnable instanceof Future2<?>) {
Future2<?> futureTask = (Future2<?>) runnable;
futureTask.tryCancel();
}
}
return remaining;
}
/* ----------------- Uncaught exception handling ----------------- */
public static class ExecutorThreadFactory extends NamingThreadFactory {
protected final UncaughtExceptionHandler ueHandler;
public ExecutorThreadFactory(String poolName, UncaughtExceptionHandler ueHandler) {
super(poolName);
this.ueHandler = ueHandler;
}
@Override
public Thread newThread(Runnable runable) {
Thread newThread = super.newThread(runable);
newThread.setUncaughtExceptionHandler((thread, throwable) -> ueHandler.accept(throwable));
return newThread;
}
}
@Override
protected void afterExecute(final Runnable runnable, final Throwable throwable) {
if (throwable == null && runnable instanceof Future) {
Future<?> future = (Future<?>) runnable;
assertTrue(future.isDone());
try {
future.get();
} catch (InterruptedException ie) {
// This should not happen because the future is done, get() should return succesfully
throw assertFail();
} catch (CancellationException ce) {
assertTrue(future.isCancelled());
return;
} catch (ExecutionException ee) {
Throwable futureThrowable = ee.getCause();
if(!isUncheckedException(futureThrowable)) {
// for Future's, we only consider it to be unexpected if it's an unchecked exception
// otherwise it can be expected the task client code know how to handle to exception
return;
} else {
handleUnexpectedException(futureThrowable);
}
}
}
// We don't handle the uncaught throwables here.
// Instead the thread uncaught exception handler handles them.
}
protected final void handleUnexpectedException(Throwable throwable) {
// Handle the uncaught exception.
// Usually this will log the exception, so that the user is noticed an internal error occurred.
uncaughtExceptionHandler.accept(throwable);
}
}