2014/10/01

ロック(排他制御)

cronを使って一定の処理をさせたいとき,毎分に設定したいけどそうすると前の処理が終わっていなくて多重起動されてしまうという問題があります(問題ない場合もあると思いますが).これを制御するのにロックファイルを使っていたのですが,ロックディレクトリにしなきゃダメだと指摘され,修正しました.

if [ -r $lockfile ]; then
  exit 0
else
  touch $lockfile
fi
.... (script) ....
rm $lockfile

[ -r $lockfile ] && exit 0
touch $lockfile

としたくなるわけですが,まったく同時に2つ以上のスクリプトが起動された場合には両方共,ロックファイル($lockfile)が無いので処理が始まってしまいます.

これを避けるには,mkdir等の排他的なシステムコールを使うのだそうです.
mkdir $lockdir 2> /dev/null || exit $?
.... (script) ....
rmdir $lockdir

mkdir は既に $lockdir が存在していた場合はエラーになりますので処理を停止できます.たとえスクリプトが同時に起動されたとしても,mkdirで排他してくれるそうです.

ですが,スクリプトが強制終了された時にロックディレクトリが残ってしまうという問題があります.スクリプトの中で使っているコマンドが異常終了したような場合にはちゃんとロックディレクトリを後始末しましょう.

error() {
  echo $1
  rmdir $lockdir
  exit 1
}
などとしておいて,
./command || error "failed ./command"
のようなエラー制御を入れておけば良いはずです.面倒ですが.

が,それでも,スクリプトが強制終了(たとえば,rebootなどの操作)される可能性もあります.そうすると,もうお手上げでロックディレクトリが常に存在してしまうためスクリプトが動きません.これを回避するために,一定時間以上経過していたらロックディレクトリを消すというロジックもあるようですが,じゃぁ,一定時間はどれくらいだ?という問題が残ります.予期せぬ長時間処理になった場合,どうするかということになります.

が,眼から鱗な解決策が!
lockdir=/dev/shm/xxxx

RAMディスクにロックディレクトリを作成しておけば,rebootが行われた場合でもすっきりとロックファイルを消すことが出来ます.もちろん共有空間ですので,ファイル名のバッティングだけは要注意ですが.

ただし,終了した後のアプリケーションとしてのケアは別途必要ですので悪しからず.