Proc.java 18.1 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, CloudBees, Inc.
K
kohsuke 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * 
 * 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.
 */
K
kohsuke 已提交
24 25
package hudson;

26
import hudson.Launcher.ProcStarter;
27
import hudson.model.TaskListener;
28
import hudson.remoting.Channel;
K
Kohsuke Kawaguchi 已提交
29 30
import hudson.util.DaemonThreadFactory;
import hudson.util.ExceptionCatchingThreadFactory;
31
import hudson.util.NamingThreadFactory;
32
import hudson.util.NullStream;
K
kohsuke 已提交
33
import hudson.util.StreamCopyThread;
34
import hudson.util.ProcessTree;
35
import org.apache.commons.io.input.NullInputStream;
K
kohsuke 已提交
36

K
kohsuke 已提交
37 38 39 40
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
41
import java.util.Locale;
K
kohsuke 已提交
42
import java.util.Map;
43
import java.util.concurrent.CancellationException;
44
import java.util.concurrent.CountDownLatch;
K
kohsuke 已提交
45
import java.util.concurrent.ExecutionException;
46 47
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
K
kohsuke 已提交
48
import java.util.concurrent.Future;
49
import java.util.concurrent.TimeUnit;
J
jglick 已提交
50 51
import java.util.logging.Level;
import java.util.logging.Logger;
K
kohsuke 已提交
52 53 54 55 56 57 58 59 60

/**
 * External process wrapper.
 *
 * <p>
 * Used for launching, monitoring, waiting for a process.
 *
 * @author Kohsuke Kawaguchi
 */
K
kohsuke 已提交
61
public abstract class Proc {
62
    protected Proc() {}
K
kohsuke 已提交
63

64 65 66 67 68
    /**
     * Checks if the process is still alive.
     */
    public abstract boolean isAlive() throws IOException, InterruptedException;

K
kohsuke 已提交
69 70 71 72 73 74 75
    /**
     * Terminates the process.
     *
     * @throws IOException
     *      if there's an error killing a process
     *      and a stack trace could help the trouble-shooting.
     */
76
    public abstract void kill() throws IOException, InterruptedException;
K
kohsuke 已提交
77

K
kohsuke 已提交
78
    /**
79 80 81 82
     * Waits for the completion of the process.
     *
     * Unless the caller opts to pump the streams via {@link #getStdout()} etc.,
     * this method also blocks until we finish reading everything that the process has produced
K
kohsuke 已提交
83
     * to stdout/stderr.
K
kohsuke 已提交
84 85 86 87 88 89 90 91 92 93
     *
     * <p>
     * If the thread is interrupted while waiting for the completion
     * of the process, this method terminates the process and
     * exits with a non-zero exit code.
     *
     * @throws IOException
     *      if there's an error launching/joining a process
     *      and a stack trace could help the trouble-shooting.
     */
94
    public abstract int join() throws IOException, InterruptedException;
K
kohsuke 已提交
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
    /**
     * Returns an {@link InputStream} to read from {@code stdout} of the child process.
     * <p>
     * When this method returns null, {@link Proc} will internally pump the output from
     * the child process to your {@link OutputStream} of choosing.
     *
     * @return
     *      null unless {@link ProcStarter#readStdout()} is used to indicate
     *      that the caller intends to pump the stream by itself.
     * @since 1.399
     */
    public abstract InputStream getStdout();

    /**
     * Returns an {@link InputStream} to read from {@code stderr} of the child process.
     * <p>
     * When this method returns null, {@link Proc} will internally pump the output from
     * the child process to your {@link OutputStream} of choosing.
     *
     * @return
     *      null unless {@link ProcStarter#readStderr()} is used to indicate
     *      that the caller intends to pump the stream by itself.
     * @since 1.399
     */
    public abstract InputStream getStderr();

    /**
     * Returns an {@link OutputStream} to write to {@code stdin} of the child process.
     * <p>
     * When this method returns null, {@link Proc} will internally pump the {@link InputStream}
     * of your choosing to the child process.
     *
     * @return
     *      null unless {@link ProcStarter#writeStdin()} is used to indicate
     *      that the caller intends to pump the stream by itself.
     * @since 1.399
     */
    public abstract OutputStream getStdin();

135
    private static final ExecutorService executor = Executors.newCachedThreadPool(new ExceptionCatchingThreadFactory(new NamingThreadFactory(new DaemonThreadFactory(), "Proc.executor")));
K
Kohsuke Kawaguchi 已提交
136
    
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    /**
     * Like {@link #join} but can be given a maximum time to wait.
     * @param timeout number of time units
     * @param unit unit of time
     * @param listener place to send messages if there are problems, incl. timeout
     * @return exit code from the process
     * @throws IOException for the same reasons as {@link #join}
     * @throws InterruptedException for the same reasons as {@link #join}
     * @since 1.363
     */
    public final int joinWithTimeout(final long timeout, final TimeUnit unit,
            final TaskListener listener) throws IOException, InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        try {
            executor.submit(new Runnable() {
                public void run() {
                    try {
                        if (!latch.await(timeout, unit)) {
                            listener.error("Timeout after " + timeout + " " +
                                    unit.toString().toLowerCase(Locale.ENGLISH));
                            kill();
                        }
                    } catch (InterruptedException x) {
K
Kohsuke Kawaguchi 已提交
160
                        x.printStackTrace(listener.error("Failed to join a process"));
161
                    } catch (IOException x) {
K
Kohsuke Kawaguchi 已提交
162
                        x.printStackTrace(listener.error("Failed to join a process"));
163
                    } catch (RuntimeException x) {
K
Kohsuke Kawaguchi 已提交
164
                        x.printStackTrace(listener.error("Failed to join a process"));
165 166 167 168 169 170 171 172 173
                    }
                }
            });
            return join();
        } finally {
            latch.countDown();
        }
    }
    
K
kohsuke 已提交
174 175 176 177 178
    /**
     * Locally launched process.
     */
    public static final class LocalProc extends Proc {
        private final Process proc;
179
        private final Thread copier,copier2;
K
kohsuke 已提交
180
        private final OutputStream out;
K
kohsuke 已提交
181
        private final EnvVars cookie;
182
        private final String name;
K
kohsuke 已提交
183

184 185 186
        private final InputStream stdout,stderr;
        private final OutputStream stdin;

K
kohsuke 已提交
187 188 189
        public LocalProc(String cmd, Map<String,String> env, OutputStream out, File workDir) throws IOException {
            this(cmd,Util.mapToEnv(env),out,workDir);
        }
K
kohsuke 已提交
190

K
kohsuke 已提交
191 192 193
        public LocalProc(String[] cmd, Map<String,String> env,InputStream in, OutputStream out) throws IOException {
            this(cmd,Util.mapToEnv(env),in,out);
        }
K
kohsuke 已提交
194

K
kohsuke 已提交
195 196 197
        public LocalProc(String cmd,String[] env,OutputStream out, File workDir) throws IOException {
            this( Util.tokenize(cmd), env, out, workDir );
        }
K
kohsuke 已提交
198

K
kohsuke 已提交
199 200
        public LocalProc(String[] cmd,String[] env,OutputStream out, File workDir) throws IOException {
            this(cmd,env,null,out,workDir);
K
kohsuke 已提交
201 202
        }

K
kohsuke 已提交
203 204 205
        public LocalProc(String[] cmd,String[] env,InputStream in,OutputStream out) throws IOException {
            this(cmd,env,in,out,null);
        }
206

K
kohsuke 已提交
207
        public LocalProc(String[] cmd,String[] env,InputStream in,OutputStream out, File workDir) throws IOException {
208 209 210 211 212 213 214 215
            this(cmd,env,in,out,null,workDir);
        }

        /**
         * @param err
         *      null to redirect stderr to stdout.
         */
        public LocalProc(String[] cmd,String[] env,InputStream in,OutputStream out,OutputStream err,File workDir) throws IOException {
216
            this( calcName(cmd),
217
                  stderr(environment(new ProcessBuilder(cmd),env).directory(workDir), err==null || err== SELFPUMP_OUTPUT),
218 219 220
                  in, out, err );
        }

221 222
        private static ProcessBuilder stderr(ProcessBuilder pb, boolean redirectError) {
            if(redirectError)    pb.redirectErrorStream(true);
223
            return pb;
224 225 226
        }

        private static ProcessBuilder environment(ProcessBuilder pb, String[] env) {
227 228 229 230 231 232 233
            if(env!=null) {
                Map<String, String> m = pb.environment();
                m.clear();
                for (String e : env) {
                    int idx = e.indexOf('=');
                    m.put(e.substring(0,idx),e.substring(idx+1,e.length()));
                }
234 235
            }
            return pb;
K
kohsuke 已提交
236
        }
K
kohsuke 已提交
237

238
        private LocalProc( String name, ProcessBuilder procBuilder, InputStream in, OutputStream out, OutputStream err ) throws IOException {
K
kohsuke 已提交
239
            Logger.getLogger(Proc.class.getName()).log(Level.FINE, "Running: {0}", name);
240
            this.name = name;
K
kohsuke 已提交
241
            this.out = out;
242
            this.cookie = EnvVars.createCookie();
K
kohsuke 已提交
243 244
            procBuilder.environment().putAll(cookie);
            this.proc = procBuilder.start();
245

246
            InputStream procInputStream = proc.getInputStream();
247 248 249 250 251 252 253 254 255 256 257 258
            if (out==SELFPUMP_OUTPUT) {
                stdout = procInputStream;
                copier = null;
            } else {
                copier = new StreamCopyThread(name+": stdout copier", procInputStream, out);
                copier.start();
                stdout = null;
            }

            if (in == null) {
                // nothing to feed to stdin
                stdin = null;
K
kohsuke 已提交
259
                proc.getOutputStream().close();
260 261 262 263 264 265 266 267
            } else
            if (in==SELFPUMP_INPUT) {
                stdin = proc.getOutputStream();
            } else {
                new StdinCopyThread(name+": stdin copier",in,proc.getOutputStream()).start();
                stdin = null;
            }

268
            InputStream procErrorStream = proc.getErrorStream();
269
            if(err!=null) {
270 271 272 273 274 275 276 277
                if (err==SELFPUMP_OUTPUT) {
                    stderr = procErrorStream;
                    copier2 = null;
                } else {
                    stderr = null;
                    copier2 = new StreamCopyThread(name+": stderr copier", procErrorStream, err);
                    copier2.start();
                }
278
            } else {
279 280 281 282 283
                // the javadoc is unclear about what getErrorStream() returns when ProcessBuilder.redirectErrorStream(true),
                //
                // according to the source code, Sun JREs still still returns a distinct reader end of a pipe that needs to be closed.
                // but apparently at least on some IBM JDK5, returned input and error streams are the same.
                // so try to close them smartly
284 285 286
                if (procErrorStream!=procInputStream) {
                    procErrorStream.close();
                }
287
                copier2 = null;
288
                stderr = null;
289
            }
K
kohsuke 已提交
290 291
        }

292 293 294 295 296 297 298 299 300 301 302 303
        public InputStream getStdout() {
            return stdout;
        }

        public InputStream getStderr() {
            return stderr;
        }

        public OutputStream getStdin() {
            return stdin;
        }

K
kohsuke 已提交
304 305 306 307
        /**
         * Waits for the completion of the process.
         */
        @Override
K
kohsuke 已提交
308
        public int join() throws InterruptedException, IOException {
309 310 311 312 313 314 315 316 317
            // show what we are waiting for in the thread title
            // since this involves some native work, let's have some soak period before enabling this by default 
            Thread t = Thread.currentThread();
            String oldName = t.getName();
            if (SHOW_PID) {
                ProcessTree.OSProcess p = ProcessTree.get().get(proc);
                t.setName(oldName+" "+(p!=null?"waiting for pid="+p.getPid():"waiting for "+name));
            }

K
kohsuke 已提交
318
            try {
K
kohsuke 已提交
319
                int r = proc.waitFor();
K
Kohsuke Kawaguchi 已提交
320
                // see http://wiki.jenkins-ci.org/display/JENKINS/Spawning+processes+from+build
K
kohsuke 已提交
321
                // problems like that shows up as infinite wait in join(), which confuses great many users.
K
kohsuke 已提交
322
                // So let's do a timed wait here and try to diagnose the problem
323
                if (copier!=null)   copier.join(10*1000);
K
kohsuke 已提交
324
                if(copier2!=null)   copier2.join(10*1000);
325
                if((copier!=null && copier.isAlive()) || (copier2!=null && copier2.isAlive())) {
K
kohsuke 已提交
326 327
                    // looks like handles are leaking.
                    // closing these handles should terminate the threads.
K
Kohsuke Kawaguchi 已提交
328
                    String msg = "Process leaked file descriptors. See http://wiki.jenkins-ci.org/display/JENKINS/Spawning+processes+from+build for more information";
K
kohsuke 已提交
329 330
                    Throwable e = new Exception().fillInStackTrace();
                    LOGGER.log(Level.WARNING,msg,e);
K
kohsuke 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345

                    // doing proc.getInputStream().close() hangs in FileInputStream.close0()
                    // it could be either because another thread is blocking on read, or
                    // it could be a bug in Windows JVM. Who knows.
                    // so I'm abandoning the idea of closing the stream
//                    try {
//                        proc.getInputStream().close();
//                    } catch (IOException x) {
//                        LOGGER.log(Level.FINE,"stdin termination failed",x);
//                    }
//                    try {
//                        proc.getErrorStream().close();
//                    } catch (IOException x) {
//                        LOGGER.log(Level.FINE,"stderr termination failed",x);
//                    }
K
kohsuke 已提交
346 347 348 349
                    out.write(msg.getBytes());
                    out.write('\n');
                }
                return r;
K
kohsuke 已提交
350 351
            } catch (InterruptedException e) {
                // aborting. kill the process
352
                destroy();
353
                throw e;
354 355
            } finally {
                t.setName(oldName);
K
kohsuke 已提交
356 357 358
            }
        }

359 360 361 362 363 364 365 366 367 368
        @Override
        public boolean isAlive() throws IOException, InterruptedException {
            try {
                proc.exitValue();
                return false;
            } catch (IllegalThreadStateException e) {
                return true;
            }
        }

K
kohsuke 已提交
369
        @Override
K
kohsuke 已提交
370
        public void kill() throws InterruptedException, IOException {
371
            destroy();
K
kohsuke 已提交
372
            join();
K
kohsuke 已提交
373 374
        }

375 376 377
        /**
         * Destroys the child process without join.
         */
378
        private void destroy() throws InterruptedException {
379
            ProcessTree.get().killAll(proc,cookie);
380 381
        }

382 383 384 385 386
        /**
         * {@link Process#getOutputStream()} is buffered, so we need to eagerly flash
         * the stream to push bytes to the process.
         */
        private static class StdinCopyThread extends Thread {
K
kohsuke 已提交
387 388 389
            private final InputStream in;
            private final OutputStream out;

390
            public StdinCopyThread(String threadName, InputStream in, OutputStream out) {
K
kohsuke 已提交
391 392 393 394 395
                super(threadName);
                this.in = in;
                this.out = out;
            }

396
            @Override
K
kohsuke 已提交
397 398
            public void run() {
                try {
399 400 401
                    try {
                        byte[] buf = new byte[8192];
                        int len;
K
Kohsuke Kawaguchi 已提交
402
                        while ((len = in.read(buf)) >= 0) {
403 404 405 406 407 408
                            out.write(buf, 0, len);
                            out.flush();
                        }
                    } finally {
                        in.close();
                        out.close();
K
kohsuke 已提交
409 410 411
                    }
                } catch (IOException e) {
                    // TODO: what to do?
K
kohsuke 已提交
412 413 414
                }
            }
        }
K
kohsuke 已提交
415 416

        private static String calcName(String[] cmd) {
K
kohsuke 已提交
417
            StringBuilder buf = new StringBuilder();
K
kohsuke 已提交
418 419 420 421 422 423
            for (String token : cmd) {
                if(buf.length()>0)  buf.append(' ');
                buf.append(token);
            }
            return buf.toString();
        }
424 425 426

        public static final InputStream SELFPUMP_INPUT = new NullInputStream(0);
        public static final OutputStream SELFPUMP_OUTPUT = new NullStream();
K
kohsuke 已提交
427 428
    }

K
kohsuke 已提交
429
    /**
430
     * Remotely launched process via {@link Channel}.
431
     *
432
     * @deprecated as of 1.399. Replaced by {@link Launcher.RemoteLauncher.ProcImpl}
K
kohsuke 已提交
433
     */
434
    @Deprecated
K
kohsuke 已提交
435 436 437 438 439 440 441 442
    public static final class RemoteProc extends Proc {
        private final Future<Integer> process;

        public RemoteProc(Future<Integer> process) {
            this.process = process;
        }

        @Override
443
        public void kill() throws IOException, InterruptedException {
K
kohsuke 已提交
444 445 446 447
            process.cancel(true);
        }

        @Override
448
        public int join() throws IOException, InterruptedException {
K
kohsuke 已提交
449 450 451 452 453
            try {
                return process.get();
            } catch (InterruptedException e) {
                // aborting. kill the process
                process.cancel(true);
454
                throw e;
K
kohsuke 已提交
455 456 457
            } catch (ExecutionException e) {
                if(e.getCause() instanceof IOException)
                    throw (IOException)e.getCause();
458
                throw new IOException("Failed to join the process",e);
459 460
            } catch (CancellationException x) {
                return -1;
K
kohsuke 已提交
461
            }
K
kohsuke 已提交
462
        }
463 464 465 466 467

        @Override
        public boolean isAlive() throws IOException, InterruptedException {
            return !process.isDone();
        }
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482

        @Override
        public InputStream getStdout() {
            return null;
        }

        @Override
        public InputStream getStderr() {
            return null;
        }

        @Override
        public OutputStream getStdin() {
            return null;
        }
K
kohsuke 已提交
483
    }
K
kohsuke 已提交
484 485

    private static final Logger LOGGER = Logger.getLogger(Proc.class.getName());
486 487 488 489
    /**
     * Debug switch to have the thread display the process it's waiting for.
     */
    public static boolean SHOW_PID = false;
K
kohsuke 已提交
490
}