setlockを使って簡単に多重起動防止機能を実装する


ギョームの必要上、cronを使って定期実行をするスクリプトに多重起動防止の機能を実装したいときがあった。
その際に、`setlock`というdaemontoolsに付属されているスクリプトを使うと、ロックファイルを使って簡単に多重起動防止機能が実装できることを会社の先輩に教わったので、使い方のメモを残しておく。

- [cr.yp.to/daemontools/setlock.html](http://cr.yp.to/daemontools/setlock.html)
- [The setlock program（↑上記サイトの日本語版）](http://www.emaillab.org/djb/tools/daemontools/setlock.html)

サンプルコードの実行環境はCentOS6.5。

## サンプル
例えば`sleeping.rb`という、10秒sleepして「wake up!」と出力するだけの単純なスクリプトがあったとする。

```ruby:sleeping.rb
sleep 10
puts "wake up!"
```

setlockを使うと、次のようにしてsleeping.rbの多重起動の防止をすることができる。
（ :warning: /var/lock/vagrantに書き込みと読み込みの権限がある前提 ）

```console
$ setlock -Xn /var/lock/vagrant/sleeping.lock ruby sleeping.rb
```

このとき、別プロンプトを立ち上げるなどして同じコマンドでsleeping.rbを続けて起動しようとすると、次のようなエラーを吐いてスクリプトが終了する。

```console
$ setlock -Xn /var/lock/vagrant/sleeping.lock ruby sleeping.rb
setlock: fatal: unable to lock /var/lock/sleeping.lock: temporary failure
```

また、`N`オプションを使えば、スクリプトを即終了せず、起動中のプロセスが終了するのを待ってから実行するというようなこともできる。

```console
$ setlock -XN /var/lock/vagrant/sleeping.lock ruby sleeping.rb
# (現在起動中のスクリプトが終了するのを待ってから実行される)
wake up!
```

## 仕組み
setlockは単純に、第1引数に指定したファイルをロックして、続く引数に指定されたコマンド起動を制御する機能を提供してくれる。
上のサンプルでは、`/var/lock/vagrant/sleeping.lock`をロックし、このファイルがロックされている間は`ruby sleeping.rb`を実行しない（あるいは実行を待つ）といったことをしていた。

なので、もしsetlockの第1引数に指定するロックファイルが異なると、sleeping.rbは多重起動することになる。

```console:このケースではsleeping.rbが多重起動する
# /var/lock/vagrant/sleeping.lockを使う
$ setlock -Xn /var/lock/vagrant/sleeping.lock ruby sleeping.rb

# /var/lock/vagrant/sleeping.lock2を使う
$ setlock -Xn /var/lock/vagrant/sleeping.lock2 ruby sleeping.rb
```

今回自分はcronで定期実行するスクリプトに対して多重起動防止機能を実装したかったので、setlockはとても都合が良かった。

## setlockのオプション
オプションは4つのみ

| オプション | 意味 |
| :--: | :-- |
| -n | 指定したファイルがロックされている場合、コマンド実行を諦めて終了する |
| -N | デフォルトオプション。指定したファイルがロックされている場合、新しいロックをかけることができるまで待機する |
| -x | ファイルのロックができない（あるいは作成できない）場合、コード0で終了する |
| -X | デフォルトオプション。ファイルのロックができない（あるいは作成できない）場合、エラーメッセージを吐いて非0コードで終了する |

どのオプションを使うかは、スクリプトの要件次第だと思う。
自分の場合はロックされている場合は非0で終了してしまって、次のcron実行を待つようにしたかったので「-Xn」オプションを使っていた。

## 所感
setlockの存在を知らないでコードを書いていたときは、実行するスクリプト内に「ロックファイルを作って、排他ロックをかけて、ロックがされている場合にはエラーメッセージを吐いて終了する」という本質的でない処理が含まれていて、コードの見通しが悪くなっていた。

setlockを使うことで実行スクリプト内には本質的な処理だけを記述して、ロック処理はコードの外（コマンドライン上）に逃すことができたので、コードがスッキリして良かったし、多重起動防止機能自体も自分で実装せずに枯れた技術であるdaemontoolsに任せることができて安心感があった。

## 【参考】daemontoolsのインストール
参考までに、daemontoolsのインストール方法を載せておく（setlockはdaemontoolsに含まれている）。
yumではインストールができないのみたいなので、ソースからインストールを行う。

[cr.yp.to/daemontools/install.html](http://cr.yp.to/daemontools/install.html)のインストール手順を参考に行った。


```console
$ sudo su -
$ yum install wget

# /packageディレクトリの作成と移動
$ mkdir -p /package
$ chmod 1755 /package
$ cd /package

# daemontoolsのソースを取得
$ wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz
$ tar -xpf daemontools-0.76.tar.gz
$ rm -f daemontools-0.76.tar.gz
$ cd admin/daemontools-0.76/

# パッチを当てる
$ wget http://www.qmail.org/moni.csi.hu/pub/glibc-2.3.1/daemontools-0.76.errno.patch
# 接続できない場合コチラなら繋がるかも => http://djbware.csi.hu/patches/daemontools-0.76.errno.patch
$ patch -p1 < daemontools-0.76.errno.patch

# インストール
$ package/install
```

ヘルプの表示でインストール確認

```
$ setlock --h
setlock: usage: setlock [ -nNxX ] file program [ arg ... ]
```