1 /*
   2  * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.test.lib.thread;
  25 
  26 import jdk.test.lib.process.OutputAnalyzer;
  27 import jdk.test.lib.process.ProcessTools;
  28 
  29 import java.io.PrintWriter;
  30 
  31 import java.util.concurrent.CountDownLatch;
  32 import java.util.function.Predicate;
  33 
  34 /**
  35  * The helper class for starting and stopping {@link Process} in a separate thread.
  36  */
  37 public class ProcessThread extends TestThread {
  38 
  39     /**
  40      * Creates a new {@code ProcessThread} object.
  41      *
  42      * @param threadName The name of thread
  43      * @param cmd The string array of program and its arguments to pass to {@link ProcessBuilder}
  44      */
  45     public ProcessThread(String threadName, String... cmd) {
  46         super(new ProcessRunnable(new ProcessBuilder(cmd)), threadName);
  47     }
  48 
  49     /**
  50      * Creates a new {@code ProcessThread} object.
  51      *
  52      * @param threadName The name of thread.
  53      * @param pb The ProcessBuilder to execute.
  54      */
  55     public ProcessThread(String threadName, ProcessBuilder pb) {
  56         super(new ProcessRunnable(pb), threadName);
  57     }
  58 
  59 
  60     /**
  61      * Creates a new {@code ProcessThread} object.
  62      *
  63      * @param threadName The name of thread
  64      * @param waitfor A predicate to determine whether the target process has been initialized
  65      * @param cmd The string array of program and its arguments to pass to {@link ProcessBuilder}
  66      */
  67     public ProcessThread(String threadName, Predicate<String> waitfor, String... cmd) {
  68         super(new ProcessRunnable(new ProcessBuilder(cmd), threadName, waitfor), threadName);
  69     }
  70 
  71     /**
  72      * Creates a new {@code ProcessThread} object.
  73      *
  74      * @param threadName The name of thread.
  75      * @param waitfor A predicate to determine whether the target process has been initialized
  76      * @param pb The ProcessBuilder to execute.
  77      */
  78     public ProcessThread(String threadName, Predicate<String> waitfor, ProcessBuilder pb) {
  79         super(new ProcessRunnable(pb, threadName, waitfor), threadName);
  80     }
  81 
  82     /**
  83      * Stops {@link Process} started by {@code ProcessRunnable}.
  84      *
  85      * @throws InterruptedException
  86      */
  87     public void stopProcess() throws InterruptedException {
  88         ((ProcessRunnable) getRunnable()).stopProcess();
  89     }
  90 
  91     /**
  92      * @return The process output, or null if the process has not yet completed.
  93      */
  94     public OutputAnalyzer getOutput() {
  95         return ((ProcessRunnable) getRunnable()).getOutput();
  96     }
  97 
  98     /**
  99     * Returns the PID associated with this process thread
 100     * @return The PID associated with this process thread
 101     */
 102     public long getPid() throws InterruptedException {
 103         return ((ProcessRunnable)getRunnable()).getPid();
 104     }
 105 
 106     public void sendMessage(String message) throws InterruptedException {
 107         ((ProcessRunnable)getRunnable()).sendMessage(message);
 108     }
 109 
 110     /**
 111      * {@link Runnable} interface for starting and stopping {@link Process}.
 112      */
 113     static class ProcessRunnable extends XRun {
 114 
 115         private final ProcessBuilder processBuilder;
 116         private final CountDownLatch latch;
 117         private volatile Process process;
 118         private volatile OutputAnalyzer output;
 119         private final Predicate<String> waitfor;
 120         private final String name;
 121 
 122         /**
 123          * Creates a new {@code ProcessRunnable} object.
 124          *
 125          * @param pb The {@link ProcessBuilder} to run.
 126          */
 127         public ProcessRunnable(ProcessBuilder pb) {
 128             this(pb, "", null);
 129         }
 130 
 131         /**
 132          * Creates a new {@code ProcessRunnable} object.
 133          *
 134          * @param pb The {@link ProcessBuilder} to run.
 135          * @param name An optional process name; may be null
 136          * @param waitfor A predicate to determine whether the target process has been initialized; may be null
 137          */
 138         public ProcessRunnable(ProcessBuilder pb, String name, Predicate<String> waitfor) {
 139             this.processBuilder = pb;
 140             this.latch = new CountDownLatch(1);
 141             this.name = name;
 142             this.waitfor = waitfor;
 143         }
 144 
 145         /**
 146          * Starts the process in {@code ProcessThread}.
 147          * All exceptions which occurs here will be caught and stored in {@code ProcessThread}.
 148          *
 149          * see {@link XRun}
 150          */
 151         @Override
 152         public void xrun() throws Throwable {
 153             this.process = ProcessTools.startProcess(name, processBuilder, waitfor);
 154             // Release when process is started
 155             latch.countDown();
 156 
 157             // Will block...
 158             try {
 159                 this.process.waitFor();
 160                 output = new OutputAnalyzer(this.process);
 161             } catch (Throwable t) {
 162                 String name = Thread.currentThread().getName();
 163                 System.out.println(String.format("ProcessThread[%s] failed: %s", name, t.toString()));
 164                 throw t;
 165             } finally {
 166                 this.process.destroyForcibly().waitFor();
 167                 String logMsg = ProcessTools.getProcessLog(processBuilder, output);
 168                 System.out.println(logMsg);
 169             }
 170         }
 171 
 172         /**
 173          * Stops the process.
 174          *
 175          * @throws InterruptedException
 176          */
 177         public void stopProcess() throws InterruptedException {
 178             // Wait until process is started
 179             latch.await();
 180             if (this.process != null) {
 181                 System.out.println("ProcessThread.stopProcess() will kill process");
 182                 this.process.destroy();
 183             }
 184         }
 185 
 186         /**
 187          * Returns the OutputAnalyzer with stdout/stderr from the process.
 188          * @return The process output, or null if process not completed.
 189          * @throws InterruptedException
 190          */
 191         public OutputAnalyzer getOutput() {
 192             return output;
 193         }
 194 
 195         /**
 196          * Returns the PID associated with this process runnable
 197          * @return The PID associated with this process runnable
 198          */
 199         public long getPid() throws InterruptedException {
 200             return getProcess().pid();
 201         }
 202 
 203         public void sendMessage(String message) throws InterruptedException {
 204             try (PrintWriter pw = new PrintWriter(this.getProcess().getOutputStream())) {
 205                 pw.println(message);
 206                 pw.flush();
 207             }
 208         }
 209 
 210         private Process getProcess() throws InterruptedException {
 211             latch.await();
 212             return process;
 213         }
 214     }
 215 
 216 }