Index: trunk/php/wmerrors/wmerrors.c |
— | — | @@ -3,6 +3,8 @@ |
4 | 4 | #include "config.h" |
5 | 5 | #endif |
6 | 6 | |
| 7 | +#include <stdlib.h> |
| 8 | + |
7 | 9 | #include "php.h" |
8 | 10 | #include "php_ini.h" |
9 | 11 | #include "php_wmerrors.h" |
— | — | @@ -12,15 +14,25 @@ |
13 | 15 | #include "ext/standard/php_smart_str.h" /* for smart_str */ |
14 | 16 | #include "Zend/zend_builtin_functions.h" /* for zend_fetch_debug_backtrace */ |
15 | 17 | |
| 18 | +static int wmerrors_post_deactivate(); |
16 | 19 | static void wmerrors_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); |
17 | 20 | static void wmerrors_show_message(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args TSRMLS_DC); |
18 | 21 | static void wmerrors_get_concise_backtrace(smart_str *s TSRMLS_DC); |
19 | 22 | static void wmerrors_write_full_backtrace(php_stream *logfile_stream); |
20 | 23 | static void wmerrors_write_request_info(php_stream *logfile_stream TSRMLS_DC); |
| 24 | +static void wmerrors_alarm_handler(int signo); |
| 25 | +static void wmerrors_install_alarm(TSRMLS_D); |
| 26 | +static void wmerrors_remove_alarm(TSRMLS_D); |
21 | 27 | |
22 | 28 | ZEND_DECLARE_MODULE_GLOBALS(wmerrors) |
23 | 29 | |
| 30 | +PHP_FUNCTION(wmerrors_malloc_test); |
| 31 | + |
| 32 | +ZEND_BEGIN_ARG_INFO(wmerrors_malloc_test_arginfo, 0) |
| 33 | +ZEND_END_ARG_INFO() |
| 34 | + |
24 | 35 | zend_function_entry wmerrors_functions[] = { |
| 36 | + PHP_FE(wmerrors_malloc_test, wmerrors_malloc_test_arginfo) |
25 | 37 | {NULL, NULL, NULL} |
26 | 38 | }; |
27 | 39 | |
— | — | @@ -39,7 +51,9 @@ |
40 | 52 | #if ZEND_MODULE_API_NO >= 20010901 |
41 | 53 | "1.1.2", |
42 | 54 | #endif |
43 | | - STANDARD_MODULE_PROPERTIES |
| 55 | + NO_MODULE_GLOBALS, |
| 56 | + wmerrors_post_deactivate, |
| 57 | + STANDARD_MODULE_PROPERTIES_EX |
44 | 58 | }; |
45 | 59 | |
46 | 60 | |
— | — | @@ -54,6 +68,7 @@ |
55 | 69 | STD_PHP_INI_BOOLEAN("wmerrors.log_backtrace", "0", PHP_INI_ALL, OnUpdateBool, log_backtrace, zend_wmerrors_globals, wmerrors_globals) |
56 | 70 | STD_PHP_INI_BOOLEAN("wmerrors.ignore_logging_errors", "0", PHP_INI_ALL, OnUpdateBool, ignore_logging_errors, zend_wmerrors_globals, wmerrors_globals) |
57 | 71 | STD_PHP_INI_BOOLEAN("wmerrors.backtrace_in_php_error_message", "0", PHP_INI_ALL, OnUpdateBool, backtrace_in_php_error_message, zend_wmerrors_globals, wmerrors_globals) |
| 72 | + STD_PHP_INI_ENTRY("wmerrors.timeout", "10", PHP_INI_ALL, OnUpdateLong, timeout, zend_wmerrors_globals, wmerrors_globals) |
58 | 73 | PHP_INI_END() |
59 | 74 | |
60 | 75 | void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); |
— | — | @@ -87,6 +102,7 @@ |
88 | 103 | PHP_RINIT_FUNCTION(wmerrors) |
89 | 104 | { |
90 | 105 | WMERRORS_G(recursion_guard) = 0; |
| 106 | + WMERRORS_G(alarm_set) = 0; |
91 | 107 | return SUCCESS; |
92 | 108 | } |
93 | 109 | |
— | — | @@ -97,6 +113,13 @@ |
98 | 114 | return SUCCESS; |
99 | 115 | } |
100 | 116 | |
| 117 | +int wmerrors_post_deactivate() |
| 118 | +{ |
| 119 | + TSRMLS_FETCH(); |
| 120 | + wmerrors_remove_alarm(TSRMLS_C); |
| 121 | + return SUCCESS; |
| 122 | +} |
| 123 | + |
101 | 124 | PHP_MINFO_FUNCTION(wmerrors) |
102 | 125 | { |
103 | 126 | php_info_print_table_start(); |
— | — | @@ -143,6 +166,10 @@ |
144 | 167 | /* No more OOM errors for now thanks */ |
145 | 168 | zend_set_memory_limit((size_t)-1); |
146 | 169 | |
| 170 | + if (WMERRORS_G(timeout)) { |
| 171 | + wmerrors_install_alarm(TSRMLS_C); |
| 172 | + } |
| 173 | + |
147 | 174 | /* Do not show the html error to console */ |
148 | 175 | if ( WMERRORS_G(enabled) && strncmp(sapi_module.name, "cli", 3) ) { |
149 | 176 | /* Show the message */ |
— | — | @@ -155,18 +182,27 @@ |
156 | 183 | } |
157 | 184 | |
158 | 185 | /* Put a concise backtrace in the normal output */ |
159 | | - if (WMERRORS_G(backtrace_in_php_error_message)) |
| 186 | + if (WMERRORS_G(backtrace_in_php_error_message)) { |
160 | 187 | wmerrors_get_concise_backtrace(&new_filename TSRMLS_CC); |
| 188 | + } |
161 | 189 | smart_str_appendl(&new_filename, error_filename, strlen(error_filename)); |
162 | 190 | smart_str_0(&new_filename); |
163 | 191 | |
164 | 192 | WMERRORS_G(recursion_guard) = 0; |
165 | 193 | zend_set_memory_limit(PG(memory_limit)); |
166 | 194 | |
| 195 | + if (PG(connection_status) & PHP_CONNECTION_TIMEOUT) { |
| 196 | + /* abort instead of deadlocking */ |
| 197 | + const char abort_message[] = "wmerrors: doing precautionary abort() after request timeout\n"; |
| 198 | + write(STDERR_FILENO, abort_message, sizeof(abort_message) - 1); |
| 199 | + abort(); |
| 200 | + } |
| 201 | + |
167 | 202 | /* Pass through */ |
168 | 203 | old_error_cb(type, new_filename.c, error_lineno, format, args); |
169 | 204 | |
170 | | - /* Note: old_error_cb() may not return, in which case there will be no explicit free of new_filename */ |
| 205 | + /* Note: old_error_cb() may not return, in which case there will be no |
| 206 | + * explicit free of new_filename */ |
171 | 207 | smart_str_free(&old_error_cb); |
172 | 208 | } |
173 | 209 | |
— | — | @@ -480,7 +516,13 @@ |
481 | 517 | /* Write the message out */ |
482 | 518 | if (expanded.c) { |
483 | 519 | php_write(expanded.c, expanded.len TSRMLS_CC); |
| 520 | + |
| 521 | + if (PG(connection_status) & PHP_CONNECTION_TIMEOUT) { |
| 522 | + /* Probably PHP will crash soon, better flush the output first */ |
| 523 | + sapi_flush(TSRMLS_C); |
| 524 | + } |
484 | 525 | } |
| 526 | + |
485 | 527 | |
486 | 528 | /* Clean up */ |
487 | 529 | smart_str_free(&expanded); |
— | — | @@ -521,4 +563,58 @@ |
522 | 564 | } |
523 | 565 | } |
524 | 566 | |
| 567 | +/** |
| 568 | + * Occasionally, PHP will time out during a malloc() call, generating SIGALRM. |
| 569 | + * A mutex will be held, and then the process will deadlock forever on the |
| 570 | + * next malloc() call. This timeout handler is designed to handle this |
| 571 | + * situation safely. |
| 572 | + * |
| 573 | + * We could try to avoid using malloc(), but then the process would deadlock |
| 574 | + * when it returns control to Apache. |
| 575 | + */ |
| 576 | +static void wmerrors_alarm_handler(int signo) { |
| 577 | + const char message[] = "wmerrors: timed out during fatal error handler, aborting\n"; |
| 578 | + write(STDERR_FILENO, message, sizeof(message) - 1); |
| 579 | + abort(); |
| 580 | +} |
525 | 581 | |
| 582 | +static void wmerrors_install_alarm(TSRMLS_D) { |
| 583 | +#ifdef WMERRORS_USE_TIMER |
| 584 | + struct sigaction sa; |
| 585 | + struct sigevent evp; |
| 586 | + struct itimerspec its; |
| 587 | + |
| 588 | + memset(&sa, sizeof(sa), 0); |
| 589 | + sa.sa_handler = wmerrors_alarm_handler; |
| 590 | + sigaction(SIGRTMIN+3, &sa, &WMERRORS_G(old_rt_action)); |
| 591 | + |
| 592 | + evp.sigev_notify = SIGEV_SIGNAL; |
| 593 | + evp.sigev_signo = SIGRTMIN+3; |
| 594 | + if (timer_create(CLOCK_REALTIME, &evp, &WMERRORS_G(timer)) == -1) { |
| 595 | + perror("timer_create"); |
| 596 | + abort(); |
| 597 | + } |
| 598 | + |
| 599 | + its.it_value.tv_sec = WMERRORS_G(timeout); |
| 600 | + its.it_value.tv_nsec = 0; |
| 601 | + its.it_interval.tv_sec = its.it_interval.tv_nsec = 0; |
| 602 | + timer_settime(WMERRORS_G(timer), 0, &its, NULL); |
| 603 | + WMERRORS_G(alarm_set) = 1; |
| 604 | +#endif |
| 605 | +} |
| 606 | + |
| 607 | +static void wmerrors_remove_alarm(TSRMLS_D) { |
| 608 | +#ifdef WMERRORS_USE_TIMER |
| 609 | + if (WMERRORS_G(alarm_set)) { |
| 610 | + timer_delete(WMERRORS_G(timer)); |
| 611 | + sigaction(SIGRTMIN+3, &WMERRORS_G(old_rt_action), NULL); |
| 612 | + WMERRORS_G(alarm_set) = 0; |
| 613 | + } |
| 614 | +#endif |
| 615 | +} |
| 616 | + |
| 617 | +PHP_FUNCTION(wmerrors_malloc_test) { |
| 618 | + for (;;) { |
| 619 | + free(malloc(100)); |
| 620 | + } |
| 621 | +} |
Index: trunk/php/wmerrors/debian/changelog |
— | — | @@ -1,3 +1,11 @@ |
| 2 | +php5-wmerrors (1.1.3-1) oneiric; urgency=low |
| 3 | + |
| 4 | + * Added a workaround for https://bugs.php.net/bug.php?id=31749 . Do a flush |
| 5 | + and abort after timeout instead of returning control to Apache, and set a |
| 6 | + timer which will protect against deadlocks within wmerrors itself. |
| 7 | + |
| 8 | + -- Tim Starling <tstarling@wikimedia.org> Thu, 17 Nov 2011 12:11:43 +1100 |
| 9 | + |
2 | 10 | php5-wmerrors (1.1.2-2) lucid; urgency=low |
3 | 11 | |
4 | 12 | * Recompiled against new version of php5 |
Index: trunk/php/wmerrors/README |
— | — | @@ -62,3 +62,10 @@ |
63 | 63 | If this is true, a concise backtrace, listing base filenames (not including |
64 | 64 | the path) and line numbers only, will be included in the error message |
65 | 65 | which is passed through to PHP, for output into error_log. |
| 66 | + |
| 67 | +wmerrors.timeout |
| 68 | + |
| 69 | + A timeout for the fatal error handler and part of the subsequent PHP |
| 70 | + request shutdown. PHP is prone to deadlocks during fatal error handling |
| 71 | + (bug 31749), this feature is intended to mitigate their impact. If the |
| 72 | + timeout expires, a message is written to stderr and abort() is called. |
Index: trunk/php/wmerrors/php_wmerrors.h |
— | — | @@ -17,6 +17,12 @@ |
18 | 18 | |
19 | 19 | #include "ext/standard/php_smart_str_public.h" |
20 | 20 | |
| 21 | +#if _POSIX_C_SOURCE >= 200112 && !defined(ZTS) |
| 22 | +#define WMERRORS_USE_TIMER |
| 23 | +#include <signal.h> |
| 24 | +#include <time.h> |
| 25 | +#endif |
| 26 | + |
21 | 27 | PHP_MINIT_FUNCTION(wmerrors); |
22 | 28 | PHP_MSHUTDOWN_FUNCTION(wmerrors); |
23 | 29 | PHP_RINIT_FUNCTION(wmerrors); |
— | — | @@ -31,6 +37,14 @@ |
32 | 38 | int log_backtrace; |
33 | 39 | int ignore_logging_errors; |
34 | 40 | int backtrace_in_php_error_message; |
| 41 | + long timeout; |
| 42 | + int alarm_set; |
| 43 | + void (*old_on_timeout)(int seconds TSRMLS_DC); |
| 44 | +#ifdef WMERRORS_USE_TIMER |
| 45 | + struct sigaction old_rt_action; |
| 46 | + timer_t timer; |
| 47 | +#endif |
| 48 | + |
35 | 49 | ZEND_END_MODULE_GLOBALS(wmerrors) |
36 | 50 | |
37 | 51 | |