コンテンツにスキップ

ngx_mruby v3.0.0

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

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

同梱のmrubyが3.3.0から4.0.0になる(#561)

Section titled “同梱のmrubyが3.3.0から4.0.0になる(#561)”
  • 変更前:ngx_mruby 2.xはmruby 3.3.0を同梱し、mruby 3.3.0のfull-core.gemboxが選ぶコアgemをビルドします。その中にmruby-printも入ります。
  • 変更後:このリリースはmruby 4.0.0を同梱します。これはmrubyのタグ4.0.0で、コミットは831da26b9021、日付は2026-04-20です。既定のbuild_config.rbは、NGX_MRUBY_CORE_GEMSが名前で挙げるコアgemを取り込みます。この一覧は、mruby 3.3.0のfull-core.gemboxが選んでいた集合からmruby-printを除き、hal-posix-io、hal-posix-dir、hal-posix-socketを加えたものです。mruby-printは、mruby 3.4で削除されました。Kernel#print、#puts、#pは、mruby-ioが定義します。hal-posix-io、hal-posix-dir、hal-posix-socketは、4.0.0のmruby-io、mruby-dir、mruby-socketが使うPOSIXのバックエンドです。この3つは、クラスもメソッドも追加しません。一覧は、4.0.0のfull-core.gemboxが取り込むmruby-taskとそのバックエンドのhal-posix-task、mruby-encoding、Time#strftimeを定義するmruby-strftime、Benchmarkを定義するmruby-benchmarkを含みません。一方、4.0.0のfull-core.gemboxが含まないmruby-sleepは含みます。nginxが設定を読むときにinfoのレベルで出す行は、(mruby/4.0.0) mechanism enabledで終わります。以下の項目は、スクリプト、ログの監視、C拡張、ビルドから見える違いのうち、ngx_mrubyのために見つけて測ったものを挙げます。mruby 3.3.0から4.0.0までの変更をすべて挙げたものではありません。mrubyは、利用者から見える変更をdoc/mruby3.4.mdとdoc/mruby4.0.mdに挙げています。
  • 影響の範囲:このモジュールのすべてのビルドと、モジュールが実行するすべてのRubyスクリプトです。
  • 必要な対応:同梱のmrubyと一緒にモジュールをビルドし直してください。そのうえで以下の項目を確かめ、項目が扱わない変更をmrubyの2つの一覧で確かめてください。手順はMigrating from ngx_mruby 2.x to 3.0.0にあります。同梱のmrubyを変える理由は次のとおりです。v3の計画は、安定性、性能、依存するソフトウェアの更新を、新しい機能より先にします。また、mrubyは3.xの保守のリリースを出していません。根拠はdocs/proposals/v3-plan.mdの3.3節と6節です。計画の7節の決定1は、v3の目標をmruby 4.1.0としています。オーナーは2026-10-04に、その前の段階として4.0.0を承認しました。4.1.0のタグが付いたら、nextは4.1.0に移ります。

自前のbuild_config.rbは、mruby 4.0.0のコアgemの変更に合わせる必要がある(#561)

Section titled “自前のbuild_config.rbは、mruby 4.0.0のコアgemの変更に合わせる必要がある(#561)”
  • 変更前:自前のbuild_config.rbは、mruby-printを名前で挙げることも、full-core.gemboxを取り込んでmruby 3.3.0のコアgemを得ることもできました。そのコアgemにはmruby-sleepも入ります。
  • 変更後:mruby 4.0.0にはmruby-printがありません。build_config.rbのngx_mruby_core_gemsは、同梱のmrubyに無い名前があるとビルドを止めます。4.0.0のfull-core.gemboxは、mruby-taskとmruby-encodingを取り込み、mruby-sleepを含みません。mruby-taskはKernel#sleepを定義し直します。また、mrb_stateを開くすべてのプロセスでSIGALRMのインターバルタイマー(setitimer)を動かし、mrb_stateの数を最大8個(MRB_TASK_MAX_VMS)に制限します。mruby-encodingについてはString#valid_encoding?の項目を参照してください。4.0.0のmruby-io、mruby-dir、mruby-socketはHALのバックエンドを必要とします。一覧にバックエンドが無いと、これらはhal-posix-io、hal-posix-dir、hal-posix-socketを自分で読み込みます。そのとき、「No HAL specified … (explicit selection recommended)」という警告を出します。コンテナの/sに作業ディレクトリをマウントした4.0.0のビルドは、Windowsのバックエンドを自分で選び、windows.h: No such file or directoryで止まりました。mruby/lib/mruby/gem.rbのfor_windows?が、ルートにある1文字のディレクトリをWindowsのドライブとみなすためです。
  • 影響の範囲:このリリースのものとは別のbuild_config.rbを使い、mruby-printを名前で挙げるか、full-core.gemboxを取り込むビルドです。
  • 必要な対応:このリリースのbuild_config.rbから始めるか、NGX_MRUBY_CORE_GEMSと同じようにコアgemを名前で選んでください。mruby-printを除き、hal-posix-io、hal-posix-dir、hal-posix-socketを加えてください。mruby-taskとmruby-encodingを使うかどうかは、コアのgemの説明に従って決めてください。

サードパーティのmrbgemをbuild_config.rb.lockが記録するコミットでビルドする(#558)

Section titled “サードパーティのmrbgemをbuild_config.rb.lockが記録するコミットでビルドする(#558)”
  • 変更前:build_config.rbは、サードパーティのmrbgemをコミットを指定せずに挙げます。.gitignoreは、mrubyのrakeが書くbuild_config.rb.lockを挙げているので、ロックはリポジトリに入りません。新しいチェックアウトやソースのアーカイブからのビルドは、各gemのブランチの先頭をgit clone --depth 1でクローンします。そのため、同じngx_mrubyのコミットを別の日にビルドすると、別のgemのコードが入ることがあります。
  • 変更後:build_config.rb.lockはリポジトリに入っています。ロックは、既定のビルドに入るサードパーティのgemと、それらが依存するgemのコミットを記録します。依存するgemの例は、同梱のrack-based-apiが必要とするksss/mruby-stringioです。新しいチェックアウトやソースのアーカイブからのビルドは、これらのgemを履歴ごとクローンし、記録されたコミットをチェックアウトします。そのため、ngx_mrubyのあるコミットをビルドすると、いつも同じgemのコードが入ります。自前のbuild_config.rbが加えるgemは、ブランチの先頭でクローンされます。rakeはそのgemの項目を手元のツリーにあるロックに加えるので、git statusはロックを変更ありと表示します。rakeは、既にあるクローンにはフェッチしません。そのため、ロックが変わった後にmruby/buildを残したツリーでビルドすると、ビルドがfatal: reference is not a treeで止まることがあります。止まるのは、新しいコミットを持たないクローンがあるときです。matsumotory/mruby-vedisが自分でクローンするsymisc/vedisは固定されません。
  • 変更の理由:
    • 背景:v3の計画は、すべてのサードパーティのgemをコミットで固定し、build_config.rb.lockをコミットします。同じタグから同じバイナリをビルドできるようにするためです。根拠はdocs/proposals/v3-plan.mdの4節のPillar Aです。
    • 前の挙動を残せなかった理由:固定が無いと、ビルドに入るgemのコードはビルドした日で決まります。gemのリポジトリのどれかが変わると、その変更は、リリースしたタグを後でビルドするたびに入ります。CIの実行にも毎回入ります。どちらも、ngx_mrubyに変更が無くても起きます。
    • ドキュメントが示していた挙動:2.xのドキュメントは、サードパーティのgemのコミットについて何も書いていません。docs/install/README.mdは、既定のmrbgemを名前だけで挙げています。
  • 影響の範囲:サードパーティのgemを最新のコミットで取り込むことを前提にした、ソースからのビルドです。gemを加える自前のbuild_config.rbと、ロックが変わるときにmruby/buildを残すツリーも当たります。
  • 必要な対応:新しいチェックアウトとソースのアーカイブでは、何もしなくてかまいません。gemを別のコミットでビルドするには、gemのコミットとbuild_config.rb.lockの説明に従ってロックの項目を変えてください。pullで入る変更を含め、ロックが変わったら、次のビルドの前にmruby/buildを削除してください。

Hash#inspectとHash#to_sの形式が変わる(#561)

Section titled “Hash#inspectとHash#to_sの形式が変わる(#561)”
  • 変更前:{"a"=>1}と{:a=>1}です。
  • 変更後:{"a" => 1}と{a: 1}です。mruby 3.4からこの形式です。これらのメソッドでHashを文字列にするものは、すべて同じように変わります。文字列の式展開("#{h}")、sprintf("%s", h)、Array#inspectがこれに当たります。
  • 影響の範囲:Hashのinspectやto_sを、レスポンスのボディやヘッダに入れるスクリプトと、固定の文字列と比べるスクリプトです。ngx_mrubyのテストスイートでもこの変化が出ました。変化が出たのはtest/t/ngx_mruby.rbの/async_http_sub_requestです。
  • 必要な対応:Hashのinspectやto_sを、レスポンスのボディやヘッダでデータの形式として使わないでください。文字列を自分で組み立てるか、JSON.generateを使ってください。

Exception#inspectの形式が変わり、error.logのmrb_run failedの行も変わる(#561)

Section titled “Exception#inspectの形式が変わり、error.logのmrb_run failedの行も変わる(#561)”
  • 変更前:boom (RuntimeError)です。ngx_mrubyは、スクリプトの例外をException#inspectで文字列にして、error.logのmrb_run failedの行に書きます。この行のレベルはerrorです。ただし、nginxが設定を読む間にmruby_stream_server_context_codeの例外が出す行のレベルはemergです。その行はmrb_run failed: ngx_mruby stream module configuration failed: error: ...です。行の例はmrb_run failed: return 500 HTTP status code to client: error: boom (RuntimeError)です。
  • 変更後:#<RuntimeError: boom>です。そのため、行はerror: #<RuntimeError: boom>で終わります。Exception#messageと#to_sは変わりません。
  • 影響の範囲:古い形式に合わせたログのパーサーとアラートと、Exception#inspectを使うスクリプトです。ngx_mrubyのテストスイートでもerror.logにこの変化が出ました。
  • 必要な対応:古い形式に合わせたログのパーサーとアラートを直してください。

nginxが設定を読む間のメッセージのレベルがnoticeからinfoになる(#565)

Section titled “nginxが設定を読む間のメッセージのレベルがnoticeからinfoになる(#565)”
  • 変更前:nginxが設定を読む間、ngx_mrubyは次のメッセージをnoticeのレベルで出します。http {}のブロックに対して1回出すngx_mruby/<version> (mruby/<version>) mechanism enabled、Rubyのコードを持つディレクティブごとのngx_mruby NOTICE ...: compile info: ...、stream {}の中のそうしたディレクティブごとのngx_mruby-stream-module NOTICE ...: compile info: ...、mruby_setとmruby_set_codeごとのngx_mruby NOTICE ...: target variable=(...)です。nginxは、設定を読む間に出すログを起動時のログに書きます。起動時のログのレベルは、error_logの設定にかかわらずnoticeです。そのため、nginxを起動するたびにこれらの行が書かれます。error_log ... warn;でも同じです。
  • 変更後:同じメッセージをinfoのレベルで出します。本文のNOTICEはINFOになります。行はngx_mruby INFO ...とngx_mruby-stream-module INFO ...です。nginxを起動しても、これらの行は書かれません。nginx -tは、これらの行を起動時のログだけに[info]で書きます。nginxが起動時のログの行を端末にも出力するのは、warn以上のレベルの行だけです。そのため、nginx -tの出力にこれらの行は出ません。動いている設定でerror_logのレベルがinfoかdebugなら、reloadはそのerror_logにこれらの行を書きます。開けないスクリプトのファイルのような、設定を読む間のエラーは、これまでどおりemergで出ます。
  • 変更の理由:
    • 不具合:nginxを起動するたびにこれらの行が書かれ、error_logのレベルをnoticeより上にしても止まりませんでした(#320)。
    • 前の挙動を残せなかった理由:nginxは、設定を読む間のログを起動時のログにnoticeのレベルで書き、設定したerror_logにはその後で切り替えます。そのため、error_logをどう設定しても、noticeのメッセージは除けません。起動時にこれらの行を出さないためには、noticeより下のレベルにするしかありません。
    • ドキュメントが示していた挙動:2.xのドキュメントは、これらのメッセージについて何も書いていません。
  • 影響の範囲:nginxの起動時にこれらの行を探すログの監視とスクリプトです。例えば、モジュールが読み込まれたことを確かめるためにこれらの行を探すものです。
  • 必要な対応:これらの行を見るには、nginx -t -e stderrを実行するか、nginx -tの後で起動時のログを読むか、error_logをinfoにしてreloadしてください。nginx -t -e stderrは、起動時のログを端末に書きます。起動時のログは、-eで与えたファイルです。-eが無いときは、nginxのビルドで決めたエラーログのパスです。既定のパスは、prefixの下のlogs/error.logです。説明はMessages logged while nginx reads the configurationにあります。

NoMethodErrorのメッセージがレシーバのクラスを示す(#561)

Section titled “NoMethodErrorのメッセージがレシーバのクラスを示す(#561)”
  • 変更前:undefined method 'foo'です。
  • 変更後:undefined method 'foo' for NilClassです。
  • 影響の範囲:NoMethodErrorのメッセージにマッチさせるスクリプトとログの監視です。
  • 必要な対応:メッセージでなく、例外のクラスで判定してください。

privateとprotectedの制限が効く(#561)

Section titled “privateとprotectedの制限が効く(#561)”
  • 変更前:制限は効きません。privateやprotectedの後で定義したメソッド、initialize、トップレベルでdefで定義したメソッド、Kernelのメソッドを、明示したレシーバで呼べます。Kernel自身もレシーバにできます。例えばKernel.Integer("12")は12です。Module#instance_methodsとオブジェクトの#methodsは、privateメソッドも挙げます。メソッドpubとprivateメソッドprivを持つクラスでは、instance_methods(false)は[:priv, :pub]です。
  • 変更後:制限が効きます。mruby 3.4からです。これらのメソッドをself以外の明示したレシーバで呼ぶと、NoMethodErrorが出ます。メッセージはprivate method '<name>' called for <class>です。対象には、raise、sprintf、puts、Integer、randのようなKernelのメソッドと、mruby 4.0.0がprivateとして定義するinitialize_copy、respond_to_missing?、method_missingも入ります。"a".initialize_copy("b")も例外を出します。Kernel自身をレシーバにして今も動くのは、Kernel.format、Kernel.sprintf、Kernel.raise、Kernel.sleep、Kernel.usleepだけです。Integer、Float、String、Array、Hash、eval、rand、putsを含むKernelの他のprivateメソッドは、NoMethodErrorを出します。Kernel.Integer("12")はprivate method 'Integer' called for Moduleを出します。respond_to?は、どちらのバージョンでもprivateメソッドにtrueを返します。例えばKernel.respond_to?(:Integer)はtrueです。そのため、respond_to?では、そうした呼び出しが動くかどうかを判定できません。Module#instance_methodsとオブジェクトの#methodsは、privateメソッドを挙げません。上のクラスのinstance_methods(false)は[:pub]です。privateメソッドは、Module#private_instance_methodsと#private_methodsが挙げます。ただし、ハンドラのコードのトップレベルでdefで定義したメソッドはpublicのままで、明示したレシーバで呼べます。理由は#502の提案の1.4節にあります。
  • 影響の範囲:そうしたメソッドを明示したレシーバで呼ぶスクリプトです。Kernel.Integer(x)をはじめとするKernelをレシーバにした呼び出しも入ります。instance_methodsやmethodsの結果からprivateメソッドを探すスクリプトも当たります。
  • 必要な対応:レシーバなしで呼ぶか、sendで呼んでください。例えば、Kernel.Integer(x)の代わりにInteger(x)と書きます。Kernel.send(:Integer, x)はどちらのバージョンでも動きます。privateメソッドを挙げるには、private_instance_methodsかprivate_methodsを使ってください。

Module#remove_const、#private、#public、#protected、#module_functionとフックがprivateになる(#561)

Section titled “Module#remove_const、#private、#public、#protected、#module_functionとフックがprivateになる(#561)”
  • 変更前:publicです。Foo.remove_const(:X)、M.included(Foo)、M.prepended(Foo)、obj.singleton_method_added(:m)が動きます。フックは、ModuleとClassの#included、#extended、#prepended、#inherited、#method_addedと、BasicObject#singleton_method_addedです。
  • 変更後:privateです。明示したレシーバで呼ぶと、NoMethodError: private method ... calledが出ます。sendでは動きます。mrubyは今もフックを自分で呼びます。prependはprependedを呼び、def obj.mはsingleton_method_addedを呼びます。
  • 影響の範囲:これらのメソッドのどれかを明示したレシーバで呼ぶスクリプトです。
  • 必要な対応:クラスやモジュールの本体の中でレシーバなしで呼ぶか、sendで呼んでください。例えばFoo.send(:remove_const, :X)です。

append_features、prepend_features、extend_objectのオーバーライドが呼ばれなくなる(#561)

Section titled “append_features、prepend_features、extend_objectのオーバーライドが呼ばれなくなる(#561)”
  • 変更前:include、prepend、extendはこの3つのメソッドを呼ぶので、モジュールはこれらをオーバーライドできます。
  • 変更後:3つのメソッドはありません。Module.new.respond_to?(:append_features, true)はfalseで、他の2つも同じです。include、prepend、extendは、これらを呼ばずにモジュールを加えます。そのため、オーバーライドしたメソッドは呼ばれず、エラーも出ません。フックのincluded、prepended、extendedは今も呼ばれます。
  • 影響の範囲:3つのメソッドのどれかをオーバーライドするモジュールです。
  • 必要な対応:そのコードをincluded、prepended、extendedに移してください。

allbits?、anybits?、nobits?がIntegerだけのメソッドになる(#561)

Section titled “allbits?、anybits?、nobits?がIntegerだけのメソッドになる(#561)”
  • 変更前:Numericのメソッドなので、Floatでも動きます。1.5.allbits?(1)はtrueです。
  • 変更後:Integerだけのメソッドです。Floatに呼ぶとNoMethodErrorが出ます。Integerではこれまでどおり動きます。例えば6.allbits?(2)はtrueです。
  • 影響の範囲:Floatにこれらを呼ぶスクリプトです。
  • 必要な対応:Floatを先にto_iで変換してください。
  • 変更前:FileTestはクラスです。
  • 変更後:FileTestはモジュールです。FileTest.exist?などのメソッドは、これまでどおり呼べます。
  • 影響の範囲:メソッドを呼ぶ以外の形でFileTestをクラスとして使うスクリプトです。
  • 必要な対応:メソッドを呼ぶだけなら、何もしなくてかまいません。

メソッドの本体で定数に代入するとSyntaxErrorになる(#561)

Section titled “メソッドの本体で定数に代入するとSyntaxErrorになる(#561)”
  • 変更前:許されます。例はdef m; X = 1; endです。
  • 変更後:スクリプトをコンパイルするときにSyntaxError: dynamic constant assignmentが出ます。
  • 影響の範囲:メソッドの本体で定数に代入するスクリプトです。ngx_mrubyのRack風のKernel#runは、Kernel.const_setでServerを設定します。
  • 必要な対応:Object.const_set(:X, 1)かKernel.const_setを使ってください。

Floatの値にビット演算が無くなる(#561)

Section titled “Floatの値にビット演算が無くなる(#561)”
  • 変更前:~、&、|、^は整数部に対して動きます。1.5 & 1は1、~1.5は-2です。シフトの回数をnとします。nが1以上のとき、<<はFloatに2のn乗を掛けて切り捨てます。1.5 << 1は3、-1.5 << 1は-3、1.5 << 40は1649267441664です。結果がIntegerの範囲を超えると、Floatを返します。例えば1.5 << 70は1.77088743107612e+21です。nが1から32のとき、>>は正のFloatを2のn乗で割って切り捨てます。8.0 >> 1は4、1.5 >> 1は0です。nが1から32のとき、>>は負のFloatには-1を返します。例えば-8.0 >> 1は-1です。nが32を超えると、>>は正のFloatに0を、負のFloatに-1を返します。1e20 >> 32は23283064365、1e20 >> 33は0です。nが0のときは、Floatをそのまま返します。例えば-1.5 >> 0は-1.5です。nが負なら、逆の向きにシフトします。例えば1.5 >> -1は3です。
  • 変更後:~、&、|、^、>>、<<は、Floatに対してNoMethodErrorを出します。mruby 3.4が6つすべてを削除しました。
  • 影響の範囲:6つの演算子のどれかをFloatに使うスクリプトです。
  • 必要な対応:~、&、|、^では、先にto_iで変換してください。<<と>>では、先にto_iで変換すると別の数になることがあります。1.5.to_i << 1は2、-8.0.to_i >> 1は-4です。前の数が必要なら、シフトの回数をnとして次のようにしてください。<<でnが1以上なら、Floatに2のn乗を掛けてto_iを呼びます。>>でnが1から32なら、正のFloatを2のn乗で割ってto_iを呼び、負のFloatには-1を使います。>>でnが32を超えるなら、正のFloatには0を、負のFloatには-1を使います。この場合に割り算をすると、別の数になります。例えば(1e20 / 2**40).to_iは90949470ですが、1e20 >> 40は0でした。nが0なら、Floatをそのまま使います。

String#valid_encoding?が既定のビルドに無くなる(#561)

Section titled “String#valid_encoding?が既定のビルドに無くなる(#561)”
  • 変更前:mruby-string-extが定義します。ngx_mruby 2.xは、既定のbuild_config.rbではmrubyをMRB_UTF8_STRINGなしでビルドします。そのため、このメソッドはすべての文字列にtrueを返し、"\xfe"にもtrueを返します。build_config.rbでMRB_UTF8_STRINGを定義したビルドでは、UTF-8として正しくない文字列にfalseを返します。
  • 変更後:mruby 3.4からは、このメソッドを定義するのはmruby-encodingだけです。既定のビルドはそのgemを含まないので、呼ぶとNoMethodErrorが出ます。メッセージはundefined method 'valid_encoding?' for Stringです。このメソッドを呼ぶハンドラは、500を返します。
  • 影響の範囲:String#valid_encoding?を呼ぶスクリプトです。
  • 必要な対応:2.xの既定のbuild_config.rbで使っていたスクリプトでは、呼び出しを削除してください。そこでは、このメソッドがfalseを返したことはありません。あるいは、mruby-encodingを加えてビルドし、コアのgemの説明に従ってスクリプトを確かめてください。MRB_UTF8_STRINGを定義していた2.xのビルドで判定を続けるには、mruby-encodingが必要です。既定のビルドがmruby-encodingを含まない理由は次のとおりです。このgemはビルド全体にMRB_UTF8_STRINGを定義するので、String#size、#[]、#indexが、バイトでなく文字を数えます。そのため、String#sizeからContent-Lengthを計算するスクリプトは、別の数を得ます。オーナーは2026-10-04に、このgemを既定のビルドから外し、必要なときに加える形にすると決めました。

シードを与えたRandomが別の数を返す(#561)

Section titled “シードを与えたRandomが別の数を返す(#561)”
  • 変更前:Random.new(seed)とsrand(seed)は、1つのシードに対して1つの数列を返します。アルゴリズムはxoshiroです。
  • 変更後:同じシードに対して別の数列を返します。mruby 4.0は、xoshiroの代わりにPCGを使います。
  • 影響の範囲:シードを与えた乱数生成器が返す数に依存するスクリプトです。
  • 必要な対応:シードを与えた乱数生成器が返す数に、バージョンをまたいで依存しないでください。

Set#inspectとSet#to_sの形式が変わり、Set#flatten_mergeが無くなる(#561)

Section titled “Set#inspectとSet#to_sの形式が変わり、Set#flatten_mergeが無くなる(#561)”
  • 変更前:#<Set: {1, 2}>です。Set#flatten_mergeはpublicメソッドです。例えばSet.new([1]).flatten_merge(Set.new([2, Set.new([3])]))は、1、2、3を要素とするSetです。
  • 変更後:Set[1, 2]です。これらのメソッドでSetを文字列にするものは、すべて同じように変わります。文字列の式展開("#{set}")、sprintf("%s", set)、Array#inspectがこれに当たります。mruby 4.0.0はSet#flatten_mergeを削除したので、呼ぶとNoMethodErrorが出ます。mrubyの利用者から見える変更の一覧は、この削除を挙げていません。
  • 影響の範囲:Setのinspectやto_sをデータの形式として使うスクリプト、それを固定の文字列と比べるスクリプト、Set#flatten_mergeを呼ぶスクリプトです。
  • 必要な対応:Hash#inspectと同じく、文字列を自分で組み立ててください。set.to_a.join(",")は、どちらのバージョンでも同じ文字列を返します。s.flatten_merge(t)の代わりにs.merge(Set.new(t).flatten)を呼んでください。どちらのバージョンでも、この呼び出しは、2.xのs.flatten_merge(t)と同じSetを返します。tがSetでもArrayでも同じです。

Hash#default_proc=がProcでないオブジェクトにTypeErrorを出す(#561)

Section titled “Hash#default_proc=がProcでないオブジェクトにTypeErrorを出す(#561)”
  • 変更前:受け付けます。
  • 変更後:TypeErrorが出ます。
  • 影響の範囲:Hash#default_proc=に、Procでないオブジェクトを渡すスクリプトです。
  • 必要な対応:Procかnilを渡してください。

キーワード引数を受けるブロックに、キーワード引数を付けてyieldすると動く(#561)

Section titled “キーワード引数を受けるブロックに、キーワード引数を付けてyieldすると動く(#561)”
  • 変更前:ArgumentError: missing keywordが出ます。
  • 変更後:ブロックがキーワード引数を受け取ります。
  • 影響の範囲:キーワード引数を受けるブロックに、キーワード引数を付けてyieldするスクリプトです。例えば、このArgumentErrorをrescueしていたスクリプトです。
  • 必要な対応:何もしなくてかまいません。mrubyの不具合の修正です。

ハンドラのコードで、ブロックやforの本体の中のreturnがコードを終える(#561)

Section titled “ハンドラのコードで、ブロックやforの本体の中のreturnがコードを終える(#561)”
  • 変更前:ハンドラのコードのトップレベルで、[0].each do ... endや3.times { ... }のようなブロックの中や、forの本体の中にreturnを書くと、LocalJumpErrorが出ます。ハンドラは500を返します。error.logにはmrb_run failed: return 500 HTTP status code to client: error: unexpected return (LocalJumpError)が出ます。returnの前に出力を書いていたコンテンツハンドラも500を返します。その出力は送りません。2.7.0は、この出力を200の応答で送りました。例外はログに出すだけでした。2.7.1は、Nginx.rputsの後に出たすべての例外について、この挙動を変えました。詳しくは2.7.1のリリースノートのエントリ3にあります。mruby_set_codeの変数には空の値が入ります。ブロックの外でコードのトップレベルに書いたreturnは、コードを終えます。
  • 変更後:このreturnは、トップレベルのreturnと同じくコードを終えます。後のコードは動きません。応答は、それまでにコードが決めたものになります。つまり、Nginx.returnで決めたステータスと、Nginx.rputsで書いた出力です。mruby_set_codeでは、ブロックの中のreturnの値が変数に入ります。error.logには、このreturnについての行は出ません。
  • 変更の理由:
    • 不具合:#502が報告した不具合です。ハンドラを途中で終えるreturnをブロックやforの本体の中に書くと、LocalJumpErrorが出ました。ハンドラは、コードが決めた応答の代わりに500を返しました。
    • 前の挙動を残せなかった理由:このエラーはmruby 3.3.0から来ていました。ngx_mrubyは、ハンドラのコードをファイバーの最初のフレームで動かします。mruby 3.3.0は、こうしたreturnが抜けるフレームとしてこのフレームを受け付けません。mruby 4.0.0は受け付けます。詳しくはハンドラのコードのフレームについての提案の1.3節にあります。エラーを残すには、同梱のmruby 4.0.0を3.3.0の規則に戻す必要がありました。#502は、このエラーを不具合として報告しています。
    • ドキュメントが示していた挙動:2.xのdocs/class_and_method/README.mdは、ngx_mrubyがハンドラのコードでreturn文を使えることと、returnがハンドラから戻ることを書いています。ブロックの中を例外とする記述はありません。
  • 影響の範囲:トップレベルのブロックやforの本体の中にreturnを持つハンドラのコードです。メソッドやlambdaの中のreturnは当たりません。500やLocalJumpErrorの行に頼っていたログのアラートとスクリプトも当たります。
  • 必要な対応:何もしなくてかまいません。2.xでの回避の書き方は、どちらのバージョンでも動きます。回避の書き方は、コードのトップレベルにreturnを書くことと、コードをメソッドやlambdaの中に置くことです。
  • このリリースで変わらない挙動:ハンドラのコードを動かすフレームは変わりません。selfはmainです。ローカル変数は、コードを1回実行する間だけ使えます。定数、グローバル変数、mainのインスタンス変数、defで定義したメソッドは、ワーカーの中に残ります。詳しくは提案の1.4節にあります。

出力なしで終わるcontentハンドラが、500でなく、空のボディの200で応答する(#574)

Section titled “出力なしで終わるcontentハンドラが、500でなく、空のボディの200で応答する(#574)”
  • 変更前:2.7.1と2.7.2では、メインリクエストのcontentハンドラが出力なしで終わると、次の場合に500が返りました。ステータスを設定していない場合、Nginx::HTTP_OKを設定した場合、201と204以外の300未満のステータスを設定した場合です。出力なしとは、何も書かない場合、Nginx.rputsで""かnilだけを書く場合、レスポンスヘッダだけを設定する場合です。Nginx::HTTP_OKは、Nginx.return、Nginx.send_header、Nginx.status_code=のどれで設定しても同じです。300未満のステータスの例はNginx::HTTP_ACCEPTEDです。Nginx::Async::HTTP.sub_requestが再開した後にこのように終わった場合も、同じでした。500のボディはnginxのエラーページか、500に設定したerror_pageで、ヘッダにはConnection: closeが付きました。詳しくは2.7.1のリリースノートの項目13にあります。error.logにはerrorレベルの行が出ました。その行は、status code is 200, but response body is empty. return NGX_HTTP_INTERNAL_SERVER_ERROR、status code is not set, and response body is empty. return NGX_HTTP_INTERNAL_SERVER_ERROR、status code is <N>, but response body is empty. return NGX_HTTP_INTERNAL_SERVER_ERRORのどれかです。2.7.0までは、出力なしのNginx::HTTP_OKに同じ500が返りました。ほかの場合には、レスポンスがまったく返りませんでした。nginxは、HTTP/1.0の接続を1バイトも送らずに閉じ、keep-aliveの接続をkeepalive_timeoutまでレスポンスなしで開いたままにしました。
  • 変更後:そのようなハンドラは、nginx自身のreturn 200;と同じに応答します。ステータスはハンドラが設定したもので、設定していなければ200です。ヘッダにはContent-Length: 0が付き、ボディは空です。Content-Typeは付きません。ngx_mrubyの出力のある応答にも付きません。nginxのreturn 200;は、default_typeのContent-Typeを付けます。HEADリクエストにも同じヘッダが返ります。locationのヘッダフィルタとボディフィルタは、この空のレスポンスにも動きます。対象はmruby_output_header_filter、mruby_output_body_filterと、それぞれの_codeの形です。ngx_mrubyは、空の応答を送る前に、読まれていないリクエストボディを破棄します。出力のある応答を送る前にも、2.7.1から同じように破棄します。詳しくは2.7.1のリリースノートの項目12にあります。そのため、開いたままの接続の次のリクエストは、クライアントがボディの後に送ったリクエストになります。nginxは、keep-aliveの接続を次のリクエストのために開いておきます。例外は、mrubyの出力のボディフィルタがあるlocationへのHEADリクエストです。この場合、nginxはヘッダを送ってから接続を閉じます。出力のあるハンドラでも、2.7.1とこのリリースで同じです。error.logには、上の行は出ません。
  • 変更の理由:
    • 不具合:#200が報告した不具合です。Nginx.return Nginx::HTTP_OKを呼んでボディなしで終わるcontentハンドラには、500が返りました。nginx自身のreturn 200;は、空のボディの200で応答します。HTTPは、この応答を認めています。RFC 9110の15.3.1節によると、200のレスポンスはメッセージのコンテントを含むと想定されますが、メッセージのフレーミングが長さ0を明示する場合は除きます。Content-Length: 0は、長さ0を明示します。2.7.1は、ステータスを設定していない場合と、nginxが自分では応答しない300未満のほかのステータスにも、同じ500を返すようにしました。それまで、これらのリクエストにはレスポンスがまったく返らなかったためです(#566)。
    • 前の挙動を残せなかった理由:500のままでは、contentハンドラは空のボディの200で応答できません。ハンドラが選べる空の応答は204だけです。204は、クライアントとキャッシュにとって別のステータスです。v3の計画は、空の200をv3の挙動の変更として受け入れています。計画はdocs/proposals/v3-plan.mdの7節の項目8です。2.x系列は互換性を優先するので、500を残します。
    • ドキュメントが示していた挙動:2.7.1のドキュメントは、前の挙動を説明していました。出力なしで終わるcontentハンドラは500で応答し、空のボディにはNginx::HTTP_NO_CONTENTを使う、という説明です。この説明は2.7.1のNginx.returnにあります。2.7.1より前のドキュメントは、Nginx.returnについて「Returns nginx status code to send back HTTP status to the client」と書いているだけでした。この記述は2.7.0のNginx.returnにあります。
  • 影響の範囲:出力なしで終わることのあるmruby_content_handlerとmruby_content_handler_codeのハンドラと、上の行を見るログの監視です。ハンドラの例は、条件が成り立つときだけ書くハンドラ、空かnilになりうる値をNginx.rputsに渡すハンドラ、レスポンスヘッダだけを設定するハンドラ、Nginx.return Nginx::HTTP_OKを呼んでボディなしで終わるハンドラです。変わらない挙動:出力なしの200未満のステータスは、2.7.1と2.7.2のときと同じく500になり、status code is <N>, but response body is emptyの行が出ます。201、204、300以上のステータスには、nginx自身が応答します。出力のあるハンドラとNginx.redirectも変わりません。ほかのハンドラも変わりません。rewriteハンドラやaccessハンドラで、出力なしでNginx.return Nginx::HTTP_OKを呼ぶと、これまでどおり500が返り、status code is 200, but response body is emptyの行が出ます。サブリクエストも変わりません。対象はNginx::Async::HTTP.sub_request、SSIのinclude、auth_requestです。サブリクエストのcontentハンドラが、出力なしでNginx::HTTP_OKを返すと、サブリクエストのステータスはこれまでどおり500になります。何も書かず、ステータスも設定しないハンドラは、これまでどおり、レスポンスのボディもステータスも無いままサブリクエストを終えます。
  • 必要な対応:ほとんどの設定では、何もしなくてかまいません。空のボディで応答すべきハンドラは、Nginx::HTTP_OKを返せるようになりました。返すものが無いことを500で知らせていたスクリプトは、そのステータスを自分で設定してください。例はNginx.return Nginx::HTTP_INTERNAL_SERVER_ERRORとNginx.return Nginx::HTTP_NOT_FOUNDで、nginxはこれまでどおり応答します。response body is empty. return NGX_HTTP_INTERNAL_SERVER_ERRORに一致させるログの監視に見える行は、次の場合だけになります。contentハンドラが200未満のステータスを返した場合と、ほかのハンドラやサブリクエストでNginx::HTTP_OKを返した場合です。
  • テスト:test/conf/conf.d/54-content-empty-body.confとtest/t/cases/content_empty_body.rbの、ポート18136のテストです。1つの接続で、ハンドラが読まないボディを持つPOSTを2つ送るテストも含みます。test/t/ngx_mruby.rbの「Nginx.return 200 dont have body」と、test/t/cases/request_api.rbの「send_header 200 without a body answers 200 with an empty body」も、この挙動を試します。

空のボディで終わる出力のボディフィルタが、接続を閉じずにレスポンスを送る(#574)

Section titled “空のボディで終わる出力のボディフィルタが、接続を閉じずにレスポンスを送る(#574)”
  • 変更前:出力のボディフィルタが空のボディで終わると、nginxはalertレベルでzero size buf in writerをログに出し、ステータス行を書く前に接続を閉じました。クライアントは1バイトも受け取りませんでした。対象のボディフィルタはmruby_output_body_filterとmruby_output_body_filter_codeです。空のボディで終わるのは、フィルタがNginx::Filter#body=かNginx::Filter#outputで""を設定した場合と、ボディの無いレスポンスでフィルタがボディを空のままにした場合です。2.7.2と2.7.1でも、それより前のリリースでも同じです。
  • 変更後:レスポンスは、そのステータスとContent-Length: 0と空のボディで送られます。error.logには、このレスポンスについてのalertの行は出ません。
  • 変更の理由:
    • 不具合:ボディフィルタは、空のボディを、memoryのフラグを立てた0バイトのバッファとして次へ渡していました。nginxのwriterは、このバッファを拒否します(zero size buf in writer)。そのため、レスポンスは送られませんでした。nginx自身のreturn 200;は、このフラグを立てない空の最後のバッファを渡します(ngx_http_send_response)。2.x系列にも同じ不具合があります。2.7.1のtest/t/cases/filter_connection.rbは、これを2.xの挙動として記録しています。テストの名前は「an empty String closes the connection without a response」です。
    • 前の挙動を残せなかった理由:送られないレスポンスは、クライアントを応答の無いままにします。上の項目の空の200も、そのlocationのボディフィルタを通ります。この変更が無いと、そのようなlocationは、空の200の代わりに何も送りません。
    • ドキュメントが示していた挙動:2.7.1のNginx::Filter#body=は、フィルタが設定したボディそのままでレスポンスを送り、Content-Lengthをその長さにすると書いています。空の文字列を例外とする記述はありません。
  • 影響の範囲:空のボディで終わることのある出力のボディフィルタを持つlocationです。上の項目の、出力なしで終わるcontentハンドラのlocationも当たります。zero size buf in writerの行を見るログの監視も当たります。これらのレスポンスでは、この行が出なくなるからです。
  • 必要な対応:ありません。
  • テスト:test/t/cases/filter_connection.rbのポート18112のテスト「an empty String sends the status with Content-Length: 0」と、ポート18136のボディフィルタのあるlocationのテスト(test/t/cases/content_empty_body.rb)です。

Nginx::Varのアクセサが、ワーカーで最初に使われたときにNginx::Varのメソッドになる(#610)

Section titled “Nginx::Varのアクセサが、ワーカーで最初に使われたときにNginx::Varのメソッドになる(#610)”
  • 変更前:Nginx::Varへのv.NAMEとv.NAME = valueは、毎回Nginx::Var#method_missingを通りました。Nginx::Varが自分で持つメソッドは、method_missing、exist?、setの3つでした。v.respond_to?(:request_method)はfalseを返し、Nginx::Var.instance_methods(false)はこの3つだけを挙げました。スクリプトが変数と同じ名前のメソッドをObjectかKernelに定義すると、v.NAMEは変数を読まずにそのメソッドを呼びました。例は、ハンドラのコードのトップレベルのdefです。
  • 変更後:ワーカープロセスで、ある名前の最初のv.NAMEかv.NAME = valueは、NAMEかNAME=をNginx::Varのメソッドとして定義します。そのワーカーでその名前を後で使うと、このメソッドが呼ばれます。このメソッドは、method_missingと同じに変数を読み書きします。値、変数でない名前へのnil、例外、error.logの行は変わりません。ワーカーは、スクリプトが使う宣言済みの変数ごとに、getterとsetterを定義します。宣言済みの変数とは、nginxとそのモジュールの変数と、setが宣言する変数です。接頭辞の変数には、スクリプトが使う最初の16の名前にgetterを定義します。接頭辞の変数の名前の例は、arg_qとhttp_x_request_idです。メソッドを定義するのは、名前がaからz、0から9、_だけでできているときです。setterの名前は、その後に=が付きます。v.Request_Methodは、これまでどおりmethod_missingを通って$request_methodを読みます。次の場合には、メソッドを定義しません。変数でない名前、接頭辞の変数のsetter、Nginx::Varのサブクラスのインスタンス、凍結したNginx::Var、Nginx::Varかその祖先が同じ名前のメソッドを既に持つ名前です。最初に使った後は、v.respond_to?(:NAME)、v.methods、Nginx::Var.instance_methods(false)、Nginx::Var.method_defined?(:NAME)がそのメソッドを示します。何を示すかはワーカーがそれまでに処理したリクエストで決まるので、2つのワーカーが違う答えを返すことがあります。メソッドができた後は、スクリプトが同じ名前のメソッドを後からObjectかKernelに定義しても、v.NAMEは変数を読みます。
  • 変更の理由:
    • 背景:mruby 4.0.0では、どのクラスも定義していないメソッド名を探すと、その名前をNginx::Var、Object、Kernel、BasicObjectの全部のメソッドと比べます。比べる項目は110を超えます。mrubyは、見つからなかった名前をキャッシュしません。そのため、v.NAMEは毎回、method_missingが動く前にこの探索をしていました。#561のcallgrindの比較では、mruby 3.3.0との差のうち、varのシナリオの74%と、authのシナリオの59%が、この探索の分でした。説明は構想の1.1節にあります。名前のメソッドがあれば、mrubyのメソッドのキャッシュがそれを見つけます。
    • 前の挙動を残せなかった理由:mrubyのメソッドのキャッシュは、探索で見つけたメソッドにしか答えません。v.NAMEが探索を飛ばすには、メソッドを定義するしかなく、定義したメソッドはイントロスペクションに見えます。構想のほかの案は、いま書かれているスクリプトを遅い経路に残すか、mrubyのリリースを待つ案です。説明は構想の2節にあります。オーナーは、2026-10-15に、イントロスペクションの変化を3.0.0の挙動の変更として受け入れ、2026-10-19にそれを確かめました。記録は構想の状態にあります。
    • ドキュメントが示していた挙動:2.7.1のNginx::Varのクラスは、Nginx::Var#VARNAMEかNginx::Var#VARNAME=を評価するとNginx::Var#method_missingが呼ばれると書いています。respond_to?とNginx::Varのメソッドについては、何も書いていませんでした。
  • 影響の範囲:Nginx::Varかそのインスタンスにrespond_to?、methods、instance_methods、method_defined?を呼ぶスクリプトです。例は、名前が変数かどうかをこれで判断するスクリプトです。変数と同じ名前のメソッドをObjectかKernelに定義し、その変数をv.NAMEでも読むスクリプトも当たります。Nginx::Var#method_missingを包むスクリプトも当たります。例は、superを呼ぶmethod_missingを持つモジュールをNginx::Varにprependするスクリプトです。包んだ処理は、メソッドを定義する名前については最初のアクセスのときだけ動きます。
  • 必要な対応:変数があるかどうかは、v.exist?(name)で判断してください。スクリプトのメソッドには、nginxの変数の名前でない名前を付けてください。
  • テスト:test/conf/conf.d/68-var-accessor.confとtest/t/cases/var_method_lookup.rbの、ポート18159のテストです。

Nginx::Connection、Nginx::Server、Nginx::Requestが、Nginx::Varを呼ばずに変数を読む(#610)

Section titled “Nginx::Connection、Nginx::Server、Nginx::Requestが、Nginx::Varを呼ばずに変数を読む(#610)”
  • 変更前:次のメソッドは、自分の変数のアクセサをNginx::Varに呼んでいました。Nginx::Connection#remote_ip、#remote_port、#local_ip、#local_port、Nginx::Server#document_root、#realpath_root、#path、Nginx::Request#hostname、#authority、#filename、#user、#get_bodyです。変数は、remote_addr、remote_port、server_addr、server_port、document_root、realpath_root、host、http_host、request_filename、remote_user、request_bodyです。そのNginx::Varは、オブジェクトが作ってインスタンス変数@iv_varに持っていました。スクリプトがそのようなメソッドか独自のmethod_missingをNginx::Varに定義するか、@iv_varに別のオブジェクトを入れると、これらのメソッドが返す値が変わりました。
  • 変更後:これらのメソッドは、nginxから変数をCで読みます。スクリプトがNginx::Varにどんなメソッドを定義しても、nginxがその変数に持つ値を返します。Nginx::Varを作らず、@iv_varも設定しません。Nginx::Request#varは、これまでどおりオブジェクトのNginx::Varを作って@iv_varに持ちます。リクエストの外では、これまでと同じRuntimeErrorを出します。
  • 変更の理由:
    • 背景:これらの変数の名前は決まっています。AGENTS.mdの「Design principles」は、初期化のときに一度だけ用意できる決まったものを、リクエストごとに探したり確保したりしないように求めています。Rubyの呼び出しは、呼ぶたびにNginx::Varでメソッドを探しました。この探索は、上の項目の探索と同じです。また、オブジェクトごとにNginx::Varを作り、そのクラスを探しました。いまは、名前とそのハッシュのキーを一度だけ用意します。説明は構想の案(d)にあります。
    • 前の挙動を残せなかった理由:スクリプトが値を変えられたのは、@iv_varのオブジェクトへのRubyの呼び出しがあったからです。この呼び出しをせずに変数を読むと、その費用とその効果の両方が無くなります。
    • ドキュメントが示していた挙動:2.7.1のNginx::Connectionのクラスは、remote_ip、remote_port、local_ip、local_portがnginxの変数$remote_addr、$remote_port、$server_addr、$server_portと同じだと書いています。Nginx::Server#realpath_rootも、$realpath_rootについて同じことを書いています。いまは、どの場合にもその値を返します。
  • 影響の範囲:Nginx::Varへのメソッドの定義、独自のmethod_missing、@iv_varの別のオブジェクトで、これらのメソッドが返す値を変えるスクリプトです。Nginx::Request#varを先に呼ばずに、これらのオブジェクトの@iv_varを読むスクリプトも当たります。
  • 必要な対応:別の値を返すには、Nginx::Connection、Nginx::Server、Nginx::Requestそのものにメソッドを定義するか、自分のメソッドを呼んでください。変数は、@iv_varでなくNginx::Varで読んでください。Nginx::Varは、Nginx::Request#var、Nginx.var、Nginx::Var.newで得られます。
  • テスト:test/t/cases/var_method_lookup.rbの、ポート18159のテストです。

Dirをmrubyのコアgemが定義するようになり、閉じたDirがRuntimeErrorでなくIOErrorを出す(#559)

Section titled “Dirをmrubyのコアgemが定義するようになり、閉じたDirがRuntimeErrorでなくIOErrorを出す(#559)”
  • 変更前:Dirはiij/mruby-dirが定義します。閉じたDirを読んだり、その項目を繰り返し処理したり、もう一度閉じたりすると、RuntimeErrorが出ます。メッセージはclosed directoryです。ブロックの中でDirを閉じたときのDir.openも、RuntimeErrorを出します。ブロックなしのDir#eachとDir.foreachは、NoMethodErrorを出します。
  • 変更後:Dirは、mruby 4.0.0のコアgemであるmruby-dirが定義します。このgemはiij/mruby-dirをもとにしていて、iij/mruby-dirのメソッドをすべて持ちます。閉じたDirを読んだり、その項目を繰り返し処理したり、もう一度閉じたりすると、IOErrorが出ます。メッセージはclosed directoryです。ブロック付きのDir.openは、ブロックの中でDirを閉じたときも、ブロックの値を返します。ブロックなしのDir#eachとDir.foreachは、Enumeratorを返します。ブロック付きのDir.foreachは、開いたDirを閉じます。これは「不具合の修正」に挙げます。また、Dirには、iij/mruby-dirに無いメソッドがあります。それは「新機能」に挙げます。
  • 変更の理由:
    • 背景:v3の計画は、サードパーティのgemのiij/mruby-env、iij/mruby-process、iij/mruby-dirを、mrubyのコアgemに置き換えます。根拠はdocs/proposals/v3-plan.mdの4節のPillar Aです。3つのうち、mruby 4.0.0がコアgemを持つのはDirだけです。
    • 前の挙動を残せなかった理由:RuntimeErrorは、iij/mruby-dirが出します。iij/mruby-dirは、ENABLE_IOなしでコンパイルすると、閉じたディレクトリにRuntimeErrorを出します。既定のビルドはENABLE_IOを定義しません。RuntimeErrorを残すには、iij/mruby-dirを残すしかありませんでした。iij/mruby-dirには、2018-09-14からコミットがありません。また、iij/mruby-dirのDir.foreachは、開いたDirをGCが解放するまで閉じません。
    • ドキュメントが示していた挙動:2.xのdocs/class_and_method/README.mdは、既定のビルドで使えるクラスにDirのクラスを挙げ、iij/mruby-dirへリンクしています。iij/mruby-dirのREADMEはメソッドを挙げていますが、閉じたDirの例外については何も書いていません。
  • 影響の範囲:閉じることのあるDirのメソッドや、ブロックの中でDirを閉じるDir.openの周りで、RuntimeErrorをrescueするスクリプトです。rescue RuntimeErrorが例外を捕まえなくなると、ハンドラは500を返します。このときログに出る行はmrb_run failed: ... error: #<IOError: closed directory>です。
  • 必要な対応:IOErrorをrescueしてください。両方のバージョンで動かすスクリプトは、IOErrorとRuntimeErrorの両方か、StandardErrorをrescueしてください。

C拡張:MRB_TT_DATAと宣言していないクラスのオブジェクトでは、Cのデータをラップできない(#561)

Section titled “C拡張:MRB_TT_DATAと宣言していないクラスのオブジェクトでは、Cのデータをラップできない(#561)”
  • 変更前:mruby 3.xは、インスタンスの型を設定していないクラスを検査しませんでした。Data_Wrap_Structやmrb_data_object_allocで、Cの構造体を自分のクラスのオブジェクトでラップするgemは、そのオブジェクトを得られました。
  • 変更後:mruby 4.0は、クラスをMRB_SET_INSTANCE_TT(klass, MRB_TT_DATA)で宣言していないと、割り当てのときにTypeError: allocation failure of <class>を出します。
  • 影響の範囲:その宣言をしていないクラスのオブジェクトでCのデータをラップするmrbgemとC拡張です。matsumotory/mruby-unameの、matsumotory/mruby-uname#1より前のコミットがその1つです。このとき、UnameのどのメソッドもTypeError: allocation failure of Unameを出します。それを呼ぶハンドラは、500を返します。ngx_mrubyのテストスイートでもこの変化が出ました。変化が出たのはtest/t/cases/gem_uname.rbです。
  • 必要な対応:クラスをMRB_SET_INSTANCE_TT(klass, MRB_TT_DATA)で宣言してください。既定のビルドは、matsumotory/mruby-unameを、この宣言をするコミットに固定します。詳しくは「ビルドとテストの変更」を参照してください。mruby-unameを取り込む自前のbuild_config.rbには、そのコミットか、それより後のコミットが必要です。

C拡張:mrubyが、Cのメソッドに渡す引数の数を引数仕様で検査する(#561)

Section titled “C拡張:mrubyが、Cのメソッドに渡す引数の数を引数仕様で検査する(#561)”
  • 変更前:mruby 3.3は、Cのメソッドに渡す引数の数を、定義したときの引数仕様で検査しました。仕様はMRB_ARGS_REQ(2)などです。ただし、検査したのは仕様がMRB_ARGS_NONE()のときだけです。他の仕様では、mrb_get_argsだけが、自分が読むものに対して引数を検査しました。
  • 変更後:mruby 4.0は、すべてのCのメソッドで、呼ぶ前に引数の数を検査します。仕様が求めるより少ない引数で呼ぶと、ArgumentErrorが出ます。仕様に可変長引数(MRB_ARGS_REST()、MRB_ARGS_ANY())が無ければ、仕様が許すより多い引数で呼んでもArgumentErrorが出ます。mrb_get_argsが受け付けるかどうかは関係ありません。仕様がキーワードを受けないとき、空でないキーワードのハッシュは1つの引数と数えます。MRB_ARGS_REQ(1)で定義して"o|o"を読むメソッドは、2つの引数で呼ぶとArgumentError: wrong number of arguments (given 2, expected 1)を出します。MRB_ARGS_REQ(2)で定義して"S"を読むメソッドは、1つの引数で呼ぶと(given 1, expected 2)を出します。mruby 3.3では、どちらの呼び出しも動きました。
  • 影響の範囲:仕様とmrb_get_argsが読むものが、どちらかの向きで食い違う、mrbgemとC拡張のCのメソッドです。ngx_mruby自身のCのメソッドは影響を受けません。src/にある110の定義のそれぞれで、仕様は、そのmrb_get_argsが受け付ける引数の数をすべて受け付けます。
  • 必要な対応:mrb_get_argsが読むものに合う仕様で、メソッドを定義してください。"o|o"ならMRB_ARGS_ARG(1, 1)です。

C拡張:ヘッダと関数の名前が変わり、失敗したときのmrb_open()の戻り値が変わる(#561)

Section titled “C拡張:ヘッダと関数の名前が変わり、失敗したときのmrb_open()の戻り値が変わる(#561)”
  • 変更前:mruby/ext/io.h、mrb_alloca()、mrb_open_allocf(f, ud)、mrb_open_core(f, ud)があります。後の2つは、mrb_allocfの型のアロケータを受け取ります。既定のアロケータはmrb_default_allocf()です。プログラムは、このアロケータを定義し直せました。gemの初期化が失敗すると、mrb_open()はNULLを返します。
  • 変更後:IOのヘッダはmruby/io.hになりました。mruby/ext/io.hはありません。mrb_alloca()はmrb_temp_alloc()になりました。mruby.hはmrb_alloca()をそのマクロとして残し、コメントに「for compatibility」と書いています。mrb_open_allocf()、mrb_allocf、mrb_default_allocf()はありません。mrb_open_core()は引数を取りません。mrubyは、mrb_basic_alloc_func(void *ptr, size_t size)でメモリを割り当てます。プログラムはこの関数を定義して、mrubyのアロケータを置き換えられます。説明は、mrubyのdoc/guides/memory.mdにあります。gemの初期化が失敗すると、mrb_open()はNULLでなく、mrb->excを設定したmrb_stateを返します。
  • 影響の範囲:mrbgemと、mrubyを組み込むプログラムのうち、Cのソースが次のどれかに当たるものです。mruby/ext/io.hをインクルードするもの、mrb_open_allocf()を呼ぶもの、mrb_open_core()を引数付きで呼ぶものは、ビルドが止まります。自分のmrb_default_allocf()を定義するものでは、mrubyがそれを呼ばなくなります。mrb_open()の結果をNULLかどうかだけで判定するものも当たります。mrb_alloca()の呼び出しは、今もコンパイルできます。
  • 必要な対応:mruby/io.hをインクルードし、mrb_temp_alloc()を呼んでください。mrb_open_allocf(f, ud)の代わりにmrb_open()を呼び、mrb_open_core()は引数なしで呼んでください。プログラムがアロケータを置き換えるところでは、mrb_default_allocf()の代わりにmrb_basic_alloc_func()を定義してください。mrb_open()の結果は、mruby.hのMRB_OPEN_FAILURE()で判定してください。このマクロは、NULLのときと、excを設定したmrb_stateのときに真になります。

C拡張:mruby.hをインクルードするソースに、生成されたヘッダが必要になる(#561)

Section titled “C拡張:mruby.hをインクルードするソースに、生成されたヘッダが必要になる(#561)”
  • 変更前:mruby.hはmruby/presym.hをインクルードしませんでした。そのため、MRB_SYM()を使わないソースは、mruby/includeだけでコンパイルできました。MRB_NO_PRESYMでpresymを無効にすることもできました。ngx_mruby 2.xは、自分のソースをこの形でコンパイルしていました。2.xのconfig.inは、インクルードパスにmruby/srcとmruby/includeだけを置きます。
  • 変更後:mruby.hはmruby/presym.hをインクルードし、mruby/presym.hは生成されたmruby/presym/id.hをインクルードします(mruby/mruby#6733)。MRB_NO_PRESYMも無くなりました。そのため、mruby.hをインクルードするすべてのソースにbuild/host/includeが必要です。mruby/includeだけでコンパイルすると、そのソースはfatal error: mruby/presym/id.h: No such file or directoryで止まります。
  • 影響の範囲:mrubyのビルドとは別にコンパイルする、mruby.hをインクルードするCのソースです。./configure --with-mruby-incdirで、そのディレクトリを含まないディレクトリの一覧を与えたビルドでの、ngx_mrubyのソースもこれに入ります。
  • 必要な対応:mrubyのビルドが作るincludeディレクトリ、mruby/build/host/includeをインクルードパスに加えてください。ngx_mrubyのビルドは、自分のソースにはこれを加えます。--with-mruby-incdirの既定の一覧は<mruby_root>/src <mruby_root>/include <mruby_root>/build/host/includeです(#559)。--with-mruby-incdirで自分で一覧を与えるときも、このディレクトリが必要です。ngx_mrubyのソースがmruby.hとmruby/presym.hをインクルードするためです(#572)。詳しくは3-B. Makefileを使うを参照してください。

既定のビルドからauto-sslのmrbgemと、それに伴うクラスが無くなる(#556)

Section titled “既定のビルドからauto-sslのmrbgemと、それに伴うクラスが無くなる(#556)”
  • 変更前:2.xの既定のbuild_config.rbは、同梱のauto-sslのmrbgemと、それが依存するgemをビルドします。auto-sslはmrbgems/auto-sslにあり、Nginx::SSL::ACMEのクラスを定義します。依存するgemは、pyama86/mruby-acme-client、pyama86/mruby-polarssl、matsumotory/mruby-httprequest、matsumotory/mruby-simplehttp、mattn/mruby-http、mattn/mruby-base64、takahashim/mruby-forwardable、iij/mruby-tempfileです。スクリプトは、これらのgemが定義するものを使えます。Nginx::SSL::ACME::ClientとNginx::SSL::ACME::Dehydrated、クラスとモジュールのAcme、CustomHttpRequest、OpenSSL、PolarSSL、HttpRequest、SimpleHttp、HTTP、Base64、Forwardable、FORWARDABLE、Tempfile、TempfilePath、そしてDir.tmpdirとDir.mktmpdirです。
  • 変更後:既定のビルドには、これらがどれもありません。これらの定数は無く、Dir.tmpdirとDir.mktmpdirはNoMethodErrorを出します。これらのどれかを使うハンドラは、500を返します。そのときログに出る行は、mrb_run failed: ... error: #<NameError: uninitialized constant Base64>のような形です。Dirのクラス自体は残ります。このリリースには、auto-sslのmrbgem自体がありません。次の項目を参照してください。
  • 変更の理由:
    • 背景:v3の計画の決定6は、v3の既定のビルドからauto-sslを外し、nginx-acmeと、ストアから証明書を引く例に置き換えます。根拠はdocs/proposals/v3-plan.mdの7節です。auto-sslのACMEのクライアントであるNginx::SSL::ACME::Clientは、ACMEv1を使います。Let’s Encryptは、ACMEv1をもう提供していません。こちらの根拠は計画の2.2節と3.4節です。
    • 前の挙動を残せなかった理由:このgemは、Let’s Encryptから証明書を取得できません。また、既定のビルドに入る他のgemが必要としないサードパーティのgemを8つ、すべての既定のビルドに持ち込んでいました。その中には、GPLのpyama86/mruby-polarsslもあります。さらに、auto-sslを既定のビルドに残すと、mruby 4.0.0では既定のビルドも止まります。pyama86/mruby-polarsslがmruby/ext/io.hをインクルードするためです。次の項目を参照してください。
    • ドキュメントが示していた挙動:2.xのdocs/class_and_method/README.mdは、auto-sslを、開発の初期段階にある任意で使う同梱のmrbgemと呼んでいます。既定のビルドに入るかどうかは書いていません。同じ文書のNginx::Var Classにあるmruby_set_codeの例は、Base64.encodeを呼びます。既定のビルドでBase64.encodeが使えたのは、auto-sslが入っていたからです。
  • 影響の範囲:nginxの中で、上の定数のどれか、Dir.tmpdir、Dir.mktmpdirを使うスクリプトです。
  • 必要な対応:Base64.encode(s)の代わりに[s].pack('m0')を、Base64.decode(s)の代わりにs.unpack1('m')を使ってください。どちらもコアgemのmruby-packにあります。Dir.tmpdirの代わりにENV['TMPDIR'] || '/tmp'を使い、Dir.mktmpdirの代わりに、自分で決めた名前でDir.mkdir(path, 0700)を使ってください。例はauto-sslのmrbgemと、それと一緒に入っていたクラスにあります。他の定数には、それを定義するgemを、自前のbuild_config.rbにconf.gemの行で加えてください。pyama86/mruby-polarsslに依存するgemは、mruby 4ではビルドできません。Nginx::SSL::ACMEが必要なサーバーは、2.xのビルドを使い続けてください。次の項目を参照してください。

auto-sslのmrbgemとNGX_MRUBY_AUTO_SSLが無くなる(#640)

Section titled “auto-sslのmrbgemとNGX_MRUBY_AUTO_SSLが無くなる(#640)”
  • 変更前:ngx_mruby 2.xは、auto-sslのmrbgemを同梱し、既定のビルドに入れます。このgemはmrbgems/auto-sslにあります。このgemは、ACMEのプロトコルのクライアントであるNginx::SSL::ACME::Clientと、ACMEのクライアントのdehydratedを使うためのNginx::SSL::ACME::Dehydratedを定義します。test/conf/auto-ssl/には、両方の設定の例が入っています。
  • 変更後:このリリースには、mrbgems/auto-sslもtest/conf/auto-ssl/もありません。build_config.rbはNGX_MRUBY_AUTO_SSLをもう読みません。そのため、このリリースのどのビルドにもNginx::SSL::ACMEはありません。環境にNGX_MRUBY_AUTO_SSL=1を与えたビルドは、与えないときと同じく既定のgemをビルドします。Nginx::SSL::ACMEを使うスクリプトはNameErrorを出し、そのハンドラは500を返します。そのときerror.logにはmrb_run failedの行が出ます。
  • 変更の理由:
    • 背景:v3の計画の決定6は、auto-sslを、nginx-acmeと、ストアから証明書を引く例に置き換えます。根拠はdocs/proposals/v3-plan.mdの7節です。また、このgemは同梱のmrubyでは動きません。このgemは、TLSのためにpyama86/mruby-polarsslを必要とします。pyama86/mruby-polarsslは、ngx_mrubyが保守していないgemで、2017-03-03からコミットがありません。このgemはmruby/ext/io.hをインクルードします。mruby 4.0はこのヘッダをmruby/io.hに改名したので、auto-sslを入れたmrubyのビルドはfatal error: mruby/ext/io.h: No such file or directoryで止まります(#561)。auto-sslのACMEのクライアントであるpyama86/mruby-acme-clientはACMEv1を使います。Let’s Encryptは、2021-06にACMEv1を止めました。こちらの根拠は計画の2.2節と3.4節です。
    • 前の挙動を残せなかった理由:pyama86/mruby-polarsslが変わらない限り、このgemはmruby 4でビルドできません。ビルドできたとしても、Let’s Encryptから証明書を取得できません。nextに残すと、v3のどのビルドも使えないgemと、ビルドが止まるだけの任意の変数を残すことになります。ngx_mruby 2.xには、このgemが残ります。
    • ドキュメントが示していた挙動:2.xのdocs/class_and_method/README.mdは、auto-sslを、開発の初期段階にある任意で使う同梱のmrbgemと呼び、APIについてはmrbgems/auto-ssl/mrblibのスクリプトを参照するよう書いています。このgemをいつまで残すかは書いていません。
  • 影響の範囲:Nginx::SSL::ACME::ClientかNginx::SSL::ACME::Dehydratedを使うスクリプト、test/conf/auto-ssl/の例をもとにした設定、NGX_MRUBY_AUTO_SSL=1を与えるビルドです。
  • 必要な対応:ACMEのサーバーから証明書を取得するには、nginx自身のモジュールであるngx_http_acme_module、つまりnginx-acmeを使ってください。Nginx::SSL::ACMEが必要なサーバーは、2.xのビルドを使い続けてください。このgemの説明は2.xのドキュメントにあります。ビルドの環境からNGX_MRUBY_AUTO_SSLを除いてください。この変数は何の効果もありません。
  • スクリプトで、mruby 3.4と4.0で加わった機能を使えます。caseとinによるパターンマッチ、Array#rfind、メソッドの引数の末尾に付けるカンマ、メソッドの引数の&nilです(#561)。
  • スクリプトで、mruby 3.3.0に無いメソッドを呼べます。例えばKernel#public_send、String#b、Hash#slice!、Array#fetch_values、Integer#bit_length、Integer.sqrt、Module#private_instance_methodsです。mrubyの利用者から見える変更の一覧であるdoc/mruby3.4.mdとdoc/mruby4.0.mdは、さらに多くを挙げています(#561)。
  • Dir.childrenとDir#each_childは、CRubyと同じく.と..を除いてディレクトリの項目を挙げます。Dir.empty?は、ディレクトリに項目が無いかどうかを返します(#559, #561)。ngx_mruby 2.xは、Dirをiij/mruby-dirから取っていました。iij/mruby-dirには、この3つのメソッドがどれもありません。Dirは、今はmrubyのコアgemのmruby-dirが定義します。Class and Methodを参照してください。mruby 4.0.0では、この2つのメソッドは.と..を除きます。これはmruby 3.4からの変更で、mruby/mruby#6265です。mruby 3.3.0では、この2つはDir.entriesとDir#eachの別名で、.と..を含んでいました。ngx_mrubyのリリースに、mruby 3.3.0とmruby-dirを組み合わせたものはありません。ngx_mrubyのテストスイートでもこの変化が出ました。変化が出たのはtest/t/cases/gem_dir.rbです。そうしたビルド向けに書いたスクリプトで、これらの呼び出しの後に.と..を飛ばしていたものは、そのコードを削除できます。Dir.entriesを使い続けることもできます。DirはEnumerableもインクルードします。iij/mruby-dirはインクルードしないので、Dir.open(path) { |d| d.select { |name| name.end_with?(".txt") } }は、このリリースから動きます。ブロック付きのDir.openは、ブロックが終わるとDirを閉じます。
  • ブロック付きのDir.foreachは、開いたDirを閉じます。2.xのiij/mruby-dirでは、そのDirが持つworkerのファイルディスクリプタは、GCがDirを解放するまで開いたままでした(#559)。
  • nginxが設定を読む間にmrb_open()が失敗すると、httpのモジュールはngx_mruby: mrb_open() failedを、streamのモジュールはngx_mruby-stream-module: mrb_open() failedを、emergのレベルで出します。mrubyかgemの初期化が例外を出したときは、その後に: <class> raised while initializing mrubyが続きます。nginxはこれまでどおり止まります。ngx_mruby 2.xは、この場合に自分の行を出しませんでした(#559)。
  • 導入ガイドに#660のビルド・インストール手順を統合し、対象をnext開発ブランチへ合わせました。v3のコアgemとlockファイルの仕様を維持し、英日で同じコマンドを使います。新しいcheckoutでのビルドとループバックHTTP試用の結果に、対象コミットと検証範囲を記録しました。

  • サイトはHTMLと同じ正本から、版ごとのMarkdownページとJSON文書索引を出力します。言語、出典コミットとハッシュ、未コミット変更のあるプレビューかどうかを記録します。両系列の全文・出典・未翻訳ページのリンクを検査し、表示用の改行位置とSVGを除きつつ、コード、テスト対象のサンプル、図の出典を保ちます。開発中のAPIを安定版として扱ったり、過去の例を現在の実測値として保証したりするものではありません。

  • サイトの英日トップページに、テスト対象のnginx設定・Rubyハンドラ・HTTPのやり取りをまとめて表示し、専用の配色と画面幅に応じたレイアウトを加えます。サイトの検査はその配色・文字サイズ・共通のコード色も確認します。リブランドのプレビューでは、深紺・黒鉛色の面と銅系の差し色をドキュメントにも適用します。

  • build_config.rb.lockは、mruby 4.0.0を記録します。rakeが書くversion: 4.0.0とrelease_no: 40000です。luisbebop/mruby-polarsslの項目は無くなりました。テストのビルドが、このgemをクローンしなくなったためです(#561)。

  • ロックは、hostのビルドとtestのビルドで、matsumotory/mruby-unameをgemのmasterの50031c81b101f44e89bf482f5c31213b6c07af36に固定します。ブランチの項目はbranch: masterです。このコミットはmatsumotory/mruby-uname#1のマージのコミットで、Unameのインスタンスをデータのオブジェクトとして宣言します。宣言はMRB_SET_INSTANCE_TT(uname, MRB_TT_DATA)です。このコミットのツリーは、修正のコミットc82f8a6が持つツリーと同じです。ビルドは、gemのブランチmruby-4に依存しなくなりました(#561)。

  • テストのクライアント、つまりbuild_config.rbのtestのビルドは、NO_SSLでビルドします。設定はENV['NO_SSL'] ||= '1'です。理由は次のとおりです。matsumotory/mruby-simplehttpは、NO_SSLが無いとmruby-polarsslに依存します。また、luisbebop/mruby-polarsslはmruby-printに依存します。テストのクライアントのSimpleHttpとHttpRequestは、httpsのリクエストを送れません。テストは、curl -kとopenssl s_clientでHTTPSを送ります。説明はdocs/test/README.mdにあります。rakeはすべてのビルドのgemを1つのプロセスで読み込むので、NO_SSLは、mruby-simplehttpを持つ自前のhostのビルドにも届きます(#561)。

  • 既定のビルドは21のクローンを作ります。mruby/build/repos/hostに14、mruby/build/repos/testに7で、合わせて約9.8 MiBです。また、matsumotory/mruby-redisのビルドスクリプトが、hiredisのタグv1.5.0をmruby/build/host/mrbgems/mruby-redis/hiredis-v1.5.0にクローンします。luisbebop/mruby-polarsslとそのサブモジュールのARMmbed/mbedtls、mruby/mgem-listは、クローンしなくなりました(#561、#606)。

  • 既定のビルドに、matsumotory/mruby-redisが戻りました。2.7.2の既定のビルドと同じです。2.7.2のリリースノートの「Redisがデフォルトのビルドに戻り」で始まる項目と、#604に書いてあります。ロックは、hostのビルドで、このgemをgemのmasterの8d939104923bc86bb16d2c203d91e22bfa3196dfに固定します。ブランチの項目はbranch: masterです。このコミットはmatsumotory/mruby-redis#109のマージのコミットで、Redis.connect_set_udptrの接続をmrb->udでなくmrb_stateに持ちます。2.7.2のリリースノートの「Redis.connect_set_udptrと引数の無いRedis.newは、接続をmrb->udでなくmrb_stateに持つようになり」で始まる項目に書いてあります。このコミットのビルドは、matsumotory/mruby-redis#108から、hiredisのタグv1.5.0をクローンし、そのコミット5bb7f030f6631b243438cf0fdd3be84352b905bbを確かめ、USE_WERROR=0でビルドします。また、gemはRedis#ttlを1つの引数で宣言します。mruby 4.0.0はCのメソッドの引数の数をその宣言と照らすので、gemの前の宣言では、r.ttl "key"でArgumentErrorが発生しました。ロックは、mruby-redisが依存するmatsumotory/mruby-pointerも、そのmasterの8b46506dfdb12a79a16be1d1172ef3c9324bef96に固定します。mruby-pointerはクラスを加えません(#606)。

  • mruby/は、mrubyのタグ4.0.0のツリーを持ちます。コミットは831da26b9021です。このツリーはサブツリーのsquashとしてマージし、git-subtree-split: 831da26b9021de0369d17b71b5667e2941a1a32dを記録しているので、次のupdate-mruby-subtreeはそのコミットからマージします。docs/DEVELOPMENT.mdは、mruby/の下の衝突で止まったupdate-mruby-subtreeを仕上げる手順を説明します。git read-treeで、サブツリーをrefと同じツリーに置き換えます。git write-tree --prefix=mruby/とgit rev-parse REF^{tree}が同じidを出すことを確かめます。マージだけをコミットし、ビルドの変更はその後のコミットに入れます(#561)。

  • mrubyのビルドの環境にNGX_MRUBY_ALLOW_MISSING_CORE_GEMS=1を与えると、NGX_MRUBY_CORE_GEMSの名前のうち、ビルドするmrubyに無いものを、メッセージを出して飛ばします。この値が無いと、ビルドはそこで止まります。この値は、同梱のmrubyを更新する間に使うためのものです。説明はdocs/DEVELOPMENT.mdにあります(#559)。

  • メモリのソークテスト:sh test/soak/run.shは、-DNGX_MRUBY_DEBUG_STATSを付けてngx_mrubyをbuild_soak/にビルドし、そのビルドでシナリオを流します。この定義は、Nginx::Debugのクラスを加えます。このクラスには、Nginx::Debug.gcと、ngx_mrubyが数えたGCの登録を返すNginx::Debug.statsがあります。テストは12360と12361で待ち受けます。agent proxyのシナリオのためには、12362でモックのLLMのupstreamを起動します。build.shとtest.shの既定のビルドには、Nginx::Debugがありません。CIのsoakジョブが、このテストを流します。nextでは、必須のチェックであるci-okがこのジョブを必要とします。説明は、docs/test/README.mdの「Soak test for memory」にあります(#542, #554, #557)。

  • 性能の比較:sh test/perf/compare.sh BASE_DIRは、BASE_DIRのチェックアウトと、このチェックアウトをbuild_perf/にビルドします。そして、callgrindが数えるリクエストあたりの命令数を、シナリオごとに比べます。比較は12370と12371で待ち受け、12372でモックのLLMのupstreamを起動します。CIのperfジョブは、測るバイナリの入力、測定、ワークフローを変えるプルリクエストで、この比較を流します。説明は、docs/test/README.mdの「Performance comparison with callgrind」にあります(#549, #554)。

  • agent proxyのテスト:test/t/cases/agent_proxy.rbは、ポート18132で待ち受けるtest/conf/conf.d/60-agent-proxy.confのagent proxyのレシピに、リクエストを送ります。モックのLLMのupstreamであるtest/soak/mock_llm.rbは、test/t/cases/_agent_proxy_client.rbが12372と12373で起動します。説明は、docs/test/README.mdの「Mock LLM upstream」にあります(#554)。

  • モックのLLMのupstreamであるtest/soak/mock_llm.rbは、POST /v1/responsesにも、OpenAI Responses APIと同じ形で答えます。ストリームを使うリクエストにも、使わないリクエストにも答えます。ストリームは、response.completed、response.incomplete、response.failedのどれかのイベントで終わります。どのイベントで終わるかは、--terminalのオプションか、リクエストヘッダのx-mock-terminalで選びます。性能の比較は、既定のシナリオでresponses_stream_plain_50を測ります。PERF_SCENARIOSで名前を挙げると、responses_stream_plain_1000も測ります。メモリのソークテストはagent_responses_streamを流します。test/t/cases/agent_proxy.rbは、agent proxyのレシピを/v1/responsesでも確かめます。ポートは増えません。説明は、docs/test/README.mdの「Mock LLM upstream」にあります(#600)。

  • 新しいテストケースと、そのサーバーのポート:test/t/cases/gem_dir.rbはDirのテストで18127、test/t/cases/gem_uname.rbはmruby-unameのテストで18128です(#559)。test/t/cases/config_time_log.rbは、nginxが設定を読む間のメッセージのテストで18140です(#565)。test/t/cases/return_in_block.rbは、ハンドラのコードのブロックやforの本体の中のreturnのテストで18117です(#584)。test/t/cases/var_method_lookup.rbは、Nginx::Varのアクセサと、変数を読むメソッドのテストで18159です(#610)。test/t/cases/home_example.rbは、サイトのホームページの例のテストで18160です(#617)。test/t/cases/guides.rbは、サイトのガイドとユースケースのサンプルのテストで、18161から18168と、streamの12364です(#619)。

  • dependence/ngx_devel_kit/は、ngx_devel_kitのタグv0.3.4のツリーを持ちます。コミットはbd44d1630です。ngx_mrubyの変更は1つだけ残しました。src/ndk_set_var.cでは、mruby_setの変数のハンドラがNGX_DONEを返すと、それがnginxのスクリプトエンジンのステータスになります。このツリーはupdate-devkit-subtree v0.3.4でサブツリーのsquashとしてマージし、git-subtree-split: bd44d16302273052d6005d7bdb55f74e23813de3を記録しているので、次のupdate-devkit-subtreeはそのコミットからマージします。前のツリーはタグv0.3.1のa22dade76で、そのsrc/ndk.hは0.2.15と書いていました。ngx_mrubyは、config.inのhave=NDK_SET_VARで、NDKのset_varの部分だけを有効にします。このビルドがコンパイルするファイルのうち、v0.3.1の後のコミットが変えるのはsrc/ndk.hとsrc/ndk_string_util.hだけで、その変更は機械語を生みません。src/ndk.hの版のマクロはどこからも使われていません。NDKのconfigは常にNDKを定義するので、削除した#errorは効いていませんでした。ndk_memcpypはマクロで、それを使う唯一のファイルのsrc/ndk_conf_file.cを、このビルドはコンパイルしません。これらのコミットは、ndk_conf_set_http_complex_path_slotがpostのハンドラに渡す引数と、ndk_memcpypがコピーする大きさを直します。ndk_upstream_listが確保するバケットの配列については、大きさの式の型を直します。確保するバイト数は変わりません。また、NDKを定義しないビルドでsrc/ndk.hが出していた#errorを削除し、NDK_VERSIONを“0.3.4”にします。NDKのこれらの部分を有効にするモジュールを一緒に加えたビルドは、直したコードをコンパイルします(#597)。

  • nginx自身のテストスイート:sh test/nginx-tests/run.shは、固定したコミットのnginx-testsをproveで流します。対象は、test.shがngx_mrubyを入れてビルドしたnginxと、同じ版と同じconfigureのオプションでngx_mrubyを入れずにビルドしたnginxです。両方のビルドの件数と、それぞれで失敗したテストファイルは、build_nginxtests/summary.mdに書きます。実行には、Linuxとroot以外のユーザーとperlが要ります。テストファイルは、8000から8999のポートで待ち受けます。TEST_SH_SKIP_TESTS=1 sh test.shは、ビルドとインストールの後で止まります。CIのnginx-testsのワークフローは、nginxのバイナリかtest.shかこのスクリプトを変えるnextへのプルリクエストと、nextへのpushで、これを流します。必須のチェックであるci-okは、このワークフローを必要としません。説明は、docs/test/README.mdの「nginx’s own test suite (nginx-tests)」にあります(#602)。

  • メモリのソークテストは、Nginx::Debugそのものも確かめます。test/soak/nginx.confの各シナリオは、ウォームアップの前に、新しいlocationの/debug/checkへ1回リクエストを送ります。そのシナリオは、次の3つがすべて成り立たないと失敗します。Nginx::Debug.statsがドキュメントのとおりのキーとIntegerの値を返すこと、Nginx::Debug.gcがnilを返すこと、Nginx::Debug.gcのフルGCが、どこからも参照されないオブジェクトを解放することです。説明は、docs/test/README.mdの「Soak test for memory」にあります(#605)。

  • サイトの/start/のページの例:examples/start/には、チェックアウトからngx_mrubyを組み込んだnginxをビルドするcompose.yamlとDockerfile、nginx.confとRubyのスクリプトがあります。そのREADME.mdが、ページの/start/です。CIのexamplesワークフローは、examples/check_example.pyでこのREADMEのコマンドを順に実行し、その出力を、READMEに書いてある出力と比べます。このワークフローは、nextへのプルリクエストとnextへのpushのうち、examples/、ワークフロー自身、ビルドの入力のどれかを変えるもので動きます。ビルドの入力は、src/、mrbgems/、mruby/、dependence/、build.sh、build_config.rb、build_config.rb.lock、nginx_version、configure、config.in、Makefile.inです。必須のチェックのci-okは、このワークフローを必要としません(#618)。

  • test/t/cases/agent_proxy.rbは、Messages APIのレシピについて比べる値を、test/html/agent_proxy_auth.expected、agent_proxy_route.expected、agent_proxy_limit.expected、agent_proxy_log.expectedから読みます。各ファイルは1行に1つのkey=valueを持ちます。値がその文字列を含むことを比べる行はkey~=textです。サイトのagent proxyのページは、これらのファイルを載せます。test/t/cases/_agent_proxy_client.rbは、3つ目の引数でnginxのポートを受け取ります。省略できます(#622)。

  • test.shは、自分が起こしたnginxだけを、そのpidで止めます。テスト用のnginxを起こす前にkillall nginxを流すのをやめました。NetBSDとFreeBSDとCentOSではpkill nginxでした。どちらも、マシンのすべてのnginxを止めていました。起こす前には、同じビルドのディレクトリで前の実行が止めずに残したnginxを、logs/nginx.pidのpidで止めます。止めるのは、そのプロセスがこのビルドのsbin/nginxを動かしているときだけです。終わるときは、失敗の後と、SIGHUP、SIGINT、SIGTERMを受けたときも含めて、テスト用のnginxにSIGTERMを送ります。シグナルを受けたときは、そのとき動いているコマンドが終わった後に送ります。たとえば、nginxの待ち受けを待つ処理や、実行中のテストファイルです。プロセスが残っていれば、1、3、7、15、31秒後に送り直します。単一プロセスのnginxは、イベントを待つ直前に届いたSIGTERMを取りこぼすことがあるためです。NGINX_STOPTIME秒を過ぎるとSIGKILLを送り、終了コード1で終わります。NGINX_STOPTIMEの既定は60です。シグナルを送る前には毎回、そのpidがまだこのビルドのsbin/nginxを動かしているかを確かめます。nginxが終わった後にほかのプロセスがそのpidを使っていても、そのプロセスにはシグナルを送りません。test/t/cases/_second_instance.rbも、2つ目のnginxへのSIGQUITを同じように送り直します。説明は、docs/test/README.mdの「Stopping nginx」にあります(#635)。

  • TEST_SH_PORT_OFFSET=Nは、テストスイートのすべてのポートをNだけずらします。Nは-2345から47336までの整数で、既定は0です。2つのcheckoutが、それぞれ別のオフセットで、1台のマシンで同時にtest.shを流せます。スイートのポートは、test/t、test/conf、test/htmlと、agent proxyのテストが読み込むtest/soakの2つのファイルにある、12345から12399までと18080から18199までの数です。test.shは、新しいスクリプトのtest/t/cases/_stage.rbで、これらのファイルをbuild/nginx/harness/test/にコピーします。そのとき、5桁で書いたこの範囲の数をNだけずらします。設定とフックのスクリプトはこのコピーからインストールし、テストもこのコピーから流します。テストは、test/t/cases/_prelude.rbに足したメソッドのsuite_pathで、コピーの側のCRubyの補助のスクリプトを動かします。オフセットが0なら、コピーは元のファイルと同じです。この範囲の数を桁区切りの_入りで書いたときと、ポートが10000から65535の外に出るオフセットのとき、_stage.rbは失敗します。説明は、docs/test/README.mdの「Ports」にあります(#641)。

  • test.shがテスト用のnginxにSIGKILLを送ったときの文は、実際に送ったときだけ出ます。そのときpidがもうこのビルドのnginxを動かしていなければ、シグナルを送らずに停止を終え、停止は済んだと扱います。test.shの終了コードはテストの結果のままです。前は、SIGKILLを送ったという文を出し、test.shは終了コード1で終わっていました。起こす前に、前の実行が残したnginxを止めるときは、そこで実行が終わっていました(#641)。

  • valgrindの下でnginxを動かすCIのセルは、valgrindのエラーで失敗します。そのセルは、valgrind --error-exitcode=99 --leak-check=full --errors-for-leak-kinds=definite,indirectと、新しいファイルtest/valgrind.suppのsuppressionでnginxを動かします。ステップ「Check the valgrind reports」は、valgrindのログのERROR SUMMARYがエラーを数えたときに、セルを失敗にします。エラーは、メモリのエラーと、このファイルがsuppressしないdefinitely lostとindirectly lostのブロックです。このファイルは、nginxが終了時に確保したまま残す環境変数の配列(ngx_set_environment()、8バイト)をsuppressします。possibly lostのブロックはログに出ますが、エラーにはなりません。プルリクエストとpushでは、buildのマトリクスの1つのセル(nginx 1.31.6、静的モジュール)が、valgrindの下でnginxを動かします。サニタイザを使わない他のセルは、nginxを直接動かします。test.ymlを新しい入力valgrindを付けて手動で実行すると、サニタイザのセルを除く全部のセルがvalgrindの下でnginxを動かします。新しいワークフローvalgrind-nightly.ymlは、この実行を毎日起動します。毎日の起動は、このファイルが既定のブランチのmasterに入ってから効きます。それまでは、このファイル、test.yml、test/valgrind.suppを変えるプルリクエストで起動します。test.shは、今もvalgrindのエラーを終了ステータスに含めません。説明は、docs/test/README.mdの「valgrind in CI」にあります(#634)。

  • サイトのデプロイ:.github/workflows/site.ymlのdeployジョブは、buildジョブがビルドしてチェックしたサイトを、GitHub Pagesでhttps://ngx.mruby.org/に公開します。このジョブはnextへのpushとnextでのworkflow_dispatchで動き、プルリクエストでは動きません。actions/configure-pages、actions/upload-pages-artifact、actions/deploy-pagesはコミットで固定し、権限はpages: writeとid-token: writeだけです。Pagesの設定のoriginがhttps://ngx.mruby.orgでなければ、ジョブは止まります。サイトは、古いサイトにあったGoogleの検証ファイルgoogleba3435e4002729b6.htmlを同じURLで配信し、アナリティクスのスクリプトを読み込みません。site/scripts/check-deploy.mjsが、この2つを毎回のビルドで確かめます。デプロイの後は、セッションがsite/scripts/check-live.mjsを手で流し、公開されたサイトを確かめます。手順は、AGENTS.mdの「The site」にあります(#653)。

  • サイトは、2.xのドキュメントをmasterのdocs/から/v2/の下にビルドします。site/でnpm run buildの後にnode site/scripts/build-v2.mjs --tree DIR(npm run build:v2)を流すと、2.xのページが専用の検索インデックスと共にsite/dist/v2/に入ります。site/scripts/check-v2.mjsは、すべての2.xのページに2.xのバナーがあること、/v2/と/docs/の間のバージョンのリンク、それぞれのバージョンの検索が自分のバージョンのページだけを出すことを確かめます。siteのワークフローは、毎回masterをチェックアウトして/v2/をビルドします。masterでは、masterへのすべてのプルリクエストで、site-v2のワークフローがそのプルリクエストのdocs/でサイトをビルドし、/v2/の壊れたリンクで失敗します。masterへのすべてのpushの後には、site-dispatchのワークフローがnextのsiteのワークフローを起動します(#657、#656)。

サポートするnginxのバージョンは、nginxのmainlineとstableの系列です。README.mdを参照してください。先に、使っているバージョンより新しい2.xのリリースのリリースノートを読み、その後でこのファイルを読んでください。同梱のmruby 4.0.0と一緒に、モジュールをビルドし直してください。自前のbuild_config.rbには、上の2つ目の項目にある変更が必要です。Migrating from ngx_mruby 2.x to 3.0.0に、各項目が影響するスクリプトとビルドを見つける手順があります。アップグレードの前に、次を確かめてください。

  • 同梱のmruby 4.0.0:一緒にモジュールをビルドし直し、これらの項目が扱わない変更をmrubyの一覧で確かめてください(#561)。
  • 自前のbuild_config.rb:mruby-printを除き、hal-posix-io、hal-posix-dir、hal-posix-socketを加えてください。mruby-taskとmruby-encodingを使うかどうかを決めてください(#561)。
  • build_config.rb.lock:新しいチェックアウトとソースのアーカイブでは、何もしなくてかまいません。gemを別のコミットでビルドするには項目を変えてください。ロックが変わったら、mruby/buildを削除してください(#558)。
  • Hash#inspectとHash#to_s:データの形式として使わないでください。文字列を自分で組み立てるか、JSON.generateを使ってください(#561)。
  • Exception#inspect:mrb_run failedの行を古い形式で読むログのパーサーとアラートを直してください(#561)。
  • nginxが設定を読む間のメッセージ:見るには、nginx -t -e stderrを実行するか、nginx -tの後で起動時のログを読むか、error_logをinfoにしてreloadしてください(#565)。
  • NoMethodError:メッセージでなく、例外のクラスで判定してください(#561)。
  • privateとprotected:そうしたメソッドは、レシーバなしで呼ぶか、sendで呼んでください。例えば、Kernel.Integer(x)の代わりにInteger(x)と書きます。privateメソッドは、private_instance_methodsかprivate_methodsで挙げてください(#561)。
  • Module#remove_const、#private、#public、#protected、#module_functionとフック:クラスやモジュールの本体の中でレシーバなしで呼ぶか、sendで呼んでください(#561)。
  • append_features、prepend_features、extend_objectのオーバーライド:そのコードをincluded、prepended、extendedに移してください(#561)。
  • Floatへのallbits?、anybits?、nobits?:先にto_iで変換してください(#561)。
  • FileTest:メソッドを呼ぶだけなら、何もしなくてかまいません(#561)。
  • メソッドの本体で代入する定数:Object.const_setかKernel.const_setを使ってください(#561)。
  • Floatのビット演算:先にto_iで変換するか、項目の説明に従って前の数を計算してください(#561)。
  • String#valid_encoding?:呼び出しを削除するか、mruby-encodingを加えてビルドし、スクリプトを確かめてください(#561)。
  • シードを与えたRandomとsrand:バージョンをまたいで、その数に依存しないでください(#561)。
  • Set#inspect、Set#to_s、Set#flatten_merge:前の2つをデータの形式として使わないでください。s.flatten_merge(t)の代わりにs.merge(Set.new(t).flatten)を呼んでください(#561)。
  • Hash#default_proc=:Procかnilを渡してください(#561)。
  • キーワード引数を付けたyield:何もしなくてかまいません(#561)。
  • ハンドラのコードのブロックやforの本体の中のreturn:何もしなくてかまいません(#561)。
  • 出力なしで終わるcontentハンドラ:500に頼っていたハンドラは、意図したステータスを設定してください。例はNginx::HTTP_INTERNAL_SERVER_ERRORです。response body is empty. return NGX_HTTP_INTERNAL_SERVER_ERRORに一致させるログの監視には、メインリクエストのそのようなハンドラのうち、ステータスが無いものと、nginxが自分では応答しない200から299のステータスのものが見えなくなります(#574)。
  • 空のボディで終わる出力のボディフィルタ:何もしなくてかまいません(#574)。
  • Nginx::Varのアクセサ:変数があるかどうかはv.exist?(name)で判断し、スクリプトのメソッドにはnginxの変数の名前でない名前を付けてください(#610)。
  • Nginx::Connection、Nginx::Server、Nginx::Request:別の値を返すには、これらのクラスにメソッドを定義してください。変数は、@iv_varでなくNginx::Varで読んでください(#610)。
  • Dir:閉じることのあるDirの周りでは、RuntimeErrorの代わりにIOErrorをrescueしてください。両方のバージョン向けのスクリプトは、両方をrescueしてください(#559)。
  • C拡張:CのデータをラップするクラスをMRB_SET_INSTANCE_TT(klass, MRB_TT_DATA)で宣言し、Cのメソッドをmrb_get_argsが読むものに合う仕様で定義してください。mruby/io.h、mrb_temp_alloc()、MRB_OPEN_FAILURE()を使ってください。mrb_open()とmrb_open_core()はアロケータなしで呼び、自分のアロケータにはmrb_basic_alloc_func()を定義してください。mruby/build/host/includeをインクルードパスに加え、--with-mruby-incdirで与える一覧にも加えてください(#561, #572)。
  • auto-sslのmrbgemに伴うクラス:Base64の代わりにpack('m0')とunpack1('m')を、Dir.tmpdirの代わりにENV['TMPDIR'] || '/tmp'を、Dir.mktmpdirの代わりにDir.mkdirを使ってください。または、gemを自前のbuild_config.rbに加えてください(#556)。
  • auto-sslのmrbgem:証明書はngx_http_acme_moduleで取得してください。Nginx::SSL::ACMEが必要なサーバーは、2.xのビルドを使い続けてください。ビルドの環境からNGX_MRUBY_AUTO_SSLを除いてください(#640)。