1

I am working on a legacy (Java 6/7) project that uses ProcessBuilder to request a UUID from the machine in an OS-agnostic way. I would like to use the Process.waitFor(long timeout, TimeUnit unit) method from Java 8, but this isn't implemented in Java 6. Instead, I can use waitFor(), which blocks until completion or an error.

I would like to avoid upgrading the version of Java used to 8 if possible as this necessitates a lot of other changes (migrating code away from removed internal APIs and upgrading a production Tomcat server, for example).

How can I best implement the code for executing the process, with a timeout? I was thinking of somehow implementing a schedule that checks if the process is still running and cancelling/destroying it if it is and the timeout has been reached.

My current (Java 8) code looks like this:

/** USE WMIC on Windows */
private static String getSystemProductUUID() {
    String uuid = null;
    String line;

    List<String> cmd = new ArrayList<String>() {{
      add("WMIC.exe"); add("csproduct"); add("get"); add("UUID");
    }};

    BufferedReader br = null;
    Process p = null;
    SimpleLogger.debug("Attempting to retrieve Windows System UUID through WMIC ...");
    try {
      ProcessBuilder pb = new ProcessBuilder().directory(getExecDir());
      p = pb.command(cmd).start();

      if (!p.waitFor(TIMEOUT, SECONDS)) { // No timeout in Java 6
        throw new IOException("Timeout reached while waiting for UUID from WMIC!");
      }
      br = new BufferedReader(new InputStreamReader(p.getInputStream()));
      while ((line = br.readLine()) != null) {
        if (null != line) {
          line = line.replace("\t", "").replace(" ", "");
          if (!line.isEmpty() && !line.equalsIgnoreCase("UUID")) {
            uuid = line.replace("-", "");
          }
        }
      }
    } catch (IOException | InterruptedException ex) {
      uuid = null;
      SimpleLogger.error(
        "Failed to retrieve machine UUID from WMIC!" + SimpleLogger.getPrependedStackTrace(ex)
      );
      // ex.printStackTrace(System.err);
    } finally {
      if (null != br) {
        try {
          br.close();
        } catch (IOException ex) {
          SimpleLogger.warn(
            "Failed to close buffered reader while retrieving machine UUID!"
          );
        }
        if (null != p) {
          p.destroy();
        }
      }
    }

    return uuid;
  }
Agi Hammerthief
  • 2,114
  • 1
  • 22
  • 38
  • Possible duplicate of [How to add a timeout value when using Java's Runtime.exec()?](https://stackoverflow.com/questions/808276/how-to-add-a-timeout-value-when-using-javas-runtime-exec) – Agi Hammerthief Jan 17 '19 at 13:25

1 Answers1

2

You can use the following code which only uses features available under Java 6:

public static boolean waitFor(Process p, long t, TimeUnit u) {
    ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
    final AtomicReference<Thread> me = new AtomicReference<Thread>(Thread.currentThread());
    ScheduledFuture<?> f = ses.schedule(new Runnable() {
        @Override public void run() {
            Thread t = me.getAndSet(null);
            if(t != null) {
                t.interrupt();
                me.set(t);
            }
        }
    }, t, u);
    try {
        p.waitFor();
        return true;
    }
    catch(InterruptedException ex) {
        return false;
    }
    finally {
        f.cancel(true);
        ses.shutdown();
        // ensure that the caller doesn't get a spurious interrupt in case of bad timing
        while(!me.compareAndSet(Thread.currentThread(), null)) Thread.yield();
        Thread.interrupted();
    }
}

Note that unlike other solutions you can find somewhere, this will perform the Process.waitFor() call within the caller’s thread, which is what you would expect when looking at the application with a monitoring tool. It also helps the performance for short running sub-processes, as the caller thread will not do much more than the Process.waitFor(), i.e. does not need to wait for the completion of background threads. Instead, what will happen in the background thead, is the interruption of the initiating thread if the timeout elapsed.

Holger
  • 285,553
  • 42
  • 434
  • 765