/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.concurrency; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.application.impl.ApplicationImpl; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.util.Processor; import com.intellij.util.concurrency.AtomicFieldUpdater; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; import java.util.concurrent.CountedCompleter; /** * Executes processor on array elements in range from lo (inclusive) to hi (exclusive). * To do this it starts executing processor on first array items and, if it takes too much time, splits the work and forks the right half. * The series of splits lead to linked list of forked sub tasks, each of which is a CountedCompleter of its own, * having this task as its parent. * After the first pass on the array, this task attempts to steal work from the recently forked off sub tasks, * by traversing the linked subtasks list, unforking each subtask and calling execAndForkSubTasks() on each recursively. * After that, the task completes itself. * The process of completing traverses task parent hierarchy, decrementing each pending count until it either * decrements not-zero pending count and stops or * reaches the top, in which case it invokes {@link java.util.concurrent.ForkJoinTask#quietlyComplete()} which causes the top level task to wake up and join successfully. * The exceptions from the sub tasks bubble up to the top and saved in {@link #throwable}. */ class ApplierCompleter<T> extends CountedCompleter<Void> { private final boolean runInReadAction; private final boolean failFastOnAcquireReadAction; private final ProgressIndicator progressIndicator; @NotNull private final List<T> array; @NotNull private final Processor<? super T> processor; private final int lo; private final int hi; private final ApplierCompleter<T> next; // keeps track of right-hand-side tasks volatile Throwable throwable; private static final AtomicFieldUpdater<ApplierCompleter, Throwable> throwableUpdater = AtomicFieldUpdater.forFieldOfType(ApplierCompleter.class, Throwable.class); // if not null, the read action has failed and this list contains unfinished subtasks private final Collection<ApplierCompleter<T>> failedSubTasks; //private final List<ApplierCompleter> children = new ArrayList<ApplierCompleter>(); @Override public boolean cancel(boolean mayInterruptIfRunning) { progressIndicator.cancel(); return super.cancel(mayInterruptIfRunning); } ApplierCompleter(ApplierCompleter<T> parent, boolean runInReadAction, boolean failFastOnAcquireReadAction, @NotNull ProgressIndicator progressIndicator, @NotNull List<T> array, @NotNull Processor<? super T> processor, int lo, int hi, @NotNull Collection<ApplierCompleter<T>> failedSubTasks, ApplierCompleter<T> next) { super(parent); this.runInReadAction = runInReadAction; this.failFastOnAcquireReadAction = failFastOnAcquireReadAction; this.progressIndicator = progressIndicator; this.array = array; this.processor = processor; this.lo = lo; this.hi = hi; this.failedSubTasks = failedSubTasks; this.next = next; } @Override public void compute() { if (failFastOnAcquireReadAction) { ((ApplicationImpl)ApplicationManager.getApplication()).executeByImpatientReader(()-> wrapInReadActionAndIndicator(this::execAndForkSubTasks)); } else { wrapInReadActionAndIndicator(this::execAndForkSubTasks); } } private void wrapInReadActionAndIndicator(@NotNull final Runnable process) { Runnable toRun = runInReadAction ? () -> { if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(process)) { failedSubTasks.add(this); doComplete(throwable); } } : process; ProgressIndicator existing = ProgressManager.getInstance().getProgressIndicator(); if (existing == progressIndicator) { // we are already wrapped in an indicator - most probably because we came here from helper which steals children tasks toRun.run(); } else { ProgressManager.getInstance().executeProcessUnderProgress(toRun, progressIndicator); } } static class ComputationAbortedException extends RuntimeException {} // executes tasks one by one and forks right halves if it takes too much time // returns the linked list of forked halves - they all need to be joined; null means all tasks have been executed, nothing was forked @Nullable private ApplierCompleter<T> execAndForkSubTasks() { int hi = this.hi; long start = System.currentTimeMillis(); ApplierCompleter<T> right = null; Throwable throwable = null; try { for (int i = lo; i < hi; ++i) { progressIndicator.checkCanceled(); if (!processor.process(array.get(i))) { throw new ComputationAbortedException(); } long finish = System.currentTimeMillis(); long elapsed = finish - start; if (elapsed > 5 && hi - i >= 2 && getSurplusQueuedTaskCount() <= JobSchedulerImpl.CORES_COUNT) { int mid = i + hi >>> 1; right = new ApplierCompleter<>(this, runInReadAction, failFastOnAcquireReadAction, progressIndicator, array, processor, mid, hi, failedSubTasks, right); //children.add(right); addToPendingCount(1); right.fork(); hi = mid; start = finish; } } // traverse the list looking for a task available for stealing if (right != null) { throwable = right.tryToExecAllList(); } } catch (Throwable e) { cancelProgress(); throwable = e; } finally { doComplete(moreImportant(throwable, this.throwable)); } return right; } private static Throwable moreImportant(Throwable throwable1, Throwable throwable2) { Throwable result; if (throwable1 == null) { result = throwable2; } else if (throwable2 == null) { result = throwable1; } else { // any exception wins over PCE because the latter can be induced by canceled indicator because of the former result = throwable1 instanceof ProcessCanceledException ? throwable2 : throwable1; } return result; } private void doComplete(Throwable throwable) { ApplierCompleter<T> a = this; ApplierCompleter<T> child = a; while (true) { // update parent.throwable in a thread safe way Throwable oldThrowable; Throwable newThrowable; do { oldThrowable = a.throwable; newThrowable = moreImportant(oldThrowable, throwable); } while (oldThrowable != newThrowable && !throwableUpdater.compareAndSet(a, oldThrowable, newThrowable)); throwable = newThrowable; if (a.getPendingCount() == 0) { // currently avoid using onExceptionalCompletion since it leaks exceptions via ForkJoinTask.exceptionTable a.onCompletion(child); //a.onExceptionalCompletion(throwable, child); child = a; //noinspection unchecked a = (ApplierCompleter)a.getCompleter(); if (a == null) { // currently avoid using completeExceptionally since it leaks exceptions via ForkJoinTask.exceptionTable child.quietlyComplete(); break; } } else if (a.decrementPendingCountUnlessZero() != 0) { break; } } } private void cancelProgress() { if (!progressIndicator.isCanceled()) { progressIndicator.cancel(); } } // tries to unfork, execute and re-link subtasks private Throwable tryToExecAllList() { ApplierCompleter<T> right = this; Throwable result = throwable; while (right != null) { if (right.tryUnfork()) { right.execAndForkSubTasks(); result = moreImportant(result, right.throwable); } right = right.next; } return result; } boolean completeTaskWhichFailToAcquireReadAction() { final boolean[] result = {true}; // these tasks could not be executed in the other thread; do them here for (final ApplierCompleter<T> task : failedSubTasks) { ApplicationManager.getApplication().runReadAction(() -> task.wrapInReadActionAndIndicator(() -> { for (int i = task.lo; i < task.hi; ++i) { if (!task.processor.process(task.array.get(i))) { result[0] = false; break; } } })); } return result[0]; } @Override public String toString() { return "("+lo+"-"+hi+")"+(getCompleter() == null ? "" : " parent: "+getCompleter()); } }