-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: 2.5.5
-
Component/s: None
-
None
-
2
-
None
-
PHP Drivers
-
Not Needed
-
None
-
None
-
None
-
None
-
None
-
None
Track GitHub Issue: https://github.com/mongodb/mongo-php-driver/issues/2199
Summary
When the extension runs inside FrankenPHP (ZTS, worker mode) 1.13.x, the process aborts after FrankenPHP reboots its PHP threads:
mongoc-handshake.c:904 mongoc_handshake_data_append(): assertion failed: gMongocHandshake.platform
FrankenPHP 1.13.0 (php/frankenphp#2364) reboots all PHP threads when a file watched by watch changes, or when opcache_reset() is called. The reboot tears the engine down and starts it again inside the same OS process (php_module_shutdown() + tsrm_shutdown(), then startup again).
What I think happens
- On reboot, every thread runs GSHUTDOWN. The last one brings phongo_num_threads to 0 and calls mongoc_cleanup() (phongo.c, PHP_GSHUTDOWN_FUNCTION). _mongoc_handshake_cleanup() frees the handshake globals.
- MINIT runs again and calls mongoc_init(). In libmongoc 2.5.6, mongoc_init() and mongoc_cleanup() are guarded by bson_once (mongoc-init.c, lines 161-162 and 207-208). The second mongoc_init() in the same process is a no-op, so _mongoc_handshake_init() never runs again.
- The next new MongoDB\Client() / MongoDB\Driver\Manager reaches mongoc_handshake_data_append(), finds gMongocHandshake.platform unset, and the assertion aborts the whole server.
The comment above phongo_num_threads assumes the last GSHUTDOWN means process end. That no longer holds when the SAPI restarts the engine in-process.
Environment
- ext-mongodb 2.5.5 (bundled libmongoc / libbson 2.5.6, libmongocrypt 1.20.5)
- PHP 8.5.12 ZTS
- FrankenPHP 1.13.2 (Caddy 2.11.7), worker mode, Debian-based official image, extension installed with install-php-extensions mongodb
- The client is created lazily inside the worker on the first request that needs it (a Monolog handler)
Evidence
In a development container with watch enabled, we saw 19 thread reboots over 20k log lines. Each of the 7 assertions came right after a rebooting all PHP threads line, and the container restarted 32 times. Production (no watch, no opcache_reset()) is not affected. I have not reduced it to a minimal standalone script yet. The expected minimal repro is a FrankenPHP worker with watch that creates a MongoDB\Driver\Manager and runs a command per request, then a touch of a watched file and one more request.
Question
Is restarting the engine in-process (MSHUTDOWN/GSHUTDOWN followed by MINIT in the same process) something the extension can support? For example, skip mongoc_cleanup() under ZTS and leave it to process exit, or re-run the handshake init on MINIT. Or should this be handled on the FrankenPHP side? I am happy to open a matching issue there.