Skip to content

Commit ad85e71

Browse files
authoredMar 3, 2023
fix: support for timeouts with ZTS on Linux (#10141)
1 parent d66ca5d commit ad85e71

File tree

11 files changed

+245
-1
lines changed

11 files changed

+245
-1
lines changed
 

‎Zend/Zend.m4

+22
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,28 @@ fi
269269
AC_MSG_CHECKING(whether to enable zend signal handling)
270270
AC_MSG_RESULT($ZEND_SIGNALS)
271271
272+
dnl Don't enable Zend Max Execution Timers by default until PHP 8.3 to not break the ABI
273+
AC_ARG_ENABLE([zend-max-execution-timers],
274+
[AS_HELP_STRING([--enable-zend-max-execution-timers],
275+
[whether to enable zend max execution timers])],
276+
[ZEND_MAX_EXECUTION_TIMERS=$enableval],
277+
[ZEND_MAX_EXECUTION_TIMERS='no'])
278+
279+
AS_CASE(["$host_alias"], [*linux*], [], [ZEND_MAX_EXECUTION_TIMERS='no'])
280+
281+
PHP_CHECK_FUNC(timer_create, rt)
282+
if test "$ac_cv_func_timer_create" != "yes"; then
283+
ZEND_MAX_EXECUTION_TIMERS='no'
284+
fi
285+
286+
if test "$ZEND_MAX_EXECUTION_TIMERS" = "yes"; then
287+
AC_DEFINE(ZEND_MAX_EXECUTION_TIMERS, 1, [Use zend max execution timers])
288+
CFLAGS="$CFLAGS -DZEND_MAX_EXECUTION_TIMERS"
289+
fi
290+
291+
AC_MSG_CHECKING(whether to enable zend max execution timers)
292+
AC_MSG_RESULT($ZEND_MAX_EXECUTION_TIMERS)
293+
272294
])
273295

274296
AC_MSG_CHECKING(whether /dev/urandom exists)

‎Zend/zend.c

+18
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "zend_attributes.h"
3636
#include "zend_observer.h"
3737
#include "zend_fibers.h"
38+
#include "zend_max_execution_timer.h"
3839
#include "Optimizer/zend_optimizer.h"
3940

4041
static size_t global_map_ptr_last = 0;
@@ -800,6 +801,10 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
800801
executor_globals->record_errors = false;
801802
executor_globals->num_errors = 0;
802803
executor_globals->errors = NULL;
804+
#ifdef ZEND_MAX_EXECUTION_TIMERS
805+
executor_globals->pid = 0;
806+
executor_globals->oldact = (struct sigaction){0};
807+
#endif
803808
}
804809
/* }}} */
805810

@@ -821,6 +826,7 @@ static void zend_new_thread_end_handler(THREAD_T thread_id) /* {{{ */
821826
{
822827
zend_copy_ini_directives();
823828
zend_ini_refresh_caches(ZEND_INI_STAGE_STARTUP);
829+
zend_max_execution_timer_init();
824830
}
825831
/* }}} */
826832
#endif
@@ -1612,6 +1618,18 @@ ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *
16121618
abort();
16131619
}
16141620

1621+
ZEND_API ZEND_COLD ZEND_NORETURN void zend_strerror_noreturn(int type, int errn, const char *message)
1622+
{
1623+
#ifdef HAVE_STR_ERROR_R
1624+
char buf[1024];
1625+
strerror_r(errn, buf, sizeof(buf));
1626+
#else
1627+
char *buf = strerror(errn);
1628+
#endif
1629+
1630+
zend_error_noreturn(type, "%s: %s (%d)", message, buf, errn);
1631+
}
1632+
16151633
ZEND_API ZEND_COLD void zend_error_zstr(int type, zend_string *message) {
16161634
zend_string *filename;
16171635
uint32_t lineno;

‎Zend/zend.h

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "zend_smart_str_public.h"
4040
#include "zend_smart_string_public.h"
4141
#include "zend_signal.h"
42+
#include "zend_max_execution_timer.h"
4243

4344
#define zend_sprintf sprintf
4445

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

354355
ZEND_COLD void zenderror(const char *error);
355356

357+
/* For internal C errors */
358+
ZEND_API ZEND_COLD ZEND_NORETURN void zend_strerror_noreturn(int type, int errn, const char *message);
359+
356360
/* The following #define is used for code duality in PHP for Engine 1 & 2 */
357361
#define ZEND_STANDARD_CLASS_DEF_PTR zend_standard_class_def
358362
extern ZEND_API zend_class_entry *zend_standard_class_def;

‎Zend/zend_execute_API.c

+41
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
#ifdef HAVE_UNISTD_H
4444
#include <unistd.h>
4545
#endif
46+
#ifdef ZEND_MAX_EXECUTION_TIMERS
47+
#include <sys/syscall.h>
48+
#endif
4649

4750
ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data);
4851
ZEND_API void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value);
@@ -192,6 +195,7 @@ void init_executor(void) /* {{{ */
192195
EG(num_errors) = 0;
193196
EG(errors) = NULL;
194197

198+
zend_max_execution_timer_init();
195199
zend_fiber_init();
196200
zend_weakrefs_init();
197201

@@ -403,6 +407,7 @@ void shutdown_executor(void) /* {{{ */
403407
zend_shutdown_executor_values(fast_shutdown);
404408

405409
zend_weakrefs_shutdown();
410+
zend_max_execution_timer_shutdown();
406411
zend_fiber_shutdown();
407412

408413
zend_try {
@@ -1314,8 +1319,27 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */
13141319
/* }}} */
13151320

13161321
#ifndef ZEND_WIN32
1322+
# ifdef ZEND_MAX_EXECUTION_TIMERS
1323+
static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */
1324+
{
1325+
if (si->si_value.sival_ptr != &EG(max_execution_timer_timer)) {
1326+
#ifdef MAX_EXECUTION_TIMERS_DEBUG
1327+
fprintf(stderr, "Executing previous handler (if set) for unexpected signal SIGRTMIN received on thread %d\n", (pid_t) syscall(SYS_gettid));
1328+
#endif
1329+
1330+
if (EG(oldact).sa_sigaction) {
1331+
EG(oldact).sa_sigaction(dummy, si, uc);
1332+
1333+
return;
1334+
}
1335+
if (EG(oldact).sa_handler) EG(oldact).sa_handler(dummy);
1336+
1337+
return;
1338+
}
1339+
# else
13171340
static void zend_timeout_handler(int dummy) /* {{{ */
13181341
{
1342+
# endif
13191343
#ifndef ZTS
13201344
if (EG(timed_out)) {
13211345
/* Die on hard timeout */
@@ -1415,6 +1439,21 @@ static void zend_set_timeout_ex(zend_long seconds, bool reset_signals) /* {{{ */
14151439
zend_error_noreturn(E_ERROR, "Could not queue new timer");
14161440
return;
14171441
}
1442+
#elif defined(ZEND_MAX_EXECUTION_TIMERS)
1443+
zend_max_execution_timer_settime(seconds);
1444+
1445+
if (reset_signals) {
1446+
sigset_t sigset;
1447+
struct sigaction act;
1448+
1449+
act.sa_sigaction = zend_timeout_handler;
1450+
sigemptyset(&act.sa_mask);
1451+
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
1452+
sigaction(SIGRTMIN, &act, NULL);
1453+
sigemptyset(&sigset);
1454+
sigaddset(&sigset, SIGRTMIN);
1455+
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
1456+
}
14181457
#elif defined(HAVE_SETITIMER)
14191458
{
14201459
struct itimerval t_r; /* timeout requested */
@@ -1480,6 +1519,8 @@ void zend_unset_timeout(void) /* {{{ */
14801519
}
14811520
tq_timer = NULL;
14821521
}
1522+
#elif ZEND_MAX_EXECUTION_TIMERS
1523+
zend_max_execution_timer_settime(0);
14831524
#elif defined(HAVE_SETITIMER)
14841525
if (EG(timeout_seconds)) {
14851526
struct itimerval no_timeout;

‎Zend/zend_globals.h

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323

2424
#include <setjmp.h>
25+
#include <sys/types.h>
2526

2627
#include "zend_globals_macros.h"
2728

@@ -36,6 +37,7 @@
3637
#include "zend_multibyte.h"
3738
#include "zend_multiply.h"
3839
#include "zend_arena.h"
40+
#include "zend_max_execution_timer.h"
3941

4042
/* Define ZTS if you want a thread-safe Zend */
4143
/*#undef ZTS*/
@@ -266,6 +268,12 @@ struct _zend_executor_globals {
266268
uint32_t num_errors;
267269
zend_error_info **errors;
268270

271+
#ifdef ZEND_MAX_EXECUTION_TIMERS
272+
timer_t max_execution_timer_timer;
273+
pid_t pid;
274+
struct sigaction oldact;
275+
#endif
276+
269277
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
270278
};
271279

‎Zend/zend_max_execution_timer.c

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Kévin Dunglas <kevin@dunglas.dev> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifdef ZEND_MAX_EXECUTION_TIMERS
18+
19+
#include <stdio.h>
20+
#include <signal.h>
21+
#include <time.h>
22+
#include <unistd.h>
23+
#include <errno.h>
24+
#include <sys/syscall.h>
25+
#include <sys/types.h>
26+
27+
#include "zend.h"
28+
#include "zend_globals.h"
29+
30+
// Musl Libc defines this macro, glibc does not
31+
// 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
32+
# ifndef sigev_notify_thread_id
33+
# define sigev_notify_thread_id _sigev_un._tid
34+
# endif
35+
36+
ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
37+
{
38+
struct sigevent sev;
39+
sev.sigev_notify = SIGEV_THREAD_ID;
40+
sev.sigev_value.sival_ptr = &EG(max_execution_timer_timer);
41+
sev.sigev_signo = SIGRTMIN;
42+
sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid);
43+
44+
EG(pid) = getpid();
45+
// 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
46+
if (timer_create(CLOCK_BOOTTIME, &sev, &EG(max_execution_timer_timer)) != 0) {
47+
zend_strerror_noreturn(E_ERROR, errno, "Could not create timer");
48+
}
49+
50+
# ifdef MAX_EXECUTION_TIMERS_DEBUG
51+
fprintf(stderr, "Timer %#jx created on thread %d\n", (uintmax_t) EG(max_execution_timer_timer), sev.sigev_notify_thread_id);
52+
# endif
53+
54+
sigaction(sev.sigev_signo, NULL, &EG(oldact));
55+
}
56+
/* }}} */
57+
58+
void zend_max_execution_timer_settime(zend_long seconds) /* {{{ }*/
59+
{
60+
/* Timer not initialized or shutdown. */
61+
if (!EG(pid)) {
62+
return;
63+
}
64+
65+
timer_t timer = EG(max_execution_timer_timer);
66+
67+
struct itimerspec its;
68+
its.it_value.tv_sec = seconds;
69+
its.it_value.tv_nsec = its.it_interval.tv_sec = its.it_interval.tv_nsec = 0;
70+
71+
# ifdef MAX_EXECUTION_TIMERS_DEBUG
72+
fprintf(stderr, "Setting timer %#jx on thread %d (%ld seconds)...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid), seconds);
73+
# endif
74+
75+
if (timer_settime(timer, 0, &its, NULL) != 0) {
76+
zend_strerror_noreturn(E_ERROR, errno, "Could not set timer");
77+
}
78+
}
79+
/* }}} */
80+
81+
void zend_max_execution_timer_shutdown(void) /* {{{ */
82+
{
83+
/* Don't try to delete a timer created before a call to fork() */
84+
if (EG(pid) != getpid()) {
85+
return;
86+
}
87+
88+
EG(pid) = 0;
89+
90+
timer_t timer = EG(max_execution_timer_timer);
91+
92+
# ifdef MAX_EXECUTION_TIMERS_DEBUG
93+
fprintf(stderr, "Deleting timer %#jx on thread %d...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid));
94+
# endif
95+
96+
int err = timer_delete(timer);
97+
if (err != 0) {
98+
zend_strerror_noreturn(E_ERROR, errno, "Could not delete timer");
99+
}
100+
}
101+
/* }}}} */
102+
103+
#endif

‎Zend/zend_max_execution_timer.h

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Kévin Dunglas <kevin@dunglas.dev> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifndef ZEND_MAX_EXECUTION_TIMER_H
18+
#define ZEND_MAX_EXECUTION_TIMER_H
19+
20+
# ifdef ZEND_MAX_EXECUTION_TIMERS
21+
22+
#include "zend_long.h"
23+
24+
/* Must be called after calls to fork() */
25+
ZEND_API void zend_max_execution_timer_init(void);
26+
void zend_max_execution_timer_settime(zend_long seconds);
27+
void zend_max_execution_timer_shutdown(void);
28+
29+
# else
30+
31+
#define zend_max_execution_timer_init()
32+
#define zend_max_execution_timer_settime(seconds)
33+
#define zend_max_execution_timer_shutdown()
34+
35+
# endif
36+
#endif

‎configure.ac

+2-1
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ vasprintf \
609609
asprintf \
610610
nanosleep \
611611
memmem \
612+
strerror_r \
612613
)
613614

614615
AX_FUNC_WHICH_GETHOSTBYNAME_R
@@ -1633,7 +1634,7 @@ PHP_ADD_SOURCES(Zend, \
16331634
zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \
16341635
zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \
16351636
zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \
1636-
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c \
1637+
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_max_execution_timer.c \
16371638
Optimizer/zend_optimizer.c \
16381639
Optimizer/pass1.c \
16391640
Optimizer/pass3.c \

‎ext/pcntl/pcntl.c

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
# define NSIG 32
5151
#endif
5252

53+
#include "Zend/zend_max_execution_timer.h"
54+
5355
ZEND_DECLARE_MODULE_GLOBALS(pcntl)
5456
static PHP_GINIT_FUNCTION(pcntl);
5557

@@ -531,6 +533,8 @@ PHP_FUNCTION(pcntl_fork)
531533
if (id == -1) {
532534
PCNTL_G(last_error) = errno;
533535
php_error_docref(NULL, E_WARNING, "Error %d", errno);
536+
} else if (id == 0) {
537+
zend_max_execution_timer_init();
534538
}
535539

536540
RETURN_LONG((zend_long) id);

‎ext/standard/info.c

+6
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,12 @@ PHPAPI ZEND_COLD void php_print_info(int flag)
891891
efree(descr);
892892
}
893893

894+
#ifdef ZEND_MAX_EXECUTION_TIMERS
895+
php_info_print_table_row(2, "Zend Max Execution Timers", "enabled" );
896+
#else
897+
php_info_print_table_row(2, "Zend Max Execution Timers", "disabled" );
898+
#endif
899+
894900
#if HAVE_IPV6
895901
php_info_print_table_row(2, "IPv6 Support", "enabled" );
896902
#else

‎ext/standard/tests/general_functions/phpinfo.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Thread Safety => %s%A
3434
Zend Signal Handling => %s
3535
Zend Memory Manager => %s
3636
Zend Multibyte Support => %s
37+
Zend Max Execution Timers => %s
3738
IPv6 Support => %s
3839
DTrace Support => %s
3940

0 commit comments

Comments
 (0)
Please sign in to comment.