How to run command line within java using thread async - java

I am new to java. I am tasked to write java program to run the command lines. I tested the command line under the DOS prompt since i do not have have access to Linux box yet. it worked fine. See the PROGRAM below for full command line syntax. the job will take 6 input files and generate some output files. Next i tried to create a class to and using getruntime and process to process this job. Even it compiled without error but when i run it just show the cursor blinking... i thought i need to use Thread async technique. please provide some advices since i do not have enough time for the projects. I also would like to implement a call back or return values when the job is done. an example would be greatly appreciated. Thanks
import java.io.*;
public class RunJob {
// public static final String PROGRAM = "c:\\wrk\\java.exe Hello";
//one command line below
public static final String PROGRAM = "c:/java.exe -cp \"wrk/jmp.jar;wrk/colt.jar\" gov.lanl.yadas.reliability.UltimateMissileReliabilityModel 10000 \"wrk/\" x1.dat x2c.dat x3.dat x4.dat x5.dat x6.dat true";
// Set to true to end the loop
static boolean done = false;
public static void main(String argv[]) throws IOException {
BufferedReader is;
String line;
String returnMsg = "Start ";
final Process p = Runtime.getRuntime().exec(PROGRAM);
System.out.println("start");
Thread waiter = new Thread() {
public void run() {
try {
p.waitFor();
} catch (InterruptedException ex) {
System.out.println("InterruptedException");
return;
}
System.out.println("Program terminated!");
done = true;
}
};
waiter.start();
is = new BufferedReader(new InputStreamReader(p.getInputStream()));
while (!done && ((line = is.readLine()) != null))
{
System.out.println(line);
returnMsg = returnMsg + line;
}
System.out.println(returnMsg);
System.out.println("End");
return;
}// main
}

I assume that there is a good reason why you want to run a java program from another java program and not just from a shell script, or by invoking an API - but if not - please reconsider.
As to your problem - if your application produces a lot of output (the one you are running as a process) - your application will hang. The p.waitFor() will halt until the process ends. But if you don't read the information from the InputStream - it will overflow and hang!
Advice #1: put the p.waitFor() at the end.
Advice #2: read this article. If I remember correctly it is the one I read when I had a similar problem. You can also google for "StreamGobbler" - it is a common name for a separate thread that "gobbles" your streams.
Advice #3: Don't forget the ErrorStream - if your application will produce too many errors - that stream will cause the process to hang as well.

Related

Java - run Python script and monitor continuous output

I'm using a Raspberry Pi to receive the UID of some RFID cards from a RC522 reader. The python script I'm running is here: https://github.com/mxgxw/MFRC522-python
For various reasons I won't go into, I have to process these IDs in Java.
It seems the most viable solution is to run the python script and read in the result into Java. The problem is, the Python code gives continuous output, i.e. it will print the ID of the card into the console window as and when a card is tapped onto the reader, and will only terminate on a user's command.
I'm currently using a ProcessBuilder to execute the script, however it seems like it's more suited to run the program and read in the immediate result back to Java (which of course is null if I haven't tapped a card onto the reader). I've tried executing the code in a while(true) loop to continuously start the process - but this doesn't work:
import java.io.*;
public class PythonCaller {
/**
* #param args
* #throws IOException
*/
public static void main(String[] args) throws IOException {
// set up the command and parameter
String pythonScriptPath = "/home/pi/MFRC522-python/Read.py";
String[] cmd = new String[3];
cmd[0] = "sudo";
cmd[1] = "python"; // check version of installed python: python -V
cmd[2] = pythonScriptPath;
// create runtime to execute external command
ProcessBuilder pb = new ProcessBuilder(cmd);
// retrieve output from python script
pb.redirectError();
while(true){
Process p = pb.start();
System.out.println("Process Started...");
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
int ret = new Integer(in.readLine()).intValue();
System.out.println("value is : "+ret);
}
}
}
The output on the console window is blank - no exceptions thrown or println's.
Any help would be massively appreciated!!
Thanks
EDIT - I've surrounded my code in a try/catch to see if there's anything at all being thrown, and it doesn't seem to be the case
I use the following programs to try to reproduce the problem
PythonCaller.java
import java.io.*;
public class PythonCaller {
public static void main(String[] args) throws IOException {
// set up the command and parameter
String pythonScriptPath = "/home/pi/test.py";
String[] cmd = { "python", pythonScriptPath };
// create runtime to execute external command
ProcessBuilder pb = new ProcessBuilder(cmd);
// retrieve output from python script
pb.redirectError();
while(true){
Process p = pb.start();
System.out.println("Process Started...");
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
int ret = new Integer(in.readLine()).intValue();
System.out.println("value is : "+ret);
}
}
}
test.py
uid =(123,456,789,999)
print "Card read UID: "+str(uid[0])+","+str(uid[1])+","+str(uid[2])+","+str(uid[3])
The method pb.redirectError() doesn't modify anything. It returns a value, your codes does nothing with it. (see http://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html#redirectError%28%29). What you want is probably redirectErrorStream(boolean redirectErrorStream)
The second line of the python test program is taken directly from "Read.py" (line 44). It causes an error with the java intValue() method. If I replace it with String ret = in.readLine();, the program seems to work.
Since the Process p = pb.start(); is inside the loop, the python subprogram is called repeatedly.
The next step should be to try running the python program manually in a console, see what it does.
(n.b. I had to remove "sudo" and change paths to be able to test on my system, you should have no problems replacing things for your setup).
I've managed to get around it by editing my Python script - it returns null if there's no card on the reader, and the UID if there is.
I'll probably use observer pattern or similar on the Java end to detect when there's a card. Very resource intensive but it'll have to do for now!

Java - Use Input and OutputStream of ProcessBuilder continuously

I want to use an external tool while extracting some data (loop through lines).
For that I first used Runtime.getRuntime().exec() to execute it.
But then my extraction got really slow. So I am searching for a possibility to exec the external tool in each instance of the loop, using the same instance of shell.
I found out, that I should use ProcessBuilder. But it's not working yet.
Here is my code to test the execution (with input from the answers here in the forum already):
public class ExecuteShell {
ProcessBuilder builder;
Process process = null;
BufferedWriter process_stdin;
BufferedReader reader, errReader;
public ExecuteShell() {
String command;
command = getShellCommandForOperatingSystem();
if(command.equals("")) {
return; //Fehler! No error handling yet
}
//init shell
builder = new ProcessBuilder( command);
builder.redirectErrorStream(true);
try {
process = builder.start();
} catch (IOException e) {
System.out.println(e);
}
//get stdout of shell
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//get stdin of shell
process_stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
System.out.println("ExecuteShell: Constructor successfully finished");
}
public String executeCommand(String commands) {
StringBuffer output;
String line;
try {
//single execution
process_stdin.write(commands);
process_stdin.newLine();
process_stdin.flush();
} catch (IOException e) {
System.out.println(e);
}
output = new StringBuffer();
line = "";
try {
if (!reader.ready()) {
output.append("Reader empty \n");
return output.toString();
}
while ((line = reader.readLine())!= null) {
output.append(line + "\n");
return output.toString();
}
if (!reader.ready()) {
output.append("errReader empty \n");
return output.toString();
}
while ((line = errReader.readLine())!= null) {
output.append(line + "\n");
}
} catch (Exception e) {
System.out.println("ExecuteShell: error in executeShell2File");
e.printStackTrace();
return "";
}
return output.toString();
}
public int close() {
// finally close the shell by execution exit command
try {
process_stdin.write("exit");
process_stdin.newLine();
process_stdin.flush();
}
catch (IOException e) {
System.out.println(e);
return 1;
}
return 0;
}
private static String getShellCommandForOperatingSystem() {
Properties prop = System.getProperties( );
String os = prop.getProperty( "os.name" );
if ( os.startsWith("Windows") ) {
//System.out.println("WINDOWS!");
return "C:/cygwin64/bin/bash";
} else if (os.startsWith("Linux") ) {
//System.out.println("Linux!");
return"/bin/sh";
}
return "";
}
}
I want to call it in another Class like this Testclass:
public class TestExec{
public static void main(String[] args) {
String result = "";
ExecuteShell es = new ExecuteShell();
for (int i=0; i<5; i++) {
// do something
result = es.executeCommand("date"); //execute some command
System.out.println("result:\n" + result); //do something with result
// do something
}
es.close();
}
}
My Problem is, that the output stream is always empty:
ExecuteShell: Constructor successfully finished
result:
Reader empty
result:
Reader empty
result:
Reader empty
result:
Reader empty
result:
Reader empty
I read the thread here: Java Process with Input/Output Stream
But the code snippets were not enough to get me going, I am missing something. I have not really worked with different threads much. And I am not sure if/how a Scanner is of any help to me. I would really appreciate some help.
Ultimatively, my goal is to call an external command repeatetly and make it fast.
EDIT:
I changed the loop, so that the es.close() is outside. And I wanted to add, that I do not want only this inside the loop.
EDIT:
The problem with the time was, that the command I called caused an error. When the command does not cause an error, the time is acceptable.
Thank you for your answers
You are probably experiencing a race condition: after writing the command to the shell, your Java program continues to run, and almost immediately calls reader.ready(). The command you wanted to execute has probably not yet output anything, so the reader has no data available. An alternative explanation would be that the command does not write anything to stdout, but only to stderr (or the shell, maybe it has failed to start the command?). You are however not reading from stderr in practice.
To properly handle output and error streams, you cannot check reader.ready() but need to call readLine() (which waits until data is available) in a loop. With your code, even if the program would come to that point, you would read only exactly one line from the output. If the program would output more than one line, this data would get interpreted as the output of the next command. The typical solution is to read in a loop until readLine() returns null, but this does not work here because this would mean your program would wait in this loop until the shell terminates (which would never happen, so it would just hang infinitely).
Fixing this would be pretty much impossible, if you do not know exactly how many lines each command will write to stdout and stderr.
However, your complicated approach of using a shell and sending commands to it is probably completely unnecessary. Starting a command from within your Java program and from within the shell is equally fast, and much easier to write. Similarly, there is no performance difference between Runtime.exec() and ProcessBuilder (the former just calls the latter), you only need ProcessBuilder if you need its advanced features.
If you are experiencing performance problems when calling external programs, you should find out where they are exactly and try to solve them, but not with this approach. For example, normally one starts a thread for reading from both the output and the error stream (if you do not start separate threads and the command produces large output, everything might hang). This could be slow, so you could use a thread pool to avoid repeated spawning of processes.

Closing a Thread's GUI after using Runtime.exec()

I am making a game with LibGDX, now I am trying to restart the game by re-running the jar, for that I am using the jar's path, finding it by using:
String location = new File(DesktopLauncher.class
.getProtectionDomain().getCodeSource().getLocation()
.getPath()).toString().replace("%20", " ");
After using that I attempt to restart using a Process and the Runtime.getRuntime().exec("java -jar " + location + "\\Test.jar");
Now that far it works, but the problem is that the first instance of the game from which I create the new instance (from which I restart), remains on the screen and won't close until the second instance closes.
This is my code for the restart:
public static void restart() {
Gdx.app.exit();
try {
String location = new File(DesktopLauncher.class
.getProtectionDomain().getCodeSource().getLocation()
.getPath()).toString().replace("%20", " ");
System.out.println(location);
Process pro = Runtime.getRuntime().exec(
"java -jar " + location + "\\Test.jar");
BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
"reprot.txt")));
InputStream stream = pro.getErrorStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(
stream));
String line = "";
writer.write(location);
while ((line = reader.readLine()) != null) {
writer.write(line);
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Am I doing something wrong? How can I close the first instance of the game after starting the second instance?
I tried doing it using a different thread, having this code:
public static void main(String[] args) {
try {
String location = new File(DesktopLauncher.class
.getProtectionDomain().getCodeSource().getLocation()
.getPath()).toString();
System.out.println(location);
Process pro = Runtime.getRuntime().exec(
"java -jar " + location + "\\Test.jar");
BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
"report.txt")));
InputStream stream = pro.getErrorStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(
stream));
String line = "";
writer.write(location);
while ((line = reader.readLine()) != null) {
writer.write(line);
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
But it still has the same problem.
EDIT: I tried to used System.exit(0);, and try to use the LwjglApplication to close it but it remains the same, however I have made some progress:
When creating this new process, the second instance of the game, the first instance's UI freezes, causing the game not to respond. I thought, well if it doesn't respond I should just find a way to kill it off and leave the other instance, but that can't be accomplished due to the fact that when closing once instance of the game (by forcing a close on it), you close both instances.
I think I figured out a piece of this annoying puzzle:
Let's say our main instance of the game is called 'Game_1', and the instance that we're creating is 'Game_2'.
After looking at the code and thinking about what happens (with testing of small classes and not the large game), I think that that the 'Game_1' isn't closing because 'Game_2' isn't closing.
In more complex terms, the instance of 'Game_1' won't close because it is somehow attached to 'Game_2' and thus is waiting for 'Game_2' to close before it itself will close.
So if that correct, the way to close 'Game_1' would to make 'Game_2' run simultaneously to 'Game_1' making it independent and thus allowing 'Game_1' to continue with the current progress of the code, which will be the implementation of Gdx.app.exit(); method.
So now the question remains, how do I make the instance of 'Game_2' run independently from 'Game_1'? Or how would I make 'Game_1' continue the code or, not to wait till an exit value will be received from 'Game_2'.
EDIT2: MASSIVE PROGRESS After adding a line of code System.exit(0); in the restart class, 'Game_1' continued not to respond, BUT after terminating 'Game_1', 'Game_2' did not get turned off, I'll continue to play around with it until I figure out what to do.
EDIT3: I continue to try and fix it so it'll work, but have ran into another problem. I figured out that if I can simulate an exit value for the process of 'Game_2' without actually exiting, I can terminate 'Game_1' 's UI, while keeping game 2 still alive, if anyone has any ideas please share them with me.
EDIT4: I continue my attempts to do this, but I can't follow what's going on, I'm trying to pass a PID to the restart class by writing
"java -cp " + location + "\\Test.jar Restart " + PID but it doesn't seem to work, or I don't seem to receive any information (syso for example) from the Restart class. On top of that I have found a memory leak inside my game that I will address once I finish working this out.
Please, if you have any idea how to help me, even just a theory, please share it.
EDIT5: I have established the efficiency of the termination of a given process using this LINK
Here is the solution, since I can't answer my problem until tomorrow:
Alright, finally, I finished solving it, it has a few problems, only two of them I will mention since it concerns the code in general and not how I'm using it. 'Game_1' will be the game that was started first, and 'Game_2' will be the instance of the restarted game. This is it:
First off I got the PID of the current process that is currently running, 'Game_1', from which I will create 'Game_2'. The problem with this is that Java applications all have the same name, 'Java.exe', and what that causes is a bunch of applications of the same name, so for now I add a message saying that the game should be the only java instance on the computer, not eclipse, or anything like that.
The code for the PID retrieval is this:
private static String getPID() {
try {
String line;
Process p = Runtime.getRuntime().exec(
System.getenv("windir") + "\\system32\\" + "tasklist.exe");
BufferedReader input = new BufferedReader(new InputStreamReader(
p.getInputStream()));
while ((line = input.readLine()) != null) {
System.out.println(line);
if (line.contains("java")) {
String data = line.subSequence(27, 35).toString();
data = data.trim();
return data;
}
}
input.close();
} catch (IOException e) {
e.printStackTrace();
}
return "-1";
}
Now, later on, I will look for a way to name the process that is currently running, so that you won't have to use line.contains("java") since it might give more than one instance, but for now it's as good as it gets.
This code uses an exe file inside of windows that basically gives all the current processes running on the computer, so you can find your.
The returned list is given in this format:
Image Name PID Session Name Session# Mem Usage
========================= ======== ================ =========== ============
All the processes will be located here.
The PID is located between the 27th character to the 35th, and that's why I added
String data = line.subSequence(27, 35).toString();
so that it returns the PID of the process.
After doing that I prepared a cmd with an execution command as follows:
String jarLocation = new File(YourClass.class.getProtectedDomain().getCodeSource().getLocation().getPath()).toString();
String command = "java -cp " + jarLocation + " your.Package.here.Restart \""+PID+"\"";
Runtime.getRuntime().exec("cmd /C start cmd.exe /C \"" + command + "\"");
Now first off I got the location of the .jar file. It is returned in the following format:
C:\A%20Folder\To%20YourJar\YourJar.jar
So there needs to be the following formatting to the location
jarLocation = jarLocation.replace("%20", " ");
Just to turn all the %20's to white spaces.
Note If you do not have spaces in your directory the previous step of formatting is not required.
After that I had prepared the actual command, which is as follows (this is for me, but you can change it to fit your needs).
java - calling the java program in cmd.
-cp - execute a class located inside of a jar file. Then I added the jar location, then added the package and added an argument (for the String[] args in the main method) of the PID to be terminated.
Now the following line of code represents a OS dependency, so if you want to add multiple OS support, I would recommend finding the equivalent to cmd in the other OS and figuring out how to use it.
The last line of code is the execution, where we get the runtime, start a cmd and execute a single command before closing the cmd.
You can find details about it in the following issue: LINK
#Vincent Ramdhanie also gives a link to commands you can run using runtime when activating cmd.
After that I had a class that was actually restarting the game itself, which is named Restart.
Like the last line of code, a line of code there represents OS dependency, so if you want to support multiple OS's, find the equivalent to taskkil in other OS's. According to #erhun it's pkill for Linux or something, sorry I don't exactly remember.
This is the code for that class:
public static void main(String[] args) {
try {
String location = new File(DesktopLauncher.class
.getProtectionDomain().getCodeSource().getLocation()
.getPath()).toString();
location = "\"" + location.replaceAll("%20", " ");
Runtime.getRuntime().exec("taskkill /F /PID " + args[0]);
Runtime.getRuntime().exec("java -jar " + location);
} catch (IOException e) {
e.printStackTrace();
}
}
Like with the previous line, location here means the same thing as before, and you have to format it if you have spaces in the directory.
After that you need to terminate the previous process, that is where taskkill /F /PID + args[0] comes in. If you run that you will terminate the task with the id of args[0], which was 'Game_1' 's PID.
After that I just run the jar file and you're good to go.
I would like to note something, I tried running it so that the main class (DesktopLauncher) would use the Restart class through an exec command in runtime, but the problem presisted, and I found that the only way to fix this, was to work around it, and use cmd. (This was after using cmd to debug the location string).
That's it, I worked a whole week, trying to fix this problem, and as crude as this is, it's a solution, for the mean time. If I have a problem somewhere in this code please tell me.
There is a much 'easier' method to do what you want. You will of course have to adapt to your own application as what you are trying to do is completely outside of libgdx's scope. It is a cross-platform library and the idea update/restart is very different with mobile.
An actual desktop cross-platform solution can be found here, I would highly suggest you not use your method as it is not a reliable solution and very platform specific.
Below is an example of how you would do it in libgdx. You need two things, code to launch the application and code to restart it.
Launcher:
public class TestLauncher {
public static void main(final String[] args) {
final LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "Game";
cfg.width = 1280;
cfg.height = 720;
cfg.backgroundFPS = 12;
cfg.foregroundFPS = 60;
final Runnable rebootable = new Runnable() {
#Override public void run() {
if (Gdx.app != null) {
Gdx.app.exit();
}
TestLauncher.restart();
}
};
new LwjglApplication(new RebootTest(rebootable), cfg);
}
public static void restart() {
final StringBuilder cmd = new StringBuilder();
cmd.append(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java ");
for (final String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
cmd.append(jvmArg + " ");
}
cmd.append("-cp ").append(ManagementFactory.getRuntimeMXBean().getClassPath()).append(" ");
cmd.append(TestLauncher.class.getName()).append(" ");
try {
Runtime.getRuntime().exec(cmd.toString());
} catch (final IOException e) {
e.printStackTrace();
}
}
}
Sample Game Code:
public class RebootTest implements ApplicationListener {
private final Runnable rebootHook;
private Stage stage;
private Skin skin;
public RebootTest(final Runnable rebootHook) {
this.rebootHook = rebootHook;
}
#Override public void create() {
this.stage = new Stage();
this.skin = new Skin(Gdx.files.internal("skin/uiskin.json"));
final Table table = new Table();
table.setFillParent(true);
final TextButton button = new TextButton("Reboot", this.skin);
button.addListener(new ClickListener() {
#Override public void clicked(final InputEvent event, final float x, final float y) {
Gdx.app.postRunnable(RebootTest.this.rebootHook);
}
});
table.add(button).expand().size(120, 40);
this.stage.addActor(table);
Gdx.input.setInputProcessor(this.stage);
}
#Override public void resize(final int width, final int height) {}
#Override public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
this.stage.act();
this.stage.draw();
}
#Override public void pause() {}
#Override public void resume() {}
#Override public void dispose() {
if (this.stage != null) {
this.stage.dispose();
}
if (this.skin != null) {
this.skin.dispose();
}
}
}

Receiving values every second from Linux utilities in a Java program

I want to receive in Java the output from a Linux command line program. I need to read the values line by line, because the utilities are reporting their values once per second and the Java program should not need to wait until the end of execution. It should receive the values every second.
The following small program works fine in the case of the ping command, but not for the perf stat command.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class Main {
Process p;
BufferedReader reader;
public Main(int number) throws IOException {
if (number == 1) {
// does NOT work, blocks on readLine()
p = Runtime.getRuntime().exec("sudo perf stat -e cycles -I 1000 -p 9264"); // change PID to the one to be monitored
}
else if (number == 2) {
// WORKS!
p = Runtime.getRuntime().exec("ping www.google.com");
}
else {
System.out.println("Either 1 or 2...");
System.exit(0);
}
reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
}
public void process() throws IOException {
String res = "";
res = reader.readLine();
System.out.println(res);
}
public static void main (String[] args) throws IOException, InterruptedException {
Main myMain = new Main(Integer.parseInt(args[0]));
while (true) {
myMain.process();
Thread.sleep(1000);
}
}
}
So when running java Main 2 it works correctly, but when invoking java Main 1 it will block on the reader.readLine() call.
What's the difference between the two commands? Also with the command 'ls -l' it works correctly and I receive the values line by line.
EDIT:
The command itself works fine, when I run it directly from the command line. The -I option was introduced in newer kernel versions, it did not exist before (I am using kernel 3.11, Ubuntu).
When using 2>$1 to get also the stderr, it will indeed read a value every second, but it will always read null.
The problem seems to be that perf stat does not use stdout by default, but stderr. See the log-fd option.
So you can either redirect stderr to stdout in the command you use,
Or you capture the input Stream from stderr of the Process

Java Process.waitFor() and Readline hangs

First, this is my code :
import java.io.*;
import java.util.Date;
import com.banctecmtl.ca.vlp.shared.exceptions.*;
public class PowershellTest implements Runnable {
public static final String PATH_TO_SCRIPT = "C:\\Scripts\\ScriptTest.ps1";
public static final String SERVER_IP = "XX.XX.XX.XXX";
public static final String MACHINE_TO_MOD = "MachineTest";
/**
* #param args
* #throws OperationException
*/
public static void main(String[] args) throws OperationException {
new PowershellTest().run();
}
public PowershellTest(){}
#Override
public synchronized void run() {
String input = "";
String error = "";
boolean isHanging = false;
try {
Runtime runtime = Runtime.getRuntime();
Process proc = runtime.exec("powershell -file " + PATH_TO_SCRIPT +" "+ SERVER_IP +" "+ MACHINE_TO_MOD);
proc.getOutputStream().close();
InputStream inputstream = proc.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
proc.waitFor();
String line;
while (!isHanging && (line = bufferedreader.readLine()) != null) {
input += (line + "\n");
Date date = new Date();
while(!bufferedreader.ready()){
this.wait(1000);
//if its been more then 1 minute since a line has been read, its hanging.
if(new Date().getTime() - date.getTime() >= 60000){
isHanging = true;
break;
}
}
}
inputstream.close();
inputstream = proc.getErrorStream();
inputstreamreader = new InputStreamReader(inputstream);
bufferedreader = new BufferedReader(inputstreamreader);
isHanging = false;
while (!isHanging && (line = bufferedreader.readLine()) != null) {
error += (line + "\n");
Date date = new Date();
while(!bufferedreader.ready()){
this.wait(1000);
//if its been more then 1 minute since a line has been read, its hanging.
if(new Date().getTime() - date.getTime() >= 60000){
isHanging = true;
break;
}
}
}
inputstream.close();
proc.destroy();
} catch (IOException e) {
//throw new OperationException("File IO problem.", e);
} catch (InterruptedException e) {
//throw new OperationException("Script thread problem.",e);
}
System.out.println("Error : " + error + "\nInput : " + input);
}
}
I'm currently trying to run a powershell script that will start/stop a vm (VMWARE) on a remote server. The script work from command line and so does this code. The thing is, I hate how I have to use a thread (and make it wait for the script to respond, as explained further) for such a job. I had to do it because both BufferedReader.readline() and proc.waitFor() hang forever.
The script, when ran from cmd, is long to execute. it stall for 30 sec to 1 min from validating authentification with the server and executing the actual script. From what I saw from debugging, the readline hang when it start receiving those delays from the script.
I'm also pretty sure it's not a memory problem since I never had any OOM error in any debugging session.
Now I understand that Process.waitFor() requires me to flush the buffer from both the error stream and the regular stream to work and so that's mainly why I don't use it (I need the output to manage VM specific errors, certificates issues, etc.).
I would like to know if someone could explain to me why it hangs and if there is a way to just use the typical readline() without having it to hang so hard. Even if the script should have ended since a while, it still hang (I tried to run both the java application and a cmd command using the exact same thing I use in the java application at the same time, left it runingfor 1h, nothing worked). It is not just stuck in the while loop, the readline() is where the hanging is.
Also this is a test version, nowhere close to the final code, so please spare me the : this should be a constant, this is useless, etc. I will clean the code later. Also the IP is not XX.XX.XX.XXX in my code, obviously.
Either explanation or suggestion on how to fix would be greatly appreciated.
Ho btw here is the script I currently use :
Add-PSSnapin vmware.vimautomation.core
Connect-VIServer -server $args[0]
Start-VM -VM "MachineTest"
If you need more details I will try to give as much as I can.
Thanks in advance for your help!
EDIT : I also previously tested the code with a less demanding script, which job was to get the content of a file and print it. Since no waiting was needed to get the information, the readline() worked well. I'm thus fairly certain that the problem reside on the wait time coming from the script execution.
Also, forgive my errors, English is not my main language.
Thanks in advance for your help!
EDIT2 : Since I cannot answer to my own Question :
Here is my "final" code, after using threads :
import java.io.*;
public class PowershellTest implements Runnable {
public InputStream is;
public PowershellTest(InputStream newIs){
this.is = newIs;
}
#Override
public synchronized void run() {
String input = "";
String error = "";
try {
InputStreamReader inputstreamreader = new InputStreamReader(is);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
String line;
while ((line = bufferedreader.readLine()) != null) {
input += (line + "\n");
}
is.close();
} catch (IOException e) {
//throw new OperationException("File IO problem.", e);
}
System.out.println("Error : " + error + "\nInput : " + input);
}
}
And the main simply create and start 2 thread (PowerShellTest instances), 1 with the errorStream and 1 with the inputStream.
I believe I made a dumb error when I first coded the app and fixed it somehow as I reworked the code over and over. It still take a good 5-6 mins to run, which is somehow similar if not longer than my previous code (which is logical since the errorStream and inputStream get their information sequentially in my case).
Anyway, thanks to all your answer and especially Miserable Variable for his hint on threading.
First, don't call waitFor() until after you've finished reading the streams. I would highly recommend you look at ProcessBuilder instead of simply using Runtime.exec, and split the command up yourself rather than relying on Java to do it for you:
ProcessBuilder pb = new ProcessBuilder("powershell", "-file", PATH_TO_SCRIPT,
SERVER_IP, MACHINE_TO_MOD);
pb.redirectErrorStream(true); // merge stdout and stderr
Process proc = pb.start();
redirectErrorStream merges the error output into the normal output, so you only have to read proc.getInputStream(). You should then be able to just read that stream until EOF, then call proc.waitFor().
You are currently waiting to complete reading from inputStream before starting to read from errorStream. If the process writes to its stderr before stdout maybe you are getting into a deadlock situation.
Try reading from both streams from concurrently running threads. While you are at it, also remove proc.getOutputStream().close();. It shouldn't affect the behavior, but it is not required either.

Categories