Hi!

In the last few days I have been investigating Xdebug bug #2051: 
"Segfault on fatal error when setting xdebug.mode via php-fpm pool 
config" (https://bugs.xdebug.org/view.php?id=2051).

I have now tracked this down to an unexpectedness in how PHP-FPM handles 
extension/module initialisation.

Literature since at least 2006 (Sara's Extending and Embedding PHP 
book), as well as numerous presentations that I have seen and given, and 
including the online PHP Internals Book 
(https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html), 
always expected that the MINIT (and MSHUTDOWN) functions are called once 
per worker process. However, PHP-FPM does not do that. It only calls 
extension's MINITs once in the main control process, but it *does* call 
an MSHUTDOWN for each worker process.

In the past, this has already caused an issue where I couldn't create a 
monitoring thread in MINIT, and then wait for it to end in MSHUTDOWN, as 
MSHUTDOWN would wait on the same thread in each worker process, although 
only one was created in MINIT.

The way how PHP-FPM handles this breaks the generally assumed "one 
MINIT, one MSHUTDOWN call" per process approach.

In particular, in the case of Xdebug bug #2051 it creates a problem with 
the following set-up:

1. php.ini has a `xdebug.mode=off`
2. the pool configuration has a `php_admin_value[xdebug.mode] = debug` 
   directive 

When PHP-FPM starts up, it calls Xdebug's MINIT, which checks the value 
of the `xdebug.mode` INI setting. If it set to `off` it does 
**nothing**, including setting up handles such as the zend_error_cb 
handlers in `xdebug_base_minit`::

        PHP_MINIT_FUNCTION(xdebug)
        {
                …

                if (XDEBUG_MODE_IS_OFF()) {
                        return SUCCESS;
                }

                …
                xdebug_base_minit(INIT_FUNC_ARGS_PASSTHRU);
        }

MINIT sets the `xdebug_old_error_cb` and `xdebug_new_error_cb` handlers 
to something else than `NULL`::

        void xdebug_base_minit(INIT_FUNC_ARGS)
        {
                /* Record Zend and Xdebug error callbacks, the actual setting 
is done in
                 * base on RINIT */
                xdebug_old_error_cb = zend_error_cb;
                xdebug_new_error_cb = xdebug_error_cb;

In each RINIT, which is called once per request, it also checks the INI 
setting, and if set, uses some of the handlers that were set-up in 
MINIT. Please note that PHP-FPM has changed the value of `xdebug.mode` 
in this worker process to `debug` (ie, not `off`), (simplified)::

        PHP_RINIT_FUNCTION(xdebug)
        {
                …
                if (XDEBUG_MODE_IS_OFF()) {
                        return SUCCESS;
                }
                …
                xdebug_base_rinit();
        }

        void xdebug_base_rinit()
        {
                xdebug_base_use_xdebug_error_cb();
        }
        
        void xdebug_base_use_xdebug_error_cb(void)
        {
                zend_error_cb = xdebug_new_error_cb;
        }

When now an error occurs, PHP calls `zend_error_cb`, but this is now 
unset (`NULL`) as when MINIT was called, the `xdebug.mode` setting was 
still set to `off`, but during RINIT it was changed by PHP-FPM to 
`debug`. Xdebug does not expect this setting to change, especially 
because it is marked as `PHP_INI_SYSTEM`.

In my opinion this is a bug (or two) in PHP-FPM, as:

- it does not follow the expected one MINIT/MSHUTDOWN cycle, that for
  example Apache (1 and 2) use, and which is documented in books and 
  online material (and my memory)
- it disrespects `PHP_INI_SYSTEM`

My suggestion for a fix would be to emulate what Apache always did:

One MINIT/MSHUTDOWN in the main control process (I think it needed that 
to be able to implement php_admin_value and php_value), and then 
additionally also in each worker process.

I did have a brief look at implementing this, but haven't managed to get 
it to work yet — mainly because I am unfamiliar with the PHP-FPM code at 
the moment.

cheers,
Derick

-- 
PHP 7.4 Release Manager
Host of PHP Internals News: https://phpinternals.news
Like Xdebug? Consider supporting me: https://xdebug.org/support
https://derickrethans.nl | https://xdebug.org | https://dram.io
twitter: @derickr and @xdebug
-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to