We create subprocesses in Java using ProcessBuilder API. One glitch in that APIs are that there is a possibility of both the parent process and child process getting blocked or being deadlocked, if we forget to do some things. Process class Javadoc mentions about it.
Understanding about SubProcess' Default Output stream
Do you know what happens when System.out.println is called on a subprocess? It does not go to console. The subprocesses does not have console attached to it. Then where does the bytes go?
If you have played with Linux command terminal a bit, you would have known about pipes.
Pipes are all about passing output of one command as input to other command. Now you would have asked a question to yourselves that there should be a storage where the output of the first command should have been saved. Yes, correct. There is a storage called buffer allocated by kernel for saving this information and the second command will read the input from that buffer.
Ex: ps -ef | grep java is equivalent to the ps -ef > kernel buffer < grep java.
There is a limitation with the above approach. The kernel buffer size is limited and if the output of the first command is big enough than kernel buffer then the command will get stuck.
This is the same logic that gets applied when we create subprocess using Java. The stuff that subprocess writes using System.out.println gets written to shared os buffer. We will get our hands dirty by simulating it.
SImulating the deadlock by filling the internal buffer
a) First create a Java Project and create a package called test.
b) Then create the MainProcess class and SubProcess class as given below.
c) MainProcess simply launches SubProcess class and waits for its completion.
To make sure the class is found in JVM's classpath, we need to set the directory where the JVM should search for the SubProcess class.
d) In the SubProcess class, we just run java command. You have to make sure that JAVA_HOME/bin is set in class path. Running java command just lists the help message. It is displayed in standard error stream. We just read from it and write to System.out.println.
e) As we write to System.out.println in subprocess, it gets written to the shared buffer. We are looping for 50 times and writing to the buffer to make sure that the buffer is filled and deadlock is simulated.
a) First create a Java Project and create a package called test.
b) Then create the MainProcess class and SubProcess class as given below.
c) MainProcess simply launches SubProcess class and waits for its completion.
To make sure the class is found in JVM's classpath, we need to set the directory where the JVM should search for the SubProcess class.
d) In the SubProcess class, we just run java command. You have to make sure that JAVA_HOME/bin is set in class path. Running java command just lists the help message. It is displayed in standard error stream. We just read from it and write to System.out.println.
e) As we write to System.out.println in subprocess, it gets written to the shared buffer. We are looping for 50 times and writing to the buffer to make sure that the buffer is filled and deadlock is simulated.
package test;
import java.io.File;
import java.io.IOException;
public class MainProcess
{
public static void main(String[] args)
{
ProcessBuilder pb = new ProcessBuilder();
pb.command("java", "test.SubProcess");
try
{
pb.directory(new File(new File(".").getCanonicalFile(), "bin"));
Process p = pb.start();
p.waitFor();
} catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
}
}
import java.io.File;
import java.io.IOException;
public class MainProcess
{
public static void main(String[] args)
{
ProcessBuilder pb = new ProcessBuilder();
pb.command("java", "test.SubProcess");
try
{
pb.directory(new File(new File(".").getCanonicalFile(), "bin"));
Process p = pb.start();
p.waitFor();
} catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
}
}
package test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class SubProcess
{
public static void main(String[] args) throws IOException
{
List<String> commandOutput = getJavaCommandHelp();
for (int i = 1; i <= 50; i++)
{
System.out.println(commandOutput);
}
}
private static List<String> getJavaCommandHelp() throws IOException
{
Process p = new ProcessBuilder("java").start();
List<String> allLines = new ArrayList<String>();
InputStream is = p.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String curLine;
while ((curLine = br.readLine()) != null)
{
allLines.add(curLine);
}
return allLines;
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class SubProcess
{
public static void main(String[] args) throws IOException
{
List<String> commandOutput = getJavaCommandHelp();
for (int i = 1; i <= 50; i++)
{
System.out.println(commandOutput);
}
}
private static List<String> getJavaCommandHelp() throws IOException
{
Process p = new ProcessBuilder("java").start();
List<String> allLines = new ArrayList<String>();
InputStream is = p.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String curLine;
while ((curLine = br.readLine()) != null)
{
allLines.add(curLine);
}
return allLines;
}
}
What is the fix
In the MainProcess after the line ProcessBuilder pb = new ProcessBuilder(), write the following line.
pb.inheritIO();
This line makes sure that whenever we write something using System.out.println in a subprocess, it gets written to output stream of MainProcess which in this case is Console. Now there won't be any deadlock.