2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.project;
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.*;
21 import com.intellij.openapi.application.impl.ApplicationImpl;
22 import com.intellij.openapi.components.ServiceManager;
23 import com.intellij.openapi.diagnostic.Attachment;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
26 import com.intellij.openapi.progress.*;
27 import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
28 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
29 import com.intellij.openapi.startup.StartupManager;
30 import com.intellij.openapi.ui.MessageType;
31 import com.intellij.openapi.util.*;
32 import com.intellij.openapi.wm.AppIconScheme;
33 import com.intellij.openapi.wm.IdeFrame;
34 import com.intellij.openapi.wm.WindowManager;
35 import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
36 import com.intellij.openapi.wm.ex.StatusBarEx;
37 import com.intellij.ui.AppIcon;
38 import com.intellij.util.ExceptionUtil;
39 import com.intellij.util.concurrency.Semaphore;
40 import com.intellij.util.containers.ContainerUtil;
41 import com.intellij.util.containers.Queue;
42 import com.intellij.util.io.storage.HeavyProcessLatch;
43 import com.intellij.util.ui.UIUtil;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46 import org.jetbrains.annotations.TestOnly;
49 import java.util.ArrayList;
50 import java.util.HashSet;
53 import java.util.concurrent.atomic.AtomicReference;
55 public class DumbServiceImpl extends DumbService implements Disposable, ModificationTracker {
56 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl");
57 private static final NotNullLazyValue<DumbPermissionServiceImpl> ourPermissionService = NotNullLazyValue.createValue(
58 () -> (DumbPermissionServiceImpl)ServiceManager.getService(DumbPermissionService.class));
59 private static Throwable ourForcedTrace;
60 private final AtomicReference<State> myState = new AtomicReference<>(State.SMART);
61 private volatile Throwable myDumbStart;
62 private volatile TransactionId myDumbStartTransaction;
63 private final DumbModeListener myPublisher;
64 private long myModificationCount;
65 private final Set<Object> myQueuedEquivalences = new HashSet<>();
66 private final Queue<DumbModeTask> myUpdatesQueue = new Queue<>(5);
69 * Per-task progress indicators. Modified from EDT only.
70 * The task is removed from this map after it's finished or when the project is disposed.
72 private final Map<DumbModeTask, ProgressIndicatorEx> myProgresses = ContainerUtil.newConcurrentMap();
74 private final Queue<Runnable> myRunWhenSmartQueue = new Queue<>(5);
75 private final Project myProject;
76 private final ThreadLocal<Integer> myAlternativeResolution = new ThreadLocal<>();
78 public DumbServiceImpl(Project project) {
80 myPublisher = project.getMessageBus().syncPublisher(DUMB_MODE);
83 @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
84 public static DumbServiceImpl getInstance(@NotNull Project project) {
85 return (DumbServiceImpl)DumbService.getInstance(project);
89 public void cancelTask(@NotNull DumbModeTask task) {
90 if (ApplicationManager.getApplication().isInternal()) LOG.info("cancel " + task);
91 ProgressIndicatorEx indicator = myProgresses.get(task);
92 if (indicator != null) {
98 public void dispose() {
99 ApplicationManager.getApplication().assertIsDispatchThread();
100 myUpdatesQueue.clear();
101 myQueuedEquivalences.clear();
102 myRunWhenSmartQueue.clear();
103 for (DumbModeTask task : new ArrayList<>(myProgresses.keySet())) {
105 Disposer.dispose(task);
110 public Project getProject() {
115 public boolean isAlternativeResolveEnabled() {
116 return myAlternativeResolution.get() != null;
120 public void setAlternativeResolveEnabled(boolean enabled) {
121 Integer oldValue = myAlternativeResolution.get();
122 int newValue = (oldValue == null ? 0 : oldValue) + (enabled ? 1 : -1);
123 assert newValue >= 0 : "Non-paired alternative resolution mode";
124 myAlternativeResolution.set(newValue == 0 ? null : newValue);
128 public ModificationTracker getModificationTracker() {
133 public boolean isDumb() {
134 return myState.get() != State.SMART;
138 public void setDumb(boolean dumb) {
140 myState.set(State.RUNNING_DUMB_TASKS);
141 myPublisher.enteredDumbMode();
144 myState.set(State.WAITING_FOR_FINISH);
145 updateFinished(true);
150 public void runWhenSmart(@NotNull Runnable runnable) {
151 StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> {
152 synchronized (myRunWhenSmartQueue) {
154 myRunWhenSmartQueue.addLast(runnable);
164 public void queueTask(@NotNull final DumbModeTask task) {
165 TransactionId contextTransaction = TransactionGuard.getInstance().getContextTransaction();
167 final Throwable trace = ourForcedTrace != null ? ourForcedTrace : new Throwable(); // please report exceptions here to peter
168 if (LOG.isDebugEnabled()) LOG.debug("Scheduling task " + task);
169 final Application application = ApplicationManager.getApplication();
171 if (application.isUnitTestMode() || application.isHeadlessEnvironment()) {
172 final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
173 if (indicator != null) {
174 indicator.pushState();
176 AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing task");
178 task.performInDumbMode(indicator != null ? indicator : new EmptyProgressIndicator());
182 if (indicator != null) {
183 indicator.popState();
185 Disposer.dispose(task);
190 Runnable runnable = () -> queueTaskOnEdt(task, contextTransaction, trace);
191 if (application.isDispatchThread()) {
192 runnable.run(); // will log errors if not already in a write-safe context
194 TransactionGuard.submitTransaction(myProject, runnable);
198 private void queueTaskOnEdt(@NotNull DumbModeTask task,
199 @Nullable TransactionId contextTransaction,
200 @NotNull Throwable trace) {
201 if (!addTaskToQueue(task)) return;
203 if (myState.get() == State.SMART || myState.get() == State.WAITING_FOR_FINISH) {
204 WriteAction.run(() -> enterDumbMode(contextTransaction, trace));
205 ApplicationManager.getApplication().invokeLater(this::startBackgroundProcess);
209 private boolean addTaskToQueue(@NotNull DumbModeTask task) {
210 if (!myQueuedEquivalences.add(task.getEquivalenceObject())) {
211 Disposer.dispose(task);
215 myProgresses.put(task, new ProgressIndicatorBase());
216 Disposer.register(task, () -> {
217 ApplicationManager.getApplication().assertIsDispatchThread();
218 myProgresses.remove(task);
220 myUpdatesQueue.addLast(task);
224 private void enterDumbMode(@Nullable TransactionId contextTransaction, @NotNull Throwable trace) {
225 boolean wasSmart = !isDumb();
226 synchronized (myRunWhenSmartQueue) {
227 myState.set(State.SCHEDULED_TASKS);
230 myDumbStartTransaction = contextTransaction;
231 myModificationCount++;
234 myPublisher.enteredDumbMode();
236 catch (Throwable e) {
243 public static DumbModePermission getExplicitPermission() {
244 return ourPermissionService.getValue().getPermission();
248 public static AccessToken forceDumbModeStartTrace(@NotNull Throwable trace) {
249 ApplicationManager.getApplication().assertIsDispatchThread();
250 final Throwable prev = ourForcedTrace;
251 ourForcedTrace = trace;
252 return new AccessToken() {
254 public void finish() {
255 //noinspection AssignmentToStaticFieldFromInstanceMethod
256 ourForcedTrace = prev;
261 private void queueUpdateFinished(boolean modal) {
262 if (myState.compareAndSet(State.RUNNING_DUMB_TASKS, State.WAITING_FOR_FINISH)) {
263 StartupManager.getInstance(myProject).runWhenProjectIsInitialized(
264 () -> TransactionGuard.getInstance().submitTransaction(myProject, myDumbStartTransaction, () ->
266 () -> updateFinished(modal))));
270 private void updateFinished(boolean modal) {
271 synchronized (myRunWhenSmartQueue) {
272 if (!myState.compareAndSet(State.WAITING_FOR_FINISH, State.SMART)) {
277 myModificationCount++;
278 if (myProject.isDisposed()) return;
280 if (ApplicationManager.getApplication().isInternal()) LOG.info("updateFinished");
282 // some listeners might start yet another dumb mode
283 // allow that whatever the current modality is, because it won't harm anyone
284 allowStartingDumbModeInside(modal ? DumbModePermission.MAY_START_MODAL : DumbModePermission.MAY_START_BACKGROUND,
285 this::notifyUpdateFinished);
288 private void notifyUpdateFinished() {
290 myPublisher.exitDumbMode();
291 FileEditorManagerEx.getInstanceEx(myProject).refreshIcons();
294 // It may happen that one of the pending runWhenSmart actions triggers new dumb mode;
295 // in this case we should quit processing pending actions and postpone them until the newly started dumb mode finishes.
297 final Runnable runnable;
298 synchronized (myRunWhenSmartQueue) {
299 if (myRunWhenSmartQueue.isEmpty()) {
302 runnable = myRunWhenSmartQueue.pullFirst();
307 catch (ProcessCanceledException e) {
308 LOG.error("Task canceled: " + runnable, new Attachment("pce.trace", ExceptionUtil.getThrowableText(e)));
310 catch (Throwable e) {
311 LOG.error("Error executing task " + runnable, e);
318 public void showDumbModeNotification(@NotNull final String message) {
319 UIUtil.invokeLaterIfNeeded(() -> {
320 final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject);
321 if (ideFrame != null) {
322 StatusBarEx statusBar = (StatusBarEx)ideFrame.getStatusBar();
323 statusBar.notifyProgressByBalloon(MessageType.WARNING, message, null, null);
329 public void waitForSmartMode() {
334 final Application application = ApplicationManager.getApplication();
335 if (application.isReadAccessAllowed() || application.isDispatchThread()) {
336 throw new AssertionError("Don't invoke waitForSmartMode from inside read action in dumb mode");
339 final Semaphore semaphore = new Semaphore();
341 runWhenSmart(semaphore::up);
343 if (semaphore.waitFor(50)) {
346 ProgressManager.checkCanceled();
351 public JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) {
352 final DumbUnawareHider wrapper = new DumbUnawareHider(dumbUnawareContent);
353 wrapper.setContentVisible(!isDumb());
354 getProject().getMessageBus().connect(parentDisposable).subscribe(DUMB_MODE, new DumbModeListener() {
357 public void enteredDumbMode() {
358 wrapper.setContentVisible(false);
362 public void exitDumbMode() {
363 wrapper.setContentVisible(true);
371 public void smartInvokeLater(@NotNull final Runnable runnable) {
372 smartInvokeLater(runnable, ModalityState.defaultModalityState());
376 public void smartInvokeLater(@NotNull final Runnable runnable, @NotNull ModalityState modalityState) {
377 ApplicationManager.getApplication().invokeLater(() -> {
379 runWhenSmart(() -> smartInvokeLater(runnable, modalityState));
383 }, modalityState, myProject.getDisposed());
387 public void completeJustSubmittedTasks() {
388 assert myProject.isInitialized();
389 if (myState.get() != State.SCHEDULED_TASKS) {
398 private void showModalProgress() {
400 ((ApplicationImpl)ApplicationManager.getApplication()).executeSuspendingWriteAction(myProject, IdeBundle.message("progress.indexing"), () ->
401 runBackgroundProcess(ProgressManager.getInstance().getProgressIndicator(), true));
404 if (myState.get() != State.SMART) {
405 if (myState.get() != State.WAITING_FOR_FINISH) throw new AssertionError(myState.get());
406 WriteAction.run(() -> updateFinished(true));
411 private void startBackgroundProcess() {
413 ProgressManager.getInstance().run(new Task.Backgroundable(myProject, IdeBundle.message("progress.indexing"), false) {
415 public void run(@NotNull final ProgressIndicator visibleIndicator) {
416 runBackgroundProcess(visibleIndicator, false);
420 catch (Throwable e) {
421 queueUpdateFinished(false);
422 LOG.error("Failed to start background index update task", e);
426 private void runBackgroundProcess(@NotNull final ProgressIndicator visibleIndicator, boolean modal) {
427 if (!myState.compareAndSet(State.SCHEDULED_TASKS, State.RUNNING_DUMB_TASKS)) return;
429 final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance();
430 final Thread self = Thread.currentThread();
431 try (AccessToken ignored = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing tasks")) {
432 shutdownTracker.registerStopperThread(self);
434 if (visibleIndicator instanceof ProgressIndicatorEx) {
435 ((ProgressIndicatorEx)visibleIndicator).addStateDelegate(new AppIconProgress());
438 DumbModeTask task = null;
440 Pair<DumbModeTask, ProgressIndicatorEx> pair = getNextTask(task, modal);
441 if (pair == null) break;
444 ProgressIndicatorEx taskIndicator = pair.second;
445 if (visibleIndicator instanceof ProgressIndicatorEx) {
446 taskIndicator.addStateDelegate(new AbstractProgressIndicatorExBase() {
448 protected void delegateProgressChange(@NotNull IndicatorAction action) {
449 super.delegateProgressChange(action);
450 action.execute((ProgressIndicatorEx)visibleIndicator);
454 runSingleTask(task, taskIndicator);
457 catch (Throwable unexpected) {
458 LOG.error(unexpected);
461 shutdownTracker.unregisterStopperThread(self);
465 private static void runSingleTask(final DumbModeTask task, final ProgressIndicatorEx taskIndicator) {
466 if (ApplicationManager.getApplication().isInternal()) LOG.info("Running dumb mode task: " + task);
468 // nested runProcess is needed for taskIndicator to be honored in ProgressManager.checkCanceled calls deep inside tasks
469 ProgressManager.getInstance().runProcess(() -> {
471 taskIndicator.checkCanceled();
473 taskIndicator.setIndeterminate(true);
474 taskIndicator.setText(IdeBundle.message("progress.indexing.scanning"));
476 task.performInDumbMode(taskIndicator);
478 catch (ProcessCanceledException ignored) {
480 catch (Throwable unexpected) {
481 LOG.error(unexpected);
486 @Nullable private Pair<DumbModeTask, ProgressIndicatorEx> getNextTask(@Nullable final DumbModeTask prevTask, final boolean modal) {
487 final Ref<Pair<DumbModeTask, ProgressIndicatorEx>> result = Ref.create();
488 invokeAndWaitIfNeeded(() -> {
489 if (myProject.isDisposed()) return;
490 if (prevTask != null) {
491 Disposer.dispose(prevTask);
495 if (myUpdatesQueue.isEmpty()) {
496 queueUpdateFinished(modal);
500 DumbModeTask queuedTask = myUpdatesQueue.pullFirst();
501 myQueuedEquivalences.remove(queuedTask.getEquivalenceObject());
502 ProgressIndicatorEx indicator = myProgresses.get(queuedTask);
503 if (indicator.isCanceled()) {
504 Disposer.dispose(queuedTask);
508 result.set(Pair.create(queuedTask, indicator));
515 private static void invokeAndWaitIfNeeded(Runnable runnable) {
516 Application app = ApplicationManager.getApplication();
517 if (app.isDispatchThread()) {
522 Semaphore semaphore = new Semaphore();
524 app.invokeLater(() -> {
530 }, ModalityState.any());
534 catch (ProcessCanceledException ignore) {
539 public long getModificationCount() {
540 return myModificationCount;
544 public Throwable getDumbModeStartTrace() {
548 private class AppIconProgress extends ProgressIndicatorBase {
549 private double lastFraction;
552 public void setFraction(final double fraction) {
553 if (fraction - lastFraction < 0.01d) return;
554 lastFraction = fraction;
555 UIUtil.invokeLaterIfNeeded(
556 () -> AppIcon.getInstance().setProgress(myProject, "indexUpdate", AppIconScheme.Progress.INDEXING, fraction, true));
560 public void finish(@NotNull TaskInfo task) {
561 if (lastFraction != 0) { // we should call setProgress at least once before
562 UIUtil.invokeLaterIfNeeded(() -> {
563 AppIcon appIcon = AppIcon.getInstance();
564 if (appIcon.hideProgress(myProject, "indexUpdate")) {
565 appIcon.requestAttention(myProject, false);
566 appIcon.setOkBadge(myProject, true);
574 /** Non-dumb mode. For all other states, {@link #isDumb()} returns true. */
578 * A state between entering dumb mode ({@link #queueTaskOnEdt}) and actually starting the background progress later ({@link #runBackgroundProcess}).
579 * In this state, it's possible to call {@link #completeJustSubmittedTasks()} and perform all submitted the tasks modally.
580 * This state can happen after {@link #SMART} or {@link #WAITING_FOR_FINISH}. Followed by {@link #RUNNING_DUMB_TASKS}.
585 * Indicates that a background thread is currently executing dumb tasks.
590 * Set after background execution ({@link #RUNNING_DUMB_TASKS}) finishes, until the dumb mode can be exited
591 * (in a write-safe context on EDT when project is initialized). If new tasks are queued at this state, it's switched to {@link #SCHEDULED_TASKS}.