diff --git a/Zend/Zend.m4 b/Zend/Zend.m4 index 47e79f77dc2cb..fbf2943266632 100644 --- a/Zend/Zend.m4 +++ b/Zend/Zend.m4 @@ -297,19 +297,29 @@ fi AC_MSG_CHECKING(whether to enable zend signal handling) AC_MSG_RESULT($ZEND_SIGNALS) + +dnl Enable Zend Max Execution Timers by default on macOS +AS_CASE(["$host_alias"], [*darwin*], [ZEND_MAX_EXECUTION_TIMERS="yes"], [ZEND_MAX_EXECUTION_TIMERS=$ZEND_ZTS]) + 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=$ZEND_ZTS]) - -AS_CASE(["$host_alias"], [*linux*|*freebsd*], [], [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 + []) + +AS_CASE( + ["$host_alias"], + [*linux*|*freebsd*], [ + AC_SEARCH_LIBS([timer_create], [rt],, [ZEND_MAX_EXECUTION_TIMERS='no']) + ], + [*darwin*], [], + [ZEND_MAX_EXECUTION_TIMERS='no'] +) + +AS_CASE(["$host_alias"], [*darwin*], [], [ + AC_SEARCH_LIBS([timer_create], [rt],, [ZEND_MAX_EXECUTION_TIMERS='no']) +]) if test "$ZEND_MAX_EXECUTION_TIMERS" = "yes"; then AC_DEFINE(ZEND_MAX_EXECUTION_TIMERS, 1, [Use zend max execution timers]) diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 71512ccf1b122..a3439808292ce 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -400,6 +400,10 @@ ZEND_API zend_class_entry *zend_fetch_class(zend_string *class_name, uint32_t fe ZEND_API zend_class_entry *zend_fetch_class_with_scope(zend_string *class_name, uint32_t fetch_type, zend_class_entry *scope); ZEND_API zend_class_entry *zend_fetch_class_by_name(zend_string *class_name, zend_string *lcname, uint32_t fetch_type); +#if defined(__APPLE__) && !defined(ZTS) +void zend_timeout_handler(void); +#endif + ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name); ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function_str(const char *name, size_t len); ZEND_API void ZEND_FASTCALL zend_init_func_run_time_cache(zend_op_array *op_array); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index c297523b9249a..7168ec04c8a04 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1401,20 +1401,25 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */ #ifndef ZEND_WIN32 # ifdef ZEND_MAX_EXECUTION_TIMERS +# if defined(__APPLE__) && !defined(ZTS) +void zend_timeout_handler(void) /* {{{ */ +# else static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */ +# endif { -#ifdef ZTS +# 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 +# endif +# ifndef __APPLE__ if (si->si_value.sival_ptr != &EG(max_execution_timer_timer)) { -#ifdef MAX_EXECUTION_TIMERS_DEBUG +# 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 +# endif if (EG(oldact).sa_sigaction) { EG(oldact).sa_sigaction(dummy, si, uc); @@ -1425,6 +1430,7 @@ static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */ return; } +# endif /* ifndef __APPLE__ */ # else static void zend_timeout_handler(int dummy) /* {{{ */ { @@ -1537,6 +1543,7 @@ static void zend_set_timeout_ex(zend_long seconds, bool reset_signals) /* {{{ */ #elif defined(ZEND_MAX_EXECUTION_TIMERS) zend_max_execution_timer_settime(seconds); +# if !defined(__APPLE__) || defined(ZTS) if (reset_signals) { sigset_t sigset; struct sigaction act; @@ -1544,11 +1551,13 @@ static void zend_set_timeout_ex(zend_long seconds, bool reset_signals) /* {{{ */ act.sa_sigaction = zend_timeout_handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_ONSTACK | SA_SIGINFO; - sigaction(SIGRTMIN, &act, NULL); + sigaction(ZEND_MAX_EXECUTION_TIMERS_SIGNAL, &act, NULL); + sigemptyset(&sigset); - sigaddset(&sigset, SIGRTMIN); + sigaddset(&sigset, ZEND_MAX_EXECUTION_TIMERS_SIGNAL); sigprocmask(SIG_UNBLOCK, &sigset, NULL); } +# endif #elif defined(HAVE_SETITIMER) { struct itimerval t_r; /* timeout requested */ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 8900a5f416f53..65a19fb69f817 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -24,6 +24,9 @@ #include #include #include +#ifdef __APPLE__ +#include +#endif #include "zend_globals_macros.h" @@ -299,7 +302,12 @@ struct _zend_executor_globals { #endif #ifdef ZEND_MAX_EXECUTION_TIMERS +# ifdef __APPLE__ + dispatch_source_t max_execution_timer_source; + bool max_execution_timer_suspended; +# else timer_t max_execution_timer_timer; +# endif pid_t pid; struct sigaction oldact; #endif diff --git a/Zend/zend_max_execution_timer.c b/Zend/zend_max_execution_timer.c index 16c2b7e2b8d23..5440256d4e74d 100644 --- a/Zend/zend_max_execution_timer.c +++ b/Zend/zend_max_execution_timer.c @@ -16,6 +16,103 @@ #ifdef ZEND_MAX_EXECUTION_TIMERS +# ifdef __APPLE__ + +#include +# ifdef ZTS +#include +# else +#include "zend_execute.h" +# endif + +#include "zend.h" +#include "zend_globals.h" + +// macOS doesn't support timer_create(), fallback to Grand Central Dispatch + +static inline void zend_max_execution_timer_handler(void *arg) +{ +#ifdef ZTS + pthread_t *tid = (pthread_t *) arg; + pthread_kill(*tid, ZEND_MAX_EXECUTION_TIMERS_SIGNAL); +#else + zend_timeout_handler(); +#endif +} + +static inline void zend_max_execution_timer_cancel(void *arg) +{ + free(arg); +} + +ZEND_API void zend_max_execution_timer_init(void) /* {{{ */ +{ + pid_t pid = getpid(); + + if (EG(pid) == pid) { + return; + } + + dispatch_queue_global_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); + EG(max_execution_timer_source) = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + if (EG(max_execution_timer_source) == NULL) { + zend_strerror_noreturn(E_ERROR, errno, "Could not create dispatch source"); + } + + EG(pid) = pid; + EG(max_execution_timer_suspended) = 1; + +# ifdef ZTS + pthread_t tid = pthread_self(); + pthread_t *ptid = malloc(sizeof(pthread_t)); + memcpy(ptid, &tid, sizeof(pthread_t)); + dispatch_set_context(EG(max_execution_timer_source), ptid); +#else + //dispatch_set_context(EG(max_execution_timer_source), NULL); +# endif + + dispatch_source_set_event_handler_f(EG(max_execution_timer_source), zend_max_execution_timer_handler); + dispatch_source_set_cancel_handler_f(EG(max_execution_timer_source), zend_max_execution_timer_cancel); +} /* }}} */ + +void zend_max_execution_timer_settime(zend_long seconds) /* {{{ */ +{ + if (seconds == 0) { + if (!EG(max_execution_timer_suspended)) { + dispatch_suspend(EG(max_execution_timer_source)); + EG(max_execution_timer_suspended) = 1; + } + + return; + } + + dispatch_source_set_timer( + EG(max_execution_timer_source), + dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), + seconds * NSEC_PER_SEC, + 0 + ); + if (EG(max_execution_timer_suspended)) { + dispatch_resume(EG(max_execution_timer_source)); + EG(max_execution_timer_suspended) = 0; + } +} /* }}} */ + +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; + + dispatch_source_cancel(EG(max_execution_timer_source)); + //dispatch_release(EG(max_execution_timer_source)); +} /* }}} */ + +# else + #include #include #include @@ -23,25 +120,25 @@ #include #include #include -# ifdef __FreeBSD__ -# include -# endif +# ifdef __FreeBSD__ +# include +# endif #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 +# ifndef sigev_notify_thread_id +# define sigev_notify_thread_id _sigev_un._tid +# endif // FreeBSD doesn't support CLOCK_BOOTTIME -# ifdef __FreeBSD__ -# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_MONOTONIC -# else -# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_BOOTTIME -# endif +# ifdef __FreeBSD__ +# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_MONOTONIC +# else +# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_BOOTTIME +# endif ZEND_API void zend_max_execution_timer_init(void) /* {{{ */ { @@ -54,12 +151,12 @@ 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; -# ifdef __FreeBSD__ + sev.sigev_signo = ZEND_MAX_EXECUTION_TIMERS_SIGNAL; +# ifdef __FreeBSD__ sev.sigev_notify_thread_id = pthread_getthreadid_np(); -# else +# else sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid); -# endif +# endif // 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(ZEND_MAX_EXECUTION_TIMERS_CLOCK, &sev, &EG(max_execution_timer_timer)) != 0) { @@ -68,9 +165,9 @@ ZEND_API void zend_max_execution_timer_init(void) /* {{{ */ EG(pid) = pid; -# ifdef MAX_EXECUTION_TIMERS_DEBUG +# 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 +# endif sigaction(sev.sigev_signo, NULL, &EG(oldact)); } @@ -89,9 +186,9 @@ void zend_max_execution_timer_settime(zend_long seconds) /* {{{ }*/ 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 +# 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 +# endif if (timer_settime(timer, 0, &its, NULL) != 0) { zend_strerror_noreturn(E_ERROR, errno, "Could not set timer"); @@ -110,9 +207,9 @@ void zend_max_execution_timer_shutdown(void) /* {{{ */ timer_t timer = EG(max_execution_timer_timer); -# ifdef MAX_EXECUTION_TIMERS_DEBUG +# ifdef MAX_EXECUTION_TIMERS_DEBUG fprintf(stderr, "Deleting timer %#jx on thread %d...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid)); -# endif +# endif int err = timer_delete(timer); if (err != 0) { @@ -121,4 +218,5 @@ void zend_max_execution_timer_shutdown(void) /* {{{ */ } /* }}}} */ +# endif #endif diff --git a/Zend/zend_max_execution_timer.h b/Zend/zend_max_execution_timer.h index 789f50e6b7dcd..491fd74d06a7c 100644 --- a/Zend/zend_max_execution_timer.h +++ b/Zend/zend_max_execution_timer.h @@ -21,6 +21,12 @@ #include "zend_long.h" +# ifdef __APPLE__ +#define ZEND_MAX_EXECUTION_TIMERS_SIGNAL SIGIO +# else +#define ZEND_MAX_EXECUTION_TIMERS_SIGNAL SIGRTMIN +# endif + /* 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); diff --git a/sapi/fpm/tests/status-listen.phpt b/sapi/fpm/tests/status-listen.phpt index fb400c8d62467..e9b471229d299 100644 --- a/sapi/fpm/tests/status-listen.phpt +++ b/sapi/fpm/tests/status-listen.phpt @@ -9,6 +9,7 @@ require_once "tester.inc"; $cfg = <<