Hatena::ブログ(Diary)

(ひ)メモ このページをアンテナに追加 RSSフィード

2013-02-27 (Wed)

プロセス間の期限付き排他ロック

追記 2013-03-04

@ さんにも言及されてました><

追記 2013-03-01

つづきを書きました。

追記 2013-02-27

コメント欄でのやりとりの通り、穴がある(クリティカルセクションに複数プロセスが突入する可能性がある)のでちょっと出直してきます!!

やりたいこと

  • プロセス間の排他的実行を制御したい
  • 一定時間経過したら実行できるようにしたい
    • 例えば、フェイルオーバーを実行するスクリプトは、何度も実行できるとフェイルオーバー/バックを繰り返してフラップするので、一度フェイルオーバーしたら一定時間は実行できないようにしたい

実装

  • シンボリックリンクを使って排他制御する
  • 期限切れは、シンボリックリンクそのものの mtime で表現する
    • mtime <= now なら期限切れ
    • つまり、シンボリックリンクの mtime を期限が切れる時刻(未来)にする
    • シンボリックリンクの mtime は作成時のままにして、ttl を意識して、now - mtime > ttl で判定するのと比較した利点
      • スクリプトのttlを確認しなくても、statコマンドでシンボリックリンクのmtimeを見れば、いつ期限が切れるのかすぐにわかる
      • touch -h (-hオプションは多分coreutils 8.1以上ので対応)やlutime(3)を使って、スクリプト外から期限の延長が行える (mtimeを更に未来の時刻にすればいい)
  • シンボリックリンクは、その元となるファイルは存在していなくても構わない
    • ロック元のプロセスを特定できる情報を持った(存在しない)ファイル名にしておく
      • 識別子 + PID

実行例

ttl 10秒でロックを獲得。unlockせずに終了。
$ ./ipc-lock-withttl.pl
2013-02-27T11:00:00 [INFO] Got lock! yay!! at ./ipc-lock-withttl.pl line 20

10秒経過するまでは、再実行してもロックを獲得できない
$ ./ipc-lock-withttl.pl
2013-02-27T11:00:03 [CRITICAL] Cannot lock at ./ipc-lock-withttl.pl line 17
$ ./ipc-lock-withttl.pl
2013-02-27T11:00:07 [CRITICAL] Cannot lock at ./ipc-lock-withttl.pl line 17

10秒経過すると、再度ロックを獲得できる
$ ./ipc-lock-withttl.pl
2013-02-27T11:00:10 [INFO] Got lock! yay!! at ./ipc-lock-withttl.pl line 20

【募】

  • それ○○でできるよ!!
  • IPC::Lock::WithTTL よりよい名前
  • 実装の妥当性に対するご意見
  • その他、はげましのお便り

fumiyasfumiyas 2013/02/27 13:29 ロックファイルがタイムアウトした状態で 2つ以上のプロセス or スレッドが
同時にロックを獲得しようとすると、競合が発生する可能性があります。
タイムアウト時、あるプロセスがロック解除(ロックファイル削除)して
ロック獲得試行する間に別のプロセスが割り込んだ場合。この問題を
symlink のロックで完全に回避するのは難しいかも。

ロックを獲得しているプロセス ID が存在しているかどうかチェックする機能
(オプション?)が欲しい。

タイムアウトでロック解除するときにロック獲得しているプロセスに
シグナル送信するオプションもあったら便利?

hirose31hirose31 2013/02/27 15:00 id:fumiyas さん

自分の場合はロック獲得できるのが唯一つであることが保証されればいい(これはsymlink(2)で担保されます)ので、unlockしたプロセスがロックを獲得できずに割り込んだプロセスが獲得してもよかったりします。

これが許容できないケースでは、指摘の通り、今回の実装は適さないですねぇ。

fumiyasfumiyas 2013/02/27 15:11 (1)プロセスAがタイムアウト検知→(2)プロセスBがタイムアウト検知→
(3)Aがロックファイル削除→(4)Aがロックファイル作成→
(5)Bがロックファイル削除→(6)Bがロックファイル作成…
の結果、同時に 2つのプロセスがロックを獲得する可能性があるのですが、
これは許容できないですよね?

ロックファイル作成後に symlink 先に自分の PID が含まれるかどうかを
チェック→自分じゃなければロック失敗、とすればいいのかもしれません。

hirose31hirose31 2013/02/27 15:26 id:fumiyas さん

おっしゃる通りです><

でもこれ、PIDを確認するとしても、

(1) [A]タイムアウト検知
(2) [B]タイムアウト検知
(3) [A]ロックファイル削除 (過去にだれかが作ったもの)
(4) [A]ロックファイル作成
(5) [A]ロックファイル確認 (確かにAが作ったやつ)
(6) [A]クリティカルブロック突入
(7) [B]ロックファイル削除 (さっきAが作ったもの)
(8) [B]ロックファイル作成
(9) [B]ロックファイル確認 (確かにBが作ったやつ)
(10) [B]クリティカルブロック突入

となると、ダメですよね。。。

fumiyasfumiyas 2013/02/27 15:29 あー、駄目ですね。チェック後にほかのプロセスが削除するかもしれないから。
flock(1) みたいにロックの獲得、タイムアウト、開放を 1主体(?)で実行する
以外に方法は無いような気がします。

もしくは素直に flock(2) か fcntl(2) のロックを使うのがよいでしょうね。
NFS の場合でもちゃんと設定していれば fcntl(2) のロックは動きますし。

viverviver 2013/02/27 19:18 分散システムにおけるリースの概念(?)と同じ方法が使えるんじゃないかと思います。
実装は多少面倒ですが、誰かがflockを握りっぱなしで固まってしまって困る確率は、ほぼ0です。

例えばこんなフォーマットのファイルを使って:

<pid>\t<expiration time>

1. ファイルを1つあらかじめ用意しておく(flockできるように)
2. フェイルオーバーを実行したいプロセスは、ファイルをflock(排他ロックでOK)して:
 a. ファイルの中身が空か、先頭のpidが0なら、自分のpidとexpiration time=現在時刻+X秒 にセットする→acquire状態に入る
 b. 先頭がpidが自分と異なるが、現在時刻がexpiration timeより後なら、a.と同様に自分のpidとexpiration timeをセットする。それから先頭のpidのプロセスにKILLシグナルを飛ばしてフェイルオーバー処理が同時に走らないように防止する→acquire状態に入る
c. 先頭のpidが自分と同じなら、単にexpiration timeを更新する→acquire状態に入る
d. そうでなければ、ちょっとしてからまた見にいく
3. flockを解除する
4. フェイルオーバー処理が終わったら、pid=0, expiration time=現在時刻+”再度フェイルオーバーを走らせても良い秒数" にセットする→acquire状態を抜ける
5. もしフェイルオーバー処理がX秒以内に終わることを高確率で期待できないなら、acquire状態のとき、バックグラウンドスレッドで定期的に2.を実行してexpiration timeを更新する
6. バックグラウンドスレッドで2.を実行中に、2-d.の条件に入ってしまったら、別のプロセスがフェイルオーバーを同時に走らせていることを意味するので、自分はいさぎよく自殺する

X秒は短めの方が、処理中のプロセスが固まってしまった場合に素早く別のプロセスが対応させられるので良いです。
ただ短くすると5,6を実装しないといけないので、それなりに長くしてシンプルさを取るのもアリです。

viverviver 2013/02/27 20:38 2-a.でpid=0のときの扱いが間違ってますね^^; pid=0でもexpiration timeのチェックが必要。

この機構を使って実装したPerfectQueueというジョブキューの実装があったりします、という宣伝:https://github.com/treasure-data/perfectqueue

sakurai_youheisakurai_youhei 2013/02/28 02:51 これにプロセス間で共通のlockディレクトリを作成・削除する仕組みを入れればできる気が。作成が成功したプロセスだけ処理を継続するようにすれば、、、昔のCGIみたいに。VMware Playerもこの方式で排他制御を実装しているように見受けられるし。

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

2003 | 11 | 12 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 05 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 12 |
2012 | 01 | 02 | 03 | 06 | 08 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 10 |
2015 | 01 | 02 | 07 | 10 |
2016 | 01 | 05 | 10 | 12 |