Skip to content

fix: support for timeouts with ZTS on Linux #10141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
33a9070
fix: support for timeouts with ZTS on Linux
dunglas Dec 12, 2022
4cf916d
add gettid macro for old glibc
dunglas Dec 20, 2022
6cc4cdc
use the syscall instead of gettid()
dunglas Dec 20, 2022
590e011
indentation
dunglas Dec 20, 2022
d1eef55
fix cast
dunglas Dec 20, 2022
b5277f0
hide debug info by default
dunglas Dec 20, 2022
cfd0a6e
create only one timer per thread
dunglas Dec 26, 2022
215e097
call previous signal handler
dunglas Dec 26, 2022
1683177
fix
dunglas Dec 26, 2022
ba39b43
better error handling
dunglas Dec 26, 2022
e44dd06
fix error message generation
dunglas Dec 26, 2022
82b923f
more debug details
dunglas Dec 26, 2022
da6511e
refactor, fix pcntl_fork()
dunglas Dec 27, 2022
3832066
use strerror() on Windows
dunglas Dec 27, 2022
c00f354
try to fix tracing JIT
dunglas Dec 27, 2022
bfe9e42
better initialization
dunglas Dec 27, 2022
d696a3a
fix some tests
dunglas Dec 27, 2022
4f4bf35
fix SAPI tests
dunglas Dec 28, 2022
d1391f6
better timer creation
dunglas Dec 29, 2022
ff02b8c
cleanup
dunglas Dec 29, 2022
da21e3f
skip delete when needed
dunglas Dec 29, 2022
9dad7b9
improve timer support detection
dunglas Dec 30, 2022
1f72b22
add entry in phpinfo
dunglas Dec 30, 2022
b8770bb
rename to zend_timers
dunglas Jan 9, 2023
cde29fd
Measure wall-time instead of CPU-time
dunglas Jan 9, 2023
d261423
reduce API surface
dunglas Jan 9, 2023
f0eabbd
cleanup API
dunglas Jan 10, 2023
9ba1025
add ZEND_NORETURN to zend_strerror_noreturn()
dunglas Jan 15, 2023
aec1f1b
store pid
dunglas Jan 15, 2023
f1182d0
fix review
dunglas Jan 30, 2023
11e5ab6
fix tests
dunglas Jan 31, 2023
9e6b557
feat: use CLOCK_BOOTTIME
dunglas Feb 8, 2023
c6675d8
feat: use real-time signal
dunglas Feb 8, 2023
b96ef6a
rename
dunglas Feb 20, 2023
c172e8d
arnaud's patch
dunglas Feb 24, 2023
fdcd57e
disable max execution timers by default
dunglas Feb 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Zend/Zend.m4
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,28 @@ fi
AC_MSG_CHECKING(whether to enable zend signal handling)
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_MSG_CHECKING(whether /dev/urandom exists)
Expand Down
18 changes: 18 additions & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "zend_attributes.h"
#include "zend_observer.h"
#include "zend_fibers.h"
#include "zend_max_execution_timer.h"
#include "Optimizer/zend_optimizer.h"

static size_t global_map_ptr_last = 0;
Expand Down Expand Up @@ -800,6 +801,10 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
executor_globals->record_errors = false;
executor_globals->num_errors = 0;
executor_globals->errors = NULL;
#ifdef ZEND_MAX_EXECUTION_TIMERS
executor_globals->pid = 0;
executor_globals->oldact = (struct sigaction){0};
#endif
}
/* }}} */

Expand All @@ -821,6 +826,7 @@ static void zend_new_thread_end_handler(THREAD_T thread_id) /* {{{ */
{
zend_copy_ini_directives();
zend_ini_refresh_caches(ZEND_INI_STAGE_STARTUP);
zend_max_execution_timer_init();
}
/* }}} */
#endif
Expand Down Expand Up @@ -1612,6 +1618,18 @@ ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *
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_string *filename;
uint32_t lineno;
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "zend_smart_str_public.h"
#include "zend_smart_string_public.h"
#include "zend_signal.h"
#include "zend_max_execution_timer.h"

#define zend_sprintf sprintf

Expand Down Expand Up @@ -353,6 +354,9 @@ ZEND_API ZEND_COLD void zend_value_error(const char *format, ...) ZEND_ATTRIBUTE

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 */
#define ZEND_STANDARD_CLASS_DEF_PTR zend_standard_class_def
extern ZEND_API zend_class_entry *zend_standard_class_def;
Expand Down
41 changes: 41 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#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_internal)(zend_execute_data *execute_data, zval *return_value);
Expand Down Expand Up @@ -192,6 +195,7 @@ void init_executor(void) /* {{{ */
EG(num_errors) = 0;
EG(errors) = NULL;

zend_max_execution_timer_init();
zend_fiber_init();
zend_weakrefs_init();

Expand Down Expand Up @@ -403,6 +407,7 @@ void shutdown_executor(void) /* {{{ */
zend_shutdown_executor_values(fast_shutdown);

zend_weakrefs_shutdown();
zend_max_execution_timer_shutdown();
zend_fiber_shutdown();

zend_try {
Expand Down Expand Up @@ -1314,8 +1319,27 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */
/* }}} */

#ifndef ZEND_WIN32
# ifdef ZEND_MAX_EXECUTION_TIMERS
static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */
{
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) /* {{{ */
{
# endif
#ifndef ZTS
if (EG(timed_out)) {
/* Die on hard timeout */
Expand Down Expand Up @@ -1415,6 +1439,21 @@ static void zend_set_timeout_ex(zend_long seconds, bool reset_signals) /* {{{ */
zend_error_noreturn(E_ERROR, "Could not queue new timer");
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)
{
struct itimerval t_r; /* timeout requested */
Expand Down Expand Up @@ -1480,6 +1519,8 @@ void zend_unset_timeout(void) /* {{{ */
}
tq_timer = NULL;
}
#elif ZEND_MAX_EXECUTION_TIMERS
zend_max_execution_timer_settime(0);
#elif defined(HAVE_SETITIMER)
if (EG(timeout_seconds)) {
struct itimerval no_timeout;
Expand Down
8 changes: 8 additions & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@


#include <setjmp.h>
#include <sys/types.h>

#include "zend_globals_macros.h"

Expand All @@ -36,6 +37,7 @@
#include "zend_multibyte.h"
#include "zend_multiply.h"
#include "zend_arena.h"
#include "zend_max_execution_timer.h"

/* Define ZTS if you want a thread-safe Zend */
/*#undef ZTS*/
Expand Down Expand Up @@ -266,6 +268,12 @@ struct _zend_executor_globals {
uint32_t num_errors;
zend_error_info **errors;

#ifdef ZEND_MAX_EXECUTION_TIMERS
timer_t max_execution_timer_timer;
pid_t pid;
struct sigaction oldact;
#endif

void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

Expand Down
103 changes: 103 additions & 0 deletions Zend/zend_max_execution_timer.c
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions Zend/zend_max_execution_timer.h
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ vasprintf \
asprintf \
nanosleep \
memmem \
strerror_r \
)

AX_FUNC_WHICH_GETHOSTBYNAME_R
Expand Down Expand Up @@ -1633,7 +1634,7 @@ PHP_ADD_SOURCES(Zend, \
zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.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_observer.c zend_system_id.c zend_enum.c zend_fibers.c \
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_max_execution_timer.c \
Optimizer/zend_optimizer.c \
Optimizer/pass1.c \
Optimizer/pass3.c \
Expand Down
4 changes: 4 additions & 0 deletions ext/pcntl/pcntl.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
# define NSIG 32
#endif

#include "Zend/zend_max_execution_timer.h"

ZEND_DECLARE_MODULE_GLOBALS(pcntl)
static PHP_GINIT_FUNCTION(pcntl);

Expand Down Expand Up @@ -531,6 +533,8 @@ PHP_FUNCTION(pcntl_fork)
if (id == -1) {
PCNTL_G(last_error) = errno;
php_error_docref(NULL, E_WARNING, "Error %d", errno);
} else if (id == 0) {
zend_max_execution_timer_init();
}

RETURN_LONG((zend_long) id);
Expand Down
6 changes: 6 additions & 0 deletions ext/standard/info.c
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,12 @@ PHPAPI ZEND_COLD void php_print_info(int flag)
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

#if HAVE_IPV6
php_info_print_table_row(2, "IPv6 Support", "enabled" );
#else
Expand Down
Loading