/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2005
* Copyright by ESO (in the framework of the ALMA collaboration),
* All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package alma.acs.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Helper class that allows tests (but also other code) to be executed concurrently,
* in an attempt to "bombard" a tested class with parallel calls,
* all arriving in the smallest possible time window.
* <p>
* This class is similar to {@link java.util.concurrent.ExecutorService},
* but it synchronizes the actual execution of all threads.
* Note that the simple creation and starting of threads does not guarantee
* that these threads run after {@link Thread#start()} returns.
* Therefore this class does not support methods such as {@link ExecutorService#execute(Runnable)}
* which execute one <code>Runnable</code> in one thread independently of the other threads that it runs.
* <p>
* An instance is good for only one burst of threads, otherwise IllegalStateException will be thrown.
*
* @author hsommer
*/
public class ThreadBurstExecutorService
{
private final ThreadPoolExecutor delegate;
private final CountDownLatch threadGate;
private final AtomicBoolean executed;
public ThreadBurstExecutorService(ThreadFactory threadFactory) {
delegate = new ThreadPoolExecutor(
0, // corePoolSize
Integer.MAX_VALUE, // maximumPoolSize
0L, TimeUnit.SECONDS, // keepAliveTime
new SynchronousQueue<Runnable>(), // pseudo workQueue (length 0)
threadFactory
// no custom RejectedExecutionHandler used -> RejectedExecutionException thrown if queue is full
);
threadGate = new CountDownLatch(1);
executed = new AtomicBoolean(false);
}
/**
* Submits a task and waits until the thread for this task has started (without yet executing the task!).
* Then this thread will block until {@link #executeAllAndWait(long, TimeUnit)} is called
* or the <code>awaitExecutionTimeout</code> occurs.
* @param task
* The task that should be executed when {@link #executeAllAndWait(long, TimeUnit)} is called.
* @param awaitExecutionTimeout
* This timeout is used twice: (a) to wait for the thread to be created and started,
* and to wait for the user to call {@link #executeAllAndWait(long, TimeUnit)}.
* @return An object handle that can be used to wait for completion of this task,
* or exceptions if any are thrown (e.g. ExecutionException), or to cancel the task.
* @throws InterruptedException If waiting for the thread to be created and started is interrupted.
* @throws IllegalStateException if {@link #executeAllAndWait(long, TimeUnit)} was called already.
*/
public <T> Future<T> submit(Callable<T> task, long awaitExecutionTimeout, TimeUnit unit) throws InterruptedException {
if (executed.get()) {
throw new IllegalStateException();
}
CountDownLatch confirmWaiting = new CountDownLatch(1);
InterceptingCallable<T> interceptingCallable =
new InterceptingCallable<T>(task, threadGate, confirmWaiting, awaitExecutionTimeout, unit);
Future<T> future = delegate.submit(interceptingCallable);
// wait till the thread has started, otherwise a subsequent executeAllAndWait call might come too early
if (!confirmWaiting.await(awaitExecutionTimeout, unit)) {
// thread has not started within awaitExecutionTimeout..
// TODO: feedback to the future, perhaps exception
System.out.println("ThreadBurstExecutorService#submit: Starting of thread too slow!");
}
return future;
}
/**
* Variant of {@link #submit(Callable, long, TimeUnit)} which gives the task as a {@link Runnable}.
* This does not allow to pass return values or exceptions from the thread to the caller,
* but may be more customary to use.
*/
public Future<Void> submit(final Runnable task, long awaitExecutionTimeout, TimeUnit unit) throws InterruptedException {
Callable<Void> adapter = new Callable<Void>() {
public Void call() throws Exception {
task.run();
return null;
}
};
return submit(adapter, awaitExecutionTimeout, unit);
}
/**
* Unleashes all submitted (and therefore already started) threads at the same time.
* Then waits until all tasks finish, or until the given <code>timeout</code> occurs.
* <p>
* This method must only be called once for a given instance of ThreadBurstExecutor.
*
* @return true if all tasks have run, and false if the timeout elapsed before termination.
* @throws IllegalStateException if {@link #executeAllAndWait(long, TimeUnit)} was called already.
* @throws InterruptedException If waiting for the tasks to finish was interrupted.
*/
public boolean executeAllAndWait(long timeout, TimeUnit unit) throws InterruptedException {
if (executed.getAndSet(true)) {
throw new IllegalStateException();
}
// this will unleash all threads that block on threadGate
threadGate.countDown();
delegate.shutdown();
return delegate.awaitTermination(timeout, unit);
}
/**
* Attempts to stop all running threads, waiting no longer than the given <code>timeout</code>.
* This method can be called either before {@link #executeAllAndWait} to stop the threads
* that block on the thread gate to open, or afterwards to stop running tasks.
* <p>
* There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. This
* implementation cancels tasks via {@link Thread#interrupt}, so any task that fails to respond to interrupts may
* never terminate.
*
* @param timeout
* @param unit time unit
* @return true if this executor terminated and false if the timeout elapsed before termination
* @throws InterruptedException
*/
public boolean terminateAllAndWait(long timeout, TimeUnit unit) throws InterruptedException {
delegate.shutdownNow();
return delegate.awaitTermination(timeout, unit);
}
/**
* Wrapper used to block a thread created by {@link ThreadBurstExecutorService#delegate}, until the thread gate opens.
*/
private static class InterceptingCallable<V> implements Callable<V> {
private final Callable<V> delegateCallable;
private final CountDownLatch threadGate;
private final CountDownLatch confirmWaiting;
private final long awaitExecutionTimeout;
private final TimeUnit unit;
InterceptingCallable(Callable<V> delegate, CountDownLatch threadGate, CountDownLatch confirmWaiting, long awaitExecutionTimeout, TimeUnit unit) {
this.delegateCallable = delegate;
this.threadGate = threadGate;
this.confirmWaiting = confirmWaiting;
this.awaitExecutionTimeout = awaitExecutionTimeout;
this.unit = unit;
}
/**
* The executor has started this thread, but here we wait for the thread gate to open,
* and only then call the delegate runnable submitted by the user.
*/
public V call() throws Exception {
confirmWaiting.countDown();
if(threadGate.await(awaitExecutionTimeout, unit) == false) {
String msg = "Timeout occured while waiting for thread gate to be opened.";
System.out.println(msg);
throw new TimeoutException(msg);
}
else {
return delegateCallable.call();
}
}
}
}