From fb8459441399fe84dcd80c082e21226ee843d797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 10 Oct 2026 07:53:34 +0200 Subject: [PATCH] fix: default max_consecutive_failures to 6 for Caddy workers toWorkerOptions always passed MaxConsecutiveFailures to WithWorkerMaxFailures, so an unset value overrode the documented default of 6 with 0 and any Caddyfile or JSON worker aborted startup on its first boot failure. The field is now a pointer, so the default applies when it is omitted and an explicit 0 still means no retries. --- caddy/caddy_test.go | 29 +++++++++++++++++++++++++++++ caddy/workerconfig.go | 9 ++++++--- testdata/worker-fail-first-boot.php | 14 ++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 testdata/worker-fail-first-boot.php diff --git a/caddy/caddy_test.go b/caddy/caddy_test.go index cd7e602087..7184a09253 100644 --- a/caddy/caddy_test.go +++ b/caddy/caddy_test.go @@ -245,6 +245,35 @@ func TestModuleWorkerInheritsEnv(t *testing.T) { tester.AssertGetResponse("http://localhost:"+testPort+"/worker-with-env.php", http.StatusOK, "Worker has APP_ENV=inherit_this") } +func TestWorkerRetriesBootFailureByDefault(t *testing.T) { + marker := filepath.Join(t.TempDir(), "booted") + tester := caddytest.NewTester(t) + initServer(t, tester, ` + { + skip_install_trust + admin localhost:2999 + + frankenphp { + worker { + file ../testdata/worker-fail-first-boot.php + num 1 + env BOOT_MARKER `+marker+` + } + } + } + + http://localhost:`+testPort+` { + route { + php { + root ../testdata + } + } + } + `, "caddyfile") + + tester.AssertGetResponse("http://localhost:"+testPort+"/worker-fail-first-boot.php", http.StatusOK, "booted after a failure") +} + func TestNamedModuleWorkers(t *testing.T) { var wg sync.WaitGroup testPortNum, _ := strconv.Atoi(testPort) diff --git a/caddy/workerconfig.go b/caddy/workerconfig.go index b39eb731e0..ac022a3a6f 100644 --- a/caddy/workerconfig.go +++ b/caddy/workerconfig.go @@ -37,7 +37,7 @@ type workerConfig struct { // The path to match against the worker MatchPath []string `json:"match_path,omitempty"` // MaxConsecutiveFailures sets the maximum number of consecutive failures before panicking (defaults to 6, set to -1 to never panick) - MaxConsecutiveFailures int `json:"max_consecutive_failures,omitempty"` + MaxConsecutiveFailures *int `json:"max_consecutive_failures,omitempty"` options []frankenphp.WorkerOption } @@ -138,7 +138,7 @@ func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) { return wc, d.Errf("max_consecutive_failures must be >= -1") } - wc.MaxConsecutiveFailures = v + wc.MaxConsecutiveFailures = new(v) default: return wc, wrongSubDirectiveError("worker", "name, file, num, env, watch, match, max_consecutive_failures, max_threads", v) } @@ -159,10 +159,13 @@ func (wc *workerConfig) toWorkerOptions() ([]frankenphp.WorkerOption, error) { opts := []frankenphp.WorkerOption{ frankenphp.WithWorkerEnv(wc.Env), frankenphp.WithWorkerWatchMode(wc.Watch), - frankenphp.WithWorkerMaxFailures(wc.MaxConsecutiveFailures), frankenphp.WithWorkerMaxThreads(wc.MaxThreads), } + if wc.MaxConsecutiveFailures != nil { + opts = append(opts, frankenphp.WithWorkerMaxFailures(*wc.MaxConsecutiveFailures)) + } + // options collected while provisioning the module, e.g. the Mercure hub opts = append(opts, wc.options...) diff --git a/testdata/worker-fail-first-boot.php b/testdata/worker-fail-first-boot.php new file mode 100644 index 0000000000..169073ef26 --- /dev/null +++ b/testdata/worker-fail-first-boot.php @@ -0,0 +1,14 @@ +