From 3d44678e8ca1ac5ed4f020ebb68ff921dc0c4457 Mon Sep 17 00:00:00 2001 From: kohsuke Date: Thu, 10 Jun 2010 01:44:15 +0000 Subject: [PATCH] Added a new extension point to plug in custom utility to kill processes. git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@31898 71c3de6d-444a-0410-be80-ed276b4c234a --- core/src/main/java/hudson/Proc.java | 2 +- .../main/java/hudson/util/ProcessKiller.java | 86 +++++++++++++++++++ .../main/java/hudson/util/ProcessTree.java | 80 +++++++++++++---- .../java/hudson/util/ProcessTreeRemoting.java | 6 +- 4 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/hudson/util/ProcessKiller.java 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;