com.signalfx.shaded.jetty.util.thread.QueuedThreadPool Maven / Gradle / Ivy
Show all versions of signalfx-codahale Show documentation
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package com.signalfx.shaded.jetty.util.thread;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.signalfx.shaded.jetty.util.AtomicBiInteger;
import com.signalfx.shaded.jetty.util.BlockingArrayQueue;
import com.signalfx.shaded.jetty.util.StringUtil;
import com.signalfx.shaded.jetty.util.annotation.ManagedAttribute;
import com.signalfx.shaded.jetty.util.annotation.ManagedObject;
import com.signalfx.shaded.jetty.util.annotation.ManagedOperation;
import com.signalfx.shaded.jetty.util.annotation.Name;
import com.signalfx.shaded.jetty.util.component.ContainerLifeCycle;
import com.signalfx.shaded.jetty.util.component.Dumpable;
import com.signalfx.shaded.jetty.util.component.DumpableCollection;
import com.signalfx.shaded.jetty.util.component.LifeCycle;
import com.signalfx.shaded.jetty.util.log.Log;
import com.signalfx.shaded.jetty.util.log.Logger;
import com.signalfx.shaded.jetty.util.thread.ThreadPool.SizedThreadPool;
/**
* A thread pool with a queue of jobs to execute.
* Jetty components that need threads (such as network acceptors and selector) may lease threads
* from this thread pool using a {@link ThreadPoolBudget}; these threads are "active" from the point
* of view of the thread pool, but not available to run transient jobs such as processing
* an HTTP request or a WebSocket frame.
* QueuedThreadPool has a {@link ReservedThreadExecutor} which leases threads from this pool,
* but makes them available as if they are "idle" threads.
* QueuedThreadPool therefore has the following fundamental values:
*
* - {@link #getThreads() threads}: the current number of threads. These threads may execute
* a job (either internal or transient), or may be ready to run (either idle or reserved).
* This number may grow or shrink as the thread pool grows or shrinks.
* - {@link #getReadyThreads() readyThreads}: the current number of threads that are ready to
* run transient jobs.
* This number may grow or shrink as the thread pool grows or shrinks.
* - {@link #getLeasedThreads() leasedThreads}: the number of threads that run internal jobs.
* This number is typically constant after this thread pool is {@link #start() started}.
*
* Given the definitions above, the most interesting definitions are:
*
* - {@link #getThreads() threads} = {@link #getReadyThreads() readyThreads} + {@link #getLeasedThreads() leasedThreads} + {@link #getUtilizedThreads() utilizedThreads}
* - readyThreads = {@link #getIdleThreads() idleThreads} + {@link #getAvailableReservedThreads() availableReservedThreads}
* - {@link #getMaxAvailableThreads() maxAvailableThreads} = {@link #getMaxThreads() maxThreads} - leasedThreads
* - {@link #getUtilizationRate() utilizationRate} = utilizedThreads / maxAvailableThreads
*
* Other definitions, typically less interesting because they take into account threads that
* execute internal jobs, or because they don't take into account available reserved threads
* (that are essentially ready to execute transient jobs), are:
*
* - {@link #getBusyThreads() busyThreads} = utilizedThreads + leasedThreads
* - {@link #getIdleThreads()} idleThreads} = readyThreads - availableReservedThreads
*
*/
@ManagedObject("A thread pool")
public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactory, SizedThreadPool, Dumpable, TryExecutor
{
private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
private static final Runnable NOOP = () ->
{
};
/**
* Encodes thread counts:
*
* - Hi
- Total thread count or Integer.MIN_VALUE if the pool is stopping
* - Lo
- Net idle threads == idle threads - job queue size. Essentially if positive,
* this represents the effective number of idle threads, and if negative it represents the
* demand for more threads
*
*/
private final AtomicBiInteger _counts = new AtomicBiInteger(Integer.MIN_VALUE, 0);
private final AtomicLong _lastShrink = new AtomicLong();
private final Set _threads = ConcurrentHashMap.newKeySet();
private final Object _joinLock = new Object();
private final BlockingQueue _jobs;
private final ThreadGroup _threadGroup;
private final ThreadFactory _threadFactory;
private String _name = "qtp" + hashCode();
private int _idleTimeout;
private int _maxThreads;
private int _minThreads;
private int _reservedThreads = -1;
private TryExecutor _tryExecutor = TryExecutor.NO_TRY;
private int _priority = Thread.NORM_PRIORITY;
private boolean _daemon = false;
private boolean _detailedDump = false;
private int _lowThreadsThreshold = 1;
private ThreadPoolBudget _budget;
public QueuedThreadPool()
{
this(200);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads)
{
this(maxThreads, Math.min(8, maxThreads));
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads)
{
this(maxThreads, minThreads, 60000);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("queue") BlockingQueue queue)
{
this(maxThreads, minThreads, 60000, -1, queue, null);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout)
{
this(maxThreads, minThreads, idleTimeout, null);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue queue)
{
this(maxThreads, minThreads, idleTimeout, queue, null);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue queue, @Name("threadGroup") ThreadGroup threadGroup)
{
this(maxThreads, minThreads, idleTimeout, -1, queue, threadGroup);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads,
@Name("idleTimeout") int idleTimeout, @Name("reservedThreads") int reservedThreads,
@Name("queue") BlockingQueue queue, @Name("threadGroup") ThreadGroup threadGroup)
{
this(maxThreads, minThreads, idleTimeout, reservedThreads, queue, threadGroup, null);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads,
@Name("idleTimeout") int idleTimeout, @Name("reservedThreads") int reservedThreads,
@Name("queue") BlockingQueue queue, @Name("threadGroup") ThreadGroup threadGroup,
@Name("threadFactory") ThreadFactory threadFactory)
{
if (maxThreads < minThreads)
throw new IllegalArgumentException("max threads (" + maxThreads + ") less than min threads (" + minThreads + ")");
setMinThreads(minThreads);
setMaxThreads(maxThreads);
setIdleTimeout(idleTimeout);
setStopTimeout(5000);
setReservedThreads(reservedThreads);
if (queue == null)
{
int capacity = Math.max(_minThreads, 8) * 1024;
queue = new BlockingArrayQueue<>(capacity, capacity);
}
_jobs = queue;
_threadGroup = threadGroup;
setThreadPoolBudget(new ThreadPoolBudget(this));
_threadFactory = threadFactory == null ? this : threadFactory;
}
@Override
public ThreadPoolBudget getThreadPoolBudget()
{
return _budget;
}
public void setThreadPoolBudget(ThreadPoolBudget budget)
{
if (budget != null && budget.getSizedThreadPool() != this)
throw new IllegalArgumentException();
updateBean(_budget, budget);
_budget = budget;
}
@Override
protected void doStart() throws Exception
{
if (_reservedThreads == 0)
{
_tryExecutor = NO_TRY;
}
else
{
ReservedThreadExecutor reserved = new ReservedThreadExecutor(this, _reservedThreads);
reserved.setIdleTimeout(_idleTimeout, TimeUnit.MILLISECONDS);
_tryExecutor = reserved;
}
addBean(_tryExecutor);
_lastShrink.set(System.nanoTime());
super.doStart();
// The threads count set to MIN_VALUE is used to signal to Runners that the pool is stopped.
_counts.set(0, 0); // threads, idle
ensureThreads();
}
@Override
protected void doStop() throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("Stopping {}", this);
super.doStop();
removeBean(_tryExecutor);
_tryExecutor = TryExecutor.NO_TRY;
// Signal the Runner threads that we are stopping
int threads = _counts.getAndSetHi(Integer.MIN_VALUE);
// If stop timeout try to gracefully stop
long timeout = getStopTimeout();
BlockingQueue jobs = getQueue();
// Fill the job queue with noop jobs to wakeup idle threads.
for (int i = 0; i < threads; ++i)
jobs.offer(NOOP);
// If we have a timeout, try to let jobs complete naturally for half our stop time
if (timeout > 0)
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
Thread.yield();
// If we still have threads running, get a bit more aggressive
// interrupt remaining threads
for (Thread thread : _threads)
{
if (LOG.isDebugEnabled())
LOG.debug("Interrupting {}", thread);
thread.interrupt();
}
// If we have a timeout, wait again for the other half of our stop time
if (timeout > 0)
{
joinThreads(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2);
Thread.yield();
if (LOG.isDebugEnabled())
{
for (Thread unstopped : _threads)
{
StringBuilder dmp = new StringBuilder();
for (StackTraceElement element : unstopped.getStackTrace())
{
dmp.append(System.lineSeparator()).append("\tat ").append(element);
}
LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
}
}
else
{
for (Thread unstopped : _threads)
{
LOG.warn("{} Couldn't stop {}", this, unstopped);
}
}
}
// Close any un-executed jobs
while (!_jobs.isEmpty())
{
Runnable job = _jobs.poll();
if (job instanceof Closeable)
{
try
{
((Closeable)job).close();
}
catch (Throwable t)
{
LOG.warn(t);
}
}
else if (job != NOOP)
{
LOG.warn("Stopped without executing or closing {}", job);
}
}
if (_budget != null)
_budget.reset();
synchronized (_joinLock)
{
_joinLock.notifyAll();
}
}
private void joinThreads(long stopByNanos) throws InterruptedException
{
for (Thread thread : _threads)
{
long canWait = TimeUnit.NANOSECONDS.toMillis(stopByNanos - System.nanoTime());
if (LOG.isDebugEnabled())
LOG.debug("Waiting for {} for {}", thread, canWait);
if (canWait > 0)
thread.join(canWait);
}
}
/**
* @return the maximum thread idle time in ms
*/
@ManagedAttribute("maximum time a thread may be idle in ms")
public int getIdleTimeout()
{
return _idleTimeout;
}
/**
* Set the maximum thread idle time in ms.
* Threads that are idle for longer than this period may be stopped.
*
* @param idleTimeout the maximum thread idle time in ms
*/
public void setIdleTimeout(int idleTimeout)
{
_idleTimeout = idleTimeout;
ReservedThreadExecutor reserved = getBean(ReservedThreadExecutor.class);
if (reserved != null)
reserved.setIdleTimeout(idleTimeout, TimeUnit.MILLISECONDS);
}
/**
* @return the maximum number of threads
*/
@Override
@ManagedAttribute("maximum number of threads in the pool")
public int getMaxThreads()
{
return _maxThreads;
}
/**
* @param maxThreads the maximum number of threads
*/
@Override
public void setMaxThreads(int maxThreads)
{
if (_budget != null)
_budget.check(maxThreads);
_maxThreads = maxThreads;
if (_minThreads > _maxThreads)
_minThreads = _maxThreads;
}
/**
* @return the minimum number of threads
*/
@Override
@ManagedAttribute("minimum number of threads in the pool")
public int getMinThreads()
{
return _minThreads;
}
/**
* @param minThreads minimum number of threads
*/
@Override
public void setMinThreads(int minThreads)
{
_minThreads = minThreads;
if (_minThreads > _maxThreads)
_maxThreads = _minThreads;
if (isStarted())
ensureThreads();
}
/**
* @return number of reserved threads or -1 for heuristically determined
*/
@ManagedAttribute("number of configured reserved threads or -1 for heuristic")
public int getReservedThreads()
{
return _reservedThreads;
}
/**
* @param reservedThreads number of reserved threads or -1 for heuristically determined
*/
public void setReservedThreads(int reservedThreads)
{
if (isRunning())
throw new IllegalStateException(getState());
_reservedThreads = reservedThreads;
}
/**
* @return the name of the this thread pool
*/
@ManagedAttribute("name of the thread pool")
public String getName()
{
return _name;
}
/**
* Sets the name of this thread pool, used as a prefix for the thread names.
*
* @param name the name of the this thread pool
*/
public void setName(String name)
{
if (isRunning())
throw new IllegalStateException(getState());
_name = name;
}
/**
* @return the priority of the pool threads
*/
@ManagedAttribute("priority of threads in the pool")
public int getThreadsPriority()
{
return _priority;
}
/**
* @param priority the priority of the pool threads
*/
public void setThreadsPriority(int priority)
{
_priority = priority;
}
/**
* @return whether to use daemon threads
* @see Thread#isDaemon()
*/
@ManagedAttribute("thread pool uses daemon threads")
public boolean isDaemon()
{
return _daemon;
}
/**
* @param daemon whether to use daemon threads
* @see Thread#setDaemon(boolean)
*/
public void setDaemon(boolean daemon)
{
_daemon = daemon;
}
@ManagedAttribute("reports additional details in the dump")
public boolean isDetailedDump()
{
return _detailedDump;
}
public void setDetailedDump(boolean detailedDump)
{
_detailedDump = detailedDump;
}
@ManagedAttribute("threshold at which the pool is low on threads")
public int getLowThreadsThreshold()
{
return _lowThreadsThreshold;
}
public void setLowThreadsThreshold(int lowThreadsThreshold)
{
_lowThreadsThreshold = lowThreadsThreshold;
}
/**
* @return the number of jobs in the queue waiting for a thread
*/
@ManagedAttribute("size of the job queue")
public int getQueueSize()
{
// The idle counter encodes demand, which is the effective queue size
int idle = _counts.getLo();
return Math.max(0, -idle);
}
/**
* @return the maximum number (capacity) of reserved threads
* @see ReservedThreadExecutor#getCapacity()
*/
@ManagedAttribute("maximum number (capacity) of reserved threads")
public int getMaxReservedThreads()
{
TryExecutor tryExecutor = _tryExecutor;
if (tryExecutor instanceof ReservedThreadExecutor)
{
ReservedThreadExecutor reservedThreadExecutor = (ReservedThreadExecutor)tryExecutor;
return reservedThreadExecutor.getCapacity();
}
return 0;
}
/**
* @return the number of available reserved threads
* @see ReservedThreadExecutor#getAvailable()
*/
@ManagedAttribute("number of available reserved threads")
public int getAvailableReservedThreads()
{
TryExecutor tryExecutor = _tryExecutor;
if (tryExecutor instanceof ReservedThreadExecutor)
{
ReservedThreadExecutor reservedThreadExecutor = (ReservedThreadExecutor)tryExecutor;
return reservedThreadExecutor.getAvailable();
}
return 0;
}
/**
* The fundamental value that represents the number of threads currently known by this thread pool.
* This value includes threads that have been leased to internal components, idle threads, reserved threads
* and threads that are executing transient jobs.
*
* @return the number of threads currently known to the pool
* @see #getReadyThreads()
* @see #getLeasedThreads()
*/
@Override
@ManagedAttribute("number of threads in the pool")
public int getThreads()
{
int threads = _counts.getHi();
return Math.max(0, threads);
}
/**
* The fundamental value that represents the number of threads ready to execute transient jobs.
*
* @return the number of threads ready to execute transient jobs
* @see #getThreads()
* @see #getLeasedThreads()
* @see #getUtilizedThreads()
*/
@ManagedAttribute("number of threads ready to execute transient jobs")
public int getReadyThreads()
{
return getIdleThreads() + getAvailableReservedThreads();
}
/**
* The fundamental value that represents the number of threads that are leased
* to internal components, and therefore cannot be used to execute transient jobs.
*
* @return the number of threads currently used by internal components
* @see #getThreads()
* @see #getReadyThreads()
*/
@ManagedAttribute("number of threads used by internal components")
public int getLeasedThreads()
{
return getMaxLeasedThreads() - getMaxReservedThreads();
}
/**
* The maximum number of threads that are leased to internal components,
* as some component may allocate its threads lazily.
*
* @return the maximum number of threads leased by internal components
* @see #getLeasedThreads()
*/
@ManagedAttribute("maximum number of threads leased to internal components")
public int getMaxLeasedThreads()
{
ThreadPoolBudget budget = _budget;
return budget == null ? 0 : budget.getLeasedThreads();
}
/**
* The number of idle threads, but without including reserved threads.
* Prefer {@link #getReadyThreads()} for a better representation of
* "threads ready to execute transient jobs".
*
* @return the number of idle threads but not reserved
* @see #getReadyThreads()
*/
@Override
@ManagedAttribute("number of idle threads but not reserved")
public int getIdleThreads()
{
int idle = _counts.getLo();
return Math.max(0, idle);
}
/**
* The number of threads executing internal and transient jobs.
* Prefer {@link #getUtilizedThreads()} for a better representation of
* "threads executing transient jobs".
*
* @return the number of threads executing internal and transient jobs
* @see #getUtilizedThreads()
*/
@ManagedAttribute("number of threads executing internal and transient jobs")
public int getBusyThreads()
{
return getThreads() - getReadyThreads();
}
/**
* The number of threads executing transient jobs.
*
* @return the number of threads executing transient jobs
* @see #getReadyThreads()
*/
@ManagedAttribute("number of threads executing transient jobs")
public int getUtilizedThreads()
{
return getThreads() - getLeasedThreads() - getReadyThreads();
}
/**
* The maximum number of threads available to run transient jobs.
*
* @return the maximum number of threads available to run transient jobs
*/
@ManagedAttribute("maximum number of threads available to run transient jobs")
public int getMaxAvailableThreads()
{
return getMaxThreads() - getLeasedThreads();
}
/**
* The rate between the number of {@link #getUtilizedThreads() utilized threads}
* and the maximum number of {@link #getMaxAvailableThreads() utilizable threads}.
* A value of {@code 0.0D} means that the thread pool is not utilized, while a
* value of {@code 1.0D} means that the thread pool is fully utilized to execute
* transient jobs.
*
* @return the utilization rate of threads executing transient jobs
*/
@ManagedAttribute("utilization rate of threads executing transient jobs")
public double getUtilizationRate()
{
return (double)getUtilizedThreads() / getMaxAvailableThreads();
}
/**
* Returns whether this thread pool is low on threads.
* The current formula is:
*
* maxThreads - threads + readyThreads - queueSize <= lowThreadsThreshold
*
*
* @return whether the pool is low on threads
* @see #getLowThreadsThreshold()
*/
@Override
@ManagedAttribute(value = "thread pool is low on threads", readonly = true)
public boolean isLowOnThreads()
{
return getMaxThreads() - getThreads() + getReadyThreads() - getQueueSize() <= getLowThreadsThreshold();
}
@Override
public void execute(Runnable job)
{
// Determine if we need to start a thread, use and idle thread or just queue this job
int startThread;
while (true)
{
// Get the atomic counts
long counts = _counts.get();
// Get the number of threads started (might not yet be running)
int threads = AtomicBiInteger.getHi(counts);
if (threads == Integer.MIN_VALUE)
throw new RejectedExecutionException(job.toString());
// Get the number of truly idle threads. This count is reduced by the
// job queue size so that any threads that are idle but are about to take
// a job from the queue are not counted.
int idle = AtomicBiInteger.getLo(counts);
// Start a thread if we have insufficient idle threads to meet demand
// and we are not at max threads.
startThread = (idle <= 0 && threads < _maxThreads) ? 1 : 0;
// The job will be run by an idle thread when available
if (!_counts.compareAndSet(counts, threads + startThread, idle + startThread - 1))
continue;
break;
}
if (!_jobs.offer(job))
{
// reverse our changes to _counts.
if (addCounts(-startThread, 1 - startThread))
LOG.warn("{} rejected {}", this, job);
throw new RejectedExecutionException(job.toString());
}
if (LOG.isDebugEnabled())
LOG.debug("queue {} startThread={}", job, startThread);
// Start a thread if one was needed
while (startThread-- > 0)
startThread();
}
@Override
public boolean tryExecute(Runnable task)
{
TryExecutor tryExecutor = _tryExecutor;
return tryExecutor != null && tryExecutor.tryExecute(task);
}
/**
* Blocks until the thread pool is {@link LifeCycle#stop stopped}.
*/
@Override
public void join() throws InterruptedException
{
synchronized (_joinLock)
{
while (isRunning())
{
_joinLock.wait();
}
}
while (isStopping())
{
Thread.sleep(1);
}
}
private void ensureThreads()
{
while (true)
{
long counts = _counts.get();
int threads = AtomicBiInteger.getHi(counts);
if (threads == Integer.MIN_VALUE)
break;
// If we have less than min threads
// OR insufficient idle threads to meet demand
int idle = AtomicBiInteger.getLo(counts);
if (threads < _minThreads || (idle < 0 && threads < _maxThreads))
{
// Then try to start a thread.
if (_counts.compareAndSet(counts, threads + 1, idle + 1))
startThread();
// Otherwise continue to check state again.
continue;
}
break;
}
}
protected void startThread()
{
boolean started = false;
try
{
Thread thread = _threadFactory.newThread(_runnable);
if (LOG.isDebugEnabled())
LOG.debug("Starting {}", thread);
_threads.add(thread);
_lastShrink.set(System.nanoTime());
thread.start();
started = true;
}
finally
{
if (!started)
addCounts(-1, -1); // threads, idle
}
}
private boolean addCounts(int deltaThreads, int deltaIdle)
{
while (true)
{
long encoded = _counts.get();
int threads = AtomicBiInteger.getHi(encoded);
int idle = AtomicBiInteger.getLo(encoded);
if (threads == Integer.MIN_VALUE) // This is a marker that the pool is stopped.
return false;
long update = AtomicBiInteger.encode(threads + deltaThreads, idle + deltaIdle);
if (_counts.compareAndSet(encoded, update))
return true;
}
}
@Override
public Thread newThread(Runnable runnable)
{
return PrivilegedThreadFactory.newThread(() ->
{
Thread thread = new Thread(_threadGroup, runnable);
thread.setDaemon(isDaemon());
thread.setPriority(getThreadsPriority());
thread.setName(_name + "-" + thread.getId());
thread.setContextClassLoader(getClass().getClassLoader());
return thread;
});
}
protected void removeThread(Thread thread)
{
_threads.remove(thread);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
List