2011/01/29

重複起動を防止するRuby用のdaemonクラスライブラリ

SourceForge.jpにできたPersonalForgeのGitリポジトリ機能を使ってみました。

登録したのはCouchDB周りでDBのメンテナンス用Daemonを作るために作成したRuby用Daemonライブラリyadaemon.rbとサンプルコードです。

有名なdaemonライブラリは便利そうにみえますが、今回は使わない機能が豊富で、欲しかった機能は重複起動を防ぐ機能だったので自分で作成する事にしました。

今回はRuby 1.9.2用に作成しました。

雛型コード

#!/usr/local/bin/ruby
#-*- coding: utf-8 -*-
require 'yadaemon'
opts = { "daemon"=>true,"euid"=>1000 }
daemon = YaDaemon.new("testapp","test.pid","/tmp",opts)
daemon.run do |pid|
  i = 0
  while daemon.running
    open("/tmp/testapp/test.log","w") do |f|
      f.write(format("updated: %d\n", i))
      f.flush
    end
    i += 1
    sleep 5
  end
end

PersonalForge

Gitリポジトリ

よくあるGitリポジトリのビューがPersonalForge Summaryページから提供されています。

コードのチェックアウト

gitを使ってチェックアウトする事ができます。

$ git clone git://git.pf.sourceforge.jp/gitroot/y/ya/yasundial/MyDaemonWrapper4Ruby.git

基本的な挙動

よくあるdaemonと同じように、pidファイルを使います。

runメソッドに指定したブロックを実効する前に、いくつかのチェック作業を行ないます。

  • シナリオ# - メインラインシナリオ
  • A1 - ユーザがプロセスを起動する
  • A2 - 既に実行しているプロセスがないかpidファイルの存在をチェックする
  • A3a - pidファイルが存在する場合、中に記述されている番号のプロセスが存在するか確認する
  • A3b - pidファイルが存在しない場合、pidファイルが作成できる事を確認する
  • A4 - piddirが書き込み可能か確認する
  • A5 - forkする場合は、forkする
  • A6 - 既存pidファイルにpidを保存する
  • A7 - rootで起動して、かつ、指定がある場合、eUID, eGIDにswitchする
  • A8 - runメソッドがyieldを実行し、ユーザが指定したジョブを実行する
  • A9 - ユーザが指定したジョブを実行した後は特に何もせず後続の処理を続ける

オプション

インスタンス化の手順

daemon = YaDaemon.new(appname, pidfile, pidpdir, options)

YaDaemonクラスのインスタンスを作成する際に指定できるパラメータは次の通りです。

  • appname: appname used for sub-directory name of the pidpdir.
  • pidfile: pid filename
  • pidpdir: pid parent dir
  • options: Hash object
    • debug => true/false
    • daemon => true/false
    • euid => number
    • egid => number
    • perms => number

意味と使い方を順番に解説していきます。

appname

パスを含まないディレクトリ名にとることができる文字列を記述します。

名前はアプリケーションの名前の意味ですが、実際には"pidpdir"直下にサブディレクトリを作るためのディレクトリ名として使われます。

euidが指定されている場合には、所有者がeuidで指定したユーザになります。

pidfile

パスを含まないファイル名を記述します。

指定したファイルにPID番号が書き出されます。

ファイルオーナはプログラムを実行したユーザのUIDになります。

pidpdir

ディレクトリへの絶対パスを記述します。

pidpdir自体のowner/groupなどのパーミッションは一切変更されません。

ディレクトリが存在していれば、このディレクトリappnameのディレクトリが作成されます。

options: debug (default: false)

オプションが指定されている場合にはデバッグメッセージがpidfileと同じディレクトリ(pidpdir/appname/)に"debug.log"の名前でログファイルが出力されます。

options: daemon (default: false)

trueの場合には、プロセスをforkしてstderr/stdout/stdinを切り離します。

d.j.b.のdaemontoolsと一緒に使う事を想定しているため、標準ではfalseに設定されています。

options: euid (default: Process::euid)

実効UIDを整数値で指定。文字列でグループ名を指定する事はできません。

プロセス特定のユーザで実効する場合には、ファイルの権限変更などに供えて、予防的にこのオプションを指定する事をお勧めします。

過ってrootユーザで実効した場合でも、指定したeuidに遷移した後にrunメソッドが呼ばれます。

options: egid (default: Process::egid)

実効GIDを整数値で指定。文字列でグループ名を指定する事はできません。

options: perms (default: 0711)

整数値で指定する必要がありますが、0711は711とは異なります。

0711,02711のような8進数をベースに指定します。

2011/1/31追記:stop/restartオプションの追加

pidファイルの中にPIDを埋め込んでいるので、これを利用してstop()force_stop()の2つのメソッドを持っています。

使い方の例はsf.jpのGitリポジトリの中に入れてありますが、簡単にサンプルコードだけを載せておきます。

トップにあるコードで、daemon.run()の呼び出しの前に、次のようなコードを書いておきます。

stop()メソッドを使った例

if ARGV[0] == "restart"
  begin
    daemon.stop
    while daemon.check_proc
      sleep 1
    end
    puts "running process was terminated."
  rescue
    puts $!
    puts "failed to restart process."
  end
elsif ARGV[0] == "stop"
  begin
    daemon.stop
    while daemon.check_proc
      sleep 1
    end
    puts "running process was terminated."
  rescue
    puts $!
  end
  exit
end

これを呼ぶと @piddirの直下に"stop.txt"ファイルが作成され、whileの条件式にしているdaemon.running()がfalseを返すようになります。

長期間sleepするような作りになっていると、daemon.running()メソッドが呼ばれるまで何も反応しませんが、ちょっとコードを工夫すれば問題なく安全に停止することができるでしょう。

0 件のコメント: