観察日記 2011-05-18

autoload/require と thread unsafe

なひさんから1.8のautoloadがthread unsafeなのがどうにかならんかといま口頭で言われているが
unsafeだっけ?

今気づいたのだが
rb_mutex_lock()の remove_signal_thread_list() って何の意味もなくないか? add が ubf_selectからしか呼ばれなくて、いまはrb_mutex_lockはrbf_select使わないんだから
複数のスレッドが同時に定数触るとどれか一個がrequireするけどその他はNameErrorになるらしいよ、なひさん曰く。
同時に、というのがなかなか難しそうだ。
require中も普通にスレッドは切り替わるということか。そうだったっけ?
reuireしたライブラリが中でThread.startしたりするしね。
まあでもrequireの中がスレッド切り替わるのは割としょうがないとして
なひさんが気になっているのはrequireの前の
autoloadの途中でスレッドが切り替わることがあるらしいよ
俺が見た分けではないが。
ふむ
まずautoload対象テーブルから該当定数を削除して、
そんでもってrequireを実行する、
のだけど、requireというのはつまりファイル読み込み+evalのようなもので、
ファイル読み込みはrubyのI/Oを使うのでスレッドスケジューリングが行われる。
... という結論に達しました。
なので、指摘された事象は確かに発生しうる。
読み込み中マークを付けておいて、それが付いてたら終わるまで待ってそのまま帰るとかいう仕様にする必要がある?
わからん
evalのようなもの実行中は当然スレッドが切り替わるから、ファイル読み込み部分の扱いだけ頑張ってもあまり意味ないような気がする。
あるのかな?
まず定数を削除するのではなくて、そこで定数を削除せずにそのままにしとけばいいのではないかと
なひさんはいっている
すると複数のスレッドで同時にrequireが起き... いやどうなんだろう。
先にloaded_featureに登録されるのかな。
requireが複数のスレッドで同時に走るとどうなるかはまた別の問題じゃね
なのかな。
requireが終わるまで待つのは筋が悪いねえ、終わんないかもしんないしねえ
それはそうですよねえ。
require終わるまで待たないと先にすすめないじゃん
明示的にrequireする時は終わんないと先に進めないけど
autoloadの時はようは定数だけありゃいいので
べつに待たないと進めないわけじゃないろう
まあじゃあどこまでいったらOKかってのはちょっとすぐには答えは出てこないけど。
スレッド1でautoload走行中にスレッド2がその定数にアクセスした場合、スレッド1のautoloadが終わるまでスレッド2がブロックする
というのが自然な気がする、のですが...
なんか無駄に難しそうな
まあそれが説明は楽だけど
スレッド1でautoloadしてるときにスレッド2が定数触ったらブロックするけど、スレッド1のautoloadの中のrequireの中でThread.startしたときは
そのThread.startが定数触るのは許可しないと無限に終わらないので
むずそうですね
ううむ
module文とかclass文ってどの瞬間に定数が生まれるんだべ
class文の中でdef self.ほげとかできるってことは、その瞬間には既にオブジェクトはある。
そこでself.じゃなくて定数.にできたかなあ
できた、ような...
できるなあ。
できないと class Foo; class Foo::Bar; end; end とか書けないことに
ということはmodule Foo <-この瞬間には生えてそう
うん、実装も、空のclassオブジェクト(moduleならそっち)を生成して、定数定義して、それから中身を実行してる。
さて。
該当の定数が定義される瞬間まで、アクセス期待の皆さんはブロックされる
というのがそうすると妥当なのかなー、とか思うんだけど、
定義されないままrequireが終わったらどうなるんだろうはっはっは
めんどくせえええ
まあ終わったならそれはそれで?
とりあえず、理想の仕様はそんな感じですよね。
ブロックするわけじゃないしな。
今だとautoloadの中で定数定義がないとそもそもどうなるんだろう
終わったところで改めてアクセスされて、普通に見つからないよエラー
なひさんが言ってる現象が自分とこでも起きる感じ。
定数を使うというのは考えないのか
akr_: とは?
クラスオブジェクトが得られたって、new できないとかなんのため
そこはまた次の問題という理解。
require 終了まで待つ以外に選択肢があるとは思えない
require中に生成されたスレッドがその定数をアクセスした場合は?
外と中を別々に処理せよ、と言われてしまうとまあそうなのかもしれない。
それはautoload限らなくてrequire全般のどうあるべきかって問題かなあ。> newできない定数が見えてなにがうれしいのか
さっき、微妙にごまかしたんだけど、
class文の実行は、クラスオブジェクトができて、定数定義されて、継承が行われて、
で、それからclass文の中のブロックの実行が始まるんだけど、まあ今でも普通にその途中にスレッド切り替え起きますよね。
スレッドを作ったなら定数アクセスで待ってもいいような気がするな
そろそろ1.9の実装に到着した?
しないしする気がしない。
1.9ではその問題はパーフェクトに解決済みとか言い出す気かこのパチモンめ
class文中のスレッド切り替えについて考えたらなんかrubyキモイなーと思っていろいろ考える気力を失った。
1.9ではautoloadするときに定数は削除しない
で、どうなるの?
autoload中の場合には黙って上書きされる
定義するとき
他のスレッドでアクセスしたときは普通のrequireと同じでMutexで待つ
みんながautoloadして、ブロックしまくる、ということ?
そうなる
ふむ。
まあそれだとautoload固有の問題はなくなって、requireの問題は残ってるけど、まあそれはそれと思えば、結構妥当だねえ
1.9が1.8みたいにautoloadテーブル持ってるのかどうか知らんけど(持ってるんだよね?)
いつ消すの?
まあまてよ、えーと
いいのか。
いやそこはいいところをついている気がする
そもそも、実は消さなくても本当は問題が起きないというか
require が終わったときに消すのならいいが、定数を上書きするときにけすとよろしくない
requireが終わってから消せば別に誰も困らないじゃないか。
ですね。
終わったときにautoloadのままだったら例外
きゅるきゅると戻ると、1.8はなんであのタイミングで消すんだよ!
バグってたからだよ
上書きするときに消すとまずいってのは?
1.8の挙動はバグってことにすると、なひさんとJRubyは嬉しいらしい。
上書きした後、他のスレッドが定数を参照すると、初期化が終わっていない値が取り出される
うい。
たとえば、initialize がまだ定義されていないクラスオブジェクトとか
なので、定義した瞬間、というのは、あまりよくないですよねー、ということですね。
autoloadに限った話じゃないけど、わざわざ危険が危なくなるような仕組みにする必要はない、と。
でもどうなんだろうな。
class文に入った瞬間そのクラスができちゃうのはrubyの(いいか悪いかはともかく)仕様なんだから、autoloadだけ安全側に倒すというのはどうなんだろう?
ライブラリ中のクラスを使うときは、そのライブラリを require 仕手から使うんだから、普通は安全では
autoloadの最中は同じ定数にアクセスしたスレッドはブロックするから
してから
途中では見えないはず
同じ定数というか
同じファイルをautoloadする定数
requireの普通の使い方とautoloadの対比ではまあそうか。
autoload :foo, "foo" は
requireで定義されるクラスをrequireが終わる前に別スレッドで早速アクセスしてやるぜ、なんてゆーこまったちゃんは普通に死ねよ、というのもそれはそのとおりだな。
def Foo; require "foo"; Foo; end と同じような感じ
使う前に require しろよばか、と表現したい
結論としては
お、結論が。
それは思う。>requireせよ
1.8のautoloadはバグってる。
スレッド使う困ったちゃんはしねよ
で、1.8は同時同ファイルrequireは手当て入ってる?
それはやってなかったかな
foo.rb が C を定義するとして、Thread.new { require "foo" }; Thread.new { sleep 1; C.new } とかありえない
それは正しい仕様があるとも言っている?<バグってる。
(1) あるスレッドで始まったautoloadが完了するまで、他のスレッドにもautoloadを実行する権利がある
(2) 同一ファイルへのrequireを複数スレッドで同時実行することはできず、最初に始めた子が終わるまで他のスレッドは待たされる(そしてloadedと見なされて完了する)
ということでOKでしょう、という結論。
なんとなく良さそうにみえるけど本当に大丈夫かな。
追補: require の cycle はやめろ
http://www.atdot.net/sp/view/dgccll
なにこの例外?
autoloadで互いに呼び合うとdeadlockになったり
8てなに
autoloadでお互いに呼び合うとdeadlockになるのって当たり前じゃね?
なんかみえたらいけないオブジェクトがみえてるのではないかという気になる
requireだとならなくない?ってのとautoloadって互いに呼び合う状態になってるかどうかライブラリの外に依存してる可能性が。
requireでもなるんじゃね?
> (1) あるスレッドで始まったautoloadが完了するまで、他のスレッドにもautoloadを実行する権利がある
> (2) 同一ファイルへのrequireを複数スレッドで同時実行することはできず、最初に始めた子が終わるまで他のスレッドは待たされる(そしてloadedと見なされて完了する)
という暫定結論
あれ、requireだと完了するまでrequire済みマークつかない?
初期化してないオブジェクトが見えた http://www.atdot.net/sp/view/klccll
それがまさに条件(2)の話だと思う。
さすがだ
で、きみたちすぐに怖いバグ見つけるね!
これ要するに今言った(1)(2)の組み合わせが成立してないってことだよね。
大事なところを聞き逃した気がするけど、まさにJRubyのその辺を今日直したよ。
-jaだつってんだろ!(いえなんでもいいです)
で、n0kadaは居る?
JRubyはどういう仕様にしたの?
$LOADED_FEATURESのcase insensitive patch、revertしようよー。 http://redmine.ruby-lang.org/issues/show/4255 これ。
1.9.3ni
sleepしたら明示的にGVLを離すんじゃね?
1.9.3に入っちゃう前に。要るのこれ?
まずいんだっけ?
n0kada: 必要がない。というのが私の理解ですが、まちがってるかも
GVL は関係ない
Mutexだってなかださん言ったじゃん
NaHi_: 必要があるような気がするけど確信はない。
require って呼び出した直後に $" にはいると思ってたけど、require し終わったあとなんだ
requireするfile毎にreentrant lockがあって、lockして読み終わったあとに$"に入れる (JRuby)
sleep が (スレッドスイッチが) C の定義より前なら問題は起きない http://www.atdot.net/sp/view/6tccll
C を autoload から普通の定数に変えるタイミングの問題
lock済みthreadがlockとりに飛び込んできたらcircular require。
そうか
requireじゃなくてautoloadでしたか。。。
autoloadじゃなくなるからか
(1)が成立していなかったわけか。
autoload_nodeから削除するのをautoload終了まで遅らせるか
1.8でもload_lockってのがあってロックしてんな。
気持ちよく納得した。
1.9は正しくて、1.8の話をしてるんだよね?
正しい気がする仕様を考えて、それは1.9と同じになるはずだと思ったけど、1.9の今の実装も微妙にその仕様と違ってた
ということが発覚したところです。
おお。
ちなみにJRubyがその部分の1.8/1.9 compatをしているところはここ: https://github.com/jruby/jruby/blob/master/src/org/jruby/RubyModule.java#L2869
1.8なら定数削除してからautoloadに飛び込む。1.9ならそのまま。なので1.8だとエラー。
Javaなんか読めるかッ!
autoloadでrequireしてる中では普通に扱って、そのほかのスレッドではautoload中として扱うって綺麗に実装できるんだろか
うう、いい時間なので、LOADED_FEATURESの話はまた来ます。マウント先がたまたまcase sensitiveだったら、ビルド環境がたまたまinsensitiveだったら挙動が変わるとか、まっとうとは思えん。
それはねー、LOADED_FEATURESに限った話じゃないよ。
綺麗に実装というか、余り何も考えなくていい。
autoload のデータにスレッドを記録しておくんじゃないの
require って呼び出した直後に $" にはいると思ってたけど、require し終わったあとなんだ
あとです。
あ、ごめん
二度発言した
rb_const_setではload中ならautoload_nodeを削除
lock -> load -> 入れる -> unlock
その中でThread.startしたらとか
autoloadが終わったらautoload_nodeを削除
そもそもrb_const_getは定数が普通に定義されていたらautoloadしよっかなーとか考えない
逆だ、load中でなければ削除して上書き
ので、autoloadテーブル(1.8の場合)が残ってようがなんだろうが問題ないはずなんだ。
requireが完了したらautoloadテーブルから削除しましょうね、というのはいいとして、
完了してから削除するまでの間に待たされてた他のスレッドが動き出したりはしないのかしら。
特にスレッドを切り替えそうなコードはないから大丈夫なのかな。
rb_const_get_0内でrb_autoload_load->st_lookupなので
大丈夫ではないかと
先生、逆ではないですか。
st_loockup -> rb_autoload_load なので大丈夫ではないかと。
と、ずっと1.8を見ながら話してるんだけど、1.9で逆だったりして。
大丈夫逆じゃない。
で、1.9の場合、require完了からautoloadテーブル削除の間にスレッドが切り替わる可能性は普通にあるよね?
べつに問題ないような
autoload による require がすぐに終わるだけでは
そか。
じゃあオッケー
でもなんか引っかかる。
えーと、requireでその定数が定義されない場合、またautoload処理が走る(ただしrequireはすぐ終わる)
ということがタイミング次第で起きる気がするけど別に問題ないから問題ないか?
autoload_delete()がいったんまずその定数を削除するのは何故だろうか。
少なくとも1.9ではそれをやめないとタイミング次第でまずそう。
普通のrequireと環境をそろえるためかなぁ なんとなく。
更に削除せずに内部でFoo=って書いたら二重定義にならない?
でもautoloadが実行される前提条件自体が「その定数がまだ定義されていないこと」だよ。
だから二重定義は起きようがないと思う。
今の1.8のフローは、
st_lookup(定数) -> st_delete(定数) -> st_delete(autoload) -> require
th->errinfo ってなんだっけ
$! かな。
に、数値の 8 を入れるやつはだれだ
1.9は st_lookup(定数) -> require -> その中の定数定義の途中でst_delete(定数) -> st_delete(autoload)
目標フローは st_lookup(定数) -> require -> st_delete(autoload)
http://www.atdot.net/sp/view/y5fcll autoloadのだれかがいれてるのは間違いない
なんと誰のところでも再現する?
私のはwindowsでmameさんはlinuxだろうから、割と何処でも出そう
うちでも起きた。
だから、単に未初期化のゴミ値、でもないんすね。
意味がわからないんだけど、eval.cのruby_cleanup()のコメントに
{unak} /* th->errinfo contains a NODE while break'ing */
って書いてある。
例外じゃないなんかをぶちこむ特殊ケースがある、ということかしら...
で、eval_intern.hにNEW_THROW_OBJECTというマクロがあって、
これ使って生成したNODEが入ってるタイミングがあるらしい。
17になってる
NODEはFixnumには見えないと思うんだけどなあ。
普通にポインタだよね?
内部で使う例外オブジェクトがあって
Fixnumになってなかったかな
ほう
どこを見たもんかな...
thread.c:1320 th->errinfo = INT2FIX(TAG_FATAL);
これかなぁ
おお
TAGだろうと思ってgrepかけたが負けた。
17っていうのはFixnumna
Fixnumな8のVALUE値のことすか。
そう
eTerminateSignalが投げられてることになってる
で、と。
よくわからんな。
http://www.atdot.net/sp/raw/s5gcll
こうすると
{n0kada} ^C"exception reentered"
{n0kada} -e:1:in `join': Interrupt
こうなるようになった
これreenteredなの?
とりあえず適当に、ですか。
何がFATALなんだろこれ。
http://www.atdot.net/sp/view/g8gcll
autoload ではなく require で発動する
--disable-gem でも発動するから rubygems は無罪
元々完全に処理が足りてない、ような気はするのだが
で、なんだっけ、って感じ
Ctrl+Cが押されました。なので全スレッドを殺しに行きます。
殺しました。これマジ殺しなのでFATALです。
ここまではOK
なんだけど、ensureするまでは死ねないわけでぇ
ensureでは死因を見られるんですけどぉ
FATALなので死因がありませーん
... ということでいいんでしょうかね。
nil入れたらどうなるんだろ。
{sorah-bot} Join: xibbar
autoload関係ないな
http://www.atdot.net/sp/view/s6hcll
例えばこんな感じで。
いやそうでもないか
require 中に例外が起きたら re-raise するんだけど
TAG_FATAL は re-raise できない
あれ、なんで loaded が false なんだろうね
一応このパッチで現象は止まった
止まった?
ふうむ
変わらんように見えるんだが
rb_load_file()が完了しないとloadedはTRUEにならないから、そこには特に疑問はないけど。
ただ、stateがTAG_FATALのはずだから、if (!loaded && state != TAG_FATAL) でいいんじゃね? とか
いや、それじゃその後死ぬのかな。YARVわかんね。
だめぽいな。没
そうか。だいたいわかった。
if (!loaded) の前に if (state == TAG_FATAL) TH_JUMP_TAG(th, TAG_FATAL); がいいのかなーとか思うけどたぶん効果は全く同じ?
あれ
俺の案だとうまくいった(p $!はnil)けど、mameさんの奴だと現象変わらん
GET_THREAD()->errinfoじゃなくてth->errinfoじゃないといけないとかそんなんかしら
$!はnilになるべきなん?
nilになるべきだとおもふ。
n0kada: FATALは例外じゃないんだから$!に入れるべきものはない。故にnilのままでいいのではないか。というのが私めの見解でございます。
じゃぁrb_threadptr_execute_interrupts_recでerrinfoにINT2FIX(TAG_FATAL)入れてるのがおかしくない?
それがおかしい、というのは思わなくもない。
例えば、こっちのスレッドでもInterrupt例外が起きました、というのが真っ当な気もするですよね。
$!は直接Interruptで抜けてるわけでもないんだよな
うん
Interruptで終わろうとしてるのはmainだけ
うん
なので、Interruptは全スレッドで起こそうよ、という提案なわけです。
今から入れるの怖すぎると思う。
(じゃあ提案するな)
Interruptだと止められる
rescueできる
ensureでretryとかはとりあえず無視
したがって見えるべきなのはfatalかなんかでは
で、このパターンはそういう解もありえるんじゃないかという程度だけど、他のFATALになるパターンがあるとして、そういう時は結局$!をnilにして処理を進めるしかないんじゃない?
fatalって見えるもんだっけ?
ふつうはみえない
fatalを見えるようにする、という仕様変更
それも怖すぎる。
fatalの場合ensureが実行されない、というのは
ダメだよなやっぱり
fatal って本当に必要なんだろうか、って前も言った気がする
えっ、だめなの
なんか前に俺こんな話をdevでしなかったっけ
だめです
終了時に他のスレッド殺すのも普通の例外でいいじゃんね
ensureで拾えたら、fatalで死ぬ保証がないような
保証はない
「普通の例外」て何?
fatal でない例外
海原雄山が怒鳴り散らしそうな仕様
mainと同じInterruptを配送するくらいしか思いつかないですけど...
いいんじゃね?
死ねと言われました例外
なんで終了時に他のスレッド殺すくらいで fatal とまでいうのか
全然 fatal じゃない
fatal の意味論が明確じゃないから責めようがない。Cのレベルで直感に反する名前なんてほかにいくらでも・・・
{unak} Thread.new{begin; sleep; ensure; p $!; end}.kill # => nil
なので、やっぱnilでいいじゃん
{unak} Thread.new{begin; sleep; ensure; p $!; end}.join
でCtrl+Cしてもやっぱりnil
これと違うようにする理由は特にないんじゃない。
fatalで殺してるわけじゃない
いや、違う挙動にならないといけない理由は何? と言ってる。
俺には思いつかん。
ある種の例外にしてしまうとrescueできるのでやっぱよくないかな。rescueできない例外を作る(言い換えるとfatalの類を見えるようにするということ)のもありかと思うがさすがにどうかと思う。

case insensitive と load

unak: http://redmine.ruby-lang.org/issues/show/4255 のr30508がWindowsのために?必要になる理由のかけらでも思い出したら教えてください
{unak} http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=30508
blah.rbが2回実際にrequireされるから。
1.9.2-p0で、1回しか読まれない気がするんですが。。。
r30508って未リリースですよね。
これがなくても別の形でのガードが入ってる?
なんかガードがあるならそれでいいので別に僕はこれにこだわりがあるわけではないですよん。
foo.rbは一回だけ: http://www.atdot.net/sp/view/uiicll
expand_path したら foo.rb に揃うとか
expand_path じゃないか、realpath だっけ
まあ、なんかそんなの。
mame1: そう思います。なので、あのパッチは要らない。Charlesが騒いだだけで無視しとけばよかったんじゃないかと。
どうなんだろ。
n0kadaさんがが意図的に作ったtest_require.rb以外に実例ないんだから、せめて困ってからでよい。
で、Charlesは主に1.8を念頭に言っていたらしく、1.8は確かに困る。syouheiさんと話したけど、でもまあ、もう直せないよねえ1.8は。
1.8はexpand_pathだかなんだかをしてないからガード自体もされてない、のでテラヤバス
ということですかね。
で、1.9はそれでガードされてるので、$:だか$"だかしらんけど、それ自体を特別な子として扱う必要ないよ。求められてるのはガードだけなんだもの
というのがなひさんの見解、と思っていい?
はい。
$"ですね。今回覚えたw
んじゃ、私としては、$"を特別な子として扱う仕様変更が要るかどうかには特に意見はありません。
つまり、必要性があるという認識はない。ただし、そういう仕様でも別に悪いとも思わない。
2011 年にもなって 1.8 を念頭にしゃべるとか (笑)
4255で議論されている「HFS+だからってケース非依存じゃないよ」「マウントしたら」「コンパイル時に決まるわけない」とかいうのはどうですか。検出方法がそもそもイケてなくない?
そこはおいらあきらめてる。
それは認める
こんなhackでも入れたい?
$" に限った話じゃなくて、他の事も全部決め打ちしてる。
だから別問題。
rb_file_identical_pだかなんだかを使うとか
本来静的ではなく動的に判断すべき、という話は、Windows/Macどころか全プラットフォームにまたがる話だし(例えばLinuxでFATをどう扱う? とか)
file.cとかdir.cとかも眺めながら考えないといかん長期的な課題。
なら戻そう!
とかいうのをここでそう言ってても仕方ないことは認識していて、なんとなく脈がありそうなのでreopenしたいと思います。
なので、file.cやdir.cと同じ扱いなのは別にdirtyでもなんでもなくて、今のrubyの仕様としてはふつー。
load.cよく読んでもわかんないんだもんよ。見当違いかもと思ってました。
Macで同じ扱いなのかどうかは知らんけど。
なのでですね、静的に決まるかどうかが気にいらんとかいう攻め方はダメでですね(そういうこと言う奴はfile.cとdir.cも全部直せ)、
必要性の有無の確認で押すのがいいのではないでしょうか。
はーい。特にfile.cとdir.cのことはよくわからないので、そのほうがよさげですw

教養ちゃんねる

IRCのログ読んでたら勉強になりそうだなあ。
どこかアーカイブあるんでしたっけ?
ほしいですねえ
勉強になると思いますよ
日本神話とか仏教とキリスト教とイスラム教の比較とか
w > 教の比較
ぶっちゃけ、普段そんなにrubyの中身の話してる気はしない。
今日はautoloadの話を卜部さん経由で聞いたからたまたま。