Skip to content

Commit 81e8ae1

Browse files
committed
fix: support for timeouts with ZTS on Linux
add gettid macro for old glibc use the syscall instead of gettid() indentation fix cast hide debug info by default create only one timer per thread call previous signal handler fix better error handling fix error message generation more debug details refactor, fix pcntl_fork() use strerror() on Windows try to fix tracing JIT better initialization fix some tests fix SAPI tests better timer creation cleanup skip delete when needed improve timer support detection add entry in phpinfo
1 parent 7b08fe9 commit 81e8ae1

File tree

15 files changed

+264
-2
lines changed

15 files changed

+264
-2
lines changed

Zend/Zend.m4

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,28 @@ fi
272272
AC_MSG_CHECKING(whether to enable zend signal handling)
273273
AC_MSG_RESULT($ZEND_SIGNALS)
274274
275+
dnl By default, enable Zend Timer only for ZTS builds
276+
AC_ARG_ENABLE([zend-timer],
277+
[AS_HELP_STRING([--enable-zend-timer],
278+
[whether to enable zend timer])],
279+
[ZEND_TIMER=$enableval],
280+
[ZEND_TIMER=$ZEND_ZTS])
281+
282+
AS_CASE(["$host_alias"], [*linux*], [], [ZEND_TIMER='no'])
283+
284+
PHP_CHECK_FUNC(timer_create, rt)
285+
if test "$ac_cv_func_timer_create" != "yes"; then
286+
ZEND_TIMER='no'
287+
fi
288+
289+
if test "$ZEND_TIMER" = "yes"; then
290+
AC_DEFINE(ZEND_TIMER, 1, [Use zend timer])
291+
CFLAGS="$CFLAGS -DZEND_TIMER"
292+
fi
293+
294+
AC_MSG_CHECKING(whether to enable zend timer)
295+
AC_MSG_RESULT($ZEND_TIMER)
296+
275297
])
276298

277299
AC_MSG_CHECKING(whether /dev/urandom exists)

Zend/zend.c

Lines changed: 25 additions & 1 deletion
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_timer.h"
3839
#include "Optimizer/zend_optimizer.h"
3940

4041
static size_t global_map_ptr_last = 0;
@@ -816,6 +817,17 @@ static void zend_new_thread_end_handler(THREAD_T thread_id) /* {{{ */
816817
{
817818
zend_copy_ini_directives();
818819
zend_ini_refresh_caches(ZEND_INI_STAGE_STARTUP);
820+
#ifdef ZEND_TIMER
821+
zend_timer_create();
822+
#endif
823+
}
824+
/* }}} */
825+
826+
static void zend_thread_shutdown_handler(void) { /* {{{ */
827+
zend_interned_strings_dtor();
828+
#ifdef ZEND_TIMER
829+
zend_timer_delete();
830+
#endif
819831
}
820832
/* }}} */
821833
#endif
@@ -1006,7 +1018,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
10061018

10071019
#ifdef ZTS
10081020
tsrm_set_new_thread_end_handler(zend_new_thread_end_handler);
1009-
tsrm_set_shutdown_handler(zend_interned_strings_dtor);
1021+
tsrm_set_shutdown_handler(zend_thread_shutdown_handler);
10101022
#endif
10111023
}
10121024
/* }}} */
@@ -1593,6 +1605,18 @@ ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *
15931605
abort();
15941606
}
15951607

1608+
ZEND_API ZEND_COLD void zend_strerror_noreturn(int type, int errn, const char *message)
1609+
{
1610+
#ifdef HAVE_STR_ERROR_R
1611+
char buf[1024];
1612+
strerror_r(errn, buf, sizeof(buf));
1613+
#else
1614+
char *buf = strerror(errn);
1615+
#endif
1616+
1617+
zend_error_noreturn(type, "%s: %s (%d)", message, buf, errn);
1618+
}
1619+
15961620
ZEND_API ZEND_COLD void zend_error_zstr(int type, zend_string *message) {
15971621
zend_string *filename;
15981622
uint32_t lineno;

Zend/zend.h

Lines changed: 4 additions & 0 deletions
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_timer.h"
4243

4344
#define zend_sprintf sprintf
4445

@@ -357,6 +358,9 @@ ZEND_API ZEND_COLD void zend_value_error(const char *format, ...) ZEND_ATTRIBUTE
357358

358359
ZEND_COLD void zenderror(const char *error);
359360

361+
/* For internal C errors */
362+
ZEND_API ZEND_COLD void zend_strerror_noreturn(int type, int errn, const char *message);
363+
360364
/* The following #define is used for code duality in PHP for Engine 1 & 2 */
361365
#define ZEND_STANDARD_CLASS_DEF_PTR zend_standard_class_def
362366
extern ZEND_API zend_class_entry *zend_standard_class_def;

Zend/zend_execute_API.c

Lines changed: 42 additions & 0 deletions
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_TIMER
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);
@@ -170,6 +173,9 @@ void init_executor(void) /* {{{ */
170173
EG(full_tables_cleanup) = 0;
171174
ZEND_ATOMIC_BOOL_INIT(&EG(vm_interrupt), false);
172175
ZEND_ATOMIC_BOOL_INIT(&EG(timed_out), false);
176+
#ifdef ZEND_TIMER
177+
zend_timer_create();
178+
#endif
173179

174180
EG(exception) = NULL;
175181
EG(prev_exception) = NULL;
@@ -1359,8 +1365,27 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */
13591365
/* }}} */
13601366

13611367
#ifndef ZEND_WIN32
1368+
# ifdef ZEND_TIMER
1369+
static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */
1370+
{
1371+
if (si->si_value.sival_ptr != &EG(timer)) {
1372+
#ifdef TIMER_DEBUG
1373+
fprintf(stderr, "Executing previous handler (if set) for unexpected signal SIGIO received on thread %d\n", (pid_t) syscall(SYS_gettid));
1374+
#endif
1375+
1376+
if (EG(oldact).sa_sigaction) {
1377+
EG(oldact).sa_sigaction(dummy, si, uc);
1378+
1379+
return;
1380+
}
1381+
if (EG(oldact).sa_handler) EG(oldact).sa_handler(dummy);
1382+
1383+
return;
1384+
}
1385+
# else
13621386
static void zend_timeout_handler(int dummy) /* {{{ */
13631387
{
1388+
# endif
13641389
#ifndef ZTS
13651390
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
13661391
/* Die on hard timeout */
@@ -1460,6 +1485,21 @@ static void zend_set_timeout_ex(zend_long seconds, bool reset_signals) /* {{{ */
14601485
zend_error_noreturn(E_ERROR, "Could not queue new timer");
14611486
return;
14621487
}
1488+
#elif defined(ZEND_TIMER)
1489+
zend_timer_settime(seconds);
1490+
1491+
if (reset_signals) {
1492+
sigset_t sigset;
1493+
struct sigaction act;
1494+
1495+
act.sa_sigaction = zend_timeout_handler;
1496+
sigemptyset(&act.sa_mask);
1497+
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
1498+
sigaction(SIGIO, &act, NULL);
1499+
sigemptyset(&sigset);
1500+
sigaddset(&sigset, SIGIO);
1501+
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
1502+
}
14631503
#elif defined(HAVE_SETITIMER)
14641504
{
14651505
struct itimerval t_r; /* timeout requested */
@@ -1525,6 +1565,8 @@ void zend_unset_timeout(void) /* {{{ */
15251565
}
15261566
tq_timer = NULL;
15271567
}
1568+
#elif ZEND_TIMER
1569+
zend_timer_settime(0);
15281570
#elif defined(HAVE_SETITIMER)
15291571
if (EG(timeout_seconds)) {
15301572
struct itimerval no_timeout;

Zend/zend_globals.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "zend_multibyte.h"
3838
#include "zend_multiply.h"
3939
#include "zend_arena.h"
40+
#include "zend_timer.h"
4041

4142
/* Define ZTS if you want a thread-safe Zend */
4243
/*#undef ZTS*/
@@ -271,6 +272,11 @@ struct _zend_executor_globals {
271272
zend_string *filename_override;
272273
zend_long lineno_override;
273274

275+
#ifdef ZEND_TIMER
276+
timer_t timer;
277+
struct sigaction oldact;
278+
#endif
279+
274280
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
275281
};
276282

Zend/zend_timer.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
| [email protected] so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Kévin Dunglas <[email protected]> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifdef ZEND_TIMER
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_timer_create(void) /* {{{ */
37+
{
38+
struct sigevent sev;
39+
sev.sigev_notify = SIGEV_THREAD_ID;
40+
sev.sigev_value.sival_ptr = &EG(timer);
41+
// The chosen signal must:
42+
// 1. not be used internally by libc
43+
// 2. be allowed to happen spuriously without consequences
44+
// 3. not be commonly used by applications, this excludes SIGALRM, SIGUSR1 and SIGUSR2
45+
// 4. not be used by profilers, this excludes SIGPROF
46+
// 5. not be used internally by runtimes of programs that can embed PHP, this excludes SIGURG, which is used by Go
47+
sev.sigev_signo = SIGIO;
48+
sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid);
49+
50+
if (timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &EG(timer)) != 0) {
51+
zend_strerror_noreturn(E_ERROR, errno, "Could not create timer");
52+
}
53+
54+
# ifdef TIMER_DEBUG
55+
fprintf(stderr, "Timer %#jx created on thread %d\n", (uintmax_t) EG(timer), sev.sigev_notify_thread_id);
56+
# endif
57+
58+
sigaction(sev.sigev_signo, NULL, &EG(oldact));
59+
}
60+
/* }}} */
61+
62+
ZEND_API void zend_timer_settime(zend_long seconds) /* {{{ }*/
63+
{
64+
timer_t timer = EG(timer);
65+
66+
struct itimerspec its;
67+
its.it_value.tv_sec = seconds;
68+
its.it_value.tv_nsec = its.it_interval.tv_sec = its.it_interval.tv_nsec = 0;
69+
70+
# ifdef TIMER_DEBUG
71+
fprintf(stderr, "Setting timer %#jx on thread %d (%ld seconds)...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid), seconds);
72+
# endif
73+
74+
if (timer_settime(timer, 0, &its, NULL) != 0) {
75+
zend_strerror_noreturn(E_ERROR, errno, "Could not set timer");
76+
}
77+
}
78+
/* }}} */
79+
80+
ZEND_API void zend_timer_delete(void) /* {{{ */
81+
{
82+
timer_t timer = EG(timer);
83+
if (timer == (timer_t){0}) {
84+
/* Don't trigger an error here because the timer may not be initialized when PHP fail early, and on threads created by PHP but not managed by it. */
85+
# ifdef TIMER_DEBUG
86+
fprintf(stderr, "Could not delete timer that has not been created on thread %d\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid));
87+
# endif
88+
89+
return;
90+
}
91+
92+
# ifdef TIMER_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+
EG(timer) = (timer_t){0};
98+
if (err != 0) {
99+
zend_strerror_noreturn(E_ERROR, errno, "Could not delete timer");
100+
}
101+
}
102+
/* }}}} */
103+
104+
#endif

Zend/zend_timer.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
| [email protected] so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Kévin Dunglas <[email protected]> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifndef ZEND_TIMER_H
18+
#define ZEND_TIMER_H
19+
20+
# ifdef ZEND_TIMER
21+
22+
#include "zend_long.h"
23+
24+
ZEND_API void zend_timer_create(void);
25+
ZEND_API void zend_timer_settime(zend_long seconds);
26+
ZEND_API void zend_timer_delete(void);
27+
28+
# endif
29+
#endif

configure.ac

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ asprintf \
621621
nanosleep \
622622
memmem \
623623
memrchr \
624+
strerror_r \
624625
)
625626

626627
AX_FUNC_WHICH_GETHOSTBYNAME_R
@@ -1681,7 +1682,7 @@ PHP_ADD_SOURCES(Zend, \
16811682
zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \
16821683
zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \
16831684
zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \
1684-
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c \
1685+
zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c zend_timer.c \
16851686
Optimizer/zend_optimizer.c \
16861687
Optimizer/pass1.c \
16871688
Optimizer/pass3.c \

ext/opcache/ZendAccelerator.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4657,6 +4657,9 @@ static int accel_finish_startup(void)
46574657
zend_accel_error(ACCEL_LOG_WARNING, "Preloading failed to setuid(%d)", pw->pw_uid);
46584658
exit(1);
46594659
}
4660+
#ifdef ZEND_TIMER
4661+
zend_timer_create();
4662+
#endif
46604663
in_child = true;
46614664
} else { /* parent */
46624665
int status;

ext/pcntl/pcntl.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#define LONG_CONST(c) (zend_long) c
5858

5959
#include "pcntl_arginfo.h"
60+
#include "Zend/zend_timer.h"
6061

6162
ZEND_DECLARE_MODULE_GLOBALS(pcntl)
6263
static PHP_GINIT_FUNCTION(pcntl);
@@ -184,6 +185,10 @@ PHP_FUNCTION(pcntl_fork)
184185
if (id == -1) {
185186
PCNTL_G(last_error) = errno;
186187
php_error_docref(NULL, E_WARNING, "Error %d", errno);
188+
} else if (id == 0) {
189+
#ifdef ZEND_TIMER
190+
zend_timer_create();
191+
#endif
187192
}
188193

189194
RETURN_LONG((zend_long) id);

ext/standard/info.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,12 @@ PHPAPI ZEND_COLD void php_print_info(int flag)
898898
efree(descr);
899899
}
900900

901+
#ifdef ZEND_TIMER
902+
php_info_print_table_row(2, "Zend Timer", "enabled" );
903+
#else
904+
php_info_print_table_row(2, "Zend Timer", "disabled" );
905+
#endif
906+
901907
#ifdef HAVE_IPV6
902908
php_info_print_table_row(2, "IPv6 Support", "enabled" );
903909
#else

0 commit comments

Comments
 (0)