観察日記 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には見えないと思うんだけどなあ。
普通にポインタだよね?