0

我正在尝试编写一个脚本,它将启动我的(节点)开发服务器,并且每当它收到一个SIGHUP它应该重新启动服务器。

我已经生成了服务器,将其关闭并在SIGHUP. 但是因为我wait在生成代码中使用,SIGHUP处理程序永远不会真正返回,这导致信号永远不会再次触发。

这是我的脚本的精简版:

SERVER_PID=""

start_server() {
    npm start &
    SERVER_PID=$!
    wait $SERVER_PID
}
terminate_server() {
    [ ! "xSERVER_PID" = "X" ] && kill -SIGTERM $SERVER_PID
    SERVER_PID=""
}
refresh_server() {
    terminate_server
    start_server
}

trap refresh_server SIGHUP
start_server

正如我所提到的,它可以很好地启动服务器,并且在 first 上按预期工作SIGHUP,但是由于inrefresh_server永远不会返回,因此后续信号不会触发任何操作。waitstart_server

现在,我已经通过取出waitin解决了这个问题start_server,并在底部添加了一个无限的“while-true-sleep”循环(在初始调用之后start_server),但我确信必须有更好的方法来完成我想要达到的目标。此外,我不喜欢睡眠循环方法导致的信号触发延迟。

4

3 回答 3

0

while-true-wait 循环怎么样?

#!/bin/bash
SERVER_PID=""
SERVER_NAME="npm start"

start_server() {
    $SERVER_NAME &
    SERVER_PID=$!
    SERVER_ACTIVE=true
}
terminate_server() {
    [ ! "xSERVER_PID" = "X" ] && kill -SIGTERM $SERVER_PID
    SERVER_PID=""
}
refresh_server() {
    terminate_server
    start_server
}

trap refresh_server SIGHUP
start_server
while $SERVER_ACTIVE; do
  SERVER_ACTIVE=false
  wait $SERVER_PID
done

如果应该重复,则需要某种等待事件循环,无论是在脚本中显式地还是隐藏在 bash 中的某个地方

于 2013-02-21T21:47:48.867 回答
0

bash 下的信号陷阱

从异常返回

就像在其他编程语言中使用信号一样,信号捕获很容易以错误的方式完成;

使用时,trap您不必陷阱评估中处理您的函数,而只需设置一个标志主程序可以在陷阱异常结束后检查,以尽可能缩短异常执行时间。

特别是,您不必陷阱执行级别启动fork子进程!

一个正确的例子

#!/bin/bash

SERVER_PID=""
CMD_TRAP=""

npm() { #Doing something that could be checked from external
    if [ "$1" ] && [ "$1" == "start" ] ;then
    while :;do
        date "+%s%N" >/tmp/dummyfile.txt
        sleep .333
          done
    fi
}

start_server() {
    npm start &
    SERVER_PID=$!
}
terminate_server() {
    [ "$SERVER_PID" ] && ps $SERVER_PID &>/dev/null && kill -TERM $SERVER_PID
    SERVER_PID=""
}
refresh_server() {
    terminate_server
    start_server
}

printf "for:\n   server restart, hit: 'kill -USR2 %d'\n" $$
printf "   server stop, hit: 'kill %d' (or Ctrl+C)\n" $$

trap 'CMD_TRAP=refresh' USR2 HUP
trap 'CMD_TRAP=terminate' TERM INT

start_server
while [ "$SERVER_PID" ];do 
    wait $SERVER_PID
    case "$CMD_TRAP" in
        refresh   ) refresh_server   ;;
        terminate ) terminate_server ;;
        *         ) refresh_server   ;;        # in case server just end.
      esac;
    CMD_TRAP=""
    [ "$SERVER_PID" ] && echo "LOOP." || echo "EXIT."
  done

特征

这个演示脚本做:

  • 服务将在脚本启动时启动,
  • USR2如果收到一个或一个HUP信号,服务将重新启动,
  • 如果它刚刚完成或收到任何信号,服务将重新启动并且
  • 服务将正确停止TERM是收到信号或
    • 如果Ctrl-C在控制台上被击中。
  • 异常处理正确(立即返回主程序)
  • 没有不需要的/非托管的错误消息

输出样本

窗口1

tty
/dev/pts/0

窗口2

ps --tty pts/0 fw
  PID TTY      STAT   TIME COMMAND
 2996 pts/0    Ss     0:01 bash
 5187 pts/0    S+     0:00  \_ bash

窗口1

./serverScript.sh 
for:
   server restart, hit: 'kill -USR2 11469'
   server stop, hit: 'kill 11469' (or Ctrl+C)

窗口2

ps --tty pts/0 fw
  PID TTY      STAT   TIME COMMAND
 2996 pts/0    Ss     0:01 bash
 5187 pts/0    S      0:00  \_ bash
11469 pts/0    S+     0:00      \_ /bin/bash ./servermon.sh
11470 pts/0    S+     0:00          \_ /bin/bash ./servermon.sh

cat /tmp/dummyfile.txt 
1361603642256133674

cat /tmp/dummyfile.txt 
1361603648712606114

    ps --tty pts/0 fw
  PID TTY      STAT   TIME COMMAND
 2996 pts/0    Ss     0:01 bash
 5187 pts/0    S      0:00  \_ bash
11469 pts/0    S+     0:00      \_ /bin/bash ./servermon.sh
11470 pts/0    S+     0:01          \_ /bin/bash ./servermon.sh
16814 pts/0    S+     0:00              \_ sleep .333

kill -USR2 11469

窗口1

LOOP.

窗口2

    ps --tty pts/0 fw
  PID TTY      STAT   TIME COMMAND
 2996 pts/0    Ss     0:01 bash
 5187 pts/0    S      0:00  \_ bash
11469 pts/0    S+     0:00      \_ /bin/bash ./servermon.sh
17152 pts/0    S+     0:00          \_ /bin/bash ./servermon.sh
17532 pts/0    S+     0:00              \_ sleep .333

cat /tmp/dummyfile.txt
1361604208069564188
cat /tmp/dummyfile.txt
1361604209103660589

kill -USR2 11469

窗口1

LOOP.

窗口2

cat /tmp/dummyfile.txt
1361604278583723517

cat /tmp/dummyfile.txt
1361604279605292149

kill 11469

窗口1

EXIT.
$

窗口 1 终止循环服务器子进程。

窗口1

./serverScript.sh 
for:
   server restart, hit: 'kill -USR2 19232'
   server stop, hit: 'kill 19232' (or Ctrl+C)

然后如果Ctrl+C被按下:

窗口1

^CEXIT.
$

窗口2

ps --tty pts/0 fw
  PID TTY      STAT   TIME COMMAND
 2996 pts/0    Ss     0:01 bash
 5187 pts/0    S+     0:00  \_ bash

解释

无法堆叠异常。因此,在执行异常时,可以忽略另一个中断:

如果对于示例,这可能变得很重要,您的refresh_server()函数必须在重新启动服务器之前压缩和轮换一些日志:

refresh_server() {
    terminate_server
    lockedfilename=-$(date +%F_%H-%M-%S-$$)
    mv /srv/logfile /srv/logfile-$lockedfilename
    gzip /srv/logfile-$lockedfilename
    start_server
}

许多中断可以在主循环中汇总或忽略,但处理必须仅在主级别完成。

有一个关于问题所在的小演示:

窗口1

trap "echo USR2 sleep 4;sleep 4" USR2
while :;do printf "\r%s " $(date +%s%N);sleep .333;done
1361606565xxxxxxxxx

xxxxxxxxx改变 3x / 秒)

窗口2

for ((i=5;i--;));do echo KILL;kill -USR2 5187;sleep .5;done
KILL
KILL
KILL
KILL
KILL

1361606722582175969 USR2 sleep 4
USR2 sleep 4
1361606770xxxxxxxxx

我的循环中只有两个超过五次的中断被困住。

如果您搜索解释:5 x 0,5 = 2,5 秒。它远少于 4 秒睡眠,那么为什么我收到第二个中断,但不是全部五个?

在主循环中解开陷阱

在主循环中解开陷阱有一个小技巧:

while [ "$SERVER_PID" ];do 
    wait $SERVER_PID

    OLDIFS="$IFS" IFS=$'\n'
    TRAPS=($(trap))                            # save traps
    IFS="$OLDIFS"
    eval "$(printf "trap -- %s\n" ${TRAPS[@]##*\'})"  # untrap

    case "$CMD_TRAP" in
        refresh   ) refresh_server   ;;
        terminate ) terminate_server ;;
        *         ) refresh_server   ;;        # in case server just end.
      esac;
    CMD_TRAP=""
    if [ "$SERVER_PID" ]
    then echo "LOOP."
    else echo "EXIT."

        eval "$(printf "%s\n" "${TRAPS[@]}")"      # restore traps
    fi
  done
于 2013-02-23T00:47:05.510 回答
0

这是一个带有尾递归 start_server 的解决方案,只需对您的代码进行少量更改。(删除一行并添加三行包含HUPPED

HUPPED=false
SERVER_PID=""

start_server() {
    npm start &
    SERVER_PID=$!
    wait $SERVER_PID
    if $HUPPED; then HUPPED=false; start_server; fi
}
terminate_server() {
    [ ! "xSERVER_PID" = "X" ] && kill -SIGTERM $SERVER_PID
    SERVER_PID=""
}
refresh_server() {
    HUPPED=true
    terminate_server
    #start_server
}

trap refresh_server SIGHUP
start_server
于 2013-02-23T01:16:47.283 回答