blob: eb64c824b801f2248c8311a1a8235310176d9b10 [file] [log] [blame]
/*
* Copyright 2000-2013 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.ide.startup.impl;
import com.intellij.ide.caches.CacheUpdater;
import com.intellij.ide.startup.StartupManagerEx;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.*;
import com.intellij.openapi.project.impl.ProjectLifecycleListener;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.startup.StartupActivity;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.impl.local.FileWatcher;
import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.util.SmartList;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class StartupManagerImpl extends StartupManagerEx {
private static final Logger LOG = Logger.getInstance("#com.intellij.ide.startup.impl.StartupManagerImpl");
private final List<Runnable> myPreStartupActivities = Collections.synchronizedList(new LinkedList<Runnable>());
private final List<Runnable> myStartupActivities = new LinkedList<Runnable>();
private final List<Runnable> myDumbAwarePostStartupActivities = Collections.synchronizedList(new LinkedList<Runnable>());
private final List<Runnable> myNotDumbAwarePostStartupActivities = Collections.synchronizedList(new LinkedList<Runnable>());
private boolean myPostStartupActivitiesPassed = false; // guarded by this
private final List<CacheUpdater> myCacheUpdaters = new LinkedList<CacheUpdater>();
private volatile boolean myPreStartupActivitiesPassed = false;
private volatile boolean myStartupActivitiesRunning = false;
private volatile boolean myStartupActivitiesPassed = false;
private final Project myProject;
public StartupManagerImpl(Project project) {
myProject = project;
}
@Override
public void registerPreStartupActivity(@NotNull Runnable runnable) {
LOG.assertTrue(!myPreStartupActivitiesPassed, "Registering pre startup activity that will never be run");
myPreStartupActivities.add(runnable);
}
@Override
public void registerStartupActivity(@NotNull Runnable runnable) {
LOG.assertTrue(!myStartupActivitiesPassed, "Registering startup activity that will never be run");
myStartupActivities.add(runnable);
}
@Override
public synchronized void registerPostStartupActivity(@NotNull Runnable runnable) {
LOG.assertTrue(!myPostStartupActivitiesPassed, "Registering post-startup activity that will never be run");
(DumbService.isDumbAware(runnable) ? myDumbAwarePostStartupActivities : myNotDumbAwarePostStartupActivities).add(runnable);
}
@Override
public void registerCacheUpdater(@NotNull CacheUpdater updater) {
LOG.assertTrue(!myStartupActivitiesPassed, CacheUpdater.class.getSimpleName() + " must be registered before startup activity finished");
myCacheUpdaters.add(updater);
}
@Override
public boolean startupActivityRunning() {
return myStartupActivitiesRunning;
}
@Override
public boolean startupActivityPassed() {
return myStartupActivitiesPassed;
}
@Override
public synchronized boolean postStartupActivityPassed() {
return myPostStartupActivitiesPassed;
}
public void runStartupActivities() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
HeavyProcessLatch.INSTANCE.processStarted();
try {
runActivities(myPreStartupActivities);
myPreStartupActivitiesPassed = true;
myStartupActivitiesRunning = true;
runActivities(myStartupActivities);
myStartupActivitiesRunning = false;
myStartupActivitiesPassed = true;
}
finally {
HeavyProcessLatch.INSTANCE.processFinished();
}
}
});
}
public void runPostStartupActivitiesFromExtensions() {
for (final StartupActivity extension : Extensions.getExtensions(StartupActivity.POST_STARTUP_ACTIVITY)) {
final Runnable runnable = new Runnable() {
public void run() {
if (!myProject.isDisposed()) {
extension.runActivity(myProject);
}
}
};
if (extension instanceof DumbAware) {
runActivity(runnable);
}
else {
queueSmartModeActivity(runnable);
}
}
}
// queue each activity in smart mode separately so that if one of them starts dumb mode, the next ones just wait for it to finish
private void queueSmartModeActivity(final Runnable activity) {
DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
@Override
public void run() {
runActivity(activity);
}
});
}
public synchronized void runPostStartupActivities() {
final Application app = ApplicationManager.getApplication();
if (myPostStartupActivitiesPassed) return;
runActivities(myDumbAwarePostStartupActivities);
DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
public void run() {
//noinspection SynchronizeOnThis
synchronized (StartupManagerImpl.this) {
app.assertIsDispatchThread();
runActivities(myDumbAwarePostStartupActivities); // they can register activities while in the dumb mode
if (!myNotDumbAwarePostStartupActivities.isEmpty()) {
while (!myNotDumbAwarePostStartupActivities.isEmpty()) {
queueSmartModeActivity(myNotDumbAwarePostStartupActivities.remove(0));
}
// return here later to set myPostStartupActivitiesPassed
DumbService.getInstance(myProject).runWhenSmart(this);
}
else {
myPostStartupActivitiesPassed = true;
}
}
}
});
Registry.get("ide.firstStartup").setValue(false);
}
public void scheduleInitialVfsRefresh() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
if (myProject.isDisposed()) return;
Application app = ApplicationManager.getApplication();
if (!app.isHeadlessEnvironment()) {
checkProjectRoots();
final long sessionId = VirtualFileManager.getInstance().asyncRefresh(null);
final MessageBusConnection connection = app.getMessageBus().connect();
connection.subscribe(ProjectLifecycleListener.TOPIC, new ProjectLifecycleListener.Adapter() {
@Override
public void afterProjectClosed(@NotNull Project project) {
RefreshQueue.getInstance().cancelSession(sessionId);
connection.disconnect();
}
});
}
else {
VirtualFileManager.getInstance().syncRefresh();
}
}
});
}
private void checkProjectRoots() {
LocalFileSystem fs = LocalFileSystem.getInstance();
if (!(fs instanceof LocalFileSystemImpl)) return;
FileWatcher watcher = ((LocalFileSystemImpl)fs).getFileWatcher();
if (!watcher.isOperational()) return;
List<String> manualWatchRoots = watcher.getManualWatchRoots();
if (manualWatchRoots.isEmpty()) return;
VirtualFile[] roots = ProjectRootManager.getInstance(myProject).getContentRoots();
if (roots.length == 0) return;
List<String> nonWatched = new SmartList<String>();
for (VirtualFile root : roots) {
if (!(root.getFileSystem() instanceof LocalFileSystem)) continue;
String rootPath = root.getPath();
for (String manualWatchRoot : manualWatchRoots) {
if (FileUtil.isAncestor(manualWatchRoot, rootPath, false)) {
nonWatched.add(rootPath);
}
}
}
if (!nonWatched.isEmpty()) {
String message = ApplicationBundle.message("watcher.non.watchable.project");
watcher.notifyOnFailure(message, null);
LOG.info("unwatched roots: " + nonWatched);
LOG.info("manual watches: " + manualWatchRoots);
}
}
public void startCacheUpdate() {
try {
DumbServiceImpl dumbService = DumbServiceImpl.getInstance(myProject);
if (!ApplicationManager.getApplication().isUnitTestMode()) {
// pre-startup activities have registered dumb tasks that load VFS (scanning files to index)
// only after these tasks pass does VFS refresh make sense
dumbService.queueTask(new DumbModeTask() {
@Override
public void performInDumbMode(@NotNull final ProgressIndicator indicator) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (!myProject.isDisposed()) {
scheduleInitialVfsRefresh();
}
}
});
}
@Override
public String toString() {
return "initial refresh";
}
});
}
if (!myCacheUpdaters.isEmpty()) {
dumbService.queueCacheUpdateInDumbMode(myCacheUpdaters);
}
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Throwable e) {
LOG.error(e);
}
}
private static void runActivities(@NotNull List<Runnable> activities) {
while (!activities.isEmpty()) {
runActivity(activities.remove(0));
}
}
private static void runActivity(Runnable runnable) {
ProgressManager.checkCanceled();
try {
runnable.run();
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Throwable ex) {
LOG.error(ex);
}
}
@Override
public synchronized void runWhenProjectIsInitialized(@NotNull final Runnable action) {
final Application application = ApplicationManager.getApplication();
if (application == null) return;
final Runnable runnable;
if (DumbService.isDumbAware(action)) {
runnable = new DumbAwareRunnable() {
public void run() {
action.run();
}
};
}
else {
runnable = new Runnable() {
public void run() {
action.run();
}
};
}
if (myProject.isInitialized() || application.isUnitTestMode() && myPostStartupActivitiesPassed) {
// in tests which simulate project opening, post-startup activities could have been run already.
// Then we should act as if the project was initialized
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
if (!myProject.isDisposed()) {
runnable.run();
}
}
});
}
else {
registerPostStartupActivity(runnable);
}
}
@TestOnly
public synchronized void prepareForNextTest() {
myPreStartupActivities.clear();
myStartupActivities.clear();
myDumbAwarePostStartupActivities.clear();
myNotDumbAwarePostStartupActivities.clear();
myCacheUpdaters.clear();
}
@TestOnly
public synchronized void checkCleared() {
try {
assert myStartupActivities.isEmpty() : "Activities: " + myStartupActivities;
assert myDumbAwarePostStartupActivities.isEmpty() : "DumbAware Post Activities: " + myDumbAwarePostStartupActivities;
assert myNotDumbAwarePostStartupActivities.isEmpty() : "Post Activities: " + myNotDumbAwarePostStartupActivities;
assert myPreStartupActivities.isEmpty() : "Pre Activities: " + myPreStartupActivities;
}
finally {
prepareForNextTest();
}
}
}