All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mindsnacks.zinc.classes.downloads.PriorityJobQueue Maven / Gradle / Ivy

There is a newer version: 1.6.2
Show newest version
package com.mindsnacks.zinc.classes.downloads;

import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.mindsnacks.zinc.exceptions.ZincRuntimeException;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * User: NachoSoto
 * Date: 9/21/13
 */
public class PriorityJobQueue {
    private static final int INITIAL_QUEUE_CAPACITY = 20;
    private static final int SCHEDULER_TERMINATION_TIMEOUT = 30;
    private static final int EXECUTOR_SERVICE_TERMINATION_TIMEOUT = 300;
    private static final int FUTURE_WAITING_SECONDS_INTERVAL = 2;

    private final int mConcurrency;
    private final ThreadFactory mThreadFactory;
    private final DataProcessor mDataProcessor;
    private final PriorityCalculator mPriorityCalculator;

    private ExecutorService mScheduler;
    private ListeningExecutorService mExecutorService;
    private ListeningExecutorService mFuturesExecutorService;

    private final SortablePriorityBlockingQueue mQueue;
    private final Map> mFutures = new HashMap>();
    private final Set mAddedElements = new HashSet();

    private final Lock mLock = new ReentrantLock();
    private final Condition mEnqueued = mLock.newCondition();
    private final Semaphore mEnqueuedDataSemaphore;
    private final AtomicBoolean mShouldReorder = new AtomicBoolean(false);

    public PriorityJobQueue(final int concurrency,
                            final ThreadFactory threadFactory,
                            final PriorityCalculator priorityCalculator,
                            final DataProcessor dataProcessor) {
        mConcurrency = concurrency;
        mThreadFactory = threadFactory;
        mDataProcessor = dataProcessor;
        mEnqueuedDataSemaphore = new Semaphore(concurrency);

        mPriorityCalculator = priorityCalculator;
        mQueue = new SortablePriorityBlockingQueue(new PriorityBlockingQueue(INITIAL_QUEUE_CAPACITY, createPriorityComparator(mPriorityCalculator)));
    }

    public boolean isRunning() {
        return (mScheduler != null || mExecutorService != null);
    }

    public synchronized void start() {
        checkServiceIsRunning(false, "Service is already running");

        mScheduler = Executors.newSingleThreadExecutor(mThreadFactory);
        mFuturesExecutorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool(mThreadFactory));
        mExecutorService = MoreExecutors.listeningDecorator(new ThreadPoolExecutor(
                mConcurrency,
                mConcurrency,
                0L, TimeUnit.MICROSECONDS,
                new LinkedBlockingQueue(),
                mThreadFactory) {
            @Override
            protected void afterExecute(final Runnable r, final Throwable t) {
                super.afterExecute(r, t);

                mEnqueuedDataSemaphore.release();
            }
        });

        mScheduler.submit(createSchedulerTask());
    }

    private Comparator createPriorityComparator(final PriorityCalculator priorityCalculator) {
        final Comparator comparator = DownloadPriority.createComparator();

        return new Comparator() {
            @Override
            public int compare(final Input o1, final Input o2) {
                return comparator.compare(
                        priorityCalculator.getPriorityForObject(o1),
                        priorityCalculator.getPriorityForObject(o2));
            }
        };
    }

    public synchronized boolean stop() throws InterruptedException {
        checkServiceIsRunning(true, "Service is already stopped");

        boolean stopped = false;
        mScheduler.shutdownNow();
        stopped = mScheduler.awaitTermination(SCHEDULER_TERMINATION_TIMEOUT, TimeUnit.SECONDS);

        mExecutorService.shutdown();
        stopped &= mExecutorService.awaitTermination(EXECUTOR_SERVICE_TERMINATION_TIMEOUT, TimeUnit.SECONDS);

        mFuturesExecutorService.shutdown();
        stopped &= mFuturesExecutorService.awaitTermination(EXECUTOR_SERVICE_TERMINATION_TIMEOUT, TimeUnit.SECONDS);

        if (stopped) {
            mScheduler = mExecutorService = mFuturesExecutorService = null;
        }

        return stopped;
    }

    public void add(final Input element) {
        mLock.lock();

        try {
            mAddedElements.add(element);
            mQueue.offer(element);
        } finally {
            mLock.unlock();
        }
    }

    public Future get(final Input element) throws JobNotFoundException {
        checkServiceIsRunning(true, "Service should be running");

        if (mAddedElements.contains(element)) {
            return waitForFuture(element);
        } else {
            throw new JobNotFoundException(element);
        }
    }

    public static interface DataProcessor {
        Callable process(Input data);
    }

    public void recalculatePriorities() {
        mShouldReorder.lazySet(true);
    }

    private Runnable createSchedulerTask() {
        return new Runnable() {
            public void run() {
                try {
                    Input data;
                    while ((data = mQueue.take()) != null) {
                        mEnqueuedDataSemaphore.acquire();

                        mLock.lock();

                        try {
                            mFutures.put(data, submit(data));
                            mEnqueued.signal();

                            if (mShouldReorder.getAndSet(false)) {
                                mQueue.reorder();
                            }
                        } finally {
                            mLock.unlock();
                        }
                    }
                } catch (final InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        };
    }

    private void checkServiceIsRunning(final boolean shouldBeRunning, final String errorMessage) {
        if (isRunning() != shouldBeRunning) {
            throw new ZincRuntimeException(errorMessage);
        }
    }

    private ListenableFuture submit(final Input element) {
        return mExecutorService.submit(mDataProcessor.process(element));
    }

    private Future waitForFuture(final Input element) {
        return Futures.dereference(mFuturesExecutorService.submit(new Callable>() {
            @Override
            public ListenableFuture call() throws Exception {
                ListenableFuture result;

                mLock.lock();

                try {
                    while ((result = mFutures.get(element)) == null) {
                        mEnqueued.await(FUTURE_WAITING_SECONDS_INTERVAL, TimeUnit.SECONDS);
                    }
                } finally {
                    mLock.unlock();
                }

                return result;
            }
        }));
    }

    public static class JobNotFoundException extends ZincRuntimeException {
        public JobNotFoundException(final Object object) {
            super((object == null) ? "Object is null" : "Object '" + object.toString() + "' had not been added");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy