All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.concurrency.JobLauncherImpl Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition platform-impl library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2015 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.Patches;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.AbstractProgressIndicatorBase;
import com.intellij.util.Consumer;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import jsr166e.ForkJoinPool;
import jsr166e.ForkJoinTask;
import jsr166e.ForkJoinWorkerThread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author cdr
 */
public class JobLauncherImpl extends JobLauncher {
  private static final AtomicLong bits = new AtomicLong();
  static {
    assert Patches.USE_REFLECTION_TO_ACCESS_JDK8 : "Please port to java.util.concurrent.ForkJoinPool";
  }
  private static final ForkJoinPool.ForkJoinWorkerThreadFactory FACTORY = new ForkJoinPool.ForkJoinWorkerThreadFactory() {
    @Override
    public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
      final int n = addThread();
      ForkJoinWorkerThread thread = new ForkJoinWorkerThread(pool) {
        @Override
        protected void onTermination(Throwable exception) {
          finishThread(n);
          super.onTermination(exception);
        }
      };
      thread.setName("JobScheduler FJ pool "+ n +"/"+ JobSchedulerImpl.CORES_COUNT);
      return thread;
    }

    private int addThread() {
      boolean set;
      int n;
      do {
        long l = bits.longValue();
        long next = (l + 1) | l;
        n = Long.numberOfTrailingZeros(l + 1);
        set = bits.compareAndSet(l, next);
      } while (!set);
      return n;
    }
    private void finishThread(int n) {
      boolean set;
      do {
        long l = bits.get();
        long next = l & ~(1L << n);
        set = bits.compareAndSet(l, next);
      } while (!set);
    }
  };

  private static final ForkJoinPool pool = new ForkJoinPool(JobSchedulerImpl.CORES_COUNT, FACTORY, null, false);
  static final int CORES_FORK_THRESHOLD = 1;

  @Override
  public  boolean invokeConcurrentlyUnderProgress(@NotNull final List things,
                                                     ProgressIndicator progress,
                                                     boolean runInReadAction,
                                                     boolean failFastOnAcquireReadAction,
                                                     @NotNull final Processor thingProcessor) throws ProcessCanceledException {
    // supply our own indicator even if we haven't given one - to support cancellation
    final ProgressIndicator wrapper = progress == null ? new AbstractProgressIndicatorBase() : new SensitiveProgressWrapper(progress);

    Boolean result = processImmediatelyIfTooFew(things, wrapper, runInReadAction, thingProcessor);
    if (result != null) return result.booleanValue();

    ApplierCompleter applier = new ApplierCompleter(null, runInReadAction, wrapper, things, thingProcessor, 0, things.size(), null);
    try {
      pool.invoke(applier);
      if (applier.throwable != null) throw applier.throwable;
    }
    catch (ApplierCompleter.ComputationAbortedException e) {
      return false;
    }
    catch (ProcessCanceledException e) {
      // task1.processor returns false and the task cancels the indicator
      // then task2 calls checkCancel() and get here
      return false;
    }
    catch (RuntimeException e) {
      throw e;
    }
    catch (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  Boolean processImmediatelyIfTooFew(@NotNull final List things,
                                                        final ProgressIndicator progress,
                                                        boolean runInReadAction,
                                                        @NotNull final Processor 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) {
      final AtomicBoolean result = new AtomicBoolean(true);
      Runnable runnable = new Runnable() {
        @Override
        public void run() {
          ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() {
            @Override
            public void run() {
              //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  AsyncFuture invokeConcurrentlyUnderProgressAsync(@NotNull List things,
                                                                       ProgressIndicator progress,
                                                                       boolean failFastOnAcquireReadAction,
                                                                       @NotNull Processor thingProcessor) {
    return AsyncUtil.wrapBoolean(invokeConcurrentlyUnderProgress(things, progress, failFastOnAcquireReadAction, thingProcessor));
  }

  @NotNull
  @Override
  public Job submitToJobThread(@NotNull final Runnable action, @Nullable Consumer onDoneCallback) {
    VoidForkJoinTask task = new VoidForkJoinTask(action, onDoneCallback);
    pool.submit(task);
    return task;
  }

  private static class VoidForkJoinTask extends ForkJoinTask implements Job {
    private final Runnable myAction;
    private final Consumer myOnDoneCallback;

    private VoidForkJoinTask(@NotNull Runnable action, @Nullable Consumer onDoneCallback) {
      myAction = action;
      myOnDoneCallback = onDoneCallback;
    }

    @Override
    public Void getRawResult() {
      return null;
    }

    @Override
    protected void setRawResult(Void value) {
    }

    @Override
    protected boolean exec() {
      try {
        myAction.run();
        complete(null); // complete manually before calling callback
      }
      catch (Throwable throwable) {
        completeExceptionally(throwable);
      }
      finally {
        if (myOnDoneCallback != null) {
          myOnDoneCallback.consume(this);
        }
      }
      return true;
    }

    //////////////// Job
    @Override
    public String getTitle() {
      throw new IncorrectOperationException();
    }

    @Override
    public boolean isCanceled() {
      return isCancelled();
    }

    @Override
    public void addTask(@NotNull Callable 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 scheduleAndWaitForResults() throws Throwable {
      throw new IncorrectOperationException();
    }

    @Override
    public void cancel() {
      cancel(true);
    }

    @Override
    public void schedule() {
      throw new IncorrectOperationException();
    }

    @Override
    public void waitForCompletion(int millis) throws InterruptedException, ExecutionException, TimeoutException, CancellationException {
      get(millis, TimeUnit.MILLISECONDS);
    }
  }

  /**
   * Process all elements from the {@code failedToProcess} and then {@code things} concurrently in the underlying pool maintaining its load.
   * 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  boolean processQueue(@NotNull final BlockingQueue things,
                                  @NotNull final Queue failedToProcess,
                                  @NotNull final ProgressIndicator progress,
                                  @NotNull final T tombStone,
                                  @NotNull final Processor thingProcessor) {
    class MyTask implements Callable {
      private final int mySeq;
      private boolean result;

      private MyTask(int seq) {
        mySeq = seq;
      }

      @Override
      public Boolean call() throws Exception {
        ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() {
          @Override
          public void run() {
            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 (Error e) {
        throw e;
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    List> tasks = new ArrayList>();
    for (int i = 0; i < JobSchedulerImpl.CORES_COUNT; i++) {
      tasks.add(pool.submit(new MyTask(i)));
    }

    boolean result = true;
    RuntimeException exception = null;
    for (ForkJoinTask task : tasks) {
      try {
        result &= task.join();
      }
      catch (RuntimeException e) {
        exception = e;
      }
    }
    if (exception != null) {
      throw exception;
    }
    return result;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy