a7b7c0c8dd36e2fadc990b46a413d1d6b6020d52
[idea/community.git] / platform / platform-impl / src / com / intellij / execution / process / KillableColoredProcessHandler.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.execution.process;
17
18 import com.intellij.execution.ExecutionException;
19 import com.intellij.execution.KillableProcess;
20 import com.intellij.execution.configurations.GeneralCommandLine;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.util.Key;
23 import com.intellij.openapi.util.SystemInfo;
24 import org.jetbrains.annotations.NotNull;
25
26 import java.io.IOException;
27 import java.nio.charset.Charset;
28
29 /**
30  * This process handler supports ANSI coloring and "soft-kill" feature.
31  * At first "stop" button send SIGINT signal to process, if it still hangs user can terminate it recursively with SIGKILL signal.
32  * <p>
33  * Soft kill works on Unix, and also on Windows if a mediator process was used.
34  *
35  * @author Roman.Chernyatchik
36  */
37 public class KillableColoredProcessHandler extends ColoredProcessHandler implements KillableProcess {
38   private static final Logger LOG = Logger.getInstance(KillableColoredProcessHandler.class);
39   private static final Key<Boolean> MEDIATOR_KEY = Key.create("KillableColoredProcessHandler.Mediator.Process");
40
41   private boolean myShouldKillProcessSoftly = true;
42   private boolean myMediatedProcess = false;
43
44   public KillableColoredProcessHandler(@NotNull GeneralCommandLine commandLine) throws ExecutionException {
45     super(commandLine);
46   }
47
48   /**
49    * Starts a process with a {@link RunnerMediator mediator} when {@code withMediator} is set to {@code true} and the platform is Windows.
50    */
51   public KillableColoredProcessHandler(@NotNull GeneralCommandLine commandLine, boolean withMediator) throws ExecutionException {
52     super(mediate(commandLine, withMediator));
53     myMediatedProcess = withMediator && MEDIATOR_KEY.get(commandLine) == Boolean.TRUE;
54   }
55
56   /**
57    * {@code commandLine} must not be not empty (for correct thread attribution in the stacktrace)
58    */
59   public KillableColoredProcessHandler(@NotNull Process process, /*@NotNull*/ String commandLine) {
60     super(process, commandLine);
61   }
62
63   /**
64    * {@code commandLine} must not be not empty (for correct thread attribution in the stacktrace)
65    */
66   public KillableColoredProcessHandler(@NotNull Process process, /*@NotNull*/ String commandLine, @NotNull Charset charset) {
67     super(process, commandLine, charset);
68   }
69
70   private static GeneralCommandLine mediate(GeneralCommandLine commandLine, boolean withMediator) {
71     if (withMediator && SystemInfo.isWindows) {
72       boolean mediatorInjected = RunnerMediator.injectRunnerCommand(commandLine);
73       MEDIATOR_KEY.set(commandLine, mediatorInjected);
74     }
75     return commandLine;
76   }
77
78   /**
79    * @return true, if graceful process termination should be attempted first
80    */
81   protected boolean shouldKillProcessSoftly() {
82     return myShouldKillProcessSoftly;
83   }
84
85   /**
86    * Sets whether the process will be terminated gracefully.
87    *
88    * @param shouldKillProcessSoftly true, if graceful process termination should be attempted first (i.e. soft kill)
89    */
90   public void setShouldKillProcessSoftly(boolean shouldKillProcessSoftly) {
91     myShouldKillProcessSoftly = shouldKillProcessSoftly;
92   }
93
94   /**
95    * This method shouldn't be overridden, see shouldKillProcessSoftly
96    */
97   private boolean canKillProcessSoftly() {
98     if (processCanBeKilledByOS(myProcess)) {
99       if (SystemInfo.isWindows) {
100         // runnerw.exe can send Ctrl+C events to a wrapped process
101         return myMediatedProcess;
102       }
103       else if (SystemInfo.isUnix) {
104         // 'kill -SIGINT <pid>' will be executed
105         return true;
106       }
107     }
108     return false;
109   }
110
111   @Override
112   protected void destroyProcessImpl() {
113     // Don't close streams, because a process may survive graceful termination.
114     // Streams will be closed after the process is really terminated.
115     try {
116       myProcess.getOutputStream().flush();
117     }
118     catch (IOException e) {
119       LOG.warn(e);
120     }
121     finally {
122       doDestroyProcess();
123     }
124   }
125
126   @Override
127   protected void notifyProcessTerminated(int exitCode) {
128     try {
129       super.closeStreams();
130     }
131     finally {
132       super.notifyProcessTerminated(exitCode);
133     }
134   }
135
136   @Override
137   protected void doDestroyProcess() {
138     boolean gracefulTerminationAttempted = shouldKillProcessSoftly() && canKillProcessSoftly() && destroyProcessGracefully();
139     if (!gracefulTerminationAttempted) {
140       // execute default process destroy
141       super.doDestroyProcess();
142     }
143   }
144
145   protected boolean destroyProcessGracefully() {
146     if (SystemInfo.isWindows && myMediatedProcess) {
147       return RunnerMediator.destroyProcess(myProcess, true);
148     }
149     else if (SystemInfo.isUnix) {
150       return UnixProcessManager.sendSigIntToProcessTree(myProcess);
151     }
152     return false;
153   }
154
155   @Override
156   public boolean canKillProcess() {
157     return processCanBeKilledByOS(getProcess());
158   }
159
160   @Override
161   public void killProcess() {
162     // execute 'kill -SIGKILL <pid>' on Unix
163     killProcessTree(getProcess());
164   }
165
166   /** @deprecated use {@link #KillableColoredProcessHandler(GeneralCommandLine, boolean)} (to be removed in IDEA 17) */
167   @SuppressWarnings("unused")
168   public static KillableColoredProcessHandler create(@NotNull GeneralCommandLine commandLine) throws ExecutionException {
169     return new KillableColoredProcessHandler(commandLine, true);
170   }
171 }