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.diagnostic.Attachment;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
25 import com.intellij.openapi.progress.*;
26 import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
27 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
28 import com.intellij.openapi.startup.StartupManager;
29 import com.intellij.openapi.ui.MessageType;
30 import com.intellij.openapi.util.*;
31 import com.intellij.openapi.wm.AppIconScheme;
32 import com.intellij.openapi.wm.IdeFrame;
33 import com.intellij.openapi.wm.WindowManager;
34 import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
35 import com.intellij.openapi.wm.ex.StatusBarEx;
36 import com.intellij.ui.AppIcon;
37 import com.intellij.util.ExceptionUtil;
38 import com.intellij.util.concurrency.Semaphore;
39 import com.intellij.util.containers.ContainerUtil;
40 import com.intellij.util.containers.Queue;
41 import com.intellij.util.io.storage.HeavyProcessLatch;
42 import com.intellij.util.ui.UIUtil;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45 import org.jetbrains.annotations.TestOnly;
48 import java.util.ArrayList;
49 import java.util.HashSet;
52 import java.util.concurrent.atomic.AtomicReference;
54 public class DumbServiceImpl extends DumbService implements Disposable, ModificationTracker {
55 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl");
56 private static Throwable ourForcedTrace;
57 private final AtomicReference<State> myState = new AtomicReference<>(State.SMART);
58 private volatile Throwable myDumbStart;
59 private volatile TransactionId myDumbStartTransaction;
60 private final DumbModeListener myPublisher;
61 private long myModificationCount;
62 private final Set<Object> myQueuedEquivalences = new HashSet<>();
63 private final Queue<DumbModeTask> myUpdatesQueue = new Queue<>(5);
66 * Per-task progress indicators. Modified from EDT only.
67 * The task is removed from this map after it's finished or when the project is disposed.
69 private final Map<DumbModeTask, ProgressIndicatorEx> myProgresses = ContainerUtil.newConcurrentMap();
71 private final Queue<Runnable> myRunWhenSmartQueue = new Queue<>(5);
72 private final Project myProject;
73 private final ThreadLocal<Integer> myAlternativeResolution = new ThreadLocal<>();
75 public DumbServiceImpl(Project project) {
77 myPublisher = project.getMessageBus().syncPublisher(DUMB_MODE);
80 @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
81 public static DumbServiceImpl getInstance(@NotNull Project project) {
82 return (DumbServiceImpl)DumbService.getInstance(project);
86 public void cancelTask(@NotNull DumbModeTask task) {
87 if (ApplicationManager.getApplication().isInternal()) LOG.info("cancel " + task);
88 ProgressIndicatorEx indicator = myProgresses.get(task);
89 if (indicator != null) {
95 public void dispose() {
96 ApplicationManager.getApplication().assertIsDispatchThread();
97 myUpdatesQueue.clear();
98 myQueuedEquivalences.clear();
99 myRunWhenSmartQueue.clear();
100 for (DumbModeTask task : new ArrayList<>(myProgresses.keySet())) {
102 Disposer.dispose(task);
107 public Project getProject() {
112 public boolean isAlternativeResolveEnabled() {
113 return myAlternativeResolution.get() != null;
117 public void setAlternativeResolveEnabled(boolean enabled) {
118 Integer oldValue = myAlternativeResolution.get();
119 int newValue = (oldValue == null ? 0 : oldValue) + (enabled ? 1 : -1);
120 assert newValue >= 0 : "Non-paired alternative resolution mode";
121 myAlternativeResolution.set(newValue == 0 ? null : newValue);
125 public ModificationTracker getModificationTracker() {
130 public boolean isDumb() {
131 return myState.get() != State.SMART;
135 public void setDumb(boolean dumb) {
137 myState.set(State.RUNNING_DUMB_TASKS);
138 myPublisher.enteredDumbMode();
141 myState.set(State.WAITING_FOR_FINISH);
147 public void runWhenSmart(@NotNull Runnable runnable) {
148 StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> {
149 synchronized (myRunWhenSmartQueue) {
151 myRunWhenSmartQueue.addLast(runnable);
161 public void queueTask(@NotNull final DumbModeTask task) {
162 TransactionId contextTransaction = TransactionGuard.getInstance().getContextTransaction();
164 final Throwable trace = ourForcedTrace != null ? ourForcedTrace : new Throwable(); // please report exceptions here to peter
165 if (LOG.isDebugEnabled()) LOG.debug("Scheduling task " + task);
166 final Application application = ApplicationManager.getApplication();
168 if (application.isUnitTestMode() || application.isHeadlessEnvironment()) {
169 final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
170 if (indicator != null) {
171 indicator.pushState();
173 AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing task");
175 task.performInDumbMode(indicator != null ? indicator : new EmptyProgressIndicator());
179 if (indicator != null) {
180 indicator.popState();
182 Disposer.dispose(task);
187 Runnable runnable = () -> queueTaskOnEdt(task, contextTransaction, trace);
188 if (application.isDispatchThread()) {
189 runnable.run(); // will log errors if not already in a write-safe context
191 TransactionGuard.submitTransaction(myProject, runnable);
195 private void queueTaskOnEdt(@NotNull DumbModeTask task,
196 @Nullable TransactionId contextTransaction,
197 @NotNull Throwable trace) {
198 if (!addTaskToQueue(task)) return;
200 if (myState.get() == State.SMART || myState.get() == State.WAITING_FOR_FINISH) {
201 WriteAction.run(() -> enterDumbMode(contextTransaction, trace));
202 ApplicationManager.getApplication().invokeLater(this::startBackgroundProcess);
206 private boolean addTaskToQueue(@NotNull DumbModeTask task) {
207 if (!myQueuedEquivalences.add(task.getEquivalenceObject())) {
208 Disposer.dispose(task);
212 myProgresses.put(task, new ProgressIndicatorBase());
213 Disposer.register(task, () -> {
214 ApplicationManager.getApplication().assertIsDispatchThread();
215 myProgresses.remove(task);
217 myUpdatesQueue.addLast(task);
221 private void enterDumbMode(@Nullable TransactionId contextTransaction, @NotNull Throwable trace) {
222 boolean wasSmart = !isDumb();
223 synchronized (myRunWhenSmartQueue) {
224 myState.set(State.SCHEDULED_TASKS);
227 myDumbStartTransaction = contextTransaction;
228 myModificationCount++;
231 myPublisher.enteredDumbMode();
233 catch (Throwable e) {
240 public static AccessToken forceDumbModeStartTrace(@NotNull Throwable trace) {
241 ApplicationManager.getApplication().assertIsDispatchThread();
242 final Throwable prev = ourForcedTrace;
243 ourForcedTrace = trace;
244 return new AccessToken() {
246 public void finish() {
247 //noinspection AssignmentToStaticFieldFromInstanceMethod
248 ourForcedTrace = prev;
253 private void queueUpdateFinished() {
254 if (myState.compareAndSet(State.RUNNING_DUMB_TASKS, State.WAITING_FOR_FINISH)) {
255 StartupManager.getInstance(myProject).runWhenProjectIsInitialized(
256 () -> TransactionGuard.getInstance().submitTransaction(myProject, myDumbStartTransaction, () ->
258 () -> updateFinished())));
262 private void updateFinished() {
263 synchronized (myRunWhenSmartQueue) {
264 if (!myState.compareAndSet(State.WAITING_FOR_FINISH, State.SMART)) {
269 myModificationCount++;
270 if (myProject.isDisposed()) return;
272 if (ApplicationManager.getApplication().isInternal()) LOG.info("updateFinished");
274 notifyUpdateFinished();
277 private void notifyUpdateFinished() {
279 myPublisher.exitDumbMode();
280 FileEditorManagerEx.getInstanceEx(myProject).refreshIcons();
283 // It may happen that one of the pending runWhenSmart actions triggers new dumb mode;
284 // in this case we should quit processing pending actions and postpone them until the newly started dumb mode finishes.
286 final Runnable runnable;
287 synchronized (myRunWhenSmartQueue) {
288 if (myRunWhenSmartQueue.isEmpty()) {
291 runnable = myRunWhenSmartQueue.pullFirst();
296 catch (ProcessCanceledException e) {
297 LOG.error("Task canceled: " + runnable, new Attachment("pce.trace", ExceptionUtil.getThrowableText(e)));
299 catch (Throwable e) {
300 LOG.error("Error executing task " + runnable, e);
307 public void showDumbModeNotification(@NotNull final String message) {
308 UIUtil.invokeLaterIfNeeded(() -> {
309 final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject);
310 if (ideFrame != null) {
311 StatusBarEx statusBar = (StatusBarEx)ideFrame.getStatusBar();
312 statusBar.notifyProgressByBalloon(MessageType.WARNING, message, null, null);
318 public void waitForSmartMode() {
323 final Application application = ApplicationManager.getApplication();
324 if (application.isReadAccessAllowed() || application.isDispatchThread()) {
325 throw new AssertionError("Don't invoke waitForSmartMode from inside read action in dumb mode");
328 final Semaphore semaphore = new Semaphore();
330 runWhenSmart(semaphore::up);
332 if (semaphore.waitFor(50)) {
335 ProgressManager.checkCanceled();
340 public JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) {
341 final DumbUnawareHider wrapper = new DumbUnawareHider(dumbUnawareContent);
342 wrapper.setContentVisible(!isDumb());
343 getProject().getMessageBus().connect(parentDisposable).subscribe(DUMB_MODE, new DumbModeListener() {
346 public void enteredDumbMode() {
347 wrapper.setContentVisible(false);
351 public void exitDumbMode() {
352 wrapper.setContentVisible(true);
360 public void smartInvokeLater(@NotNull final Runnable runnable) {
361 smartInvokeLater(runnable, ModalityState.defaultModalityState());
365 public void smartInvokeLater(@NotNull final Runnable runnable, @NotNull ModalityState modalityState) {
366 ApplicationManager.getApplication().invokeLater(() -> {
368 runWhenSmart(() -> smartInvokeLater(runnable, modalityState));
372 }, modalityState, myProject.getDisposed());
376 public void completeJustSubmittedTasks() {
377 assert myProject.isInitialized();
378 if (myState.get() != State.SCHEDULED_TASKS) {
387 private void showModalProgress() {
389 ((ApplicationImpl)ApplicationManager.getApplication()).executeSuspendingWriteAction(myProject, IdeBundle.message("progress.indexing"), () ->
390 runBackgroundProcess(ProgressManager.getInstance().getProgressIndicator()));
393 if (myState.get() != State.SMART) {
394 if (myState.get() != State.WAITING_FOR_FINISH) throw new AssertionError(myState.get());
395 WriteAction.run(() -> updateFinished());
400 private void startBackgroundProcess() {
402 ProgressManager.getInstance().run(new Task.Backgroundable(myProject, IdeBundle.message("progress.indexing"), false) {
404 public void run(@NotNull final ProgressIndicator visibleIndicator) {
405 runBackgroundProcess(visibleIndicator);
409 catch (Throwable e) {
410 queueUpdateFinished();
411 LOG.error("Failed to start background index update task", e);
415 private void runBackgroundProcess(@NotNull final ProgressIndicator visibleIndicator) {
416 if (!myState.compareAndSet(State.SCHEDULED_TASKS, State.RUNNING_DUMB_TASKS)) return;
418 final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance();
419 final Thread self = Thread.currentThread();
420 try (AccessToken ignored = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing tasks")) {
421 shutdownTracker.registerStopperThread(self);
423 if (visibleIndicator instanceof ProgressIndicatorEx) {
424 ((ProgressIndicatorEx)visibleIndicator).addStateDelegate(new AppIconProgress());
427 DumbModeTask task = null;
429 Pair<DumbModeTask, ProgressIndicatorEx> pair = getNextTask(task);
430 if (pair == null) break;
433 ProgressIndicatorEx taskIndicator = pair.second;
434 if (visibleIndicator instanceof ProgressIndicatorEx) {
435 taskIndicator.addStateDelegate(new AbstractProgressIndicatorExBase() {
437 protected void delegateProgressChange(@NotNull IndicatorAction action) {
438 super.delegateProgressChange(action);
439 action.execute((ProgressIndicatorEx)visibleIndicator);
443 runSingleTask(task, taskIndicator);
446 catch (Throwable unexpected) {
447 LOG.error(unexpected);
450 shutdownTracker.unregisterStopperThread(self);
454 private static void runSingleTask(final DumbModeTask task, final ProgressIndicatorEx taskIndicator) {
455 if (ApplicationManager.getApplication().isInternal()) LOG.info("Running dumb mode task: " + task);
457 // nested runProcess is needed for taskIndicator to be honored in ProgressManager.checkCanceled calls deep inside tasks
458 ProgressManager.getInstance().runProcess(() -> {
460 taskIndicator.checkCanceled();
462 taskIndicator.setIndeterminate(true);
463 taskIndicator.setText(IdeBundle.message("progress.indexing.scanning"));
465 task.performInDumbMode(taskIndicator);
467 catch (ProcessCanceledException ignored) {
469 catch (Throwable unexpected) {
470 LOG.error(unexpected);
475 @Nullable private Pair<DumbModeTask, ProgressIndicatorEx> getNextTask(@Nullable final DumbModeTask prevTask) {
476 final Ref<Pair<DumbModeTask, ProgressIndicatorEx>> result = Ref.create();
477 invokeAndWaitIfNeeded(() -> {
478 if (myProject.isDisposed()) return;
479 if (prevTask != null) {
480 Disposer.dispose(prevTask);
484 if (myUpdatesQueue.isEmpty()) {
485 queueUpdateFinished();
489 DumbModeTask queuedTask = myUpdatesQueue.pullFirst();
490 myQueuedEquivalences.remove(queuedTask.getEquivalenceObject());
491 ProgressIndicatorEx indicator = myProgresses.get(queuedTask);
492 if (indicator.isCanceled()) {
493 Disposer.dispose(queuedTask);
497 result.set(Pair.create(queuedTask, indicator));
504 private static void invokeAndWaitIfNeeded(Runnable runnable) {
505 Application app = ApplicationManager.getApplication();
506 if (app.isDispatchThread()) {
511 Semaphore semaphore = new Semaphore();
513 app.invokeLater(() -> {
519 }, ModalityState.any());
523 catch (ProcessCanceledException ignore) {
528 public long getModificationCount() {
529 return myModificationCount;
533 public Throwable getDumbModeStartTrace() {
537 private class AppIconProgress extends ProgressIndicatorBase {
538 private double lastFraction;
541 public void setFraction(final double fraction) {
542 if (fraction - lastFraction < 0.01d) return;
543 lastFraction = fraction;
544 UIUtil.invokeLaterIfNeeded(
545 () -> AppIcon.getInstance().setProgress(myProject, "indexUpdate", AppIconScheme.Progress.INDEXING, fraction, true));
549 public void finish(@NotNull TaskInfo task) {
550 if (lastFraction != 0) { // we should call setProgress at least once before
551 UIUtil.invokeLaterIfNeeded(() -> {
552 AppIcon appIcon = AppIcon.getInstance();
553 if (appIcon.hideProgress(myProject, "indexUpdate")) {
554 appIcon.requestAttention(myProject, false);
555 appIcon.setOkBadge(myProject, true);
563 /** Non-dumb mode. For all other states, {@link #isDumb()} returns true. */
567 * A state between entering dumb mode ({@link #queueTaskOnEdt}) and actually starting the background progress later ({@link #runBackgroundProcess}).
568 * In this state, it's possible to call {@link #completeJustSubmittedTasks()} and perform all submitted the tasks modally.
569 * This state can happen after {@link #SMART} or {@link #WAITING_FOR_FINISH}. Followed by {@link #RUNNING_DUMB_TASKS}.
574 * Indicates that a background thread is currently executing dumb tasks.
579 * Set after background execution ({@link #RUNNING_DUMB_TASKS}) finishes, until the dumb mode can be exited
580 * (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}.