/* * Copyright 2000-2016 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.process; import com.intellij.execution.ExecutionException; import com.intellij.execution.KillableProcess; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.SystemInfo; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.charset.Charset; /** * This process handler supports the "soft-kill" feature (see {@link KillableProcessHandler}). * At first "stop" button send SIGINT signal to process, if it still hangs user can terminate it recursively with SIGKILL signal. * <p> * Soft kill works on Unix, and also on Windows if a mediator process was used. */ public class KillableProcessHandler extends OSProcessHandler implements KillableProcess { private static final Logger LOG = Logger.getInstance(KillableProcessHandler.class); private static final Key<Boolean> MEDIATOR_KEY = Key.create("KillableProcessHandler.Mediator.Process"); private boolean myShouldKillProcessSoftly = true; private final boolean myMediatedProcess; public KillableProcessHandler(@NotNull GeneralCommandLine commandLine) throws ExecutionException { super(commandLine); myMediatedProcess = MEDIATOR_KEY.get(commandLine) == Boolean.TRUE; } /** * Starts a process with a {@link RunnerMediator mediator} when {@code withMediator} is set to {@code true} and the platform is Windows. */ public KillableProcessHandler(@NotNull GeneralCommandLine commandLine, boolean withMediator) throws ExecutionException { this(mediate(commandLine, withMediator)); } /** * {@code commandLine} must not be not empty (for correct thread attribution in the stacktrace) */ public KillableProcessHandler(@NotNull Process process, /*@NotNull*/ String commandLine) { super(process, commandLine); myMediatedProcess = false; } /** * {@code commandLine} must not be not empty (for correct thread attribution in the stacktrace) */ public KillableProcessHandler(@NotNull Process process, /*@NotNull*/ String commandLine, @NotNull Charset charset) { super(process, commandLine, charset); myMediatedProcess = false; } @NotNull protected static GeneralCommandLine mediate(@NotNull GeneralCommandLine commandLine, boolean withMediator) { if (withMediator && SystemInfo.isWindows && MEDIATOR_KEY.get(commandLine) == null) { boolean mediatorInjected = RunnerMediator.injectRunnerCommand(commandLine); MEDIATOR_KEY.set(commandLine, mediatorInjected); } return commandLine; } /** * @return true, if graceful process termination should be attempted first */ public boolean shouldKillProcessSoftly() { return myShouldKillProcessSoftly; } /** * Sets whether the process will be terminated gracefully. * * @param shouldKillProcessSoftly true, if graceful process termination should be attempted first (i.e. soft kill) */ public void setShouldKillProcessSoftly(boolean shouldKillProcessSoftly) { myShouldKillProcessSoftly = shouldKillProcessSoftly; } /** * This method shouldn't be overridden, see shouldKillProcessSoftly */ private boolean canKillProcessSoftly() { if (processCanBeKilledByOS(myProcess)) { if (SystemInfo.isWindows) { // runnerw.exe can send Ctrl+C events to a wrapped process return myMediatedProcess; } else if (SystemInfo.isUnix) { // 'kill -SIGINT <pid>' will be executed return true; } } return false; } @Override protected void destroyProcessImpl() { // Don't close streams, because a process may survive graceful termination. // Streams will be closed after the process is really terminated. try { myProcess.getOutputStream().flush(); } catch (IOException e) { LOG.warn(e); } finally { doDestroyProcess(); } } @Override protected void notifyProcessTerminated(int exitCode) { try { super.closeStreams(); } finally { super.notifyProcessTerminated(exitCode); } } @Override protected void doDestroyProcess() { boolean gracefulTerminationAttempted = shouldKillProcessSoftly() && canKillProcessSoftly() && destroyProcessGracefully(); if (!gracefulTerminationAttempted) { // execute default process destroy super.doDestroyProcess(); } } protected boolean destroyProcessGracefully() { if (SystemInfo.isWindows && myMediatedProcess) { return RunnerMediator.destroyProcess(myProcess, true); } else if (SystemInfo.isUnix) { return UnixProcessManager.sendSigIntToProcessTree(myProcess); } return false; } @Override public boolean canKillProcess() { return processCanBeKilledByOS(getProcess()); } @Override public void killProcess() { // execute 'kill -SIGKILL <pid>' on Unix killProcessTree(getProcess()); } }