/* * Copyright (c) 1998-2010 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.env.thread; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.config.ConfigException; import com.caucho.env.shutdown.ExitCode; import com.caucho.env.shutdown.ShutdownService; import com.caucho.lifecycle.Lifecycle; import com.caucho.util.Alarm; import com.caucho.util.L10N; import com.caucho.util.ThreadDump; /** * A generic pool of threads available for Alarms and Work tasks. */ public final class ThreadPool { private static final L10N L = new L10N(ThreadPool.class); private static final Logger log = Logger.getLogger(ThreadPool.class.getName()); private static final long MAX_EXPIRE = Long.MAX_VALUE / 2; private static final int DEFAULT_THREAD_MAX = 8192; private static final int DEFAULT_IDLE_MIN = 4; private static final int DEFAULT_PRIORITY_IDLE_MIN = 4; private static final long DEFAULT_IDLE_TIMEOUT = 120000L; private static final int DEFAULT_EXECUTOR_TASK_MAX = 16; private static final long PRIORITY_TIMEOUT = 10L; private static final NullRunnable NULL_RUNNABLE = new NullRunnable(); private static final AtomicReference<ThreadPool> _globalThreadPool = new AtomicReference<ThreadPool>(); private final AtomicInteger _gId = new AtomicInteger(); // configuration items private int _threadMax = DEFAULT_THREAD_MAX; private int _idleMin = DEFAULT_IDLE_MIN; private long _idleTimeout = DEFAULT_IDLE_TIMEOUT; // the minimum number of free threads reserved for priority tasks private int _priorityIdleMin = DEFAULT_PRIORITY_IDLE_MIN; private int _executorTaskMax = DEFAULT_EXECUTOR_TASK_MAX; private final ThreadLauncher _launcher; private final Lifecycle _lifecycle = new Lifecycle(); // // lifecycle count to drain on environment change // private final AtomicLong _resetCount = new AtomicLong(); // // thread max and thread lifetime counts // private final AtomicInteger _activeCount = new AtomicInteger(); // number of threads which are in the process of starting private final AtomicInteger _startingCount = new AtomicInteger(); private final AtomicLong _createCount = new AtomicLong(); private final AtomicLong _overflowCount = new AtomicLong(); // next time when an idle thread can expire private final AtomicLong _threadIdleExpireTime = new AtomicLong(); // // the idle stack // private final AtomicReference<ThreadNode> _idleHead = new AtomicReference<ThreadNode>(); private final AtomicInteger _idleCount = new AtomicInteger(); private final AtomicReference<ThreadNode> _priorityIdleHead = new AtomicReference<ThreadNode>(); private final AtomicInteger _priorityIdleCount = new AtomicInteger(); // number of threads in the wait queue // // task/priority overflow queues // private final ConcurrentLinkedQueue<ThreadTask> _taskQueue = new ConcurrentLinkedQueue<ThreadTask>(); private final ConcurrentLinkedQueue<ThreadTask> _priorityQueue = new ConcurrentLinkedQueue<ThreadTask>(); private int _waitCount; // // executor // private final Object _executorLock = new Object(); // number of executor tasks running private int _executorTaskCount; // queue for waiting executor tasks private ExecutorQueueItem _executorQueueHead; private ExecutorQueueItem _executorQueueTail; public ThreadPool() { _launcher = new ThreadLauncher(); // initialize default values init(); } public static ThreadPool getCurrent() { return getThreadPool(); } public static ThreadPool getThreadPool() { ThreadPool pool = _globalThreadPool.get(); if (pool == null) { pool = new ThreadPool(); if (_globalThreadPool.compareAndSet(null, pool)) { pool.start(); } else { pool = _globalThreadPool.get(); } } return pool; } // // Configuration properties // /** * Sets the maximum number of threads. */ public void setThreadMax(int max) { if (max < _idleMin) throw new ConfigException(L.l("<thread-idle-min> ({0}) must be less than <thread-max> ({1})", _idleMin, max)); if (max < 1) throw new ConfigException(L.l("<thread-max> ({0}) must be greater than zero", max)); _threadMax = max; init(); } /** * Gets the maximum number of threads. */ public int getThreadMax() { return _threadMax; } /** * Sets the minimum number of idle threads. */ public void setIdleMin(int min) { if (_threadMax < min) throw new ConfigException(L.l("<thread-idle-min> ({0}) must be less than <thread-max> ({1})", min, _threadMax)); if (min <= 0) throw new ConfigException(L.l("<thread-idle-min> ({0}) must be greater than 0.", min)); _idleMin = min; init(); } /** * Gets the minimum number of idle threads. */ public int getIdleMin() { return _idleMin; } /** * Sets the idle timeout */ public void setIdleTimeout(long timeout) { _idleTimeout = timeout; } /** * Returns the idle timeout. */ public long getIdleTimeout() { return _idleTimeout; } /** * Sets the minimum number of free threads reserved for priority tasks. */ public void setPriorityIdleMin(int priority) { _priorityIdleMin = priority; init(); } public int getPriorityIdleMin() { return _priorityIdleMin; } /** * Sets the maximum number of executor threads. */ public void setExecutorTaskMax(int max) { if (_threadMax < max) throw new ConfigException(L.l("<thread-executor-max> ({0}) must be less than <thread-max> ({1})", max, _threadMax)); if (max == 0) throw new ConfigException(L.l("<thread-executor-max> must not be zero.")); _executorTaskMax = max; } /** * Gets the maximum number of executor threads. */ public int getExecutorTaskMax() { return _executorTaskMax; } // // statistics // /** * Returns the total thread count. */ public int getThreadCount() { return _activeCount.get(); } /** * Returns the active thread count. */ public int getThreadActiveCount() { return _activeCount.get() - _idleCount.get(); } /** * Returns the starting thread count. */ public int getThreadStartingCount() { return _startingCount.get(); } /** * Returns the idle thread count. */ public int getThreadIdleCount() { return _idleCount.get(); } /** * Returns the priority idle thread count. */ public int getPriorityIdleCount() { return _priorityIdleCount.get(); } /** * Returns the waiting thread count. */ public int getThreadWaitCount() { return _waitCount; } /** * Returns the free thread count. */ public int getFreeThreadCount() { return _threadMax - _activeCount.get() - _startingCount.get(); } /** * Returns the total created thread count. */ public long getThreadCreateCountTotal() { return _createCount.get(); } /** * Returns the total created overflow thread count. */ public long getThreadOverflowCountTotal() { return _overflowCount.get(); } /** * Returns priority queue size */ public int getThreadPriorityQueueSize() { return _priorityQueue.size(); } /** * Returns task queue size */ public int getThreadTaskQueueSize() { return _taskQueue.size(); } // // initialization // private void init() { long now = Alarm.getCurrentTime(); _threadIdleExpireTime.set(now + _idleTimeout); } public void start() { if (_lifecycle.toActive()) { _launcher.start(); } } // // Scheduling methods // /** * Schedules a new task. */ public boolean schedule(Runnable task) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean isPriority = false; boolean isQueue = true; return scheduleImpl(task, loader, MAX_EXPIRE, isPriority, isQueue); } /** * Adds a new task. */ public boolean schedule(Runnable task, long timeout) { long expire; if (timeout < 0 || timeout > MAX_EXPIRE) expire = MAX_EXPIRE; else expire = Alarm.getCurrentTimeActual() + timeout; ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean isPriority = false; boolean isQueue = true; return scheduleImpl(task, loader, expire, isPriority, isQueue); } /** * Adds a new task. */ public void schedulePriority(Runnable task) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); long expire = Alarm.getCurrentTimeActual() + PRIORITY_TIMEOUT; boolean isPriority = true; boolean isQueue = true; if (! scheduleImpl(task, loader, expire, isPriority, isQueue)) { log.warning(this + " unable to schedule priority thread " + task + " pri=" + _priorityIdleMin + " active=" + _activeCount.get() + " idle=" + (_idleCount.get() + _startingCount.get()) + " max=" + _threadMax); OverflowThread item = new OverflowThread(task); item.start(); } } /** * Schedules an executor task. */ public boolean scheduleExecutorTask(Runnable task) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); synchronized (_executorLock) { _executorTaskCount++; if (_executorTaskCount <= _executorTaskMax || _executorTaskMax < 0) { boolean isPriority = false; boolean isQueue = true; return scheduleImpl(task, loader, MAX_EXPIRE, isPriority, isQueue); } else { ExecutorQueueItem item = new ExecutorQueueItem(task, loader); if (_executorQueueTail != null) _executorQueueTail._next = item; else _executorQueueHead = item; _executorQueueTail = item; return false; } } } /** * Called when an executor task completes */ public void completeExecutorTask() { ExecutorQueueItem item = null; synchronized (_executorLock) { _executorTaskCount--; assert(_executorTaskCount >= 0); if (_executorQueueHead != null) { item = _executorQueueHead; _executorQueueHead = item._next; if (_executorQueueHead == null) _executorQueueTail = null; } } if (item != null) { Runnable task = item.getRunnable(); ClassLoader loader = item.getLoader(); boolean isPriority = false; boolean isQueue = true; scheduleImpl(task, loader, MAX_EXPIRE, isPriority, isQueue); } } /** * Adds a new task. */ public boolean start(Runnable task) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean isPriority = false; boolean isQueue = false; return scheduleImpl(task, loader, MAX_EXPIRE, isPriority, isQueue); } /** * Adds a new task. */ public boolean start(Runnable task, long timeout) { long expire; if (timeout < 0 || timeout > MAX_EXPIRE) expire = MAX_EXPIRE; else expire = Alarm.getCurrentTimeActual() + timeout; ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean isPriority = false; boolean isQueue = false; return scheduleImpl(task, loader, expire, isPriority, isQueue); } /** * Adds a new task. */ public void startPriority(Runnable task) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); long expire = Alarm.getCurrentTimeActual() + PRIORITY_TIMEOUT; boolean isPriority = true; boolean isQueue = true; if (! scheduleImpl(task, loader, expire, isPriority, isQueue)) { log.warning(this + " unable to start priority thread " + task + " pri=" + _priorityIdleMin + " active=" + _activeCount.get() + " idle=" + _idleCount.get() + " starting=" + _startingCount.get() + " max=" + _threadMax); ThreadDump.dumpThreads(); OverflowThread item = new OverflowThread(task); item.start(); } } /** * Adds a new task. */ public boolean startPriority(Runnable task, long timeout) { long expire; if (timeout < 0 || timeout > MAX_EXPIRE) expire = MAX_EXPIRE; else expire = Alarm.getCurrentTimeActual() + timeout; ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean isPriority = true; boolean isQueue = false; return scheduleImpl(task, loader, expire, isPriority, isQueue); } /** * main scheduling implementation class. */ private boolean scheduleImpl(Runnable task, ClassLoader loader, long expireTime, boolean isPriority, boolean isQueueIfFull) { if (scheduleIdle(task, loader, isPriority)) { return true; } _launcher.wake(); if (! isQueueIfFull && isThreadMax()) { return false; } Thread requestThread = null; if (! isQueueIfFull) { requestThread = Thread.currentThread(); } ThreadTask taskItem = new ThreadTask(task, loader, requestThread); if (isPriority) { _priorityQueue.offer(taskItem); } else { _taskQueue.offer(taskItem); } // wake any threads idled after our offer ResinThread thread = popIdleThread(); if (thread != null) { thread.scheduleTask(NULL_RUNNABLE, null); } else if (isPriority && (thread = popPriorityThread()) != null) { thread.scheduleTask(NULL_RUNNABLE, null); } if (! isQueueIfFull) { taskItem.park(expireTime); } return true; } private boolean isThreadMax() { return _threadMax <= _activeCount.get() && _startingCount.get() == 0; } private boolean scheduleIdle(Runnable task, ClassLoader loader, boolean isPriority) { if (! _priorityQueue.isEmpty()) return false; if (_taskQueue.isEmpty()) { ResinThread thread = popIdleThread(); if (thread != null) { thread.scheduleTask(task, loader); return true; } } if (! isPriority) { return false; } ResinThread priorityThread = popPriorityThread(); if (priorityThread != null) { priorityThread.scheduleTask(task, loader); return true; } else { return false; } } /** * Returns the next available task, returning null if the thread * should exit. */ ThreadTask nextTask(ResinThread thread) { ThreadTask item = _priorityQueue.poll(); if (item != null) return item; int idleCount = _idleCount.get(); // if we have spare threads, process any task queue item if (_priorityIdleMin <= idleCount) { item = _taskQueue.poll(); if (item != null) return item; } return null; } // // idle stack management // /** * Returns true if the idle thread should expire. */ boolean isIdleExpire() { if (! _lifecycle.isActive()) return true; long now = Alarm.getCurrentTimeActual(); long idleExpire = _threadIdleExpireTime.get(); // if idle queue is full and the expire is set, return and exit if (_idleMin < _idleCount.get() && idleExpire < now) { long nextIdleExpire = now + _idleTimeout; return _threadIdleExpireTime.compareAndSet(idleExpire, nextIdleExpire); } return false; } void pushIdleThread(ResinThread thread) { int priorityIdle = _priorityIdleCount.get(); if (priorityIdle < _priorityIdleMin && _priorityIdleCount.compareAndSet(priorityIdle, priorityIdle + 1)) { pushIdleThread(_priorityIdleHead, thread); } else { _idleCount.incrementAndGet(); pushIdleThread(_idleHead, thread); } } private void pushIdleThread(AtomicReference<ThreadNode> idleHeadRef, ResinThread thread) { ThreadNode head = new ThreadNode(thread); ThreadNode next; do { next = idleHeadRef.get(); head.setNext(next); } while (! idleHeadRef.compareAndSet(next, head)); } private ResinThread popPriorityThread() { ResinThread thread = popIdleThread(_priorityIdleHead); if (thread != null) { _priorityIdleCount.decrementAndGet(); } return thread; } private ResinThread popIdleThread() { ResinThread thread = popIdleThread(_idleHead); if (thread != null) { int idleCount = _idleCount.decrementAndGet(); if (_idleMin <= idleCount) return thread; } long now = Alarm.getCurrentTimeActual(); _threadIdleExpireTime.set(now + _idleTimeout); _launcher.wake(); return thread; } private ResinThread popIdleThread(AtomicReference<ThreadNode> idleHeadRef) { ThreadNode head; ThreadNode next; do { head = idleHeadRef.get(); if (head == null) return null; next = head.getNext(); } while (! idleHeadRef.compareAndSet(head, next)); return head.getThread(); } // // lifecycle methods // /** * Resets the thread pool, letting old threads drain. */ public void reset() { _resetCount.incrementAndGet(); } /** * Resets the thread pool, letting old threads drain. */ public void closeEnvironment(ClassLoader env) { // XXX: incorrect reset(); } /** * interrupts all the idle threads. */ public void interrupt() { ResinThread thread; while ((thread = popIdleThread()) != null) { thread.close(); } } long onThreadStart() { _activeCount.incrementAndGet(); int startCount = _startingCount.decrementAndGet(); if (startCount < 0) { _startingCount.set(0); new IllegalStateException().printStackTrace(); } _createCount.incrementAndGet(); _launcher.wake(); return _resetCount.get(); } void onThreadEnd() { _activeCount.decrementAndGet(); _launcher.wake(); } /** * Checks if the launcher should start another thread. */ private boolean doStart() { int idleCount = _idleCount.get(); int startingCount = _startingCount.get(); int threadCount = _activeCount.get() + startingCount; if (_threadMax < threadCount) { return false; } else if (idleCount + startingCount < _idleMin) { _startingCount.incrementAndGet(); return true; } else { return false; } } public void close() { if (this == _globalThreadPool.get()) throw new IllegalStateException(L.l("Cannot close global thread pool")); _lifecycle.toDestroy(); interrupt(); } public String toString() { return getClass().getSimpleName() + "[]"; } final class OverflowThread extends Thread { private Runnable _task; private ClassLoader _loader; OverflowThread(Runnable task) { super("resin-overflow-" + task.getClass().getSimpleName()); setDaemon(true); _task = task; _loader = Thread.currentThread().getContextClassLoader(); } /** * The main thread execution method. */ public void run() { Thread thread = Thread.currentThread(); thread.setContextClassLoader(_loader); try { _overflowCount.incrementAndGet(); _task.run(); } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } } } final class ThreadLauncher extends Thread { private final AtomicBoolean _isWake = new AtomicBoolean(); private ThreadLauncher() { super("resin-thread-launcher"); setDaemon(true); } void wake() { if (! _isWake.getAndSet(true)) { LockSupport.unpark(this); } } /** * Starts a new connection */ private boolean startConnection(boolean isWait) throws InterruptedException { if (doStart()) { try { long now = Alarm.getCurrentTimeActual(); _threadIdleExpireTime.set(now + _idleTimeout); int id = _gId.incrementAndGet(); ResinThread poolThread = new ResinThread(ThreadPool.this, id); poolThread.start(); return true; } catch (Throwable e) { e.printStackTrace(); String msg = "Resin exiting because of failed thread"; ShutdownService.shutdownActive(ExitCode.THREAD, msg); } } else { Thread.interrupted(); if (isWait && ! _isWake.getAndSet(false)) { LockSupport.park(); } } return false; } public void run() { ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); Thread.currentThread().setContextClassLoader(systemLoader); try { for (int i = 0; i < _idleMin; i++) startConnection(false); } catch (Throwable e) { e.printStackTrace(); } while (_lifecycle.isActive()) { try { startConnection(true); } catch (Throwable e) { e.printStackTrace(); String msg = "ThreadPool start connection failed"; ShutdownService.shutdownActive(ExitCode.THREAD, msg); } } } } static class ExecutorQueueItem { Runnable _runnable; ClassLoader _loader; ExecutorQueueItem _next; ExecutorQueueItem(Runnable runnable, ClassLoader loader) { _runnable = runnable; _loader = loader; } Runnable getRunnable() { return _runnable; } ClassLoader getLoader() { return _loader; } } static class NullRunnable implements Runnable { @Override public void run() { } } static final class ThreadNode { private final ResinThread _thread; private ThreadNode _next; ThreadNode(ResinThread thread) { _thread = thread; } ResinThread getThread() { return _thread; } void setNext(ThreadNode next) { _next = next; } ThreadNode getNext() { return _next; } } }