package de.invesdwin.util.concurrent; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import de.invesdwin.util.collections.loadingcache.ALoadingCache; import de.invesdwin.util.shutdown.IShutdownHook; import de.invesdwin.util.shutdown.ShutdownHookManager; import de.invesdwin.util.time.duration.Duration; import de.invesdwin.util.time.fdate.FTimeUnit; @ThreadSafe public class WrappedExecutorService implements ExecutorService { private static final Duration FIXED_THREAD_KEEPALIVE_TIMEOUT = new Duration(60, FTimeUnit.SECONDS); private final Lock pendingCountLock = new ReentrantLock(); private final ALoadingCache<Long, Condition> pendingCount_limitListener = new ALoadingCache<Long, Condition>() { @Override protected Condition loadValue(final Long key) { return pendingCountLock.newCondition(); } }; private final AtomicLong pendingCount = new AtomicLong(); private final Object pendingCountWaitLock = new Object(); private final java.util.concurrent.ThreadPoolExecutor delegate; private volatile boolean logExceptions = false; private volatile boolean waitOnFullPendingCount = false; @GuardedBy("this") private IShutdownHook shutdownHook; protected WrappedExecutorService(final java.util.concurrent.ThreadPoolExecutor delegate, final String name) { this.delegate = delegate; this.shutdownHook = newShutdownHook(delegate); configure(name); } /** * Prevent reference leak to this instance by using a static method */ private static IShutdownHook newShutdownHook(final java.util.concurrent.ThreadPoolExecutor delegate) { return new IShutdownHook() { @Override public void shutdown() throws Exception { delegate.shutdownNow(); } }; } public boolean isLogExceptions() { return logExceptions; } public WrappedExecutorService withLogExceptions(final boolean logExceptions) { this.logExceptions = logExceptions; return this; } void incrementPendingCount(final boolean skipWaitOnFullPendingCount) throws InterruptedException { if (waitOnFullPendingCount && !skipWaitOnFullPendingCount) { synchronized (pendingCountWaitLock) { //Only one waiting thread may be woken up when this limit is reached! while (pendingCount.get() >= getFullPendingCount()) { awaitPendingCount(getWrappedInstance().getMaximumPoolSize() - 1); } notifyPendingCountListeners(pendingCount.incrementAndGet()); } } else { notifyPendingCountListeners(pendingCount.incrementAndGet()); } } void decrementPendingCount() { notifyPendingCountListeners(pendingCount.decrementAndGet()); } private void notifyPendingCountListeners(final Long currentPendingCount) { pendingCountLock.lock(); try { for (final Long limit : pendingCount_limitListener.keySet()) { if (currentPendingCount <= limit) { final Condition condition = pendingCount_limitListener.get(limit); if (condition != null) { condition.signalAll(); } } } } finally { pendingCountLock.unlock(); } } private synchronized void configure(final String name) { /* * All executors should be shutdown on application shutdown. */ ShutdownHookManager.register(shutdownHook); /* * All threads should stop after 60 seconds of idle time */ delegate.setKeepAliveTime(FIXED_THREAD_KEEPALIVE_TIMEOUT.longValue(), FIXED_THREAD_KEEPALIVE_TIMEOUT.getTimeUnit().timeUnitValue()); /* * Fixes non starting with corepoolsize von 0 and not filled queue (Java Conurrency In Practice Chapter 8.3.1). * If this bug can occur, a exception would be thrown here. */ delegate.allowCoreThreadTimeOut(true); /* * Named threads improve debugging. */ if (!(delegate.getThreadFactory() instanceof WrappedThreadFactory)) { delegate.setThreadFactory(new WrappedThreadFactory(name, delegate.getThreadFactory())); } } private synchronized void unconfigure() { if (shutdownHook != null) { ShutdownHookManager.unregister(shutdownHook); } shutdownHook = null; } public boolean isWaitOnFullPendingCount() { return waitOnFullPendingCount; } public WrappedExecutorService withWaitOnFullPendingCount(final boolean waitOnFullPendingCount) { this.waitOnFullPendingCount = waitOnFullPendingCount; return this; } public java.util.concurrent.ThreadPoolExecutor getWrappedInstance() { return delegate; } @Override public void shutdown() { getWrappedInstance().shutdown(); unconfigure(); } @Override public List<Runnable> shutdownNow() { final List<Runnable> l = getWrappedInstance().shutdownNow(); unconfigure(); return l; } @Override public boolean isShutdown() { return getWrappedInstance().isShutdown(); } @Override public boolean isTerminated() { return getWrappedInstance().isTerminated(); } @Override public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { return getWrappedInstance().awaitTermination(timeout, unit); } public boolean awaitTermination() throws InterruptedException { return awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); } public long getPendingCount() { return pendingCount.get(); } /** * Waits for pendingCount to shrink to a given limit size. * * WARNING: Pay attention when using this feature so that executors don't have circular task dependencies. If they * depend on each others pendingCount, this may cause a deadlock! */ public void awaitPendingCount(final long limit) throws InterruptedException { pendingCountLock.lock(); try { final Condition condition = pendingCount_limitListener.get(limit); while (getPendingCount() > limit) { Threads.throwIfInterrupted(); condition.await(); } } finally { pendingCountLock.unlock(); } } public void waitOnFullPendingCount() throws InterruptedException { awaitPendingCount(getFullPendingCount()); } public int getFullPendingCount() { return getWrappedInstance().getMaximumPoolSize(); } @Override public void execute(final Runnable command) { try { getWrappedInstance().execute(WrappedRunnable.newInstance(this, command)); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } @Override public <T> Future<T> submit(final Callable<T> task) { try { return getWrappedInstance().submit(WrappedCallable.newInstance(this, task)); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); return new InterruptingFuture<T>(); } } @Override public <T> Future<T> submit(final Runnable task, final T result) { try { return getWrappedInstance().submit(WrappedRunnable.newInstance(this, task), result); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); return new InterruptingFuture<T>(); } } @Override public Future<?> submit(final Runnable task) { try { return getWrappedInstance().submit(WrappedRunnable.newInstance(this, task)); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); return new InterruptingFuture<Object>(); } } @Override public <T> List<Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks) throws InterruptedException { return getWrappedInstance().invokeAll(WrappedCallable.newInstance(this, tasks)); } @Override public <T> List<Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit) throws InterruptedException { return getWrappedInstance().invokeAll(WrappedCallable.newInstance(this, tasks), timeout, unit); } @Override public <T> T invokeAny(final Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { return getWrappedInstance().invokeAny(WrappedCallable.newInstance(this, tasks)); } @Override public <T> T invokeAny(final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return getWrappedInstance().invokeAny(WrappedCallable.newInstance(this, tasks), timeout, unit); } @Override protected void finalize() throws Throwable { super.finalize(); shutdown(); } }