package org.netbeans.gradle.project.tasks; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.jtrim.cancel.Cancellation; import org.jtrim.cancel.CancellationToken; import org.jtrim.collections.RefCollection; import org.jtrim.collections.RefLinkedList; import org.jtrim.concurrent.CancelableTask; import org.jtrim.concurrent.CleanupTask; import org.jtrim.concurrent.TaskExecutor; import org.jtrim.event.InitLaterListenerRef; import org.jtrim.event.ListenerRef; import org.jtrim.event.UnregisteredListenerRef; import org.jtrim.utils.ExceptionHelper; public final class PriorityAwareExecutor { private final TaskExecutor wrapped; private final TaskQueue taskQueue; public PriorityAwareExecutor(TaskExecutor wrapped) { ExceptionHelper.checkNotNullArgument(wrapped, "wrapped"); this.wrapped = wrapped; this.taskQueue = new TaskQueue(); } private void executeForPriority( CancellationToken cancelToken, Priority priority, CancelableTask task, CleanupTask cleanupTask) { TaskDef taskDef = new TaskDef(cancelToken, task, cleanupTask); RefCollection.ElementRef<?> queueRef = taskQueue.addTask(priority, taskDef); taskDef.init(queueRef); final AtomicReference<TaskDef> taskDefRef = new AtomicReference<>(null); CancelableTask forwarderTask = new CancelableTask() { @Override public void execute(CancellationToken cancelToken) throws Exception { TaskDef task = taskQueue.pollTask(); taskDefRef.set(task); task.doTask(cancelToken); } }; CleanupTask forwarderCleanupTask = new CleanupTask() { @Override public void cleanup(boolean canceled, Throwable error) throws Exception { TaskDef def = taskDefRef.get(); if (def != null) { def.cleanup(canceled, error); } else if (canceled) { // This means, that the executor has been terminated // so poll one cleanup task and execute it. TaskDef task = taskQueue.pollTask(); if (task != null) { task.cleanup(canceled, error); } } } }; wrapped.execute(Cancellation.UNCANCELABLE_TOKEN, forwarderTask, forwarderCleanupTask); } private TaskExecutor getExecutor(final Priority priority) { return new TaskExecutor() { @Override public void execute(CancellationToken cancelToken, CancelableTask task, CleanupTask cleanupTask) { executeForPriority(cancelToken, priority, task, cleanupTask); } }; } public TaskExecutor getHighPriorityExecutor() { return getExecutor(Priority.HIGH); } public TaskExecutor getLowPriorityExecutor() { return getExecutor(Priority.LOW); } private static final class TaskQueue { private final Lock queueLock; // TODO: Allow arbitrary priority private final RefLinkedList<TaskDef> queueHighPriority; private final RefLinkedList<TaskDef> queueLowPriority; public TaskQueue() { this.queueLock = new ReentrantLock(); this.queueLowPriority = new RefLinkedList<>(); this.queueHighPriority = new RefLinkedList<>(); } private RefLinkedList<TaskDef> getQueue(Priority priority) { return priority == Priority.HIGH ? queueHighPriority : queueLowPriority; } private static <E> RefCollection.ElementRef<E> wrapLocked(final RefCollection.ElementRef<E> ref, final Lock lock) { return new RefCollection.ElementRef<E>() { @Override public E setElement(E newElement) { throw new UnsupportedOperationException("Cannot update element"); } @Override public E getElement() { return ref.getElement(); } @Override public boolean isRemoved() { lock.lock(); try { return ref.isRemoved(); } finally { lock.unlock(); } } @Override public void remove() { lock.lock(); try { ref.remove(); } finally { lock.unlock(); } } }; } public RefCollection.ElementRef<?> addTask(Priority priority, TaskDef task) { RefLinkedList<TaskDef> queue = getQueue(priority); queueLock.lock(); try { return wrapLocked(queue.addLastGetReference(task), queueLock); } finally { queueLock.unlock(); } } public TaskDef pollTask() { queueLock.lock(); try { TaskDef result = queueHighPriority.poll(); if (result == null) { result = queueLowPriority.poll(); } return result; } finally { queueLock.unlock(); } } } private enum Priority { HIGH, LOW } private static final class TaskDef { private volatile CancellationToken cancelToken; private volatile CancelableTask task; private volatile boolean skippedExecute; private final CleanupTask cleanupTask; private final AtomicReference<ListenerRef> cancelRef; public TaskDef(CancellationToken cancelToken, CancelableTask task, CleanupTask cleanupTask) { this.cancelToken = cancelToken; this.task = task; this.cleanupTask = cleanupTask; this.cancelRef = new AtomicReference<>(null); this.skippedExecute = false; } public void init(final RefCollection.ElementRef<?> queueRef) { final InitLaterListenerRef cancelRefRef = new InitLaterListenerRef(); cancelRefRef.init(cancelToken.addCancellationListener(new Runnable() { @Override public void run() { removeTask(); if (cleanupTask == null) { queueRef.remove(); } cancelRefRef.unregister(); } })); if (!this.cancelRef.compareAndSet(null, cancelRefRef)) { cancelRefRef.unregister(); } } public void removeTask() { task = null; cancelToken = null; } public void doTask(CancellationToken executorCancelToken) throws Exception { CancellationToken currentCancelToken = cancelToken; currentCancelToken = currentCancelToken != null ? Cancellation.anyToken(executorCancelToken, currentCancelToken) : executorCancelToken; CancelableTask currentTask = task; if (currentTask != null) { currentTask.execute(currentCancelToken); } else { skippedExecute = true; } } public void cleanup(boolean canceled, Throwable error) throws Exception { try { ListenerRef currentCancelRef = cancelRef.getAndSet(UnregisteredListenerRef.INSTANCE); if (currentCancelRef != null) { currentCancelRef.unregister(); } } finally { if (cleanupTask != null) { cleanupTask.cleanup(canceled || skippedExecute, error); } } } } }