/* * The MIT License * * Copyright 2014 Jesse Glick. * * 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 org.jenkinsci.plugins.durabletask; import com.sun.jna.Library; import com.sun.jna.Native; import hudson.Launcher; import hudson.remoting.VirtualChannel; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.security.MasterToSlaveCallable; /** * Utility class to track whether a given process is still alive. */ final class ProcessLiveness { private static final Logger LOGGER = Logger.getLogger(ProcessLiveness.class.getName()); private static final Map<Launcher,Boolean> workingLaunchers = Collections.synchronizedMap(new WeakHashMap<Launcher,Boolean>()); /** * Determines whether a process is believed to still be alive. * @param channel a connection to the machine on which it would be running * @param pid a process ID * @param launcher a way to start processes * @return true if it is apparently still alive (or we cannot tell); false if it is believed to not be running */ public static boolean isAlive(VirtualChannel channel, int pid, Launcher launcher) throws IOException, InterruptedException { Boolean working = workingLaunchers.get(launcher); if (working == null) { // Check to see if our logic correctly reports that an unlikely PID is not running. working = !_isAlive(channel, 9999, launcher); workingLaunchers.put(launcher, working); if (working) { LOGGER.log(Level.FINE, "{0} on {1} appears to be working", new Object[] {launcher, channel}); } else { LOGGER.log(Level.WARNING, "{0} on {1} does not seem able to determine whether processes are alive or not", new Object[] {launcher, channel}); // TODO Channel.toString should report slave name, but would be nice to also report OS } } if (!working) { return true; } return _isAlive(channel, pid, launcher); } private static boolean _isAlive(VirtualChannel channel, int pid, Launcher launcher) throws IOException, InterruptedException { if (launcher instanceof Launcher.LocalLauncher || launcher instanceof Launcher.RemoteLauncher) { try { boolean alive = channel.call(new Liveness(pid)); LOGGER.log(Level.FINER, "{0} is alive? {1}", new Object[] {pid, alive}); return alive; } catch (RuntimeException x) { LOGGER.log(Level.WARNING, "cannot determine liveness of " + pid, x); return true; } } else { // Using a special launcher; let it decide how to do this. // TODO perhaps this should be a method in Launcher, with the following fallback in DecoratedLauncher: return launcher.launch().cmds("ps", "-o", "pid=", Integer.toString(pid)).quiet(true).join() == 0; } } private static final class Liveness extends MasterToSlaveCallable<Boolean,RuntimeException> { private final int pid; Liveness(int pid) { this.pid = pid; } @Override public Boolean call() throws RuntimeException { // JNR-POSIX does not seem to work on FreeBSD at least, so using JNA instead. LibC libc = LibC.INSTANCE; if (libc.getpgid(0) == -1) { throw new IllegalStateException("getpgid does not seem to work on this platform"); } return libc.getpgid(pid) != -1; } } private interface LibC extends Library { /** * Get the process group ID for a process. * From <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgid.html">Open Group Base Specifications Issue 7</a>: * <blockquote> * The getpgid() function shall return the process group ID of the process whose process ID is equal to pid. * If pid is equal to 0, getpgid() shall return the process group ID of the calling process. * Upon successful completion, getpgid() shall return a process group ID. Otherwise, it shall return (pid_t)-1 and set errno to indicate the error. * The getpgid() function shall fail if: […] There is no process with a process ID equal to pid. […] * </blockquote> */ int getpgid(int pid); LibC INSTANCE = (LibC) Native.loadLibrary("c", LibC.class); } private ProcessLiveness() {} }