package cz.cuni.mff.d3s.been.objectrepository; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import cz.cuni.mff.d3s.been.persistence.SuccessAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cz.cuni.mff.d3s.been.cluster.Reapable; import cz.cuni.mff.d3s.been.cluster.Reaper; import cz.cuni.mff.d3s.been.cluster.Service; /** * A general shared structure digestion service * * @param <T> Type of digested items */ public class Digester<T> implements Service, Reapable { private static final long POOL_SHUTDOWN_TIMEOUT_MILLIS = 3000; private static final Logger log = LoggerFactory.getLogger(Digester.class); private final Float failRateThreshold; private final Long suspendTimeOnHighFailRate; private final ExecutorService pool; private final SuccessAction<T> successAction; private final FailAction<T> failAction; private final Take<T> take; private final Poll<T> poll; private final FailRate failRateMonitor; Digester(Take<T> take, Poll<T> poll, SuccessAction<T> successAction, FailAction<T> failAction, Float failRateThreshold, Long suspendTimeOnHighFailRate) { this.failRateThreshold = failRateThreshold; this.suspendTimeOnHighFailRate = suspendTimeOnHighFailRate; this.take = take; this.poll = poll; this.successAction = successAction; this.failAction = failAction; this.pool = Executors.newCachedThreadPool(); this.failRateMonitor = new FailRate(); } /** * Create a digester * * @param take Blocking take action on targeted shared data structure * @param poll Non blocking poll action on targeted shared data structure * @param successAction Action to perform on digested actions * @param failAction Action to perform on digested actions if success action fails * @param failRateThreshold Rate of failure at which this digester suspends its digestion attempts temporarily * @param suspendTimeOnHighFailRate Time (in milliseconds) this digester should suspend for if the fail rate surpasses fail rate threshold * @param <T> Type of digested items * * @return A new digester instance */ public static <T> Digester<T> create(Take<T> take, Poll<T> poll, SuccessAction<T> successAction, FailAction<T> failAction, Float failRateThreshold, Long suspendTimeOnHighFailRate) { return new Digester<T>(take, poll, successAction, failAction, failRateThreshold, suspendTimeOnHighFailRate); } /** * Suggest adding a new ephemerous consumer to this digester's internal thread pool. This doesn't necessarily result in a new thread, because the digester may deem itself unreliable and refuse thread creation. */ public void addNewWorkingThread() { if(failRateMonitor.getFailRate() < failRateThreshold) { pool.execute(new EphemerousConsumer<T>(poll, successAction, failAction, failRateMonitor)); } } @Override public void start() { pool.execute(new LingeringConsumer<T>(take, successAction, failAction, failRateMonitor, failRateThreshold, suspendTimeOnHighFailRate)); } @Override public void stop() { pool.shutdownNow(); try { pool.awaitTermination(POOL_SHUTDOWN_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("Queue worker pool forced to terminate. Results may have been lost."); } } @Override public Reaper createReaper() { return new Reaper() { @Override protected void reap() throws InterruptedException { Digester.this.stop(); } }; } }