/* * Copyright 2000-2014 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.execution.impl; import com.intellij.CommonBundle; import com.intellij.execution.*; import com.intellij.execution.configuration.CompatibilityAwareRunProfile; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironmentBuilder; import com.intellij.execution.runners.ExecutionUtil; import com.intellij.execution.runners.ProgramRunner; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.execution.ui.RunContentManager; import com.intellij.execution.ui.RunContentManagerImpl; import com.intellij.execution.ui.RunnerLayoutUi; import com.intellij.ide.SaveAndSyncHandler; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.impl.SimpleDataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Trinity; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.AppUIUtil; import com.intellij.ui.docking.DockManager; import com.intellij.util.Alarm; import com.intellij.util.Consumer; import com.intellij.util.ObjectUtil; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; public class ExecutionManagerImpl extends ExecutionManager implements Disposable { public static final Key<Object> EXECUTION_SESSION_ID_KEY = Key.create("EXECUTION_SESSION_ID_KEY"); private static final Logger LOG = Logger.getInstance(ExecutionManagerImpl.class); private static final ProcessHandler[] EMPTY_PROCESS_HANDLERS = new ProcessHandler[0]; private final Project myProject; private RunContentManagerImpl myContentManager; private final Alarm awaitingTerminationAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); private final List<Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor>> myRunningConfigurations = ContainerUtil.createLockFreeCopyOnWriteList(); @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass") @NotNull public static ExecutionManagerImpl getInstance(@NotNull Project project) { return (ExecutionManagerImpl)ServiceManager.getService(project, ExecutionManager.class); } ExecutionManagerImpl(@NotNull Project project) { myProject = project; } public static void stopProcess(@Nullable RunContentDescriptor descriptor) { stopProcess(descriptor != null ? descriptor.getProcessHandler() : null); } public static void stopProcess(@Nullable ProcessHandler processHandler) { if (processHandler == null) { return; } processHandler.putUserData(ProcessHandler.TERMINATION_REQUESTED, Boolean.TRUE); if (processHandler instanceof KillableProcess && processHandler.isProcessTerminating()) { // process termination was requested, but it's still alive // in this case 'force quit' will be performed ((KillableProcess)processHandler).killProcess(); return; } if (!processHandler.isProcessTerminated()) { if (processHandler.detachIsDefault()) { processHandler.detachProcess(); } else { processHandler.destroyProcess(); } } } @Override public void dispose() { for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { Disposer.dispose(trinity.first); } myRunningConfigurations.clear(); } @NotNull @Override public RunContentManager getContentManager() { if (myContentManager == null) { myContentManager = new RunContentManagerImpl(myProject, DockManager.getInstance(myProject)); Disposer.register(myProject, myContentManager); } return myContentManager; } @NotNull @Override public ProcessHandler[] getRunningProcesses() { if (myContentManager == null) return EMPTY_PROCESS_HANDLERS; List<ProcessHandler> handlers = null; for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) { ProcessHandler processHandler = descriptor.getProcessHandler(); if (processHandler != null) { if (handlers == null) { handlers = new SmartList<>(); } handlers.add(processHandler); } } return handlers == null ? EMPTY_PROCESS_HANDLERS : handlers.toArray(new ProcessHandler[handlers.size()]); } @Override public void compileAndRun(@NotNull final Runnable startRunnable, @NotNull final ExecutionEnvironment environment, @Nullable final RunProfileState state, @Nullable final Runnable onCancelRunnable) { long id = environment.getExecutionId(); if (id == 0) { id = environment.assignNewExecutionId(); } RunProfile profile = environment.getRunProfile(); if (!(profile instanceof RunConfiguration)) { startRunnable.run(); return; } final RunConfiguration runConfiguration = (RunConfiguration)profile; final List<BeforeRunTask> beforeRunTasks = RunManagerEx.getInstanceEx(myProject).getBeforeRunTasks(runConfiguration); if (beforeRunTasks.isEmpty()) { startRunnable.run(); } else { DataContext context = environment.getDataContext(); final DataContext projectContext = context != null ? context : SimpleDataContext.getProjectContext(myProject); final long finalId = id; final Long executionSessionId = new Long(id); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { /** @noinspection SSBasedInspection*/ @Override public void run() { for (BeforeRunTask task : beforeRunTasks) { if (myProject.isDisposed()) { return; } @SuppressWarnings("unchecked") BeforeRunTaskProvider<BeforeRunTask> provider = BeforeRunTaskProvider.getProvider(myProject, task.getProviderId()); if (provider == null) { LOG.warn("Cannot find BeforeRunTaskProvider for id='" + task.getProviderId() + "'"); continue; } ExecutionEnvironment taskEnvironment = new ExecutionEnvironmentBuilder(environment).contentToReuse(null).build(); taskEnvironment.setExecutionId(finalId); EXECUTION_SESSION_ID_KEY.set(taskEnvironment, executionSessionId); if (!provider.executeTask(projectContext, runConfiguration, taskEnvironment, task)) { if (onCancelRunnable != null) { SwingUtilities.invokeLater(onCancelRunnable); } return; } } // important! Do not use DumbService.smartInvokeLater here because it depends on modality state // and execution of startRunnable could be skipped if modality state check fails SwingUtilities.invokeLater(() -> { if (!myProject.isDisposed()) { DumbService.getInstance(myProject).runWhenSmart(startRunnable); } }); } }); } } @Override public void startRunProfile(@NotNull final RunProfileStarter starter, @NotNull final RunProfileState state, @NotNull final ExecutionEnvironment environment) { final Project project = environment.getProject(); RunContentDescriptor reuseContent = getContentManager().getReuseContent(environment); if (reuseContent != null) { reuseContent.setExecutionId(environment.getExecutionId()); environment.setContentToReuse(reuseContent); } final Executor executor = environment.getExecutor(); project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStartScheduled(executor.getId(), environment); Runnable startRunnable; startRunnable = () -> { if (project.isDisposed()) { return; } RunProfile profile = environment.getRunProfile(); project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarting(executor.getId(), environment); Consumer<Throwable> errorHandler = e -> { if (!(e instanceof ProcessCanceledException)) { ExecutionException error = e instanceof ExecutionException ? (ExecutionException)e : new ExecutionException(e); ExecutionUtil .handleExecutionError(project, ExecutionManager.getInstance(project).getContentManager().getToolWindowIdByEnvironment(environment), profile, error); } LOG.info(e); project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment); }; try { starter.executeAsync(state, environment).done(descriptor -> AppUIUtil.invokeOnEdt(() -> { if (descriptor != null) { final Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity = Trinity.create(descriptor, environment.getRunnerAndConfigurationSettings(), executor); myRunningConfigurations.add(trinity); Disposer.register(descriptor, () -> myRunningConfigurations.remove(trinity)); getContentManager().showRunContent(executor, descriptor, environment.getContentToReuse()); final ProcessHandler processHandler = descriptor.getProcessHandler(); if (processHandler != null) { if (!processHandler.isStartNotified()) { processHandler.startNotify(); } project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarted(executor.getId(), environment, processHandler); ProcessExecutionListener listener = new ProcessExecutionListener(project, executor.getId(), environment, processHandler, descriptor); processHandler.addProcessListener(listener); // Since we cannot guarantee that the listener is added before process handled is start notified, // we have to make sure the process termination events are delivered to the clients. // Here we check the current process state and manually deliver events, while // the ProcessExecutionListener guarantees each such event is only delivered once // either by this code, or by the ProcessHandler. boolean terminating = processHandler.isProcessTerminating(); boolean terminated = processHandler.isProcessTerminated(); if (terminating || terminated) { listener.processWillTerminate(new ProcessEvent(processHandler), false /*doesn't matter*/); if (terminated) { //noinspection ConstantConditions Integer exitCode = processHandler.getExitCode(); listener.processTerminated( new ProcessEvent(processHandler, processHandler.isStartNotified() ? ObjectUtil.notNull(processHandler.getExitCode(), -1) : -1)); } } } environment.setContentToReuse(descriptor); } else { project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment); } }, o -> project.isDisposed())).rejected(errorHandler); } catch (ExecutionException e) { errorHandler.consume(e); } }; if (ApplicationManager.getApplication().isUnitTestMode()) { startRunnable.run(); } else { compileAndRun(() -> TransactionGuard.submitTransaction(project, startRunnable), environment, state, () -> { if (!project.isDisposed()) { project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment); } }); } } @Override public void restartRunProfile(@NotNull Project project, @NotNull Executor executor, @NotNull ExecutionTarget target, @Nullable RunnerAndConfigurationSettings configuration, @Nullable ProcessHandler processHandler) { ExecutionEnvironmentBuilder builder = createEnvironmentBuilder(project, executor, configuration); if (processHandler != null) { for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) { if (descriptor.getProcessHandler() == processHandler) { builder.contentToReuse(descriptor); break; } } } restartRunProfile(builder.target(target).build()); } @NotNull private static ExecutionEnvironmentBuilder createEnvironmentBuilder(@NotNull Project project, @NotNull Executor executor, @Nullable RunnerAndConfigurationSettings configuration) { ExecutionEnvironmentBuilder builder = new ExecutionEnvironmentBuilder(project, executor); ProgramRunner runner = RunnerRegistry.getInstance().getRunner(executor.getId(), configuration != null ? configuration.getConfiguration() : null); if (runner == null && configuration != null) { LOG.error("Cannot find runner for " + configuration.getName()); } else if (runner != null) { assert configuration != null; builder.runnerAndSettings(runner, configuration); } return builder; } @Override public void restartRunProfile(@NotNull Project project, @NotNull Executor executor, @NotNull ExecutionTarget target, @Nullable RunnerAndConfigurationSettings configuration, @Nullable RunContentDescriptor currentDescriptor) { ExecutionEnvironmentBuilder builder = createEnvironmentBuilder(project, executor, configuration); restartRunProfile(builder.target(target).contentToReuse(currentDescriptor).build()); } @Override public void restartRunProfile(@Nullable ProgramRunner runner, @NotNull ExecutionEnvironment environment, @Nullable RunContentDescriptor currentDescriptor) { ExecutionEnvironmentBuilder builder = new ExecutionEnvironmentBuilder(environment).contentToReuse(currentDescriptor); if (runner != null) { builder.runner(runner); } restartRunProfile(builder.build()); } public static boolean isProcessRunning(@Nullable RunContentDescriptor descriptor) { ProcessHandler processHandler = descriptor == null ? null : descriptor.getProcessHandler(); return processHandler != null && !processHandler.isProcessTerminated(); } @Override public void restartRunProfile(@NotNull final ExecutionEnvironment environment) { RunnerAndConfigurationSettings configuration = environment.getRunnerAndConfigurationSettings(); List<RunContentDescriptor> runningIncompatible; if (configuration == null) { runningIncompatible = Collections.emptyList(); } else { runningIncompatible = getIncompatibleRunningDescriptors(configuration); } RunContentDescriptor contentToReuse = environment.getContentToReuse(); final List<RunContentDescriptor> runningOfTheSameType = new SmartList<>(); if (configuration != null && configuration.isSingleton()) { runningOfTheSameType.addAll(getRunningDescriptorsOfTheSameConfigType(configuration)); } else if (isProcessRunning(contentToReuse)) { runningOfTheSameType.add(contentToReuse); } List<RunContentDescriptor> runningToStop = ContainerUtil.concat(runningOfTheSameType, runningIncompatible); if (!runningToStop.isEmpty()) { if (configuration != null) { if (!runningOfTheSameType.isEmpty() && (runningOfTheSameType.size() > 1 || contentToReuse == null || runningOfTheSameType.get(0) != contentToReuse) && !userApprovesStopForSameTypeConfigurations(environment.getProject(), configuration.getName(), runningOfTheSameType.size())) { return; } if (!runningIncompatible.isEmpty() && !userApprovesStopForIncompatibleConfigurations(myProject, configuration.getName(), runningIncompatible)) { return; } } for (RunContentDescriptor descriptor : runningToStop) { stopProcess(descriptor); } } awaitingTerminationAlarm.addRequest(new Runnable() { @Override public void run() { if (ExecutorRegistry.getInstance().isStarting(environment)) { awaitingTerminationAlarm.addRequest(this, 100); return; } for (RunContentDescriptor descriptor : runningOfTheSameType) { ProcessHandler processHandler = descriptor.getProcessHandler(); if (processHandler != null && !processHandler.isProcessTerminated()) { awaitingTerminationAlarm.addRequest(this, 100); return; } } start(environment); } }, 50); } private static void start(@NotNull ExecutionEnvironment environment) { RunnerAndConfigurationSettings settings = environment.getRunnerAndConfigurationSettings(); ProgramRunnerUtil.executeConfiguration(environment, settings != null && settings.isEditBeforeRun(), true); } private static boolean userApprovesStopForSameTypeConfigurations(Project project, String configName, int instancesCount) { RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project); final RunManagerConfig config = runManager.getConfig(); if (!config.isRestartRequiresConfirmation()) return true; DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() { @Override public boolean isToBeShown() { return config.isRestartRequiresConfirmation(); } @Override public void setToBeShown(boolean value, int exitCode) { config.setRestartRequiresConfirmation(value); } @Override public boolean canBeHidden() { return true; } @Override public boolean shouldSaveOptionsOnCancel() { return false; } @NotNull @Override public String getDoNotShowMessage() { return CommonBundle.message("dialog.options.do.not.show"); } }; return Messages.showOkCancelDialog(project, ExecutionBundle.message("rerun.singleton.confirmation.message", configName, instancesCount), ExecutionBundle.message("process.is.running.dialog.title", configName), ExecutionBundle.message("rerun.confirmation.button.text"), CommonBundle.message("button.cancel"), Messages.getQuestionIcon(), option) == Messages.OK; } private static boolean userApprovesStopForIncompatibleConfigurations(Project project, String configName, List<RunContentDescriptor> runningIncompatibleDescriptors) { RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project); final RunManagerConfig config = runManager.getConfig(); if (!config.isRestartRequiresConfirmation()) return true; DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() { @Override public boolean isToBeShown() { return config.isRestartRequiresConfirmation(); } @Override public void setToBeShown(boolean value, int exitCode) { config.setRestartRequiresConfirmation(value); } @Override public boolean canBeHidden() { return true; } @Override public boolean shouldSaveOptionsOnCancel() { return false; } @NotNull @Override public String getDoNotShowMessage() { return CommonBundle.message("dialog.options.do.not.show"); } }; final StringBuilder names = new StringBuilder(); for (final RunContentDescriptor descriptor : runningIncompatibleDescriptors) { String name = descriptor.getDisplayName(); if (names.length() > 0) { names.append(", "); } names.append(StringUtil.isEmpty(name) ? ExecutionBundle.message("run.configuration.no.name") : String.format("'%s'", name)); } //noinspection DialogTitleCapitalization return Messages.showOkCancelDialog(project, ExecutionBundle .message("stop.incompatible.confirmation.message", configName, names.toString(), runningIncompatibleDescriptors.size()), ExecutionBundle.message("incompatible.configuration.is.running.dialog.title", runningIncompatibleDescriptors.size()), ExecutionBundle.message("stop.incompatible.confirmation.button.text"), CommonBundle.message("button.cancel"), Messages.getQuestionIcon(), option) == Messages.OK; } @NotNull private List<RunContentDescriptor> getRunningDescriptorsOfTheSameConfigType(@NotNull final RunnerAndConfigurationSettings configurationAndSettings) { return getRunningDescriptors(runningConfigurationAndSettings -> configurationAndSettings == runningConfigurationAndSettings); } @NotNull private List<RunContentDescriptor> getIncompatibleRunningDescriptors(@NotNull RunnerAndConfigurationSettings configurationAndSettings) { final RunConfiguration configurationToCheckCompatibility = configurationAndSettings.getConfiguration(); return getRunningDescriptors(runningConfigurationAndSettings -> { RunConfiguration runningConfiguration = runningConfigurationAndSettings == null ? null : runningConfigurationAndSettings.getConfiguration(); if (runningConfiguration == null || !(runningConfiguration instanceof CompatibilityAwareRunProfile)) { return false; } return ((CompatibilityAwareRunProfile)runningConfiguration).mustBeStoppedToRun(configurationToCheckCompatibility); }); } @NotNull public List<RunContentDescriptor> getRunningDescriptors(@NotNull Condition<RunnerAndConfigurationSettings> condition) { List<RunContentDescriptor> result = new SmartList<>(); for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { if (condition.value(trinity.getSecond())) { ProcessHandler processHandler = trinity.getFirst().getProcessHandler(); if (processHandler != null /*&& !processHandler.isProcessTerminating()*/ && !processHandler.isProcessTerminated()) { result.add(trinity.getFirst()); } } } return result; } @NotNull public List<RunContentDescriptor> getDescriptors(@NotNull Condition<RunnerAndConfigurationSettings> condition) { List<RunContentDescriptor> result = new SmartList<>(); for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { if (trinity.getSecond() != null && condition.value(trinity.getSecond())) { result.add(trinity.getFirst()); } } return result; } @NotNull public Set<Executor> getExecutors(RunContentDescriptor descriptor) { Set<Executor> result = new HashSet<>(); for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { if (descriptor == trinity.first) result.add(trinity.third); } return result; } @NotNull public Set<RunnerAndConfigurationSettings> getConfigurations(RunContentDescriptor descriptor) { Set<RunnerAndConfigurationSettings> result = new HashSet<>(); for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) { if (descriptor == trinity.first) result.add(trinity.second); } return result; } private static class ProcessExecutionListener extends ProcessAdapter { @NotNull private final Project myProject; @NotNull private final String myExecutorId; @NotNull private final ExecutionEnvironment myEnvironment; @NotNull private final ProcessHandler myProcessHandler; @NotNull private final RunContentDescriptor myDescriptor; @NotNull private final AtomicBoolean myWillTerminateNotified = new AtomicBoolean(); @NotNull private final AtomicBoolean myTerminateNotified = new AtomicBoolean(); public ProcessExecutionListener(@NotNull Project project, @NotNull String executorId, @NotNull ExecutionEnvironment environment, @NotNull ProcessHandler processHandler, @NotNull RunContentDescriptor descriptor) { myProject = project; myExecutorId = executorId; myEnvironment = environment; myProcessHandler = processHandler; myDescriptor = descriptor; } @Override public void processTerminated(ProcessEvent event) { if (myProject.isDisposed()) return; if (!myTerminateNotified.compareAndSet(false, true)) return; ApplicationManager.getApplication().invokeLater(() -> { RunnerLayoutUi ui = myDescriptor.getRunnerLayoutUi(); if (ui != null && !ui.isDisposed()) { ui.updateActionsNow(); } }, ModalityState.any()); myProject.getMessageBus().syncPublisher(EXECUTION_TOPIC).processTerminated(myExecutorId, myEnvironment, myProcessHandler, event.getExitCode()); SaveAndSyncHandler saveAndSyncHandler = SaveAndSyncHandler.getInstance(); if (saveAndSyncHandler != null) { saveAndSyncHandler.scheduleRefresh(); } } @Override public void processWillTerminate(ProcessEvent event, boolean shouldNotBeUsed) { if (myProject.isDisposed()) return; if (!myWillTerminateNotified.compareAndSet(false, true)) return; myProject.getMessageBus().syncPublisher(EXECUTION_TOPIC).processTerminating(myExecutorId, myEnvironment, myProcessHandler); } } }