diff --git a/core/pom.xml b/core/pom.xml index 827d6e3c5ec7..cb7f3d52d03e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -515,7 +515,7 @@ THE SOFTWARE. org.jvnet.winp winp - 1.26 + 1.27 org.jenkins-ci diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java index 0ef4c2192e7d..843653f72da8 100644 --- a/core/src/main/java/hudson/util/ProcessTree.java +++ b/core/src/main/java/hudson/util/ProcessTree.java @@ -144,6 +144,8 @@ public final Iterator iterator() { */ public abstract void killAll(Map modelEnvVars) throws InterruptedException; + private final long softKillWaitSeconds = Integer.getInteger("SoftKillWaitSeconds", 2 * 60); // by default processes get at most 2 minutes to respond to SIGTERM (JENKINS-17116) + /** * Convenience method that does {@link #killAll(Map)} and {@link OSProcess#killRecursively()}. * This is necessary to reliably kill the process and its descendants, as some OS @@ -501,6 +503,8 @@ public void killRecursively() throws InterruptedException { return; LOGGER.log(FINER, "Killing recursively {0}", getPid()); + // Firstly try to kill the root process gracefully, then do a forcekill if it does not help (algorithm is described in JENKINS-17116) + killSoftly(); p.killRecursively(); killByKiller(); } @@ -512,10 +516,39 @@ public void kill() throws InterruptedException { } LOGGER.log(FINER, "Killing {0}", getPid()); + // Firstly try to kill it gracefully, then do a forcekill if it does not help (algorithm is described in JENKINS-17116) + killSoftly(); p.kill(); killByKiller(); } + private void killSoftly() throws InterruptedException { + // send Ctrl+C to the process + try { + if (!p.sendCtrlC()) { + return; + } + } + catch (WinpException e) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Failed to send CTRL+C to pid=" + getPid(), e); + } + return; + } + + // after that wait for it to cease to exist + long deadline = System.nanoTime() + softKillWaitSeconds * 1000000000; + int sleepTime = 10; // initially we sleep briefly, then sleep up to 1sec + do { + if (!p.isRunning()) { + break; + } + + Thread.sleep(sleepTime); + sleepTime = Math.min(sleepTime * 2, 1000); + } while (System.nanoTime() < deadline); + } + @Override public synchronized List getArguments() { if(args==null) { @@ -718,12 +751,29 @@ protected final File getFile(String relativePath) { * Tries to kill this process. */ public void kill() throws InterruptedException { + // after sending SIGTERM, wait for the process to cease to exist + long deadline = System.nanoTime() + softKillWaitSeconds * 1000000000; + kill(deadline); + } + + private void kill(long deadline) throws InterruptedException { if (getVeto() != null) return; try { int pid = getPid(); LOGGER.fine("Killing pid="+pid); UnixReflection.destroy(pid); + // after sending SIGTERM, wait for the process to cease to exist + int sleepTime = 10; // initially we sleep briefly, then sleep up to 1sec + File status = getFile("status"); + do { + if (!status.exists()) { + break; // status is gone, process therefore as well + } + + Thread.sleep(sleepTime); + sleepTime = Math.min(sleepTime * 2, 1000); + } while (System.nanoTime() < deadline); } catch (IllegalAccessException e) { // this is impossible IllegalAccessError x = new IllegalAccessError(); @@ -740,11 +790,22 @@ public void kill() throws InterruptedException { } public void killRecursively() throws InterruptedException { + // after sending SIGTERM, wait for the processes to cease to exist until the deadline + long deadline = System.nanoTime() + softKillWaitSeconds * 1000000000; + killRecursively(deadline); + } + + private void killRecursively(long deadline) throws InterruptedException { // We kill individual processes of a tree, so handling vetoes inside #kill() is enough for UnixProcess es LOGGER.fine("Recursively killing pid="+getPid()); - for (OSProcess p : getChildren()) - p.killRecursively(); - kill(); + for (OSProcess p : getChildren()) { + if (p instanceof UnixProcess) { + ((UnixProcess)p).killRecursively(deadline); + } else { + p.killRecursively(); // should not happen, fallback to non-deadline version + } + } + kill(deadline); } /**