views:

256

answers:

1

I need to write a Groovy script which launches a process, reads the processes out & err streams, and wait for a particular line of text to be outputted. The wait should not be indefinite, but should time out after a while.

This is what I came up with. Is there a better way?

def proc = "groovy test.groovy".execute(null, new File("."))
def timeout = 10 * 1000

printProcessOutput(proc, timeout) {line, count -> 
    false /* replace with evaluation of some actual condition */ 
}

def printProcessOutput(proc, millis, condition) {
    def queue = new java.util.concurrent.LinkedBlockingQueue()
    def out = new StreamReader(new InputStreamReader(proc.inputStream), queue)
    def err = new StreamReader(new InputStreamReader(proc.errorStream), queue)

    def outThread = new Thread(out); outThread.start()
    def errThread = new Thread(err); errThread.start()


    def start = System.currentTimeMillis()
    def end = start
    def count = 0
    while (end < start + millis) {
        def line = queue.poll(10, java.util.concurrent.TimeUnit.MILLISECONDS)
        if (line) {
            println line
            count++
            if (condition(line, count)) {
                break
            }
        }
        end = System.currentTimeMillis()
    }

    out.kill(); try { outThread.interrupt() } catch (ex) { }
    err.kill(); try { errThread.interrupt() } catch (ex) { }

    def temp = []
    queue.drainTo(temp)
    temp.each { println "TEMP: $it" }
}

class StreamReader implements Runnable {
    final def reader
    final def queue
    volatile def killed = false

    public StreamReader(reader, queue) {
        this.reader = reader
        this.queue = queue
    }

    def void run() {
        def buff = new BufferedReader(reader)
        def line = buff.readLine()

        while (!killed && line != null) {
            queue.offer(line)
            line = buff.readLine()
        }
    }

    def kill() {
        killed = true
    }
}

test.groovy file is simply:

def rand = new Random()

def delta = 5 * 60 * 1000
def start = System.currentTimeMillis()
def end = start

while (end < start + delta) {
    if (rand.nextBoolean()) {
        System.err.println("ERR " + new Date())
    } else {
        System.out.println("OUT " + new Date())
    }
    Thread.sleep(100)
    end = System.currentTimeMillis()
}
println "Done"
A: 

That seems like it would work. A few suggestions that could simplify it further:

  1. The wait time for queue.poll() should probably be the remaining time: (start + millis) - now
  2. If the values in the queue aren't necessary, the threads could check for the condition and signal a CountDownLatch.

Of course, if the target program is a groovy script, you could probably just run it in the same JVM and have it communicate with the caller using a CountDownLatch or something.

Joshua Davis