package hu.sztaki.ilab.longneck.process.task;
import hu.sztaki.ilab.longneck.bootstrap.CompactProcess;
import hu.sztaki.ilab.longneck.bootstrap.PropertyUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
/**
*
* @author Molnár Péter <molnarp@sztaki.mta.hu>
*/
public class ThreadManager {
/** Log object. */
private final Logger LOG = Logger.getLogger(ThreadManager.class);
/** Number of worker threads. */
private int workerThreadsNum;
/** Source queue size. */
private int sourceQueueSize;
/** Target queue size. */
private int targetQueueSize;
/** Error queue size. */
private int errorQueueSize;
/** Monitor loop wait time. */
private int monitorLoopWaitTime;
/** The source reader runnable. */
private SourceReader sourceReader;
/** Source reader thread. */
private Thread sourceReaderThread;
/** Target writer runnable. */
private TargetWriter targetWriter;
/** Target writer thread. */
private Thread targetWriterThread;
/** Error writer runnable. */
private ErrorWriter errorWriter;
/** Target writer thread. */
private Thread errorWriterThread;
/** Process worker runnables. */
private ProcessWorker[] processWorkers;
/** Process worker threads. */
private Thread[] processWorkerThreads;
/** The process to run. */
private CompactProcess compactProcess;
/** Runtime properties. */
private Properties runtimeProperties;
/** Source queue where read records are appended. */
private BlockingQueue<QueueItem> sourceQueue;
/** Target queue where processed records are appended. */
private BlockingQueue<QueueItem> targetQueue;
/** Error queue where error reports are appended. */
private BlockingQueue<QueueItem> errorQueue;
/** Number of records read from source. */
private long recordCountSource = 0;
/** Number of failed records. */
private long recordCountError = 0;
/** Number of records written to target. */
private long recordCountTarget = 0;
public ThreadManager() {
}
public ThreadManager(Properties runtimeProperties) {
this.runtimeProperties = runtimeProperties;
}
public void init() {
// Assign properties
this.workerThreadsNum =
PropertyUtils.getIntProperty(runtimeProperties, "workerThreadsNum", 1);
this.sourceQueueSize =
PropertyUtils.getIntProperty(runtimeProperties, "sourceQueueSize", 100);
this.targetQueueSize =
PropertyUtils.getIntProperty(runtimeProperties, "targetQueueSize", 100);
this.errorQueueSize =
PropertyUtils.getIntProperty(runtimeProperties, "errorQueueSize", 100);
this.monitorLoopWaitTime =
PropertyUtils.getIntProperty(runtimeProperties, "monitorLoopWaitTime", 200);
sourceQueue = new ArrayBlockingQueue<QueueItem>(sourceQueueSize);
targetQueue = new ArrayBlockingQueue<QueueItem>(targetQueueSize);
errorQueue = new ArrayBlockingQueue<QueueItem>(errorQueueSize);
// Create source reader
sourceReader = new SourceReader(sourceQueue, compactProcess.getProcess().getSource(), runtimeProperties);
// Create target writer
targetWriter = new TargetWriter(targetQueue, compactProcess.getProcess().getTarget(), runtimeProperties);
// Create error writer
errorWriter = new ErrorWriter(errorQueue, compactProcess.getProcess().getErrorTarget(), runtimeProperties);
// Create reader and writer threads
sourceReaderThread = new Thread(sourceReader, "source reader");
targetWriterThread = new Thread(targetWriter, "target writer");
errorWriterThread = new Thread(errorWriter, "error writer");
// Create process worker collections
processWorkers = new ProcessWorker[workerThreadsNum];
processWorkerThreads = new Thread[workerThreadsNum];
// Create process workers
for (int i = 0; i < processWorkers.length; ++i) {
processWorkers[i] = new ProcessWorker(sourceQueue, targetQueue, errorQueue,
compactProcess.getFrameAddressResolver(),
compactProcess.getProcess().getTopLevelBlocks(),
runtimeProperties);
processWorkerThreads[i] = new Thread(processWorkers[i],
String.format("process worker %1$d", i));
}
}
public void run() {
LOG.info("====== Thread manager starting up. ======");
// Start threads
targetWriterThread.start();
errorWriterThread.start();
for (int i = 0; i < processWorkerThreads.length; ++i) {
processWorkerThreads[i].start();
}
sourceReaderThread.start();
while (!sourceReaderThread.isAlive()) {
Thread.yield();
}
// Wait for threads
try {
// Monitor thread status
while (sourceReaderThread.isAlive() || sourceQueue.size() > 0) {
for (int i = 0; i < processWorkerThreads.length; ++i) {
if (!processWorkerThreads[i].isAlive()) {
LOG.error(String.format("Process worker %1$d died unexpectedly, shutting down.", i));
processWorkerThreads[i].join();
stop();
return;
}
}
if (!targetWriterThread.isAlive()) {
LOG.error("Target writer died unexpectedly, shutting down.");
stop();
return;
}
if (!errorWriterThread.isAlive()) {
LOG.error("Error writer died unexpectedly, shutting down.");
stop();
return;
}
Thread.sleep(monitorLoopWaitTime);
}
LOG.debug("Source reader is dead, and queue size = 0.");
sourceReaderThread.join();
for (int i = 0; i < processWorkerThreads.length; ++i) {
sourceQueue.put(new QueueItem(true));
}
// Stop worker threads
for (int i = 0; i < processWorkerThreads.length; ++i) {
processWorkerThreads[i].join();
}
// Stop target writer thread
targetQueue.put(new QueueItem(true));
targetWriterThread.join();
// Stop error writer thread
errorQueue.put(new QueueItem(true));
errorWriterThread.join();
} catch (InterruptedException ex) {
LOG.warn("Thread interrupted.", ex);
}
LOG.info("Reader stats: " + getReaderStatistics().toString());
LOG.info("Writer stats: " + getWriterStatistics().toString());
LOG.info("Worker avg stats: " + TaskStatistics.avg(getWorkerStatistics()).toString());
LOG.info("====== Thread manager shutting down. ======");
}
public synchronized void stop() {
sourceReader.setRunning(false);
try {
sourceQueue.clear();
for (int j = 0; j < processWorkerThreads.length; ++j) {
sourceQueue.offer(new QueueItem(true), 100, TimeUnit.MILLISECONDS);
}
targetQueue.clear();
targetQueue.offer(new QueueItem(true), 100, TimeUnit.MILLISECONDS);
errorQueue.clear();
errorQueue.offer(new QueueItem(true), 100, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
LOG.debug(ex.getMessage(), ex);
}
for (int i = 0; i < workerThreadsNum; ++i) {
processWorkers[i].setRunning(false);
}
targetWriter.setRunning(false);
errorWriter.setRunning(false);
// Wait for threads
try {
// Stop source reader thread
sourceReaderThread.join(200);
if (sourceReaderThread.isAlive()) {
sourceReaderThread.interrupt();
}
// Stop worker threads
for (int i = 0; i < workerThreadsNum; ++i) {
processWorkerThreads[i].join(200);
if (processWorkerThreads[i].isAlive()) {
processWorkerThreads[i].interrupt();
}
}
// Stop target writer thread
targetWriterThread.join(200);
if (targetWriterThread.isAlive()) {
targetWriterThread.interrupt();
}
// Stop error writer thread
targetWriterThread.join(200);
if (errorWriterThread.isAlive()) {
errorWriterThread.interrupt();
}
} catch (InterruptedException ex) {
LOG.error("Interrupted.", ex);
}
}
public List<TaskStatistics> getWorkerStatistics() {
List<TaskStatistics> stats = new ArrayList<TaskStatistics>();
for (ProcessWorker worker : processWorkers) {
stats.add(worker.getStats());
}
return stats;
}
public TaskStatistics getReaderStatistics() {
return sourceReader.getStats();
}
public TaskStatistics getWriterStatistics() {
return targetWriter.getStats();
}
public void addShutdownHook() {
final ThreadManager threadManager = this;
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
threadManager.stop();
}
});
}
public CompactProcess getProcess() {
return compactProcess;
}
public void setProcess(CompactProcess process) {
this.compactProcess = process;
}
public int getSourceQueueSize() {
return sourceQueueSize;
}
public void setSourceQueueSize(int sourceQueueSize) {
this.sourceQueueSize = sourceQueueSize;
}
public int getTargetQueueSize() {
return targetQueueSize;
}
public void setTargetQueueSize(int targetQueueSize) {
this.targetQueueSize = targetQueueSize;
}
public int getErrorQueueSize() {
return errorQueueSize;
}
public void setErrorQueueSize(int errorQueueSize) {
this.errorQueueSize = errorQueueSize;
}
public int getWorkerThreadsNum() {
return workerThreadsNum;
}
public void setWorkerThreadsNum(int workerThreadsNum) {
this.workerThreadsNum = workerThreadsNum;
}
public Properties getRuntimeProperties() {
return runtimeProperties;
}
public void setRuntimeProperties(Properties runtimeProperties) {
this.runtimeProperties = runtimeProperties;
}
public long getRecordCountTarget() {
return recordCountTarget;
}
public long getRecordCountError() {
return recordCountError;
}
public long getRecordCountSource() {
return recordCountSource;
}
}