Here is my workflow:
I get job from DB, I run a few tasks, I run an external program that reads a file and produces another one (this usually takes under 10 seconds). Here is the code:
Process p = Runtime.getRuntime().exec(prog, null, new File(path));
BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String s;
String errorString = "";
while((s = stdInput.readLine()) != null) {
if(s.toLowerCase().contains("error")) {
Log.writeLog("error: " + s);
errorString += s + "\r\n";
}
}
if(errorString.length() > 1) {
Emailer.email(name + "*" + errorString, "ERROR");
}
while((s = stdError.readLine()) != null) {
Log.writeLog("ERROR: " + s);
}
However, the snippet hanged. I control the server that the code runs on through LogMeIn, once I logged in, the process unblocked (total running time around 280 seconds) and continued. The process did not produce an ERROR results. This happens from time to time more often than we would like to. We do quite a bit of small IOs operation in the program and the harddrive gets pretty full from time to time.
Any idea what could be happening?
Thanks!
EDIT: the server is a just a regular computer that is connected to LogMeIn. My fear is that since it is a regular computer, it may powerdown the CPU/hard drive when not in use (not sure the correct terminology). This would somewhat explain why it would continue once I logged in to LogMeIn and had access to a computer.
EDIT2: directly following the process, I run this. And this hangs for an absurd amount of time as well (usually 5 seconds, took 200+ seconds). Makes me thing that the hard drive is decided to take a nap?
private void cleanup(String path) {
File srcPath = new File(path);
File[] files = srcPath.listFiles();
if(files != null) {
for(File file : files) {
if(file.isDirectory()) {
cleanup(file.getAbsolutePath());
} else {
if(file.getAbsolutePath().endsWith(".original")) {
String fileName = file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(".original"));
IO.delete(fileName);
if(!IO.renameFile(file.getAbsolutePath(), new File(fileName).getAbsolutePath())) {
Log.writeLogSevere("Failed to rename file, this could be a problem..." + fileName);
} else {
Log.writeLog("Cleaned up: " + fileName);
}
}
}
}
}
}
You are not draining the error stream. You do it at the end, which may often be too late. The output buffer of the process fills up and the process blocks waiting to get more space in the stderr output buffer.
You must either use a separate thread for that, or (much simpler) redirectErrorStream using ProcessBuilder.
The most likely thing is that the thread running p didn't die and p.getInputStream() is not null, and but not data on it.
While it's hanging, I would check the current running processes (ps command on Unix) or Task manager on windows. This will tell you if p is done or not. If it is not, then whatever that program is, has issues and it's holding up the rest of your code.
Related
I'm writing a wrapper for the command line based headless server of the game Factorio. I'm using a ProcessBuilder and getting the stdout of the server and am using the rcon connection (long story, stdin wasn't working) to communicate with it. I've gotten it nearly completed up until the point where I was going to package up the jar and run it on the physical server. This is when I noticed that running the jar instead of running from the IDE (Intelij) prevents any output from the server from coming through. I did some more snooping and found that whenever I am using .getInputStream() from the process, the server's output is only sent if the program was launched alongisde the console (java.exe) and doesn't get sent at all if the program was launched without it (javaw.exe).
I tested .getInputStream() with cmd.exe and it works just fine with both java.exe and javaw.exe. I also check to see if the code was following some flow that I wasn't expecting when ran outside of the IDE but it blocks on read.readLine() as if it's waiting for input but not receiving anything.
The process is initialized here:
ProcessBuilder pb = new ProcessBuilder(gameExecutablePath, "--start-server", saveLocation + "\\" + saveName, "--server-settings", serverSettingsLocation + "\\" + serverSettingsName, "-c", serverConfigLocation + "\\" + serverConfigName, "--rcon-port", "" + rconPort, "--rcon-password", "" + rconPasskey);
pb.redirectErrorStream(true);
try
{
myProcess = pb.start();
}catch(Exception e){e.printStackTrace();}
if(myProcess == null) { stop();}
OutputStream serverInput = myProcess.getOutputStream();
write = new BufferedWriter(new OutputStreamWriter(serverInput));
InputStream serverOutput = myProcess.getInputStream();
read = new BufferedReader(new InputStreamReader(serverOutput));
Later, the input is handled by the gui and is sent via rcon (after the server has been fully initialized), and the output is read and printed to the gui's feed, JTextArea, here:
while(serverRunning)
{
try
{
String line = "" + read.readLine();
if(line == null)
{
continue;
}
line = line.trim();
if(line.contains("Opening socket for broadcast"))
{
serverInitialized = true;
initializeRCONConnection();
}
synchronized(serverFeedStringBuilder)
{
printToServerFeed(line);
}
}catch(Exception e){e.printStackTrace();printToServerFeed(e.getMessage());}
}
The application should have printed out the server's output such as all the initializing text about modloading, connecting to the matchmaking server, etc. like it does in the IDE and when java.exe is used. Instead it doesn't output anything at all but the process continues to run and the server can be connected to in-game after it's done initializing.
When I run this code and the call graph is really large, the program prints to the last line that opt outputs and is blocked at readLine, even though there is nothing left. Anyone know what the problem is? opt -print-callgraph file sends the call graph to the error stream. I tried executing opt -print-callgraph file 2> callgraph so that I can read from a file instead but it complains that there are too many positional arguments.
Oddly enough, the code runs fine for call graphs that are small in size.
I tried using ProcessBuilder as well but I get the same problem.
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("opt -print-callgraph " + file);
BufferedReader in = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s = null;
try {
// Gets stuck at readLine after printing out the last line.
while ((s = in.readLine()) != null) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
in.close();
}
You need to read both streams, in separate threads, or else merge them so you're reading them both at the same time. Otherwise the process can block if output is unconsumed. In this case there must be unconsumed output in stdout which is blocking the process, which means it won't finish, which means it won't close stderr, which means reading stderr will block.
In a java program, I am generating an sh script for use on a centOS machine, which will use sox and lame to decode an MP3 audio file, then apply some gain to the file respectively. Im having some issues getting the Process.waitFor() method to do anything other than hang indefinitely. Here is the code:
try
{
// TODO code application logic here
String reviewPath = "/SomeDirectory/";
String fileName = "FileName";
String extension = ".mp3";
StringBuilder sb = new StringBuilder();
sb.append("#!/bin/bash\n");
sb.append("cd " + reviewPath + "\n");
sb.append("lame --decode " + fileName + extension + "\n");
File script = new File(reviewPath + fileName + ".sh");
script.createNewFile();
script.setExecutable(true);
FileWriter writer = new FileWriter(script);
writer.write(sb.toString());
writer.close();
Process p = Runtime.getRuntime().exec(script.getAbsolutePath());
String line;
BufferedReader bri = new BufferedReader
(new InputStreamReader(p.getInputStream()));
BufferedReader bre = new BufferedReader
(new InputStreamReader(p.getErrorStream()));
while ((line = bri.readLine()) != null) {
System.out.println(line);
}
bri.close();
while ((line = bre.readLine()) != null) {
System.out.println(line);
}
bre.close();
p.waitFor();
System.out.println("Done.");
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
The odd part is that when I run the .sh file it generates by hand, it runs and exits nicely, but when I execute it from a process object in java, it never exits. The exitValue of the process is always "Process has not exited". Ive tried adding set -e to the script, and exit to the end of the script. Short of using the kill command (which I dont really think I can do here) Im at a loss as to what is going on here. Any suggestions?
Add something like while(p.getInputStream().read() != -1); after starting the process. The buffer will get filled and the process will stop waiting for something (in this case, your program) to read from it to free up space.
I figured it out! The problem here was indeed that the output streams needed to be flushed for the application to exit, but simply reading from the streams is not enough. I used Suresh Koya's suggestion and used the processBuilder api, and redirected the error stream on the process before starting it, and read from the streams. This fixed the issues I was having :D
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.
I am running wkhtmltopdf from Java. I create a process but this one seems to be hanging, as it repeats again and again the the console, also in the Task Manager.
This is how I run wkhtmltopdf:
String command = applicationLocation + "wkhtmltopdf.exe -O Landscape " + reqURL + "?" + reqQuery + " c:/PDF/" + folderName + "/" + id + "/" + folderName + ".pdf";
Process p = Runtime.getRuntime().exec(command);
How can I "destroy" the process, after the job has been done?
This did not work for me, the process never stopped and the code never entered the while loop either:
ProcessBuilder pb = new ProcessBuilder(application, htmlFilePath, pdfFilePath);
Process process = pb.start();
BufferedReader errStreamReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
System.out.println("read errstreamreader");
//not "process.getInputStream()"
String line = null;
line = errStreamReader.readLine();
while(line != null) {
System.out.println(line);
line = errStreamReader.readLine();
if(line.equals("Done")) {
process.destroy();
System.out.println("destroyed process");
}
}
That was a very ugly problem. My code caused the loop. I called this class which generates the PDF from a servlet with this code:
// create pdf
if(action != null && action.equals("pdf")) {
String reqURL = request.getRequestURL().toString();
String reqQuery = "id=" + bomModuleId+ "&action=pdf";
String folderName = "doonot";
GeneratePDF obj = new GeneratePDF();
obj.genrateCmd(reqURL, "xxx", "xxx", reqQuery, folderName, "10.07.2012");
}
It turned out, that wkhtmltopdf used exactely the same URL, so it made a request to that page, landed in this loop, and called wkhtmltopdf again. So I ended up in like 450 wkhtmltopdf processes, and everything crashed.
Solution: I removed "&action=pdf"!
As it turns out i had the same problem too: looping wkhtmltopdf creates a bunch of processes causing my machine,running Windows 7, to drastically slow down.
The problem isnt really with wkhtmltopdf as it is with Runtime exec. It creates more wkhtmltopdf processes without waiting for any of the previous to finish until the lack of the system's resources freezes your computer.
My Solution: add p.waitFor(). It will wait for the wkhtmltopdf process to finish and then continue the loop.
This solution can still be slow because now a large html file can slow down the completion of your loop. So, if you want a still faster solution I suggest using the --read-from-stdin option from wkhtmltopdf.
As for reading the output of wkhtmltopdf I suggest looking at this question and ALL of its answers.