コンテンツにスキップ

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、High)、NULバイトを含むNginx::Varの値から未初期化のワーカーメモリが漏れる問題(GHSA-gxqc-3p4h-5p36、Medium)、出力を書くハンドラへのHEADリクエストがレスポンスボディを受け取る問題(GHSA-8mxf-gvvp-38fq、Medium)です。詳しくは項目6、7、8に書きました。

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

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

Section titled “アップグレードの前に読む挙動の変更”

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

Section titled “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)。
    • 前の挙動を残せなかった理由:これらの呼び出しの結果は、名前と引数の外のメモリから来ていました。そのため、同じ呼び出しでも、nilが返ることも、ワーカープロセスが終了することもありました。このような名前はどの変数も指さず、引数の無いセッターには設定する値がありません。そのため、残せる結果がありません。代わりに、Rubyと同じ例外が発生します。受け手に無いメソッドを呼んだときのNoMethodErrorと、メソッドが取る数より少ない引数で呼んだときのArgumentErrorです。
    • ドキュメントが示していた挙動:2.7.1のドキュメントには、アクセサがNginx::Var#VARNAMEとNginx::Var#VARNAME=で呼ばれると書いてあります。名前が=で終わるときは、「the 2nd arg」を変数に設定するとも書いてあります(2.7.1のNginx::Var#method_missing)。変数の名前を含まない名前と、値の無いセッターについては、何も書いてありません。
  • 影響の範囲: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)

Section titled “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)。
    • 前の挙動を残せなかった理由:freezeしたStringは、変更できないはずのオブジェクトです。バイトを共有するStringは、コードが持つ別のオブジェクトです。Symbolの名前は、ワーカーのすべてのリクエストが使います。変数を探すには小文字の名前が要るので、setはコピーを小文字にします。
    • ドキュメントが示していた挙動:2.7.1のドキュメントには、setはnginxの変数に値を設定すると書いてあります(2.7.1のNginx::Var#set)。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)

Section titled “3. Redisがデフォルトのビルドに戻り、mruby-redisはどのプラットフォームでもhiredisのv1.5.0をビルドし、hiredisの警告では止まらなくなりました(#604、matsumotory/mruby-redis#108)”
  • 変更前:2.7.1のリリースノートの項目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)。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のマージの後に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、matsumotory/mruby-redis#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)。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)。このgemのビルドについては、「clones the hiredis library from an unpinned branch, which broke the build of ngx_mruby」と書いてあります(2.7.1のdocs/install)。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)。また、Redisは「with default build configuration」で使えるクラスの1つに入っていました(2.7.0の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に書いてあります。ほかのタグでは、ビルドはコミットを確かめません。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)

Section titled “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のマージの後に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)。
    • 前の挙動を残せなかった理由: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の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)

Section titled “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は出力の無いハンドラについてこれを直し、そのレビューが記録したとおり、この場合は残していました。このリリースで、この場合を直しました(#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)。2.7.1のリリースノートは、この場合を「既知の制限」の節に書いていました(v2.7.1.ja.md)。メソッドのドキュメントには、出力の後にこのようなステータスを設定した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バイトを含む値でもワーカーの未初期化メモリが漏れなくなりました

Section titled “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、2.7.1のNginx::Var#set)。値が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リクエストは、ヘッダだけを受け取るようになりました

Section titled “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の「出力の後にステータスを設定する」経路にも効きます。出力の後に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のメソッド)。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が起きなくなりました

Section titled “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)。
  • test.sh:HTTPのテストサーバーは、test/t/cases/var_input.rbのために、18153でも待ち受けます(#589)。
  • test.sh:最初のリクエストの前にNGINX_HEATTIME秒だけ止まる代わりに、テストのnginxが接続を受け付けるまで待ち、待った時間を表示します。valgrindで動かすCIのセルの1つが、Errno::ECONNREFUSEDで失敗したためです。そのセルでは、10秒止まった後も、nginxが設定を読んでいました(#564)。待つポートは、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)。
  • 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)。
  • 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)。
  • 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)。
  • テストの無かったディレクティブと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)。
  • test.sh:HTTPのテストサーバーは、test/t/cases/output_then_status.rbのために、18154でも待ち受けます(#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に書いたとおり、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)。