Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Non-worker mode: thread-pool deadlock ~5s after startup under concurrent load (1.12.6, ZTS) — threads wedged in regularThread.waitForRequest / transitionToNewHandler

オープン
#2,558 コメント 5 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@cyppe がすでに取り組んでいます。

2026年8月7日 から。

  • #2595 @cyppe による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
42/100
issue の種類
バグ
明瞭さ
説明が足りない
活発さ
静か
技術スタック
go, php
領域
backend

調査の方向性

threadregular.go の waitForRequest と handleRequestWithRegularPHPThreads から始め、続いて phpthread.go の transitionToNewHandler と internal/state/state.go の ThreadState.WaitFor を追跡してください。可能であれば起動時のトラフィックバーストを再現し、参照されている goroutine の経路を調査してください。この負荷で PHP プールが行き詰まらなくなり、その障害を回帰テストでカバーできれば完了です。

索引モデルが issue の本文から書いたものです。

説明

Summary

On FrankenPHP 1.12.6 (PHP 8.4.23 ZTS, Caddy 2.11.4), in classic non-worker mode, a container intermittently deadlocks its entire PHP thread pool a few seconds after a clean startup, under the burst of concurrent traffic that a rolling deploy sends the instant the server binds. Once wedged: :8000 stops serving all requests, the Caddy admin endpoint :2019 stays fully responsive, the container never recovers (a curl :8000/up health check hangs forever), and only a restart clears it. It's probabilistic — one of two identical hosts wedges on a given deploy while the other is fine.

This is in the same area as #2250 (ThreadState.WaitFor / go_frankenphp_main_thread_is_ready), but the trigger is different: here the server binds, logs server running, and serves real traffic for ~5 s before the whole pool deadlocks — not a never-binds init hang.

Environment

  • FrankenPHP v1.12.6, Caddy v2.11.4, PHP 8.4.23 (ZTS) — image dunglas/frankenphp:1.12.6-php8.4-bookworm (amd64)
  • Classic / regular mode (php_server; no worker directive)
  • frankenphp { num_threads 300 max_threads 300 max_wait_time 10s } — fixed thread count. Pinning num_threads == max_threads to disable dynamic scaling did not prevent it.
  • Docker, 32-core / ~60 GB host, non-privileged, listening on :8000; a reverse proxy cuts live traffic over to the new container immediately on deploy.
  • Also reproduced on 1.12.4; upgrading 1.12.4 → 1.12.6 did not fix it.

Timeline (from the container's own logs)

"FrankenPHP started 🐘"   php_version=8.4.23  num_threads=300  max_threads=300  max_requests=0
"server running"          name=srv0
... ~5 seconds of normal request handling (HTTP 200s) ...
<no request handled after ~T+5s; :8000 wedged; :2019 still answering; never recovers>

Goroutine profile at the deadlock (462 goroutines)

Two dominant clusters (?debug=1):

~151 PHP threads stuck in waitForRequest, reached via a cgo callback (locked to thread):

151 @ ...
#  github.com/dunglas/frankenphp.(*regularThread).waitForRequest+0x346          /go/src/app/threadregular.go:110
#  github.com/dunglas/frankenphp.(*regularThread).beforeScriptExecution+0x584   /go/src/app/threadregular.go:53
#  github.com/dunglas/frankenphp.(*phpThread).transitionToNewHandler+0x66       /go/src/app/phpthread.go:177
#  github.com/dunglas/frankenphp.(*inactiveThread).beforeScriptExecution+0x10e  /go/src/app/threadinactive.go:26
#  github.com/dunglas/frankenphp.(*inactiveThread).beforeScriptExecution+0xfe   /go/src/app/threadinactive.go:36
#  github.com/dunglas/frankenphp.go_frankenphp_before_script_execution+0x16f    /go/src/app/phpthread.go:237
#  _cgoexp_..._go_frankenphp_before_script_execution+0x1a                       /go/src/app/phpmainthread.go:280
#  runtime.cgocallbackg1 / runtime.cgocallbackg / runtime.cgocallback

~148 incoming requests parked waiting for a PHP thread:

148 @ ...
#  github.com/dunglas/frankenphp.handleRequestWithRegularPHPThreads+0x828   /go/src/app/threadregular.go:148
#  github.com/dunglas/frankenphp.ServeHTTP+0x710                            /go/src/app/frankenphp.go:427
#  ...caddyhttp request pipeline...

Representative full stacks (?debug=2):

goroutine 17 [chan receive, 8 minutes, locked to thread]:
github.com/dunglas/frankenphp/internal/state.(*ThreadState).WaitFor(0x..., {0x..., 0x2, 0x2})
	/go/src/app/internal/state/state.go:167 +0x158
github.com/dunglas/frankenphp.go_frankenphp_main_thread_is_ready()
	/go/src/app/phpmainthread.go:238 +0x7d

goroutine 98 [select, 8 minutes, locked to thread]:
github.com/dunglas/frankenphp.(*regularThread).waitForRequest(0x...)
	/go/src/app/threadregular.go:110 +0x347
github.com/dunglas/frankenphp.(*regularThread).beforeScriptExecution(0x...)
	/go/src/app/threadregular.go:53 +0x585
github.com/dunglas/frankenphp.(*phpThread).transitionToNewHandler(0x...)
	/go/src/app/phpthread.go:177 +0x67
github.com/dunglas/frankenphp.(*inactiveThread).beforeScriptExecution(0x...)
	/go/src/app/threadinactive.go:26 +0x10f
github.com/dunglas/frankenphp.(*inactiveThread).beforeScriptExecution(0x...)
	/go/src/app/threadinactive.go:36 +0xff
github.com/dunglas/frankenphp.go_frankenphp_before_script_execution(0x0?)
	/go/src/app/phpthread.go:237 +0x170

Interpretation (tentative)

Looks like a thread-pool state-machine deadlock: ~half the PHP threads are wedged mid-transitionToNewHandler → waitForRequest (inside a cgo callback, locked to thread), so they never pick up the ~148 requests parked in handleRequestWithRegularPHPThreads, while go_frankenphp_main_thread_is_ready is still blocked in ThreadState.WaitFor. The trigger appears to be the burst of concurrent requests arriving in the first seconds after server running, while threads are still transitioning inactive → regular.

Notes

  • Mutex profiling isn't enabled in this build (/debug/pprof/mutex sampling period = 0), so I couldn't capture the lock holder. Happy to enable SetMutexProfileFraction and re-capture, or share the full 462-goroutine ?debug=2 dump (~14k lines).
  • Reproduction is probabilistic on deploy under live traffic. I can try to build a minimal repro (bind, then immediately hit with N concurrent requests during thread warm-up) if useful.
  • Possibly related: #2250 (same WaitFor/main_thread_is_ready code), #2205 (worker starvation after restarts).
主要言語
Go
スター
11.4k
フォーク
490
平均マージ
2日 21時間
マージ済み PR(30日)
29

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

php/frankenphp のほかの issue

php/frankenphp の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。