rfc:zendsignals

Zend Signal Handling

Introduction

This idea came about at Facebook while investigating ways to optimize Zend Engine calls to ap_block/unblock_alarms running under Apache 1.x. After some research Rasmus Lerdorf's original proposal to the internals list was found and it matched our plans. The purpose of which is as follows:

  • Make HANDLE_BLOCK/UNBLOCK_INTERRUPTIONS macros useful to extensions running under all SAPIs. (ie: php+apache2-prefork w/ apc)
  • Improve performance of PHP running under SAPI's that currently implement block/unblock hooks. (apache 1.x)

Improved Stability

The Zend Engine exposes an alarm blocking mechanism via macros however the specific implementation is left up to each SAPI, many of which do not provide the necessary functionality. In a standard PHP environment this is likely to be acceptable as the PHP engine only implements a single alarm based feature, the max_execution_time timeout. A survey of bundled SAPI's shows that almost all implement STANDARD_SAPI_MODULE_PROPERTIES in their sapi_module_struct, which does not provide any alarm blocking callback functionality. Only apache and apache_hooks define the alarm blocking callbacks, and only if !PHP_WIN32. The chance of being in a critical section while timing out is low and even if so it may not be problematic as the timeout triggers an immediate shutdown.

As extensions such as APC are introduced the need for stable alarm handling increases. The likelihood of being within within a critical section increases as an extension may be maintaining a shared memory segment using a variety of locking mechanisms, some of which may in fact further increase these odds such as spinlocking.

Extensions will now be able implement the zend alarm blocking macros in their critical sections and be assured that these critical sections will be protected.

Details

Startup

During startup the zend engine will register handlers for following signals

  1. SIGALRM, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2
  2. SIGPROF, if not compiled for windows or cygwin.

If any of these signals have previously registered handlers they will be stored internally.

Deferred Signals

If a signal is received, zend_signal_handler_defer() will check to see if execution is in a critical section. If not the previously registered handler will be called via zend_signal_handler(). This supports basic handlers as well as extended siginfo style handler functions.

If execution is within a critical section, execution of the previous handler will be deferred until the HANDLE_UNBLOCK_INTERRUPTIONS macro is reached. During deferred execution if any additional signals are received they are queued. All queued signals handlers will be invoked when HANDLE_UNBLOCK_INTERRUPTIONS is reached.

Zend Alarm Blocking Macros

For optimal performance the new HANDLE_BLOCK/UNBLOCK_INTERRUPTIONS macros account for critical sections by incrementing and decrementing the zend_signal_globals_t.depth counter. No functions are called until depth==0 in HANDLE_UNBLOCK_INTERRUPTIONS.

This accounting will not work in ZTS enabled mode thus support for deferred signal handling is automatically disabled when ZTS is enabled.

zend_signal()

The zend_signal() function allows registration of arbitrary signals within the zend engine to be deferred while executing critical sections. This is now used to register the SIGALRM and SIGPROF signals that provide the max_execution_time timeout functionality within PHP.

Performance

zend_alloc

Using this php test designed to stress memory allocation by assigning a million integers into an array a real time time improvement is measurable.

Average of 10 runs
php-5.2.5 938.9716
php-5.2.5-patched 915.8652
difference 23.1063
improvement 2.46%

Note: Although within the range of error these measurements seem consistent. The same script executed via ApacheBench showed no detectible request time improvement once transit cost was introduced.

callgrind

Benchmarks using Valgrind/Callgrind show the following results on a heavyweight page with a total of almost 3 billion instructions.

Callgrind
php-5.2.5
total instructions 2,707,532,005
ap_block_alarms calls 4,998,851
ap_unblock_alarms calls 4,998,851
php-5.2.5-patched
total instructions 2,607,562,502
ap_block_alarms calls 0
ap_unblock_alarms calls 0
  1. 9,997,702 less function calls
  2. 99,969,503 less instructions (3.6%)

Although some measure of improved performance is expected and measurable this functionality should at the very least improve stability at no cost.

Considerations

  1. Limited Zend Signal Handling support has been implemented for ZTS enabled php builds. Future development is planned to add support for ZTS windows builds.
  1. For simplicity the _zend_signal_info_t.prev structure is implemented as a static vector of NSIG size. On some systems NSIG may not be defined and 65 will be used as the default vector size.
  1. A signal queue of ZEND_SIGNAL_QUEUE_SIZE is created to handle recieved signals within critical sections. It is initialized to support 32 signals. If more are received after this they are discarded.

Discoveries

pcntl extension signals

The pcntl extension allows signal handlers to be defined in PHP userspace via pctnl_signal(). The handler is installed via signal and any previously registered handlers for the specified signal are ignored. While this is technically incompatible with Zend Signals, if a handler is installed via pctnl_signal for signals SIGALRM, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2, the deferred protection offered by Zend Signals will basically be disabled for that signal number.

Note: Although --enable-pcntl states “(CLI/CGI only)” in the config.m4, there is no actual enforcement of this at compile time. The extension can be running in any SAPI.

Update: pcntl has been modified to register signals via zend_signal() when available. Critical sections will now continue to have deferred protection even after signals are registered via pcntl.

PHP SIGCHLD signal handler

To deal with zombied or defunct children SIGCHILD handling was added to PHP via --enable-sigchild. This is apparently very common during disconnect when using oracle libraries to connect via the BEQ interface. When enabled, a handler for SIGCHILD is installed during php_startup. This handler calls waitpid() for any children assuring that their exit status is read and the zombies will not remain until php exits.

Since Zend Signals does not install a handler for SIGCHILD there is no explicit conflict. Also the simplicity of the handler should ensure that it does not adversely affect any critical zend sections. In future versions we may want to bring this functionality into Zend and enable by default on all platforms that support SIGCHLD ass well as implement via sigaction().

Note: When calling wait() or waitpid() within a handler the global errno may be modified. I have modified the existing handler in this patch to account for this.

Changelog

  1. 2008-07-05 Lucas Nealan: Initial creation (Discussed on internals)
  2. 2008-07-08 Lucas Nealan: Updated patch for php_request_shutdown order issue (prev patch)
  3. 2008-07-29 Lucas Nealan: Update patch to fix reentrance in handler, enable by default, stolen signal reporting (prev patch)
  4. 2008-08-01 Lucas Nealan: Update patch to fix tests, alloc/free on php startup/shutdown, ini for shutdown tests (prev patch)
  5. 2008-08-03 Lucas Nealan: Incorporated ZTS support by Arnaud Le Blanc, moved ini def to zend.c, added HEAD patch (prev patch)
  6. 2008-08-05 Arnaud Le Blanc: Added zend_sigaction() and ported PCNTL to use it. (PHP_5_3_0 prev patch PHP_HEAD prev patch)
  7. 2008-08-12 Lucas Nealan: Update patches to latest CVS, minor TWS fixes etc. (PHP_5_3_0 prev patc PHP_HEAD prev patch)
  8. 2009-11-20 Brian Shire: Updated patches for latest CVS with some fixes for bugs caused by another fix to the handling of signals during user-space shutdown functions. (PHP_5_3_0 prev patch PHP_HEAD prev patch)
rfc/zendsignals.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1