When java calls the shell, there is a suspended animation problem (deadlock problem)

A false death problem (deadlock problem) occurs when java calls the shell, org.springframework.dao.CannotAcquireLockException:Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

Phenomenon: When using java to call the shell to execute related commands, problems such as freezing will occur. Then an error will be reported org.springframework.dao.CannotAcquireLockException: Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
At first, I always thought it was a mysql transaction lock, and finally found out that it was a bug in java calling the shell

solve:

In fact, the main problem comes from waitfor().
Let's take a look at the description of waitFor():

The JDK help documentation says this: If necessary, wait until the process represented by the Process object has terminated. If the child process has been terminated, this method returns immediately. But calling this method directly will cause the current thread to block until the child process exits. This is also explained in the JDK documentation: Because the local system is effective for the buffer pool provided by the standard input and output, the wrong fast writing to the standard output and fast reading from the standard input may cause the subprocess So, even deadlock. Well, the key to the problem lies in the buffer: the standard output of the executable program is relatively large, but the standard buffer of the running window is not large enough, so blocking occurs. Next, let's analyze the buffer. Where did this thing come from? 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 standard error stream, and the JVM does not read it, it will not be able to continue writing data when the buffer is full, eventually causing a block in waitfor(). Knowing what the problem is, we can fix it. Most of the methods mentioned on the Internet are to open two threads to read the contents of the standard output buffer and standard error stream of the window before the waitfor() command.

solve:

way 1

package com.xxx.xxx;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.InputStreamReader;

public class CallCmd {
    
    
    // 关流方法
    private void closeStream(Closeable stream) {
    
    
        if (stream != null) {
    
    
            try {
    
    
                stream.close();
            } catch (Exception e) {
    
    
                System.out.println("close stream failed.");
            }
        }
    }

    // 此方法用来执行脚本或命令
    // String cmd:脚本(绝对路径)位置或命令内容
    // File dir:执行命令的子进程的工作目录(null则表示和当前主进程工作目录相同)
    public String callCMD(String cmd, File dir) {
    
    
        StringBuilder result = new StringBuilder();
        Process process = null;
        BufferedReader bufrIn = null;
        BufferedReader bufrError = null;
        try {
    
    
            // 执行命令, 返回一个子进程对象(命令在子进程中执行)
            process = Runtime.getRuntime().exec(cmd, null, dir);
            // 方法阻塞,等待命令执行完成
            process.waitFor();
            // 获取命令执行结果, 有两个结果: 正常的输出和错误的输出(PS: 子进程的输出就是主进程的输入)
            bufrIn =
                    new BufferedReader(
                            new InputStreamReader(process.getInputStream(), "UTF-8")); // 正常的输出
            bufrError =
                    new BufferedReader(
                            new InputStreamReader(process.getErrorStream(), "UTF-8")); // 错误的输出
            // 读取输出
            String line = null;
            while ((line = bufrIn.readLine()) != null) {
    
    
                result.append(line).append('\n');
            }
            while ((line = bufrError.readLine()) != null) {
    
    
                result.append(line).append('\n');
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            closeStream(bufrIn);
            closeStream(bufrError);
            // 销毁子进程
            if (process != null) {
    
    
                process.destroy();
            }
        }
        // 返回执行结果
        return result.toString();
    }
}

way 2

I am implementing ProcessBuilder this way

 private void  execReloadAll(){
    
    
        List<String> cmds = new ArrayList<String>();
        ## 拼装相关的脚本命令
        cmds.add(StrongSwanCommand.COMMAND_SH);
        cmds.add(StrongSwanCommand.COMMAND_ENV);
        ProcessBuilder pb=new ProcessBuilder(cmds);
        int exitCode = 0;
        try {
    
    
            Process process = pb.start();
            /**
             * 问题:调用shell时出现假死问题
             * waitFor()一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。
             * 对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,
             * 甚至死锁。问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。接着来分析缓冲区,哪来的这个东西,
             * 当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。
             * 假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。
             *
             */
            String result = "";
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    process.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
    
    
                sb.append(line).append("\n");
            }
            result = sb.toString();

            exitCode = process.waitFor();
            log.info("result:---{}",result);
 

            // 等待该进程执行完毕
            if (exitCode != 0) {
    
    
                // 如果进程运行结果不为0,表示进程是错误退出的
                // 获得进程实例的错误输出
                InputStream error = process.getErrorStream();
                log.info("加载失败:{}",error);
                throw new BizException(ErrorCode.IKE_LOAD_ALL_ERROR.getCode(),ErrorCode.IKE_LOAD_ALL_ERROR.getMsg());
            }
            // 获得进程实例的标准输出
            InputStream sdin = process.getInputStream();
            log.info("加载:{}",sdin);
        } catch (Exception e) {
    
    
            log.error("执行失败", e);
            throw new BizException(ErrorCode.LOAD_ALL_ERROR.getCode(),ErrorCode.LOAD_ALL_ERROR.getMsg());

        }
        assert exitCode == 0;
    }

or through exec

public static String exec(String cmd) {
    
    
	String result = "";
	try {
    
    
		String[] shellCmd = new String[] {
    
     "/bin/sh", "-c", cmd };
		Process ps = Runtime.getRuntime().exec(cmd);
		
 
		BufferedReader br = new BufferedReader(new InputStreamReader(
				ps.getInputStream()));
		StringBuffer sb = new StringBuffer();
		String line;
		while ((line = br.readLine()) != null) {
    
    
			sb.append(line).append("\n");
		}
		result = sb.toString();
        ps.waitFor();
		System.out.println(result);
	} catch (Exception e) {
    
    
		e.printStackTrace();
	}
	return result;
}

Guess you like

Origin blog.csdn.net/sunrj_niu/article/details/124470481