Haskellでメール関連のサーバーを書いていて、いろいろ調べました。かなりがっかりな結果ですが、包み隠さず書きます。
forkIO
forkIO でユーザースレッドを作ると、内部では select() を使います。select() はファイル記述子を 1024 までしか扱えないという壁があって、使い物になりません。(せめて poll() を使って欲しいなぁ。)
世の中には行儀が悪いクライアントが多く、コネクションをリセットします。すると、ハンドルが失われ、明示的に hClose できません。ですから、ソケットの開放は、ガベージコレクション任せとなります。結局、1024 個のソケットを簡単に使い切ってしまうということです。
追記:GHC のガベージコレクションは優秀で、ソケットはすぐに回収されることが分かりました。
forkOS
forkOS ではカーネルスレッドを作ります。内部では pthread_create() を使います。スレッドのスタックの大きさと、利用できるメモリーから、作成できるスレッドの数が決まります。さんざん探したのですが、スレッドのスタックの大きさを変更する方法が見つからず、小さい数のスレッドしか作れないので、使い物になりません。
これを超えられても、select() の壁が待っています。
deamonize
System.Posix.Daemonize の deamonize は、forkProcess を2回呼ぶことで、プロセスを端末から切り離します。
forkProcess は、ghc -threaded でリンクされたランタイムの IO スレッドを殺します。ですので、ghc -threaded でコンパイルしたプログラムでは、daemonize を呼んだ後から、IO がおかしくなります。
forkProcess
結局、残された道は、forkProcess を使うことです。
Haskell が得意なスレッド間通信が使えず、かっこ悪いのですが、これなら daemonize とも相性がいいです。
forkProcess の前に、設定ファイルを解析したつもりでも、遅延評価されるかもしれません。よって、設定ファイルの解析は、正格評価にしておく必要があります。
まとめ
現在の GHC では、スレッドを使うと、ちゃんとしたサーバーは書けません。