/* * 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.openapi.progress; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; import com.intellij.openapi.progress.impl.ProgressManagerImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Consumer; import com.intellij.util.concurrency.QueueProcessor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import static com.intellij.util.concurrency.QueueProcessor.ThreadToUse; /** * Runs backgroundable tasks one by one. * To add a task to the queue use {@link #run(Task.Backgroundable)} * BackgroundTaskQueue may have a title - this title will be used if the task which is currently running doesn't have a title. */ @SomeQueue public class BackgroundTaskQueue { @NotNull protected final String myTitle; @NotNull protected final QueueProcessor<TaskData> myProcessor; @NotNull private final Object TEST_TASK_LOCK = new Object(); private volatile boolean myForceAsyncInTests = false; public BackgroundTaskQueue(@Nullable Project project, @NotNull String title) { myTitle = title; Condition disposeCondition = project != null ? project.getDisposed() : ApplicationManager.getApplication().getDisposed(); myProcessor = new QueueProcessor<>(TaskData::consume, true, ThreadToUse.AWT, disposeCondition); } public void clear() { myProcessor.clear(); } public boolean isEmpty() { return myProcessor.isEmpty(); } public void waitForTasksToFinish() { myProcessor.waitFor(); } public void run(@NotNull Task.Backgroundable task) { run(task, null, null); } public void run(@NotNull Task.Backgroundable task, @Nullable ModalityState modalityState, @Nullable ProgressIndicator indicator) { BackgroundableTaskData taskData = new BackgroundableTaskData(task, modalityState, indicator); if (!myForceAsyncInTests && ApplicationManager.getApplication().isUnitTestMode()) { runTaskInCurrentThread(taskData); } else { myProcessor.add(taskData, modalityState); } } @TestOnly public void setForceAsyncInTests(boolean value, @Nullable Disposable disposable) { myForceAsyncInTests = value; if (disposable != null) { Disposer.register(disposable, new Disposable() { @Override public void dispose() { myForceAsyncInTests = false; } }); } } private void runTaskInCurrentThread(@NotNull BackgroundableTaskData data) { Task.Backgroundable task = data.myTask; ProgressIndicator indicator = data.myIndicator; if (indicator == null) indicator = new EmptyProgressIndicator(); ModalityState modalityState = data.myModalityState; if (modalityState == null) modalityState = ModalityState.NON_MODAL; ProgressManagerImpl pm = (ProgressManagerImpl)ProgressManager.getInstance(); // prohibit simultaneous execution from different threads synchronized (TEST_TASK_LOCK) { pm.runProcessWithProgressInCurrentThread(task, indicator, modalityState); } } protected interface TaskData extends Consumer<Runnable> { } protected class BackgroundableTaskData implements TaskData { @NotNull private final Task.Backgroundable myTask; @Nullable private final ModalityState myModalityState; @Nullable private final ProgressIndicator myIndicator; public BackgroundableTaskData(@NotNull Task.Backgroundable task, @Nullable ModalityState modalityState, @Nullable ProgressIndicator indicator) { myTask = task; myModalityState = modalityState; myIndicator = indicator; } @Override public void consume(@NotNull Runnable continuation) { Task.Backgroundable task = myTask; ProgressIndicator indicator = myIndicator; if (indicator == null) { if (ApplicationManager.getApplication().isHeadlessEnvironment()) { indicator = new EmptyProgressIndicator(); } else { // BackgroundableProcessIndicator should be created from EDT indicator = new BackgroundableProcessIndicator(task); } } ModalityState modalityState = myModalityState; if (modalityState == null) modalityState = ModalityState.NON_MODAL; if (StringUtil.isEmptyOrSpaces(task.getTitle())) { task.setTitle(myTitle); } boolean synchronous = (task.isHeadless() && !myForceAsyncInTests) || (task.isConditionalModal() && !task.shouldStartInBackground()); ProgressManagerImpl pm = (ProgressManagerImpl)ProgressManager.getInstance(); if (synchronous) { try { pm.runProcessWithProgressSynchronously(task, null); } finally { continuation.run(); } } else { pm.runProcessWithProgressAsynchronously(task, indicator, continuation, modalityState); } } } }