---
title: "ngx_mruby v2.7.2"
language: "ja"
documentation_version: "3.x"
channel: "development"
release_context: null
source_revision: "10297d1e28653321d6996609fc53718d3b7c8278"
working_tree_clean: true
source_path: "docs/releases/v2.7.2.ja.md"
source_sha256: "5184e7247723b1ff8210f8bca3f4afd910c1bd46546f367c7855d9b726c8c58b"
source_at_revision: true
source_url: "https://github.com/matsumotory/ngx_mruby/blob/10297d1e28653321d6996609fc53718d3b7c8278/docs/releases/v2.7.2.ja.md"
html_url: "https://ngx.mruby.org/ja/releases/v2.7.2/"
markdown_url: "https://ngx.mruby.org/markdown/ja/releases/v2.7.2.md"
last_updated: "2026-10-29"
---

# ngx_mruby v2.7.2

ngx_mruby 2.7.2は、2.x系列のメンテナンスリリースです。

3件のセキュリティ問題を、それぞれ個別のアドバイザリとともに修正しました。`Nginx::Upstream#server=`とstreamの`Nginx::Stream::Connection#upstream_server=`のuse-after-free（[GHSA-22jc-w698-vfqw](https://github.com/matsumotory/ngx_mruby/security/advisories/GHSA-22jc-w698-vfqw)、High）、NULバイトを含む`Nginx::Var`の値から未初期化のワーカーメモリが漏れる問題（[GHSA-gxqc-3p4h-5p36](https://github.com/matsumotory/ngx_mruby/security/advisories/GHSA-gxqc-3p4h-5p36)、Medium）、出力を書くハンドラへのHEADリクエストがレスポンスボディを受け取る問題（[GHSA-8mxf-gvvp-38fq](https://github.com/matsumotory/ngx_mruby/security/advisories/GHSA-8mxf-gvvp-38fq)、Medium）です。詳しくは項目6、7、8に書きました。

修正の一部で、既存の設定、Rubyのスクリプト、ビルドから見える挙動が変わります。アップグレードの前に最初の節を読んでください。各項目では、2.7.1で見えた挙動を「変更前」に、2.7.2で見える挙動を「変更後」に書きました。挙動を変える必要があった理由は「変更の理由」に書き、「不具合」か「背景」、「前の挙動を残せなかった理由」、「ドキュメントが示していた挙動」の3行に分けました。続けて、影響を受ける設定と必要な対応を書きました。テストスイートが変更を試している項目には、そのテストのポートも書きました。「このリリースで変わらない挙動」には、関係する挙動のうち変わらないものを書きました。

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

### 1. 変数を指さないメソッド名や値の無いセッターでNginx::Varを呼ぶと、例外が発生します（#589）

- 変更前：`Nginx::Var`は、自分が定義していないメソッドの呼び出しを、同じ名前のnginxの変数へのアクセスとして扱います。たとえば`v.http_host`は`$http_host`を読みます。名前が`=`で終わるメソッドは、`=`の前の名前の変数に値を設定するセッターとして扱います。たとえば`v.http_host = "a"`は`$http_host`に値を設定します。実行時に名前を組み立てるコードは、名前を`send`に渡します。次の3種類の名前では、アクセサが名前か引数の外のメモリを読んだので、結果はそのメモリの内容で決まりました。
  - `v.send("")`のように空の名前で呼ぶと、アクセサは名前の前の1バイトを読みました。結果は、たいてい`nil`でした。
  - `v.send("=", "x")`のように`=`だけでできた名前で呼ぶと、アクセサは名前の終わりより先を読みました。`nil`が返ることも、ワーカープロセスが終了することもありました。
  - `v.send("http_host=")`のように値を渡さずにセッターを呼ぶと、アクセサは引数の外から値を読みました。ワーカープロセスが終了することも、コードが渡していない値が変数に設定されることもありました。
- 変更後：空の名前と、`=`だけでできた名前では、`NoMethodError`が発生します。メッセージは、`undefined method '' for Nginx::Var`と`undefined method '=' for Nginx::Var`です。値を渡さずにセッターを呼ぶと、`ArgumentError`が発生し、変数は元の値のままです。メッセージは`wrong number of arguments (given 0, expected 1) for Nginx::Var#http_host=`です。アクセサは、名前と引数を読む前に、それらを確かめます。
- 変更の理由：
  - 不具合：「変更前」に書いたとおり、アクセサは、空の名前では名前の前の1バイトを読みました。`=`だけでできた名前では、名前のコピーの終わりより先まで`strtok`で読みました。値の無いセッターでは、空の引数の並びから最初の要素を読みました（[#589](https://github.com/matsumotory/ngx_mruby/pull/589)）。
  - 前の挙動を残せなかった理由：これらの呼び出しの結果は、名前と引数の外のメモリから来ていました。そのため、同じ呼び出しでも、`nil`が返ることも、ワーカープロセスが終了することもありました。このような名前はどの変数も指さず、引数の無いセッターには設定する値がありません。そのため、残せる結果がありません。代わりに、Rubyと同じ例外が発生します。受け手に無いメソッドを呼んだときの`NoMethodError`と、メソッドが取る数より少ない引数で呼んだときの`ArgumentError`です。
  - ドキュメントが示していた挙動：2.7.1のドキュメントには、アクセサが`Nginx::Var#VARNAME`と`Nginx::Var#VARNAME=`で呼ばれると書いてあります。名前が`=`で終わるときは、「the 2nd arg」を変数に設定するとも書いてあります（[2.7.1のNginx::Var#method_missing](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/class_and_method/README.md#nginxvarmethod_missingsymbol--args---result)）。変数の名前を含まない名前と、値の無いセッターについては、何も書いてありません。
- 影響の範囲：`send`、`public_send`、`method_missing`に実行時に組み立てた名前を渡して`Nginx::Var`を呼ぶコードのうち、名前が空か`=`だけになりうるものです。値を渡さずにセッターを呼ぶコードも、影響を受けます。
- 必要な対応：呼び出しの前に名前を確かめるか、名前が空になりうるところで`NoMethodError`をrescueしてください。セッターには、必ず値を渡してください。
- このリリースで変わらない挙動：アクセサが読み書きする変数は、前と同じです。大文字を含む名前も、前と同じく、小文字にした名前の変数を指します。2つ以上の値を渡したセッターは、これまでどおり最初の値を使います。`=foo=`のような名前も、これまでどおり`$foo`に値を設定します。
- テスト：`test/t/cases/var_input.rb`のポート18153のテストです。

### 2. Nginx::Var#setは、名前として渡したStringとSymbolを変えなくなりました（#589）

- 変更前：`Nginx::Var#set(name, value)`は、小文字にした名前で変数を探しました。その名前を得るために、`name`のバイトをその場で小文字に書き換えました。そのため、呼び出しの後、`name`に渡したStringは小文字になっていました。freezeしたStringでも同じでした。バイトを共有するStringも、小文字になりました。たとえば、長いStringから`dup`で作ったコピーです。長い名前のSymbolでは、`set`は、ワーカーがそのSymbolのために持つ名前を小文字に書き換えました。そのため、そのワーカーの後のどのリクエストでも、そのSymbolの`to_s`は小文字の名前を返しました。ここで長いとは、64ビットのビルドで27バイトを超えることです。mrubyは、それより短いStringをStringのオブジェクトの中に持ち、コピーはそのバイトを共有しません。
- 変更後：`set`は、名前を小文字にしたコピーで変数を探します。`name`に渡したStringとSymbolは、元のままです。`set`が値を設定する変数と、`set`が返す値は、前と同じです。
- 変更の理由：
  - 不具合：「変更前」に書いたとおり、`set`は、`name`に渡したStringに書き込みました。その結果、freezeしたString、バイトを共有するString、Symbolの名前まで書き換わりました（[#589](https://github.com/matsumotory/ngx_mruby/pull/589)）。
  - 前の挙動を残せなかった理由：freezeしたStringは、変更できないはずのオブジェクトです。バイトを共有するStringは、コードが持つ別のオブジェクトです。Symbolの名前は、ワーカーのすべてのリクエストが使います。変数を探すには小文字の名前が要るので、`set`はコピーを小文字にします。
  - ドキュメントが示していた挙動：2.7.1のドキュメントには、`set`はnginxの変数に値を設定すると書いてあります（[2.7.1のNginx::Var#set](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/class_and_method/README.md#nginxvarsetname-value)）。`set`が引数を変えるとは、書いてありません。
- 影響の範囲：`set`に渡したStringかSymbolを呼び出しの後で使い、それが小文字になっていると期待するコードです。
- 必要な対応：`set`が名前を小文字にすることに頼っていたコードでなければ、対応は要りません。そのようなコードでは、自分で名前に`downcase`を呼んでください。
- テスト：`test/t/cases/var_input.rb`のポート18153のテストです。

### 3. Redisがデフォルトのビルドに戻り、mruby-redisはどのプラットフォームでもhiredisのv1.5.0をビルドし、hiredisの警告では止まらなくなりました（#604、matsumotory/mruby-redis#108）

- 変更前：[2.7.1のリリースノート](/ja/releases/v2.7.1/#1-redisはデフォルトのビルドに含まれなくなりました)の項目1に書いたとおり、2.7.1のデフォルトの`build_config.rb`には`matsumotory/mruby-redis`が入っていません。そのため、この`build_config.rb`でビルドしたバイナリには`Redis`クラスが無く、`Redis`を参照するコードでは`NameError`が発生します。そのメッセージは「uninitialized constant Redis」です。`conf.gem github: 'matsumotory/mruby-redis'`のコメントアウトを外すか、独自の`build_config.rb`でこのgemを有効にしたビルドは、gemのビルドスクリプトからhiredisを得ます。gemのビルドディレクトリに`hiredis`ディレクトリがまだ無いとき、このスクリプトはhiredisをデフォルトブランチの先頭からcloneし、macOSではv0.13.3をcheckoutしました。スクリプトはmrubyのビルドのCのフラグでhiredisをコンパイルし、hiredisの1.2.0以降のMakefileは`-Werror`を加えます。そのため、hiredisに新しい警告が出ると、ビルドが止まりました。hiredisの1.4.0以降とmrubyのgccのツールチェーンを組み合わせると、ビルドは`ffc.h:330:5: error: "FFC_DEBUG" is not defined, evaluates to 0 [-Werror=undef]`で止まりました。
- 変更後：デフォルトの`build_config.rb`に、`matsumotory/mruby-redis`が戻りました（[#604](https://github.com/matsumotory/ngx_mruby/pull/604)）。2.7.0以前のデフォルトの`build_config.rb`と同じです。この`build_config.rb`でビルドしたバイナリには`Redis`クラスがあり、そのビルドはgemとhiredisをGitHubからcloneします。gemのビルドスクリプトは、macOSを含むすべてのプラットフォームで、hiredisのタグ`v1.5.0`を`hiredis-v1.5.0`にcloneします。cloneしたツリーが、このタグのコミット`5bb7f030f6631b243438cf0fdd3be84352b905bb`でないときは、両方のコミットを示すメッセージを出してビルドを止めます。スクリプトはhiredisを`USE_WERROR=0`でビルドするので、ビルドはhiredisの警告を表示して先へ進みます。また、スクリプトはhiredisを`make install`だけでビルドするので、`hiredis-test`をビルドしなくなりました。Alpineのようなmuslのシステムでは、`linux-headers`が要らなくなりました。前のビルドが残した`hiredis`ディレクトリは使いません。hiredisの変更は、gemの変更です。ngx_mrubyの2.xはgemのlockをコミットしていないので、ビルドはgemを`master`ブランチの先頭からcloneします。[matsumotory/mruby-redis#108](https://github.com/matsumotory/mruby-redis/pull/108)のマージの後にgemをcloneするビルドは、すべてこの変更を受けます。2.7.1のビルドも同じです。mrubyは、前にcloneしたgemを更新しません。また、mrubyは、`build_config.rb.lock`が記録したコミットでgemをcheckoutします。このファイルは、mrubyがビルドの終わりに書き、2.xではgitが無視するファイルです。そのため、マージの前にmruby-redisをビルドしたソースツリーは、mrubyのビルドディレクトリ`mruby/build`と`build_config.rb.lock`の両方を消すまで、前のコミットのgemとそのhiredisを使い続けます。
- 変更の理由：
  - 不具合：バージョンを固定しないcloneのため、hiredisの新しいコミットは、gemを有効にしたすべての新しいビルドに届きました。hiredisの1.4.0は、上の`FFC_DEBUG`のエラーで、そのようなビルドを止めました（[#528](https://github.com/matsumotory/ngx_mruby/pull/528)、[matsumotory/mruby-redis#108](https://github.com/matsumotory/mruby-redis/pull/108)）。2.7.1は、この不具合のために、gemをデフォルトのビルドから外しました。2.7.1のリリースノートの項目1に、そう書いてあります。
  - 前の挙動を残せなかった理由：hiredisのデフォルトブランチに追従するビルドは、hiredisのどのコミットでも壊れる可能性があります。2.7.1がgemをデフォルトのビルドから外したのは、このためです。リリースを固定してそのコミットを確かめると、どのビルドも同じhiredisを使います。`-Werror`も外す必要がありました。ngx_mrubyのsanitizerのフラグ`-fsanitize=address,undefined -fno-omit-frame-pointer`を付けると、gccの13から15では、hiredisのv1.5.0のビルドが`sds.c:525`の`null format string [-Werror=format-truncation=]`で止まります。そのため、`-Werror`を残すと、新しいコンパイラーでは、固定したビルドも壊れます。`USE_WERROR=0`は、`-Werror`を使わないビルドのために、hiredisの1.3.0以降が用意したスイッチです（[redis/hiredis#1210](https://github.com/redis/hiredis/pull/1210)）。v1.5.0は、LinuxとmacOSでビルドでき、gemのテストに通ります。そのため、macOSでもv0.13.3は要らなくなりました。hiredisを固定したので、gemをデフォルトのビルドから外しておく理由は無くなりました。2.7.1のリリースノートの項目1は、「mruby-redisがhiredisを固定するまで」gemをデフォルトの一覧に残せないとしていました。gemを外したままにすると、2.7.0以前のデフォルトのビルドで`Redis`を使っていたスクリプトは、`Redis`を使えないままです。READMEと`docs/use_case`の例と、同梱の`auto-ssl` mrbgemのクライアントも、これに当たります。
  - ドキュメントが示していた挙動：2.7.1のリリースノートの項目1には、「#528に書いたhiredisの問題が残っている間は、このgemのビルドが失敗することがあります」と書いてあります。2.7.1のドキュメントには、`build_config.rb`でこのgemの行のコメントアウトを外すと書いてあります（[2.7.1のbuilt-in mrbgems](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/class_and_method/README.md#built-in-mrbgems)）。このgemのビルドについては、「clones the hiredis library from an unpinned branch, which broke the build of ngx_mruby」と書いてあります（[2.7.1のdocs/install](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/install/README.md#2-configuring-mrbgems)）。hiredisのバージョンは書いてありません。また、`Redis`クラスについては、「is not in the default build since 2.7.1」と書いてあります。2.7.0のドキュメントでは、mruby-redisが「the default mrbgems」の1つに入っていました（[2.7.0のdocs/install](https://github.com/matsumotory/ngx_mruby/blob/v2.7.0/docs/install/README.md#2-configuring-mrbgems)）。また、`Redis`は「with default build configuration」で使えるクラスの1つに入っていました（[2.7.0のbuilt-in mrbgems](https://github.com/matsumotory/ngx_mruby/blob/v2.7.0/docs/class_and_method/README.md#built-in-mrbgems)）。
- 影響の範囲：2.7.2のデフォルトのビルドです。そのバイナリには`Redis`クラスが戻るので、`Redis`を参照するコードで`NameError`が発生しなくなります。そのビルドは、GitHubから3つのリポジトリを余分にcloneします。それぞれの`master`ブランチの先頭のmatsumotory/mruby-redisとmatsumotory/mruby-pointerと、hiredisのタグ`v1.5.0`です。mruby-pointerは、mruby-redisが依存するgemで、クラスを加えません。そして、ビルドはhiredisをnginxか動的モジュールに静的にリンクするので、そのサイズが約190 KB増えます。たとえば、`next`の性能の比較は、`test/build_release.sh`でリリースのオプションのnginxをビルドします。そのオプションでビルドしてstripした静的なnginxは、Linux aarch64とgcc 11.4で、3.88 MBから4.08 MBになります。Redisのサーバーが要るのは、`Redis`を呼ぶスクリプトだけです。hiredisの変更が影響するのは、2.7.2のデフォルトのビルド、mruby-redisを有効にした2.7.1のビルド、2.7.0以前のデフォルトのビルドです。2.7.0以前のデフォルトの`build_config.rb`には、このgemが入っています。これらのビルドは、マージの後にgemをcloneすると、hiredisのデフォルトブランチの先頭の代わりに、macOSではv0.13.3の代わりに、hiredisのv1.5.0を使います。mruby-redis#108は、gemをmruby 3.3.0とmruby 4.0.0で試しました。2.6.0から2.7.2はmruby 3.3.0を同梱し、2.5.0以前はそれより古いmrubyを同梱します。gemはhiredisを静的にリンクするので、前にビルドしたバイナリは、`mruby/build`と`build_config.rb.lock`を消してビルドし直すまで前のhiredisのままです。2.7.1のデフォルトの`build_config.rb`でのビルドは、このgemを含まないので、影響を受けません。
- 必要な対応：2.7.2のデフォルトのビルドでは、対応は要りません。ただし、前にこのソースツリーでmruby-redisをビルドしたときは、先に`mruby/build`と`build_config.rb.lock`を消してください。mruby-redisを有効にしたビルドと、2.7.0以前のデフォルトのビルドが、これに当たります。`mruby/build`だけを消しても足りません。次のビルドはgemをcloneし直しますが、lockが記録したコミットでcheckoutするので、hiredisはバージョンを固定しないcloneのままです。そのうえで、これまでどおりビルドしてください。2.7.1のリリースノートの項目1の注意「このgemのビルドが失敗することがあります」は、もう当てはまりません。2.7.1のデフォルトのビルドと同じくgemを入れずにビルドするには、`build_config.rb`の`conf.gem github: 'matsumotory/mruby-redis'`をコメントアウトしてください。ほかのhiredisのリリースでビルドするには、ビルドのときに環境変数`MRUBY_REDIS_HIREDIS_VERSION`にそのタグを設定するか、`build_config.rb`で`ENV['MRUBY_REDIS_HIREDIS_VERSION']`を設定してください。設定の方法は、[mruby-redisのREADME](https://github.com/matsumotory/mruby-redis#readme)に書いてあります。ほかのタグでは、ビルドはコミットを確かめません。mruby-redis#108では、前の先頭のコミット2cc63c1で、gccとmruby 3.3.0を使い、`v1.4.1`でもビルドとテストをしました。
- このリリースで変わらない挙動：2.7.0と、gemを有効にした2.7.1のビルドと同じく、`Redis`の呼び出しは、Redisのサーバーが答えるまでワーカーの中で待ちます。gemがhiredisのブロックするAPIを使うためです。その間、ワーカーはほかのリクエストを処理しません。mruby-redis#108は、mruby 4.0.0のために`Redis#ttl`の宣言を変えました。同梱のmruby 3.3.0では、`Redis#ttl`は前と同じに動きます。
- テスト：ngx_mrubyのテストスイートには、Redisを呼ぶテストがありません。また、テストスイートはRedisのサーバーを起動しません。#604から、テストスイートのデフォルトのビルドはこのgemをビルドします。そのため、sanitizerのセルを含むCIのすべてのセルが、gemをビルドしてhiredisをリンクし、テストのnginxには`Redis`クラスがあります。#604には、`master`でのテストスイートの結果が載っています。静的なモジュール、動的なモジュール、sanitizerのそれぞれの結果です。また、nginxのバイナリにあるgemとhiredisのシンボルと、Redisを呼ぶリクエストの結果も載っています。リクエストの結果は、Redisのサーバーが動いていないときと動いているときの両方です。mruby-redis#108には、ngx_mrubyの`master`（03aad7031）と`next`（75bfa6ff4）のテストスイートの結果が載っています。どちらも、そのプルリクエストの先頭のコミットでgemを有効にしてビルドし、テストに通りました。gemのCIは、Ubuntu 22.04とUbuntu 24.04でgemのテストを流します。Ubuntu 24.04では、ngx_mrubyのCIのsanitizerの設定を使います。

### 4. Redis.connect_set_udptrと引数の無いRedis.newは、接続をmrb->udでなくmrb_stateに持つようになり、引数が1つのRedis.newはArgumentErrorを発生させます（matsumotory/mruby-redis#109）

- 変更前：mruby-redisを含むビルドでは、`Redis.connect_set_udptr host, port[, timeout]`がサーバーに接続し、その接続をmatsumotory/mruby-pointerに預けました。このメソッドには`Redis.connect_set_raw`という別名もあります。mruby-pointerは、自分の構造体を`mrb_state`のユーザーデータのポインタ`mrb->ud`に置きます。引数の無い`Redis.new`は、そこから接続を読みました。一方、ngx_mrubyも自分のデータを`mrb->ud`に置きます。streamのモジュールは、`stream {}`ブロックのどのコードが動く間も、`mrb->ud`に自分のコンテキストを置きます。HTTPのモジュールは、`mruby_server_context_handler_code`、`mruby_ssl_handshake_handler`、`mruby_ssl_handshake_handler_code`のコードが動く間、`mrb->ud`にサーバーの設定を置きます。これらのコードでは、2つのメソッドは接続をngx_mrubyのデータに書き込み、そこから読みました。`http {}`ブロックのほかのコードでは、`mrb->ud`はNULLです。そこでは、`Redis.connect_set_udptr`は接続した後に`RuntimeError`を発生させ、その接続を開いたまま残しました。メッセージは「can not set, mrb ud pointer was not initialized」です。引数の無い`Redis.new`も`RuntimeError`を発生させました。メッセージは「can not get. mrb ud pointer was not initialized」です。接続を預けた後は、引数の無い`Redis.new`を呼ぶたびに、同じ接続を持つオブジェクトが返りました。これらのオブジェクトは、GCで回収されるときに、それぞれその接続を閉じました。引数が1つの`Redis.new`は、その呼び出しが設定していない値を使いました。新しいオブジェクトでは、その値はNULLのポインタでした。そのため、ワーカープロセスが終了しました。
- 変更後：gemは、接続を`mrb_state`の中に持ちます。置き場は`Redis`クラスのインスタンス変数で、Rubyのコードからは読めません。gemは`mrb->ud`を読み書きしなくなりました。2つのメソッドは、`http {}`ブロックでも`stream {}`ブロックでも、どのコードでも同じに動きます。`Redis.connect_set_udptr`は接続を持っておきます。接続に成功したときは、前に持っておいた接続のうち、`Redis.new`がまだ受け取っていないものを閉じます。`Redis.connect_set_udptr`は、接続に失敗すると、前と同じく`Redis::ConnectionError`を発生させます。このとき、失敗した接続を閉じるようになりました。前に持っておいた接続は、そのまま残ります。引数の無い`Redis.new`は、持っておいた接続を受け取ります。接続を持つオブジェクトは1つだけになります。そのため、引数の無い`Redis.new`を2回目に呼ぶと、`Redis.connect_set_udptr`をもう一度呼ぶまで例外が発生します。持っておいた接続が無いときも同じです。この例外は`RuntimeError`のサブクラスの`Redis::ConnectionError`で、メッセージは「no connection kept: call Redis.connect_set_udptr first」です。引数が1つの`Redis.new`は`ArgumentError`を発生させます。メッセージは「wrong number of arguments (given 1, expected 0, 2 or 3)」です。この変更は、gemの変更です。ngx_mrubyの2.xはgemのlockをコミットしていないので、[matsumotory/mruby-redis#109](https://github.com/matsumotory/mruby-redis/pull/109)のマージの後にgemをcloneするビルドは、すべてこの変更を受けます。2.7.1のビルドも同じです。前にこのgemをビルドしたソースツリーは、`mruby/build`と`build_config.rb.lock`を消すまで、前にcloneしたgemを使い続けます。項目3も参照してください。
- 変更の理由：
  - 不具合：mrubyは、`mrb->ud`を、mrubyを組み込むアプリケーションに任せています。ngx_mrubyは、`mrb->ud`を使います。2つのメソッドは、上に挙げたコードでngx_mrubyのデータをmruby-pointerの構造体として使い、`http {}`ブロックのほかのコードでは例外を発生させました。引数が1つの`Redis.new`は、その呼び出しが設定していない値を使いました（[matsumotory/mruby-redis#109](https://github.com/matsumotory/mruby-redis/pull/109)）。
  - 前の挙動を残せなかった理由：`mrb->ud`には、gemのデータを置く場所がありません。gemが接続を`mrb->ud`に置く限り、2つのメソッドは次のどちらかになります。ngx_mrubyが`mrb->ud`をNULLにしているところでは、例外を発生させます。そうでないところでは、ngx_mrubyのデータを使います。また、1つの接続を複数のオブジェクトに渡すと、オブジェクトはそれぞれその接続を閉じます。そのため、同じ接続が2回以上閉じられます。接続を持つオブジェクトを1つにすると、接続は1回だけ閉じられます。引数が1つの呼び出しには、接続するホストとポートがそろっていません。前の結果は、その呼び出しが設定していないポインタから来ていたので、残せる結果がありません。そのため、引数の数が合わないときにRubyが発生させる`ArgumentError`を発生させます。
  - ドキュメントが示していた挙動：この変更の前は、mruby-redisのREADMEにも、ngx_mrubyのドキュメントにも、2つのメソッドの説明はありませんでした。[mruby-pointer](https://github.com/matsumotory/mruby-pointer#readme)のREADMEには、2つのメソッドを使うCのプログラムの例があります。この例は、`mrb_udptr_init`と`mrb_udptr_copy`を呼んで、2つの`mrb_state`の間で接続を共有します。ngx_mrubyは、どちらの関数も呼びません。
- 影響の範囲：`Redis.connect_set_udptr`か`Redis.connect_set_raw`を呼ぶスクリプトと、引数を付けずに、または引数を1つだけ付けて`Redis.new`を呼ぶスクリプトです。対象のビルドは、mruby-redisがデフォルトのビルドに戻った2.7.2のデフォルトのビルドと、このgemを含むほかのビルドです。ほかのビルドは、gemを有効にした2.7.1のビルドと、2.7.0以前のデフォルトのビルドです。デフォルトのビルドに戻ったことは、項目3に書いてあります。すべての`Redis`のオブジェクトを`Redis.new host, port[, timeout]`で作るスクリプトは、影響を受けません。
- 必要な対応：引数の無い`Redis.new`を2回目以降に呼ぶ前には、`Redis.connect_set_udptr`をもう一度呼んでください。または、`Redis.new`が返したオブジェクトを持っておいて使い回すか、代わりに`Redis.new host, port`を呼んでください。`Redis::ConnectionError`は`RuntimeError`のサブクラスです。そのため、引数の無い`Redis.new`の`RuntimeError`をrescueしていたコードは、新しい例外もそのままrescueします。例外のクラスを`RuntimeError`と完全に一致させて比べるコードと、メッセージを比べるコードだけを直してください。`Redis.connect_set_udptr`は、ワーカーで動くコードで呼んでください。`mruby_init_worker_code`、`mruby_stream_init_worker_code`、ハンドラーがこれに当たります。`mruby_init_code`、`mruby_server_context_handler_code`、`mruby_stream_init_code`、`mruby_stream_server_context_code`で持っておいた接続は、すべてのワーカーにコピーされます。前にこのソースツリーでmruby-redisをビルドしたときは、先に`mruby/build`と`build_config.rb.lock`を消してください。
- このリリースで変わらない挙動：`Redis.new host, port[, timeout]`と、`Redis`のほかのメソッドは、前と同じに動きます。mruby-redisのビルドは、gemがもう呼ばないmatsumotory/mruby-pointerを、これまでどおりcloneします。
- テスト：ngx_mrubyのテストスイートには、Redisを使うテストがありません。mruby-redis#109は、`Redis.connect_set_udptr`、`Redis.connect_set_raw`、引数の無い`Redis.new`、引数が1つの`Redis.new`のテストをgemに加えます。その中には、ホストのアプリケーションと同じ方法で`mrb->ud`を設定したテストがあります。また、そのプルリクエストには、ngx_mrubyの`master`と`next`のテストスイートの結果が載っています。どちらも、そのプルリクエストの先頭のコミットでgemを有効にしてビルドし、テストに通りました。

### 5. 出力を書いてから300未満のステータスを設定したcontentハンドラは、そのステータスで応答します（#594）

- 変更前：メインリクエストの`mruby_content_handler*`が、`Nginx.rputs`か`Nginx.echo`で出力を書いてから、`Nginx.return`、`Nginx.send_header`、`Nginx.status_code=`のどれかで、200、201、204以外の300未満のステータスか0を設定すると、レスポンスが送られませんでした。300未満のステータスの例は`Nginx::HTTP_ACCEPTED`です。nginxは、HTTP/1.0の接続を1バイトも送らずに閉じ、HTTP/1.1のkeep-aliveの接続を、`keepalive_timeout`までレスポンスなしで開いたままにしました。`keepalive_timeout`のデフォルトは75秒です。そのため、クライアントは自分のタイムアウトまで待ちました。2.7.1では、この件を「既知の制限」の節に書いていました。
- 変更後：そのようなリクエストには、ハンドラが設定したステータスで出力が返ります。`Content-Length`は出力の長さです。これは、nginx自身の`return 202 "text";`の応答から`Content-Type`を除いたものと同じです。ngx_mrubyは、出力の後の200と同じく、`Content-Type`を付けません。`Nginx::Async::HTTP.sub_request`が再開した後にこのように終わった場合も、同じです。たとえば、`Nginx.rputs "queued"`の後に`Nginx.return Nginx::HTTP_ACCEPTED`を呼ぶと、ボディが`queued`の`202 Accepted`が返ります。200未満のステータスも同じように送られ、ステータス行は理由句の無い数字だけになります。たとえば、100のステータス行は`HTTP/1.1 100 `です。nginx自身の`return 100 "text";`も、同じように送ります。ただし、HTTPでは、このようなステータスを最終のレスポンスに使えません。`Nginx.return 0`はステータスを設定しないので、ステータスの無い出力と同じく、出力は200で返ります。keep-aliveの接続は、出力のある200の後と同じく、次のリクエストのために開いたままです。
- 変更の理由：
  - 不具合：出力の後の`Nginx.return`は出力の終わりに印を付け、ngx_mrubyは、出力を送らずにステータスをnginxに渡していました。nginxは、201、204、300以上のステータスには、自分でレスポンスを送ります。しかし、それ以外の300未満のステータスでは、何も送らずにリクエストを終えます。そのため、リクエストにレスポンスがありませんでした。[#566](https://github.com/matsumotory/ngx_mruby/pull/566)は出力の無いハンドラについてこれを直し、その[レビュー](https://github.com/matsumotory/ngx_mruby/pull/566#issuecomment-5977670159)が記録したとおり、この場合は残していました。このリリースで、この場合を直しました（[#594](https://github.com/matsumotory/ngx_mruby/pull/594)）。
  - 前の挙動を残せなかった理由：残すと、これらのリクエストはレスポンスの無いままになります。出力を書いてから200を設定するcontentハンドラは、既に出力で応答しています。この修正は、nginxが自分で応答しない残りのステータスにも、同じように、ハンドラが設定したステータスで応答します。2.7.1でレスポンスを受け取っていたリクエストが、違うレスポンスを受け取ることはありません。
  - ドキュメントが示していた挙動：2.7.1のドキュメントには、`Nginx.rputs`は「Puts response text」、`Nginx.return`は「Returns nginx status code to send back HTTP status to the client」と書いてあります（[2.7.1のNginx.return](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/class_and_method/README.md#nginxreturnnginx_status_code)）。2.7.1のリリースノートは、この場合を「既知の制限」の節に書いていました（[v2.7.1.ja.md](/ja/releases/v2.7.1/#既知の制限)）。メソッドのドキュメントには、出力の後にこのようなステータスを設定したcontentハンドラが何を返すかは、書いてありませんでした。
- 影響の範囲：出力を書いてから、200、201、204以外の300未満のステータスか0を設定する`mruby_content_handler*`のハンドラです。
- 必要な対応：出力をそのステータスで返すつもりのハンドラには、対応は要りません。200で応答すべきハンドラは、`Nginx::HTTP_OK`を設定してください。
- このリリースで変わらない挙動：出力の後の`Nginx::HTTP_OK`は、2.7.1と同じく、出力を200で返します。出力の後の`Nginx::HTTP_CREATED`、`Nginx::HTTP_NO_CONTENT`、300以上のステータスには、2.7.1と同じく、出力の無い、nginx自身のそのステータスの応答が返ります。最初の`Nginx.rputs`か`Nginx.echo`より前に設定したステータスは、これまでどおり出力に使われず、出力は200で返ります。ほかのフェーズのハンドラも変わりません。出力を書いてから`Nginx::HTTP_ACCEPTED`を設定するrewriteハンドラは、これまでどおりレスポンスを送りません。サブリクエストも変わりません。サブリクエストはレスポンスのボディなしで終わり、親のリクエストが応答します。
- テスト：`test/conf/conf.d/68-output-then-status.conf`と`test/t/cases/output_then_status.rb`の、ポート18154のテストです。

### 6. Nginx::Varは値の全体をコピーするので、NULバイトを含む値でもワーカーの未初期化メモリが漏れなくなりました

- 変更前：`Nginx::Var`のアクセサ（`v.name = value`）と`Nginx::Var#set(name, value)`は、値を`ngx_cpystrn`でリクエストのプールのバッファにコピーしました。`ngx_cpystrn`は、元の値の最初のNULバイトでコピーを止めます。しかし、変数に記録する長さ（`vv->len`）は、mrubyの文字列の全長でした。mrubyの文字列はバイト列で、NULを含むことがあります。リクエストボディやデコードした引数のような、クライアント由来の値がこれに当たります。そのため、NULより後ろのバイトは書かれないまま残り、変数の長さはバッファの全体を指しました。変数を読み戻すと、`vv->len`バイトが返ります。読み戻すのは、`Nginx::Var#name`を呼ぶときや、nginxが`$name`を出力するときです。そのため、NULより後ろの手つかずのバイトは、リクエストのプールにある未初期化のメモリから来ました。さらに、その変数をクライアントへ出すと、未初期化のバイトがワーカーの外へ出ました。クライアントへ出すのは、変数の`Nginx.rputs`や`Nginx.echo`、`add_header $name`、proxyするリクエストに入れた値、ログに出した値のときです。既にリクエストを処理したワーカーでは、そのメモリは、前のデータの断片やヒープのポインタを持つことがありました。
- 変更後：値の全長を`ngx_memcpy`でコピーします。そのため、NULを含む値もバイト単位でそのまま保存され、NULより後ろのメモリは露出しません。アクセサ（`ngx_mrb_var_set_vector`）と`Nginx::Var#set`（`ngx_mrb_var_set_func`）の両方を直しました。
- 変更の理由：
  - 不具合：「変更前」に書いたとおり、`ngx_cpystrn`は最初のNULでコピーを止める一方、変数は全長を持ちました。そのため、NULを含む値で設定した変数は、NULより後ろの、リクエストのプールにある未初期化のメモリを読みました。
  - 前の挙動を残せなかった理由：NULより後ろのバイトは書かれていないので、読み戻す値は、設定した値ではありませんでした。また、使い回したワーカーでは、前のデータの断片やヒープのポインタをクライアントへ運ぶことがありました。NULを含む値はそのまま保存する必要があり、その後ろのメモリを読んでも、残せる結果はありません。
  - ドキュメントが示していた挙動：2.7.1のドキュメントには、アクセサと`set`はnginxの変数に値を設定すると書いてあります（[2.7.1のNginx::Var#method_missing](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/class_and_method/README.md#nginxvarmethod_missingsymbol--args---result)、[2.7.1のNginx::Var#set](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/class_and_method/README.md#nginxvarsetname-value)）。値がNULバイトで切れることや、変数を読み戻すとほかのメモリが返ることは、書いてありません。
- 影響の範囲：クライアント由来でNULバイトを含みうる値を、`Nginx::Var#set`か`v.name =`のアクセサでnginxの変数に入れ、その変数をクライアントへ出す設定です。
- 必要な対応：対応は要りません。値は全長で保存されます。アップグレードできない旧版では、値を保存する前にNULバイトを除いてください。たとえば`value.delete("\x00")`です。
- このリリースで変わらない挙動：NULバイトを含まない値は、前と同じく保存され、読み戻されます。
- テスト：`test/t/cases/var_input.rb`の`/set_nul_value`と`/set_nul_value_via_set`の、ポート18153のテストです。

### 7. 出力を書くcontentハンドラへのHEADリクエストは、ヘッダだけを受け取るようになりました

- 変更前：HEADリクエストでは、nginxが`r->header_only`を立て、レスポンスはボディなしのヘッダだけであるべきです。しかし、メインリクエストのcontentハンドラが`Nginx.rputs`か`Nginx.echo`で出力を書くと、`ngx_mrb_finalize_rputs`（`src/http/ngx_http_mruby_core.c`）は、`r->header_only`を見ずに、`ngx_http_send_header`の後で出力のチェインを`ngx_http_output_filter`に渡しました。そのため、このようなlocationへのHEADリクエストは、ヘッダの後にボディを受け取りました。keep-aliveの接続では、余分なボディのバイトが、クライアントが次のレスポンスを読む位置をずらしました。そのため、その接続のレスポンスとリクエストの対応が崩れました。前段のリバースproxyが1本の上流の接続を複数のクライアントで使い回すと、レスポンスを別のリクエストに対応づけることがありました。これは、レスポンスのキューがずれる問題で、HTTPのリクエストとレスポンスのスマグリングの類です。クライアントは、出力するmrubyのcontentハンドラのあるlocationへHEADリクエストを送るだけで、これを起こせました。
- 変更後：HEADリクエストには、ボディを送りません。`ngx_mrb_finalize_rputs`は、nginx自身のcontentハンドラと同じく、`ngx_http_output_filter`の前に`r->header_only`を見ます。`Content-Length`を含むヘッダは変わらないので、HEADのレスポンスは、ボディが持つはずだった長さを、これまでどおり報告します。これは、[#594](https://github.com/matsumotory/ngx_mruby/pull/594)の「出力の後にステータスを設定する」経路にも効きます。出力の後に202を設定するハンドラへのHEADリクエストは、ボディなしで202のヘッダを受け取ります。
- 変更の理由：
  - 不具合：「変更前」に書いたとおり、`ngx_mrb_finalize_rputs`は、`r->header_only`を見ずに出力のチェインを`ngx_http_output_filter`に渡しました。そのため、出力を書いたcontentハンドラは、HEADリクエストにボディで応答しました。
  - 前の挙動を残せなかった理由：HEADのレスポンスはボディを持てません。keep-aliveの接続では、このボディが後続のレスポンスのフレーミングをずらし、上流の接続をクライアントをまたいで使い回す前段のproxyが、それを別のリクエストに対応づけることがありました。nginx自身のcontentハンドラは、HEADにボディを送りません。mrubyのcontentハンドラも、同じようにします。
  - ドキュメントが示していた挙動：2.7.1のドキュメントには、`Nginx.rputs`は「Puts response text」と書いてあります（[2.7.1のメソッド](https://github.com/matsumotory/ngx_mruby/blob/v2.7.1/docs/class_and_method/README.md)）。HEADリクエストがその出力を受け取るとは、書いてありません。また、HTTPは、HEADリクエストへのレスポンスにボディを認めていません。
- 影響の範囲：`Nginx.rputs`か`Nginx.echo`で出力を書く、メインリクエストのハンドラのあるlocationで、HEADリクエストを受け取る設定です。この修正は共有の出力処理`ngx_mrb_finalize_rputs`にあるので、contentハンドラだけでなく、出力を書くrewriteハンドラやaccessハンドラも、HEADリクエストにはヘッダだけを返します。
- 必要な対応：対応は要りません。
- このリリースで変わらない挙動：GETリクエストは、前と同じに動きます。
- テスト：`test/t/cases/head_output.rb`のポート18137のテストと、`test/t/cases/output_then_status.rb`のポート18154に足したHEADのケースです。

### 8. Nginx::Upstream#server=とNginx::Stream::Connection#upstream_server=は、後続のリクエストや接続でもpeerを有効に保ち、use-after-freeが起きなくなりました

- 変更前：`Nginx::Upstream#server=`（`src/http/ngx_http_mruby_upstream.c`）は、新しいサーバー名をリクエストのプールのメモリ（`ngx_palloc(r->pool, ...)`）にコピーし、`ngx_parse_url(r->pool, ...)`で解決して、その結果をupstreamのround-robinのpeerに入れました。peerは、ワーカーの寿命を持つconfiguration poolにあり、そのupstreamを使うすべてのリクエストが共有します。stream側の`Nginx::Stream::Connection#upstream_server=`（`src/stream/ngx_stream_mruby_connection.c`）も同じで、peerにRubyの文字列や、connection poolのメモリを入れました。このRubyの文字列は、mrubyのGCが解放や移動をしうる文字列です。サーバーを設定したリクエストや接続が終わると、そのプールは解放されますが、共有のpeerは、そこを指したままでした。server=を呼ばずに同じupstreamを使う後続のリクエストや接続は、解放済みのメモリを読みました。これに当たるのは、別のlocationからそのupstreamへの`proxy_pass`や、`Nginx::Upstream#server`の読み取りや、後続の接続での読み取りです。nginxが接続する宛先と、ログに出す名前が、解放済みの領域から来ました。これはuse-after-freeで、ワーカーのクラッシュ、宛先の取り違え、ログへの解放済みメモリの漏洩が起こりえました。
- 変更後：名前と解決後のsockaddrを、peerごとに、ワーカーの寿命を持つストアにコピーします。このストアは、peerにつき1つで、cycle poolに1回だけ確保して再利用します。そのため、peerは解放済みのメモリを指しません。server=を繰り返して呼んでも、1つのストアを再利用するので、増え続けません。ストアは1から255バイトを持ち、宛先が解決できたことも確かめます。`Nginx::Upstream#server=`（HTTP）と`Nginx::Stream::Connection#upstream_server=`（stream）の両方を直しました。
- 変更の理由：
  - 不具合：「変更前」に書いたとおり、ワーカーの寿命を持つpeerが、リクエストのプールやconnection poolのメモリを指し、stream側ではGCが移動しうるmrubyの文字列を指しました。そのため、後でupstreamを使うと、解放済みのメモリを読みました。
  - 前の挙動を残せなかった理由：peerは、それを設定したリクエストや接続より長く生きるので、それらのプールへのポインタを持てません。名前と宛先は、peerと同じだけ生きる必要があるので、ワーカーの寿命を持つストアにコピーします。
  - ドキュメントが示していた挙動：ドキュメントに載った使い方は、`mruby_rewrite_handler_code`でサーバーを設定し、同じリクエストでproxy_passします。server=を呼ばない後続のリクエストが有効なpeerを読むことや、設定したリクエストが終わった後に`Nginx::Upstream#server`を読むと有効なメモリが返ることは、書いてありませんでした。
- 影響の範囲：`Nginx::Upstream#server=`（HTTP）か`Nginx::Stream::Connection#upstream_server=`（stream）を使う設定です。
- 必要な対応：対応は要りません。
- このリリースで変わらない挙動：upstreamを使うリクエストや接続ごとにサーバーを設定する使い方は、これまでどおり動きます。読み戻す値は、設定した値です。
- このリリースで変わらない挙動：共有メモリの`zone`を持つupstreamは、すべてのワーカーが共有する共有メモリにpeerを置きます。そのため、peerをどれか1つのワーカーのメモリに向けることは、この修正のワーカーごとのストアでも、旧コードのリクエストのプールでも、ワーカーをまたいで不正です。このリリースは、`zone`を使わない場合を正します。`zone`を持つupstreamは、名前とsockaddrをzoneのslabに置く必要があり、本リリースの範囲外です。ここは旧コードのほうが悪く、前からある問題です。
- テスト：HTTPは`test/t/cases/upstream_server.rb`のポート18139のテスト、streamは`test/t/cases/stream_upstream_server.rb`のポート12364と12365のテストです。

## 新機能

ありません。

## 不具合の修正

- nginxが値を返さない変数を`Nginx::Var`で読むと、`Nginx::Var`は`error.log`に`<name> is NULL`の行を書きます。たとえば、rootが存在しないlocationで`$realpath_root`を読んだときです。この行で、変数の名前は名前の最後の文字で終わるようになりました。前は、メモリの上で名前の後に続くバイトが、行に入ることがありました（[#589](https://github.com/matsumotory/ngx_mruby/pull/589)）。

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

- `test.sh`：HTTPのテストサーバーは、`test/t/cases/var_input.rb`のために、18153でも待ち受けます（[#589](https://github.com/matsumotory/ngx_mruby/pull/589)）。
- `test.sh`：最初のリクエストの前に`NGINX_HEATTIME`秒だけ止まる代わりに、テストのnginxが接続を受け付けるまで待ち、待った時間を表示します。valgrindで動かすCIのセルの1つが、`Errno::ECONNREFUSED`で失敗したためです。そのセルでは、10秒止まった後も、nginxが設定を読んでいました（[#564](https://github.com/matsumotory/ngx_mruby/pull/564#issuecomment-5977252444)）。待つポートは、`test/conf/nginx.conf`と`test/conf/conf.d/*.conf`の`listen`ディレクティブにある全部のポートです。`NGINX_HEATTIME`は待つ時間の上限になり、既定は60秒です。これまでは止まる時間で、既定は2秒、CIでは10秒でした。CIは、`NGINX_HEATTIME`を設定しなくなりました。上限までにnginxが待ち受けないときと、待ち受ける前にnginxが終了したときは、まだ接続を拒むポートを表示して失敗します。`test.sh`は、nginxを起動する前にも、それらのポートでどのプロセスも接続を受け付けなくなるまで待ちます。待ち方は、`docs/test/README.md`の「Waiting for nginx to listen」に書いてあります（[#593](https://github.com/matsumotory/ngx_mruby/pull/593)）。
- CI：3つのworkflowが、サプライチェーンを検査します。検査の内容は、`docs/test/README.md`の「Supply chain checks」に書いてあります。`zizmor.yml`は、`.github/workflows/`、`.github/dependabot.yml`、`.github/zizmor.yml`を変えるプルリクエストとpushで、静的解析のツールzizmorを実行し、所見が1件でもあれば失敗します。`scorecard.yml`は、`master`へのpushと毎週に、OpenSSF Scorecardを実行し、結果をScorecard APIとcode scanningへ公開します。`gem-heads.yml`は、毎週月曜日に、`build_config.rb`の`conf.gem github:`の行ごとに、ビルドがcloneするブランチの先頭のコミットと、前の報告から動いたかを報告します。`mirror-v2x.yml`は、`contents: write`をworkflow全体でなくjobで求めるようになりました。Dependabotは、actionの新しいリリースを、公開から7日たってから提案します（[#599](https://github.com/matsumotory/ngx_mruby/pull/599)）。
- CI：測るバイナリの入力（`src/`、`mrbgems/`、`mruby/`、`dependence/`、最上位のビルドのファイル、`nginx_version`）か、比較のファイルか、ワークフローを変えるプルリクエストでは、新しい`perf`ジョブが動きます。このジョブは、ベースとプルリクエストをリリースビルドでビルドし、nginxが1リクエストあたりに実行する命令数をcallgrindで比べます。比較のファイルは、`next`ブランチから持ってきた`test/perf/`と`test/build_release.sh`と、`test/soak/`にある設定とシナリオとモックのLLMのupstreamです。`ci-ok`は、まだこのジョブを必要としません。手元では、valgrindのあるLinuxで`sh test/perf/compare.sh BASE_DIR`を実行すると、`build_perf/`でビルドし、12370から12372で待ち受けます。比較の仕組みは、`docs/test/README.md`の「Performance comparison with callgrind」に書いてあります（[#601](https://github.com/matsumotory/ngx_mruby/pull/601)）。
- CI：サニタイザのセルの「Check for sanitizer reports」のステップは、`build/nginx/logs/stderr.log`と、`build/nginx/logs/`にあるほかの`*.log`のファイルと、`build/nginx/second_instance/`にある2つ目のnginxのログも読みます。これらのファイルには、テストが起動したプロセスのレポートが入ります。前は、`build/nginx/logs/error.log`だけを読んでいました（[#601](https://github.com/matsumotory/ngx_mruby/pull/601)）。
- テストの無かったディレクティブとRubyのメソッドに、テストを加えました。テストは、`nginx -t`が`mruby_output_filter`と`mruby_output_filter_code`をそのメッセージで拒むことと、nginxが止まるときに`mruby_exit_worker_code`と`mruby_stream_exit_worker_code`が動くことを確かめます。次のメソッドについては、返す値か`error.log`に書く行を確かめます：`Nginx.module_version`、`Nginx.nginx_version`、`Nginx.server_version`、`Nginx::Request#content_type`、`#content_type=`、`#method=`、`#scheme`、`#document_root`、`Nginx::SSL.log`、Rackのloggerのメソッド、`Nginx::Utils.escape`と`.encode_parameters`、`Nginx::Stream::Connection`のセッションのアドレスを読むメソッド、`#upstream_server`、`Nginx::Stream.errlogger`、`Nginx::Stream.module_name`。HTTPのテストサーバーは18155から18157でも待ち受け、streamのテストサーバーは12363でも待ち受けます。`test/t/cases/config_hooks.rb`は別のnginxを起動します。そのnginxは18158で待ち受け、`nginx -s quit`で止まります（[#603](https://github.com/matsumotory/ngx_mruby/pull/603)）。
- `test.sh`：HTTPのテストサーバーは、`test/t/cases/output_then_status.rb`のために、18154でも待ち受けます（[#594](https://github.com/matsumotory/ngx_mruby/pull/594)）。

## 性能

2026-10-29に、`master`にある、callgrindで命令数を比べるスクリプトで測りました。使ったのは`6b27f08da`の`test/perf/compare.sh`で、手順は`docs/test/README.md`の「Performance comparison with callgrind」にあります。両方のツリーを、nginx 1.31.6でリリースビルドしました。環境は、aarch64のマシンで動くコンテナのUbuntu 22.04で、gccは11.4、valgrindは3.18.1です。各シナリオで、ウォームアップとして2000回のリクエストを送った後に、20000回のリクエストを送りました。ベースはタグ`v2.7.1`で、ツリーは`0e6185b02799`、`src/`のツリーは`8b6857bd850e`です。ヘッドは`6b27f08da`の`master`で、ツリーは`db916ab4a26f`、`src/`のツリーは`9f6fd46d1a5c`です。両方のビルドは、同じ21個のサードパーティのmrbgemsを同じコミットで使いました。ヘッドには、matsumotory/mruby-redisとmatsumotory/mruby-pointerも入っています。この2つがデフォルトのビルドに戻ったことは、項目3に書いてあります。次の表に、mrubyのガベージコレクタの分を除いて、nginxが1リクエストあたりに実行する命令数を示します。

| シナリオ | 2.7.1 | 2.7.2 | 変化率 |
|---|---|---|---|
| `hello` | 11172 | 11214 | +0.38% |
| `headers` | 34511 | 34574 | +0.18% |
| `var` | 19952 | 19998 | +0.23% |
| `filter` | 13350 | 13426 | +0.57% |
| `sleep` | 14892 | 15012 | +0.80% |
| `sub_request` | 47551 | 47782 | +0.49% |
| `file` | 13215 | 13319 | +0.79% |
| `proxy_plain_2k` | 22099 | 22099 | +0.00% |
| `proxy_plain_64k` | 22414 | 22415 | +0.00% |
| `proxy_stream_plain_50` | 53736 | 53968 | +0.43% |
| `auth` | 46142 | 47166 | +2.22% |
| `route_json_2k` | 106227 | 106944 | +0.67% |
| `ruby_call_1` | 10677 | 10685 | +0.08% |
| `ruby_call_10` | 35597 | 35712 | +0.32% |

どのシナリオの変化率も、比較のスクリプトが警告を出す3%を下回りました。相対の変化も絶対の変化も最も大きいのは`auth`で、+2.22%、1リクエストあたり1024命令です。#604がそのベースと比べたときは、CIの`perf`ジョブがx86_64のランナー`ubuntu-22.04`で、`68032bb6a`をベースにして測り、`auth`が+3.71%、`sleep`が+3.04%でした。この数は、上の表とは別のマシンと別のベースで測ったものなので、上の表の数とは比べられません。#604は、この増加のほとんどが`mrb_vm_const_get`で起きていることを突き止めました。同梱のmruby 3.3.0では、mruby-redisを入れると、128個のスロットを持つテーブルの中で`Object`の定数が118個から119個になり、定数の検索で調べるスロットが増えます。オーナーは、#604でこの差を受け入れました。2.7.1からの変更のうち、リクエストを処理する経路のコードを変えたのは#589だけです。#589では、`Nginx::Var`が、名前と引数を読む前にそれらを確かめます。また、`Nginx::Var#set`は、名前をその場で小文字にする代わりに、小文字にした名前のコピーをリクエストのプールから`ngx_pnalloc`で確保します。どちらの変更も、ブロッキングの待ちを足していません。

## アップグレード

サポートするnginxのバージョンは、[README.md](https://github.com/matsumotory/ngx_mruby/blob/next/README.md)に書いたとおり、nginxのmainlineとstableの系列です。CIは、nginx 1.31.6、1.30.5、1.28.3、1.26.3をビルドします。

- 追加、改名、削除したディレクティブはありません。このリリースで、モジュールをビルドし直してください。2.7.1をビルドしたときと同じ方法でビルドし、いつもどおりバイナリか動的モジュールを置き換えてください。

アップグレードの前に、次の点を確かめてください。各行は、上の各項目の「必要な対応」です。

- 実行時に組み立てた名前での`Nginx::Var`の呼び出し：呼び出しの前に名前を確かめるか、名前が空になりうるところで`NoMethodError`をrescueしてください。セッターには、必ず値を渡してください（項目1）。
- `Nginx::Var#set`：`set`が名前を小文字にすることに頼っていたコードでなければ、対応は要りません。そのようなコードでは、自分で名前に`downcase`を呼んでください（項目2）。
- デフォルトのビルドに戻ったmruby-redis：前にこのソースツリーでmruby-redisをビルドしたときだけ、先に`mruby/build`と`build_config.rb.lock`を消してください。ほかに対応は要りません。ビルドはhiredisのv1.5.0を使い、2.7.1のリリースノートの項目1の注意は、もう当てはまりません。gemを入れずにビルドするには、`build_config.rb`のその行をコメントアウトしてください。ほかのhiredisのタグでビルドするには、環境変数`MRUBY_REDIS_HIREDIS_VERSION`を設定してください（項目3）。
- mruby-redisの`Redis.connect_set_udptr`と引数の無い`Redis.new`：引数の無い`Redis.new`を2回目以降に呼ぶ前には、`Redis.connect_set_udptr`をもう一度呼んでください。または、`Redis.new`が返したオブジェクトを使い回すか、代わりに`Redis.new host, port`を呼んでください。新しい`Redis::ConnectionError`は`RuntimeError`のサブクラスなので、`rescue RuntimeError`はこの例外もrescueします。`Redis.connect_set_udptr`は、ワーカーで動くコードで呼んでください。前にこのソースツリーでmruby-redisをビルドしたときは、先に`mruby/build`と`build_config.rb.lock`を消してください（項目4）。
- 出力を書いてから、200、201、204以外の300未満のステータスを設定するcontentハンドラ：出力をそのステータスで返すつもりのハンドラには、対応は要りません。200で応答すべきハンドラは、`Nginx::HTTP_OK`を設定してください（項目5）。
- NULバイトを含みうる値を、`Nginx::Var#set`か`v.name =`のアクセサでnginxの変数に入れる設定：対応は要りません。値は全長で保存されます（項目6）。
- `Nginx.rputs`か`Nginx.echo`で出力を書くcontentハンドラへのHEADリクエスト：対応は要りません（項目7）。
- `Nginx::Upstream#server=`か`Nginx::Stream::Connection#upstream_server=`を使う設定：対応は要りません（項目8）。
