Proc.java 7.1 KB
Newer Older
K
kohsuke 已提交
1 2
package hudson;

K
kohsuke 已提交
3 4 5 6
import hudson.remoting.Channel;
import hudson.util.StreamCopyThread;
import hudson.util.IOException2;

K
kohsuke 已提交
7 8 9 10 11
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
K
kohsuke 已提交
12 13
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
J
jglick 已提交
14 15
import java.util.logging.Level;
import java.util.logging.Logger;
K
kohsuke 已提交
16 17 18 19 20 21 22 23 24

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

K
kohsuke 已提交
28 29 30 31 32 33 34
    /**
     * Terminates the process.
     *
     * @throws IOException
     *      if there's an error killing a process
     *      and a stack trace could help the trouble-shooting.
     */
35
    public abstract void kill() throws IOException, InterruptedException;
K
kohsuke 已提交
36

K
kohsuke 已提交
37 38 39 40 41 42 43 44 45 46 47 48
    /**
     * Waits for the completion of the process.
     *
     * <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.
     */
49
    public abstract int join() throws IOException, InterruptedException;
K
kohsuke 已提交
50

K
kohsuke 已提交
51 52 53 54 55
    /**
     * Locally launched process.
     */
    public static final class LocalProc extends Proc {
        private final Process proc;
56
        private final Thread copier;
K
kohsuke 已提交
57
        private final OutputStream out;
K
kohsuke 已提交
58

K
kohsuke 已提交
59 60 61
        public LocalProc(String cmd, Map<String,String> env, OutputStream out, File workDir) throws IOException {
            this(cmd,Util.mapToEnv(env),out,workDir);
        }
K
kohsuke 已提交
62

K
kohsuke 已提交
63 64 65
        public LocalProc(String[] cmd, Map<String,String> env,InputStream in, OutputStream out) throws IOException {
            this(cmd,Util.mapToEnv(env),in,out);
        }
K
kohsuke 已提交
66

K
kohsuke 已提交
67 68 69
        public LocalProc(String cmd,String[] env,OutputStream out, File workDir) throws IOException {
            this( Util.tokenize(cmd), env, out, workDir );
        }
K
kohsuke 已提交
70

K
kohsuke 已提交
71 72
        public LocalProc(String[] cmd,String[] env,OutputStream out, File workDir) throws IOException {
            this(cmd,env,null,out,workDir);
K
kohsuke 已提交
73 74
        }

K
kohsuke 已提交
75 76 77
        public LocalProc(String[] cmd,String[] env,InputStream in,OutputStream out) throws IOException {
            this(cmd,env,in,out,null);
        }
78

K
kohsuke 已提交
79
        public LocalProc(String[] cmd,String[] env,InputStream in,OutputStream out, File workDir) throws IOException {
80 81 82 83 84 85 86 87 88 89 90 91 92
            this( calcName(cmd),
                  environment(new ProcessBuilder(cmd),env).directory(workDir).redirectErrorStream(true).start(),
                  in, out );
        }

        private static ProcessBuilder environment(ProcessBuilder pb, String[] env) {
            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,0));
            }
            return pb;
K
kohsuke 已提交
93
        }
K
kohsuke 已提交
94

K
kohsuke 已提交
95 96
        private LocalProc( String name, Process proc, InputStream in, OutputStream out ) throws IOException {
            Logger.getLogger(Proc.class.getName()).log(Level.FINE, "Running: {0}", name);
K
kohsuke 已提交
97
            this.out = out;
K
kohsuke 已提交
98
            this.proc = proc;
99 100
            copier = new StreamCopyThread(name+": stdout copier", proc.getInputStream(), out);
            copier.start();
K
kohsuke 已提交
101 102 103 104
            if(in!=null)
                new ByteCopier(name+": stdin copier",in,proc.getOutputStream()).start();
            else
                proc.getOutputStream().close();
K
kohsuke 已提交
105 106
        }

K
kohsuke 已提交
107 108 109 110
        /**
         * Waits for the completion of the process.
         */
        @Override
K
kohsuke 已提交
111
        public int join() throws InterruptedException, IOException {
K
kohsuke 已提交
112
            try {
K
kohsuke 已提交
113 114 115 116
                int r = proc.waitFor();
                // see http://hudson.gotdns.com/wiki/display/HUDSON/Spawning+processes+from+build
                // problems like that shows up as inifinite wait in join(), which confuses great many users.
                // So let's do a timed wait here and try to diagnose the problem
117 118
                copier.join(10*1000);
                if(copier.isAlive()) {
K
kohsuke 已提交
119 120 121 122 123 124 125 126 127 128 129
                    // looks like handles are leaking.
                    // closing these handles should terminate the threads.
                    String msg = "Process leaked file descriptors. See http://hudson.gotdns.com/wiki/display/HUDSON/Spawning+processes+from+build for more information";
                    Throwable e = new Exception().fillInStackTrace();
                    LOGGER.log(Level.WARNING,msg,e);
                    proc.getInputStream().close();
                    proc.getErrorStream().close();
                    out.write(msg.getBytes());
                    out.write('\n');
                }
                return r;
K
kohsuke 已提交
130 131 132
            } catch (InterruptedException e) {
                // aborting. kill the process
                proc.destroy();
133
                throw e;
K
kohsuke 已提交
134 135 136
            }
        }

K
kohsuke 已提交
137
        @Override
K
kohsuke 已提交
138
        public void kill() throws InterruptedException, IOException {
K
kohsuke 已提交
139 140
            proc.destroy();
            join();
K
kohsuke 已提交
141 142
        }

K
kohsuke 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
        private static class ByteCopier extends Thread {
            private final InputStream in;
            private final OutputStream out;

            public ByteCopier(String threadName, InputStream in, OutputStream out) {
                super(threadName);
                this.in = in;
                this.out = out;
            }

            public void run() {
                try {
                    while(true) {
                        int ch = in.read();
                        if(ch==-1)  break;
                        out.write(ch);
                    }
                    in.close();
                    out.close();
                } catch (IOException e) {
                    // TODO: what to do?
K
kohsuke 已提交
164 165 166
                }
            }
        }
K
kohsuke 已提交
167 168 169 170 171 172 173 174 175

        private static String calcName(String[] cmd) {
            StringBuffer buf = new StringBuffer();
            for (String token : cmd) {
                if(buf.length()>0)  buf.append(' ');
                buf.append(token);
            }
            return buf.toString();
        }
K
kohsuke 已提交
176 177
    }

K
kohsuke 已提交
178 179 180 181 182 183 184 185 186 187 188
    /**
     * Retemoly launched process via {@link Channel}.
     */
    public static final class RemoteProc extends Proc {
        private final Future<Integer> process;

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

        @Override
189
        public void kill() throws IOException, InterruptedException {
K
kohsuke 已提交
190 191 192 193 194
            process.cancel(true);
            join();
        }

        @Override
195
        public int join() throws IOException, InterruptedException {
K
kohsuke 已提交
196 197 198 199 200
            try {
                return process.get();
            } catch (InterruptedException e) {
                // aborting. kill the process
                process.cancel(true);
201
                throw e;
K
kohsuke 已提交
202 203 204 205 206
            } catch (ExecutionException e) {
                if(e.getCause() instanceof IOException)
                    throw (IOException)e.getCause();
                throw new IOException2("Failed to join the process",e);
            }
K
kohsuke 已提交
207 208
        }
    }
K
kohsuke 已提交
209 210

    private static final Logger LOGGER = Logger.getLogger(Proc.class.getName());
K
kohsuke 已提交
211
}