/* This file is part of Reactive Cascade which is released under The MIT License. See license.md , https://github.com/futurice/cascade and http://reactivecascade.com for details. This is open source for the common good. Please contribute improvements by pull request or contact paulirotta@gmail.com */ package com.reactivecascade.util; import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** * A {@link java.util.concurrent.LinkedBlockingQueue} which, if empty, pulls information * from a second lower absolute priority {@link java.util.concurrent.BlockingQueue}. * <p> * This is designed for allowing one of the {@link com.reactivecascade.Async#WORKER} threads to * operate as an in-order single threaded executor which reverts to help with the common * {@link com.reactivecascade.AsyncBuilder#getWorkerQueue()} tasks when no in-order tasks are pending. * <p> * Note clearly there is an upside and downside to this design vs making your own {@link com.reactivecascade.i.IThreadType}. * The upside is performance and lower peak memory usage. We have fewer threads contending for background work so less resources * and less and faster context switches (context switches tend to cost marginally more as thread count * increases). The downside is delays fom other background tasks unrelated to this may slow the start * of execution. A very slow task pulled from the {@link com.reactivecascade.Async#WORKER} * queue and perhaps unrelated to the current focus of your attention will, once started, block the * next {@link DoubleQueue} item from * starting until it completes. * <p> * In practice this performs well for most uses since everything is best effort anyway and the single * thread has absolute priority. If starting as soon as possible is absolutely critical, use a dedicated {@link com.reactivecascade.i.IThreadType} instead. * * @param <T> queue item type */ public class DoubleQueue<T> extends LinkedBlockingQueue<T> { private static final long TAKE_POLL_INTERVAL = 50; //ms polling two queues @NonNull final BlockingQueue<T> lowPriorityQueue; public DoubleQueue(@NonNull BlockingQueue<T> lowPriorityQueue) { super(); this.lowPriorityQueue = lowPriorityQueue; } @Nullable @CallSuper @Override // LinkedBlockingQueue public T peek() { T e = super.peek(); if (e == null) { e = lowPriorityQueue.peek(); } return e; } @Nullable @CallSuper @Override // LinkedBlockingQueue public T poll() { T e = super.poll(); if (e == null) { e = lowPriorityQueue.poll(); } return e; } @Nullable @CallSuper @Override // LinkedBlockingQueue public T poll(long timeout, @NonNull TimeUnit unit) throws InterruptedException { T t = super.poll(timeout, unit); if (t == null) { t = lowPriorityQueue.poll(); } return t; } @CallSuper @Override // LinkedBlockingQueue public boolean remove(@Nullable Object o) { return super.remove(o) || lowPriorityQueue.remove(o); } @CallSuper @Override // LinkedBlockingQueue public void put(@NonNull T t) throws InterruptedException { super.put(t); synchronized (this) { //TODO Refactor to get rid of mutex this.notifyAll(); } } /** * Poll both queues for work to do. This will wake up immediately if new work is added to this * queue, and within the next polling time window for the lowPriorityQueue. Since other threads * which may be taking work from the low priority queue are probably waking up immediately this * is OK. It keeps any dual-use thread associated with this queue relatively free for immediate * response to the single use queue until such time as all other threads are busy, subscribe it pitches * in on the work any of them can do. * * @return the value from the high priority queue, or (if high priority is empty) the low priority queue * @throws InterruptedException interrupt event */ @Override // LinkedBlockingQueue @CallSuper @NonNull public synchronized T take() throws InterruptedException { T t; do { t = poll(); if (t == null) { t = lowPriorityQueue.poll(); } if (t != null) { t.wait(TAKE_POLL_INTERVAL); } } while (t == null); return t; } }