/* * 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.ex.ApplicationUtil; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.util.StandardProgressIndicatorBase; import com.intellij.util.Consumer; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; import com.intellij.util.io.storage.HeavyProcessLatch; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; /** * @author cdr */ public class JobLauncherImpl extends JobLauncher { static final int CORES_FORK_THRESHOLD = 1; @Override public <T> boolean invokeConcurrentlyUnderProgress(@NotNull final List<T> things, ProgressIndicator progress, boolean runInReadAction, boolean failFastOnAcquireReadAction, @NotNull final Processor<? super T> thingProcessor) throws ProcessCanceledException { // supply our own indicator even if we haven't given one - to support cancellation // use StandardProgressIndicator by default to avoid assertion in SensitiveProgressWrapper() ctr later final ProgressIndicator wrapper = progress == null ? new StandardProgressIndicatorBase() : new SensitiveProgressWrapper(progress); Boolean result = processImmediatelyIfTooFew(things, wrapper, runInReadAction, thingProcessor); if (result != null) return result.booleanValue(); HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); List<ApplierCompleter<T>> failedSubTasks = Collections.synchronizedList(new ArrayList<>()); ApplierCompleter<T> applier = new ApplierCompleter<>(null, runInReadAction, failFastOnAcquireReadAction, wrapper, things, thingProcessor, 0, things.size(), failedSubTasks, null); try { ForkJoinPool.commonPool().invoke(applier); if (applier.throwable != null) throw applier.throwable; } catch (ApplierCompleter.ComputationAbortedException e) { // one of the processors returned false return false; } catch (ApplicationUtil.CannotRunReadActionException e) { // failFastOnAcquireReadAction==true and one of the processors called runReadAction() during the pending write action throw e; } catch (ProcessCanceledException e) { // task1.processor returns false and the task cancels the indicator // then task2 calls checkCancel() and get here return false; } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new RuntimeException(e); } assert applier.isDone(); return applier.completeTaskWhichFailToAcquireReadAction(); } // if {@code things} are too few to be processed in the real pool, returns TRUE if processed successfully, FALSE if not // returns null if things need to be processed in the real pool private static <T> Boolean processImmediatelyIfTooFew(@NotNull final List<T> things, @NotNull final ProgressIndicator progress, boolean runInReadAction, @NotNull final Processor<? super T> thingProcessor) { // commit can be invoked from within write action //if (runInReadAction && ApplicationManager.getApplication().isWriteAccessAllowed()) { // throw new RuntimeException("Must not run invokeConcurrentlyUnderProgress() from under write action because of imminent deadlock"); //} if (things.isEmpty()) return true; if (things.size() <= 1 || JobSchedulerImpl.CORES_COUNT <= CORES_FORK_THRESHOLD || runInReadAction && ApplicationManager.getApplication().isWriteAccessAllowed() ) { final AtomicBoolean result = new AtomicBoolean(true); Runnable runnable = () -> ProgressManager.getInstance().executeProcessUnderProgress(() -> { //noinspection ForLoopReplaceableByForEach for (int i = 0; i < things.size(); i++) { T thing = things.get(i); if (!thingProcessor.process(thing)) { result.set(false); break; } } }, progress); if (runInReadAction) { if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(runnable)) return false; } else { runnable.run(); } return result.get(); } return null; } // This implementation is not really async @NotNull @Override public <T> AsyncFuture<Boolean> invokeConcurrentlyUnderProgressAsync(@NotNull List<T> things, ProgressIndicator progress, boolean failFastOnAcquireReadAction, @NotNull Processor<? super T> thingProcessor) { return AsyncUtil.wrapBoolean(invokeConcurrentlyUnderProgress(things, progress, failFastOnAcquireReadAction, thingProcessor)); } @NotNull @Override public Job<Void> submitToJobThread(@NotNull final Runnable action, @Nullable Consumer<Future> onDoneCallback) { VoidForkJoinTask task = new VoidForkJoinTask(action, onDoneCallback); task.submit(); return task; } private static class VoidForkJoinTask implements Job<Void> { private final Runnable myAction; private final Consumer<Future> myOnDoneCallback; private enum Status { STARTED, EXECUTED } // null=not yet executed, STARTED=started execution, EXECUTED=finished private volatile Status myStatus; private final ForkJoinTask<Void> myForkJoinTask = new ForkJoinTask<Void>() { @Override public Void getRawResult() { return null; } @Override protected void setRawResult(Void value) { } @Override protected boolean exec() { myStatus = Status.STARTED; try { myAction.run(); complete(null); // complete manually before calling callback } catch (Throwable throwable) { completeExceptionally(throwable); } finally { myStatus = Status.EXECUTED; if (myOnDoneCallback != null) { myOnDoneCallback.consume(this); } } return true; } }; private VoidForkJoinTask(@NotNull Runnable action, @Nullable Consumer<Future> onDoneCallback) { myAction = action; myOnDoneCallback = onDoneCallback; } private void submit() { ForkJoinPool.commonPool().submit(myForkJoinTask); } //////////////// Job // when canceled in the middle of the execution returns false until finished @Override public boolean isDone() { boolean wasCancelled = myForkJoinTask.isCancelled(); // must be before status check Status status = myStatus; return status == Status.EXECUTED || status == null && wasCancelled; } @Override public String getTitle() { throw new IncorrectOperationException(); } @Override public boolean isCanceled() { return myForkJoinTask.isCancelled(); } @Override public void addTask(@NotNull Callable<Void> task) { throw new IncorrectOperationException(); } @Override public void addTask(@NotNull Runnable task, Void result) { throw new IncorrectOperationException(); } @Override public void addTask(@NotNull Runnable task) { throw new IncorrectOperationException(); } @Override public List<Void> scheduleAndWaitForResults() throws Throwable { throw new IncorrectOperationException(); } @Override public void cancel() { myForkJoinTask.cancel(true); } @Override public void schedule() { throw new IncorrectOperationException(); } // waits for the job to finish execution (when called on a canceled job in the middle of the execution, wait for finish) @Override public void waitForCompletion(int millis) throws InterruptedException, ExecutionException, TimeoutException { HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); while (!isDone()) { try { myForkJoinTask.get(millis, TimeUnit.MILLISECONDS); break; } catch (CancellationException e) { // was canceled in the middle of execution // can't do anything but wait. help other tasks in the meantime if (!isDone()) { ForkJoinPool.commonPool().awaitQuiescence(millis, TimeUnit.MILLISECONDS); } } } } } /** * Process all elements from the {@code failedToProcess} and then {@code things} concurrently in the underlying pool. * Processing happens concurrently maintaining {@code JobSchedulerImpl.CORES_COUNT} parallelism. * Stop when {@code tombStone} element is occurred. * If was unable to process some element, add it back to the {@code failedToProcess} queue. * @return true if all elements processed successfully, false if at least one processor returned false or exception occurred */ public <T> boolean processQueue(@NotNull final BlockingQueue<T> things, @NotNull final Queue<T> failedToProcess, @NotNull final ProgressIndicator progress, @NotNull final T tombStone, @NotNull final Processor<? super T> thingProcessor) { class MyTask implements Callable<Boolean> { private final int mySeq; private boolean result; private MyTask(int seq) { mySeq = seq; } @Override public Boolean call() throws Exception { ProgressManager.getInstance().executeProcessUnderProgress(() -> { try { while (true) { progress.checkCanceled(); T element = failedToProcess.poll(); if (element == null) element = things.take(); if (element == tombStone) { things.offer(element); result = true; break; } try { if (!thingProcessor.process(element)) { result = false; break; } } catch (RuntimeException e) { failedToProcess.add(element); throw e; } } } catch (InterruptedException e) { throw new RuntimeException(e); } }, progress); return result; } @Override public String toString() { return super.toString() + " seq="+mySeq; } } boolean isSmallEnough = things.contains(tombStone); if (isSmallEnough) { try { // do not distribute for small queues return new MyTask(0).call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } List<ForkJoinTask<Boolean>> tasks = new ArrayList<>(); for (int i = 0; i < JobSchedulerImpl.CORES_COUNT; i++) { tasks.add(ForkJoinPool.commonPool().submit(new MyTask(i))); } boolean result = true; RuntimeException exception = null; for (ForkJoinTask<Boolean> task : tasks) { try { result &= task.join(); } catch (RuntimeException e) { exception = e; } } if (exception != null) { throw exception; } return result; } }