Skip to content

Commit 4488419

Browse files
committed
feat: macOS support for Zend Max Execution timers
1 parent 291a8bd commit 4488419

File tree

5 files changed

+149
-29
lines changed

5 files changed

+149
-29
lines changed

Zend/Zend.m4

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,24 @@ AC_ARG_ENABLE([zend-max-execution-timers],
304304
[ZEND_MAX_EXECUTION_TIMERS=$enableval],
305305
[ZEND_MAX_EXECUTION_TIMERS=$ZEND_ZTS])
306306
307-
AS_CASE(["$host_alias"], [*linux*|*freebsd*], [], [ZEND_MAX_EXECUTION_TIMERS='no'])
308-
309-
PHP_CHECK_FUNC(timer_create, rt)
310-
if test "$ac_cv_func_timer_create" != "yes"; then
311-
ZEND_MAX_EXECUTION_TIMERS='no'
312-
fi
307+
AS_CASE(
308+
["$host_alias"],
309+
[*linux*|*freebsd*], [
310+
PHP_CHECK_FUNC(timer_create, rt)
311+
if test "$ac_cv_func_timer_create" != "yes"; then
312+
ZEND_MAX_EXECUTION_TIMERS='no'
313+
fi
314+
],
315+
[*darwin*], [],
316+
[ZEND_MAX_EXECUTION_TIMERS='no']
317+
)
318+
319+
AS_CASE(["$host_alias"], [*darwin*], [], [
320+
PHP_CHECK_FUNC(timer_create, rt)
321+
if test "$ac_cv_func_timer_create" != "yes"; then
322+
ZEND_MAX_EXECUTION_TIMERS='no'
323+
fi
324+
])
313325
314326
if test "$ZEND_MAX_EXECUTION_TIMERS" = "yes"; then
315327
AC_DEFINE(ZEND_MAX_EXECUTION_TIMERS, 1, [Use zend max execution timers])

Zend/zend_execute_API.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,7 @@ static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */
14111411
}
14121412
#endif
14131413

1414+
#ifndef __APPLE__
14141415
if (si->si_value.sival_ptr != &EG(max_execution_timer_timer)) {
14151416
#ifdef MAX_EXECUTION_TIMERS_DEBUG
14161417
fprintf(stderr, "Executing previous handler (if set) for unexpected signal SIGRTMIN received on thread %d\n", (pid_t) syscall(SYS_gettid));
@@ -1425,6 +1426,7 @@ static void zend_timeout_handler(int dummy, siginfo_t *si, void *uc) /* {{{ */
14251426

14261427
return;
14271428
}
1429+
#endif
14281430
# else
14291431
static void zend_timeout_handler(int dummy) /* {{{ */
14301432
{
@@ -1544,9 +1546,10 @@ static void zend_set_timeout_ex(zend_long seconds, bool reset_signals) /* {{{ */
15441546
act.sa_sigaction = zend_timeout_handler;
15451547
sigemptyset(&act.sa_mask);
15461548
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
1547-
sigaction(SIGRTMIN, &act, NULL);
1549+
sigaction(ZEND_MAX_EXECUTION_TIMERS_SIGNAL, &act, NULL);
1550+
15481551
sigemptyset(&sigset);
1549-
sigaddset(&sigset, SIGRTMIN);
1552+
sigaddset(&sigset, ZEND_MAX_EXECUTION_TIMERS_SIGNAL);
15501553
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
15511554
}
15521555
#elif defined(HAVE_SETITIMER)

Zend/zend_globals.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
#include <setjmp.h>
2525
#include <stdint.h>
2626
#include <sys/types.h>
27+
#ifdef __APPLE__
28+
#include <dispatch/dispatch.h>
29+
#endif
2730

2831
#include "zend_globals_macros.h"
2932

@@ -299,7 +302,12 @@ struct _zend_executor_globals {
299302
#endif
300303

301304
#ifdef ZEND_MAX_EXECUTION_TIMERS
305+
# ifdef __APPLE__
306+
dispatch_source_t max_execution_timer_timer;
307+
bool max_execution_timer_suspended;
308+
# else
302309
timer_t max_execution_timer_timer;
310+
# endif
303311
pid_t pid;
304312
struct sigaction oldact;
305313
#endif

Zend/zend_max_execution_timer.c

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,122 @@
1616

1717
#ifdef ZEND_MAX_EXECUTION_TIMERS
1818

19+
# ifdef __APPLE__
20+
21+
#include <dispatch/dispatch.h>
22+
# ifdef ZTS
23+
#include <pthread.h>
24+
# endif
25+
26+
#include "zend.h"
27+
#include "zend_globals.h"
28+
29+
// macOS doesn't support timer_create(), fallback to Grand Central Dispatch
30+
31+
static inline void zend_max_execution_timer_handler(void *arg)
32+
{
33+
pthread_t *tid = (pthread_t *) arg;
34+
pthread_kill(*tid, ZEND_MAX_EXECUTION_TIMERS_SIGNAL);
35+
}
36+
37+
static inline void zend_max_execution_timer_cancel(void *arg)
38+
{
39+
pthread_t *tid = (pthread_t *) arg;
40+
free(tid);
41+
}
42+
43+
ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
44+
{
45+
pid_t pid = getpid();
46+
47+
if (EG(pid) == pid) {
48+
return;
49+
}
50+
51+
dispatch_queue_global_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
52+
EG(max_execution_timer_timer) = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
53+
if (EG(max_execution_timer_timer) == NULL) {
54+
zend_strerror_noreturn(E_ERROR, errno, "Could not create dispatch source");
55+
}
56+
57+
EG(pid) = pid;
58+
EG(max_execution_timer_suspended) = 1;
59+
60+
# ifdef ZTS
61+
pthread_t lpid = pthread_self();
62+
pthread_t *tid = malloc(sizeof(pthread_t));
63+
memcpy(tid, &lpid, sizeof(pthread_t));
64+
dispatch_set_context(EG(max_execution_timer_timer), tid);
65+
# endif
66+
67+
dispatch_source_set_event_handler_f(EG(max_execution_timer_timer), zend_max_execution_timer_handler);
68+
dispatch_source_set_cancel_handler_f(EG(max_execution_timer_timer), zend_max_execution_timer_cancel);
69+
} /* }}} */
70+
71+
void zend_max_execution_timer_settime(zend_long seconds) /* {{{ */
72+
{
73+
if (seconds == 0) {
74+
if (!EG(max_execution_timer_suspended)) {
75+
dispatch_suspend(EG(max_execution_timer_timer));
76+
EG(max_execution_timer_suspended) = 1;
77+
}
78+
79+
return;
80+
}
81+
82+
dispatch_source_set_timer(
83+
EG(max_execution_timer_timer),
84+
dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC),
85+
seconds * NSEC_PER_SEC,
86+
0
87+
);
88+
if (EG(max_execution_timer_suspended)) {
89+
dispatch_resume(EG(max_execution_timer_timer));
90+
EG(max_execution_timer_suspended) = 0;
91+
}
92+
} /* }}} */
93+
94+
void zend_max_execution_timer_shutdown(void) /* {{{ */
95+
{
96+
/* Don't try to delete a timer created before a call to fork() */
97+
if (EG(pid) != getpid()) {
98+
return;
99+
}
100+
101+
EG(pid) = 0;
102+
103+
dispatch_source_cancel(EG(max_execution_timer_timer));
104+
//dispatch_release(EG(max_execution_timer_timer));
105+
} /* }}} */
106+
107+
# else
108+
19109
#include <stdio.h>
20110
#include <signal.h>
21111
#include <time.h>
22112
#include <unistd.h>
23113
#include <errno.h>
24114
#include <sys/syscall.h>
25115
#include <sys/types.h>
26-
# ifdef __FreeBSD__
27-
# include <pthread_np.h>
28-
# endif
116+
# ifdef __FreeBSD__
117+
# include <pthread_np.h>
118+
# endif
29119

30120
#include "zend.h"
31121
#include "zend_globals.h"
32122

33123
// Musl Libc defines this macro, glibc does not
34124
// 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
35-
# ifndef sigev_notify_thread_id
36-
# define sigev_notify_thread_id _sigev_un._tid
37-
# endif
125+
# ifndef sigev_notify_thread_id
126+
# define sigev_notify_thread_id _sigev_un._tid
127+
# endif
38128

39129
// FreeBSD doesn't support CLOCK_BOOTTIME
40-
# ifdef __FreeBSD__
41-
# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_MONOTONIC
42-
# else
43-
# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_BOOTTIME
44-
# endif
130+
# ifdef __FreeBSD__
131+
# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_MONOTONIC
132+
# else
133+
# define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_BOOTTIME
134+
# endif
45135

46136
ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
47137
{
@@ -54,12 +144,12 @@ ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
54144
struct sigevent sev;
55145
sev.sigev_notify = SIGEV_THREAD_ID;
56146
sev.sigev_value.sival_ptr = &EG(max_execution_timer_timer);
57-
sev.sigev_signo = SIGRTMIN;
58-
# ifdef __FreeBSD__
147+
sev.sigev_signo = ZEND_MAX_EXECUTION_TIMERS_SIGNAL;
148+
# ifdef __FreeBSD__
59149
sev.sigev_notify_thread_id = pthread_getthreadid_np();
60-
# else
150+
# else
61151
sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid);
62-
# endif
152+
# endif
63153

64154
// 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
65155
if (timer_create(ZEND_MAX_EXECUTION_TIMERS_CLOCK, &sev, &EG(max_execution_timer_timer)) != 0) {
@@ -68,9 +158,9 @@ ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
68158

69159
EG(pid) = getpid();
70160

71-
# ifdef MAX_EXECUTION_TIMERS_DEBUG
161+
# ifdef MAX_EXECUTION_TIMERS_DEBUG
72162
fprintf(stderr, "Timer %#jx created on thread %d\n", (uintmax_t) EG(max_execution_timer_timer), sev.sigev_notify_thread_id);
73-
# endif
163+
# endif
74164

75165
sigaction(sev.sigev_signo, NULL, &EG(oldact));
76166
}
@@ -89,9 +179,9 @@ void zend_max_execution_timer_settime(zend_long seconds) /* {{{ }*/
89179
its.it_value.tv_sec = seconds;
90180
its.it_value.tv_nsec = its.it_interval.tv_sec = its.it_interval.tv_nsec = 0;
91181

92-
# ifdef MAX_EXECUTION_TIMERS_DEBUG
182+
# ifdef MAX_EXECUTION_TIMERS_DEBUG
93183
fprintf(stderr, "Setting timer %#jx on thread %d (%ld seconds)...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid), seconds);
94-
# endif
184+
# endif
95185

96186
if (timer_settime(timer, 0, &its, NULL) != 0) {
97187
zend_strerror_noreturn(E_ERROR, errno, "Could not set timer");
@@ -110,9 +200,9 @@ void zend_max_execution_timer_shutdown(void) /* {{{ */
110200

111201
timer_t timer = EG(max_execution_timer_timer);
112202

113-
# ifdef MAX_EXECUTION_TIMERS_DEBUG
203+
# ifdef MAX_EXECUTION_TIMERS_DEBUG
114204
fprintf(stderr, "Deleting timer %#jx on thread %d...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid));
115-
# endif
205+
# endif
116206

117207
int err = timer_delete(timer);
118208
if (err != 0) {
@@ -121,4 +211,5 @@ void zend_max_execution_timer_shutdown(void) /* {{{ */
121211
}
122212
/* }}}} */
123213

214+
# endif
124215
#endif

Zend/zend_max_execution_timer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121

2222
#include "zend_long.h"
2323

24+
# ifdef __APPLE__
25+
#define ZEND_MAX_EXECUTION_TIMERS_SIGNAL SIGALRM
26+
# else
27+
#define ZEND_MAX_EXECUTION_TIMERS_SIGNAL SIGRTMIN
28+
# endif
29+
2430
/* Must be called after calls to fork() */
2531
ZEND_API void zend_max_execution_timer_init(void);
2632
void zend_max_execution_timer_settime(zend_long seconds);

0 commit comments

Comments
 (0)