frankenphp icon indicating copy to clipboard operation
frankenphp copied to clipboard

FrankenPHP compiled against shared libphp.so reports version at compile time, not loaded version.

Open henderkes opened this issue 9 months ago • 18 comments

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


henderkes avatar May 23 '25 02:05 henderkes

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.

henderkes avatar Jun 05 '25 04:06 henderkes

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.

withinboredom avatar Jun 07 '25 06:06 withinboredom

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.

henderkes avatar Jun 07 '25 06:06 henderkes

"should" and "will" are quite different. The former implies "in most cases" while the latter suggests "always".

withinboredom avatar Jun 07 '25 06:06 withinboredom

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.

henderkes avatar Jun 07 '25 06:06 henderkes

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.

withinboredom avatar Jun 07 '25 07:06 withinboredom

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.

henderkes avatar Jun 07 '25 07:06 henderkes

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).

withinboredom avatar Jun 07 '25 07:06 withinboredom

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.

henderkes avatar Jun 07 '25 07:06 henderkes

Maybe, instead of outputting the full version of 8.x.y, we only output 8.x?

withinboredom avatar Jun 07 '25 07:06 withinboredom

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

withinboredom avatar Jun 07 '25 07:06 withinboredom

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.

henderkes avatar Jun 07 '25 07:06 henderkes

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.

withinboredom avatar Jun 07 '25 09:06 withinboredom

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.

henderkes avatar Jun 07 '25 09:06 henderkes

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=

henderkes avatar Jun 20 '25 13:06 henderkes

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.

bukka avatar Jun 25 '25 10:06 bukka

@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?

henderkes avatar Nov 07 '25 15:11 henderkes

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!

henderkes avatar Nov 17 '25 16:11 henderkes