/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.dqp.internal.process;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.resource.spi.work.Work;
import org.teiid.adminapi.impl.WorkerPoolStatisticsMetadata;
import org.teiid.core.util.NamedThreadFactory;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.query.QueryPlugin;
/**
* An Executor that:
* <ol>
* <li>minimizes thread creation</li>
* <li>allows for proper timeout of idle threads</li>
* <li>allows for queuing</li>
* </ol>
* <br/>
* A non-fifo (lifo) {@link SynchronousQueue} based {@link ThreadPoolExecutor} satisfies 1 and 2, but not 3.
* A bounded or unbound queue based {@link ThreadPoolExecutor} allows for 3, but will tend to create
* up to the maximum number of threads and makes no guarantee on thread scheduling.
* <br/>
* So the approach here is to use a virtual thread pool off of a {@link SynchronousQueue}
* backed {@link ThreadPoolExecutor}.
* <br/>
* There is also only a single master scheduling thread with actual executions deferred.
*
* TODO: there is a race condition between retiring threads and adding work, which may create extra threads.
* That is a flaw with attempting to reuse, rather than create threads.
* TODO: bounded queuing - we never bothered bounding in the past with our worker pools, but reasonable
* defaults would be a good idea.
*/
public class ThreadReuseExecutor implements TeiidExecutor {
public interface PrioritizedRunnable extends Runnable {
final static int NO_WAIT_PRIORITY = 0;
int getPriority();
long getCreationTime();
DQPWorkContext getDqpWorkContext();
}
public static class RunnableWrapper implements PrioritizedRunnable, Work {
Runnable r;
DQPWorkContext workContext = DQPWorkContext.getWorkContext();
long creationTime;
int priority;
public RunnableWrapper(Runnable r) {
if (r instanceof PrioritizedRunnable) {
PrioritizedRunnable pr = (PrioritizedRunnable)r;
creationTime = pr.getCreationTime();
priority = pr.getPriority();
workContext = pr.getDqpWorkContext();
} else {
creationTime = System.currentTimeMillis();
priority = Integer.MAX_VALUE;
}
this.r = r;
}
@Override
public long getCreationTime() {
return creationTime;
}
@Override
public int getPriority() {
return priority;
}
@Override
public void run() {
if (workContext.getSecurityHelper() != null) {
//if using the inheritable thread local security or if unassocation has been sloppy, there may a security context associated
workContext.getSecurityHelper().clearSecurityContext();
}
workContext.runInContext(r);
}
public DQPWorkContext getDqpWorkContext() {
return workContext;
}
@Override
public void release() {
}
}
private final ThreadPoolExecutor tpe;
private volatile int activeCount;
private volatile int highestActiveCount;
private volatile int highestQueueSize;
private volatile boolean terminated;
private volatile int submittedCount;
private volatile int completedCount;
private Object poolLock = new Object();
private AtomicInteger threadCounter = new AtomicInteger();
private Set<Thread> threads = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<Thread, Boolean>()));
private String poolName;
private int maximumPoolSize;
private Queue<PrioritizedRunnable> queue = new PriorityQueue<PrioritizedRunnable>(11, new Comparator<PrioritizedRunnable>() {
@Override
public int compare(PrioritizedRunnable pr1, PrioritizedRunnable pr2) {
int result = pr1.getPriority() - pr2.getPriority();
if (result == 0) {
return Long.signum(pr1.getCreationTime() - pr2.getCreationTime());
}
return result;
}
});
private long warnWaitTime = 500;
public ThreadReuseExecutor(String name, int maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
this.poolName = name;
tpe = new ThreadPoolExecutor(0,
Integer.MAX_VALUE, 2, TimeUnit.MINUTES,
new SynchronousQueue<Runnable>(), new NamedThreadFactory("Worker")) { //$NON-NLS-1$
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t != null) {
LogManager.logError(LogConstants.CTX_RUNTIME, t, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30021));
}
}
};
}
public void execute(final Runnable command) {
executeDirect(new RunnableWrapper(command));
}
private void executeDirect(final PrioritizedRunnable command) {
synchronized (poolLock) {
checkForTermination();
submittedCount++;
boolean atMaxThreads = activeCount == maximumPoolSize;
if (atMaxThreads) {
queue.add(command);
int queueSize = queue.size();
if (queueSize > highestQueueSize) {
highestQueueSize = queueSize;
}
return;
}
activeCount++;
highestActiveCount = Math.max(activeCount, highestActiveCount);
}
tpe.execute(new Runnable() {
@Override
public void run() {
Thread t = Thread.currentThread();
threads.add(t);
String name = t.getName();
t.setName(name + "_" + poolName + threadCounter.getAndIncrement()); //$NON-NLS-1$
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_RUNTIME, MessageLevel.TRACE)) {
LogManager.logTrace(LogConstants.CTX_RUNTIME, "Beginning work with virtual worker", t.getName()); //$NON-NLS-1$
}
PrioritizedRunnable r = command;
while (r != null) {
boolean success = false;
try {
r.run();
success = true;
} finally {
synchronized (poolLock) {
if (success) {
completedCount++;
//we only poll if successful, to let the exception handling happen immediately otherwise
r = queue.poll();
}
if (!success || r == null) {
threads.remove(t);
activeCount--;
if (activeCount == 0 && terminated) {
poolLock.notifyAll();
}
}
}
if (success) {
long warnTime = warnWaitTime;
if (r != null && System.currentTimeMillis() - r.getCreationTime() > warnTime) {
LogManager.logWarning(LogConstants.CTX_RUNTIME, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30009, maximumPoolSize, poolName, highestQueueSize, warnTime));
warnWaitTime*=2; //we don't really care if this is synchronized
}
}
t.setName(name);
}
}
};
});
}
private void checkForTermination() {
if (terminated) {
throw new RejectedExecutionException();
}
}
public int getActiveCount() {
return activeCount;
}
public int getSubmittedCount() {
return submittedCount;
}
public int getCompletedCount() {
return completedCount;
}
public int getPoolSize() {
return activeCount;
}
public boolean isTerminated() {
return terminated;
}
public void shutdown() {
this.terminated = true;
}
public int getLargestPoolSize() {
return this.highestActiveCount;
}
public WorkerPoolStatisticsMetadata getStats() {
WorkerPoolStatisticsMetadata stats = new WorkerPoolStatisticsMetadata();
stats.setName(poolName);
stats.setQueued(queue.size());
stats.setHighestQueued(highestQueueSize);
stats.setActiveThreads(getActiveCount());
stats.setMaxThreads(this.maximumPoolSize);
stats.setTotalSubmitted(getSubmittedCount());
stats.setHighestActiveThreads(getLargestPoolSize());
stats.setTotalCompleted(getCompletedCount());
return stats;
}
public boolean hasWork() {
synchronized (poolLock) {
return this.getSubmittedCount() - this.getCompletedCount() > 0 && !this.isTerminated();
}
}
public List<Runnable> shutdownNow() {
this.shutdown();
synchronized (poolLock) {
synchronized (threads) {
for (Thread t : threads) {
t.interrupt();
}
}
List<Runnable> result = new ArrayList<Runnable>(queue);
queue.clear();
result.addAll(this.tpe.shutdownNow());
return result;
}
}
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long timeoutMillis = unit.toMillis(timeout);
long finalMillis = System.currentTimeMillis() + timeoutMillis;
synchronized (poolLock) {
while (this.activeCount > 0 || !terminated) {
if (timeoutMillis < 1) {
return false;
}
poolLock.wait(timeoutMillis);
timeoutMillis = finalMillis - System.currentTimeMillis();
}
}
return true;
}
}