package com.facebook; import java.util.concurrent.Executor; class PrioritizedWorkQueue { public static final int PRIORITY_RUNNING = -1; public static final int PRIORITY_ACTIVE = 0; public static final int PRIORITY_BACKGROUND = 1; private static final int PRIORITY_COUNT = 2; private static final int DEFAULT_MAX_CONCURRENT = 8; private final WorkNode[] queues = new WorkNode[PRIORITY_COUNT]; private final int maxConcurrent; private final Executor executor; private WorkNode runningItems = null; private int runningCount = 0; PrioritizedWorkQueue() { this(DEFAULT_MAX_CONCURRENT, Settings.getExecutor()); } PrioritizedWorkQueue(int maxConcurrent, Executor executor) { this.maxConcurrent = maxConcurrent; this.executor = executor; } WorkItem addActiveWorkItem(Runnable callback) { WorkNode node = new WorkNode(callback); synchronized (queues) { queues[node.priority] = node.addToList(queues[node.priority]); } startItem(); return node; } void backgroundAll() { setPriorityOnAll(PRIORITY_BACKGROUND); } void validate() { synchronized (queues) { // Verify that priority on items agrees with the priority of the queue they are in for (int priority = 0; priority < PRIORITY_COUNT; priority++) { if (queues[priority] != null) { WorkNode walk = queues[priority]; do { walk.verify(priority); walk = walk.getNext(); } while (walk != queues[priority]); } } // Verify that all running items know they are running, and counts match int count = 0; if (runningItems != null) { WorkNode walk = runningItems; do { walk.verify(PRIORITY_RUNNING); count++; walk = walk.getNext(); } while (walk != runningItems); } assert runningCount == count; } } private void startItem() { finishItemAndStartNew(null); } private void finishItemAndStartNew(WorkNode finished) { WorkNode ready = null; synchronized (queues) { if (finished != null) { runningItems = finished.removeFromList(runningItems); runningCount--; } if (runningCount < maxConcurrent) { ready = extractNextReadyItem(); if (ready != null) { ready.setPriorityRunning(); runningItems = ready.addToList(runningItems); runningCount++; } } } if (ready != null) { execute(ready); } } private void execute(final WorkNode node) { executor.execute(new Runnable() { @Override public void run() { try { node.getCallback().run(); } finally { finishItemAndStartNew(node); } } }); } private WorkNode extractNextReadyItem() { for (int priority = 0; priority < PRIORITY_COUNT; priority++) { WorkNode ready = queues[priority]; if (ready != null) { queues[priority] = ready.removeFromList(queues[priority]); return ready; } } return null; } private void setPriorityOnAll(int priority) { synchronized (queues) { for (int i = 0; i < PRIORITY_COUNT; i++) { if (i != priority) { WorkNode move = queues[i]; if (move != null) { do { move.priority = priority; move = move.getNext(); } while (move != queues[i]); queues[i] = null; queues[priority] = move.spliceLists(queues[priority]); } } } } } private class WorkNode implements WorkItem { private final Runnable callback; private int priority; private WorkNode next; private WorkNode prev; WorkNode(Runnable callback) { this.callback = callback; this.priority = PRIORITY_ACTIVE; } @Override public boolean cancel() { synchronized (queues) { if ((priority != PRIORITY_RUNNING) && (next != null)) { queues[priority] = removeFromList(queues[priority]); return true; } } return false; } @Override public void setPriority(int newPriority) { assert priority >= 0; assert priority < PRIORITY_COUNT; synchronized (queues) { if (priority != PRIORITY_RUNNING) { if (next != null) { queues[priority] = removeFromList(queues[priority]); } priority = newPriority; queues[priority] = addToList(queues[priority]); } } } @Override public int getPriority() { return priority; } void setPriorityRunning() { synchronized (queues) { priority = PRIORITY_RUNNING; } } Runnable getCallback() { return callback; } WorkNode getNext() { return next; } WorkNode addToList(WorkNode list) { assert next == null; assert prev == null; if (list == null) { list = next = prev = this; } else { next = list; prev = list.prev; next.prev = prev.next = this; } return list; } WorkNode removeFromList(WorkNode list) { assert next != null; assert prev != null; if (list == this) { if (next == this) { list = null; } else { list = next; } } next.prev = prev; prev.next = next; next = prev = null; return list; } WorkNode spliceLists(WorkNode list) { if (list == null) { list = this; } else { WorkNode listPrev = list.prev; list.prev = prev; prev.next = list; listPrev.next = this; prev = listPrev; } return list; } void verify(int expectedPriority) { assert priority == expectedPriority; assert prev.next == this; assert next.prev == this; } } interface WorkItem { boolean cancel(); int getPriority(); void setPriority(int priority); } }