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
- コードのチェックアウト
- 基本的な挙動
- オプション
- appname
- pidfile
- pidpdir
- options: debug (default: false)
- options: daemon (default: false)
- options: euid (default: Process::euid)
- options: egid (default: Process::egid)
- options: perms (default: 0711)
- 2011/1/31追記:stop/restartオプションの追加
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()の呼び出しの前に、次のようなコードを書いておきます。
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()メソッドが呼ばれるまで何も反応しませんが、ちょっとコードを工夫すれば問題なく安全に停止することができるでしょう。