refactoring of runner mediator
authorDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Wed, 1 Dec 2010 16:07:02 +0000 (19:07 +0300)
committerDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Wed, 1 Dec 2010 16:07:02 +0000 (19:07 +0300)
platform/platform-impl/src/com/intellij/execution/process/RunnerMediator.java
platform/platform-impl/src/com/intellij/execution/process/RunnerMediatorManager.java
platform/platform-impl/src/com/intellij/execution/process/UnixProcessManager.java [new file with mode: 0644]

index c70493b229bb5da692c754605f141c383d0f636e..524e16b0767aa58f269a9bd025d541015ebd05f6 100644 (file)
+/*
+ * Copyright 2000-2010 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.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.util.SystemInfo;
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-import com.sun.jna.Platform;
 import org.jetbrains.annotations.NotNull;
 
-import java.io.*;
-import java.lang.reflect.Field;
-import java.util.*;
+import java.io.File;
+import java.io.OutputStream;
+import java.io.PrintWriter;
 
 /**
- * This runner manages ctrl+break(ctrl+c) termination of process.
- *
  * @author traff
  */
 public class RunnerMediator {
-  private static final Logger LOG = Logger.getInstance("#com.intellij.execution.process.RunnerMediator");
+  public static final Logger LOG = Logger.getInstance("#com.intellij.execution.process.RunnerMediator");
 
-  private static final int SIGINT = 2;
-  private static final int SIGKILL = 9;
-  private static final String UID_KEY_NAME = "PROCESSUUID";
+  private static final char IAC = (char)5;
+  private static final char BRK = (char)3;
 
-  private RunnerMediator() {
+  /**
+   * Creates default runner mediator
+   * @return
+   */
+  public static RunnerMediator getInstance() {
+    return new RunnerMediator();
   }
 
-  public static String getRunnerPath() {
-    if (File.separatorChar == '\\') {
-      return RunnerMediatorManager.getInstance().getRunnerPath();
+  /**
+   * Sends sequence of two chars(codes 5 and 3) to a process output stream
+   */
+  private static void sendCtrlBreakThroughStream(Process process) {
+    OutputStream os = process.getOutputStream();
+    PrintWriter pw = new PrintWriter(os);
+    try {
+      pw.print(IAC);
+      pw.print(BRK);
+      pw.flush();
     }
-    else {
-      throw new IllegalStateException("There is no need of runner under unix based OS");
+    finally {
+      pw.close();
     }
   }
 
-  public static void injectRunnerCommand(@NotNull GeneralCommandLine commandLine) {
-    commandLine.getParametersList().addAt(0, commandLine.getExePath());
-    commandLine.setExePath(getRunnerPath());
-  }
-
-  public static String injectUid(@NotNull GeneralCommandLine commandLine) {
-    String uid = commandLine.getExePath() + ":" + UUID.randomUUID().toString();
-    commandLine.getEnvParams().put(UID_KEY_NAME, uid);
-    return uid;
-  }
-
-  public static ColoredProcessHandler createProcessWithStopInjections(@NotNull GeneralCommandLine commandLine)
-    throws ExecutionException {
+  /**
+   * In case of windows creates process with runner mediator(runnerw.exe) injected to command line string, which adds a capability
+   * to terminate process tree gracefully with ctrl+break.
+   *
+   * Returns appropriate process handle, which in case of Unix is able to terminate whole process tree by sending sig_kill
+   *
+   */
+  public ProcessHandler createProcess(@NotNull GeneralCommandLine commandLine) throws ExecutionException {
     if (isWindows()) {
       injectRunnerCommand(commandLine);
     }
 
-    Process p = commandLine.createProcess();
+    Process process = commandLine.createProcess();
 
-    return new CustomDestroyProcessHandler(p, commandLine);
+    return createProcessHandler(process, commandLine);
   }
 
-  public static boolean canSendSignals() {
-    return SystemInfo.isLinux || SystemInfo.isMac;
-  }
-
-  public static boolean isWindows() {
-    if (File.separatorChar == '\\') {
-      return true;
-    }
-    return false;
-  }
-
-
-  public static void sendSigInt(Process process) {
-    sendSignal(process, SIGINT);
-  }
-
-  public static void sendSigKill(Process process) {
-    sendSignal(process, SIGKILL);
-  }
-
-  public static void sendSignal(Process process, int signal) {
-    if (C_LIB == null) {
-      throw new IllegalStateException("no CLIB");
-    }
-    int our_pid = C_LIB.getpid();
-    int process_pid = getProcessPid(process);
-
-    try {
-      String[] psCmd = getCmd();
-      Process p = Runtime.getRuntime().exec(psCmd);
-
-      @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
-      BufferedReader stdInput = new BufferedReader(new
-                                                     InputStreamReader(p.getInputStream()));
-
-      BufferedReader stdError = new BufferedReader(new
-                                                     InputStreamReader(p.getErrorStream()));
-      try {
-        String s;
-        stdInput.readLine(); //ps output header
-        int foundPid = 0;
-        while ((s = stdInput.readLine()) != null) {
-          StringTokenizer st = new StringTokenizer(s, " ");
-
-          int parent_pid = Integer.parseInt(st.nextToken());
-          int pid = Integer.parseInt(st.nextToken());
-
-          ProcessInfo.register(pid, parent_pid);
-
-          if (pid == process_pid) {
-            if (parent_pid == our_pid) {
-              foundPid = pid;
-            }
-            else {
-              throw new IllegalStateException("process is not our child");
-            }
-          }
-        }
-
-        if (foundPid != 0) {
-          ProcessInfo.killProcTree(foundPid, signal);
-        }
-        else {
-          throw new IllegalStateException("process not found: " + process_pid + ", idea pid =" + our_pid);
-        }
-
-        StringBuffer errorStr = new StringBuffer();
-        while ((s = stdError.readLine()) != null) {
-          errorStr.append(s).append("\n");
-        }
-        if (errorStr.length() > 0) {
-          throw new IllegalStateException("error:" + errorStr.toString());
-        }
-      }
-      finally {
-        stdInput.close();
-        stdError.close();
-      }
-    }
-    catch (IOException e) {
-      throw new IllegalStateException(e);
-    }
+  /**
+   * Creates process handler for process able to be terminated with method RunnerMediator.destroyProcess.
+   * You can override this method to customize process handler creation.
+   * @return
+   */
+  protected ProcessHandler createProcessHandler(@NotNull Process process, @NotNull GeneralCommandLine commandLine) {
+    return new CustomDestroyProcessHandler(process, commandLine);
   }
 
-  private static int getProcessPid(Process proc) {
-    try {
-      Field f = proc.getClass().getDeclaredField("pid");
-      f.setAccessible(true);
-      int pid = ((Number)f.get(proc)).intValue();
-      return pid;
-    }
-    catch (NoSuchFieldException e) {
-      throw new IllegalStateException("system is not linux", e);
-    }
-    catch (IllegalAccessException e) {
-      throw new IllegalStateException("system is not linux", e);
-    }
-  }
 
-  private static String[] getCmd() {
-    if (SystemInfo.isLinux) {
-      return new String[]{"ps", "e", "--format", "%P%p%a"};
-    }
-    else if (SystemInfo.isMac) {
-      return new String[]{"ps", "-ax", "-E", "-o", "ppid,pid,command"};
+  private String getRunnerPath() {
+    if (File.separatorChar == '\\') {
+      return RunnerMediatorManager.getInstance().getRunnerPath();
     }
     else {
-      throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
+      throw new IllegalStateException("There is no need of runner under unix based OS");
     }
   }
 
-  private static boolean containsMarker(@NotNull String environ, @NotNull String uid) {
-    return environ.contains(uid);
-  }
-
-  @NotNull
-  public static String readProcEnviron(int child_pid) throws FileNotFoundException {
-    StringBuffer res = new StringBuffer();
-    Scanner s = new Scanner(new File("/proc/" + child_pid + "/environ"));
-    while (s.hasNextLine()) {
-      res.append(s).append("\n");
-    }
-    return res.toString();
+  private void injectRunnerCommand(@NotNull GeneralCommandLine commandLine) {
+    commandLine.getParametersList().addAt(0, commandLine.getExePath());
+    commandLine.setExePath(getRunnerPath());
   }
 
-  private static void sendSignal(int pid, int signal) {
-    C_LIB.kill(pid, signal);
+  public static boolean isUnix() {
+    return SystemInfo.isLinux || SystemInfo.isMac;
   }
 
-  interface CLib extends Library {
-    int getpid();
-
-    int kill(int pid, int signal);
+  public static boolean isWindows() {
+    if (File.separatorChar == '\\') {
+      return true;
+    }
+    return false;
   }
 
-  static CLib C_LIB;
-
-  static {
+  /**
+   * Destroys process tree: in case of windows via imitating ctrl+break, in case of unix via sending sig_kill to every process in tree.
+   * @param process to kill with all subprocesses
+   */
+  public static boolean destroyProcess(Process process) {
     try {
-      if (!Platform.isWindows()) {
-
-        C_LIB = ((CLib)Native.loadLibrary("c", CLib.class));
+      if (isWindows()) {
+        sendCtrlBreakThroughStream(process);
+        return true;
+      }
+      else if (isUnix()) {
+        UnixProcessManager.sendSigKillToProcessTree(process);
+        return true;
+      }
+      else {
+        return false;
       }
     }
     catch (Exception e) {
-      C_LIB = null;
+      LOG.error("Couldn't terminate the process", e);
+      return false;
     }
   }
 
+  /**
+   *
+   */
   public static class CustomDestroyProcessHandler extends ColoredProcessHandler {
-    private static final char IAC = (char)5;
-    private static final char BRK = (char)3;
 
-    private final String myCommand;
 
     public CustomDestroyProcessHandler(@NotNull Process process,
                                        @NotNull GeneralCommandLine commandLine) {
       super(process, commandLine.getCommandLineString());
-      myCommand = commandLine.getExePath();
     }
 
     @Override
     protected void destroyProcessImpl() {
-      if (!doCustomDestroy()) {
+      if (!RunnerMediator.destroyProcess(getProcess())) {
         super.destroyProcessImpl();
       }
     }
-
-    private boolean doCustomDestroy() {
-      try {
-        if (isWindows()) {
-          sendCtrlBreakThroughStream();
-          return true;
-        }
-        else if (canSendSignals()) {
-          sendSigKill(getProcess());
-          return true;
-        }
-        else {
-          return false;
-        }
-      }
-      catch (Exception e) {
-        LOG.error("Couldn't terminate the process", e);
-        return false;
-      }
-    }
-
-    private void sendCtrlBreakThroughStream() {
-      OutputStream os = getProcessInput();
-      PrintWriter pw = new PrintWriter(os);
-      try {
-        pw.print(IAC);
-        pw.print(BRK);
-        pw.flush();
-      }
-      finally {
-        pw.close();
-      }
-    }
-  }
-
-  private static class ProcessInfo {
-
-    private ProcessInfo() {
-    }
-
-    private static Map<Integer, List<Integer>> BY_PARENT = Maps.newTreeMap(); // pid -> list of children pids
-
-    public static void register(Integer pid, Integer parentPid) {
-      List<Integer> children = BY_PARENT.get(parentPid);
-      if (children == null) children = Lists.newLinkedList();
-      children.add(pid);
-      BY_PARENT.put(parentPid, children);
-    }
-
-    static void killProcTree(int pid, int signal) {
-      List<Integer> children = BY_PARENT.get(pid);
-      if (children != null) {
-        for (int child : children) killProcTree(child, signal);
-      }
-      sendSignal(pid, signal);
-    }
   }
 }
index 3863a25fafb6da57ecf587633e1ad25fea65dc32..cedf335b85227dd44184b8cc7e1438a5dc601732 100644 (file)
@@ -21,7 +21,6 @@ import com.intellij.openapi.components.ServiceManager;
  * @author traff
  */
 public class RunnerMediatorManager {
-
   public static RunnerMediatorManager getInstance() {
     return ServiceManager.getService(RunnerMediatorManager.class);
   }
diff --git a/platform/platform-impl/src/com/intellij/execution/process/UnixProcessManager.java b/platform/platform-impl/src/com/intellij/execution/process/UnixProcessManager.java
new file mode 100644 (file)
index 0000000..c37a9c7
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2000-2010 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.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.intellij.openapi.util.SystemInfo;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Platform;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.StringTokenizer;
+
+/**
+ * Utility class to terminate unix processes.
+ *
+ * @author traff
+ */
+public class UnixProcessManager {
+  public static final int SIGINT = 2;
+  public static final int SIGKILL = 9;
+
+  private static CLib C_LIB;
+
+  static {
+    try {
+      if (!Platform.isWindows()) {
+        C_LIB = ((CLib)Native.loadLibrary("c", CLib.class));
+      }
+    }
+    catch (Exception e) {
+      C_LIB = null;
+    }
+  }
+
+  private UnixProcessManager() {
+  }
+
+  public static void sendSignal(Process process, int signal) {
+    int process_pid = getProcessPid(process);
+    sendSignal(process_pid, signal);
+  }
+
+  public static void sendSignal(int pid, int signal) {
+    checkCLib();
+    C_LIB.kill(pid, signal);
+  }
+
+  private static void checkCLib() {
+    if (C_LIB == null) {
+      throw new IllegalStateException("System is not unix(couldn't load c library)");
+    }
+  }
+
+  public static void sendSigIntToProcessTree(Process process) {
+    sendSignalToProcessTree(process, SIGINT);
+  }
+
+  public static void sendSigKillToProcessTree(Process process) {
+    sendSignalToProcessTree(process, SIGKILL);
+  }
+
+  /**
+   * Sends signal to every child process of a tree root process
+   * @param process tree root process
+   *
+   */
+  public static void sendSignalToProcessTree(Process process, int signal) {
+    checkCLib();
+
+    int our_pid = C_LIB.getpid();
+    int process_pid = getProcessPid(process);
+
+    try {
+      String[] psCmd = getPSCmd();
+      Process p = Runtime.getRuntime().exec(psCmd);
+
+      ProcessInfo processInfo = new ProcessInfo();
+
+      @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
+      BufferedReader stdInput = new BufferedReader(new
+                                                     InputStreamReader(p.getInputStream()));
+      BufferedReader stdError = new BufferedReader(new
+                                                     InputStreamReader(p.getErrorStream()));
+      try {
+        String s;
+        stdInput.readLine(); //ps output header
+        int foundPid = 0;
+        while ((s = stdInput.readLine()) != null) {
+          StringTokenizer st = new StringTokenizer(s, " ");
+
+          int parent_pid = Integer.parseInt(st.nextToken());
+          int pid = Integer.parseInt(st.nextToken());
+
+          processInfo.register(pid, parent_pid);
+
+          if (pid == process_pid) {
+            if (parent_pid == our_pid) {
+              foundPid = pid;
+            }
+            else {
+              throw new IllegalStateException("process is not our child");
+            }
+          }
+        }
+
+        if (foundPid != 0) {
+          processInfo.killProcTree(foundPid, signal);
+        }
+        else {
+          throw new IllegalStateException("process not found: " + process_pid + ", idea pid =" + our_pid);
+        }
+
+        StringBuffer errorStr = new StringBuffer();
+        while ((s = stdError.readLine()) != null) {
+          errorStr.append(s).append("\n");
+        }
+        if (errorStr.length() > 0) {
+          throw new IllegalStateException("error:" + errorStr.toString());
+        }
+      }
+      finally {
+        stdInput.close();
+        stdError.close();
+      }
+    }
+    catch (IOException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  public static int getProcessPid(Process process) {
+    try {
+      Field f = process.getClass().getDeclaredField("pid");
+      f.setAccessible(true);
+      return ((Number)f.get(process)).intValue();
+    }
+    catch (NoSuchFieldException e) {
+      throw new IllegalStateException("system is not unix", e);
+    }
+    catch (IllegalAccessException e) {
+      throw new IllegalStateException("system is not unix", e);
+    }
+  }
+
+  public static String[] getPSCmd() {
+    if (SystemInfo.isLinux) {
+      return new String[]{"ps", "e", "--format", "%P%p%a"};
+    }
+    else if (SystemInfo.isMac) {
+      return new String[]{"ps", "-ax", "-E", "-o", "ppid,pid,command"};
+    }
+    else {
+      throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
+    }
+  }
+
+  public static boolean containsMarker(@NotNull String environ, @NotNull String uid) {
+    return environ.contains(uid);
+  }
+
+  @NotNull
+  public static String readProcEnviron(int child_pid) throws FileNotFoundException {
+    StringBuffer res = new StringBuffer();
+    Scanner s = new Scanner(new File("/proc/" + child_pid + "/environ"));
+    while (s.hasNextLine()) {
+      res.append(s).append("\n");
+    }
+    return res.toString();
+  }
+
+
+  public interface CLib extends Library {
+    int getpid();
+
+    int kill(int pid, int signal);
+  }
+
+  private static class ProcessInfo {
+    private Map<Integer, List<Integer>> BY_PARENT = Maps.newTreeMap(); // pid -> list of children pids
+
+    public void register(Integer pid, Integer parentPid) {
+      List<Integer> children = BY_PARENT.get(parentPid);
+      if (children == null) children = Lists.newLinkedList();
+      children.add(pid);
+      BY_PARENT.put(parentPid, children);
+    }
+
+    void killProcTree(int pid, int signal) {
+      List<Integer> children = BY_PARENT.get(pid);
+      if (children != null) {
+        for (int child : children) killProcTree(child, signal);
+      }
+      sendSignal(pid, signal);
+    }
+  }
+}