Issues with Runtime.exec Process.getInputStream and Process.waitFor() - java

I have written a code to check whether HSQL, JBoss, Radius (AAA sever) and MySQL is running or not.The code is written in an inifinte while loop to continuously monitor.Now I tested that if this service (let's say hsql) is running then /bin/bash -c ps -ef | grep 'hsql' | wc -l will return 3 when this is passed as an argument in Runtime.exec() method. Now I have come to know that the inputstream or errstream of the forked process must not be overflowed. Otherwise it'll cause deadlock.
Keeping that in mind I have written this code:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
public class MonitorHsqlJBossRadius {
public static void main(String[] args) {
String hsqlCmd[] = {"/bin/bash","-c","ps -ef | grep 'hsql' | wc -l "};
String jbossCommand[] = {"/bin/sh","-c","ps -ef | grep 'jboss' | wc -l"};
String radiusCommand[] = {"/bin/sh","-c","ps -ef | grep 'radius' | wc -l"};
String mySqlCommand[] = {"/bin/sh","-c","/etc/init.d/mysqld status"};
String line = null;
String mobNo = "(obscured)";
Process process = null;
Runtime runtime = Runtime.getRuntime();
BufferedReader reader = null;
SendSMS sender = new SendSMS();
boolean sendMsgHsql = false;
boolean sendMsgJBoss = false;
boolean sendMsgRadius = false;
int itr = 1;
while(true)
{
try
{
process = runtime.exec(hsqlCmd);
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while((line=reader.readLine())!=null)
{
if (!line.equals("3"))
sendMsgHsql = true;
}
process.waitFor();
if (sendMsgHsql)
{
//sender.sendSMS("HSQL is not running "+(new Date().toString()), mobNo,1,itr);
System.out.println("HSQL is not running "+itr);
}
else
System.out.println("HSQL is running "+itr);
sendMsgHsql = false;
process = runtime.exec(jbossCommand);
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while((line=reader.readLine())!=null)
{
if (!line.equals("3"))
sendMsgJBoss = true;
}
process.waitFor();
if (sendMsgJBoss)
{
//sender.sendSMS("JBoss is not running "+(new Date().toString()), mobNo,2,itr);
System.out.println("JBoss is not running "+itr);
}
else
System.out.println("JBoss is running "+itr);
sendMsgJBoss = false;
process = runtime.exec(radiusCommand);
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while((line=reader.readLine())!=null)
{
if(!line.equals("3"))
sendMsgRadius = true;
}
process.waitFor();
if (sendMsgRadius)
{
//sender.sendSMS("Radius is not running "+(new Date().toString()), mobNo,3,itr);
System.out.println("Radius is not running "+itr);
}
else
System.out.println("Radius is running "+itr);
sendMsgRadius = false;
}
catch (IOException e) {
e.printStackTrace();
}
catch (InterruptedException e) {
e.printStackTrace();
}
try
{
System.out.println("\n--------- "+itr+" ------------\n");
itr++;
Thread.sleep(1000*5);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The output I'm expecting is as follows
HSQL is running/not running 1
JBoss is running/not running 1
MySQL is running/not running 1
-------------------1----------------
HSQL is running/not running 2
JBoss is running/not running 2
MySQL is running/not running 2
-------------------2----------------
and so on...
Howevere I'm not getting output like that. Sometimes HSQL's status message is not printed or sometimes JBoss status message is not printed. Even sometimes I get output like
------------------1----------------------
after this status messages are printed. All I'm trying to say is that seems to be some race condition or some synchronization problem.

Trying to determine whether a process is running by parsing the output of ps is usually not a good idea. The typical error is that grep processname matches (atleast) two processes: both processname and the grep-process itself.
The usual way of doing this is:
when starting the process, write the pid to a file
when checking the process, read the pid from file and see if it exists by sending it a signal
An even more robust and elegant way of doing it, is to let the process open a socket and checking if this is available, but in this case you wouldn't want to add code for this in your servers. However, the processes you want to check already listen to TCP-ports. I would write code that tried to connect to this port (typically port 80 for jboss, 3306 for mysql, 9001 for hsql) Radius is a bit more tricky as it uses UDP. You could try to perform an authentication, or you could look for specific characteristics with your particular radius-server.

I don't believe your code is hanging due to a race condition or synchronization at all. I don't have access to your server, so I can't run your code to be sure, but instead I suspect that the processes you are running are blocking because they are writing to their standard error and you are not reading from it.
It is possible to read from the standard error stream of a Process created by Runtime.getRuntime().exec(), but reading from this stream has to be done in a separate thread. Instead, I would recommend using a ProcessBuilder. In my opinion, the most significant benefit of a ProcessBuilder is that it can redirect the standard error of the process into the same stream as the standard output. This means you only have one stream to read from and there is no need for a separate thread.
I'll also note that you are opening various BufferedReaders but not closing them. I would recommend that you close them when you have finished with them.
Finally, I would recommend that you don't repeat virtually the same code three times. It should be possible to write a method that takes the command line to run as a String array parameter, runs the command line using a ProcessBuilder and returns whether a line 3 was among the output from that command.

Related

Terminal command won't print

I have a code which calls a specific command within the Mac Terminal, I then use a buffered reader to read and print the result from the terminal. I have been doing this with a couple of commands, only problem is I am now in need of using the command ps -ef | grep google (program name is of course different, but for now I'll just use google). The only issue by using this command is the fact that the buffered reader doesn't seem to be able to read and print the outcome. I am 100% sure that the actual command works, as I have tried to run the command in the terminal alone. I am not exactly sure why this problem is all of a sudden happening now.
The command is used to check whether a specific application is running and where it has been launched from.
The code I am using is the one below here.
try {
String procss;
Process pRun = Runtime.getRuntime().exec("ps -ef | grep google");
BufferedReader input = new BufferedReader(new InputStreamReader(pRun.getInputStream()));
while ((procss = input.readLine()) != null) {
if(!procss.contains("Contents"))
{
//Do something
}
}
input.close();
} catch (Exception err) {
err.printStackTrace();
}
To give an example of a command where it has worked without any problem, I have also just included a working code below. To show how I would actually like it to work, but just with the right command ps -ef | grep google instead.
try {
String procss;
Process pRun = Runtime.getRuntime().exec("top -F -R -o cpu");
BufferedReader input = new BufferedReader(new InputStreamReader(pRun.getInputStream()));
while ((procss = input.readLine()) != null) {
if(!procss.contains("testApplication"))
{
//Do something
}
}
input.close();
} catch (Exception err) {
err.printStackTrace();
}
I am not sure whether I need to use another method of reading the outcome or what I have to do for my code to actually work again.

Runtime.exec().waitFor() not actually waiting for

I've got some code that uses Runtime.exec() to run an external .jar (built as an IzPack installer).
If I run this external.jar from the command line like so:
java -jar external.jar
Then the command prompt does not return control until the application is finished. However, if I run external.jar from within some java class, using:
Process p = Runtime.getRuntime().exec("java -jar external.jar");
int exitCode = p.waitFor();
System.out.println("Process p returned: " + exitCode);
Then p returns almost instantly with a success code of 0, despite external.jar having not yet completed execution (i've also tried this via the ProcessBuilder route of external file execution).
Why does it wait to return from the command line, but not when executed from within another java program?
I've also set up 3 jars, A, B and C where A calls B which calls C (using Runtime.exec()), where C Thread.sleeps for 10 seconds, as a simple test, and as expected, A doesn't return until 10 seconds after it runs.
I figure this is probably some kind of a threading issue with external.jar where execution is being handed over from one thing to another, but given that it works directly from the command line i kind of expected to see the same behaviour (perhaps naively) when called from within another java program.
I've tested this on Windows and Ubuntu with Java 6.
Thanks!
another possible way to achieve this might be to capture the output of the process and wait for it to finish.
For example:
Process tr = Runtime.getRuntime().exec( new String[]{"wkhtmltopdf",mainPage,mainPagePDF});
BufferedReader stdOut=new BufferedReader(new InputStreamReader(tr.getInputStream()));
String s;
while((s=stdOut.readLine())!=null){
//nothing or print
}
Normally the output stream is tr.getInputStream() but depending on the program you are executing the process output stream migh be:
tr.getInputStream()
tr.getErrorStream()
tr.getOutputStream()
By doing this while loop you force your program to wait the process to finish.
You can use Process Builder....
ProcessBuilder pb = new ProcessBuilder("java", "-jar", "/fielname.jar");
Process p = pb.start();
p.waitFor();
Are you spawning a new thread to handle the spawning of the process? If so the origional program will continue to operate independently of the spawned process and therefore waitFor() will only work on the new process and not the parent.
Process.waitFor() is useless for some native system command.
You need to get the process's output to determine if it is returned.
I wrote a sample code for you
/**
*
* #param cmdarray command and parameter of System call
* #param dir the directory execute system call
* #param returnImmediately true indicate return after system call immediately;
* false otherwise.
* if set true, the returned call result does not have reference value
* #return the return code of system call , default is -1
*/
public static int systemCall(String[] cmdarray,File dir,boolean returnImmediately)
{
int result = -1;
try {
Process p = Runtime.getRuntime().exec(cmdarray,null,dir);
if(!returnImmediately)
{
java.io.InputStream stdin = p.getInputStream();
java.io.InputStreamReader isr = new java.io.InputStreamReader(stdin);
java.io.BufferedReader br = new java.io.BufferedReader(isr);
String line = null;
while ( (line = br.readLine()) != null)
System.out.println(line);
}
try{result = p.exitValue();}
catch(Exception ie){;}
} catch (IOException e) {
e.printStackTrace();}
return result;
}
public static void main(String[] argc){
String[] cmdarray = {"jar","cvf","s2.jar","*"};
File dir = new File("D:\\src\\struts-2.3.1");
int k = systemCall(cmdarray,dir,true);
System.out.println("k="+k);
}
I had the same problem using processs to execute some software using the console, and i just solved it using process.waitFor()
For me it worked perfectly.
try{
Process tr = Runtime.getRuntime().exec( new String[]{ "wkhtmltopdf",frontPage,frontPagePDF});
tr.waitFor();
} catch (Exception ex) {
EverLogger.logEntry("Error al pasar a PDF la portada", "error", "activity");
return;
}
some more code here.

Reading InputStream from a batch file's process skips to next line

I am trying to run a batch file with Runtime.exec() and then output its InputStream into a JTextArea. What I have works, but only partially. What happens is the batch file runs, but if it executes a command other than something like "echo" that command immediately terminates and the next line executes. For example, let's say I try to run a simple batch file like this:
#echo off
echo hello. waiting 5 seconds.
timeout /t 5 /nobreak > NUL
echo finished. goodbye.
The batch file executes, and the JTextArea says
hello. waiting 5 seconds.
finished. goodbye.
but it doesn't wait for 5 seconds in the middle.
I can't figure out why it's doing this. Here's what I use to run the batch file and read its InputStream.
private class ScriptRunner implements Runnable {
private final GUI.InfoGUI gui; // the name of my GUI class
private final String script;
public ScriptRunner(final GUI.InfoGUI gui, final File script) {
this.gui = gui;
this.script = script.getAbsolutePath();
}
#Override
public void run() {
try {
final Process p = Runtime.getRuntime().exec(script);
StreamReader output = new StreamReader(p.getInputStream(), gui);
Thread t = new Thread(output);
t.start();
int exit = p.waitFor();
output.setComplete(true);
while (t.isAlive()) {
sleep(500);
}
System.out.println("Processed finished with exit code " + exit);
} catch (final Exception e) {
e.printStackTrace();
}
}
}
private class StreamReader implements Runnable {
private final InputStream is;
private final GUI.InfoGUI gui;
private boolean complete = false;
public StreamReader(InputStream is, GUI.InfoGUI gui) {
this.is = is;
this.gui = gui;
}
#Override
public void run() {
BufferedReader in = new BufferedReader(new InputStreamReader(is));
try {
while (!complete || in.ready()) {
while (in.ready()) {
gui.setTextAreaText(in.readLine() + "\n");
}
sleep(250);
}
} catch (final Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (final Exception e) {
e.printStackTrace();
}
}
public void setComplete(final boolean complete) {
this.complete = complete;
}
}
public void sleep(final long ms) {
try {
Thread.sleep(ms);
} catch (final InterruptedException ie) {
}
}
I know my code is pretty messy, and I'm sure it contains grammatical errors.
Thanks for anything you can do to help!
You're creating a Process but you're not reading from its standard error stream. The process might be writing messages to its standard error to tell you that there's a problem, but if you're not reading its standard error, you won't be able to read these messages.
You have two options here:
Since you already have a class that reads from a stream (StreamReader), wire up another one of these to the process's standard error stream (p.getErrorStream()) and run it in another Thread. You'll also need to call setComplete on the error StreamReader when the call to p.waitFor() returns, and wait for the Thread running it to die.
Replace your use of Runtime.getRuntime().exec() with a ProcessBuilder. This class is new in Java 5 and provides an alternative way to run external processes. In my opinion its most significant improvement over Runtime.getRuntime().exec() is the ability to redirect the process's standard error into its standard output, so you only have one stream to read from.
I would strongly recommend going for the second option and choosing to redirect the process's standard error into its standard output.
I took your code and replaced the line
final Process p = Runtime.getRuntime().exec(script);
with
final ProcessBuilder pb = new ProcessBuilder(script);
pb.redirectErrorStream(true);
final Process p = pb.start();
Also, I don't have your GUI code to hand, so I wrote the output of the process to System.out instead.
When I ran your code, I got the following output:
hello. waiting 5 seconds.
ERROR: Input redirection is not supported, exiting the process immediately.
finished. goodbye.
Processed finished with exit code 0
Had you seen that error message, you might have twigged that something was up with the timeout command.
Incidentally, I noticed in one of your comments that none of the commands suggested by ughzan worked. I replaced the timeout line with ping -n 5 127.0.0.1 > NUL and the script ran as expected. I couldn't reproduce a problem with this.
The problem is definitely in timeout.exe. If you add echo %errorlevel% after line with timeout, you will see that it returns 1 if running from java. And 0 if running in usual way. Probably, it requires some specific console functionality (i.e. cursor positioning) that is suppressed when running from java process.
Is there anything I can do to get this to work while running from Java
If you don't need ability to run any batch file then consider to replace timeout with ping. Otherwise... I've tried to run batch file with JNA trough Kernel32.CreateProcess and timeout runs fine. But then you need to implement reading of process output trough native calls also.
I hope someone will suggest better way.
The ready method only tells if the stream can guarantee that something can be read immediately, without blocking. You can't really trust it because always returning false is a valid implementation. Streams with buffers may return true only when they have something buffered. So I suspect your problem is here:
while (!complete || in.ready()) {
while (in.ready()) {
gui.setTextAreaText(in.readLine() + "\n");
}
sleep(250);
}
It should rather read something like this:
String line;
while (!complete || (line=in.readLine()) != null) {
gui.setTextAreaText(line + "\n");
}
It's probably because your "timeout ..." command returned with an error.
Three ways to test it:
Check if the "timeout ..." command works in the Windows command prompt.
Replace "timeout ..." in the script with "ping -n 5 127.0.0.1 > NUL" (it essentially does the same thing)
Remove everything but "timeout /t 5 /nobreak > NUL" from your script. The process should return with an error (1) if the timeout failed because it is the last command executed.

ReadLine on TCPDump-Buffer sometimes blocks until kill tcpdump

I have a problem using TCPDump from my Android-Application.
It is supposed to read the output from tcpdump line by line and process it within my Application. The Problem is: Sometimes the code works fine, it reads the captured packets immediately. But sometimes, ReadLine blocks until I kill the tcpdump process from the Linux-Console (killall tcpdump). After doing that, my loop is processed for each line (sometimes 10, sometimes 1 or 2) - which means, the readLine should have worked, but didnĀ“t.
I read about similar problems, but did not find any solution for this problem... THANKS!!
public class ListenActivity extends Activity {
static ArrayList<Packet> packetBuffer = new ArrayList<Packet>();
static Process tcpDumpProcess = null;
static ListenThread thread = null;
public static final String TCPDUMP_COMMAND = "tcpdump -A -s0 | grep -i -e 'Cookie'\n";
private InputStream inputStream = null;
private OutputStream outputStream = null;
#Override
protected void onStart() {
super.onStart();
try {
tcpDumpProcess = new ProcessBuilder().command("su").redirectErrorStream(true).start();
inputStream = tcpDumpProcess.getInputStream();
outputStream = tcpDumpProcess.getOutputStream();
outputStream.write(TCPDUMP_COMMAND.getBytes("ASCII"));
} catch (Exception e) {
Log.e("FSE", "", e);
}
thread = new ListenThread(new BufferedReader(new InputStreamReader(inputStream)));
thread.start();
}
private class ListenThread extends Thread {
public ListenThread(BufferedReader reader) {
this.reader = reader;
}
private BufferedReader reader = null;
#Override
public void run() {
reader = new BufferedReader(new InputStreamReader(inputStream));
while (true) {
try {
String received = reader.readLine();
Log.d("FS", received);
Packet pReceived = Packet.analyze(received);
if (pReceived != null) {
packetBuffer.add(pReceived);
}
} catch (Exception e) {
Log.e("FSE", "", e);
}
}
}
}
}
Because output sent to pipes is usually block buffered, both the tcpdump process and the grep process will be waiting until they've received enough data to bother sending it onto your program. You're very lucky though, both programs you have chosen to use are prepared to modify their buffer behavior (using the setvbuf(3) function internally, in case you're curious about the details):
For tcpdump(8):
-l Make stdout line buffered. Useful if you want to see
the data while capturing it. E.g.,
``tcpdump -l | tee dat'' or ``tcpdump -l >
dat & tail -f dat''.
For grep(1):
--line-buffered
Use line buffering on output. This can cause a
performance penalty.
Try this:
"tcpdump -l -A -s0 | grep --line-buffered -i -e 'Cookie'\n";
I don't understand why, but even with the -l option the buffer is too large if you read on the standard output of the process wherein you run tcpdump.
I solve this problem by redirect TcpDump's output to a file and read this file in another thread. The TcpDump command should be something like :
tcpdump -l-A -s0 > /data/local/output.txt
The run method inside your thread have to be change to read in the output file :
File dumpedFile = new File("/data/local/output.txt");
//open a reader on the tcpdump output file
BufferedReader reader = new BufferedReader(new FileReader(dumpedFile));
String temp = new String();
//The while loop is broken if the thread is interrupted
while (!Thread.interrupted()) {
temp = reader.readLine();
if (temp!=null) {
Log.e("READER",new String(temp));
}
}
I dont exactly know what you want to do with grep but I think it's possible do achieve the same actions with a regexp inside the Java code.
You should also be aware that the TcpDump's process will never end, so you have to kill it when your activity is paused or distroy.
You can have a look here to my blog post, I explain my whole code to start/stop tcpdump.

Calling a shell script from java hangs

So I'm trying to execute a shell script which produces a lot of output(in 100s of MBs) from a Java file.
This hangs the process and never completes.
However, within the shell script, if I redirect the output of the script to some log file or /dev/null Java file executes and completes in a jiffy.
Is it because of amount of data that the Java program never completes?
If so, is there any documentation as such? or is there any limit on the amount of data(documented)?
Here's how you can simulate this scenario.
Java file will look like:
import java.io.InputStream;
public class LotOfOutput {
public static void main(String[] args) {
String cmd = "sh a-script-which-outputs-huuggee-data.sh";
try {
ProcessBuilder pb = new ProcessBuilder("bash", "-c", cmd);
pb.redirectErrorStream(true);
Process shell = pb.start();
InputStream shellIn = shell.getInputStream();
int shellExitStatus = shell.waitFor();
System.out.println(shellExitStatus);
shellIn.close();
} catch (Exception ignoreMe) {
}
}
}
The script 'a-script-which-outputs-huuggee-data.sh' may look like:
#!/bin/sh
# Toggle the line below
exec 3>&1 > /dev/null 2>&1
count=1
while [ $count -le 1000 ]
do
cat some-big-file
((count++))
done
echo
echo Yes I m done
Free beer for the right answer. :)
It's because you're not reading from the Process' output.
As per the class' Javadocs, if you don't do this then you may end up with a deadlock; the process fills its IO buffer and waits for the "shell" (or listening process) to read from it and empty it. Meanwhile your process, which should be doing this, is blocking waiting for the process to exit.
You'll want to call getInputStream() and read from that reliably (perhaps from another thread) to stop the process blocking.
Also take a look at Five Java Process Pitfalls and When Runtime.exec() Won't - both informative articles about common problems with Process.
You're never reading the input stream, so it's probably blocking because the input buffer is full.
The input/output buffer have a limited size (depending on the operating system). If I remember correctly this wasn't big or Windows XP at least. Try creating a thread that reads the InputStream as fast as possible.
Something along these lines:
class StdInWorker
implements Worker
{
private BufferedReader br;
private boolean run = true;
private int linesRead = 0;
private StdInWorker (Process prcs)
{
this.br = new BufferedReader(
new InputStreamReader(prcs.getInputStream()));
}
public synchronized void run ()
{
String in;
try {
while (this.run) {
while ((in = this.br.readLine()) != null) {
this.buffer.add(in);
linesRead++;
}
Thread.sleep(50);
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
catch (InterruptedException ie) {}
}
}
}

Categories

Resources