/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ 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 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ 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 the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.util;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
/**
* Queues execution tasks into a single pipeline where some tasks can be executed in parallel
* but preserve 'messages' order so the next task process messages on a single thread in
* the same order they were added to the previous executor
*
* Created by Anton Nashatyrev on 23.02.2016.
*/
public class ExecutorPipeline <In, Out>{
private BlockingQueue<Runnable> queue;
private ThreadPoolExecutor exec;
private boolean preserveOrder = false;
private Functional.Function<In, Out> processor;
private Functional.Consumer<Throwable> exceptionHandler;
private ExecutorPipeline <Out, ?> next;
private AtomicLong orderCounter = new AtomicLong();
private long nextOutTaskNumber = 0;
private Map<Long, Out> orderMap = new HashMap<>();
private ReentrantLock lock = new ReentrantLock();
private String threadPoolName;
private static AtomicInteger pipeNumber = new AtomicInteger(1);
private AtomicInteger threadNumber = new AtomicInteger(1);
public ExecutorPipeline(int threads, int queueSize, boolean preserveOrder, Functional.Function<In, Out> processor,
Functional.Consumer<Throwable> exceptionHandler) {
queue = new LimitedQueue<>(queueSize);
exec = new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, queue, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, threadPoolName + "-" + threadNumber.getAndIncrement());
}
});
this.preserveOrder = preserveOrder;
this.processor = processor;
this.exceptionHandler = exceptionHandler;
this.threadPoolName = "pipe-" + pipeNumber.getAndIncrement();
}
public ExecutorPipeline<Out, Void> add(int threads, int queueSize, final Functional.Consumer<Out> consumer) {
return add(threads, queueSize, false, new Functional.Function<Out, Void>() {
@Override
public Void apply(Out out) {
consumer.accept(out);
return null;
}
});
}
public <NextOut> ExecutorPipeline<Out, NextOut> add(int threads, int queueSize, boolean preserveOrder,
Functional.Function<Out, NextOut> processor) {
ExecutorPipeline<Out, NextOut> ret = new ExecutorPipeline<>(threads, queueSize, preserveOrder, processor, exceptionHandler);
next = ret;
return ret;
}
private void pushNext(long order, Out res) {
if (next != null) {
if (!preserveOrder) {
next.push(res);
} else {
lock.lock();
try {
if (order == nextOutTaskNumber) {
next.push(res);
while(true) {
nextOutTaskNumber++;
Out out = orderMap.remove(nextOutTaskNumber);
if (out == null) break;
next.push(out);
}
} else {
orderMap.put(order, res);
}
} finally {
lock.unlock();
}
}
}
}
public void push(final In in) {
final long order = orderCounter.getAndIncrement();
exec.execute(new Runnable() {
@Override
public void run() {
try {
pushNext(order, processor.apply(in));
} catch (Throwable e) {
exceptionHandler.accept(e);
}
}
});
}
public void pushAll(final List<In> list) {
for (In in : list) {
push(in);
}
}
public ExecutorPipeline<In, Out> setThreadPoolName(String threadPoolName) {
this.threadPoolName = threadPoolName;
return this;
}
public BlockingQueue<Runnable> getQueue() {
return queue;
}
public Map<Long, Out> getOrderMap() {
return orderMap;
}
public void shutdown() {
try {
exec.shutdown();
} catch (Exception e) {}
if (next != null) {
exec.shutdown();
}
}
public boolean isShutdown() {
return exec.isShutdown();
}
/**
* Shutdowns executors and waits until all pipeline
* submitted tasks complete
* @throws InterruptedException
*/
public void join() throws InterruptedException {
exec.shutdown();
exec.awaitTermination(10, TimeUnit.MINUTES);
if (next != null) next.join();
}
private static class LimitedQueue<E> extends LinkedBlockingQueue<E> {
public LimitedQueue(int maxSize) {
super(maxSize);
}
@Override
public boolean offer(E e) {
// turn offer() and add() into a blocking calls (unless interrupted)
try {
put(e);
return true;
} catch(InterruptedException ie) {
Thread.currentThread().interrupt();
}
return false;
}
}
}