---
title: "ngx_mruby v2.7.3"
language: "ja"
documentation_version: "2.x"
channel: "maintenance"
release_context: {"latest_release_tag":"v2.7.2","unreleased_versions":["v2.7.3"],"latest_release_revision":"d5ae755471c48c6617cb23495e7b65d7c5241b45"}
source_revision: "967b61cc6a232aa1ad27dd452c8b5835f94c2a01"
working_tree_clean: true
source_path: "docs/releases/v2.7.3.ja.md"
source_sha256: "bb5ea8525216c11c9fba7feff2da81e2c2a72c878902ac5988cf5bb48f3990c7"
source_at_revision: true
source_url: "https://github.com/matsumotory/ngx_mruby/blob/967b61cc6a232aa1ad27dd452c8b5835f94c2a01/docs/releases/v2.7.3.ja.md"
html_url: "https://ngx.mruby.org/v2/ja/releases/v2.7.3/"
markdown_url: "https://ngx.mruby.org/v2/markdown/ja/releases/v2.7.3.md"
last_updated: "2026-10-30"
---

# ngx_mruby v2.7.3

## アップグレードの前に読む挙動の変更

### 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_method/README.md](/v2/ja/reference/ruby/)は、APIについて`mrbgems/auto-ssl/mrblib`のスクリプトを参照するよう書いており、dehydratedの出力については何も書いていません。`#auto_accept_terms`と、`#auto_cert_deploy`の中の判定は、最初の実行で利用規約に同意するためにあります。
- 影響の範囲：`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）

- 変更前：`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のリリースノートは、この件を[既知の制限](/v2/ja/releases/v2.7.1/#既知の制限)に挙げ、contentハンドラが待てるようにするリリースを予定していました。オーナーは2026-10-04に、contentハンドラで例外を発生させず、`Nginx::Async.sleep`を使えるようにすると決めました。
  - 前の挙動を残せなかった理由：sleepの後のコードを実行するには、sleepが終わるまでリクエストを残す必要があります。リクエストを残せば、そのリクエストには応答が返ります。不具合を除く別の方法は、logハンドラと同じく、contentハンドラで`RuntimeError`を発生させることでした。この方法では、今は例外なしにこのメソッドを呼んでいるコードが動かなくなります。
  - ドキュメントが示していた挙動：[docs/class_and_method/README.md](/v2/ja/reference/ruby/#nginxasyncsleepmsec)は、contentハンドラではこのメソッドが例外を発生させず、リクエストはsleepの時点で応答なしに終わると書いていました。また、このメソッドが動く場所として、set、rewrite、accessの各ハンドラを挙げていました。
- 影響の範囲：`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）

- 変更前：`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_method/README.md](/v2/ja/reference/ruby/#nginxredirecturl_or_internal_location-http_statusnginxhttp_moved_temporarily)は、リダイレクト先のlocationがクライアントにレスポンスを返すと書いていましたが、そこで発生する例外については何も書いていませんでした。
- 影響の範囲：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）

- 変更前：`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](https://github.com/matsumotory/ngx_mruby/pull/550)）。
  - 前の挙動を残せなかった理由：結果を捨てられたセッションは、終わることも先へ進むこともありません。共有の状態に残った例外は、その例外を発生させていないセッションを終わらせます。nginxのcheckerと同じように結果を処理して、初めてセッションが終わるか先へ進みます。
  - ドキュメントが示していた挙動：[docs/class_and_method/README.md](/v2/ja/reference/ruby/#nginxstreamasync-class)は、`Nginx::Stream::Async.sleep`をaccessフェーズで使えるメソッドとして、`Nginx::Stream::Connection.stream_status=`をクライアントを拒むために`Nginx::Stream::ABORT`を設定するメソッドとして書いていました。sleepの後の結果については、何も書いていませんでした。
- 影響の範囲：`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）

- 変更前：`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](/v2/ja/reference/ruby/#nginxupstreamnewstring---upstream)は、引数を文字列として書いていました。`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）

- 変更前：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）

- 変更前：`zone`のある`upstream {}`ブロックでは、2つのsetterは、zoneの共有メモリにあるpeerを、呼んだworkerのメモリを指すように変えました。`worker_processes 1`でworkerが1つのときは、この変更は動きました。workerが2つ以上のときは、ほかのworkerではこの変更は正しくありませんでした。このことは、[2.7.2のリリースノート](/v2/ja/releases/v2.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_method/README.md](/v2/ja/reference/ruby/#nginxupstreamserverstring)は、setterがzoneを確かめないと書いていました。
- 影響の範囲：`zone`のあるupstreamブロックに、2つのsetterのどちらかを呼ぶスクリプトを持つ設定です。workerが1つの設定も含みます。
- 必要な対応：これらのsetterで変えるupstreamブロックから、`zone`を外してください。setterの変更は、どちらの場合も1つのworkerにだけ効きます。

## 新機能

ありません。

## 不具合の修正

- `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](https://github.com/matsumotory/ngx_mruby/pull/645)）。

## ビルドとテストの変更

- 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](/v2/ja/contributing/test/)の「Testing the auto-ssl mrbgem」に書いてあります（[#636](https://github.com/matsumotory/ngx_mruby/pull/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のセキュリティアドバイザリ](https://nginx.org/en/security_advisories.html)が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](https://nginx.org/en/CHANGES)と[CHANGES-1.30](https://nginx.org/en/CHANGES-1.30)にあります。その中には、次の2つがあります。1.31.7のHTTP/2のモジュールは、RFC 9218の優先度の方式を使い、RFC 7540の優先度の指定を無視します。1.31.8は、`working_directory`ディレクティブのパスを、カレントディレクトリでなくnginxのprefixを基準に解決します（[#638](https://github.com/matsumotory/ngx_mruby/pull/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](/v2/ja/contributing/test/)の「The 2.x pages of the site」に書いてあります（[#656](https://github.com/matsumotory/ngx_mruby/pull/656)）。

## アップグレード

サポートするnginxのバージョンは、[README.md](https://github.com/matsumotory/ngx_mruby/blob/master/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）。
