feat: validate a configuration before it replaces the running one - #2661
nicolas-grekas wants to merge 6 commits into
Conversation
|
Also, I did not read the comments during this review... too many comments, too many of them useless. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Validation misses per-server duplicate worker files, and worker initialization now has quadratic map-copying overhead.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds preflight configuration validation so invalid Caddy reloads do not stop the running PHP runtime.
Changes:
- Adds
frankenphp.Validate()and shared worker validation. - Implements Caddy’s configuration validator.
- Adds unit and reload regression tests.
| File | Description |
|---|---|
worker.go |
Extracts worker file and declaration validation. |
frankenphp.go |
Adds Validate() and defers worker metrics. |
frankenphp_test.go |
Tests declaration validation errors. |
docs/library.md |
Documents the validation API. |
caddy/app.go |
Adds validation and refactors option collection. |
caddy/admin_test.go |
Tests rejected reload continuity. |
caddy/config_test.go |
Updates worker-name tests. |
caddy/serveridx_test.go |
Updates server collection tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Review addressed in You were right about both: The declaration rules take lookups instead of copies of every name and path before them, per Copilot. And the comments: fair, several restated the function under them. They are down from 49 lines to 35, the ones left say why. |
henderkes
left a comment
There was a problem hiding this comment.
Please unsloppify the comments further. No AI is going to get that done.
I'd appreciate your guidance on this aspect, there aren't that many comment now. |
Start() calls Shutdown() before Init(), since the PHP runtime is a process singleton, so a declaration error takes the site down and Caddy rolls back the configuration, not the runtime that went with it: a missing worker file, a name two workers share, a thread budget that does not add up, any of them leaves the server answering 500 until the next reload. frankenphp.Validate() takes the options Init() takes and reports what it would refuse, touching nothing: the thread budget, the worker files, and the names and scopes workers may take. The rules are the ones Init() runs, newWorker() now shares them rather than holding its own copy. The Caddy app implements caddy.Validator on it, which Caddy calls while the previous configuration still serves, so a rejected reload keeps the site up. Collecting the options moved out of Start() for that, and the names workers take are uniquified per collection rather than per app, so validating a configuration does not change the names the next one gets.
Caddy calls Validate() while it provisions the app, before the modules that carry php_server blocks provision themselves, so the workers they declare were not part of what it checked: a bad one passed validation and failed inside Init(), once Shutdown() had already taken the runtime that was serving. The site then answered "server is not registered" until the next reload, which is the failure this PR is about. Start() now runs the same rules over the options it collected, before it shuts anything down, so a rejected configuration leaves the running one alone whichever block declared the worker. Also fixes two things the review found: the options a module adds while it provisions, the hot reload among them, were dropped by Start() collecting the configuration over them, and they now have a slice of their own; and the declaration rules take lookups rather than copies of every name and path taken before, which made a declaration cost the ones before it.
Marc read none of them, which is fair: several restated the name of the function under them. The ones that carry a reason stay, shorter.
aecab93 to
e272773
Compare
|
I'll look later. |
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
Review effort: Lite
Findings: 1
Open (4)
len(options)is read outside theoptionsMUlock, which introduces a data race ifoptionscan… · Newstrconv.Atoiwill fail if the worker response includes a trailing newline or surrounding… · New The doc comment immediately precedingValidatecurrently starts with “Init…”, which will be… · New This comment references the “adapt endpoint”, but the test is callingPOST /load. Update the… · New
Resolved since last review (2)
|
Copilot review should be addressed + another issue I found with |
6ab74bf to
d0a770c
Compare



Start()callsShutdown()beforeInit(), since the PHP runtime is a process singleton, so a declaration error takes the site down: Caddy rolls back the configuration, not the runtime that went with it, and the server answers 500 until the next reload. A missing worker file does it, so does a name two workers share, or a thread budget that does not add up.frankenphp.Validate()takes the optionsInit()takes and reports what it would refuse, touching nothing: the thread budget, the worker files, and the names and scopes workers may take. The rules are the onesInit()runs,newWorker()now shares them instead of holding its own copy.The Caddy app implements
caddy.Validatoron top of it, which Caddy calls while the previous configuration still serves:and the site that was running keeps serving, which
TestRejectedReloadKeepsThePreviousSiteServingchecks by counting the requests its worker served across the rejected reload.Collecting the options moved out of
Start()for that, and the names workers take are uniquified per collection rather than per app, so validating a configuration no longer changes the names the next one gets.Reported by @henderkes in #2617, where duplicate worker names added a trigger to a failure mode that predates it: #2617 (comment)