リリース

変更履歴

サーバー、PHP API、イメージに関するすべての注目すべき変更を、リリースごとに厳選してご紹介します。省略のない完全な履歴は CHANGELOG.md にあります。

最新 v0.13.0 · 2026-10-08 · 13 リリース · AGPL-3.0 CHANGELOG.md →
oxphp — releases bash
$ docker pull ghcr.io/oxphp/oxphp:0.13.0
0.13.0: Pulling from oxphp/oxphp
Status: Image is up to date
# PHP 8.4 variant:
$ docker pull ghcr.io/oxphp/oxphp:0.13.0-php8.4
v0.13.0
2026-10-08
LATEST
⚠ breaking changes
Release notes →
BREAKING

ワーカーモード: リクエストごとの max_execution_time — ワーカーが他のリクエストを抱えたまま受け取ったリクエストにも独自の制限が適用され、受け取った時点から計測されるようになりました。また set_time_limit() は、それを呼び出したリクエストにのみ影響します。共有タイマーに頼って動いていた SSE ストリームやロングポーリングは、自身で set_time_limit(0) を呼び出す必要があります。リリースイメージには php.ini が含まれていないため、制限は 30 秒です。

RUNTIME_HOOKS=1、true、all に net が含まれるように — アップグレードすると、コードを一切変更しなくても、接続、TLS ハンドシェイクと読み取り、バッファの空きを待つ書き込み、そして unix://、udp://、udg:// の読み取りがファイバーを中断するようになります。runtime_hooks は ["sleep","streams","net"] を報告するようになり、ファイバー間で共有されるストリームはトランスポートを問わず保護されます。RUNTIME_HOOKS=sleep,streams で以前のセットを維持できます。

Shareable は Shared 型専用に — PHP クラスが OxPHP\Shared\Shareable を実装したり、final になった Shared 型を継承したりすることはできなくなり、どちらも宣言時に致命的エラーになります。そのようなオブジェクトを別のスレッドに渡すと、以前はサーバーがクラッシュしていました。Shared 型や Shareable に対する PHPUnit の createMock() は失敗するようになったため、独自のマーカーインターフェースを使用してください。

Shared\* の可観測性エイリアスを削除 — oxphp_shared_channel_pending と oxphp_shared_pool_size は /metrics から、pending/size は /__ox_shared/entry の type_specific から、それらを告知していた起動時の WARN とともに削除されます。ダッシュボードとアラートは oxphp_shared_channel_count、oxphp_shared_pool_count、count に切り替える必要があります。

TRUSTED_PROXIES が IP:port 形式のエントリを読み取るように — X-Forwarded-For に IP:port を書き込むロードバランサー(Azure Application Gateway)の背後では、REMOTE_ADDR がクライアントのアドレスに、REMOTE_PORT がそのポートになります。そのようなゲートウェイの前段にある CDN もアドレスを追記する場合、アドレスは CDN 自身のものになるため、その範囲も列挙してください(Cloudflare の場合は cloudflare キーワード)。

リリース用シンボリックリンクの背後にある DOCUMENT_ROOT — ルートは起動時に一度だけ解決され、ディスクから読み取られるすべての静的ファイルとすべての PHP スクリプトは、開かれた時点でそれと照合されます。そのため /app/current を切り替えると、サーバーが再起動するまでそれらは 404 を返します。DOCUMENT_ROOT はリリースをまたいで変わらないパスに向けるか、切り替えの一環として再起動してください。

サーバーが開けないスクリプト — ワーカーモード以外では、validate_timestamps が無効で OPcache がスクリプトを保持している場合でも、削除されたスクリプトは 404 を返します。そのため、ファイルをその場で削除して再作成するデプロイ(git checkout、rsync --delete)では 404 になる時間帯が生じます。新しいファイルは古いファイルの隣に書き込み、リネームで置き換えてください。読み取れないスクリプトは 500 を返し、Cannot open script をログに出します。

シンボリックリンク経由のスクリプトはリンク先として実行 — ワーカーモード以外では、別のディレクトリへのリンクを通じて到達したスクリプトは、そのディレクトリを作業ディレクトリとして実行されます。また PHP がそれを phar として扱うかどうかは、URL ではなく解決後のファイル名で決まります。相対パスは __DIR__ から組み立ててください。

fiber.stack_size は zend.reserved_stack_size を上回る必要あり — ワーカーモードと ASYNC_WORKERS のタスクでは、ファイバーのスタック上限が PHP の予約領域(リリースビルドで 48 KiB)を空けておくようになりました。そのため、それ以下の fiber.stack_size ではすべてのリクエストが Maximum call stack size of 0 bytes で失敗し、深い再帰は以前より 44 KiB 早く停止します。

プロファイラーが無効な間、Profile\start() は何もしない — PROFILER_ENABLED がオフのときに Profile\is_active() が true を返すことはなくなったため、プロファイルを開始して is_active() をアサートするテストではプロファイラーを有効にする必要があります。OTEL_APM_ENABLED の場合、この呼び出しが以前は破棄していた APM スパンはトレースに残るようになりました。

moveTo() 後の UploadedFile::type() は移動先を読み取る — 型の検出は移動前に実行されなくなりました。そのため、type() が初めて呼ばれるまでにさらに移動、削除、または書き換えられたファイルは、その時点で存在する内容を報告するか、警告とともに application/octet-stream を報告します。移動したファイルがその場に留まらない場合や、ストリームラッパーの背後にある場合は、moveTo() の前に type() を呼び出してください。

ADDED

.oxphpdeny によるパス拒否ルール — DOCUMENT_ROOT の最上位に置いた .gitignore 構文のファイルが、そこに列挙されたパスの配信を防ぎます。すべてのルーティングモードで、静的ファイルにも PHP にも、ディスクアクセスの前に適用されます。拒否されたパスは PHP_DENY_FALLBACK で応答し、> ルールはリクエストをエントリースクリプトに渡します。起動時に一度だけ読み込まれ、拒否は oxphp_path_deny_total でカウントされます。

RUNTIME_HOOKS=net — PHP のストリームトランスポートを通じた接続、TLS ハンドシェイクと読み取り、送信バッファの空きを待つ書き込み、そして unix://、udp://、udg://、ソケットペアのストリームでの読み取りが、ワーカースレッドを占有する代わりにファイバーを中断します。streams も併せて指定する必要があり、Linux でのみ動作します。対象外: libcurl、libpq、libmemcached、DNS、永続接続の確立。

TRUSTED_PROXIES の cloudflare キーワード — キーワード、CIDR、単独の IP を 1 つのリストに混在させられるようになりました(以前は private,100.64.0.0/10 が起動エラーになっていました)。cloudflare は Cloudflare のエッジの範囲を信頼し、自身のプロキシを越えた最初のホップが Cloudflare のエッジである場合に、CF-Connecting-IP からクライアントアドレスを読み取ります。Cloudflare → ロードバランサー → OxPHP という構成には private,cloudflare が適しています。

トレースとアクセスログに User-Agent — ルートのサーバースパンは user_agent.original を、各アクセスログエントリは user_agent フィールドを持ちます。いずれも 512 バイトに切り詰められ、不正な UTF-8 は置換されるため、トレースだけでクローラーとブラウザーを見分けられます。

リクエストが切り捨てられた理由 — 過負荷のログ行は拒否を refused_queue_full、refused_wait_timeout、refused_waiting_full、refused_waiting_bytes に分けて示すようになり、拒否されたリクエストのルートスパンは oxphp.shed_reason を持ちます。そのため、引き上げるべき上限が INTERNAL_ADDR のリスナーなしでわかります。

/metrics での OTLP エクスポート失敗 — oxphp_otel_export_failures_total、oxphp_otel_export_failed_spans_total、oxphp_otel_export_retries_total が、失敗したエクスポート、それらが運んでいたスパン、即時失敗時の再試行をカウントします。失敗したエクスポートは、バッチサイズと原因とともに OTLP span export failed としてもログに出力されます。

起動時の CPU 数 — CPU count for default sizing の行が、cpus、cpu_source(host、affinity、cgroup quota、unknown のいずれか)、host_cpus、およびその結果の PHP_WORKERS と TOKIO_WORKERS を報告します。gVisor 上の DigitalOcean App Platform で見られるように、より少ない CPU 数として販売されているプランで cpu_source=host となる場合は、両方を明示的に設定する必要があります。

Debian(glibc)のナイトリーイメージ — :nightly-trixie、:php8.4-nightly-trixie、:php8.5-nightly-trixie は php:<minor>-zts-trixie 上で同じサーバーを実行し、Yii3 アプリではレスポンスあたりの CPU 使用量が Alpine より 14〜19% 少なくなりました。こちらでは www-data が UID 33 で、拡張は trixie ステージでビルドされます。タグ付きリリースは引き続き Alpine です。

PERFORMANCE

PHP 関数呼び出しのコストを削減 — スタックしたリクエストの分類のために、内部関数を含むすべての呼び出しをカウントしていたエンジンオブザーバーを廃止しました。Yii3 アプリでは、リクエストあたりの CPU が従来のモードで 5%、ワーカーモードで 7% 減少し、リクエストごとに 15,000 回の短い呼び出しを行うスクリプトでは 23〜25% 減少します。

FIXED

APM: phpredis の hGet、hSet、lPush、rPush — OTEL_APM_ENABLED が有効な場合、これら 4 つは Redis に到達せずに NULL を返していました。書き込みは失われ、読み取りは空で返り、エラーもスパンもありませんでした。フックは PHP と同様に、メソッド名を大文字小文字を区別せずに照合するようになりました。

PHP 8.5: メモリ不足エラー後の memory_limit — ワーカーモードや非同期タスクで深いスタックの OOM が発生すると、ワーカーは一切の制限を適用しなくなっていたため、次に暴走したリクエストは、カーネルがサーバーとその中のすべてのワーカーを強制終了するまで膨張していました。制限はリクエストの終了時に復元され、それでも制限を超えているワーカーはリサイクルされます。

コールスタックのオーバーフローでセグフォルトしないように — ワーカーモードや非同期タスクで PHP のスタック上限に達した再帰は、サーバーを落とすのではなく通常の Error で終了します。以前は、許容される最後の階層で realpath() を呼ぶだけでサーバーが落ちていました。上限は PHP がすべてのファイバーに設定するものになりました。

ワーカーモード: Fiber 内の致命的エラー — リクエストが開始したファイバー内、またはガベージコレクターが実行したデストラクタ内での致命的エラーは、解放済みのコールフレームを読み取り、すべての接続もろともプロセスをクラッシュさせることがありました。現在はリクエストが 500 で終了し、ワーカーは処理を続けます。

ワーカーモード: タイムアウトが正しいリクエストを終了するように — スレッドごとに 1 つしかないタイマーは、その時点で実行中だったリクエストを終了させ、本来の対象だったリクエストをハンドラーの失敗としてカウントしていたため、3 回でワーカーがリサイクルされていました。現在は各リクエストが独自の期限を持ち、中断中に期限を過ぎたリクエストは、リサイクルにカウントされない 504 で終了します。

ワーカーモード: ブート後の max_input_time — max_execution_time = 0 で max_input_time が設定されている場合、ワーカーのブート用にセットされたタイマーが、max_input_time 秒後にリクエストを終了させていました(PHP に同梱の php.ini テンプレートでは 60)。現在はワーカーが処理を始める前にそのタイマーを停止します。

非同期タスクとプールスレッドの期限 — プールスレッドの単一のタイマーは、プロセス開始からおよそ max_execution_time が経過した時点で一度だけ切れ、その時点で実行中だったタスクを Maximum execution time of 0 seconds で終了させていました。現在はプールスレッドが期限を持たないため、set_time_limit() はタスクを制限しなくなりました。タスクの制限には oxphp_async_await() のタイムアウトを使用してください。

ワーカー終了時に永続接続をクローズ — ワーカーモードでのリサイクルや動的プールの縮小のたびに、PDO::ATTR_PERSISTENT、mysqli の p:、pfsockopen() の接続がプロセスの再起動まで開いたまま残り、データベースが新しいクライアントを拒否するまで積み重なっていました。現在はスレッドの最後のリクエストの後にクローズされます。

ワーカーモード: キャンセルされた内部関数呼び出しが残すメモリ — 内部関数の中(たとえば比較関数の途中の usort())で終了したリクエストは、その関数が確保したメモリをワーカーの生存期間にわたってリークしていました。そのようなリクエストの前後で 4 MiB を超えて増加したワーカーは、次のアイドル時に警告とともにリサイクルされるようになりました。

Shared のコールバック内での致命的エラー — Mutex::withLock()、Once::getOrInit()、Registry のファクトリー、Pool::with() 内での致命的エラーは、プロセスの生存期間にわたって、ミューテックスをロックされたまま、Once を保留中のまま、キーを作成中のまま残し、プールのスロットを 1 つ欠けた状態にしていました。現在はリクエストの終了時にそのすべてが返却されます。

APM 併用時の RUNTIME_HOOKS=streams — PDO、mysqli、Redis 向けの APM のラッパーが接続ガードを迂回していたため、接続を共有するファイバーが互いの Redis の応答を読み取ったり、Cannot execute queries while other unbuffered queries are active を受け取ったりすることがありました。現在はラッパーがガードされたハンドラーを呼び出します。

RUNTIME_HOOKS=streams: リクエスト間のデストラクタ — ワーカーループの循環参照回収によって実行されたデストラクタが、共有接続上の別のリクエストのやり取りに書き込み、その応答を読み取ることがありました。保持者がその接続上で中断している接続に対するソケット待機は、タイムアウトと同様に即座に失敗し、何も送信しなくなりました。

永続 PDO コンストラクターはデータソースごとに待機 — 応答しないデータベースで止まったコンストラクターは、ワーカー上の他のすべての永続コンストラクターを、その待機時間が尽きて直列化されないまま先へ進むまで足止めしていました。現在は同じ DSN へのコンストラクターだけを待ちます。

共有された永続接続上での PDO ハンドルの破棄 — RUNTIME_HOOKS=streams の下では、あるリクエストがハンドルを解放すると別のリクエストのトランザクションがロールバックされ、pdo_mysql では読み取り中の結果セットが解放されてセグフォルトすることがありました。現在はそのクリーンアップを接続の現在の保持者だけが実行し、別のファイバーからの PDO::rollBack() は何も送信しません。

計画的なリサイクルはクラッシュではない — WORKER_MAX_MEMORY_MIB や Worker::scheduleExit() によってリサイクルされたワーカーは、クラッシュのように読める Dead workers detected の WARN ではなく、理由とともに Worker mode thread recycled として INFO でログに出力されます。

http/protobuf での OTLP エクスポート — このプロトコルでは、これまで 1 つのスパンもエクスポートされていませんでした。エクスポートスレッドがパニックし、すべてのスパンが Spans are being emitted even after Shutdown をログに出していました。現在、スパンはベース URL である OTEL_EXPORTER_OTLP_ENDPOINT 配下の /v1/traces に届きます。すでに /v1/traces が付いている場合は削除してください。

gRPC での OTLP: 接続の確立と切れた接続 — OTEL_EXPORTER_OTLP_TIMEOUT は接続の確立にも上限を設けるようになり、Linux では、データを受け付けなくなった接続は、約 15 分間すべてのエクスポートを失敗させ続けるのではなく置き換えられます。アイドルで切断された接続上で即座に失敗したエクスポートは、もう一度送信されます。設定されているが空の OTEL_EXPORTER_OTLP_ENDPOINT は、起動時に拒否されるようになりました。

oxphp_worker_stuck_total の分類 — 関数呼び出しを含まない PHP のループは c_call と判定され、スタックしたリクエストの最初の計測値が、それ以前のリクエストの値と比較されることがありました。現在はスーパーバイザーが、max_execution_time の通知に使われる割り込みを通じて、エンジン自体に問い合わせます。

fileinfo なしでの UploadedFile::moveTo() — mime_content_type() がない場合や、警告を例外に変換するエラーハンドラーの下では、moveTo() が移動前にスローし、アップロードはリクエストの終了時に削除されていました。現在は先に移動し、型の検出は type() に任せます。

非同期タスク終了時のデストラクタ例外 — タスクの後片付け中に解放されたオブジェクトのデストラクタがスローした例外は、何の痕跡も残さずに消えていました。現在は未捕捉として報告され、エラーログに記録されます。

プロファイラーの index.json が user_agent を記録するように — ドキュメントに記載されたこのフィールドは、常に null でした。

ドキュメント: SSE ガイド — ワーカーモードの例は BRPOP でイベントを読み取っていたため、各イベントは 1 つのクライアントにしか届きませんでした。現在は Redis Stream を読み取り、Last-Event-ID から再開します。ガイドは while (!connection_aborted()) ループがその条件によって終了するとはもう主張しておらず、例では ignore_user_abort(true) と set_time_limit(0) を呼び出します。

SECURITY

静的ファイル: ルーティング後に向け先を変えられたシンボリックリンク — DOCUMENT_ROOT 内のリンクが、あるリクエストで検証された後に(たとえば /etc/passwd へ)向け先を変えられると、次のリクエストではその先が辿られていました。現在、ファイルは開かれた後に、カーネルがそのディスクリプタについて報告する場所と照合されます。まだメモリ内キャッシュにあるファイルは、検証済みのバイト列を保持します。

PHP スクリプト: ルーティング後に向け先を変えられたシンボリックリンク — ワーカーモード以外では、PHP の realpath キャッシュが古いリンク先を手放すと、そのようなリンクによって攻撃者のスクリプトが実行されたり、テキストファイルが出力されたり、FIFO でワーカーがハングしたりすることがありました。現在はワーカーが自らスクリプトを開き、ディスクリプタを検査したうえで、そのファイルを PHP に渡します。差し替えられたリンクは 404 を返します。単純なスクリプトでのコストはスループットの約 3% です。

PHP 8.5: リクエストをまたぐ error_get_last() — ワーカーモードと非同期タスクでは、中断ポイントで一時停止したリクエストが、別のリクエストの致命的エラーのバックトレースを、その関数が呼び出されたときの引数(パスワード、トークン、顧客レコード)ごと読み取れることがありました。現在は各リクエストが独自のバックトレースを保持し、終了時に解放します。

TRUSTED_PROXIES: パースできない転送エントリ — 最も右側の非信頼エントリを選ぶルールは、パースできないエントリを読み飛ばしてクライアントが書き込んだ部分まで読み進めていたため、IP:port を書き込むゲートウェイの背後では、クライアントが自分のアドレスを選べてしまいました。現在は、パースできないエントリや読み取れないヘッダー行があると、ルールはそこで停止します。

分割された Cookie ヘッダー — HTTP/2 で許されているように複数のフィールド行に分割された Cookie は、何かが読み取る前に RFC 9113 に従って結合されるようになりました。以前は、後続の行にあるプラグインの __oxp_* Cookie がアプリケーションに届いてしまい、最初の行からそれを取り除くと、残りの行にあるアプリケーション自身の Cookie が失われていました。

v0.12.0
2026-09-25
⚠ breaking changes
Release notes →
BREAKING

SHARED_ENABLED=false が Shared\* を無効化するように — この設定はこれまでパースされたうえで無視されていました。現在は OxPHP\Shared\* クラスが一切登録されず、最初に生成されたクラスが Error: Class "OxPHP\Shared\Counter" not found をスローします。この設定は OX_SHARED_ENABLED や素の ENABLED としても読み取られるため、アップグレード前に 3 つすべてを確認してください。コードがどちらで動作しているかは class_exists() で判別できます。

ワーカーモード: クライアントの切断でリクエストが終了しなくなりました — ハンドラーは最後まで実行されるため、finally ブロックとクリーンアップが実行されます。ignore_user_abort が常に有効であるかのように動作し、上限は max_execution_time だけです。早期に停止するには connection_aborted() を呼び出してください。ストリーミングレスポンスは引き続き次の書き込み時に終了します。

ignore_user_abort(true) はすべてのモードで最後まで実行されます — PHP-FPM と同様に、出力は破棄され、connection_aborted() が切断を報告します。次の書き込みで停止することに依存していたストリームのループは、connection_aborted() を確認する必要があります。php.ini やワーカーのブートストラップでの ignore_user_abort = On は、すべてのストリームに適用されるようになりました。

/health はプールが失われたりスタックしたりすると 503 を返します — ボディに pool_stalled が加わり、"status": "degraded" を報告します。集約された /health に向けた Kubernetes の liveness プローブは Pod を再起動するようになるため、/health/liveness、/health/readiness、/health/startup を使用してください。

ACCESS_LOG が LOG_LEVEL に上書きされなくなりました — ACCESS_LOG=all と LOG_LEVEL=warn の組み合わせでは以前は何も書き込まれませんでしたが、現在はリクエストごとに 1 行を書き込むため、ログ容量を見積もってください。RUST_LOG=warn,access_log=off は引き続きアクセスログだけを無効化します。

payload() がフォームボディと JSON スカラーを返すように — フォームボディは SUPERGLOBALS_ENABLED=false でもパースされるようになり、"hi"、42、false という JSON ボディは null や $default ではなくその値を返します。null が意味するのは、空のボディ、パースされない Content-Type、不正な JSON、またはリテラルの null だけになりました。

クライアントの切断をキャンセルとして計上 — oxphp_request_cancelled_total{reason="client_abort"} が増加し始める一方で admission_refused_total{reason="wait_timeout"} は減少し、切断したクライアントに対する Connection error の ERROR 行は debug になりました。どちらかに対するアラートは見直してください。

プロファイル実行をトリガー別に、独自の実行 ID で記録 — oxphp_profiler_runs_total{source} は header、cookie、query、sample と新しい sdk に分かれるようになりました。実行 ID は <ts_ms>-<req_id_prefix>-<rand4> の形式になるため、/__profiler/runs/{id} はリクエスト ID に対しては 404 を返します。/__profiler/runs で request_id から検索してください。

PROFILER_MAX_DEPTH が適用されるように — これまではどこにも適用されていませんでした。デフォルトは 0(無制限)になり、32 を超える値は起動時の WARN とともに切り詰められ、上限より深い呼び出しはプロファイルに含まれません。

COMPRESSION_LEVEL は非推奨 — COMPRESSION_BROTLI_LEVEL を使用してください。古い名前も起動時の WARN とともに引き続き動作します。Zstandard と gzip も提供されるようになったため、0 以外の値は Brotli のみを意味しなくなりました。/config は compression_level の代わりに brotli_level、gzip_level、zstd_level を報告します。

SHARED_SOFT_LIMIT_RATIO と SHARED_POISON_STRICT は無視されます — どちらも一度も読み取られていませんでした。現在はどちらも起動時に WARN をログに出し、limits.soft_ratio と diagnostics.poison_strict は /__ox_shared/summary から削除されました。アラートは oxphp_shared_capacity_saturation に対して設定してください。終端の Once には Once\FailureMode::Poison を渡します。

oxphp_queue_wait_us の値が小さくなります — ワーカーがリクエストを受け取った時点で計測を止めるようになり、リクエストあたり約 0.1 ms 短くなります。1 ミリ秒未満のアラートしきい値は発火しにくくなります。oxphp_request_duration_us は変わりません。

ADDED

Zstandard 圧縮 — 動的レスポンスは、それを受け入れるクライアントに zstd(レベル 6)で送られます。Brotli より小さく、CPU は半分未満です。COMPRESSION_ENCODINGS(デフォルト br,zstd,gzip、または off)でエンコーディングを選択し、COMPRESSION_BROTLI_LEVEL(5)、COMPRESSION_ZSTD_LEVEL(6)、COMPRESSION_GZIP_LEVEL(6)を併用します。

Brotli 非対応クライアント向けの gzip — 平文 HTTP の Chromium やほとんどの CLI ツールは、これまで非圧縮のボディを受け取っていました。Accept-Encoding は RFC 9110 に従って q 値で順位付けされるようになり、q=0 は拒否として扱われます。その結果、3 MiB 未満の圧縮可能なファイルは Range リクエストに完全な 200 で応答します。

事前圧縮された静的キャッシュ — キャッシュされた静的ファイルには、最高品質の Brotli コピーがバックグラウンドで一度だけ作成されます。リクエストごとの出力より 8〜12% 小さく、同じキャッシュ予算内に収まります。

プール停滞の可視化 — oxphp_pool_stalled、oxphp_queue_depth、oxphp_queue_capacity、oxphp_admission_slots_available が、キューから処理を取り出さなくなったプールを示します。停滞中は WARN、処理が再び進むと INFO が出力されます(ワーカーモード)。

ログでの過負荷表示 — 529 で切り捨てられたリクエストは、エピソードごとに WARN を出力するようになりました(繰り返しは最大 1 分に 1 回)。拒否のないまま 1 分が経過すると、合計を示す INFO が出力されます。

MAX_CONNECTIONS の枯渇を報告 — 停止した accept ループは MAX_CONNECTIONS を名指しする WARN と、再開時の INFO をログに出します。oxphp_accept_stalled と oxphp_accept_stalls_total がこれをエクスポートします。

oxphp_admission_wait_wasted_total — どのワーカーもリクエストを開始しなかった間に発生した wait_timeout 拒否を数えます。これは QUEUE_WAIT_TIMEOUT_MS が長すぎるか、アプリケーションが自身のサーバーを呼び出しているサインです。

oxphp_worker_request_fibers_active — ワーカーごとのリクエストファイバー数です。256 はワーカーが処理の受け付けを止めたことを意味し、アイドル時に 0 に戻らない値はファイバーリークです(ワーカーモード)。

RUNTIME_HOOKS の可視化 — /config と oxphp_server_info() は、runtime_hooks をインストール済みカテゴリのパース済みリスト(例: ["sleep","streams"])として報告するため、変数のスペルミスは [] として読み取れます。

CHANGED

適応型のキュー待機予算 — QUEUE_WAIT_TIMEOUT_MS は上限になりました。開始された処理の 4 分の 1 以上が既に去ったクライアント向けだった間、サーバーは待機時間を半分にし(下限は 10 ms または上限の 1/64)、キューが空になると元に戻します。oxphp_admission_wait_budget_us、oxphp_admission_wait_budget_ceiling_us、oxphp_abandoned_work_total がこれを示します。

Brotli のリクエストごとの品質を 5 に — 以前の 4 は、gzip より多くの CPU を使いながら gzip より大きな出力を生成していました。4 KB を超える Brotli ボディは、ブロッキングプール上で圧縮されるようになりました。COMPRESSION_BROTLI_LEVEL=4 で以前の挙動に戻せます。

エラーの種類に応じた接続終了のログレベル — メッセージ中の "timeout" という単語で決めるのではなく、クライアントによる接続のクローズは debug、サーバーが強制したタイムアウトは warn のまま、それ以外は error のままになりました。

FIXED

ワーカーモード: リクエスト間のセッションリーク — 別のリクエストが中断されている間に受け付けられたリクエストは、先行するリクエストのセッション内で開始され、session_write_close() の後にはその ID を引き継いで Set-Cookie で受け取ることがありました。現在は、すべてのリクエストの前にセッションが書き込まれて閉じられ、$_SESSION が削除されます。

永続接続が誤ったリクエストに応答する問題 — ワーカーモード以外では、pfsockopen() や永続 Predis でコマンドを送信してから応答を読むまでの間にクライアントの中断がリクエストを終了させることがあり、再起動まで接続の同期がずれていました。現在は PHP-FPM と同様に、去ったクライアントへの対応は次の書き込み時にのみ行われます。

ワーカーモード: 中断後にページの構成が変わる問題 — 中断されたリクエストは finally ブロックをスキップし、オペコードの境界で捕捉された場合はワーカー上のすべてのデストラクタを無効化していたため、再帰ガードが設定されたままになり、ページから静かにブロックが失われていました。リクエストは最後まで実行されるようになりました。

ワーカーモード: リクエストごとの ini 値 — ini_set()、ignore_user_abort()、error_reporting() による変更が、並行して実行中のリクエストへリークしたり、ビジーなワーカーで持続したりすることはなくなりました。これらは中断されたリクエストとともに移動し、リクエストの終了時に復元されます。また、リクエストが同じファイバー上の前のリクエストが残した error_reporting レベルで開始されることもなくなりました。

RUNTIME_HOOKS=streams 下の永続 PDO — 重なり合う PDO::ATTR_PERSISTENT コンストラクターが、使用中のリクエストの下で接続を閉じる(致命的エラーまたはセグフォルト)ことはなくなり、別のリクエストの死活確認 ping 中のクエリが 2006 MySQL server has gone away で接続を落とすこともなくなりました。

ワーカーモード: 致命的エラーでワーカーが永久に空転しないように — プロファイラーや #[Trace] が有効な場合、1 つのワーカーで 2 回の致命的エラーが起きると、プールがアイドルに見える間もオブザーバーチェーンが CPU 100% でループすることがありました。別の致命的エラーからの回復中に発生した致命的エラーが、ワーカー上のすべてのリクエストを落とすこともなくなりました。

ワーカーモード: __destruct を持つ未捕捉の例外 — デストラクタを持つ例外クラスが 500 を致命的エラーに変え、それがリサイクルにカウントされ(31 リクエストで 8 ワーカーが退役)、ワーカー上のすべてのデストラクタを無効化し、例外をリークさせていました。

ワーカーモード: クライアントの切断でワーカーがリサイクルされないように — ignore_user_abort() の後や受け取り中の中断は致命的エラーとして読み取られ、プールを 3 つずつ入れ替えていました。現在はブレーカーがカウントする各リクエストが WARN をログに出し、ブレーカーによる終了も WARN でログに出力されます。

ワーカーモード: 書き込みで終了したストリーム — そのようなストリームがフレームの変数をワーカーの生存期間にわたってリークすることはなくなり、そのクリーンアップ中のデストラクタでの致命的エラーはエラーブレーカーにカウントされるようになりました。

QUEUE_WAIT_TIMEOUT_MS がキュー内のリクエストを制限するように — 予算はワーカーがリクエストを受け取った時点でしか確認されていなかったため、停滞したプールはキュー内のリクエストに一切応答しませんでした。現在は期限に達すると Retry-After: 3 付きの 529 を返します。

去ったクライアントをキューに入れたり拒否したりしないように — ワーカーモードは、キュー内にいる間にクライアントが去ったリクエストを実行しなくなり、クライアントが去った後に予算が尽きた場合は 529 ではなくキャンセルとして 499 を返します。client_abort は、ワーカーに受け取られる前に去ったクライアントを数えるようになりました。

/health/readiness がプールの健全性を反映するように — エグゼキューターのチェックは常に成功していました。生きているワーカースレッドのないプールや、ワーカーモードで 1 分間スタックしたプールは 503 を返すようになりました。過負荷では引き続き返しません。

oxphp_apm_trace() がコールバックを実行するように — これまではコールバックを呼び出さずに null を返していました。現在はコールバックが子スパン内で実行され、スパン ID を受け取り、その値を返します。戻り値の型は mixed になりました。

Error 系クラスのメッセージの欠落 — TypeError、ValueError をはじめとする Error 階層の例外が、メッセージとコードなしでスパンと Once のポイズン理由に届いていました。

JIT 下の mixed 戻り値型 — oxphp_async_await() や Shared\Map::get() などの関数が不正な形式の mixed 型を宣言していたため、トレーシング JIT 下のホットループは返されたすべての値をリークし、Reflection の getReturnType() はセグフォルトしていました。

oxphp_worker_memory_bytes がメモリを報告するように — ずっと 0 を示していました。現在は WORKER_MAX_MEMORY_MIB の判定対象と同じ PHP ヒープを、各リクエストの終了時にサンプリングして報告します。

ワーカーモード: リクエスト間でプロファイルが混ざる問題 — 1 つのスレッド上の中断されたリクエストがプロファイラーと APM のスパン状態を共有していたため、誤ったリクエストに帰属するプロファイルや #[Trace]/#[SlowThreshold] スパンが生成されていました。現在は各リクエストが独自の状態を持ちます。

プロファイラーの切り詰めと OXPROF — PROFILER_MAX_SPANS に達した実行は、index.json、/__profiler/stats、oxphp_profiler_truncated_total で truncated としてフラグが立てられ、ドキュメントに記載された OXPROF Cookie で実行が開始されるようになりました。

Shared\Mutex::withLock() の待機を終了できるように — ロックを待っているリクエストは 100 ms ごとに max_execution_time、シャットダウン、タスクのキャンセルに譲るようになったため、逆順のロックが再起動まで 2 つのワーカーを占有することはなくなりました。

RUNTIME_HOOKS=streams と後から読み込まれる拡張 — extension.ini より後にソートされる ini ファイルで有効化された mysqli と phpredis も、他と同様に保護されるようになりました。また RUNTIME_HOOKS は、リクエストごとのロックなしの getenv() ではなく、起動時に一度だけ読み取られます。

圧縮のエッジケース — 1 MiB から 3 MiB の静的ファイルが圧縮されるようになり、大文字小文字が混在した Content-Type の値も圧縮され、圧縮可能なレスポンスの identity コピーは Vary: Accept-Encoding を持ち、UTF-8 でない圧縮設定は起動時に失敗します。

Shared\Counter、Atomic、Flag の容量エラー — これらのコンストラクターは、他のすべての Shared 型と同様に、SHARED_MAX_ENTRIES や SHARED_MAX_BYTES を超えると Shared\CapacityException をスローするようになりました。

ドキュメントの修正 — SHARED_LOCK_DIAGNOSTICS の値は off、warn(デフォルト)、strict です。リクエスト数によるワーカーのリサイクルは記載されなくなりました。ワーカーループは動的プールの退役時にも終了します。504 は max_execution_time から発生します。加えて、グレースフルシャットダウンの上限、ランタイムイメージの ini 構成、/config のキー順序を修正しました。

SECURITY

rustls 0.23.45 — 暗号化境界をまたいで平文のハンドシェイクメッセージを受け入れていた TLS 1.3 ハンドシェイクの欠陥(RUSTSEC-2026-0285)を修正します。TLS リスナーと、OTLP およびプロファイラーのエクスポーターが影響を受けていました。トランスクリプトは認証されたままです。

v0.11.0
2026-08-20
⚠ breaking changes
Release notes →
BREAKING

FRAME_OPTIONS のデフォルトが SAMEORIGIN に — クロスオリジンのフレーム化は引き続きブロックされ、同一オリジンの埋め込み(管理画面のプレビュー、ダッシュボードのウィジェット)は追加設定なしで動作します。組み込みの DENY に依存していたデプロイメントは、明示的に設定する必要があります。

ワーカーモード: リクエスト内でのイベントループ禁止 — ワーカーモードのリクエストはファイバーになり、Revolt はその中でループを実行することを拒否します。ハンドラー内の Revolt\EventLoop::run() はスローします。ワーカーのブートストラップへ移動してください。defer()、delay()、getSuspension() は引き続き動作します。

ワーカーモード: リクエストの状態はリクエストとともに終わります — ini_set()、set_time_limit()、error_reporting() はリクエスト間でブートストラップ時のベースラインへロールバックされます。filter_input() は問い合わせているリクエストのためだけに答えます。リクエストを越えて保持された php://input ハンドルは、別のリクエストのボディではなく、閉じられたリソースとして読み取られます。

query() と cookie() が値をデコードします — rawurldecode() で補っていたコードは二重デコードになるため、余分な呼び出しを取り除く必要があります。空で送られた Cookie は null ではなく空文字列として読み取られ、query() がパースするパラメータは最大 1000 個です。

MAX_CONNECTIONS / QUEUE_CAPACITY の厳格なパース — 不正な形式の値は、黙ってデフォルトにフォールバックする代わりに起動を中止するようになりました。また QUEUE_CAPACITY=0 は容量ゼロのキューではなく自動(ワーカー数 × 128)を意味するようになりました。

ADDED

キューの待機予算 — 満杯のキューに到着したリクエストは、即座に 529 になる代わりに QUEUE_WAIT_TIMEOUT_MS(デフォルト 1 秒)までスロットの空きを待つようになりました。到着時に刻印される 1 つの期限が、スロット待ちとキュー内の待機をカバーします。QUEUE_MAX_WAITING と QUEUE_MAX_WAITING_BYTES は、待機セットをリクエスト数とボディのバイト数で制限します。

RUNTIME_HOOKS=streams — ブロッキングする tcp:// ソケット読み取りと stream_select() が、ワーカースレッドではなくファイバーを中断するようになり、コード変更なしで mysqlnd(PDO_MySQL、mysqli)、phpredis、HTTP ストリームラッパーをカバーします。並行ファイバー間で共有される接続は交換ごとにクレームされるため、リクエストが互いの応答を読むことはできません。

oxphp_admission_refused_total — ワーカーに到達する前に切り捨てられたリクエストがサーバー側に痕跡を残すようになりました。reason ラベルが、4 つの過負荷理由(529)を shutting_down(503)と pool_unavailable(500)から区別します。

ルートスパンへの自動例外キャプチャ — 5xx で応答する未処理例外や致命的エラーが、4 つの配信モードすべてで、リクエストのルートスパンに exception イベント(type、message、stacktrace、file、line)を付加するようになりました — 属性も oxphp_apm_error() の呼び出しも不要です。

データベーススパン属性 — PDO と mysqli のスパンが db.statement(リテラル値は ? に難読化)、db.operation、db.system、server.address/port、db.name、oxphp.db.slow を持つようになりました。mysqli_stmt::prepare が 34 番目の関数としてフック対象に加わりました。

CHANGED

oxphp_queue_wait_us が 1 秒まで到達 — 4 つの新しいバケット(100 ミリ秒から 1 秒)が、デフォルト予算付近の待機を +Inf にまとめる代わりに定量化します。拒否されたリクエストはヒストグラムから除外されます。

ワーカーのリサイクルはワーカーを壊すものだけを数えます — 3 回連続失敗によるリサイクルにカウントされるのは、致命的エラー、メモリ不足、スタックオーバーフローだけになりました。未捕捉の例外とキャンセルされたリクエストは中立です。ブレーカーは、これまで一度も発火しなかった同期ディスパッチパスでも作動するようになりました。

config --check のサイジング勧告 — PHP_WORKERS + QUEUE_CAPACITY + QUEUE_MAX_WAITING が MAX_CONNECTIONS に達すると、失敗にはせず警告します。これを超えると、過負荷は 529 ではなく無応答になります。

ドキュメントのフラット化 — リポジトリのドキュメントが docs/en/ から docs/ 直下へ移動しました。ロシア語と中国語の翻訳は、フランス語、ポーランド語、日本語が既に置かれている oxphp.dev へ移りました。

FIXED

ワーカーモード: $_POST と $_FILES が投入されるように — どちらもワーカーが処理するすべてのリクエストで空だったため、HTML フォームとファイルアップロードは静かに動作していませんでした。php://input を読む JSON API は影響を受けておらず、それがこの問題が隠れたままだった理由です。

filter_input() のリクエスト間リーク — filter 拡張独自の入力コピーがワーカーの生存期間にわたって蓄積し、あるリクエストに別のクライアントのクエリ値、セッション Cookie、ボディフィールドで答えていました。現在は各リクエストの開始時に返却されます。

スリープタイマーのバースト — 1 回のスケジューラポーリングで 32 個を超えるタイマーが満了すると、あふれた待機側は永久に置き去りにされていました。大きなバーストは 32 個ずつのバッチで起床するようになりました。

共有接続のクローズ — 別のリクエストが読み取りで待機している接続への fclose()、$mysqli->close()、再接続ヘルパーは、そのリクエストを解放済みメモリの中へ再開させていました。現在は、何が起きたかを示す 500 で即座に失敗します。

致命的エラーが共有状態を消さないように — 中断されたリクエストの巻き戻しが include 変数を二重解放し、ワーカーモードアプリケーションがリクエスト間の状態を保持する配列やオブジェクトを静かにリセットしていました。

非同期プールのペーシング — タスクの受け渡しは固定 1 ミリ秒のアイドルタイマーに律速されなくなりました。Shared\Channel の転送は 1 件あたり 0.69 ミリ秒から 0.14 ミリ秒へ、アイドル時のウェイクアップは 777 回/秒から 92 回/秒へ低下しました。

一時停止したリクエストが開始時刻を保持するように — $request->startTime() と oxphp_server_info()[request_time] が、中断の後に 0 や並行リクエストの値を読むことはなくなりました。

ボディ読み取りの警告が正しいリクエストに届くように — post_max_size とアップロード上限の警告は、ボディを送ったリクエストの内側で発火するようになり、エラーハンドラーはそのリクエストのスーパーグローバルを見ます。スローするハンドラーは、何も返さず接続を閉じる代わりに 500 で応答します。

v0.10.0
2026-07-12
⚠ breaking changes
Release notes →
BREAKING

デフォルトでの権限降格 — www-data アカウントが存在する環境で root として起動された場合、サーバーはリスナーをバインドしたあと、トラフィックを処理する前に www-data へ降格します。公式イメージはもはや root では動作しません。以前の挙動を維持するには --user=root を渡してください。

フェイルクローズな TLS 設定 — 中途半端に設定されたペア(TLS_KEY のない TLS_CERT、またはその逆)や UTF-8 でない証明書・鍵の値は、平文の HTTP を黙って提供する代わりに、問題のある変数名を示して起動を中止するようになりました。

ASYNC_* / SUPERGLOBALS の厳格なパース — ASYNC_WORKERS、ASYNC_QUEUE_CAPACITY、ASYNC_MAX_FIBERS に不正な値が入っている場合、黙ってデフォルトにフォールバックする代わりに起動エラーになります。また oxphp run は不正な SUPERGLOBALS_ENABLED を無視しなくなりました。

Content-Type のない QUERY は 415 ではなく 400 を返します — サーバーが生成する唯一の 415 はなくなりました。このケース向けにカスタマイズした ERROR_PAGES_DIR/415.html は 400.html にリネームする必要があります。

プロファイラーのエクスポートエンベロープの解決 — PROFILER_EXPORT_XHGUI=false は /run/import URL に対しても優先されるようになり、エンベロープの自動検出は各ツールの正規パス(xhgui は /run/import、Buggregator は /api/profiler/store)のみをキーとするようになりました。あいまいな "xhgui" 部分文字列はもう使いません。

ADDED

Buggregator へのプロファイラーエクスポート — プロファイラーの HTTP プッシュは、Buggregator の POST /api/profiler/store が期待するエンベロープで xhprof データをラップします。PROFILER_EXPORT_BUGGREGATOR で切り替えられ、tags と app_name は環境変数で設定します。

TLS 経由の OTLP トレースエクスポート — https:// の OTEL_EXPORTER_OTLP_ENDPOINT は、gRPC と http/protobuf の両方で暗号化してエクスポートされ、rustls を介してシステムのトラストストアに対して検証されるようになりました。ランタイムイメージは CA バンドルを同梱している必要があります(公式イメージは同梱しています)。

APM スパンでの完全な例外データ — 例外スパンイベントは exception.type に加えて exception.message と exception.stacktrace を持つようになりました。これにより New Relic Errors Inbox、Jaeger、Tempo がメッセージとスタックを表示します。OTEL_APM_MESSAGE_MAX_BYTES / OTEL_APM_STACKTRACE_MAX_BYTES で上限が設定されます。

長時間接続のグレースフルなドレイン — SIGTERM を受け取ると、サーバーは GOAWAY を送信し、アイドルの keep-alive を閉じ、開いている SSE/flush ストリームを速やかに終了します。DRAIN_TIMEOUT_SECONDS のデフォルトは、Kubernetes の 30 秒の猶予期間に収まるよう 30 → 25 に引き下げられました。

TLS_MIN_VERSION — 受け入れる最小のプロトコルバージョン。1.2(デフォルト)または 1.3 です。それ以外の値は — "TLSv1.2" のような異なる構文も含めて — 致命的な起動エラーになります。これは TLS が無効な場合でも検証され、/config では tls_min_version として報告されます。

CHANGED

低コストな STATIC_REVALIDATE — ファイルごとに stat() は最大でも 3 秒に 1 回だけ実行されます。静的ファイルのヒットは最大でも 1 回のシステムコールでメモリから提供され、ディスク上の変更は 3 秒以内に反映されます。デフォルトは無効のままです。

より明確な TLS 起動エラー — タイプミスのあるパスは、素の OS エラーではなく "TLS_CERT: cannot read /etc/ssl/cert.pem: No such file or directory" というエラーで失敗するようになりました。PEM のパースエラーや鍵の不一致エラーも同じ形式のプレフィックスが付きます。

FIXED

ワーカーモードでのキャンセル対象の特定 — クライアントの中断やリクエストタイムアウトが、実際にキャンセルされるべきリクエストではなく、ワーカーで最後に受け付けられたリクエストをキャンセルしてしまい、無関係な処理中のリクエストを中断させることがありました。

finish_request() と多重化されたストリーム — ワーカー上のいずれかのリクエストが oxphp_finish_request() を呼び出すと、そのワーカーに多重化されている他のすべてのストリームが、以降の oxphp_stream_flush() の出力を黙って破棄していました。

v0.9.0
2026-06-25
⚠ breaking changes
Release notes →
BREAKING

タイムアウトした await がタスクをキャンセルします — 放棄されたタスクは強制的にキャンセルされます。パークされたタスクはキャンセル処理に入って再開し、CPU バウンドなタスクはオペコードの境界で中断されます。以前はリクエストの終了まで実行され続けていました。

ADDED

ネストした oxphp_async() — 非同期タスクは自身のタスクをディスパッチし、ワーカースレッドをブロックすることなく await_all / await_any / await_race で中断できます。「ネストした非同期は不可」という制限はなくなりました。

処理中タスクの上限 — ASYNC_MAX_FIBERS(デフォルト 256)はキュー内および実行中のタスク数を制限します。上限を超えたディスパッチは AsyncException で即座に拒否されるため、ファンアウトがデッドロックすることはありません。

内部サーバーの許可リスト — INTERNAL_ALLOW_IPS は /metrics と /config 向けの CIDR 許可リストです。ヘルスエンドポイントはオーケストレーターから引き続きアクセス可能です。

プロファイラーのサンプリング除外 — PROFILER_EXCLUDE_PATHS はフレームワークの自己トラフィック(/_profiler、/_wdt)を統計的サンプリングから除外します。明示的なトリガーは引き続きプロファイリングを行います。

FIXED

チャネル送信側のウェイクアップ — 満杯の Shared\Channel でパークされたファイバーの送信待機は、recv_blocking がスロットを解放すると起床するようになりました。これにより sendTimeout は飽和状態で誤って発火しなくなりました。

非同期処理のエッジケース — グレースフルシャットダウン時のクラッシュ、クローズ済みの Promise に対する await が reject されずに停止する問題、await_all が早期に中断した際に Promise を実行したまま放置する問題を修正しました。

v0.8.0
2026-06-13
⚠ breaking changes
Release notes →
BREAKING

ワーカーのルーティング契約 — 静的アセットはディスクから、それ以外はすべてワーカーの ENTRY_FILE へ送られます。任意の .php ファイル、ディレクトリインデックス、ルートの index.php フォールバックは、リクエストごとに解決されなくなりました。

正直な CGI PATH_INFO — Framework モードでは、PATH_INFO は明示的な /index.php/extra リクエストに対してのみ設定されます。PHP_SELF はフロントコントローラーを報告します。ルーティングは REQUEST_URI で行ってください。

ADDED

Range リクエスト(RFC 9110) — 静的ファイルに対する 206 Partial Content により、動画のシーク、再開可能なダウンロード、PDF の部分読み込みが可能になります。If-Range は強い検証子とともに尊重されます。

HTTP/2 の接続制限 — 5 つの環境変数が接続ごとのリソース使用量を制限します。解決された値は起動時にログ出力されます。

CHANGED

静的ファイルの強い ETag — "<size>-<mtime>" が W/"…" を置き換えます。これは If-Range に必要です。圧縮された表現は弱いタグを維持します。

FIXED

アプリケーションのヘッダーが優先されます — PHP から設定された X-Content-Type-Options、X-Frame-Options、CSP は上書きされなくなりました。サーバーの値は、存在しない場合にのみ挿入されるフォールバックです。

ストリーミングと圧縮 — flush() でストリーミングされるレスポンスは、スクリプト終了まで brotli レイヤーで完全にバッファリングされる代わりに、非圧縮のまま通過するようになりました。

SECURITY

HTTP/2 の DoS 対策の強化 — Rapid Reset(CVE-2023-44487)、HPACK ボム、ウィンドウストール攻撃が制限されます。死んだ接続は PING/PONG キープアライブによって回収されます。

v0.7.0
2026-06-05
⚠ breaking changes
Release notes →
BREAKING

アクセスログの remote_ip — JSON フィールドの remote_addr は remote_ip にリネームされ、ポートなしの IP を保持します。これは nginx/Apache/Caddy と同じです。

ADDED

PHP スタイルの CLI — oxphp run script.php は、完全なエンジンを背後に持つ CLI セマンティクスでファイルを実行します。#!/usr/bin/env oxphp のスクリプトは直接実行でき、-d による ini オーバーライドも受け付けます。

--user による権限降格 — 特権ポートを root としてバインドしたあと、リクエストを処理するスレッドが生成される前に、非特権ユーザーへ不可逆的に降格します。

転送されたポートの尊重 — TRUSTED_PROXIES の背後では、SERVER_PORT が X-Forwarded-Port を尊重します。REMOTE_PORT は、プロキシが提供する場合に RFC 7239 の for= から埋められます。

FIXED

デコレーターのクラッシュ — 関数デコレーターの経路にあった 2 つの SIGSEGV(解放済みのインスタンスキャッシュ、リサイズをまたぐダングリング zval)はなくなりました。ネストのオーバーフローは StackOverflowException をスローするようになりました。

プロファイラー属性が引数を尊重します — #[SlowThreshold(ms:)]、#[MemoryThreshold(kb:)]、#[Mark(label:)] は、共有のデフォルト値に潰れることがなくなりました。APM のスパンイベントは OpenTelemetry に届きます。

アップロードオブジェクト API の接続 — Request::file() / files() は、あらゆる $_FILES の形状に対して null ではなく UploadedFile オブジェクトを返すようになりました。

v0.6.0
2026-05-28
⚠ breaking changes
Release notes →
BREAKING

三分岐の待機 API — Channel と Mutex はすべての待機を try* / 素の動詞 / *Timeout(int $ms) に分割し、mixed|null ではなく値型の SendResult / RecvResult を返します。

await_any は Promise.any を意味します — 最初に fulfilled になったものが優先されます。すべてが rejected の場合は AggregateAsyncException をスローします。以前の「最初に settled したもの」という挙動は、現在は oxphp_async_await_race() です。

正直なキャンセルステータス — タイムアウトは 504、グレースフルなドレインは 503 + Retry-After、クライアントの切断は 499(ログのみ)になります。スーパーバイザーによる kill とユーザーランドのキャンセルは 500 のままです。

シムなしのリネーム — Async\Exception → AsyncException、Shared\Exception → SharedException。Worker は get* プレフィックスを廃止し、Counter は最小限(add/set)になり、Flag は Atomic を反映し、Once は getOrInit()/status() を獲得します。

ADDED

Shared\Atomic + Ordering — 汎用の int64 アトミックです。明示的なメモリオーダリングを伴う load/store/swap/CAS/fetch* を備え、std::sync::atomic と一対一で対応します。

Shared\Registry — 名前をキーとするプロセスグローバルなハンドルです。すべてのワーカーが同じエントリに収束し、ファクトリは最大でも 1 回だけ実行されます。

REMOVED

タイムアウトの発生源を一本化 — REQUEST_TIMEOUT_SECONDS と oxphp_request_heartbeat() はなくなりました。max_execution_time と set_time_limit() は残ります。

FIXED

SSE における connection_aborted() — ストリーム途中での切断後に true を返すようになり、while (!connection_aborted()) のループがきれいに終了します。

PERFORMANCE

Shared\* のホットパス — 操作ごとのオーバーヘッドは、素のアトミックロードに対する criterion のノイズの範囲に収まります。8 スレッドで以前の形状と比べて幾何平均で約 4.7 倍です。

v0.5.0
2026-05-05
⚠ breaking changes
Release notes →
BREAKING

厳格なブール環境変数のパース — 受け付けるのは on/true/1/yes と off/false/0/no のみです。ture のようなタイプミスは、黙ってデフォルトにフォールバックする代わりに起動を拒否します。

ADDED

ENTRY_FILE + WORKER_MODE_ENABLED — 1 つの変数が拡張子によってルーティングモードを選択し、明示的なトグルが永続ワーカーを有効化します。INDEX_FILE / WORKER_FILE は非推奨のエイリアスになります。

OxPHP\Server\Worker クラス — ランタイムのイントロスペクションハンドルです。id、リクエスト数、メモリ、RSS を提供し、さらに scheduleExit() によるアプリケーション主導のリサイクルも可能です。

CHANGED

静的キャッシュのリネーム — STATIC_CACHE_TTL → STATIC_MAX_AGE、STATIC_CACHE → STATIC_REVALIDATE(極性は反転)。デフォルトは変更ありません。

DEPRECATED

WORKER_MAX_REQUESTS — パースはされますが、起動時の WARN とともに無視されます。WORKER_MAX_MEMORY_MIB または scheduleExit() に移行してください。

v0.4.0
2026-05-02
Release notes →
ADDED

PHP 8.5 イメージ — :php8.5、:php8.5-alpine、パッチ固定のタグが 8.4 と並んで提供されます。latest はソーク期間が終わるまで 8.4 のままです。

FIXED

リクエストをまたぐチャンクドストリーミング — ブリッジのリクエストごとのコンテキストがワーカーのリクエスト間でリークし、最初の flush 以降にチャンクドの Transfer-Encoding が失われていました。

ユーザーファイバー内の Shared\* — Fiber::start() 内の Channel::recv()/send() は、oxphp-fiber の中断経路を通って fiber_await rc=1 でクラッシュすることがなくなりました。

プラグイン API の名前付き引数 — プラグインが定義するクラスや関数が、実際のパラメータ名を公開するようになりました。名前付き引数の呼び出しが機能します。

非同期クロージャ内の共有ハンドル — oxphp_async() クロージャがキャプチャした Channel/Map/Pool は、受信側スレッドで null として届くことがなくなりました。

v0.3.0
2026-04-22
⚠ breaking changes
Release notes →
BREAKING

Async 名前空間 — 非同期クラスは OxPHP\Async\ の下に移動しました。関数群は現在 plugin-async で提供されます。

ADDED

7 つの Shared\* プリミティブ — Counter、Flag、Once、Mutex、Channel、Map、Pool。ワーカー間で共有されるアトミックな状態を提供し、レジストリによる可観測性とデッドロック検出器を備えます。

リクエストごとのプロファイラー — xhprof / speedscope / pprof / collapsed 形式の出力、cookie・header・query・統計的トリガー、xhgui エンベロープでの HTTP プッシュを提供します。

APM の自動インストルメンテーション — plugin-apm は PHP のトレーシング SDK とエラーキャプチャを備えます。非同期および APM のサブシステムは Rust プラグインへ移行しました。

本番環境向けの統合 — TRUSTED_PROXIES(RFC 7239)、Kubernetes の /readyz + /livez、セキュリティヘッダー、IP ごとのレート制限。すべて環境変数で設定します。

PERFORMANCE

Pool のホットパス — 競合のない acquire/release は Docker 内で約 0.9 µs、スレッドごとのアフィニティを備えます。

v0.2.0
2026-03-27
Release notes →
ADDED

ファイバー多重化 — 単一のワーカースレッド内での並行 I/O、W3C Trace Context による分散トレーシングと OpenTelemetry エクスポートを提供します。

oxphp_async() の Promise — 専用のスレッドプール上での PHP の並列実行を提供します。

HTTP オブジェクト API — 遅延アクセサを備えた OxPHP\Http\Request と、属性ベースのデコレーターシステムを提供します。

CHANGED

ポートとバックプレッシャー — デフォルトポートは 8080 → 80(TLS 有効時は 443)、バックプレッシャーのレスポンスは 503 → 529 になりました。

FIXED

CGI の正確性 — SERVER_PROTOCOL が実際の HTTP バージョンを反映し、REQUEST_TIME はリクエスト開始時刻になり、IPv6 の Host パース、リクエストタイムアウト時の 408 に対応しました。

v0.1.0
2026-03-08
Release notes →
ADDED

非同期 HTTP コア — グレースフルシャットダウンを備えた Hyper + Tokio サーバー、完全なスーパーグローバルを備えたカスタム oxphp SAPI、上限付きキューと 529 バックプレッシャーを備えた ZTS ワーカープールを提供します。

3 つのルーティングモード — Traditional、Framework、SPA。インメモリキャッシュ、ETag/304、brotli、TLS を備えた静的ファイルを提供します。

ワーカーモード — oxphp_worker() による永続ワーカー、早期レスポンスの完了、SSE ストリーミング、協調的タイムアウトを提供します。

可観測性 — /metrics、/health、/config、構造化された JSON アクセスログ、リクエストID を提供します。

Docker ファースト — マルチステージの Alpine ビルド、GHCR 上の amd64/arm64 イメージを提供します。