Skip to content
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

Merged
merged 7 commits into from
Nov 11, 2019
Merged

Fix Nginx::Stream::Async race condition #437

merged 7 commits into from
Nov 11, 2019

Conversation

pyama86
Copy link
Collaborator

@pyama86 pyama86 commented Nov 8, 2019

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

@matsumotory
Copy link
Owner

Thank you for your PR. The test seems failed. Please check it.

Copy link
Owner

@matsumotory matsumotory left a 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'
Copy link
Owner

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);
Copy link
Owner

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?

Copy link
Collaborator Author

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?

Copy link
Owner

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.

Copy link
Collaborator Author

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;
Copy link
Owner

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.

Copy link
Collaborator Author

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;
Copy link
Owner

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry.i fix it.

@matsumotory
Copy link
Owner

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.

@matsumotory
Copy link
Owner

ぴゃまさん、なんかよく見たら 1843183 のcommitでその前のテストの変更とか上書きしてしまって、全体として謎PRになっていたけど、だいたい理解した

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 9, 2019

当初のパッチで謎にsegvしてて、最後にクラスメソッドに気づいたから、メモリコピーしてたり謎なままコミットしてしまっていました。
ちょいこのあとコミット整理して投げます。

@matsumotory
Copy link
Owner

ほい!

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 9, 2019

CI、差し支えなければgithub actionsに移行しちゃいたいんですけどどうですか?

@matsumotory
Copy link
Owner

全然OK! なんですけど、別prで久米さんが色々ci弄ってるのでそれからが良いかも

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 9, 2019

ok牧場!

)
end

def self.stream_status=(v)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ意味ないなw

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mrb->udから受け取ったポインタ上の値は書き換わるから意図した結果になるか。

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

他のクラスメソッドも同じように引数無しで渡すインスタンスメソッドのラッパーにして互換性持たすのも良いかもね

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ、そういう意味!ちゃんと実装してた

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 9, 2019

travisだけテスト落ちる病久しぶりだな。

@matsumotory
Copy link
Owner

ぴゃまさん明日はマラソンだし今日はもう寝なよ

@@ -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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mrb_int

@pyama86 pyama86 force-pushed the segv-stream branch 3 times, most recently from 57ca5a2 to 1a4b6d4 Compare November 9, 2019 12:49
@pyama86 pyama86 changed the title Fix Nginx::Stream::Async race condition [WIP]Fix Nginx::Stream::Async race condition Nov 9, 2019
@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 9, 2019

ちょっとここまで書き進めて気付いたのですが、ログ周りもクラスメソッドになってるから、ictxをグローバル変数に待避できるようにして、run_fiberのときにpush、それぞれのメソッドでpopするような実装にしないとだめそうですね....

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 9, 2019

ちょい考えたのですが全部ud使わない実装に一気にやるとPRも膨れるのとリリースインパクトが大きくなってしまうので下記のステップで分けようかと思いますがどうですかね?

step1. mrd->udを利用したまま、async sleepの前後でudの整合性を取ることでレースを避ける実装を入れる
step2. 今回作業する中で気付いた部分をリファクタリング
step3. インスタンスメソッドはインスタンス変数を利用して、udを利用しない実装にし、クラスメソッドはグローバル変数にsession構造体をpush&getして、マルチスレッドでは競合する可能性があるが、動く状態にする

@matsumotory
Copy link
Owner

なんか全体的にこの辺は仕組みを考え直そうかな。少し考えます。

@matsumotory
Copy link
Owner

udのレースというも、udに入ったポインタを渡しているだけから、どういうケースで上書きが起きてるのかがちょっとわからないな。

@matsumotory
Copy link
Owner

再現コードだけ欲しいですね、セグフォルやつ

@matsumotory
Copy link
Owner

あー性能重視して起動時に一度だけictx作るようにしてるからか

@matsumotory
Copy link
Owner

んー?でも、fiber呼び直されるたびにsは同じものをそのまま渡しているから問題起きなさそうだけどな

@matsumotory
Copy link
Owner

https://github.com/matsumotory/ngx_mruby/blob/master/src/stream/ngx_stream_mruby_module.c#L709

このあたり。nginxはプロセス単位では直列で、ここみたいに毎回先に使うべきsを再格納しているから、レースが起きなさそうにも思える。再現コードをもらって僕もどこか問題がデバックしたいなと思えたので、再現コードあればまた教えてください!

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 10, 2019

Async sleepを多重に利用すると起きます。
Async sleepから、復帰後にmrb-udを使うと、スリープ中に処理された他のトランスポートセッションが入ってる感じすね。

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 10, 2019

それこそ、ログやクラスメソッドとかがmrb->udそのまま読むから、そこで他のセッションが格納されてる感じすね。いずれにせよセッションmrb->udっていうアクセス詞をやめないとダメそうに思うけどどうです?

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 10, 2019

家帰ってから再現テスト書いてコミットします

@matsumotory
Copy link
Owner

最初そう思ったんですけど、fiber単位でresume時であっても毎回このhandler #437 (comment) が呼ばれるから、sessionは常に対象の構造体になるはずなんですよね。再現コードやログデバッグなどでまさにここでおかしくなってるというのを確認できれば良いんですけど

@matsumotory
Copy link
Owner

それだと、Cで書かれた別のmgemがリンクされている場合は、使い方によってはそもそもmrubyの世界にmruby->ud->sを持っていくこと自体が競合になるので、その最初の一歩が使い方によっては干渉し合うんじゃないかなぁと。

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 10, 2019

ああ、そうですね。handlerの一番最初にインスタンス化されるとは限らないから。そうなるとnginxの構造体を受け取らないmrubyのメソッド全般しんどい感じですね。。コード眺めて考えてみる。

@matsumotory
Copy link
Owner

ひとまずやっていただけるということですので、run_fiberのバイトコード実行直前にちゃんとictxがそのバイトコード(proc)と紐づくnginxのコンテキスト(ictx->sなど)にアップデートできるようにする、これをこのPRでやる。

それで今のsegvは解決で、その後に、mrb->udが他のmgemで使った場合でもうまくいくような実装を考える、これをまた別PRでやる、みたいな感じはいかがでしょう?

@matsumotory
Copy link
Owner

マラソンは結構です!

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 10, 2019

はい、このPRはコミット整理しつつ、そうします。udの件は考えて別途提案します。

@matsumotory
Copy link
Owner

udの件は面白いので僕も考えてみます.....!いつもありがとうございます!

@pyama86 pyama86 changed the title [WIP]Fix Nginx::Stream::Async race condition Fix Nginx::Stream::Async race condition Nov 10, 2019
@pyama86 pyama86 changed the title Fix Nginx::Stream::Async race condition [wip]Fix Nginx::Stream::Async race condition Nov 11, 2019
@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 11, 2019

なんかセッション書き換わってしまうのなんでだと思っていたら、mrb->ud(ictx)のallocateがconfig読み込みのときだったからだった。
https://github.com/matsumotory/ngx_mruby/blob/segv-stream/src/stream/ngx_stream_mruby_module.c#L183

handlerごとにictxをコネクションからアロケートする方式へ変える。

@pyama86 pyama86 changed the title [wip]Fix Nginx::Stream::Async race condition Fix Nginx::Stream::Async race condition Nov 11, 2019
@matsumotory
Copy link
Owner

あ、いえ、それは想定した実装で、性能を考慮してセッション単位での不要な都度メモリ確保はやめたいのでconfig時にやっています。できればその領域をmrubyの世界への橋渡しに使ってください。出来るだけゼロコピーの実装を目指したい

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 11, 2019

なるほどぉ、そうなるとictxまるっとreentrantに持っていくのではなくて、sessionとstream statusを別個に持っていくのが良さそうですねぇ。しばしお待ちを。

@matsumotory
Copy link
Owner

お!そのとおり!100点の回答じゃないですか。ご飯食べなたら雑に返したので今から説明しようと思っていたところでした。

ictxは単一でもって、nginxの世界のデータをmrubyの世界に持っていくたったひとつのトンネルとして考えて、nginxのデータはすでに確保されているはずなので、ictxはhandlerごとにもたないことによってictxの不要な確保開放とメモリ使用をへらすのが狙いです。

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 11, 2019

その場合run_fiberでmrb->udの状態を戻すとなるとstream_statusを引数で渡してやらにゃならんのですが、実用上はpost_fiberでもどしてやれば問題ないのでその方向で実装しました。

@matsumotory
Copy link
Owner

うっす


ngx_stream_session_t *s = ngx_mrb_get_session();
ctx = ngx_stream_mrb_get_module_ctx(mrb, s);
ictx = mrb->ud;
Copy link
Owner

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になっている可能性はありませんかねぇ?

Copy link
Owner

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のときは呼ばれませんが、それでも整合性はとれていそうでしょうか?

Copy link
Collaborator Author

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のいずれかになります。

Copy link
Owner

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です!なんかこのあたりをコードでわかりやすく表現できると良いなと思いましたが、それはまた別のリファクタということで、ここではスコープ外のほうがよいですね。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かに。prepare〜みたいなメソッド掘るだけでもだいぶリーダブルになりそうですね。

@matsumotory matsumotory merged commit ac1cca0 into master Nov 11, 2019
@matsumotory matsumotory deleted the segv-stream branch November 11, 2019 06:50
@matsumotory
Copy link
Owner

Thank you for your effort and good job マラソン!

@pyama86
Copy link
Collaborator Author

pyama86 commented Nov 11, 2019

はい〜。実機であれこれ確認します。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants