Skip to content

feat: unify php_server logic#2499

Open
AlliBalliBaba wants to merge 62 commits into
mainfrom
refactor/phpserver
Open

feat: unify php_server logic#2499
AlliBalliBaba wants to merge 62 commits into
mainfrom
refactor/phpserver

Conversation

@AlliBalliBaba

Copy link
Copy Markdown
Contributor

Currently the concept of a php_server only exists on the caddy side and not the FrankenPHP side.
Lately we have been moving more and more in a direction of scoping requests or workers to specific php_server blocks.

This PR is an attempt at refactoring the current php_server logic so it is properly mirrored on the FrankenPHP side without BC breaks for library users (and to prevent future bugs like mentioned in #2487)

Comment thread options.go Outdated
Comment thread options.go Outdated
Comment thread options.go Outdated
Comment thread phpserver.go Outdated
@AlliBalliBaba

Copy link
Copy Markdown
Contributor Author

Another subtlety I fixed: the "external workers" from RegisterWorkers should probably persist across reloads.

@AlliBalliBaba
AlliBalliBaba requested a review from henderkes July 12, 2026 22:23

@henderkes henderkes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's also a problem with DrainWorkers on main at the moment. Seems like it takes the same path with drainPHPThreads as Shutdown logic, but then never sets the state so the php threads could restart. Not directly related to this PR but probably better to fix it right here (or remove the function, it's marked experimental anyways).

Comment thread server.go Outdated
@AlliBalliBaba

Copy link
Copy Markdown
Contributor Author

DrainWorkers always worked this way. I think in the past it was a way for library users to have a graceful worker shutdown. But it is not necessary anymore. IMO we can also remove it

@henderkes

Copy link
Copy Markdown
Contributor

+1 for removal because it currently hard-kills the current php runtime with no way to recover it, instead of letting threads perform a graceful reload.

@AlliBalliBaba

Copy link
Copy Markdown
Contributor Author

I'll open a separate PR for removing it. First will have to look up why it was added. I think it was for tests initially.

AlliBalliBaba added a commit that referenced this pull request Jul 15, 2026
This PR removes the experimental `DrainWorkers` function since it serves
no purpose anymore (as also discussed in #2499).

The original use was to have a graceful shutdown of all workers on Caddy
shutdown (added
[here](https://github.com/php/frankenphp/pull/1405/changes)). Since
shutdowns have long been fully safe, the function is not in use anymore.
It might only be confusing for library users, since it does not allow
recovering drained workers.
@nicolas-grekas

Copy link
Copy Markdown
Contributor

This refactor is exactly the direction I need for #2398, so I plan to rebase on it once it lands: WithWorkerServerScope and the per-Server worker registry replace most of the scope machinery I had to build there.

One small addition would make Server fully self-sufficient as an identity: an optional human-readable name.

Motivation: workers are now scoped per server, and cross-server name collisions are resolved with the _<num> postfix (caddy/app.go). That keeps metric keys unique, but attribution becomes order-dependent: with two php_server blocks each declaring a worker.php, metrics show worker.php and worker.php_1, and which block is which depends on parse order. If Server carried a name, metric/log emitters could qualify labels with it (e.g. server="api.example.com"), and the Caddy module could resolve a default at provision time from the host matcher or first listen address, with no new user-facing config needed.

It would also let #2398's background workers reuse the Server name as their metric namespace directly, instead of reintroducing a separate scope-label registry on top.

API-wise the minimal version is a name field on Server set via NewServer (or left "" and defaulted to the index), nothing new exported beyond that. Happy to open a PR against refactor/phpserver if you'd rather see it in code.

@AlliBalliBaba

Copy link
Copy Markdown
Contributor Author

Having a server name makes sense to me 👍 , the fallback can just be server_{index}. You can open a PR if you want to.

@nicolas-grekas

Copy link
Copy Markdown
Contributor

I opened a few follow up to help get this merged ASAP, if that helps!

@nicolas-grekas

nicolas-grekas commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@AlliBalliBaba feel free to cherry-pick/squasj my other PRs as you prefer. I'm looking forward to this being merged quickly so let me know if I can help in any way (rebase, etc)

Follow-up to #2499, targeting `refactor/phpserver`.

`Server.isRegistered` is written by `Init()`/`Shutdown()` while
in-flight `ServeHTTP()` calls read it concurrently, e.g. during an admin
API reload under traffic; the race detector flags the plain bool. This
makes it an `atomic.Bool`.

The pre-existing global `isRunning` flag has the same pattern, but
`Server` is the surface library users now interact with, so it seemed
worth fixing here; `isRunning` can be handled separately if wanted.
Follow-up to #2499, targeting `refactor/phpserver`. Test-only.

During review, an explicit `server_idx` colliding with an auto-assigned
one could make one handler serve another's files; the fix (deferring
registration to a single pass keyed by index) had no regression test,
and nothing in `caddy/` exercised `server_idx` at all. This adds unit
coverage for:

- Caddyfile parsing assigns incrementing indexes
- modules with the same `server_idx` share one server instance,
registered only once
- modules without `server_idx` each get their own server
- a module with an explicit `server_idx` equal to an already registered
one joins the existing server instead of re-registering it (the
double-registration scenario possible with JSON configs POSTed to the
admin API)

These are pure Go unit tests on `registerModules()`; an end-to-end admin
API config-swap test would be a natural follow-up but needs the PHP
runtime, so it is not included here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants