-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Nginx::Stream::Async race condition #437
Conversation
Thank you for your PR. The test seems failed. Please check it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I commented. Could you add simple tests?
build_config.rb
Outdated
@@ -78,6 +78,7 @@ | |||
conf.gem :github => 'matsumotory/mruby-simpletest' | |||
conf.gem :github => 'mattn/mruby-http' | |||
conf.gem :github => 'mattn/mruby-json' | |||
conf.gem :github => 'mattn/mruby-thread' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you include mruby-thread?
|
||
mrb_get_args(mrb, "o", &upstream); | ||
mrb_get_args(mrb, "|o", &upstream); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you use optimal arguments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is not necessary to specify an upstream server.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean that even if we don't specify an upstream configuration, we want to use instance methods like local_ip
, right? OK, fair enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exactly right!
|
||
*ctx->ictx->s = *((ngx_stream_mruby_internal_ctx_t *)mrb->ud)->s; | ||
ctx->ictx->stream_status = ((ngx_stream_mruby_internal_ctx_t *)mrb->ud)->stream_status; | ||
ngx_stream_session_t *s = ctx->ictx->s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should declare the type at the beginning of the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i got it.
ctx->ictx = (ngx_stream_mruby_internal_ctx_t *)mrb_malloc(mrb, sizeof(ngx_stream_mruby_internal_ctx_t)); | ||
ctx->ictx->s = (ngx_stream_session_t *)mrb_malloc(mrb, sizeof(ngx_stream_session_t)); | ||
|
||
*ctx->ictx->s = *((ngx_stream_mruby_internal_ctx_t *)mrb->ud)->s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you copy the dereference data to ictx->s? I think we use only the pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry.i fix it.
And could you please modify the documents in docs? If you finish to write the code, test, and docs, please check the check list of PR template. |
ぴゃまさん、なんかよく見たら 1843183 のcommitでその前のテストの変更とか上書きしてしまって、全体として謎PRになっていたけど、だいたい理解した |
当初のパッチで謎にsegvしてて、最後にクラスメソッドに気づいたから、メモリコピーしてたり謎なままコミットしてしまっていました。 |
ほい! |
CI、差し支えなければgithub actionsに移行しちゃいたいんですけどどうですか? |
全然OK! なんですけど、別prで久米さんが色々ci弄ってるのでそれからが良いかも |
ok牧場! |
) | ||
end | ||
|
||
def self.stream_status=(v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これ意味ないなw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mrb->udから受け取ったポインタ上の値は書き換わるから意図した結果になるか。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
他のクラスメソッドも同じように引数無しで渡すインスタンスメソッドのラッパーにして互換性持たすのも良いかもね
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あ、そういう意味!ちゃんと実装してた
travisだけテスト落ちる病久しぶりだな。 |
ぴゃまさん明日はマラソンだし今日はもう寝なよ |
@@ -35,14 +36,13 @@ static const struct mrb_data_type ngx_stream_mrb_upstream_context_type = { | |||
static mrb_value ngx_stream_mrb_connection_init(mrb_state *mrb, mrb_value self) | |||
{ | |||
ngx_uint_t i; | |||
int argc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mrb_int
57ca5a2
to
1a4b6d4
Compare
ちょっとここまで書き進めて気付いたのですが、ログ周りもクラスメソッドになってるから、ictxをグローバル変数に待避できるようにして、run_fiberのときにpush、それぞれのメソッドでpopするような実装にしないとだめそうですね.... |
ちょい考えたのですが全部ud使わない実装に一気にやるとPRも膨れるのとリリースインパクトが大きくなってしまうので下記のステップで分けようかと思いますがどうですかね? step1. mrd->udを利用したまま、async sleepの前後でudの整合性を取ることでレースを避ける実装を入れる |
なんか全体的にこの辺は仕組みを考え直そうかな。少し考えます。 |
udのレースというも、udに入ったポインタを渡しているだけから、どういうケースで上書きが起きてるのかがちょっとわからないな。 |
再現コードだけ欲しいですね、セグフォルやつ |
あー性能重視して起動時に一度だけictx作るようにしてるからか |
んー?でも、fiber呼び直されるたびにsは同じものをそのまま渡しているから問題起きなさそうだけどな |
このあたり。nginxはプロセス単位では直列で、ここみたいに毎回先に使うべきsを再格納しているから、レースが起きなさそうにも思える。再現コードをもらって僕もどこか問題がデバックしたいなと思えたので、再現コードあればまた教えてください! |
Async sleepを多重に利用すると起きます。 |
それこそ、ログやクラスメソッドとかがmrb->udそのまま読むから、そこで他のセッションが格納されてる感じすね。いずれにせよセッションmrb->udっていうアクセス詞をやめないとダメそうに思うけどどうです? |
家帰ってから再現テスト書いてコミットします |
最初そう思ったんですけど、fiber単位でresume時であっても毎回このhandler #437 (comment) が呼ばれるから、sessionは常に対象の構造体になるはずなんですよね。再現コードやログデバッグなどでまさにここでおかしくなってるというのを確認できれば良いんですけど |
それだと、Cで書かれた別のmgemがリンクされている場合は、使い方によってはそもそもmrubyの世界にmruby->ud->sを持っていくこと自体が競合になるので、その最初の一歩が使い方によっては干渉し合うんじゃないかなぁと。 |
ああ、そうですね。handlerの一番最初にインスタンス化されるとは限らないから。そうなるとnginxの構造体を受け取らないmrubyのメソッド全般しんどい感じですね。。コード眺めて考えてみる。 |
ひとまずやっていただけるということですので、run_fiberのバイトコード実行直前にちゃんとictxがそのバイトコード(proc)と紐づくnginxのコンテキスト(ictx->sなど)にアップデートできるようにする、これをこのPRでやる。 それで今のsegvは解決で、その後に、mrb->udが他のmgemで使った場合でもうまくいくような実装を考える、これをまた別PRでやる、みたいな感じはいかがでしょう? |
マラソンは結構です! |
はい、このPRはコミット整理しつつ、そうします。udの件は考えて別途提案します。 |
udの件は面白いので僕も考えてみます.....!いつもありがとうございます! |
なんかセッション書き換わってしまうのなんでだと思っていたら、mrb->ud(ictx)のallocateがconfig読み込みのときだったからだった。 handlerごとにictxをコネクションからアロケートする方式へ変える。 |
あ、いえ、それは想定した実装で、性能を考慮してセッション単位での不要な都度メモリ確保はやめたいのでconfig時にやっています。できればその領域をmrubyの世界への橋渡しに使ってください。出来るだけゼロコピーの実装を目指したい |
なるほどぉ、そうなるとictxまるっとreentrantに持っていくのではなくて、sessionとstream statusを別個に持っていくのが良さそうですねぇ。しばしお待ちを。 |
This reverts commit 551af63.
お!そのとおり!100点の回答じゃないですか。ご飯食べなたら雑に返したので今から説明しようと思っていたところでした。 ictxは単一でもって、nginxの世界のデータをmrubyの世界に持っていくたったひとつのトンネルとして考えて、nginxのデータはすでに確保されているはずなので、ictxはhandlerごとにもたないことによってictxの不要な確保開放とメモリ使用をへらすのが狙いです。 |
その場合run_fiberでmrb->udの状態を戻すとなるとstream_statusを引数で渡してやらにゃならんのですが、実用上はpost_fiberでもどしてやれば問題ないのでその方向で実装しました。 |
うっす |
|
||
ngx_stream_session_t *s = ngx_mrb_get_session(); | ||
ctx = ngx_stream_mrb_get_module_ctx(mrb, s); | ||
ictx = mrb->ud; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この段階でictx->sがfiberで実行すべきsと別のfiberのsになっている可能性はありませんかねぇ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
post_handlerはfiberがresumeありのときに呼ばれますが、通常のsyncなhandlerのときは呼ばれませんが、それでも整合性はとれていそうでしょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
はい。その場合、ngx_stream_mruby_handlerでictxが初期化され、ngx_stream_mrb_run_fiberがコールされるので問題ないです。
初期化経路としては ngx_stream_mruby_handler 、ngx_stream_mrb_post_fiberのいずれかになります。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ふむふむ、そうですね。asyncが実行されてコールバック待ちのときにhandlerが呼ばれてudの情報が変わっても、asyncからpost_fiberがコールバックされたときに、そこでudの情報をasyncのものに置き換えるので、その後のrun_fiberでは正しいsession情報が渡される、ということですね。
また、handlerについても、syncなhandlerはそもそも必ずhandler内でsession情報を初期化するので問題ない、と。
OKです!なんかこのあたりをコードでわかりやすく表現できると良いなと思いましたが、それはまた別のリファクタということで、ここではスコープ外のほうがよいですね。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確かに。prepare〜みたいなメソッド掘るだけでもだいぶリーダブルになりそうですね。
Thank you for your effort and good job マラソン! |
はい〜。実機であれこれ確認します。 |
Hi Mats!!!
Have you eaten dinner??
Nginx::Stream module is using
mrb->ud
.it is a possibility of becoming a race condition when call Nginx::Stream::Async.sleep.
In this patch, added mrb-> ud consistency before and after run fiber.
Thanks!
Pull-Request Check List
src/
.test/
. Please see about test docs.docs/
if you change the features such as build system, Ruby methods, class and nginx directives.