Working on a JavaFX mockup of a commandline I encountered the following problem:
If I run a process (for example a batchfile) that runs another process (for example opens the notepad with a simple start notepad) I cannot seem to properly determine when the batch-file is done executing:
Process#waitFor already returns when the batchfiles is started (I guess because I have to add cmd /c in front of the executable and cmd really is done after a short fraction of a second)
Reading the output using the Process#getInputStream only ends after I close the notepad not after the batchfile terminates.
Is there a method I keep missing? And more importantly: How can I determine the end of the process spawned through cmd /c if at all?
Reproducible example:
example.bat:
#echo off
start notepad
REM You can change the path to something else but it should be something where tree produces a longer output to reproduce the problem.
cd %USERPROFILE%\Desktop
tree
JavaCode:
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
Process myProcess = null;
try {
myProcess = Runtime.getRuntime().exec("cmd /c C:\\Users\\geisterfurz007\\Desktop\\example.bat");
} catch (IOException e) {
e.printStackTrace();
}
startReadingThread(myProcess);
try {
myProcess.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Process ended");
}
private static void startReadingThread(Process myProcess) {
InputStream stream = myProcess.getInputStream();
new Thread(() -> {
int character;
try {
while ((character = stream.read()) != -1) {
System.out.write(character);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
System.out.println("Reading ended");
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
starts printing the tree and mid printing "Process ended" is written to the console. Lastly "Reading ended" is printed after I close the notepad window.
My goal is to find the point where tree is done printing (ie the batchfile done processing) ignoring when the notepad was closed.
Based on Leviands's answer I tried to close the Processes streams once it was done executing to no avail.
The InputStream closes unfortunately mid content again and the ErrorStream (which I read in the actual application as well) won't close thus blocking the thread.
First of all a huge thanks to Leviand who mentioned InputStream#available in their answer which got me something that actually appears to work:
The idea is that at the point in time I am looking for, Process#isAlive should return false as the Stream takes longer to process than the Process processes (if that makes sense) while there should be no characters readable from the InputStream, so InputStream#available should return 0.
This leads to this piece of code:
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
Process myProcess = null;
try {
myProcess = Runtime.getRuntime().exec("cmd /c C:\\Users\\geisterfurz007\\Desktop\\example.bat");
} catch (IOException e) {
e.printStackTrace();
}
startReadingThread(myProcess).start();
}
private static Thread startReadingThread(Process myProcess) {
InputStream stream = myProcess.getInputStream();
return new Thread(() -> {
int character;
try {
while (myProcess.isAlive() || stream.available() > 0) {
if ((character = stream.read()) == -1) {
break;
}
System.out.write(character);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
With the code above I am able to achieve reading from the Processes stream ignoring any grandchild processes.
After some time using this, there are a few things to address:
I moved to using an InputStreamReader now for one main reason: I can specify encoding. All occurences of stream.available() > 0 would then have to be replaced with reader.ready().
This eats a bunch of resources when idling! It makes sense to have the thread sleep for a few milliseconds if there is nothing to read before attempting to read again.
At least in my usecase where I send each character to my GUI one by one, this kills the GUI pretty quickly for longer outputs. Consider some kind of buffer for the output before further processing on your primary thread.
You are launching startReadingThread(myProcess);, then you are telling with myProcess.waitFor(); to wait that process myProcess is ended before printing System.out.println("Reading ended"); , which is the opposite that you are willing to do.
The process that should "block" the other from starting is the startReadingThread.
The problem also look in the while loop, which was not correct.
I would change in something like (not sure on stream.available() != 0, but it's working for testing):
private static Thread startReadingThread(Process myProcess) {
InputStream stream = myProcess.getInputStream();
return new Thread(() -> {
int character;
try {
while (stream.available() != 0) {
if((character = stream.read()) == -1) {
System.out.write(character);
stream.close();
break;
}
System.out.write(character);
}
stream.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
System.out.println("Reading ended");
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
Then edit the main with:
public static void main(String[] args) {
Process myProcess = null;
try {
myProcess = Runtime.getRuntime().exec("cmd /c C:\\Users\\geisterfurz007\\Desktop\\example.bat");
} catch (IOException e) {
e.printStackTrace();
}
try {
if(myProcess.waitFor() == 0){
startReadingThread(myProcess).start();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Process ended");
output (I'm from italy):
Connected to the target VM, address: '127.0.0.1:56607', transport:
'socket' Process ended Elenco del percorso delle cartelle per il
volume OSDisk Numero di serie del volume: 12DA-8173 C:. Non esistono
sottocartelle
Reading ended Disconnected from the target VM, address:
'127.0.0.1:56607', transport: 'socket'
Process finished with exit code 0
EDIT:
Here's the test on a folder with subfolders inside (I'm attaching screenshot cause of strange symbols)
And this is the folder content:
Related
I have a class that acts as a parent process. During its run, it creates many child process and runs them simultaneously. Each child process is basically an HTTP client that connects to a service and pulls data from it.
Currently, if one of the child processes stop working from any reason, the parent process re-establishing the connection by restarting that same child process.
The disconnection of the child process may be caused by several thing. I would like to communicate the reason for disconnection from the child process to the parent process and have the parent process act accordingly based on the reason of disconnection (socket read fail, 404 not found, 401 unauthorized etc.).
Is it possible? What would be the shortest/best way to do it?
Here is my Parent class:
public class Parent {
public static void main(String[] args){
List<Process> PRlist = new ArrayList<Process>();
List<String[]> commandsList = new ArrayList<String[]>();
DateTimeFormatter frmt = DateTimeFormatter.ofPattern("hh:mm:ss");
if (args.length == 2 && args[0].matches("-f")){
String dir = System.getProperty("user.dir");
String path = dir + "/" + args[1];
try {
FileReader fr = new FileReader(path);
BufferedReader bf = new BufferedReader(fr);
String line = "";
while ((line = bf.readLine()) != null){
String[] tk = line.split(" ");
String[] cmd = {"java", "-jar", "Child.jar", "-a", tk[0], "-p", tk[1],
"-u", tk[2], "-pw", tk[3], "-m", tk[4], "-s", tk[5]};
Process pr = new ProcessBuilder().inheritIO().command(cmd).redirectInput(Redirect.INHERIT).start();
PRlist.add(pr); commandsList.add(cmd);
}
}
catch (FileNotFoundException ex) {ex.printStackTrace();}
catch (IOException ex) {ex.printStackTrace();}
int streamnum = PRlist.size();
while (true){
for (int i = 0; i < streamnum; i++){
if (!PRlist.get(i).isAlive()){
PRlist.get(i).destroy();
PRlist.remove(i);
try {
Process PR = new ProcessBuilder().inheritIO().command(commandsList.get(i)).redirectInput(Redirect.INHERIT).start();
System.out.println(commandsList.get(i)[12] + " stream re-established at " + LocalDateTime.now().format(frmt));
PRlist.add(i,PR);
} catch (IOException ex) {ex.printStackTrace();}
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {ex.printStackTrace();}
}
} else {
System.out.println("No stream file was specified.");
}
}}
Any help would be appreciated.
As example
Someting like
if (!PRlist.get(i).isAlive()){
int exitCode=PRlist.get(i).exitValue();
//Do something with exitValue
PRlist.get(i).destroy();
PRlist.get(i).destroy();
PRlist.remove(i);
try {
Process PR = new ProcessBuilder().inheritIO().command(commandsList.get(i)).redirectInput(Redirect.INHERIT).start();
System.out.println(commandsList.get(i)[12] + " stream re-established at " + LocalDateTime.now().format(frmt));
PRlist.add(i,PR);
} catch (IOException ex) {ex.printStackTrace();}
}
Examples with errorStream may be more complex, but the idea is looking at the exception being posted in errorStream via text processing.
Maybe you can work with the methods
**public ProcessBuilder.Redirect redirectError()**
Returns this process builder's standard error destination. Subprocesses subsequently started by this object's start() method redirect their standard error to this destination. The initial value is Redirect.PIPE.
Returns:
this process builder's standard error destination
Or
public abstract int exitValue()
Returns the exit value for the subprocess.
Returns:
the exit value of the subprocess represented by this Process object. By convention, the value 0 indicates normal termination.
Throws:
IllegalThreadStateException - if the subprocess represented by this Process object has not yet terminated
OK, with Java Threads you would do it like this:
class MyThreadedApp {
// main() here
/**
* Starting a new thread
*/
private void start() {
new Thread(() -> {
try {
new Worker().run();
} catch (Exception ex) {
threadEnded(ex);
}
}).start();
}
/**
* Handle errors
*
* #param ex Error
*/
private void threadEnded(Exception ex) {
System.out.println(ex.getMessage());
}
}
/**
* My worker thread
*/
class Worker {
void run() throws IOException {
// Do my stuff here
}
}
That's a basic example just to demonstrate the data flow. In practice you could have some use of technologies #lscoughlin mentioned.
There is nothing wrong with the code but when i compiles it, the header of console shows this
<terminated> CopyFileToNewFile[Java Application]C:\Program Files\Java\jre1.8.0_45\bin\javaw.exe
package com.princess;
public class CopyFileToNewFile {
public static void main(String[] args)
{
try(
FileInputStream in = new FileInputStream("myfile.txt");
FileOutputStream out = new FileOutputStream("new.txt");
) {
int c;
while((c=in.read())!=-1)
{
out.write(c);
}
}catch (FileNotFoundException e) {
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
}
}
I am using JAVA 1.7 version also i'm not used to with Eclipse
<terminated> in Eclipse simply means the program finished executing, not that something went wrong.
If you want to keep the program "alive", you have to make it wait for something, like user input, Thread.sleep(), or something else.
I am writing a program in which I want to start a shell in the background, and send and receive the input and output. I already have managed to do this, and can successfully read and write to this process. This is where I run into trouble.
I would like to have a method in ShellManager (see below code) that waits until whatever the process is doing finishes/fails, and returns input to the user.
For example, if I send tar xzf something_that_will_take_a_while.tar.gz,
I can see in the output how it takes its time, and then echoes this:
]0;~
[32mMe#MyComputer [33m~[0m
I already tried blocking the thread until ]0;~ was received, this did not work. (Never returned)
I also tried \u001B, same problem :(
I'm not sure what the symbol is, and can't find much on how to detect when the process returns.
Here is my code:
package buildSystem.shell;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import base.TermConstants;
public class ShellManager {
private InputStream termOut;
private OutputStream termIn;
private ProcessBuilder build;
private Process main;
BufferedReader reader;
BufferedWriter writer;
public ShellManager() {
build = new ProcessBuilder(TermConstants.getShellLocation());
build.redirectErrorStream(true);
}
public void start() throws IOException {
try {
main = build.start();
termOut = main.getInputStream();
termIn = main.getOutputStream();
reader = new BufferedReader (new InputStreamReader(termOut));
writer = new BufferedWriter(new OutputStreamWriter(termIn));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void writeLine(String s) throws IOException {
writer.write(s);
writer.newLine();
writer.flush();
}
public String readNextLine() throws IOException {
return reader.readLine();
}
public void end() {
try {
writeLine("exit\n");
main.waitFor();
termOut.close();
termIn.close();
reader.close();
writer.close();
} catch (IOException | InterruptedException e) {
kill();
}
}
public void kill() {
main.destroyForcibly();
try {
termOut.close();
termIn.close();
reader.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/*THE PART I AM HAVING TROUBLE WITH:*/
public void waitForReturn() {
try {
while(reader.readLine() != "\u001B") {}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Basically, I want a reliable way to detect when a program exits from a bash shell. The bash process will still be running, but the program running from that bash instance will have returned. Because of this I cannot use process.waitFor().
I tried waiting for ]0;~, and then the [32mMe#MyComputer [33m~[0m, which worked until an tar exited with an error code, in which case the two lines would be reversed. I am unsure how to proceed, as detecting that bash has returned to the user should be a relatively easy task.
Thanks for your help!
If this represents the way you have been trying to match output, it's your problem:
while(reader.readLine() != "\u001B") {}
Except in special cases, you have to use the equals() method on String instances:
while (true) {
String line = reader.readLine();
if ((line == null) || "\u001B".equals(line))
break;
}
I'm not sure why you expect ESC and a newline when a process exits though.
I believe you need to call the Process.waitFor() method.
So you need something like:
Process p = build.start();
p.waitFor()
If you are trying to simulate a bash shell, allowing input of a command, executing, and processing output without terminating. There is an open source project that may be a good reference for code on how to do this. It is available on Git. Take a look at the Jediterm Pure Java Emulator.
Thinking about simulating a bash, I also found this example for Piping between processes also be be relevant.
It does show how to extract the output of a process executing and piping that data as the input into another Java Process. Should be helpful.
I am using InetAddress to determine if my server is online.
If the server is offline it will restart the server.
This process loops every 5 minutes to check once again if the server is online.
It works fine but now I need to figure out how to specify that I want to use port 43594 when checking the server status instead of the default port 80.
Thanks! Here's my code:
import java.net.InetAddress;
public class Test extends Thread {
public static void main(String args[]) {
try {
while (true) {
try
{
InetAddress address = InetAddress.getByName("cloudnine1999.no-ip.org");
boolean reachable = address.isReachable(10000);
if(reachable){
System.out.println("Online");
}
else{
System.out.println("Offline: Restarting Server...");
Runtime.getRuntime().exec("cmd /c start start.bat");
}
}
catch (Exception e)
{
e.printStackTrace();
}
Thread.sleep(5 * 60 * 1000);
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
EDIT:
Okay so I took someones advice and I made it into this. But now when I uncomment this line..
Runtime.getRuntime().exec("cmd /c start start.bat");
I get this error..
error: unreported exception IOException; must be caught or declared to be thrown
This is my current code:
import java.net.*;
import java.io.*;
public class Test extends Thread {
public static void main(String args[]) {
try {
while (true) {
SocketAddress sockaddr = new InetSocketAddress("cloudnine1999.no-ip.org", 43594);
Socket socket = new Socket();
boolean online = true;
try {
socket.connect(sockaddr, 10000);
}
catch (IOException IOException) {
online = false;
}
if(!online){
System.out.println("OFFLINE: Restarting Server..");
//Runtime.getRuntime().exec("cmd /c start start.bat");
}
if(online){
System.out.println("ONLINE");
}
Thread.sleep(1 * 10000);
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
As I already mentioned in the comments, according to the Javadoc isReachable isn't implemented in a way that would allow you to control the selected port. Actually, if it is allowed to do so by system privileges it will just ping the machine (ICMP request).
Doing it manually (ie, using a socket) will certainly work and isn't really more complicated and/or longer:
SocketAddress sockaddr = new InetSocketAddress("cloudnine1999.no-ip.org", 43594);
// Create your socket
Socket socket = new Socket();
boolean online = true;
// Connect with 10 s timeout
try {
socket.connect(sockaddr, 10000);
} catch (SocketTimeoutException stex) {
// treating timeout errors separately from other io exceptions
// may make sense
online=false;
} catch (IOException iOException) {
online = false;
} finally {
// As the close() operation can also throw an IOException
// it must caught here
try {
socket.close();
} catch (IOException ex) {
// feel free to do something moderately useful here, eg log the event
}
}
// Now, in your initial version all kinds of exceptions were swallowed by
// that "catch (Exception e)". You also need to handle the IOException
// exec() could throw:
if(!online){
System.out.println("OFFLINE: Restarting Server..");
try {
Runtime.getRuntime().exec("cmd /c start start.bat");
} catch (IOException ex) {
System.out.println("Restarting Server FAILED due to an exception " + ex.getMessage());
}
}
EDIT: forgot to handle IOException which also means the server isn't functioning, added
EDIT2: added the handling of the IOException that close() can throw
EDIT3: and exception handling for exec()
I have read many a posts where-in they speak about reading and writing into the file NOT simultaneously using JavaME. I have a special use case scenarios where-in my log file (maybe full file or just portion of the file) is uploaded to the server on regular basis. This must continue without hampering the current logging of the application in this same file.
The code sample is a under:
boolean writing = true;
boolean reading = true;
void main() {
new Thread("THREAD-FILE-READ") {
public void run() {
InputStream instream = getFileInStream();
if (null != instream) {
while (reading) {
try {
try {
synchronized(READ_LOCK) {
READ_LOCK.wait();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if (writtenCharsLen > 0) {
byte[] bytes = new byte[writtenCharsLen];
instream.read(bytes, 0, writtenCharsLen);
System.out.println("Read="+new String(bytes));
bytes = null;
writtenCharsLen = 0;
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
closeStream(instream);
}
}.start();
new Thread("THREAD-FILE-WRITE") {
public void run() {
OutputStream outstream = getFileOutStream();
if (null != outstream) {
while (writing) {
try {
byte[] str = randomString();
if (null != str) {
writtenCharsLen = str.length;
System.out.println("Write=" + new String(str));
outstream.write(str);
str = null;
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
notifyReadStream();
}
try {
synchronized(WRITE_LOCK) {
WRITE_LOCK.wait();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
closeStream(outstream );
}
}.start();
}
void notifyReadStream() {
try {
synchronized (READ_LOCK) {
READ_LOCK.notify();
}
} catch (Exception e) {
e.printStackTrace();
}
}
void notifyWriteStream() {
try {
synchronized (WRITE_LOCK) {
WRITE_LOCK.notify();
}
} catch (Exception e) {
e.printStackTrace();
}
}
In the above code I will replace sop-read and sop-write with proper calls to network IO methods.
PS: Since this piece of code will run of multiple files and multitude of devices i need the modification as compressed as possible to keep my runtime heap as low as possible. Also this piece of code will run till the application life cycle hence closing and opening the file in middle is out of consideration.
Present Undesired Result:
The read and write threads are showing running sop's for read and write. The read thread is reading from the position the writing thread has written. I am not facing any exception in this code but the result is undesired. I have also tried synchronizing read and write streams but that is throwing IllegalMonitorStateException
Expected Result:
Reading of the stream must be triggered after writing into the stream is completed, also the read thread must be able to read from any position in the file.
Any help / pointers is useful?
EDIT: I was able to synchronize the read and the write streams using different monitors but i still feel, i could have done better using single monitor. Will try it sometime later.
I will attack this problem:
Present Undesired Result: The read and write threads are showing running sop's for read and write. The read thread is reading from the position the writing thread has written. I am not facing any exception in this code but the result is undesired. I have also tried synchronizing read and write streams but that is throwing IllegalMonitorStateException.
If you have synchronized the access using monitors i.e. the reader calls someObject.wait() and the writer calls someObject.notify(), remember that you have to wrap these calls in a synchronized block on someObject:
synchronized(someObject) {
someObject.wait();
}
synchronized(someObject) {
someObject.notify();
}
This is the cause for IllegalMonitorStateException.
Your first problem is that you are setting writtenCharsLen before you write the data. If your read thread sees it being non-zero before the write thread actually writes it, you have a problem. Move writtenCharsLen = str.length after the write.
Another problem I see in your sample is that the threads never yield control. They will be CPU hogs.