/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2015 The ZAP Development Team
*
* 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.zaproxy.zap.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* A {@code ThreadPoolExecutor} that implements {@code PausableExecutorService}.
*
* @since 2.4.0
* @see PausableExecutorService
* @see ThreadPoolExecutor
*/
public class PausableThreadPoolExecutor extends ThreadPoolExecutor implements PausableExecutorService {
private final ReentrantLock pauseLock = new ReentrantLock();
private final Condition unpaused = pauseLock.newCondition();
private boolean paused;
private List<ExecutorTerminatedListener> listeners = new ArrayList<>(1);
// NOTE: Constructors JavaDoc was copied from base class but with the name of the class replaced with this one.
/**
* Creates a new {@code PausableThreadPoolExecutor} with the given initial parameters and default thread factory and
* rejected execution handler. It may be more convenient to use one of the {@link Executors} factory methods instead of this
* general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even if they are idle, unless
* {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the pool
* @param keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle
* threads will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are executed. This queue will hold only the
* {@code Runnable} tasks submitted by the {@code execute} method.
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} is null
*/
public PausableThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory());
}
/**
* Creates a new {@code PausableThreadPoolExecutor} with the given initial parameters and default rejected execution
* handler.
*
* @param corePoolSize the number of threads to keep in the pool, even if they are idle, unless
* {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the pool
* @param keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle
* threads will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are executed. This queue will hold only the
* {@code Runnable} tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor creates a new thread
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} or {@code threadFactory} is null
*/
public PausableThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
/**
* Creates a new {@code PausableThreadPoolExecutor} with the given initial parameters and default thread factory.
*
* @param corePoolSize the number of threads to keep in the pool, even if they are idle, unless
* {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the pool
* @param keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle
* threads will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are executed. This queue will hold only the
* {@code Runnable} tasks submitted by the {@code execute} method.
* @param handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} or {@code handler} is null
*/
public PausableThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
/**
* Creates a new {@code PausableThreadPoolExecutor} with the given initial parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even if they are idle, unless
* {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the pool
* @param keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle
* threads will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are executed. This queue will hold only the
* {@code Runnable} tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor creates a new thread
* @param handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} or {@code threadFactory} or {@code handler} is null
*/
public PausableThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (paused) {
unpaused.await();
}
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
@Override
public void pause() {
pauseLock.lock();
try {
paused = true;
} finally {
pauseLock.unlock();
}
}
@Override
public void resume() {
pauseLock.lock();
try {
if (!paused) {
return;
}
paused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
/**
* {@inheritDoc}
* <p>
* Overridden to {@code resume()} before {@code shutdown()}.
*
* @see #resume()
*/
@Override
public void shutdown() {
resume();
super.shutdown();
}
/**
* {@inheritDoc}
* <p>
* Overridden to {@code resume()} before {@code shutdownNow()}.
*
* @see #resume()
*/
@Override
public List<Runnable> shutdownNow() {
resume();
return super.shutdownNow();
}
@Override
protected void terminated() {
super.terminated();
// Notify with a copy to allow listeners to remove themselves from listening when notified.
notifyListeners(new ArrayList<>(listeners));
}
/**
* Notifies the given listeners that the executor has terminated.
*
* @param listeners the listeners that will be notified
*/
private static void notifyListeners(List<ExecutorTerminatedListener> listeners) {
for (ExecutorTerminatedListener listener : listeners) {
listener.terminated();
}
}
@Override
public void addExecutorTerminatedListener(ExecutorTerminatedListener listener) {
listeners.add(listener);
}
@Override
public void removeExecutorTerminatedListener(ExecutorTerminatedListener listener) {
listeners.remove(listener);
}
}