Solve the problem of Process. getInputStream of blocking

  • 2021-09-11 20:22:34
  • OfStack

Process. getInputStream () blocking problem

In Java


Runtime.getInstance().exec (String cmd)

Or


new ProcessBuilder(String cmd).start()

The child process object Process can be generated. By calling the waitFor () method of the Process object, the main process can enter a waiting state until the child process completes execution, and then proceed to the next step. If the child process is not handled properly, it may cause the main process to block and the whole program to die.

What java Api says about Process is:

The ProcessBuilder. start () and Runtime. exec methods create a native process and return an instance of the Process subclass, which can be used to control the process and obtain information about it. The Process class provides methods for executing process input, executing output to the process, waiting for the process to complete, checking the exit status of the process, and destroying (killing) the process.

Methods of creating processes may not work well for specific processes on some native platforms, such as native window processes, daemons, Win16/DOS processes on Microsoft Windows, or shell scripts. The child process created does not have its own terminal or console. All of its standard io (that is, stdin, stdout, stderr) operations are redirected to the parent process through three streams (getOutputStream (), getInputStream (), getErrorStream ()). The parent process uses these streams to provide input to and obtain output from the child process. Because some native platforms only provide limited buffer sizes for standard input and output streams, if the output stream or input stream of read and write subprocesses fails quickly, it may cause subprocesses to block or even deadlock.

In the descriptions of getOutputStream (), getInputStream (), getErrorStream (), there is a note: Buffering their output stream and error stream is a good idea! Well, how abstract!

This is the problem. Process. getInputStream () and Process. getErrorStream () return the standard output stream and error stream of Process respectively. If the buffers of the two streams are not handled properly, the process will be blocked. Even if Process. destory () is called, the blocked child process may not be destroyed.

If you try to obtain the output stream and error stream of Process synchronously for processing, it may not be effective. In the process of sequential execution, the output stream and error stream often cannot be processed in time. There are two solutions.

Scenario 1: Obtain the output stream and error stream of Process concurrently

By starting two threads to read and process the output stream and error stream concurrently, you are too lazy to open IDE, just knock 1 code, there may be errors, as follows:

Caller:


class ProcessExecutor
{
 private Process p;
 private List<String> outputList;
 private List<String> errorOutputList;
 public ProcessExecutor(Process p) throws IOException
 {
  if(null == p)
  {
   throw new IOException("the provided Process is null");
  }
  this. p = p;
 }
 public List<String> getOutputList()
 {
  return this. outputList;
 }
 public List<String> getErrorOutputList()
 {
  return this.errorOutputList;
 }
 public int execute()
 {
  int rs = 0;
  Thread outputThread = new ProcessOutputThread(this.p.getInputStream());
  Thread errorOutputThread = new ProcessOutputThread(this.p.getErrorStream());
  outputThread.start();
  errorOutputThread.start();
  rs = p.waitFor();
  outputThread.join();
  errorOutputThread.join();
  this.outputList = outputThread.getOutputList();
  this.errorOutputList = errorOutputThread.getOutputList();
  return rs;
 }
}

Stream processing thread


class ProcessOutputThread extends Thread
{
 private InputStream is;
 private List<String> outputList;
 public ProcessOutputThread(InputStream is) throws IOException
 {
  if(null == is)
  {
   throw new IOException("the provided InputStream is null");
  }
  this. is = is;
  this.outputList = new ArrayList<String>();
 }
 public List<String> getOutputList()
 {
  return this. outputList;
 }
 @Override
 public void run()
 {
  InputStreamReader ir = null;
  BufferedReader br = null;
  try
  {
   ir = new InputStreamReader(this.is);
   br = new BufferedReader(ir);
   String output = null;
   while(null != (output = br.readLine()))
   {
    print(output);
    this.outputList.add(output);
   }
  }
  catch(IOException e)
  {
   e.print();
  }
  finally
  (
   try
   {
    if(null != br)
    {
     br.close();
    }
    if(null != ir)
    {
     ir.close();
    }
    if(null != this.is)
    {
     this.is.close();
    }
   }
   catch(IOException e)
   {
    e.print();
   }
  )
 }
}

Scenario 2: Combine output stream and error stream with redirectErrorStream () method of ProcessBuilder


public int execute()
{
 int rs = 0;
 String[] cmds = {...};//command and arg  
 ProcessBuilder builder = new ProcessBuilder(cmds);  
 builder.redirectErrorStream(true);  
 Process process = builder.start();  
 BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));  
 String output = null;  
 while (null != (readLine = br.readLine()))
 {  
     print(output);   
 }  
 rs = process.waitFor();
 return rs;
} 

Summary of Java Process Blocking Test

Process blocking reason: The input stream and the error stream are separated, and if they are not processed, blocking will occur. In the final analysis, it is essentially the io blocking problem caused by bio.

getInputStream, getErrorSteam is to obtain the console echo information of script or command. The former obtains the echo information of standard output, while the latter obtains the echo information of standard error

Principle of Process: Using Runtime. getRuntime (). exec (cmd) will establish a child process in the current process. Because the child process has no console, its standard output and standard error will be returned to the parent process Process, so this information can be obtained through getInputStream and getErrorStream.

The test code is as follows:


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class JavaExeBat {
        public JavaExeBat() {
        }
        public static void main(String[] args) {
                Process p;
                //test.bat The command in is ipconfig/all
                String cmd="sh test.sh ";
                //String cmd="ping 127.0.0.1 -c 4";
 
                try {
                        // Execute a command 
                        p = Runtime.getRuntime().exec(cmd);
                        // Get the output stream of the command result 
                        // Output stream 
                        InputStream fis=p.getInputStream();
                        // Error flow 
                        InputStream ferrs=p.getErrorStream();
                        // Use 1 Read output stream class to read 
                        InputStreamReader isr=new InputStreamReader(fis);
                        InputStreamReader errsr=new InputStreamReader(ferrs);
                        // Read lines with buffers 
                        BufferedReader br=new BufferedReader(isr);
                        BufferedReader errbr=new BufferedReader(errsr);
                        String line=null;
                        String lineerr = null;
                        // Until you finish reading it 
                        while((line=br.readLine())!=null) {
                        // The problem of blocking may occur 
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        // The problem of blocking may occur 
                                System.out.println("return err Str:" + lineerr);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
}

test. sh as follows


#!/bin/bash
 
for((i=0; i < 100000; i++));do
         // Standard output of output 
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        // Output to standard error 
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done

After testing, If only standard output or standard error is turned on in JavaExeBat. java file, the process will be rammed, and its return value cannot be obtained through waiteFor, because 100000w pieces of information are output to standard output and standard error respectively in the script, while the following code only processes getInputStream, resulting in too much information of standard error output stream returned to the current process and not processed, so it is blocked.

The code is as follows:


p = Runtime.getRuntime().exec(cmd);
                        // Get the output stream of the command result 
                        // Output stream 
                        InputStream fis=p.getInputStream();
                        // Use 1 Read output stream class to read 
                        InputStreamReader isr=new InputStreamReader(fis);
                        // Read lines with buffers 
                        BufferedReader br=new BufferedReader(isr);
                        String line=null;
                        // Until you finish reading it 
                        while((line=br.readLine())!=null) {
                        // The problem of blocking may occur 
                                System.out.println("return input Str:" + line);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);

Replacing getInputStream with getErrorStream in the above code will also tamp the process, because only one of the two, that is, standard error, is handled.

Can you process the information of the two streams synchronously? The code is as follows:


try {
                        // Execute a command 
                        p = Runtime.getRuntime().exec(cmd);
                        // Get the output stream of the command result 
                        // Output stream 
                        InputStream fis=p.getInputStream();
                        // Error flow 
                        InputStream ferrs=p.getErrorStream();
                        // Use 1 Read output stream class to read 
                        InputStreamReader isr=new InputStreamReader(fis);
                        InputStreamReader errsr=new InputStreamReader(ferrs);
                        // Read lines with buffers 
                        BufferedReader br=new BufferedReader(isr);
                        BufferedReader errbr=new BufferedReader(errsr);
                        String line=null;
                        String lineerr = null;
                        // Until you finish reading it 
                        while((line=br.readLine())!=null) {
                        // The problem of blocking may occur 
                                System.out.println("return input Str:" + line);
                        }
                        while((lineerr=errbr.readLine())!=null){
                        // The problem of blocking may occur 
                                System.out.println("return err Str:" + lineerr);
                        }
                        int exitVal = p.waitFor();
                        System.out.println("exitVal:" + exitVal);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

After the test, it will not work, because it is synchronous, there will be sequencing and blocking. The test method, change test. sh to print only standard errors, and you will find that standard error handling is blocked. The script is as follows:


#!/bin/bash
 
for((i=0; i < 100000; i++));do
        // Output to standard error 
        echo "testaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 1>&2
done

Solution ideas:

(1) Two streams are processed concurrently, and two threads are opened to process output stream and error stream respectively

(2) Merge two streams into one stream solution example:

The first way of thinking:


new ProcessBuilder(String cmd).start()
0

The second way of thinking: use ProcessBuilder and use it as redirectErrorStream (true); Merge the output stream with the error stream


new ProcessBuilder(String cmd).start()
1

Related articles: