cron から起動されたジョブを、一時停止・再開する
我が家は伊豆の山奥なので光回線が来ておらず、遅い ADSL でやりくりをしています。一方、サーバは cron(8) で rsync(1) を起動してリモートからのファイルのミラーリングをしているのですが、こいつがネットワークの帯域を専有してしまうと他の作業ができなくなってしまいます。そこで、cron から実行されたコマンドを必要に応じて一時停止・再開できると良いなと思った次第です。
(この手のデーモン化の手段は、Linux なんかでは普通に提供されていたと思うんですが、Mac でも動かしたかったのと、pid ファイルやロックファイルを使いたくなかったので、以下のような書き方になっている次第です)
以下はサンプルです。putdates0 exec
で、端末に延々と日付を表示し始めます。他の端末から putdates0 stop
とすればプロセスは一時停止、putdates0 cont
とすれば再開、putdates0 snooze
とすればプロセスは一時停止した後 10 秒後に再開します。
#!/bin/bash
# -*- coding: utf-8 -*-
## プロセスの名前(システムでユニークに)
process_name="SimplePutDatesProcess"
## スヌーズさせた際の復帰までの秒数
snooze_interval="10"
## サブコマンド: exec | term | stop | cont | snooze | _doit
subcommand="$1"
pid=$(pgrep -f "^$process_name ")
## 子孫プロセスを一覧する
function get_children() {
for pid in $(pgrep -P $1)
do
echo $pid
get_children $pid
done
}
## 子孫プロセスにシグナルを発行する
function kill_tree() {
signal=$1
pid=$2
kill -s $signal $pid $(get_children $pid)
}
## サブコマンドの処理
case $subcommand in
## プロセス名を変更するために、実際の仕事をするプロセスを exec する
exec )
if test -z "$pid"
then
exec -a "$process_name" bash "$0" _doit
fi
exit 0
;;
## プロセスの終了
term )
kill_tree TERM $pid
;;
## プロセスの一時停止
stop )
kill_tree STOP $pid
;;
## プロセスの再開
cont )
kill_tree CONT $pid
;;
## プロセスを一時停止するが、一定時間後に再開
snooze )
kill_tree STOP $pid
(
sleep $snooze_interval
bash "$0" cont
) &
;;
## 実際のジョブ
_doit )
while true
do
date
sleep 1
done
;;
esac
同種のコマンドを作成する際に上記コードをコピペしたくはありません。下記のように、実行コマンド(putdates
)と実ジョブコマンド(_putdates
)に実装を分けて、プロセス管理の機能は smartprocess
の名前で外出ししました。putdates stat
のように、プロセスの状態を確認するサブコマンドを追加しています。
#!/bin/bash
# -*- coding: utf-8 -*-
exec smartprocess _putdates PutDatesProcess 3600 "$@"
#!/bin/bash
# -*- coding: utf-8 -*-
while true
do
date
sleep 1
done
#!/bin/bash
# -*- coding: utf-8 -*-
job_command="$1"
process_name="$2"
snooze_interval="$3"
subcommand="$4"
pid=$(pgrep -f "^$process_name ")
## 子孫プロセス一覧
function get_children() {
for pid in $(pgrep -P $1)
do
echo $pid
get_children $pid
done
}
## 子孫プロセスにシグナルを発行
function kill_tree() {
signal=$1
pid=$2
kill -s $signal $pid $(get_children $pid)
}
## サブコマンドの処理
case $subcommand in
## 実際の仕事をするプロセスを exec する
exec )
if test -z "$pid"
then
exec -a "$process_name" bash "$0" "${@:1:$#-1}" _doit
fi
exit 0
;;
## プロセスの終了
term )
kill_tree TERM $pid
;;
## プロセスの一時停止
stop | sus | susp | suspend )
kill_tree STOP $pid
;;
## プロセスの再開
cont | res | resume )
kill_tree CONT $pid
;;
## プロセスを一時停止するが、一定時間後に再開
snooze )
kill_tree STOP $pid
(
sleep $snooze_interval
bash "$0" "${@:1:$#-1}" cont
) &
;;
## 実際のジョブプロセスを、排他で起動
_doit )
$job_command
;;
## プロセスの状態を表示
stat )
if test -z "$pid"
then
echo Not Running
exit 1
fi
statline=$(ps uww $pid | tail -n 1)
stat=$(echo "$statline" | awk '{print $8}')
echo $stat | grep -q -e T && state=Stopped || state=Running
started=$(echo "$statline" | awk '{print $9}')
time=$(echo "$statline" | awk '{print $10}')
echo pid: $pid \| started: $started \| time: $time \| state: $state
;;
esac
このような感じで、実行コマンドと、時間のかかる実ジョブコマンドを用意すれば、いつでもジョブの一時停止と再開ができるようになります。
crontab(5) の設定的には、一定間隔で exec
をしておき、stop
後の cont
のし忘れに対処するために、一日に一度ほど cont
するようにしておくと良いのではないでしょうか。
今のところ動作は良好なようで、Mac OS X、Linux 上で共に動作しています。何かお気づきの点がありましたらコメントをいただければと思います。
ではまた。