package io.lumify.core.util; import io.lumify.core.metrics.JmxMetricsManager; import io.lumify.core.metrics.PausableTimerContext; import io.lumify.core.metrics.PausableTimerContextAware; import com.codahale.metrics.Counter; import com.codahale.metrics.Timer; import com.google.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.LinkedList; import java.util.Queue; public abstract class ThreadedTeeInputStreamWorker<TResult, TData> implements Runnable { private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(ThreadedTeeInputStreamWorker.class); private Counter totalProcessedCounter = null; private Counter processingCounter; private Counter totalErrorCounter; private Timer processingTimeTimer; private boolean stopped; private final Queue<Work> workItems = new LinkedList<Work>(); private final Queue<WorkResult<TResult>> workResults = new LinkedList<WorkResult<TResult>>(); private JmxMetricsManager metricsManager; @Override public final void run() { if (totalProcessedCounter == null) { String namePrefix = metricsManager.getNamePrefix(this); totalProcessedCounter = metricsManager.counter(namePrefix + "total-processed"); processingCounter = metricsManager.counter(namePrefix + "processing"); totalErrorCounter = metricsManager.counter(namePrefix + "total-errors"); processingTimeTimer = metricsManager.timer(namePrefix + "processing-time"); } stopped = false; try { while (!stopped) { Work work; synchronized (workItems) { if (workItems.size() == 0) { workItems.wait(1000); continue; } work = workItems.remove(); } InputStream in = work.getIn(); try { LOGGER.debug("BEGIN doWork (%s)", getClass().getName()); TResult result; PausableTimerContext timerContext = new PausableTimerContext(processingTimeTimer); if (in instanceof PausableTimerContextAware) { ((PausableTimerContextAware) in).setPausableTimerContext(timerContext); } processingCounter.inc(); try { result = doWork(in, work.getData()); } finally { LOGGER.debug("END doWork (%s)", getClass().getName()); processingCounter.dec(); totalProcessedCounter.inc(); timerContext.stop(); } synchronized (workResults) { workResults.add(new WorkResult<TResult>(result, null)); workResults.notifyAll(); } } catch (Exception ex) { totalErrorCounter.inc(); synchronized (workResults) { workResults.add(new WorkResult<TResult>(null, ex)); workResults.notifyAll(); } } finally { try { in.close(); } catch (IOException ex) { synchronized (workResults) { workResults.add(new WorkResult<TResult>(null, ex)); workResults.notifyAll(); } } } } } catch (InterruptedException ex) { LOGGER.error("thread was interrupted", ex); } } protected abstract TResult doWork(InputStream work, TData data) throws Exception; public void enqueueWork(InputStream in, TData data) { synchronized (workItems) { workItems.add(new Work(in, data)); workItems.notifyAll(); } } public WorkResult<TResult> dequeueResult() { synchronized (workResults) { if (workResults.size() == 0) { long startTime = new Date().getTime(); while (workResults.size() == 0 && (new Date().getTime() - startTime < 10 * 1000)) { try { LOGGER.warn("worker has zero results. sleeping waiting for results."); workResults.wait(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } return workResults.remove(); } } public void stop() { stopped = true; } private class Work { private final InputStream in; private final TData data; public Work(InputStream in, TData data) { this.in = in; this.data = data; } private InputStream getIn() { return in; } private TData getData() { return data; } } public static class WorkResult<TResult> { private final TResult result; private final Exception error; public WorkResult(TResult result, Exception error) { this.result = result; this.error = error; } public Exception getError() { return error; } public TResult getResult() { return result; } } @Inject public void setMetricsManager(JmxMetricsManager metricsManager) { this.metricsManager = metricsManager; } }