Drupal の排他制御のための API lock_acquire() 関数と lock_release() 関数

Drupal 7

排他制御というのは、複数のプロセスが同時に同じ処理を行なわないように制御することをいう。例えば、ひとつのファイルを複数の人が同時に修正しないようにするもの。

それを実現するためにロックという仕組みがあって、ロックがかかっている間は他のプロセスが同じ処理を出来ないというプログラムが作れるようになっている。そのロックの API だが、ちゃんと Drupal にも用意されている。Drupal では、cron ジョブが複数同時に起動しないように作られてあったり、ちょっと時間のかかる処理の直前にロックをかけるようになっている。

今回あるサイトの構築で利用したので、簡単にメモしておこうと思う。

ロックAPI の基本的な使い方

ファイル include/lock.inc にロックをおこなうための関数が準備されている。コメント欄の説明で使い方は十分わかるだろう(以下は lock.inc の30 行目からのサンプルコードである)。

function mymodule_long_operation() {
  if (lock_acquire('mymodule_long_operation')) {
    // Do the long operation here.
    // ...
    lock_release('mymodule_long_operation');
  }
}

lock_acquire() 関数を呼び出してロックを取得する。取得出来れば処理を行う。処理を終えたら、lock_release() 関数を呼び出してロックを開放する。

lock_acquire() 関数には、キーとなる文字列を渡す。ロックが取得出来れば TRUE を返し、取得できなければ FALSE を返してくれる。

lock_release() 関数で開放するときも同じキーを指定する。キーごとにロックを設定できるようになっているためだ。

上記ソースコードの例では、ロック取得から開放するまでの間、他の人(プロセス)がロックを取得できない(FALSE が返ってくる)ので、if文の処理はひとつのプロセスからしか実行されない。

タイムアウトの指定

lock_acquire() 関数にはもうひとつ省略可能な引数がある。タイムアウトまでの時間を float 型で指定する事ができる。省略すると、30秒だ。30秒以外の時間を指定したい場合は、第二引数で指定する。

例えば、include/common.inc ファイルの cron ジョブの関数 ( drupal_cron_run() ) 内では、下記のようにロックを取得している。

  // Try to acquire cron lock.
  if (!lock_acquire('cron', 240.0)) {
    // Cron is still running normally.
    watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING);
  }
  else {
    // Make sure every queue exists. There is no harm in trying to recreate an
    // existing queue.

"cron" というキーで、240秒(4分) のタイムアウトでロックを取得している。

ロックが開放されるのを待つ

上の例だとロックが取得できなければ処理を行なわないという処理になっているんだけど、ロックが開放されるのを待つための関数が準備されている。lock_wait() 関数だ。

これは引数にロック取得の引数と同じキーを指定し、ロックが開放されるかロックがタイムアウトになると FALSE を返す関数。30秒経過するまでの間、最初は 25ms 後にロックの状態をチェックし、次回以降は 50ms 毎にチェックする。30秒経過してもロックが開放されていなければ TRUE を返す。

省略可能な第二引数 $delay で待ち時間(秒)を指定できる。

具体的には bootstrap.inc ファイルの variable_initialize() 関数で使われている。

    // Cache miss. Avoid a stampede.
    $name = 'variable_init';
    if (!lock_acquire($name, 1)) {
      // Another request is building the variable cache.
      // Wait, then re-run this function.
      lock_wait($name);
      return variable_initialize($conf);
    }
    else {
      // Proceed with variable rebuild.

lock_acquire() 関数を呼び出してロックが取得できなかった時に lock_wait() 関数を呼び出している。30秒経過するか、ロックが開放されたら、自分自身を呼び出している。こうすることで、variable のキャッシュを再構築完了するまでの間に、別のプロセスが処理を行えないようになっている。

今回は、 「あるタイミングで最初に誰か一人が処理をすればいい」 プログラムだったので、lock_wait() 関数を使わなかった。 「ある条件を満たすすべての人(プロセス)が処理をするが、複数同時に起動すると困る」 という場合に使うもの。何かの機会に使ってみたいと思う。

このエントリーをはてなブックマークに追加
Pocket