16. Process Management and Communication

Gathering Output from a Program

//----------------------------------------------------------------------------------
output = "program args".execute().text
//----------------------------------------------------------------------------------

Running Another Program

//----------------------------------------------------------------------------------
proc = "vi myfile".execute()
proc.waitFor()
//----------------------------------------------------------------------------------

Replacing the Current Program with a Different One

//----------------------------------------------------------------------------------
// Calling execute() on a String, String[] or List (of Strings or objects with
// a toString() method) will fork off another process.
// This doesn't replace the existing process but if you simply finish the original
// process (leaving the spawned process to finish asynchronously) you will achieve
// a similar thing.
"archive *.data".execute()
["archive", "accounting.data"].execute()
//----------------------------------------------------------------------------------

Reading or Writing to Another Program

//----------------------------------------------------------------------------------
// sending text to the input of another process
proc = 'groovy -e "print System.in.text.toUpperCase()"'.execute()
Thread.start{
    def writer = new PrintWriter(new BufferedOutputStream(proc.out))
    writer.println('Hello')
    writer.close()
}
proc.waitFor()
// further process output from process
print proc.text.reverse()
// =>
// OLLEH
//----------------------------------------------------------------------------------

Filtering Your Own Output

//----------------------------------------------------------------------------------
// filter your own output
keep = System.out
pipe = new PipedInputStream()
reader = new BufferedReader(new InputStreamReader(pipe))
System.setOut(new PrintStream(new BufferedOutputStream(new PipedOutputStream(pipe))))
int numlines = 2
Thread.start{
    while((next = reader.readLine()) != null) {
        if (numlines-- > 0) keep.println(next)
    }
}
(1..8).each{ println it }
System.out.close()
System.setOut(keep)
(9..10).each{ println it }
// =>
// 1
// 2
// 9
// 10


// filtering output by adding quotes and numbers
class FilterOutput extends Thread {
    Closure c
    Reader reader
    PrintStream orig
    FilterOutput(Closure c) {
        this.c = c
        orig = System.out
        def pipe = new PipedInputStream()
        reader = new BufferedReader(new InputStreamReader(pipe))
        System.setOut(new PrintStream(new BufferedOutputStream(new PipedOutputStream(pipe))))
    }
    void run() {
        def next
        while((next = reader.readLine()) != null) {
            c(orig, next)
        }
    }
    def close() {
        sleep 100
        System.out.close()
        System.setOut(orig)
    }
}
cnt = 0
number = { s, n -> cnt++; s.println(cnt + ':' + n) }
quote =  { s, n -> s.println('> ' + n) }
f1 = new FilterOutput(number); f1.start()
f2 = new FilterOutput(quote); f2.start()
('a'..'e').each{ println it }
f2.close()
f1.close()
//----------------------------------------------------------------------------------

Preprocessing Input

//----------------------------------------------------------------------------------
// Groovy programs (like Java ones) would use streams here. Just process
// another stream instead of System.in or System.out:

// process url text
input = new URL(address).openStream()
// ... process 'input' stream

// process compressed file
input = new GZIPInputStream(new FileInputStream('source.gzip'))
// ... process 'input' stream
//----------------------------------------------------------------------------------

Reading STDERR from a Program

//----------------------------------------------------------------------------------
// To read STDERR of a process you execute
proc = 'groovy -e "println args[0]"'.execute()
proc.waitFor()
println proc.err.text
// => Caught: java.lang.ArrayIndexOutOfBoundsException: 0 ...

// To redirect your STDERR to a file
System.setErr(new PrintStream(new FileOutputStream("error.txt")))
//----------------------------------------------------------------------------------

Controlling Input and Output of Another Program

//----------------------------------------------------------------------------------
// See 16.2, the technique allows both STDIN and STDOUT of another program to be
// changed at the same time, not just one or the other as per Perl 16.2 solution
//----------------------------------------------------------------------------------

Controlling the Input, Output, and Error of Another Program

//----------------------------------------------------------------------------------
// See 16.2 and 16.7, the techniques can be combined to allow all three streams
// (STDIN, STDOUT, STDERR) to be altered as required.
//----------------------------------------------------------------------------------

Communicating Between Related Processes

//----------------------------------------------------------------------------------
// Groovy can piggy-back on the many options available to Java here:
// JIPC provides a wide set of standard primitives: semaphore, event,
//   FIFO queue, barrier, shared memory, shared and exclusive locks:
//   http://www.garret.ru/~knizhnik/jipc/jipc.html
// sockets allow process to communicate via low-level packets
// CORBA, RMI, SOAP allow process to communicate via RPC calls
// shared files can also be used
// JMS allows process to communicate via a messaging service

// Simplist approach is to just link streams:
proc1 = 'groovy -e "println args[0]" Hello'.execute()
proc2 = 'groovy -e "print System.in.text.toUpperCase()"'.execute()
Thread.start{
    def reader = new BufferedReader(new InputStreamReader(proc1.in))
    def writer = new PrintWriter(new BufferedOutputStream(proc2.out))
    while ((next = reader.readLine()) != null) {
        writer.println(next)
    }
    writer.close()
}
proc2.waitFor()
print proc2.text
// => HELLO
//----------------------------------------------------------------------------------

Making a Process Look Like a File with Named Pipes

//----------------------------------------------------------------------------------
// Java/Groovy would normally just use some socket-based technique for communicating
// between processes (see 16.10 for a list of options). If you really must use a named
// pipe, you have these options:
// (1) On *nix machines:
// * Create a named pipe by invoking the mkfifo utility using execute().
// * Open a named pipe by name - which is just like opening a file.
// * Run an external process setting its input and output streams (see 16.1, 16.4, 16.5)
// (2) On Windows machines, Using JCIFS to Connect to Win32 Named Pipes, see:
// http://jcifs.samba.org/src/docs/pipes.html
// Neither of these achieve exactly the same result as the Perl example but some
// scenarios will be almost identical.
//----------------------------------------------------------------------------------

Sharing Variables in Different Processes

//----------------------------------------------------------------------------------
// The comments made in 16.10 regarding other alternative IPC mechanisms also apply here.

// This example would normally be done with multiple threads in Java/Groovy as follows.
class Shared {
    String buffer = "not set yet"
    synchronized void leftShift(value){
        buffer = value
        notifyAll()
    }
    synchronized Object read() {
        return buffer
    }
}
shared = new Shared()
rand = new Random()
threads = []
(1..5).each{
    t = new Thread(){
        def me = t
        for (j in 0..9) {
            shared << "$me.name $j"
            sleep 100 + rand.nextInt(200)
        }
    }
    t.start()
}
while(1) {
    println shared.read()
    sleep 50
}
// =>
// not set yet
// Thread-2 0
// Thread-5 1
// Thread-1 1
// Thread-4 2
// Thread-3 1
// ...
// Thread-5 9


// Using JIPC between processes (as a less Groovy alternative that is closer
// to the original cookbook) is shown below.

// ipcWriterScript:
import org.garret.jipc.client.JIPCClientFactory
port = 6000
factory = JIPCClientFactory.instance
session = factory.create('localhost', port)
mutex = session.createMutex("myMutex", false)
buffer = session.createSharedMemory("myBuffer", "not yet set")
name = args[0]
rand = new Random()
(0..99).each {
    mutex.lock()
    buffer.set("$name $it".toString())
    mutex.unlock()
    sleep 200 + rand.nextInt(500)
}
session.close()

// ipcReaderScript:
import org.garret.jipc.client.JIPCClientFactory
port = 6000
factory = JIPCClientFactory.instance
session = factory.create('localhost', port)
mutex = session.createMutex("myMutex", false)
buffer = session.createSharedMemory("myBuffer", "not yet set")
rand = new Random()
(0..299).each {
    mutex.lock()
    println buffer.get()
    mutex.unlock()
    sleep 150
}
session.close()

// kick off processes:
"java org.garret.jipc.server.JIPCServer 6000".execute()
"groovy ipcReaderScript".execute()
(0..3).each{ "groovy ipcWriterScript $it".execute() }

// =>
// ...
// 0 10
// 2 10
// 2 11
// 1 9
// 1 9
// 1 10
// 2 12
// 3 12
// 3 12
// 2 13
// ...
//----------------------------------------------------------------------------------

Listing Available Signals

//----------------------------------------------------------------------------------
// Signal handling in Groovy (like Java) is operating system and JVM dependent.
// The ISO C standard only requires the signal names SIGABRT, SIGFPE, SIGILL,
// SIGINT, SIGSEGV, and SIGTERM to be defined but depending on your platform
// other signals may be present, e.g. Windows supports SIGBREAK. For more info
// see: http://www-128.ibm.com/developerworks/java/library/i-signalhandling/
// Note: if you start up the JVM with -Xrs the JVM will try to reduce its
// internal usage of signals. Also the JVM takes over meany hooks and provides
// platform independent alternatives, e.g. see java.lang.Runtime#addShutdownHook()

// To see what signals are available for your system (excludes ones taken over
// by the JVM):
sigs = '''HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM
USR1 USR2 CHLD PWR WINCH URG POLL STOP TSTP CONT TTIN TTOU VTALRM PROF XCPU
XFSZ WAITING LWP AIO IO INFO THR BREAK FREEZE THAW CANCEL EMT
'''

sigs.tokenize(' \n').each{
    try {
        print ' ' + new sun.misc.Signal(it)
    } catch(IllegalArgumentException iae) {}
}
// =>  on Windows XP:
// SIGINT SIGILL SIGABRT SIGFPE SIGSEGV SIGTERM SIGBREAK
//----------------------------------------------------------------------------------

Sending a Signal

//----------------------------------------------------------------------------------
// To send a signal to your process:
Signal.raise(new Signal("INT"))
//----------------------------------------------------------------------------------

Installing a Signal Handler

//----------------------------------------------------------------------------------
// install a signal handler
class DiagSignalHandler implements SignalHandler { ... }
diagHandler = new DiagSignalHandler()
Signal.handle(new Signal("INT"), diagHandler)
//----------------------------------------------------------------------------------

Temporarily Overriding a Signal Handler

//----------------------------------------------------------------------------------
// temporarily install a signal handler
class DiagSignalHandler implements SignalHandler { ... }
diagHandler = new DiagSignalHandler()
oldHandler = Signal.handle(new Signal("INT"), diagHandler)
Signal.handle(new Signal("INT"), oldHandler)
//----------------------------------------------------------------------------------

Writing a Signal Handler

//----------------------------------------------------------------------------------
import sun.misc.Signal
import sun.misc.SignalHandler

class DiagSignalHandler implements SignalHandler {
    private oldHandler

    // Static method to install the signal handler
    public static install(signal) {
        def diagHandler = new DiagSignalHandler()
        diagHandler.oldHandler = Signal.handle(signal, diagHandler)
    }

    public void handle(Signal sig) {
        println("Diagnostic Signal handler called for signal "+sig)
        // Output information for each thread
        def list = []
        Thread.activeCount().each{ list += null }
        Thread[] threadArray = list as Thread[]
        int numThreads = Thread.enumerate(threadArray)
        println("Current threads:")
        for (i in 0..<numThreads) {
            println("    "+threadArray[i])
        }

        // Chain back to previous handler, if one exists
        if ( oldHandler != SIG_DFL && oldHandler != SIG_IGN ) {
            oldHandler.handle(sig)
        }
    }
}
// install using:
DiagSignalHandler.install(new Signal("INT"))
//----------------------------------------------------------------------------------

Catching Ctrl-C

//----------------------------------------------------------------------------------
// See 16.17, just don't chain to the previous handler because the default handler
// will abort the process.
//----------------------------------------------------------------------------------

Avoiding Zombie Processes

//----------------------------------------------------------------------------------
// Groovy relies on Java features here. Java doesn't keep the process around
// as it stores metadata in a Process object. You can call waitFor() or destroy()
// or exitValue() on the Process object. If the Process object is garbage collected,
// the process can still execute asynchronously with respect to the original process.

// For ensuring processes don't die, see:
// http://jakarta.apache.org/commons/daemon/
//----------------------------------------------------------------------------------

Blocking Signals

//----------------------------------------------------------------------------------
// There is no equivalent to a signal mask available directly in Groovy or Java.
// You can override and ignore individual signals using recipes 16.16 - 16.18.
//----------------------------------------------------------------------------------

Timing Out an Operation

//----------------------------------------------------------------------------------
t = new Timer()
t.runAfter(3500){
    println 'Took too long'
    System.exit(1)
}
def count = 0
6.times{
    count++
    sleep 1000
    println "Count = $count"
}
t.cancel()
// See also special JMX timer class: javax.management.timer.Timer
// For an external process you can also use: proc.waitForOrKill(3500)
//----------------------------------------------------------------------------------

Program: sigrand

//----------------------------------------------------------------------------------
// One way to implement this functionality is to automatically replace the ~/.plan
// etc. files every fixed timed interval - though this wouldn't be efficient.
// Here is a simplified version which is a simplified version compared to the
// original cookbook. It only looks at the ~/.signature file and changes it
// freely. It also doesn't consider other news reader related files.

def sigs = '''
Make is like Pascal: everybody likes it, so they go in and change it.
--Dennis Ritchie
%%
I eschew embedded capital letters in names; to my prose-oriented eyes,
they are too awkward to read comfortably. They jangle like bad typography.
--Rob Pike
%%
God made the integers; all else is the work of Man.
--Kronecker
%%
I d rather have :rofix than const. --Dennis Ritchie
%%
If you want to program in C, program in C. It s a nice language.
I use it occasionally... :-) --Larry Wall
%%
Twisted cleverness is my only skill as a programmer.
--Elizabeth Zwicky
%%
Basically, avoid comments. If your code needs a comment to be understood,
it would be better to rewrite it so it s easier to understand.
--Rob Pike
%%
Comments on data are usually much more helpful than on algorithms.
--Rob Pike
%%
Programs that write programs are the happiest programs in the world.
--Andrew Hume
'''.trim().split(/\n%%\n/)
name = 'me@somewhere.org\n'
file = new File(System.getProperty('user.home') + File.separator + '.signature')
rand = new Random()
while(1) {
    file.delete()
    file << name + sigs[rand.nextInt(sigs.size())]
    sleep 10000
}

// Another way to implement this functionality (in a completely different way to the
// original cookbook) is to use a FileWatcher class, e.g.
// http://www.rgagnon.com/javadetails/java-0490.html (FileWatcher and DirWatcher)
// http://www.jconfig.org/javadoc/org/jconfig/FileWatcher.html

// These file watchers notify us whenever the file is modified, see Pleac chapter 7
// for workarounds to not being able to get last accessed time vs last modified time.
// (We would now need to touch the file whenever we accessed it to make it change).
// Our handler called from the watchdog class would update the file contents.
//----------------------------------------------------------------------------------