Process's waitFor deadlock problem and its solution

1. The problem reappears

Use wkhtmltopdf plug-in to convert html to pdf and print barcode labels.

It is no problem to print two or three sheets. If the program is printed too many, this kind of stuck situation occurs. After a long time, the program does not respond or report an error, and there is no program output in the background. This is the case after several attempts. I feel that the program has been waiting, so I suspect a deadlock.

 

Second, find the reason

After searching for information, I learned: Process.waitFor may cause deadlock?

Because the local system provides a limited buffer pool for standard input and output, incorrectly writing to standard output and reading from standard input may cause subprocess deadlock. The key to the problem lies in the buffer area: the standard output of the executable program is relatively large, and the standard buffer area of ​​the running window is not large enough, so blocking occurs. Next, analyze the buffer. When the Runtime object calls exec(cmd), the JVM will start a child process, which will establish three pipeline connections with the JVM process: standard input, standard output and standard error stream. Assuming that the program is constantly writing data to the standard output stream and the standard error stream, and the JVM does not read it, it will not be able to continue writing data when the buffer is full, which will eventually cause blockage in waitfor().

It should be noted that the stdout and stderr of the reading program are both blocking operations, which means that they must be read separately in two threads, rather than read at once in one thread, otherwise there may still be blocking: such as reading first Take stdout and then read stderr. If the stderr output of the program has filled the buffer, the program will block and not continue execution, but the java thread is blocked on reading stdout, and stderr will only be read when stdout is over. The result is that the program is stuck for you while waiting for each other

1. When we use Runtime.exec to execute a command, the JAVA thread will create a child process for executing the command, and the child process and the JAVA thread will run independently.

2. The JAVA thread needs to wait for the execution of the command to complete, and process the log and return value of the command , so we call Process.waitFor in the JAVA thread to hang up and wait for the child process to complete.

3. When the child process is executed, the log information is continuously printed, and we obtain the normal output log and error log for processing through Process.getInputStream and Process.getErrorStream .

4. At this time, the child process continues to write data to the JAVA thread, and the JAVA thread has been blocked after calling Process.waitFor , and the child process is constantly writing data to the JAVA thread, when our Process.getInputStream buffer The buffer is full, and the JAVA thread is still suspended without consuming the data in the buffer. As a result, the child process cannot continue to write data to the buffer, and the child process also hangs. 5. At this time, the JAVA thread and the child process are in a suspended state. The JAVA thread waits for the end of the child process, and the child process waits for the JAVA thread to consume the data in the buffer buffer. The two are waiting for each other causing a deadlock .

Deadlock schematic

Three, the solution

1. Ideas

Since the child process hangs because the data in the buffer buffer is not consumed, then we start here.

a. Consume the data in the buffer

b. When the JAVA thread calls Process.waitFor , the thread will be suspended, then we use multiple threads to consume data.

2. Normal flow chart

3. Code implementation

Old code prone to deadlock

public static boolean execCommond(String... args) {
    boolean flg = true;
    Runtime run = Runtime.getRuntime();
    try {
        Process p;
        if (args != null && args.length == 1) {
            p = run.exec(args[0]);
        } else {
            p = run.exec(args);
        }
        LoggerUtils.info(CmdExecUtils.class, p.getInputStream() + "....getInputStream..");
        BufferedInputStream in = new BufferedInputStream(p.getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        
        LoggerUtils.info(CmdExecUtils.class, inBr + "....inBr..");
        
        String lineStr;
        while ((lineStr = inBr.readLine()) != null) {
           LoggerUtils.info(CmdExecUtils.class, lineStr );
            System.out.println(lineStr);// 打印输出信息
        }
        if (p.waitFor() != 0) {
            if (p.exitValue() == 1) {
               logger.info("==================================命令执行失败!");
                flg = false;
            }
        }
        inBr.close();
        in.close();
    } catch (Exception e) {
        e.printStackTrace();
        flg = false;
    }
    return flg;
}

The code after solving the deadlock:

private static ThreadPoolExecutor executor;

static {
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
            .setNameFormat("cmd-pool-%d").build();
    //根据实际情况创建线程池
    executor = new ThreadPoolExecutor(6, 10, 5,
            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1024),
            namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
}

/**
 * 流处理
 * @param stream
 */
private static void clearStream(InputStream stream) {
    //处理buffer的线程
    executor.execute(new Runnable() {
        @Override
        public void run() {

            String line = null;

            try (BufferedReader in = new BufferedReader(new InputStreamReader(stream));) {
                while ((line = in.readLine()) != null) {
                    LoggerUtils.debug(CmdExecUtils.class,line);
                }
            } catch (IOException e) {
                LoggerUtils.error(CmdExecUtils.class,"exec error : {}", e);
            }
        }
    });
}

public static boolean execCommond(String... args) {
    boolean flg = true;
    Runtime run = Runtime.getRuntime();
    try {
        Process p;
        if (args != null && args.length == 1) {
            p = run.exec(args[0]);
        } else {
            p = run.exec(args);
        }

        InputStream stream=p.getInputStream();
        LoggerUtils.info(CmdExecUtils.class, stream + "....getInputStream..");

        //消费正常日志
        clearStream(stream);
        //消费错误日志
        clearStream(p.getErrorStream());

        if (p.waitFor() != 0) {
            if (p.exitValue() == 1) {
                LoggerUtils.info(CmdExecUtils.class,"=============exec=====================命令执行失败!");
                flg = false;
            }
        }

    } catch (Exception e) {
        e.printStackTrace();
        flg = false;
    }
    return flg;
}

Guess you like

Origin blog.csdn.net/qq_34680444/article/details/109642653