Merge branch 'PHP-8.2'

* PHP-8.2:
  [ci skip] NEWS
  [ci skip] NEWS
  fix: support for timeouts with ZTS on Linux (#10141)
This commit is contained in:
Arnaud Le Blanc 2023-03-03 11:56:34 +01:00
commit 0c7fc351ea
11 changed files with 253 additions and 1 deletions

View file

@ -302,6 +302,28 @@ fi
AC_MSG_CHECKING(whether to enable zend signal handling) AC_MSG_CHECKING(whether to enable zend signal handling)
AC_MSG_RESULT($ZEND_SIGNALS) AC_MSG_RESULT($ZEND_SIGNALS)
dnl Don't enable Zend Max Execution Timers by default until PHP 8.3 to not break the ABI
AC_ARG_ENABLE([zend-max-execution-timers],
[AS_HELP_STRING([--enable-zend-max-execution-timers],
[whether to enable zend max execution timers])],
[ZEND_MAX_EXECUTION_TIMERS=$enableval],
[ZEND_MAX_EXECUTION_TIMERS='no'])
AS_CASE(["$host_alias"], [*linux*], [], [ZEND_MAX_EXECUTION_TIMERS='no'])
PHP_CHECK_FUNC(timer_create, rt)
if test "$ac_cv_func_timer_create" != "yes"; then
ZEND_MAX_EXECUTION_TIMERS='no'
fi
if test "$ZEND_MAX_EXECUTION_TIMERS" = "yes"; then
AC_DEFINE(ZEND_MAX_EXECUTION_TIMERS, 1, [Use zend max execution timers])
CFLAGS="$CFLAGS -DZEND_MAX_EXECUTION_TIMERS"
fi
AC_MSG_CHECKING(whether to enable zend max execution timers)
AC_MSG_RESULT($ZEND_MAX_EXECUTION_TIMERS)
]) ])
AC_ARG_ENABLE([gcc-global-regs], AC_ARG_ENABLE([gcc-global-regs],

View file

@ -36,6 +36,7 @@
#include "zend_observer.h" #include "zend_observer.h"
#include "zend_fibers.h" #include "zend_fibers.h"
#include "zend_call_stack.h" #include "zend_call_stack.h"
#include "zend_max_execution_timer.h"
#include "Optimizer/zend_optimizer.h" #include "Optimizer/zend_optimizer.h"
static size_t global_map_ptr_last = 0; static size_t global_map_ptr_last = 0;
@ -802,6 +803,10 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
executor_globals->stack_limit = (void*)0; executor_globals->stack_limit = (void*)0;
executor_globals->stack_base = (void*)0; executor_globals->stack_base = (void*)0;
#endif #endif
#ifdef ZEND_MAX_EXECUTION_TIMERS
executor_globals->pid = 0;
executor_globals->oldact = (struct sigaction){0};
#endif
} }
/* }}} */ /* }}} */
@ -826,6 +831,7 @@ static void zend_new_thread_end_handler(THREAD_T thread_id) /* {{{ */
#ifdef ZEND_CHECK_STACK_LIMIT #ifdef ZEND_CHECK_STACK_LIMIT
zend_call_stack_init(); zend_call_stack_init();
#endif #endif
zend_max_execution_timer_init();
} }
/* }}} */ /* }}} */
#endif #endif
@ -1607,6 +1613,18 @@ ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *
abort(); abort();
} }
ZEND_API ZEND_COLD ZEND_NORETURN void zend_strerror_noreturn(int type, int errn, const char *message)
{
#ifdef HAVE_STR_ERROR_R
char buf[1024];
strerror_r(errn, buf, sizeof(buf));
#else
char *buf = strerror(errn);
#endif
zend_error_noreturn(type, "%s: %s (%d)", message, buf, errn);
}
ZEND_API ZEND_COLD void zend_error_zstr(int type, zend_string *message) { ZEND_API ZEND_COLD void zend_error_zstr(int type, zend_string *message) {
zend_string *filename; zend_string *filename;
uint32_t lineno; uint32_t lineno;

View file

@ -40,6 +40,7 @@
#include "zend_smart_string_public.h" #include "zend_smart_string_public.h"
#include "zend_signal.h" #include "zend_signal.h"
#include "zend_type_code.h" #include "zend_type_code.h"
#include "zend_max_execution_timer.h"
#define zend_sprintf sprintf #define zend_sprintf sprintf
@ -360,6 +361,9 @@ ZEND_API ZEND_COLD void zend_value_error(const char *format, ...) ZEND_ATTRIBUTE
ZEND_COLD void zenderror(const char *error); ZEND_COLD void zenderror(const char *error);
/* For internal C errors */
ZEND_API ZEND_COLD ZEND_NORETURN void zend_strerror_noreturn(int type, int errn, const char *message);
/* The following #define is used for code duality in PHP for Engine 1 & 2 */ /* The following #define is used for code duality in PHP for Engine 1 & 2 */
#define ZEND_STANDARD_CLASS_DEF_PTR zend_standard_class_def #define ZEND_STANDARD_CLASS_DEF_PTR zend_standard_class_def
extern ZEND_API zend_class_entry *zend_standard_class_def; extern ZEND_API zend_class_entry *zend_standard_class_def;

View file

@ -44,6 +44,9 @@
#ifdef HAVE_UNISTD_H #ifdef HAVE_UNISTD_H
#include <unistd.h> #include <unistd.h>
#endif #endif
#ifdef ZEND_MAX_EXECUTION_TIMERS
#include <sys/syscall.h>
#endif
ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data); ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data);
ZEND_API void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value); ZEND_API void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value);
@ -196,6 +199,7 @@ void init_executor(void) /* {{{ */
EG(filename_override) = NULL; EG(filename_override) = NULL;
EG(lineno_override) = -1; EG(lineno_override) = -1;
zend_max_execution_timer_init();
zend_fiber_init(); zend_fiber_init();
zend_weakrefs_init(); zend_weakrefs_init();
@ -413,6 +417,7 @@ void shutdown_executor(void) /* {{{ */
zend_shutdown_executor_values(fast_shutdown); zend_shutdown_executor_values(fast_shutdown);
zend_weakrefs_shutdown(); zend_weakrefs_shutdown();
zend_max_execution_timer_shutdown();
zend_fiber_shutdown(); zend_fiber_shutdown();
zend_try { zend_try {
@ -1367,8 +1372,35 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */
/* }}} */ /* }}} */
#ifndef ZEND_WIN32 #ifndef ZEND_WIN32
# ifdef ZEND_MAX_EXECUTION_TIMERS
static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */
{
#ifdef ZTS
if (!tsrm_is_managed_thread()) {
fprintf(stderr, "zend_timeout_handler() called in a thread not managed by PHP. The expected signal handler will not be called. This is probably a bug.\n");
return;
}
#endif
if (si->si_value.sival_ptr != &EG(max_execution_timer_timer)) {
#ifdef MAX_EXECUTION_TIMERS_DEBUG
fprintf(stderr, "Executing previous handler (if set) for unexpected signal SIGRTMIN received on thread %d\n", (pid_t) syscall(SYS_gettid));
#endif
if (EG(oldact).sa_sigaction) {
EG(oldact).sa_sigaction(dummy, si, uc);
return;
}
if (EG(oldact).sa_handler) EG(oldact).sa_handler(dummy);
return;
}
# else
static void zend_timeout_handler(int dummy) /* {{{ */ static void zend_timeout_handler(int dummy) /* {{{ */
{ {
# endif
#ifdef ZTS #ifdef ZTS
if (!tsrm_is_managed_thread()) { if (!tsrm_is_managed_thread()) {
fprintf(stderr, "zend_timeout_handler() called in a thread not managed by PHP. The expected signal handler will not be called. This is probably a bug.\n"); fprintf(stderr, "zend_timeout_handler() called in a thread not managed by PHP. The expected signal handler will not be called. This is probably a bug.\n");
@ -1474,6 +1506,21 @@ static void zend_set_timeout_ex(zend_long seconds, bool reset_signals) /* {{{ */
zend_error_noreturn(E_ERROR, "Could not queue new timer"); zend_error_noreturn(E_ERROR, "Could not queue new timer");
return; return;
} }
#elif defined(ZEND_MAX_EXECUTION_TIMERS)
zend_max_execution_timer_settime(seconds);
if (reset_signals) {
sigset_t sigset;
struct sigaction act;
act.sa_sigaction = zend_timeout_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
sigaction(SIGRTMIN, &act, NULL);
sigemptyset(&sigset);
sigaddset(&sigset, SIGRTMIN);
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
}
#elif defined(HAVE_SETITIMER) #elif defined(HAVE_SETITIMER)
{ {
struct itimerval t_r; /* timeout requested */ struct itimerval t_r; /* timeout requested */
@ -1539,6 +1586,8 @@ void zend_unset_timeout(void) /* {{{ */
} }
tq_timer = NULL; tq_timer = NULL;
} }
#elif ZEND_MAX_EXECUTION_TIMERS
zend_max_execution_timer_settime(0);
#elif defined(HAVE_SETITIMER) #elif defined(HAVE_SETITIMER)
if (EG(timeout_seconds)) { if (EG(timeout_seconds)) {
struct itimerval no_timeout; struct itimerval no_timeout;

View file

@ -23,6 +23,7 @@
#include <setjmp.h> #include <setjmp.h>
#include <stdint.h> #include <stdint.h>
#include <sys/types.h>
#include "zend_globals_macros.h" #include "zend_globals_macros.h"
@ -39,6 +40,7 @@
#include "zend_multiply.h" #include "zend_multiply.h"
#include "zend_arena.h" #include "zend_arena.h"
#include "zend_call_stack.h" #include "zend_call_stack.h"
#include "zend_max_execution_timer.h"
/* Define ZTS if you want a thread-safe Zend */ /* Define ZTS if you want a thread-safe Zend */
/*#undef ZTS*/ /*#undef ZTS*/
@ -294,6 +296,12 @@ struct _zend_executor_globals {
zend_ulong reserved_stack_size; zend_ulong reserved_stack_size;
#endif #endif
#ifdef ZEND_MAX_EXECUTION_TIMERS
timer_t max_execution_timer_timer;
pid_t pid;
struct sigaction oldact;
#endif
void *reserved[ZEND_MAX_RESERVED_RESOURCES]; void *reserved[ZEND_MAX_RESERVED_RESOURCES];
}; };

View file

@ -0,0 +1,103 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Kévin Dunglas <kevin@dunglas.dev> |
+----------------------------------------------------------------------+
*/
#ifdef ZEND_MAX_EXECUTION_TIMERS
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include "zend.h"
#include "zend_globals.h"
// Musl Libc defines this macro, glibc does not
// According to "man 2 timer_create" this field should always be available, but it's not: https://sourceware.org/bugzilla/show_bug.cgi?id=27417
# ifndef sigev_notify_thread_id
# define sigev_notify_thread_id _sigev_un._tid
# endif
ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
{
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD_ID;
sev.sigev_value.sival_ptr = &EG(max_execution_timer_timer);
sev.sigev_signo = SIGRTMIN;
sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid);
EG(pid) = getpid();
// Measure wall time instead of CPU time as originally planned now that it is possible https://github.com/php/php-src/pull/6504#issuecomment-1370303727
if (timer_create(CLOCK_BOOTTIME, &sev, &EG(max_execution_timer_timer)) != 0) {
zend_strerror_noreturn(E_ERROR, errno, "Could not create timer");
}
# ifdef MAX_EXECUTION_TIMERS_DEBUG
fprintf(stderr, "Timer %#jx created on thread %d\n", (uintmax_t) EG(max_execution_timer_timer), sev.sigev_notify_thread_id);
# endif
sigaction(sev.sigev_signo, NULL, &EG(oldact));
}
/* }}} */
void zend_max_execution_timer_settime(zend_long seconds) /* {{{ }*/
{
/* Timer not initialized or shutdown. */
if (!EG(pid)) {
return;
}
timer_t timer = EG(max_execution_timer_timer);
struct itimerspec its;
its.it_value.tv_sec = seconds;
its.it_value.tv_nsec = its.it_interval.tv_sec = its.it_interval.tv_nsec = 0;
# ifdef MAX_EXECUTION_TIMERS_DEBUG
fprintf(stderr, "Setting timer %#jx on thread %d (%ld seconds)...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid), seconds);
# endif
if (timer_settime(timer, 0, &its, NULL) != 0) {
zend_strerror_noreturn(E_ERROR, errno, "Could not set timer");
}
}
/* }}} */
void zend_max_execution_timer_shutdown(void) /* {{{ */
{
/* Don't try to delete a timer created before a call to fork() */
if (EG(pid) != getpid()) {
return;
}
EG(pid) = 0;
timer_t timer = EG(max_execution_timer_timer);
# ifdef MAX_EXECUTION_TIMERS_DEBUG
fprintf(stderr, "Deleting timer %#jx on thread %d...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid));
# endif
int err = timer_delete(timer);
if (err != 0) {
zend_strerror_noreturn(E_ERROR, errno, "Could not delete timer");
}
}
/* }}}} */
#endif

View file

@ -0,0 +1,36 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Kévin Dunglas <kevin@dunglas.dev> |
+----------------------------------------------------------------------+
*/
#ifndef ZEND_MAX_EXECUTION_TIMER_H
#define ZEND_MAX_EXECUTION_TIMER_H
# ifdef ZEND_MAX_EXECUTION_TIMERS
#include "zend_long.h"
/* Must be called after calls to fork() */
ZEND_API void zend_max_execution_timer_init(void);
void zend_max_execution_timer_settime(zend_long seconds);
void zend_max_execution_timer_shutdown(void);
# else
#define zend_max_execution_timer_init()
#define zend_max_execution_timer_settime(seconds)
#define zend_max_execution_timer_shutdown()
# endif
#endif

View file

@ -636,6 +636,7 @@ asprintf \
nanosleep \ nanosleep \
memmem \ memmem \
memrchr \ memrchr \
strerror_r \
) )
AX_FUNC_WHICH_GETHOSTBYNAME_R AX_FUNC_WHICH_GETHOSTBYNAME_R
@ -1722,7 +1723,7 @@ PHP_ADD_SOURCES(Zend, \
zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \ zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \
zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \ zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c \ zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c \
zend_rc_debug.c \ zend_rc_debug.c zend_max_execution_timer.c \
Optimizer/zend_optimizer.c \ Optimizer/zend_optimizer.c \
Optimizer/pass1.c \ Optimizer/pass1.c \
Optimizer/pass3.c \ Optimizer/pass3.c \

View file

@ -58,6 +58,8 @@
#include "pcntl_arginfo.h" #include "pcntl_arginfo.h"
#include "Zend/zend_max_execution_timer.h"
ZEND_DECLARE_MODULE_GLOBALS(pcntl) ZEND_DECLARE_MODULE_GLOBALS(pcntl)
static PHP_GINIT_FUNCTION(pcntl); static PHP_GINIT_FUNCTION(pcntl);
@ -184,6 +186,8 @@ PHP_FUNCTION(pcntl_fork)
if (id == -1) { if (id == -1) {
PCNTL_G(last_error) = errno; PCNTL_G(last_error) = errno;
php_error_docref(NULL, E_WARNING, "Error %d", errno); php_error_docref(NULL, E_WARNING, "Error %d", errno);
} else if (id == 0) {
zend_max_execution_timer_init();
} }
RETURN_LONG((zend_long) id); RETURN_LONG((zend_long) id);

View file

@ -898,6 +898,12 @@ PHPAPI ZEND_COLD void php_print_info(int flag)
efree(descr); efree(descr);
} }
#ifdef ZEND_MAX_EXECUTION_TIMERS
php_info_print_table_row(2, "Zend Max Execution Timers", "enabled" );
#else
php_info_print_table_row(2, "Zend Max Execution Timers", "disabled" );
#endif
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
php_info_print_table_row(2, "IPv6 Support", "enabled" ); php_info_print_table_row(2, "IPv6 Support", "enabled" );
#else #else

View file

@ -34,6 +34,7 @@ Thread Safety => %s%A
Zend Signal Handling => %s Zend Signal Handling => %s
Zend Memory Manager => %s Zend Memory Manager => %s
Zend Multibyte Support => %s Zend Multibyte Support => %s
Zend Max Execution Timers => %s
IPv6 Support => %s IPv6 Support => %s
DTrace Support => %s DTrace Support => %s