ngx_mruby v2.7.3
アップグレードの前に読む挙動の変更
Section titled “アップグレードの前に読む挙動の変更”1. Nginx::SSL::ACME::Dehydrated#auto_cert_deployは、dehydratedの標準エラー出力も読んでCAの利用規約に同意し、同意は1回の呼び出しにつき1回までにします(#636)
Section titled “1. Nginx::SSL::ACME::Dehydrated#auto_cert_deployは、dehydratedの標準エラー出力も読んでCAの利用規約に同意し、同意は1回の呼び出しにつき1回までにします(#636)”- 変更前:auto-sslのmrbgemの
#auto_cert_deployは、dehydratedの標準出力だけを読んでいました。dehydratedの0.5.0以降は、CAの利用規約への同意を求める文を標準エラー出力に書くので、このメソッドはその文を見つけられず、#auto_accept_termsを呼びませんでした。まだ登録していないdehydratedのアカウントでは、dehydratedが止まって証明書が保存されず、ハンドシェイクが失敗しました。証明書が無いので、Nginx::SSL#certificate_data=がTypeErrorを発生させたためです。dehydratedが標準エラー出力に書いた文は、時刻とレベルの無い行のままerror.logに入っていました。同意を求める文を標準出力に書くdehydrated 0.4.0では、このメソッドは#auto_accept_termsを呼んでから自分自身を呼び直し、その回数に上限がありませんでした。登録が失敗すると、次の実行がまた同意を求め、このメソッドはmrubyのスタックが尽きるまで、ハンドシェイクの中でdehydratedを実行し続けました。 - 変更後:
#auto_cert_deployは、dehydratedの標準エラー出力を標準出力にまとめて実行します。そのためのシェルの指定は2>&1です。同意を求める文を見つけると、#auto_accept_termsがdehydrated --register --accept-termsでアカウントを登録し、#auto_cert_deployがdehydratedをもう一度実行して、証明書を取得します。同意は1回の呼び出しにつき1回までです。登録が失敗して2回目の実行がまた同意を求めたときは、dehydratedを2回実行したところで、証明書の無いまま戻ります。--cronが書いた文は、標準エラー出力の分も含めて、このメソッドがNginx::SSL.logで書く[info]の行に入ります。戻り値は、前と同じnilです。 - 変更の理由:
- 不具合:このプルリクエストが加えたauto-sslのmrbgemのテスト
test/t/cases/auto_ssl.rbを、dehydrated 0.7.2とACMEのテストサーバーのPebbleで動かしたところ、新しいアカウントでは証明書を一度も取得できませんでした。このメソッドは、test/conf/auto-ssl/dehydratedにある版のdehydrated 0.4.0に合わせて書かれていました。0.4.0は、同意を求める文を標準出力に書いていました。また、プルリクエストのレビューで、登録が失敗するとこのメソッドがdehydratedを終わりなく実行することが分かりました。dehydrated 0.7.2は、登録が失敗すると自分で作ったアカウントの鍵を消すので、実行のたびに同意を求めるためです。テストは、外部アカウントの紐付けを求めるPebbleでこれを再現します。 - 前の挙動を残せなかった理由:このメソッドは、dehydratedが出力した文の中から同意を求める文を探します。標準エラー出力を別にしたままでは、ACME v2を話すどのdehydratedでも、その文を見つけられません。ACME v2を話すのは、dehydratedの0.6.0以降です。Let’s Encryptは、2021年にACME v1を止めてから、ACME v2だけを提供しています。同意を求める文を見つけるようになると、上限が無いままでは、登録を断るCAのためにワーカーがハンドシェイクから戻らなくなります。登録を断るのは、外部アカウントの紐付けを求めるCA、障害の起きているCA、新しいアカウントの数を制限しているCAです。
- ドキュメントが示していた挙動:docs/class_
and_ は、APIについてmethod/README.md mrbgems/auto-ssl/mrblibのスクリプトを参照するよう書いており、dehydratedの出力については何も書いていません。#auto_accept_termsと、#auto_cert_deployの中の判定は、最初の実行で利用規約に同意するためにあります。
- 不具合:このプルリクエストが加えたauto-sslのmrbgemのテスト
- 影響の範囲:
test/conf/auto-ssl/nginx.conf.dehydrated_exampleのように、Nginx::SSL::ACME::Dehydrated#auto_cert_deployを呼ぶTLSのハンドシェイクのハンドラです。error_logのレベルがinfoより上のとき、dehydrated --cronが標準エラー出力に書く文は、エラーも含めて、error.logに出なくなります。例の設定のレベルはnoticeです。 - 必要な対応:新しいアカウントで証明書を取得するための対応は要りません。
dehydrated --cronのエラーをerror.logで見るには、そのサーバーが使うerror_logのレベルをinfoにしてください。1回の呼び出しの中で登録をやり直すことに頼っていたコードでは、後のハンドシェイクでこのメソッドをもう一度呼んでください。その呼び出しが、dehydratedをもう一度実行します。 - このリリースで変わらない挙動:
#auto_accept_termsは、今もdehydrated --register --accept-termsの標準出力だけを読みます。このコマンドが標準エラー出力に書く「Error registering account key」のような文は、今も時刻とレベルの無い行のままerror.logに入ります。
2. Nginx::Async.sleepを呼ぶcontentハンドラは、sleepの後に応答します(#646)
Section titled “2. Nginx::Async.sleepを呼ぶcontentハンドラは、sleepの後に応答します(#646)”- 変更前:
mruby_content_handler*のコードでNginx::Async.sleepを呼んでも、例外は発生しませんでした。しかし、nginxはそのハンドラを待ちませんでした。リクエストはsleepの時点で終わり、クライアントには応答が届かず、sleepの後のコードは実行されませんでした。接続は閉じられるか、このリクエストに応答しないまま、クライアントの次のリクエストのために開いたままになりました。contentハンドラがsleepするlocationへのサブリクエストは、そのハンドラの出力を受け取りました。Nginx::Async::HTTP.sub_requestが送るサブリクエストが、その例です。しかし、その後にnginxはレベルalertのhttp request count is zeroを書き、その接続はクライアントの次のリクエストに応答しませんでした。 - 変更後:nginxは、sleepするcontentハンドラを待ちます。sleepの後のコードが実行され、クライアントは、ハンドラが
Nginx.rputsやNginx.echoで書いた内容か、Nginx.returnで返したステータスを、レスポンスとして受け取ります。これは、sleepしないハンドラと同じです。2回以上sleepするハンドラは、最後のsleepの後に応答します。sleepの後に例外が発生したときは、ほかのハンドラと同じく、リクエストは500を受け取ります。そのようなlocationへのサブリクエストは出力を受け取り、接続はその後も使えます。contentハンドラでは、sleepの後のNginx::Async::HTTP.sub_requestはRuntimeErrorを発生させるので、リクエストは500を受け取ります。そのサブリクエストの後に、nginxがリクエストを終えないためです。2.7.2では、そのコードは実行されませんでした。 - 変更の理由:
- 不具合:contentフェーズは、ハンドラのfiberが待っている間にハンドラが返した
NGX_DONEで、リクエストをfinalizeしました。そのため、nginxはsleepのタイマーが発火する前にリクエストを解放し、リクエストのcleanupがそのタイマーを消しました。2.7.1のリリースノートは、この件を既知の制限に挙げ、contentハンドラが待てるようにするリリースを予定していました。オーナーは2026-10-04に、contentハンドラで例外を発生させず、Nginx::Async.sleepを使えるようにすると決めました。 - 前の挙動を残せなかった理由:sleepの後のコードを実行するには、sleepが終わるまでリクエストを残す必要があります。リクエストを残せば、そのリクエストには応答が返ります。不具合を除く別の方法は、logハンドラと同じく、contentハンドラで
RuntimeErrorを発生させることでした。この方法では、今は例外なしにこのメソッドを呼んでいるコードが動かなくなります。 - ドキュメントが示していた挙動:docs/class_
and_ は、contentハンドラではこのメソッドが例外を発生させず、リクエストはsleepの時点で応答なしに終わると書いていました。また、このメソッドが動く場所として、set、rewrite、accessの各ハンドラを挙げていました。method/README.md
- 不具合:contentフェーズは、ハンドラのfiberが待っている間にハンドラが返した
- 影響の範囲:
Nginx::Async.sleepを呼ぶmruby_content_handlerとmruby_content_handler_codeのコードと、そのようなコードを持つlocationへのサブリクエストです。そのリクエストは、sleepの間は開いたままになり、sleepの後に応答を受け取ります。 - 必要な対応:sleepの後の応答を期待するコードでは、対応は要りません。sleepで応答なしにリクエストを終わらせることに頼っていたコードには、今は応答が返ります。応答なしに接続を閉じるには、代わりに、ステータス444を表す
Nginx::HTTP_CLOSEをNginx.returnで返してください。サブリクエストは、contentハンドラのsleepの後でなく、rewriteハンドラかaccessハンドラから送ってください。 - このリリースで変わらない挙動:Rubyのコードから呼んだ
Nginx.redirectが実行するlocationのcontentハンドラは、今もsleepの時点でリクエストを終え、出力を返しません。Nginx.redirectを呼んだハンドラは呼び出しの後も続き、自分でリクエストに応答することがあります。その場合に、sleepしたハンドラの出力がその応答の後に続くためです。
3. Nginx.redirectが実行するlocationで例外が発生すると、そのlocationのハンドラが終わり、nginxはリクエストを終えます(#646)
Section titled “3. Nginx.redirectが実行するlocationで例外が発生すると、そのlocationのハンドラが終わり、nginxはリクエストを終えます(#646)”- 変更前:
Nginx.redirect "/path"は、/pathのハンドラを実行してから戻ります。/pathのcontentハンドラ、rewriteハンドラ、header filterのような、mrubyのハンドラかフィルタで例外が発生すると、その例外は、Nginx.redirectの呼び出しが発生させたかのように、呼び出したコードに届きました。呼び出しの後のコードは実行されませんでした。呼び出したコードが例外をrescueしなかったときは、呼び出したハンドラが500を返しました。rescueしたときは、クライアントに応答が届きませんでした。どちらの場合も、nginxはリクエストを終えませんでした。接続は、応答の後もクライアントが閉じるまで開いたままで、access logにはそのリクエストの行が書かれず、stub_statusはworkerが終わるまでその接続を有効な接続として数えました。 - 変更後:例外は、リダイレクトしない場合と同じく、
/pathのハンドラを終わらせます。リクエストは、レスポンスをまだ始めていなければ500を受け取ります。Nginx.redirectは戻り、呼び出しの後のコードは、/pathが例外を発生させない場合と同じく実行されます。nginxはリクエストを終え、access logの行を書き、接続を閉じます。Nginx.redirectを呼んだハンドラが、/pathがステータスに残した500で終わると、nginxはそのステータスをもう一度送ろうとして、レベルalertのheader already sentを書きます。これは、リダイレクト先のレスポンスの後にハンドラがステータスを返す場合に、nginxが既に書いている行と同じです。例外のerror.logの行は変わりません。ただし、header filterの例外は、2回でなく1回だけ書かれます。 - 変更の理由:
- 不具合:mrubyは、Cから再開したfiberを終わらせる例外を、longjmpで、動いているいちばん内側のmrubyのVMへ渡します。
/pathのハンドラは、Nginx.redirectを呼んだハンドラのRubyの呼び出しの中で、自分のfiberとして動きます。そのため、例外は、2つの間にあるnginxの関数を飛び越えました。飛び越えた関数はngx_http_internal_redirectと各フェーズのcheckerで、リクエストを終えるのはこれらの関数です。このプルリクエストのテストtest/t/cases/redirect_to_raising_handler.rbは、開いたまま残る接続を示します。 - 前の挙動を残せなかった理由:nginxは、例外が飛び越えた関数の中でだけリクエストを終えます。そのため、例外が呼び出したコードに届く限り、リクエストは終わりませんでした。例外を
/pathのハンドラで止めると、リクエストは終わり、呼び出したコードは、例外を発生させないlocationへリダイレクトした後と同じく続きます。 - ドキュメントが示していた挙動:docs/class_
and_ は、リダイレクト先のlocationがクライアントにレスポンスを返すと書いていましたが、そこで発生する例外については何も書いていませんでした。method/README.md
- 不具合:mrubyは、Cから再開したfiberを終わらせる例外を、longjmpで、動いているいちばん内側のmrubyのVMへ渡します。
- 影響の範囲:mrubyのハンドラかフィルタが例外を発生させる内部のlocationへ、
Nginx.redirectでリダイレクトするコードと、そのような例外をNginx.redirectの周りでrescueしていたコードです。そのようなコードには例外が届かなくなり、呼び出しの後のコードが実行されます。 - 必要な対応:例外をrescueしていなかったコードでは、対応は要りません。リダイレクト先の失敗に対処するために
Nginx.redirectの周りでrescueしていたコードでは、rescueをそのlocationのコードの中へ移してください。 - このリリースで変わらない挙動:リダイレクト先のrewriteハンドラが
Nginx::Async.sleepで待っている間に、Nginx.redirectを呼んだハンドラがステータスか例外でリクエストに応答すると、そのrewriteハンドラの出力は、タイマーが発火したときに、接続の上でその応答の後に続きます。
4. streamのセッションは、Nginx::Stream::Async.sleepの後に、コードの結果に応じて先へ進むか終わり、sleepの後の例外は次のセッションを打ち切らなくなります(#651)
Section titled “4. streamのセッションは、Nginx::Stream::Async.sleepの後に、コードの結果に応じて先へ進むか終わり、sleepの後の例外は次のセッションを打ち切らなくなります(#651)”- 変更前:
Nginx::Stream::Async.sleepの後にセッションが先へ進んだのは、Nginx::Stream::Connection.stream_statusを既定値のNginx::Stream::DECLINEDのまま残したコードだけでした。コードが例外を発生させるか、ステータスをNginx::Stream::ABORTやNginx::Stream::OKなどの値にすると、nginxはセッションを終えず、先へも進めませんでした。クライアントは、接続を閉じるまで何も受け取らず、セッションはworkerが終わるまで残りました。また、sleepの後の例外は、stream {}ブロックのセッションが共有するmrubyの状態に残りました。workerが次に扱ったセッションは、ブロックのどのserverのセッションでも、自分のコードを実行する前に打ち切られ、error.logには前の例外がそのセッションの行としてもう一度書かれました。 - 変更後:sleepの後のセッションは、sleepしないコードと同じく先へ進むか終わります。
Nginx::Stream::DECLINEDでは次のハンドラへ、Nginx::Stream::OKでは次のフェーズへ進みます。Nginx::Stream::DONEとNginx::Stream::AGAINでは、セッションは待ったままです。例外、Nginx::Stream::ABORT、それ以外のステータスでは、セッションが終わります。sleepの後の例外は、その例外を発生させたセッションの行として1回だけ書かれ、次のセッションは自分のコードを実行します。 - 変更の理由:
- 不具合:nginxのaccessフェーズのcheckerは、すぐに戻ったハンドラの結果を処理します。sleepするハンドラはcheckerに
NGX_DONEを返し、コードを再開するタイマーは、sleepの後のコードの結果を、NGX_DECLINEDのとき以外は捨てていました。また、例外の後に、sleepしないコードと同じように共有のmrubyの状態から例外を消すこともしませんでした。そのため、次のセッションのngx_stream_mrb_start_fiberは、その例外を自分の例外として読みました。HTTPのハンドラは、2.7.1で同じ消し方をするようになりました(#550)。 - 前の挙動を残せなかった理由:結果を捨てられたセッションは、終わることも先へ進むこともありません。共有の状態に残った例外は、その例外を発生させていないセッションを終わらせます。nginxのcheckerと同じように結果を処理して、初めてセッションが終わるか先へ進みます。
- ドキュメントが示していた挙動:docs/class_
and_ は、method/README.md Nginx::Stream::Async.sleepをaccessフェーズで使えるメソッドとして、Nginx::Stream::Connection.stream_status=をクライアントを拒むためにNginx::Stream::ABORTを設定するメソッドとして書いていました。sleepの後の結果については、何も書いていませんでした。
- 不具合:nginxのaccessフェーズのcheckerは、すぐに戻ったハンドラの結果を処理します。sleepするハンドラはcheckerに
- 影響の範囲:
Nginx::Stream::Async.sleepを呼んだ後に例外を発生させるか、Nginx::Stream::Connection.stream_statusをNginx::Stream::DECLINED以外の値にする、mruby_streamとmruby_stream_codeのコードです。そのような例外の後にworkerが次に扱ったstreamのセッションも、影響を受けていました。 - 必要な対応:クライアントを拒むためにsleepの後で
Nginx::Stream::ABORTを設定するコードでは、対応は要りません。そのセッションは、今は終わります。sleepの後にNginx::Stream::OKを設定していたコードは、今はproxy_passのような次のフェーズへ進みます。セッションを先へ進めてはならない場合は、代わりにNginx::Stream::ABORTを設定してください。
5. Nginx::UpstreamとNginx::Stream::Connectionは、Stringでない引数にTypeErrorを発生させます(#652)
Section titled “5. Nginx::UpstreamとNginx::Stream::Connectionは、Stringでない引数にTypeErrorを発生させます(#652)”- 変更前:
Nginx::Upstream.new、Nginx::Upstream#server=、Nginx::Stream::Connection.new、Nginx::Stream::Connection#upstream_server=は、引数のクラスを確かめずに、引数をStringとして読みました。nilを渡すと、2つのsetterはnullポインタを読み、workerのプロセスがsegmentation faultで止まりました。String以外のほかのオブジェクトを渡したときの結果は、mrubyがそのオブジェクトをどう持つかで決まりました。workerが同じように止まるか、メソッドがそのオブジェクトのメモリを文字列のバイトとして読みました。 - 変更後:4つのメソッドは、Stringでない引数に
TypeErrorを発生させます。メッセージは「… cannot be converted to String」です。この検査は、upstreamを探す前と、serverを変える前に行います。Stringと、Stringのサブクラスのインスタンスは、前と同じく使えます。 - 変更の理由:
- 不具合:これらのメソッドは、
mrb_get_args(mrb, "o", ...)で引数を受け取り、Stringにだけ使えるRSTRING_LENとRSTRING_PTRで読んでいました。場所はsrc/http/ngx_http_mruby_upstream.cとsrc/stream/ngx_stream_mruby_connection.cです。このプルリクエストのテストのtest/t/cases/upstream_server_checks.rbとtest/t/cases/stream_upstream_server_checks.rbは、nilを渡します。オーナーは2026-10-29に、これを2.7.3で直し、挙動の変更として書くと決めました。オーナーの言葉は「直していいけどその仕様前提で書いてる人もいるから振る舞いの変更としてちゃんとリリースに書くようにしてね」です。 - 前の挙動を残せなかった理由:Stringでないオブジェクトには、読むべき名前やアドレスのバイトがありません。そのため、メソッドはその引数に意味を与えられません。先にクラスを確かめることで、メソッドはほかのメモリを読まなくなります。
- ドキュメントが示していた挙動:docs/class_
and_ は、引数を文字列として書いていました。method/README.md Nginx::Upstream#new(string)とNginx::Upstream#server=(string)がその例です。ほかのクラスについては、何も書いていませんでした。
- 不具合:これらのメソッドは、
- 影響の範囲:これらのメソッドにString以外を渡すコードです。たとえば、Hashに無いキーから得た
nilや、Symbolを渡すコードです。 - 必要な対応:
to_sなどでStringを渡し、値が無い場合は呼び出しの前に処理してください。
6. Nginx::Upstream.newとNginx::Stream::Connection.newは、名前全体でupstreamを探します(#652)
Section titled “6. Nginx::Upstream.newとNginx::Stream::Connection.newは、名前全体でupstreamを探します(#652)”- 変更前:2つのメソッドは、各
upstream {}ブロックの名前と引数を、引数の長さの分だけ、大文字と小文字を区別せずに比べ、最初に一致したものを使いました。upstreamの名前の先頭の部分だけの名前でも、そのupstreamが見つかりました。たとえば、Nginx::Upstream.new("back")はupstream backendを見つけました。upstream backend_aが設定の中で先にあると、Nginx::Upstream.new("backend")はupstream backend_aを見つけました。 - 変更後:メソッドは、名前全体が引数と同じupstreamを探します。比べるときは、nginxがupstreamの名前を比べるのと同じく、大文字と小文字を区別しません。upstreamの名前の先頭の部分だけの名前では、知らない名前と同じく、
RuntimeErrorが発生します。メッセージは<name> not found upstream configです。 - 変更の理由:
- 不具合:
ngx_mrb_upstream_initとngx_stream_mrb_connection_initは、引数の長さの分だけngx_strncasecmpで比べていました。#648のドキュメントをコードと照らしたときに、これが見つかりました。test/t/cases/filter_connection.rbは、これを2.xの挙動として固定していました。オーナーは2026-10-29に、2.7.3で名前全体で探すと決めました。選んだ選択肢は「2.7.3で完全一致にする (推奨)」です。オーナーは、そのテストの期待値の変更も承認しました。選んだ選択肢は「変えてよい (推奨)」です。 - 前の挙動を残せなかった理由:先頭の部分で一致させると、あるupstreamを指したスクリプトが別のupstreamを受け取ることがあります。その場合は、
Nginx::Upstream#server=がその別のupstreamのserverを変えました。 - ドキュメントが示していた挙動:ドキュメントは、引数をupstreamの名前として書いていました。例は
"YOUR_UPSTREAM_NAME"です。名前の一部については、何も書いていませんでした。
- 不具合:
- 影響の範囲:
upstream {}ブロックの名前の一部を渡すスクリプトと、先に定義された別のupstreamの名前の先頭と同じ名前のupstreamを指すスクリプトです。 - 必要な対応:
upstream {}ブロックの名前全体を渡してください。
7. Nginx::Upstream#server=とNginx::Stream::Connection#upstream_server=は、zoneのあるupstreamに例外を発生させます(#652)
Section titled “7. Nginx::Upstream#server=とNginx::Stream::Connection#upstream_server=は、zoneのあるupstreamに例外を発生させます(#652)”- 変更前:
zoneのあるupstream {}ブロックでは、2つのsetterは、zoneの共有メモリにあるpeerを、呼んだworkerのメモリを指すように変えました。worker_processes 1でworkerが1つのときは、この変更は動きました。workerが2つ以上のときは、ほかのworkerではこの変更は正しくありませんでした。このことは、2.7.2のリリースノートのNginx::Upstream#server=の項目の「このリリースで変わらない挙動」に書いてあります。 - 変更後:2つのsetterは、何かを変える前に
RuntimeErrorを発生させます。メッセージは... does not support an upstream with a zoneです。worker_processes 1でも同じです。 - 変更の理由:
- 不具合:setterは、serverの名前とアドレスを1つのworkerのメモリに置き、共有のpeerをそこへ向けます。そのメモリを読めるのは、そのworkerだけです。オーナーは2026-10-29に、2.7.3でzoneのあるupstreamを拒むと決めました。選んだ選択肢は「2.7.3で例外にする (推奨)」で、nextの構想
docs/proposals/upstream-server-setters.mdの問い1への答えです。 - 前の挙動を残せなかった理由:すべてのworkerで正しい変更には、zoneのslabが要ります。また、serverに
resolveのパラメータがあると、nginx 1.27.3以降はnginx自身がzoneのupstreamのpeerを入れ替えます。その場で書き換える変更は、これと衝突します。2.xの系列でできる変更は、zoneの場合を拒むことです。 - ドキュメントが示していた挙動:2.7.2のリリースノートは、zoneの場合を対応しないものと書いていました。docs/class_
and_ は、setterがzoneを確かめないと書いていました。method/README.md
- 不具合:setterは、serverの名前とアドレスを1つのworkerのメモリに置き、共有のpeerをそこへ向けます。そのメモリを読めるのは、そのworkerだけです。オーナーは2026-10-29に、2.7.3でzoneのあるupstreamを拒むと決めました。選んだ選択肢は「2.7.3で例外にする (推奨)」で、nextの構想
- 影響の範囲:
zoneのあるupstreamブロックに、2つのsetterのどちらかを呼ぶスクリプトを持つ設定です。workerが1つの設定も含みます。 - 必要な対応:これらのsetterで変えるupstreamブロックから、
zoneを外してください。setterの変更は、どちらの場合も1つのworkerにだけ効きます。
ありません。
不具合の修正
Section titled “不具合の修正”Nginx::Stream::Async.sleepは、sleepが終わる前にstreamのセッションのプールが破棄されたときに、解放済みのconnectionのプールにタイマーを張ったまま残さなくなりました。sleepの途中でプールが破棄されるのは、workerの停止、設定のreload、接続のタイムアウトのときです。ngx_stream_mrb_async_sleepは、connectionのプールにあるngx_event_tのctx->sleepでタイマーを張りますが、そのプールにcleanupを登録していませんでした。そのため、残ったタイマーが発火すると、解放済みのプールを読みました。これはuse-after-freeです。sleepの最中にクライアントが接続を閉じても、この状態にはなりません。nginxは、ハンドラがresumeするまでその切断を保留するためです。ngx_stream_mrb_start_fiberは、connectionのプールにcleanupを登録するようにしました。このcleanupは、セッションが終わるときに、保留中のsleepのタイマーを消し、fiberの登録を解除します。HTTPのNginx::Async.sleepの経路は、既に同じ形で後始末しています。また、fiberのGCへの登録を、Nginx::Stream::Async.sleepの呼び出しごとでなく、fiberごとに1回にしました。前は、2回以上sleepしたfiberがGCのrootに残っていました。変更はsrc/streamだけで、ディレクティブ、Rubyのメソッド、ビルドのオプションは変わっていません(#645)。
ビルドとテストの変更
Section titled “ビルドとテストの変更”-
2.xの導入ガイドを、公開済みのv2.7.2タグと一通り実行できるループバックHTTPの例に更新しました。コンパイルと
make installを分け、設定したインストール先を一貫して使い、nginxの設定検査・reload・quitと静的ビルドの実際の検証条件を記載します。過去のDockerイメージは別に示します。実行時の挙動は変更しません。 -
Redis、ACMEのテストサーバーのPebble、dehydratedを使うauto-sslのmrbgemのテストを加えました。
test/t/cases/auto_ssl.rbは、sh test/auto_ssl/deps.sh start DIRが準備したディレクトリをNGX_MRUBY_TEST_AUTO_SSLが指すときに動き、CIはbuildのマトリクスのすべてのセルでこのテストを動かします。このテストは、テストのnginxの新しいポートの18171と18172と、このテストが起動する2つ目のnginxの18173と18174を使います。サービスは、6379、8053、8055、14000、14001、15000、15001で待ち受けます。詳しくは、docs/test/README.mdの「Testing the auto-ssl mrbgem」に書いてあります(#636)。 -
nginx 1.31.8と1.30.6:
build.shとtest.shが読むnginx_versionは、1.31.6の代わりにnginx 1.31.8を指します。CIは、1.31.6、1.30.5、1.28.3、1.26.3の代わりに、nginx 1.31.8、1.30.6、1.28.3、1.26.3のそれぞれで、ngx_mrubyを静的モジュールと動的モジュールとしてビルドします。AddressSanitizerとUndefinedBehaviorSanitizerを有効にしたビルドは、1.31.8を使います。nginx 1.31.7と1.30.6は、nginxのセキュリティアドバイザリがCVE-2026-15176、CVE-2026-93479、CVE-2026-95101、CVE-2026-97629、CVE-2026-97205として挙げる、nginxの脆弱性を修正しています。build.shがデフォルトでダウンロードするnginx 1.31.8には、これらの修正が入っています。NGINX_SRC_ENVでnginxのソースを指定するビルドは、指定したnginxを使います。これらのバージョンのために、ngx_mrubyのコードは変わっていません。nginx自身の変更は、nginxのCHANGESとCHANGES-1.30にあります。その中には、次の2つがあります。1.31.7のHTTP/2のモジュールは、RFC 9218の優先度の方式を使い、RFC 7540の優先度の指定を無視します。1.31.8は、working_directoryディレクティブのパスを、カレントディレクトリでなくnginxのprefixを基準に解決します(#638)。 -
サイトの2.xのページ:
masterへのすべてのプルリクエストで、新しいワークフローsite-v2.ymlが、そのプルリクエストのdocs/を/v2/に置いたnextのサイトをビルドし、2.xのページの壊れたリンクで失敗します。masterへのすべてのpushで、新しいワークフローsite-dispatch.ymlがnextのサイトのワークフローを起動します。どちらもci-okのneedsには入っていません。詳しくは、docs/test/README.mdの「The 2.x pages of the site」に書いてあります(#656)。
アップグレード
Section titled “アップグレード”サポートするnginxのバージョンは、README.mdに書いたとおり、nginxのmainlineとstableの系列です。CIは、nginx 1.31.8、1.30.6、1.28.3、1.26.3をビルドします。
- 追加、改名、削除したディレクティブはありません。このリリースで、モジュールをビルドし直してください。2.7.2をビルドしたときと同じ方法でビルドし、いつもどおりバイナリか動的モジュールを置き換えてください。
アップグレードの前に、次の点を確かめてください。各行は、上の各項目の「必要な対応」です。
Nginx::SSL::ACME::Dehydrated#auto_cert_deploy:新しいアカウントで証明書を取得するための対応は要りません。dehydrated --cronのエラーをerror.logで見るには、そのサーバーが使うerror_logのレベルをinfoにしてください。1回の呼び出しの中で登録をやり直すことに頼っていたコードでは、後のハンドシェイクでこのメソッドをもう一度呼んでください(項目1)。- contentハンドラの
Nginx::Async.sleep:sleepの後の応答を期待するコードでは、対応は要りません。sleepで応答なしにリクエストを終わらせることに頼っていたコードでは、代わりに、ステータス444を表すNginx::HTTP_CLOSEをNginx.returnで返してください。サブリクエストは、contentハンドラのsleepの後でなく、rewriteハンドラかaccessハンドラから送ってください(項目2)。 Nginx.redirectが実行するlocationの例外:例外をrescueしていなかったコードでは、対応は要りません。Nginx.redirectの周りでrescueしていたコードでは、rescueをそのlocationのコードの中へ移してください(項目3)。Nginx::Stream::Async.sleepの後のstreamのセッションの結果:クライアントを拒むためにNginx::Stream::ABORTを設定するコードでは、対応は要りません。sleepの後にNginx::Stream::OKを設定していたコードでは、セッションを先へ進めてはならない場合に、代わりにNginx::Stream::ABORTを設定してください(項目4)。- Stringでない引数を渡す
Nginx::UpstreamとNginx::Stream::Connection:to_sなどでStringを渡し、値が無い場合は呼び出しの前に処理してください(項目5)。 - 名前の一部を渡す
Nginx::Upstream.newとNginx::Stream::Connection.new:upstream {}ブロックの名前全体を渡してください(項目6)。 zoneのあるupstreamに使うupstreamのserverのsetter:これらのsetterで変えるupstreamブロックから、zoneを外してください(項目7)。