diff --git a/core/src/main/java/hudson/Proc.java b/core/src/main/java/hudson/Proc.java
index 7d54ae367f..29bc3e74cc 100644
--- a/core/src/main/java/hudson/Proc.java
+++ b/core/src/main/java/hudson/Proc.java
@@ -235,7 +235,7 @@ public abstract class Proc {
/**
* Destroys the child process without join.
*/
- private void destroy() {
+ private void destroy() throws InterruptedException {
ProcessTree.get().killAll(proc,cookie);
}
diff --git a/core/src/main/java/hudson/util/ProcessKiller.java b/core/src/main/java/hudson/util/ProcessKiller.java
new file mode 100644
index 0000000000..62a1285caa
--- /dev/null
+++ b/core/src/main/java/hudson/util/ProcessKiller.java
@@ -0,0 +1,86 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2010, InfraDNA, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.util;
+
+import hudson.ExtensionList;
+import hudson.ExtensionPoint;
+import hudson.model.Hudson;
+import hudson.util.ProcessTree.OSProcess;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Extension point that defines more elaborate way of killing processes, such as
+ * sudo or pfexec, for {@link ProcessTree}.
+ *
+ *
Lifecycle
+ *
+ * Each implementation of {@link ProcessKiller} is instantiated once on the master.
+ * Whenever a process needs to be killed, those implementations are serialized and sent over
+ * to the appropriate slave, then the {@link #kill(OSProcess)} method is invoked
+ * to attempt to kill the process.
+ *
+ *
+ * One of the consequences of this design is that the implementation should be stateless
+ * and concurrent-safe. That is, the {@link #kill(OSProcess)} method can be invoked by multiple threads
+ * concurrently on the single instance.
+ *
+ *
+ * Another consequence of this design is that if your {@link ProcessKiller} requires configuration,
+ * it needs to be serializable, and configuration needs to be updated atomically, as another
+ * thread may be calling into {@link #kill(OSProcess)} just when you are updating your configuration.
+ *
+ * @author jpederzolli
+ * @author Kohsuke Kawaguchi
+ * @since 1.362
+ */
+public abstract class ProcessKiller implements ExtensionPoint, Serializable {
+ /**
+ * Returns all the registered {@link ProcessKiller} descriptors.
+ */
+ public static ExtensionList all() {
+ return Hudson.getInstance().getExtensionList(ProcessKiller.class);
+ }
+
+ /**
+ * Attempts to kill the given process.
+ *
+ * @param process process to be killed. Always a {@linkplain ProcessTree.Local local process}.
+ * @return
+ * true if the killing was successful, and Hudson won't try to use other {@link ProcessKiller}
+ * implementations to kill the process. false if the killing failed or is unattempted, and Hudson will continue
+ * to use the rest of the {@link ProcessKiller} implementations to try to kill the process.
+ * @throws IOException
+ * The caller will log this exception and otherwise treat as if the method returned false, and moves on
+ * to the next killer.
+ * @throws InterruptedException
+ * if the callee performs a time consuming operation and if the thread is canceled, do not catch
+ * {@link InterruptedException} and just let it thrown from the method.
+ */
+ public abstract boolean kill(ProcessTree.OSProcess process) throws IOException, InterruptedException;
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java
index c4b0926876..ea5cf46b2c 100644
--- a/core/src/main/java/hudson/util/ProcessTree.java
+++ b/core/src/main/java/hudson/util/ProcessTree.java
@@ -30,8 +30,10 @@ import com.sun.jna.ptr.IntByReference;
import hudson.EnvVars;
import hudson.Util;
import hudson.model.Hudson;
+import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
+import hudson.slaves.SlaveComputer;
import hudson.util.ProcessTree.OSProcess;
import static hudson.util.jna.GNUCLibrary.LIBC;
@@ -89,6 +91,11 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
*/
protected final Map processes = new HashMap();
+ /**
+ * Lazily obtained {@link ProcessKiller}s to be applied on this process tree.
+ */
+ private transient volatile List killers;
+
// instantiation only allowed for subtypes in this class
private ProcessTree() {}
@@ -125,7 +132,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
* them all. This is suitable for locating daemon processes
* that cannot be tracked by the regular
*/
- public abstract void killAll(Map modelEnvVars);
+ public abstract void killAll(Map modelEnvVars) throws InterruptedException;
/**
* Convenience method that does {@link #killAll(Map)} and {@link OSProcess#killRecursively()}.
@@ -134,7 +141,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
*
* Either of the parameter can be null.
*/
- public void killAll(Process proc, Map modelEnvVars) {
+ public void killAll(Process proc, Map modelEnvVars) throws InterruptedException {
LOGGER.fine("killAll: process="+proc+" and envs="+modelEnvVars);
OSProcess p = get(proc);
if(p!=null) p.killRecursively();
@@ -142,6 +149,24 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
killAll(modelEnvVars);
}
+ /**
+ * Obtains the list of killers.
+ */
+ /*package*/ final List getKillers() throws InterruptedException {
+ if (killers==null)
+ try {
+ killers = SlaveComputer.getChannelToMaster().call(new Callable, IOException>() {
+ public List call() throws IOException {
+ return new ArrayList(ProcessKiller.all());
+ }
+ });
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Failed to obtain killers",e);
+ killers = Collections.emptyList();
+ }
+ return killers;
+ }
+
/**
* Represents a process.
*/
@@ -181,7 +206,17 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
/**
* Kills this process.
*/
- public abstract void kill();
+ public abstract void kill() throws InterruptedException;
+
+ void killByKiller() throws InterruptedException {
+ for (ProcessKiller killer : getKillers())
+ try {
+ if (killer.kill(this))
+ break;
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Failed to kill pid="+getPid(),e);
+ }
+ }
/**
* Kills this process and all the descendants.
@@ -191,7 +226,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
* where the recursive operation is not supported, this just kills
* the current process.
*/
- public abstract void killRecursively();
+ public abstract void killRecursively() throws InterruptedException;
/**
* Gets the command-line arguments of this process.
@@ -316,7 +351,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
/**
* Empty process list as a default value if the platform doesn't support it.
*/
- private static final ProcessTree DEFAULT = new ProcessTree() {
+ private static final ProcessTree DEFAULT = new Local() {
public OSProcess get(final Process proc) {
return new OSProcess(-1) {
public OSProcess getParent() {
@@ -328,8 +363,9 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
proc.destroy();
}
- public void kill() {
+ public void kill() throws InterruptedException {
proc.destroy();
+ killByKiller();
}
public List getArguments() {
@@ -348,7 +384,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
};
- private static final class Windows extends ProcessTree {
+ private static final class Windows extends Local {
Windows() {
for (final WinProcess p : WinProcess.all()) {
int pid = p.getPid();
@@ -366,9 +402,10 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
p.killRecursively();
}
- public void kill() {
+ public void kill() throws InterruptedException {
LOGGER.finer("Killing "+getPid());
p.kill();
+ killByKiller();
}
@Override
@@ -392,7 +429,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
return get(new WinProcess(proc).getPid());
}
- public void killAll(Map modelEnvVars) {
+ public void killAll(Map modelEnvVars) throws InterruptedException {
for( OSProcess p : this) {
if(p.getPid()<10)
continue; // ignore system processes like "idle process"
@@ -421,7 +458,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
}
}
- static abstract class Unix extends ProcessTree {
+ static abstract class Unix extends Local {
@Override
public OSProcess get(Process proc) {
try {
@@ -433,7 +470,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
}
}
- public void killAll(Map modelEnvVars) {
+ public void killAll(Map modelEnvVars) throws InterruptedException {
for (OSProcess p : this)
if(p.hasMatchingEnvVars(modelEnvVars))
p.killRecursively();
@@ -488,7 +525,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
/**
* Tries to kill this process.
*/
- public void kill() {
+ public void kill() throws InterruptedException {
try {
int pid = getPid();
LOGGER.fine("Killing pid="+pid);
@@ -505,10 +542,10 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
// otherwise log and let go. I need to see when this happens
LOGGER.log(Level.INFO, "Failed to terminate pid="+getPid(),e);
}
-
+ killByKiller();
}
- public void killRecursively() {
+ public void killRecursively() throws InterruptedException {
LOGGER.fine("Recursively killing pid="+getPid());
for (OSProcess p : getChildren())
p.killRecursively();
@@ -1026,6 +1063,15 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
private static final int KERN_PROCARGS2 = 49;
}
+ /**
+ * Represents a local process tree, where this JVM and the process tree run on the same system.
+ * (The opposite of {@link Remote}.)
+ */
+ public static abstract class Local extends ProcessTree {
+ Local() {
+ }
+ }
+
/**
* Represents a process tree over a channel.
*/
@@ -1044,7 +1090,7 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
}
@Override
- public void killAll(Map modelEnvVars) {
+ public void killAll(Map modelEnvVars) throws InterruptedException {
proxy.killAll(modelEnvVars);
}
@@ -1068,11 +1114,11 @@ public abstract class ProcessTree implements Iterable, IProcessTree,
return get(p.getPid());
}
- public void kill() {
+ public void kill() throws InterruptedException {
proxy.kill();
}
- public void killRecursively() {
+ public void killRecursively() throws InterruptedException {
proxy.killRecursively();
}
diff --git a/core/src/main/java/hudson/util/ProcessTreeRemoting.java b/core/src/main/java/hudson/util/ProcessTreeRemoting.java
index c82024c9ed..b3fa65f061 100644
--- a/core/src/main/java/hudson/util/ProcessTreeRemoting.java
+++ b/core/src/main/java/hudson/util/ProcessTreeRemoting.java
@@ -17,14 +17,14 @@ import java.util.Map;
*/
public class ProcessTreeRemoting {
public interface IProcessTree {
- void killAll(Map modelEnvVars);
+ void killAll(Map modelEnvVars) throws InterruptedException;
}
public interface IOSProcess {
int getPid();
IOSProcess getParent();
- void kill();
- void killRecursively();
+ void kill() throws InterruptedException;
+ void killRecursively() throws InterruptedException;
List getArguments();
EnvVars getEnvironmentVariables();
T act(ProcessCallable callable) throws IOException, InterruptedException;