使用ch.ethz.ssh2中sess.execCommand方法导致线程卡死的原因分析

背景

前几天有同事反馈,说生产上的定时任务好像卡住了,没有执行。
上服务器,查看应用日志,定时任务确实没有执行。

分析

这种情况,第一时间先把线程dump文件导出来
分析dump文件,发现线程一直在执行sess.execCommand方法

使用ch.ethz.ssh2中sess.execCommand方法导致线程卡死的原因分析

看来是这个方法的问题waitForChannelSuccessOrFailure。
赶紧看一下这个方法的源码

private final void waitForChannelSuccessOrFailure(Channel c) throws IOException {
        synchronized(c) {
            while(c.successCounter == 0 && c.failedCounter == 0) {
                if(c.state != 2) {
                    String detail = c.getReasonClosed();
                    if(detail == null) {
                        detail = "state: " + c.state;
                    }

                    throw new IOException("This SSH2 channel is not open (" + detail + ")");
                }

                try {
                    c.wait();
                } catch (InterruptedException var4) {
                    ;
                }
            }

            if(c.failedCounter != 0) {
                throw new IOException("The server denied the request.");
            }
        }
    }

线程现在一直在c.wait这里,等待服务器返回信息将他唤醒。
问题是服务器不知道啥情况,就是不返回,所以线程就一直卡在这里了

解决方案

一般来说可以通过设置超时时间来处理这些问题。但是这个方法根本没有超时时间设置的参数。
好在找到了waitForCondition这个方法,可以设置一些条件来达到超时时间设置的效果.
直接贴代码吧

/*
*列出目录下的文件信息
*/
public static String[] getRemoteDirFileNames(Connection conn, String remoteDir){
        Session sess=null;
        try {
            sess = conn.openSession();
            sess.execCommand("ls -lt "+remoteDir);
            InputStream stdout = new StreamGobbler(sess.getStdout());
            InputStream stderr = new StreamGobbler(sess.getStderr());

            byte[] buffer = new byte[100];
            String result = "";
            while (true) {
                if ((stdout.available() == 0)) {
                    int conditions = sess.waitForCondition(ChannelCondition.STDOUT_DATA |
                            ChannelCondition.STDERR_DATA | ChannelCondition.EOF, 1000*5);
                    if ((conditions & ChannelCondition.TIMEOUT) != 0) {
                        break;//超时后退出循环,要保证超时时间内,脚本可以运行完成
                    }
                    if ((conditions & ChannelCondition.EOF) != 0) {
                        if ((conditions & (ChannelCondition.STDOUT_DATA |
                                ChannelCondition.STDERR_DATA)) == 0) {
                            break;
                        }
                    }
                }

                if(stdout!=null){
                    String fileNames= ConvertUtil.parse_String(stdout);
                    if(fileNames!=null){
                        return fileNames.split("\n");
                    }
                }

                while (stderr.available() > 0) {
                    int len = stderr.read(buffer);
                    if (len > 0){
                        result += new String(buffer, 0, len);
                    }
                }
            }

        } catch (Exception e) {
            log.info("获取指定目录下文件列表失败:"+e.getMessage());
        }finally {
            sess.close();
        }
        return null;
    }