丁稚な日々

Rubyで遊んだ日々の記録。あくまで著者視点の私的な記録なので、正確さを求めないように。
Rubyと関係ない話題にはその旨注記しているはず。なので、一見関係無いように見える話題もどこかで関係あるのかもしれません。または、注記の書き忘れかもしれません...

[直前] [最新] [直後] [Top]

May.1,2013 (Wed)

Revision: 1.8 (May.01,2013 10:59)

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第5章 ファイルディスクリプタ? そのふざけた幻想をぶっ壊す!

_ 世の中には規格Cというものがあって、ANSI C(C89)とかISO C90とかISO C99とかいうものがそれなのだけど、そこにはファイルディスクリプタなんてものは存在しない。
だからファイルディスクリプタなんてもののことは忘れてしまえ!

_ ...なんて言って済めばいいのだが、Unixさんはこのファイルディスクリプタさんにべったり依存した仕組みで出来上がっている。
そしてRubyさんはそんなUnixさんにべったり依存した仕組みで組み上げられている。
やれやれだぜ。

_ さて、Windowsでは... というか、ここで言及しているmswin版Rubyの開発環境であるところのVisual C++では、規格C(C90)に準拠(*1)してまーす、ということで、これに沿ったライブラリを提供している。
このC90準拠のライブラリであるが、static linkするかdynamic linkするか、マルチスレッド対応するかしないか、でいろいろ名前が変わるのだが、とりあえず我々が使用しているのはdynamic link版(ちなみに強制的にマルチスレッド対応である)のMSVCRTというものである。
このMSVCRTも、Visual C++のバージョンごとに名前を変える、という難儀な決定を20世紀末のMicrosoftさんが下してしまったので我々泣かせなのだが、とりあえず本稿ではこれらのバージョンごとの名前の違いを気にせず、まとめて「MSVCRT」と呼ぶことにする。

_ さて、本来、規格CにはファイルディスクリプタなんてないからMSVCRTもこれをサポートする必要はないわけなのだが、それではUnix環境に存在する膨大なソースコード資産をうまく活用できない。
というわけで、MSVCRTの中で、いちおうファイルディスクリプタをサポートし、これを扱うUnixのシステムコール群のそれなりの割合をライブラリ関数として用意してくれている。
しかし、所詮は「いちおう」レベル。
『なるほどUnixプロセス』にあるようにUnixではファイルディスクリプタは様々なリソースを統一的に扱うための窓口なのだが、MSVCRTではほぼファイルI/Oのみが対象である。
ま、ほとんどのソースコード中でファイルディスクリプタはファイルI/Oを扱うためのみに使用されているという事実があるので、これは仕方あるまい。

_ とりあえず、運よく/etc/passwdなんていうファイルがあなたのWindows環境上に存在するとしたら(たぶんないと思うので適当に読み替えてほしい)、

passwd = File.open('/etc/passwd')
puts passwd.fileno

は、Windowsにおいてもちゃんと3という出力を示すはず(*2)だし、『なるほどUnixプロセス』第5章のその他のスクリプトについても、ファイル名さえ適切に読み替えれば、全く同じ出力をしてくれるはずである。
この辺は、MSVCRTさんがファイルディスクリプタをライブラリレベルでサポートしてくれているから実現されていることなのである。

_ と、ここまでは表面上のお話だが、Windows版Rubyにおいては、ここから実装の暗黒面に落ちていくことになる。
そう、MSVCRTさんはopen(2)close(2)read(2)write(2)あたりはいちおうライブラリ関数として用意してくれてはいる。
しかし、ファイルI/Oしか扱えないのにこんな名前で関数を用意してくれてても、Unix依存べったりのRubyソースコードが満足してくれるはずはないのである。
少なくともsocketくらいは統一的に扱ってくれないとねえ。

_ ところで、Windowsにおいても、Unixのファイルディスクリプタのように、リソースを統一的に扱うための仕組みがちゃんと存在する。
ハンドル、と呼ばれるものである。
Unixのファイルディスクリプタよりちょっと範囲が広くて、I/Oのみならず、プロセスやスレッドとか、mutexやセマフォとか、I/O系デバイスとか、アクセス権限管理用のトークンというものとか、とにかくかなりありとあらゆるものがこのハンドルというものを用いて管理されている。
MSVCRTにおけるファイルディスクリプタというのは、ファイルのハンドルをラップしたものとして実装されている。
そう、ファイル(と一応パイプなどのごく一部のデバイス)だけ、をね。

_ MSVCRTでも、もうちょっと頑張って他のI/O系ハンドルもちゃんとラップしてくれればよかったのだが、してくれてないので、そこはWindows版Rubyが実装で頑張ることになる。
というわけで、例によってwin32/win32.cの中で、これらの関数群はrb_w32_open()などという名前で一生懸命再定義されていたりする。
特にopen系関数については、socketやコンソールなどをちゃんとRubyソースコードが期待するように通常のファイルと統一的にファイルディスクリプタで取り扱うために(他にも理由はあるが)、MSVCRTが提供している素のopen()関数に処理を一切委ねず、一生懸命頑張ってMSVCRT内のファイルディスクリプタテーブルを自前で操作しながらopenを実装していたりする。
これは本当に涙ぐましい努力である。泣ける。
しかもファイルディスクリプタテーブルの構造自体がMSVCRTのバージョンによって変化してたりして、もう、ね...。

_ なお、read/write系とかは、ファイルディスクリプタの話は抜きにしても、他のスレッドから処理を(割と安全に)ぶっころしたりできるようにするために、すげー頑張って丸ごと再実装していたりする。
それだけ努力しても、お前らがWindows XPとかいう時代遅れのOSを手放さないために、無駄に複雑なロジックで再実装してる上に、本来の目的を十分適切には実現できていなかったりもするのである。
将来のWindows版Rubyでは、容赦なくXPサポートを切り捨てて、もっとすっきりかっこいい実装に置き換えたいものである。

_ ちなみに、open系関数を始めとする、ファイル名を引数とする関数群については、ファイル名のエンコーディングで深い悩みが発生するので、win32/win32.cの中ではいわゆるWide Char版とUTF-8版がそれぞれ用意(*3)されていたりする。

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第6章 リソースの制限? まあ、どっかにはあるんだろうけどさ

_ とりあえず、Windowsにおいては、Unixと同一のモデルではリソース制限は存在しない。
なので、Process.getrlimitProcess.setrlimitはWindows上ではNotImplementedError例外を送出する。

_ 例えばNOFILEとかはやろうと思えば別に難しくないんだけど、この辺中途半端に用意しても仕方ないし、そんなに頻繁にこいつらを使うことはないだろうし、というわけで、現時点ではWindows版Rubyでこれらのメソッドの中身であるところのgetrlimit(2)setrlimit(2)を実装する予定はない。

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第7章 環境? あるある!

_ 第6章ではNotImplementedErrorでがっかりしたが、第7章については何の問題もなくWindows版Rubyでもサポートされている内容なので安心である。
強いて言えば、Windowsでは環境変数名の大文字小文字は区別されず、故にWindows版Rubyにおいてもそうなっているので、環境変数名の大文字小文字の差異にのみ依存した環境変数名を使うのはやめてよね、というくらいである。

_ あー、そうそう、世の中にはエンコーディングというものがあってな... とかいうことを思い出してしまった。
環境変数名とか値とかに非ASCII文字を使うなんていう暴虐は皆さん避けるように。

付記

(*1) 規格C(C90)に準拠
ちなみにVisual C++ではC99以降の規格Cに準拠する予定は一切ない、という話があったようなので、皆さんもC99に走らずにC90で生活するように。
つか、今更新規のコードをCで書く必要なんてないっしょ?

(*2) 示すはず
それはいいんだが、最近のRubyをUnix上で動かすと5とか7とかになるという噂である。
何をオープンしてるんだろう?

(*3) Wide Char版とUTF-8版がそれぞれ用意
厳密には、Wide Char版が主実装で、locale(?)版とUTF-8版はそれを利用している。

May.2,2013 (Thu)

Revision: 1.4 (May.02,2013 11:38)

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第8章 プロセスの引数はリッチに解釈

_ Unixでは実際のコマンドライン引数はシェルが解釈した上でRubyに引き渡しているわけだが、WindowsではRubyが自前で解釈を行っていろいろよきにはからってくれる。
例えば、**[]などの便利なワイルドカード拡張が使えたりするので大変便利である。
ただし、リダイレクト関連の記号はシェル(普通はcmd.exe)が解釈するので、そこは要注意。
あと、コマンドライン引数についても、エンコーディングとかの扱いがいろいろややこしいことくらいは留意しておくべきであろう。

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第9章 勝手に改名するなよ!

_ なぜか『なるほどUnixプロセス』では触れられていないのだが、$PROGRAM_NAMEに代入を行うとプロセス名が変更される、という機構は、Unixにおいては概ねsetproctitle(3)を利用して実現されている。
が、こいつはどこにでもあるわけでもないので、missing/setproctitle.cというコードが用意されていて、ない場合は可能であればこれを使って同等の処理を実現するようになっている。

_ なんだけど、それがpsとかの表示に反映されるかどうかとかはまた別問題なわけで、Windowsではもちろんtasklistやタスクマネージャーの表示が変わったりはしないし、Unixと言われてる環境でも変化のない環境はあるんじゃないかなあ。

_ というわけで、あんまりこの機構には期待しない方がいいと思う。

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第10章 終了コードの闇

_ とりあえず、『なるほどUnixプロセス』の第10章で触れられている話題については、特に言うことはない。Windowsでも同じである。
それはいいのだが、実のところ、終了コードについては、送る方よりは受ける方にもっと難しい話題がいっぱいあるはずである。
例えば、プロセスがSEGVで死んだら終了コードはどうなるの? とか。
第14章にちらっとProcess::Statusオブジェクトの話題はあるようだが...。
ここで先に言っておくと、WindowsにおいてはProcess::Statusのインスタンスメソッドcoredump?exited?signaled?success?の戻り値は残念ながら意味を持たない。
なんかいい方法はないものかねえ(*1)

_ なお、Windowsではシステムとしてプロセスの終了コードは32bitの範囲で値を指定することができる。
が、他プラットフォーム(そう、Unixのことだよ!)との互換性の観点からも、8bitの範囲の値しか使わないのが常識ではある。
というわけで、Windows版Rubyでもexitの引数は8bitの範囲の値しか意味を持たない。
ただ、先日、RubyのBTSにそのことについてのバグレポートが送られてきたので、ひょっとしたら将来何らかの変化があるかもしれない。

付記

(*1) いい方法はないものかねえ
追記: 実際はWindowsでも死因自体は取れる。
ただそれをUnix風の死因にマップするのがめんどーで手を出していなかったのだが、死因が取れてるならなんとかしろ、というお叱りをこさきさんから頂いたので、trunkではr40549で対応してみた。

May.7,2013 (Tue)

Revision: 1.3 (May.02,2013 14:28)

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第11章 forkとか死ねばいいのに

_ 最初に明言しておく。好んでfork(2)を使う奴とは友達にはなれない。

_ fork(2)がどんなものなのかはそれこそ『なるほどUnixプロセス』を読んでもらうとして、ぶっちゃけお前らプロセスのコピーなんかほしいの? というのが正直なところである。
いや、もちろん、便利な場面もあるだろう。
しかし、処理を分散したいならわざわざ別プロセスを生成しなくてもスレッドを使えばいいではないか。
fork(2)に固執するのは単なる老害かUnix厨である。消えてしまえ!

_ で、えーと、結論としては、Windows版RubyではKernel#forkNotImplementedError例外を投げる。
スレッドかKernel#spawnとかを使ってくれたまえ。

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第12章 全てのプロセスは孤児である

_ この前書いたが、基本的にはWindowsにおいては全てのプロセスは生まれながらに孤児である、とみなしてよい。
あれ、これくらいしかこの章について言うことないな。
えーと、『なるほどUnixプロセス』の本章のサンプルコードは、Windowsではこんな感じで(*1)どうだろうか?

r, w = IO.pipe
w.write <<END_OF_SCRIPT
  5.times do
    sleep 1
    puts "I'm an orphan!"
  end
END_OF_SCRIPT
w.close
spawn("ruby", in: r)

abort "Parent process died..."

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第14章 ふつう待つよね

_ 『なるほどUnixプロセス』14.1のサンプルコードを、先ほどの第12章の例をベースに書き換えるとこうなる。

r, w = IO.pipe
w.write <<END_OF_SCRIPT
  5.times do
    sleep 1
    puts "I'm an orphan!"
  end
END_OF_SCRIPT
w.close
spawn("ruby", in: r)

Process.wait
abort "Parent process died..."

うん、ちゃんとWindowsでもUnixでも子プロセスの終了を待つようになったね。

_ 14.2のサンプルコードも同じようにこう変えよう

3.times do
  r, w = IO.pipe
  w.write <<-END_OF_SCRIPT
    sleep rand(5)
  END_OF_SCRIPT
  w.close
  spawn("ruby", in: r)
end

3.times do
  puts Process.wait
end

_ 14.3のサンプルコードはこうだ!

5.times do
  r, w = IO.pipe
  w.write <<-END_OF_SCRIPT
    if rand(5).even?
      exit 111
    else
      exit 112
    end
  END_OF_SCRIPT
  w.close
  spawn("ruby", in: r)
end

5.times do
  pid, status = Process.wait2
  if status.exitstatus == 111
    puts "#{pid} encountered an even number!"
  else
    puts "#{pid} encountered an odd number!"
  end
end

_ そろそろ疲れてきた。14.4。

r, w = IO.pipe
w.write <<END_OF_SCRIPT
  exit 77
END_OF_SCRIPT
w.close
favorite = spawn("ruby", in: r)

r, w = IO.pipe
w.write <<END_OF_SCRIPT
  abort "I want to be waited on!"
END_OF_SCRIPT
w.close
middle_child = spawn("ruby", in: r)

pid, status = Process.waitpid2 favorite
puts status.exitstatus

_ そして14.5。

2.times do
  r, w = IO.pipe
  w.write <<-END_OF_SCRIPT
    abort "Finished!"
  END_OF_SCRIPT
  w.close
  spawn("ruby", in: r)
end

puts Process.wait
sleep 5

puts Process.wait

しかしこれ(も、オリジナルのコードも)、1つ目のProcess.waitの前に子供二人とも死ぬよなあ。
例としてはどうなんじゃろ?

_ と、いうわけで、とりあえずforkを追放しても例が書けることを証明してみたが、まあ確かに煩雑ではある。
しかし、毎回同じようなことやってるだけなので、メソッドに切り出せばもうちょっと綺麗には見えるだろう。

_ さて、そんなわけで、Windowsでもちゃんと子プロセスの終了を待ってその終了コードを取得できることを示したわけだが... ちょっと待った!
Windowsでは表向き全てのプロセスが孤児だと言ったばかりではないか。
wapitpid系のようにpidを指定して待つものはともかく、wait系のように漠然と子プロセスの終了を待つことができる、というのはどういうことなのか?
例の隠し戸籍(ppid)を利用してどうにかしているのか?

_ うん、確かに、全プロセスを列挙してppidを使って自分の子供を探し出し、そいつが終了してるかどうかを確認する、という手もなくはない。
しかしそんなこと毎回やってたらさすがに煩雑に過ぎる。
Windows版Rubyでは、この問題を、「自分で生成した子供くらいは自分で全部覚えておこう」という、単純な手法で解決している。
Windows/MSVCRTにはそもそもwaitpid(2)はないので、win32/win32.cwaitpid()が直接この名前で実装されている。
いきなりこの関数だけ見るとわかりにくいが、つまりChildRecordという構造体配列に子プロセス情報が入っていて、poll_child_status()という内部関数でその子プロセスの終了状況をチェックしている。
なお最終的な終了状況のチェックには、WindowsのAPIであるGetExitCodeProcess()を使用している。

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第15章 ゾンビというか単なる死体だと思うが

_ 以前にもチラッと書いたが、Windowsではハンドルでプロセスを管理している。pidなんてものはぶっちゃけ飾りである。
というわけで、今言ったGetExitCodeProcess()だって引数に取るのはpidでなくプロセスハンドルである。
従って、ChildProcess構造体(これ変数と名前が同じなので注意)では、pidとセットでプロセスハンドルも保持している。
このプロセスハンドルは(そしてpidも)どこから湧いてきたのかというと、子プロセス生成時、具体的にはCreateProcess()APIを呼び出した時に入手した情報である。

_ さて、pidは単なる数値だが、プロセスハンドルは立派なOSリソースである。
用が済んだらもちろんちゃんと後始末しなければならない。
Windowsでは全てのプロセスは孤児なので、親の死後に誰にもみとってもらえなくてもゾンビ化しちゃうわけではないのだが、このプロセスハンドルを他のプロセスが握ったままだと、Unixでいうゾンビに近いイメージでOSリソースが無駄に残ることになる。
見捨てられたらゾンビ化するというより、見捨てるのを忘れたらゾンビ化するという辺りが、この2つのプラットフォーム間の面白い違いではある。

_ 先ほど書いたWindows版Rubyのwaitpid()実装では、当然のことではあるが、子プロセスが終了していたらその時点で自分が持ってるプロセスハンドルを後始末(CloseHandle()API呼び出し)するので、子プロセスを生成した後はちゃんとProcess.waitなりなんなりを呼べば問題ない。
もちろん、『なるほどUnixプロセス』第15章に登場するProcess.detachはWindows版RubyにおいてもUnix版と同様にちゃんと内部でProcess.waitしてくれているので、こちらを使うのもよいだろう。

付記

(*1) Windowsではこんな感じで
というか、これはUnixでも同じように動く。ポータビリティ万歳!

May.8,2013 (Wed)

Revision: 1.1 (May.08,2013 17:33)

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第16章 非同期処理こわい

_ 『なるほどUnixプロセス』で一番長いのが、シグナルを扱う第16章である。
というか、第15章までは長い章で5ページだったのに、第16章はいきなり12ページ、そして以降は7〜8ページの重めの章が続く、という構成なのである。
前の方の章は幾つかは統合した方がいいと思うし、逆にこの辺の章は分割も考えた方がいいんじゃないかなあ。

_ まあ、そんなことはどうでもいい。
シグナルである。
意外なことに、シグナル自体は規格Cにも含まれている。
ただし、C99時点で定義されているシグナルはわずか6つ(*1)であり、それ以外のシグナルは処理系依存である。
プログラムはsignal(3)関数によってシグナル発生時の処理を定義でき、それはシグナル発生時に現在実行中の処理と非同期に起動されるが、できることには多少の制限がある。
また、発生したシグナルによっては、その処理の終了後に何が起こるかは定かではない。

_ というわけで、シグナルというのは使用に際しては非常に不安定なものなのである。
やむをえない場合を除いては使わない方がいいのではないだろうか。

_ ... この章はこれで終わりたいんだけど、だめ?
とりあえず、SIGCHLDとかないしー。
SIGINTとか投げてもうまく行くとも限らないしー。
そもそも他プロセスにシグナル投げられるとかそんなの甘えだしー。
あ、他プロセスにシグナル投げられるかどうか、ってのはちょっと補足が必要か。
Unixの世界だとシグナルってOSの機能(っていうのかな)の一部だけど、他の世界では単に規格Cに定められた非同期例外処理である。
そして規格Cでは別のプロセスにシグナルを投げることができるなんて一言も言ってないし、僕らのMSVCRTでももちろん自プロセス内にしかシグナルは投げられない(raise(3)関数を参照せよ)。

_ そんなわけで他プロセスにシグナルを投げることができるなんてまたしても幻想に過ぎないのだが、Windows版Rubyでは例によってちょっと頑張って幾つかのシグナル、具体的にはSIGINTSIGKILLについてはwin32/win32.c内のkill()関数で他プロセスへの送出をエミュレートしている。
つっても、SIGKILLはプロセスをぶっ殺してるだけだし、SIGINTは相手に届くかどうかは相手次第であるが。

_ なお、シグナルを受け取る方、つまりKernel#trapとかの中身についてだが、これは先に書いた規格Cのsignal(3)によって実現されている。
この辺はUnix専用コードほどの複雑さはないが、どうせMSVCRTの中で閉じてる処理を行うので、そういう意味では安定していると言えなくもない、かも。

付記

(*1) 定義されているシグナルはわずか6つ
SIGABRTSIGFPESIGILLSIGINTSIGSEGVSIGTERM
ただし、処理系はこれらのシグナルを定義する必要はあるが、これらのシグナルを生成する必要は必ずしもない。

May.9,2013 (Thu)

Revision: 1.1 (May.09,2013 22:46)

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第17章 プロセス間通信もまあいろいろあるけどね

_ なんか内容的にだんだん記事を書きにくくなってきた。

_ 『なるほどUnixプロセス』17.3のコード例は、Kernel#forkが使えないプラットフォームではうまく別コードで例を示すのが難しいが、要するに既に示したコードでプロセス間通信してるわけで、まあ無理に考える必要もないだろう。

_ 17.4のコード例の1つめについては、さすがにUnix以外にUnixソケットは存在しないので、どこにでも存在するTCPソケットを使って頂きたい。
具体的にはSocket.pair(:INET, :SOCK_STREAM, 0)である。
コード例の2つ目は例によってうまく書くのは難しいが、これって要するにパイプの例をソケット使ってるだけなので、ぶっちゃけあまり意義のある例でもない気はする。

_ さて、Windowsにはpipe(2)はないが、MSVCRTはちゃんとpipe()関数を用意してくれている。
とはいうものの、それを使うと以前書いたread/write系処理の比較的安全な中断が実行できないファイルディスクリプタが得られてしまうので、例のごとくwin32/win32.cの中でrb_w32_pipe()関数として自前で再実装したものを使用している(ただしXP以前のWindowsで実行する場合はMSVCRTのpipe()関数に処理を丸投げしている)。

_ また、残念ながらWindowsはsocketpair(2)に相当する関数を特に用意してくれていないので、こちらもwin32/win32.cの中でrb_w32_socketpair()関数として自前で実装してある。

May.10,2013 (Fri)

Revision: 1.1 (May.10,2013 18:41)

不定期連載「『なるほどUnixプロセス』で学ぶWindows版Rubyの基礎」〜第18章 デーモンの定義

_ デーモンプロセスの定義はなんだろう?
おそらく、

  1. 孤児である
  2. 制御端末から切り離されている

といったあたりだろうか?

_ Windowsだと最初から孤児なので条件1は満たしている。
問題は条件2だが... まあ、例を作りながら考えていこう。

r, w = IO.pipe
w.write <<-'END_OF_SCRIPT'
  puts "child: #{Process.pid}"
  sleep
END_OF_SCRIPT
w.close
spawn("ruby", in: r)

puts "parent: #{Process.pid}"
sleep

このコードを実行すると、親プロセスと子プロセス、二つのrubyプロセスが起動され、それぞれのpidが端末(普通はコマンドプロンプト)に表示される。
別の端末でtasklist /FI "IMAGENAME eq ruby.exe"を実行すると、確かにそのpidでRubyが実行されていることがわかるだろう。
もちろん、子プロセスが端末に自分のpidを表示できたということは、子プロセスは制御端末から切り離されていないということである。

_ では、以下の例ではどうか。

r, w = IO.pipe
w.write <<-'END_OF_SCRIPT'
  STDIN.reopen("NUL")
  STDOUT.reopen("NUL")
  STDERR.reopen("NUL")
  puts "child: #{Process.pid}"
  sleep
END_OF_SCRIPT
w.close
spawn("ruby", in: r)

puts "parent: #{Process.pid}"
sleep

子プロセス側で標準入出力をNULで置き換えてしまったので、当然ながら子プロセスはもはや端末から入出力を受け取ることはできない。
念のため、別の端末でtasklist /FI "IMAGENAME eq ruby.exe"を実行して、ちゃんとRubyが2つ実行されていることを確認しておこう。

_ これで十分だろうか?
もちろんそんなわけはない。
Ctrl+Cを押すと、とりあえず親側のプロセスが死んだことはわかると思う。
が、改めてtasklist /FI "IMAGENAME eq ruby.exe"を実行すると、子プロセス側も死んでしまっていることがわかると思う。
つまり、子プロセスは依然として制御端末から切り離されていなかったので、Ctrl+Cを受け取ってしまったわけである。

_ と、いうことは、Windowsではデーモン化(に相当すること)はできないのだろうか?
実は、Rubyにはその機能がないのだが、Win32 APIにはFreeConsole()というAPIがあって、これを呼び出せば、おおよそ相当する処理を行ったことになる。
試してみよう。

r, w = IO.pipe
w.write <<-'END_OF_SCRIPT'
  require "Win32API"
  Win32API.new("kernel32", "FreeConsole", nil, 'L').call
  STDIN.reopen("NUL")
  STDOUT.reopen("NUL")
  STDERR.reopen("NUL")
  puts "child: #{Process.pid}"
  sleep
END_OF_SCRIPT
w.close
spawn("ruby", in: r)

puts "parent: #{Process.pid}"
sleep

# Win32APIはもう終わってるライブラリなのだが、手抜きのために使用した。
このコードを実行してCtrl+Cを押すと、先ほどと同じように親プロセスは死亡するが、tasklist /FI "IMAGENAME eq ruby.exe"を実行すると先ほどとは違ってちゃんと子プロセスの方は生き延びていることがわかる。
確認したらもう用はないのでtaskkill /F /PID そのプロセスのpidで殺しておくように。

_ ところで、WindowsというのはGUIをデフォルトで備えたOSなわけだが、普通のGUIアプリケーションというのはそもそも最初から制御端末から切り離されてるのではないだろうか。
もちろん代わりにグラフィカルな画面に結び付けられていると言えなくもないが、ウィンドウを表示しなければそれってばデーモンぽくないだろうか?

_ と、いうわけで、実はWindows版Rubyには「制御端末(コマンドプロンプト)に結びついておらずだからといってウィンドウを表示したりもしない」バージョンのRubyがちゃんと添付されている。
rubyw.exeがそれである。
本稿の一番最初のサンプルコードを1行だけ書き換えよう。

r, w = IO.pipe
w.write <<-'END_OF_SCRIPT'
  puts "child: #{Process.pid}"
  sleep
END_OF_SCRIPT
w.close
spawn("rubyw", in: r)  ### HERE!!! ###

puts "parent: #{Process.pid}"
sleep

tasklist /FI "IMAGENAME eq ruby*"を実行すると、親プロセスであるruby.exeと子プロセスであるrubyw.exeが実行されていることがわかる。
Ctrl+Cを押すと当然今まで同様親プロセス側は死ぬが、子プロセスのrubyw.exeは平然と動き続けている。
なんだ、最初からrubyw.exeで実行すればよかったんじゃん!

_ というわけで、Windowsにはsetsid(2)setpgrp(2)もないけれど、似たようなことができないわけじゃないし、むしろ最初からデーモンぽく動作する専用のRubyコマンドが用意されていたのである。


被捕捉アンテナ類
[Ant] [Antenna-Julia] [Rabbit's Antenna] [Ruby hotlinks]