東京ゲームショーにて「黒ひげキャッチ危機一発」を展示しています!
「刺して」「飛ばして」「キャッチして」!
お宝ゲットに世界進出!?
会場にお越しの際はぜひご体感ください!

簡単そうで意外とトリッキーだったので記事にしてみます。
VBScript は 日付だろうが時刻だろうが、時間に関するデータは、
1日を1とする数値で保持してるらしい。
なのでTimeSerialとかで作った値をCLngとかにかけると
1とか返ってきて意味わかんないことになる。
日時のシリアル作りたい時、これまではYYYYMMDDHHMMSSみたいな
フォーマットしてたけど、これからはこれでOK!
Real World なコードがブームということで、Haskell のデーモン化のサンプル
を書いてみました。Unix系とWindows系対応です。
https://github.com/mitsuji/hdaemon
ビルド方法等については、READMEを参照。
https://github.com/mitsuji/hdaemon/blob/master/README
サンプルデーモンの仕様は下記のとおり。
* 開始時にログファイルを開いて"start!"を出力
* 2秒毎に"go!"を出力
* 終了時に"stop!"を出力してログファイルを閉じる
[Haskell編]
Haskellのコードをデーモン化するにはまず、Haskellコード単体で
"start" "stop" できるようなサービス化が必要になります。
具体的にはghciからこんな感じで使えるようにすると開発もしやすそうですね。
> :load HDaemon.Server
> h <- start "log0.txt"
> stop h
start 内部で作成してサービスのハンドルとして返すMVarに
実行を継続するか否かの状態を入れておき、serve の再帰呼び出し時に確認、
stop から状態を変更させることで制御しています。
状態が Run|Shutdown|Stop の三値になっているのは、
サービスの終了シーケンスを確実に完了させるためです。
呼び出し元のコンテキストが終了しない ghci の様な環境では
Run|Stop の二値でも一見正常動作するのですが、
通常の環境では、終了シーケンスが完了する前に呼び出し元が終了しないように
stop内部で待つ必要があり、「終了操作中」状態として Shutdown が必要になります。
状態の参照はIORefでも持てるのですが、何となく安心なMVarにしてます。
[Unix(Mac OS X and Linux)編]
HDaemon/Server.hs に、下記のコードをリンクします。
posix.c
HDaemon/Daemon.hs
posix.c の main で Haskell のコンテキストを作成し、
HDaemon.Daemon.foreignMain を呼び出しています。
Unix系のOSでプログラムをデーモン化するには、
呼び出し元のプロセスをforkしてデーモン本体となる子プロセスを作成、
子プロセスのプロセスIDを/var/run/xxx.pid に保存、
chdir("/") してから標準入力・標準出力・標準エラーを閉じる
という手順が必要になります。
また、終了シーケンスを呼び出すための TERM のようなシグナルを
受け取れるように、子プロセスの開始時にハンドラを登録しておく必要があります。
デーモンを終了するときには、/var/run/xxx.pid に保存したプロセスID
を使って、どのプロセスにシグナルを送るか判断することになるでしょう。
今回、プロセスの fork については、プロセスIDの保存以外をやってくれる
forkProcess という関数がSystem.Posix.Process に用意されていたのですが
現在のところマルチプロセッサー(コア)に対応していないとのことで、
今回は同様の機能を提供してくれる unistd.h の daemon() を使って
C言語で実装することにしました。
C言語の main のパラメータをそのまま hs_init に渡しているので
+RTS -N のようなパラメータも、指定すればHaskellのコンテキストに渡されるはずです。
シグナルについては System.Posix.Signals に installHandler という、
シグナルハンドラをHaskellで定義できる非常に便利な関数が
用意されていたので、こちらを使うことにしました。
[Windows編]
HDaemon/Server.hs に、下記のコードをリンクします。
win32.c
HDaemon/Foreign.hs
HDaemon/Foreign.hs に、C言語から呼び出せるようにした
HDaemon.Server.start HDaemon.Server.stop のラッパー
(HDaemon.Foreign.foreignStart, HDaemon.Foreign.foreignStop)
を用意し、これを win32.c の Windowsサービスから呼び出しています。
Windows の場合はサービスの仕組みにHaskellが入り込む余地はないようなので
HaskellのサービスのハンドルをC言語のポインタとして参照できるようにして、
一般的なWindowsサービスのサンプルコードを元に作成したコードから呼び出す
ようにしました。
Haskell側でハンドルを Foreign.StablePtr でラップしてやると
C言語側で HsStablePtr として参照を保持することが出来るようです。
hs_init にパラメータを渡すために、Windows側で提供されているスマートな方法を
探したのですが見つからず、main の argv をグローバル変数に保存しておき、
Haslellコンテキスト作成時に参照するというトリッキーなコードになっていますが
一応想定したような動作はしてくれるようです。
※Windows の C言語のコードは無意味に長すぎるので割愛します。
[感想]
Haskell初心者な上に、デーモン化(Unix)もサービス化(Windows)も
前提知識が乏しかったので少し時間はかかったけど意外と簡単にできたと思います。
System.Posix.Signals.installHandler 見つけたときは感動しました。
あとは System.Posix.Process.forkProcess がマルチコア対応したら、
Unix版は Haskellだけで完結できると思います。
Foreign.StablePtr とか使うと何でもできそうな気がしてきますが、
FFIはなるべく使いたくないなー。
あとは、せっかくHaskellやってるのに、命令型っぽいコードばかり書いてる
感じになっちゃってるのが少し悲しいです。。。
自分への要望。
* restart 動作も実装したい。
* Unix版でpidファイルのpathを指定できるようにしたい。
* Windows版で実行中に他のプロセスからログが読み取れないのが気になる。
(ファイルの読み取りモード指定もっと細かくできるのかな?)
この辺りは具体的なサービスを書くときに対応されていくことでしょう。
さーて、ghc はネイティブコードはくし、グリーンスレッド使い放題やし、
Haskellで何つくろうかなー。