<commit_before>/*
 * 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.openapi.progress.util;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationEx;
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.util.EmptyRunnable;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ui.EdtInvocationManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.PooledThreadExecutor;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;

/**
 * Methods in this class are used to equip long background processes which take read actions with a special listener
 * that fires when a write action is about to begin, and cancels corresponding progress indicators to avoid blocking the UI.
 * These processes should be ready to get {@link ProcessCanceledException} at any moment.
 * Processes may want to react on cancellation event by restarting the activity, see
 * {@link ReadTask#onCanceled(ProgressIndicator)} for that.
 *
 * @author gregsh
 */
public class ProgressIndicatorUtils {
  private ProgressIndicatorUtils() {
  }

  @NotNull
  public static ProgressIndicator forceWriteActionPriority(@NotNull ProgressIndicator progress, @NotNull Disposable parentDisposable) {
    ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() {
        @Override
        public void beforeWriteActionStart(@NotNull Object action) {
          if (progress.isRunning()) {
            progress.cancel();
          }
        }
      }, parentDisposable);
    return progress;
  }

  @NotNull
  public static Future<?> submitWithWriteActionPriority(@NotNull ReadTask task) {
    return scheduleWithWriteActionPriority(new ProgressIndicatorBase(), task);
  }

  public static void scheduleWithWriteActionPriority(@NotNull ReadTask task) {
    submitWithWriteActionPriority(task);
  }

  @NotNull
  public static Future<?> scheduleWithWriteActionPriority(@NotNull ProgressIndicator progressIndicator, @NotNull ReadTask readTask) {
    return scheduleWithWriteActionPriority(progressIndicator, PooledThreadExecutor.INSTANCE, readTask);
  }

  @NotNull
  public static Future<?> scheduleWithWriteActionPriority(@NotNull Executor executor, @NotNull ReadTask task) {
    return scheduleWithWriteActionPriority(new ProgressIndicatorBase(), executor, task);
  }

  /**
   * Same as {@link #runInReadActionWithWriteActionPriority(Runnable)}, optionally allowing to pass a {@link ProgressIndicatorUtils}
   * instance, which can be used to cancel action externally.
   */
  public static boolean runInReadActionWithWriteActionPriority(@NotNull final Runnable action, 
                                                               @Nullable ProgressIndicator progressIndicator) {
    final Ref<Boolean> result = new Ref<>(Boolean.FALSE);
    runWithWriteActionPriority(() -> result.set(ApplicationManagerEx.getApplicationEx().tryRunReadAction(action)),
                               progressIndicator == null ? new ProgressIndicatorBase() : progressIndicator);
    return result.get();
  }

  /**
   * This method attempts to run provided action synchronously in a read action, so that, if possible, it wouldn't impact any pending, 
   * executing or future write actions (for this to work effectively the action should invoke {@link ProgressManager#checkCanceled()} or 
   * {@link ProgressIndicator#checkCanceled()} often enough). 
   * It returns {@code true} if action was executed successfully. It returns {@code false} if the action was not
   * executed successfully, i.e. if:
   * <ul>
   * <li>write action was in progress when the method was called</li>
   * <li>write action was pending when the method was called</li>
   * <li>action started to execute, but was aborted using {@link ProcessCanceledException} when some other thread initiated 
   * write action</li>
   * </ul>
   * If caller needs to retry the invocation of this method in a loop, it should consider pausing between attempts, to avoid potential
   * 100% CPU usage.
   */
  public static boolean runInReadActionWithWriteActionPriority(@NotNull final Runnable action) {
    return runInReadActionWithWriteActionPriority(action, null);
  }

  public static boolean runWithWriteActionPriority(@NotNull Runnable action, @NotNull ProgressIndicator progressIndicator) {
    final ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication();
    if (application.isDispatchThread()) {
      throw new IllegalStateException("Must not call from EDT");
    }
    if (application.isWriteActionPending()) {
      // first catch: check if write action acquisition started: especially important when current thread has read action, because
      // tryRunReadAction below would just run without really checking if a write action is pending
      if (!progressIndicator.isCanceled()) progressIndicator.cancel();
      return false;
    }

    final ApplicationAdapter listener = new ApplicationAdapter() {
      @Override
      public void beforeWriteActionStart(@NotNull Object action) {
        if (!progressIndicator.isCanceled()) progressIndicator.cancel();
      }
    };

    boolean succeededWithAddingListener = application.tryRunReadAction(() -> {
      // Even if writeLock.lock() acquisition is in progress at this point then runProcess will block wanting read action which is
      // also ok as last resort.
      application.addApplicationListener(listener);
    });
    if (!succeededWithAddingListener) { // second catch: writeLock.lock() acquisition is in progress or already acquired
      if (!progressIndicator.isCanceled()) progressIndicator.cancel();
      return false;
    }
    final Ref<Boolean> wasCancelled = new Ref<>();
    try {
      ProgressManager.getInstance().runProcess(() -> {
        try {
          action.run();
        }
        catch (ProcessCanceledException ignore) {
          wasCancelled.set(Boolean.TRUE);
        }
      }, progressIndicator);
    }
    finally {
      application.removeApplicationListener(listener);
    }
    return wasCancelled.get() != Boolean.TRUE;
  }

  @NotNull
  public static Future<?> scheduleWithWriteActionPriority(@NotNull final ProgressIndicator progressIndicator,
                                                          @NotNull final Executor executor,
                                                          @NotNull final ReadTask readTask) {
    final Application application = ApplicationManager.getApplication();
    // invoke later even if on EDT
    // to avoid tasks eagerly restarting immediately, allocating many pooled threads
    // which get cancelled too soon when a next write action arrives in the same EDT batch
    // (can happen when processing multiple VFS events or writing multiple files on save)

    // use SwingUtilities instead of application.invokeLater
    // to tolerate any immediate modality changes (e.g. https://youtrack.jetbrains.com/issue/IDEA-135180)

    CompletableFuture<?> future = new CompletableFuture<>();
    //noinspection SSBasedInspection
    EdtInvocationManager.getInstance().invokeLater(() -> {
      if (application.isDisposed()) {
        future.complete(null);
        return;
      }
      final ApplicationAdapter listener = new ApplicationAdapter() {
        @Override
        public void beforeWriteActionStart(@NotNull Object action) {
          if (!progressIndicator.isCanceled()) {
            progressIndicator.cancel();
            readTask.onCanceled(progressIndicator);
          }
        }
      };
      application.addApplicationListener(listener);
      future.whenComplete((BiConsumer<Object, Throwable>)(o, throwable) -> application.removeApplicationListener(listener));
      try {
        executor.execute(new Runnable() {
          @Override
          public void run() {
            final ReadTask.Continuation continuation = runUnderProgress(progressIndicator, readTask);
            if (continuation == null) {
              future.complete(null);
            }
            else {
              application.invokeLater(new Runnable() {
                @Override
                public void run() {
                  try {
                    if (!progressIndicator.isCanceled()) {
                      continuation.getAction().run();<commit_msg>project leaks: keep a list of started "scan file system" futures and kill em all at once<commit_after>/*
 * 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.openapi.progress.util;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationEx;
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.util.EmptyRunnable;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ui.EdtInvocationManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.PooledThreadExecutor;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;

/**
 * Methods in this class are used to equip long background processes which take read actions with a special listener
 * that fires when a write action is about to begin, and cancels corresponding progress indicators to avoid blocking the UI.
 * These processes should be ready to get {@link ProcessCanceledException} at any moment.
 * Processes may want to react on cancellation event by restarting the activity, see
 * {@link ReadTask#onCanceled(ProgressIndicator)} for that.
 *
 * @author gregsh
 */
public class ProgressIndicatorUtils {
  private ProgressIndicatorUtils() {
  }

  @NotNull
  public static ProgressIndicator forceWriteActionPriority(@NotNull ProgressIndicator progress, @NotNull Disposable parentDisposable) {
    ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() {
        @Override
        public void beforeWriteActionStart(@NotNull Object action) {
          if (progress.isRunning()) {
            progress.cancel();
          }
        }
      }, parentDisposable);
    return progress;
  }

  @NotNull
  public static Future<?> submitWithWriteActionPriority(@NotNull ReadTask task) {
    return scheduleWithWriteActionPriority(new ProgressIndicatorBase(), task);
  }

  public static void scheduleWithWriteActionPriority(@NotNull ReadTask task) {
    submitWithWriteActionPriority(task);
  }

  @NotNull
  public static Future<?> scheduleWithWriteActionPriority(@NotNull ProgressIndicator progressIndicator, @NotNull ReadTask readTask) {
    return scheduleWithWriteActionPriority(progressIndicator, PooledThreadExecutor.INSTANCE, readTask);
  }

  @NotNull
  public static Future<?> scheduleWithWriteActionPriority(@NotNull Executor executor, @NotNull ReadTask task) {
    return scheduleWithWriteActionPriority(new ProgressIndicatorBase(), executor, task);
  }

  /**
   * Same as {@link #runInReadActionWithWriteActionPriority(Runnable)}, optionally allowing to pass a {@link ProgressIndicatorUtils}
   * instance, which can be used to cancel action externally.
   */
  public static boolean runInReadActionWithWriteActionPriority(@NotNull final Runnable action, 
                                                               @Nullable ProgressIndicator progressIndicator) {
    final Ref<Boolean> result = new Ref<>(Boolean.FALSE);
    runWithWriteActionPriority(() -> result.set(ApplicationManagerEx.getApplicationEx().tryRunReadAction(action)),
                               progressIndicator == null ? new ProgressIndicatorBase() : progressIndicator);
    return result.get();
  }

  /**
   * This method attempts to run provided action synchronously in a read action, so that, if possible, it wouldn't impact any pending, 
   * executing or future write actions (for this to work effectively the action should invoke {@link ProgressManager#checkCanceled()} or 
   * {@link ProgressIndicator#checkCanceled()} often enough). 
   * It returns {@code true} if action was executed successfully. It returns {@code false} if the action was not
   * executed successfully, i.e. if:
   * <ul>
   * <li>write action was in progress when the method was called</li>
   * <li>write action was pending when the method was called</li>
   * <li>action started to execute, but was aborted using {@link ProcessCanceledException} when some other thread initiated 
   * write action</li>
   * </ul>
   * If caller needs to retry the invocation of this method in a loop, it should consider pausing between attempts, to avoid potential
   * 100% CPU usage.
   */
  public static boolean runInReadActionWithWriteActionPriority(@NotNull final Runnable action) {
    return runInReadActionWithWriteActionPriority(action, null);
  }

  public static boolean runWithWriteActionPriority(@NotNull Runnable action, @NotNull ProgressIndicator progressIndicator) {
    final ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication();
    if (application.isDispatchThread()) {
      throw new IllegalStateException("Must not call from EDT");
    }
    if (application.isWriteActionPending()) {
      // first catch: check if write action acquisition started: especially important when current thread has read action, because
      // tryRunReadAction below would just run without really checking if a write action is pending
      if (!progressIndicator.isCanceled()) progressIndicator.cancel();
      return false;
    }

    final ApplicationAdapter listener = new ApplicationAdapter() {
      @Override
      public void beforeWriteActionStart(@NotNull Object action) {
        if (!progressIndicator.isCanceled()) progressIndicator.cancel();
      }
    };

    boolean succeededWithAddingListener = application.tryRunReadAction(() -> {
      // Even if writeLock.lock() acquisition is in progress at this point then runProcess will block wanting read action which is
      // also ok as last resort.
      application.addApplicationListener(listener);
    });
    if (!succeededWithAddingListener) { // second catch: writeLock.lock() acquisition is in progress or already acquired
      if (!progressIndicator.isCanceled()) progressIndicator.cancel();
      return false;
    }
    final Ref<Boolean> wasCancelled = new Ref<>();
    try {
      ProgressManager.getInstance().runProcess(() -> {
        try {
          action.run();
        }
        catch (ProcessCanceledException ignore) {
          wasCancelled.set(Boolean.TRUE);
        }
      }, progressIndicator);
    }
    finally {
      application.removeApplicationListener(listener);
    }
    return wasCancelled.get() != Boolean.TRUE;
  }

  @NotNull
  public static Future<?> scheduleWithWriteActionPriority(@NotNull final ProgressIndicator progressIndicator,
                                                          @NotNull final Executor executor,
                                                          @NotNull final ReadTask readTask) {
    final Application application = ApplicationManager.getApplication();
    // invoke later even if on EDT
    // to avoid tasks eagerly restarting immediately, allocating many pooled threads
    // which get cancelled too soon when a next write action arrives in the same EDT batch
    // (can happen when processing multiple VFS events or writing multiple files on save)

    // use SwingUtilities instead of application.invokeLater
    // to tolerate any immediate modality changes (e.g. https://youtrack.jetbrains.com/issue/IDEA-135180)

    CompletableFuture<?> future = new CompletableFuture<>();
    //noinspection SSBasedInspection
    EdtInvocationManager.getInstance().invokeLater(() -> {
      if (application.isDisposed() || progressIndicator.isCanceled()) {
        future.complete(null);
        return;
      }
      final ApplicationAdapter listener = new ApplicationAdapter() {
        @Override
        public void beforeWriteActionStart(@NotNull Object action) {
          if (!progressIndicator.isCanceled()) {
            progressIndicator.cancel();
            readTask.onCanceled(progressIndicator);
          }
        }
      };
      application.addApplicationListener(listener);
      future.whenComplete((BiConsumer<Object, Throwable>)(o, throwable) -> application.removeApplicationListener(listener));
      try {
        executor.execute(new Runnable() {
          @Override
          public void run() {
            final ReadTask.Continuation continuation;
            try {
              continuation = runUnderProgress(progressIndicator, readTask);
            }
            catch (Throwable e) {
              future.completeExceptionally(e);
              throw e;
            }
            if (continuation == null) {
              future.complete(null);
            }
            else {
              application.invokeLater(new Runnable() {
                @Override
                public void run() {
                  try {
                    if (!progressIndicator.isCanceled()) {
                      continuation.getAction().run();
{'fraction_non_alphanumeric': 0.08172448071992884, 'fraction_numerical': 0.002197457228064668, 'mean_word_length': 3.3970554405337015, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 51, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5905046', 'n_tokens_mistral': 4623, 'n_tokens_neox': 4594, 'n_words': 1476}