FrankenPHP compiled against shared libphp.so reports version at compile time, not loaded version.
What happened?
2025/05/23 02:11:15.188 INFO frankenphp FrankenPHP started 🐘 {"php_version": "8.4.6", "num_threads": 48, "max_threads": 48}
But libphp.so was updated to 8.4.7. FrankenPHP reports the version it was compiled against, not the one that is actually being loaded.
Build Type
Custom (tell us more in the description)
Worker Mode
No
Operating System
GNU/Linux
CPU Architecture
x86_64
PHP configuration
irrelevant
Relevant log output
I've tried to change this, but can't seem to get it right. Even though the php threads are started in init(), executing any php code before the version print leads to a segmentation fault.
But libphp.so was updated to 8.4.7. FrankenPHP reports the version it was compiled against, not the one that is actually being loaded.
That may cause issues since PHP doesn't always (though it really tries to) keep a compatible ABI between versions. Upgrading PHP without recompiling or upgrading FrankenPHP may cause segfaults.
It's probably better to issue a warning on requests where the compiled version of PHP is different from the running version of PHP.
I've successfully exchanged versions 8.2.x, 8.3.20, 8.3.21, 8.4.6 and 8.4.7 without recompiling FrankenPHP. I think for a minor version increase (8.4.6 to 8.4.x) it should be fine.
"should" and "will" are quite different. The former implies "in most cases" while the latter suggests "always".
Isn't that the entire point for minor patch version increases? They keep a stable API. If that weren't the case, our systems would collapse every time we update a library without also updating all dependencies. That's also why sonames exist, i.e. libc.so.6 or libphp-8.3.so.
Edit: I mean for patch version increases, of course. If my understanding of versioning is correct, a minor increase (8.2 to 8.3) would only work if originally compiled against 8.2, but not the other way around.
FrankenPHP doesn’t make extensive use of the PHP ABI, so it can change pretty drastically unless someone modifies the SAPI struct or one of the functions FrankenPHP calls. This is unlikely to happen, though. There are a number of ABI breaks in 8.5 already. By using different versions than FrankenPHP was built with, you’re basically playing with a time-bomb. It might work, it might not.
It is up to release managers to ensure ABI compatibility between patch versions, but it isn’t guaranteed (it has happened before — I believe in 8.2-ish? and extensions had to be recompiled to prevent segfaults)
Once we have php extensions in go, this will be even more important because those extensions will probably make use of more PHP ABIs.
ABI breaks from 8.2 to 8.3 sure, or from 8.4 to 8.5. But from 8.4.X to 8.4.Y? At that point we may as well give up on trying to link against a shared version entirely. There isn't a library available that uses major-minor-patch versioning in their sonames, it's either major only, or major.minor.
It doesn't really matter how much FrankenPHP itself changes, since it links against libphp.so, not the other way around. It matters how much libphp.so changes. If the public api of libphp.so changes between patch versions, there's something going very wrong. ABI can change, as long as symbols have the same names and parameters, since they're dynamically loaded anyhow.
There was an ABI break in 8.2.x to 8.2.y (IIRC, and I may not be. It was a long time ago.) — that was my point. It isn’t guaranteed to be compatible because we’re all humans here. In most cases, it will be compatible. IMHO, it is better to play it safe and provide a warning on startup or something. That, at least, will give someone something to try fixing before they come and report a segfault issue. (it also means there is no expectation that FrankenPHP built for 8.3 works on 8.5).
Oh, for the record I wouldn't advocate to build again version 8. I currently build rpm repositories with EXTRA_LDFLAGS=zts-8.4 which sets the soname to libphp-zts-8.4.so, similar how remi does it (libphp.8.4.so). That way frankenphp would load any 8.4.X, but not 8.3 or 8.5.
If you want to restrict linking to 8.4.7, there's no reason to link shared at all.
Maybe, instead of outputting the full version of 8.x.y, we only output 8.x?
So,
- output major/minor version in the version string
- if major/minor version doesn't match what it was compiled with: issue a warning
??
note: static compiles probably want to output the full version string
That sounds good. For my planned rpm/deb repository it wouldn't be possible to load a different minor version anyhow. For brew, unless I misunderstand the dependency on php-zts without version constraints, it's currently possible to switch between versions freely. Maybe we can specify php-zts^8 there?
But my problem when tackling this was that I was never able to call any php code without causing a segfault before Start() finished. I don't know how to even get the dynamically loaded version.
Edit: I think shared and static version should behave the same way. Both should output the full version string imo - a warning should be issued in addition only with a different minor version.
I was never able to call any php code without causing a segfault before Start() finished. I don't know how to even get the dynamically loaded version.
One option would be to have a "run once" function (sync.Once) that runs before a request starts. Sure, it limits the warning to only be once a request happens, but if a request never happens to load any php, there isn’t a reason to issue the warning.
Pretty sure we can narrow this down to my inexperience with the project. I've gotten fairly accustomed to the caddy stuff, but haven't really looked into how exactly php is initialised and called, yet.
I've chosen to do Major.Minor versioning for the packages, so maybe it would be the simplest to also switch the frankenphp run startup info to just that.
[m@M-TH spc-packages]$ frankenphp -v
FrankenPHP v1.7.0 PHP 8.4 Caddy v2.10.0 h1:fonubSaQKF1YANl8TXqGcn4IbIRUDdfAkpcsfI/vX5U=
There was an ABI break in 8.2.x to 8.2.y (IIRC, and I may not be. It was a long time ago.) — that was my point. It isn’t guaranteed to be compatible because we’re all humans here.
Just for the record, every such break is a bug which we try to fix. We added some labels to php-src to catch the header modification but it happened just recently with bison anyway. So the position is that it is guaranteed unless there is a bug.
We are also considering allowing such break for high severity security issues but that would be a last resort with some comms and it should happen only very rarely if ever.
Anyway, considering how much of the ABI FrankenPHP uses, the chance of ABI break in patch version for you is pretty close to zero I think.
@dunglas this is still a minor annoyance as it affects every setting where php updates more frequently than frankenphp and affects the frankenphp version output as well as the startup messages. I didn't manage to solve it, could you perhaps take a look?
Now this is funny:
2025/11/17 13:12:17.330 INFO frankenphp FrankenPHP started 🐘 {"php_version": "8.4.13", "num_threads": 256, "max_threads": 256}
[m@M ~]$ curl http://localhost -i
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Server: Caddy
X-Powered-By: PHP/8.4.14
Date: Mon, 17 Nov 2025 14:11:16 GMT
Content-Length: 12
Hello World!