【问题标题】:Shell Scripts hangs when running through ProcessBuilder通过 ProcessBuilder 运行时,Shell 脚本挂起
【发布时间】:2020-09-15 07:01:32
【问题描述】:

我有一个 Java 程序,我在其中触发了一个 shell 脚本。 Java 代码示例是:

 ProcessBuilder pb = new ProcessBuilder(cmdList);
        p = pb.start();
        p.waitFor();

其中 cmdList 包含执行 shell 所需的所有必要输入参数。这个 shell 脚本内部有一个 for 循环,并在该循环中执行一些 DB 脚本,并在文件中打印结果信息和错误日志。

以下是示例 shell 脚本代码:

#!/bin/bash

导出 PATH=/apps/PostgresPlus/as9.6/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

设置-eE

################################################# ## 开始

TIME_ELAPSED="" TIME_ELAPSED_IN_HOURS="" SCRIPT_START_TIME_FORMATTED=date '+%F %T' SCRIPT_START_TIME_IN_SEC=date +%s

PROCESS_LOG_BASE_PATH="/data/logs/purge_log/" PROCESS_LOG="$PROCESS_LOG_BASE_PATH/purge.log"

陷阱'err=$?; logError 2>&1 “清除期间发生错误。在 $LINENO 行以 $err 状态退出:${BASH_COMMAND}。请查看日志以获取更多信息。” >>$PROCESS_LOG' 错误 trap 'logError 2>&1 "清除期间发生错误。由于收到外部中断而退出 shell 脚本执行。请检查日志以获取更多信息。" >>$PROCESS_LOG;陷阱 ERR' INT

横幅() { 回声 "+---------------------------------------------- -------------------------------------------------+ " printf "|tput bold[ %-40s tput sgr0|\n" "$1 ] tput setaf 2 $2" 回声 "+---------------------------------------------- -------------------------------------------------+ " }

logError() { printf "[ProcessId- $$] [date "+%Y-%m-%d %H:%M:%S"] tput setaf 1 tput bold [ERROR] tput setaf 1 %-40s tput sgr0\n" "$@" }

logInfo(){ printf "[ProcessId- $$] [date "+%Y-%m-%d %H:%M:%S"] tput setaf 6 bold [INFO] %-40s tput sgr0\n" "$@" } 日志警告(){ printf "[ProcessId- $$] [date "+%Y-%m-%d %H:%M:%S"] tput setaf 3 tput bold [WARNING] %-40s tput sgr0\n" "$@" }

logHint(){ printf "[ProcessId- $$] [date "+%Y-%m-%d %H:%M:%S"] tput setaf 5 tput sitm %-40s tput sgr0\n" "$@" }

主() {
banner "$SCRIPT_START_TIME_FORMATTED" "开始处理" | tee -a $PROCESS_LOG logInfo "在 $SCRIPT_START_TIME_FORMATTED 开始执行" | tee -a $PROCESS_LOG

set PGPASSWORD=$DB_PASSWORD
export PGPASSWORD=$DB_PASSWORD

# Call DB function for audit and category wise data purging, population of schema names
SCHEMA_NAMES_RESULT=$(psql -h $HOST_NAME -d $DB_NAME -U $DB_USER -p $DB_PORT -At -c "SELECT $COMMON_SCHEMA_NAME.purge_audit_and_populate_schema_names('$COMMON_SCHEMA_NAME', $PURGE_DATA_INTERVAL_IN_DAYS,'$SCHEMA_NAMES',$NUM_TOP_CONTRIBUTING_TENANTS)")

SCHEMA_NAMES_RESULT=$(echo "$SCHEMA_NAMES_RESULT" | sed 's/{//g; s/}//g; s/"//g' )

SCHEMA_NAMES=$(echo $SCHEMA_NAMES_RESULT | rev | cut -d"," -f2-  | rev)

#Convert comma separated string of tenants to array
SCHEMA_NAMES=($(echo "$SCHEMA_NAMES" | tr ',' '\n'))

# loop for multi schema
for element in "${SCHEMA_NAMES[@]}"
do
    logInfo "Effective tenant - $element, Script start time - $SCRIPT_START_TIME_FORMATTED" | tee -a $PROCESS_LOG

    # PGSQL call to DB function to execute purging

    logInfo "Time elapsed since script execution started - $TIME_ELAPSED" | tee -a $PROCESS_LOG
done

#logInfo "Purge completed!" | tee -a $PROCESS_LOG
logInfo "Purge execution completed successfully at `date '+%F %T'`" | tee -a $PROCESS_LOG
exit 0

}

mkdir -p $PROCESS_LOG_BASE_PATH 主“$@”

################################################# ## 结尾

以下是我对这个程序的观察。

  1. 直接在 putty 上运行 shell 脚本时,它可以正常执行,没有任何错误。
  2. 当通过上面的java程序触发shell脚本时,我观察到了以下行为。

    一个。它在 for 循环中的某个迭代后挂起。

    b.随着我从 shell 脚本中减少日志条目的数量,迭代(for 循环)数量不断增加。

    c。当我删除所有信息日志并继续仅打印错误日志时,它成功完成。

请有人帮忙找出这种行为背后的原因。

目前,我在 for 循环中检查了迭代次数,但是当我开始接收多个错误日志时,任何时候都会出现这个问题。

问候

库沙格拉

【问题讨论】:

    标签: java shell logging processbuilder freeze


    【解决方案1】:

    您必须使用流程流或将errout 映射到文件,以免本机缓冲区填满。如果您创建线程来使用每个流,它会更好地工作。 hacky 单线程版本是这样的:

    ProcessBuilder pb = new ProcessBuilder(cmdList);
    p = pb.start();
    try (InputStream in = p.getInputStream();
                InputStream err = p.getErrorStream();
                OutputStream closeOnly = p.getOutputStream()) {
        while (p.isAlive()) {                
            long skipped = 0L;
            try {
                skipped = in.skip(in.available()) 
                         + err.skip(err.available());
            } catch (IOException jdk8155808) {
               byte[] b = new byte[2048];
               int read = in.read(b, 0, Math.min(b.length, in.available());
               if (read > 0) {
                   skipped += read;
               }
    
               read = err.read(b, 0, Math.min(b.length, err.available());
               if (read > 0) {
                   skipped += read;
               }
            }
    
            if(skipped == 0L) {
               p.waitFor(5L, TimeUnit.MILLISECONDS);
            }
        }
    } finally {
       p.destroy();
    }
    

    线程方式是这样工作的:

    public void foo() {
        class DevNull implements Runnable {
            
            private final InputStream is;
            DevNull(final InputStream is) {
                is = Objects.requireNonNull(is);
            }
            
            public void run() {
                byte[] b = new byte[64];
                try {
                    while (is.read(b) >= 0);
                } catch(IOException ignore) {
                }
            }
        }
    
        ExecutorService e = Executors.newCachedThreadPool();
        ProcessBuilder pb = new ProcessBuilder(cmdList);
        Process p = pb.start();
        try (InputStream in = p.getInputStream();
                InputStream err = p.getErrorStream();
                OutputStream closeOnly = p.getOutputStream()) {
            e.execute(new DevNull(in));
            e.execute(new DevNull(err));
            p.waitFor();
        } finally {
            p.destroy();
            e.shutdown();
        }
    }
    

    【讨论】:

      【解决方案2】:

      感谢多线程对我有用。

      对于单线程选项,它在 skip() 上失败。

      再次感谢您帮助解决问题。

      【讨论】: