Line data Source code
1 : /* Copyright 2018 Wikimedia Foundation
2 : *
3 : * Licensed under the Apache License, Version 2.0 (the "License");
4 : * you may not use this file except in compliance with the License.
5 : * You may obtain a copy of the License at
6 : *
7 : * http://www.apache.org/licenses/LICENSE-2.0
8 : *
9 : * Unless required by applicable law or agreed to in writing, software
10 : * distributed under the License is distributed on an "AS IS" BASIS,
11 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : * See the License for the specific language governing permissions and
13 : * limitations under the License.
14 : */
15 :
16 : #include <signal.h>
17 : #include <stdint.h>
18 : #include <time.h>
19 : #include <pthread.h>
20 : #include <stdio.h>
21 : #include <stdlib.h>
22 :
23 : #include "php.h"
24 : #include "excimer_mutex.h"
25 : #include "excimer_timer.h"
26 : #include "zend_types.h"
27 :
28 : #if PHP_VERSION_ID >= 80200
29 : #define excimer_timer_atomic_bool_store(dest, value) zend_atomic_bool_store(dest, value)
30 : #else
31 : #define excimer_timer_atomic_bool_store(dest, value) *dest = value
32 : #endif
33 :
34 : excimer_timer_globals_t excimer_timer_globals;
35 : ZEND_TLS excimer_timer_tls_t excimer_timer_tls;
36 :
37 : static void excimer_timer_handle(void * data, int overrun_count);
38 : static void excimer_timer_interrupt(zend_execute_data *execute_data);
39 :
40 : /**
41 : * Add a timer to the pending list. Unsynchronised, i.e. the caller is
42 : * responsible for locking the mutex if required.
43 : */
44 90 : static void excimer_timer_list_enqueue(excimer_timer *timer)
45 : {
46 90 : excimer_timer **head_pp = &excimer_timer_tls.pending_head;
47 90 : if (!timer->pending_next) {
48 90 : if (*head_pp) {
49 0 : timer->pending_next = *head_pp;
50 0 : timer->pending_prev = (*head_pp)->pending_prev;
51 0 : (*head_pp)->pending_prev->pending_next = timer;
52 0 : (*head_pp)->pending_prev = timer;
53 : } else {
54 90 : *head_pp = timer;
55 90 : timer->pending_next = timer;
56 90 : timer->pending_prev = timer;
57 : }
58 : }
59 90 : }
60 :
61 : /**
62 : * Remove the first (FIFO) timer from the pending list and provide a pointer
63 : * to it. (unsynchronised)
64 : *
65 : * @param[out] timer_pp
66 : * @return True if a timer was returned, false if the list was empty
67 : */
68 180 : static int excimer_timer_list_dequeue(excimer_timer **timer_pp)
69 : {
70 180 : excimer_timer **head_pp = &excimer_timer_tls.pending_head;
71 180 : if (*head_pp) {
72 : // Get the pending timer
73 90 : excimer_timer *timer = *timer_pp = *head_pp;
74 90 : if (timer->pending_next == timer) {
75 : // List is now empty
76 90 : *head_pp = NULL;
77 : } else {
78 : // Relink the head and neighbours
79 0 : timer->pending_next->pending_prev = timer->pending_prev;
80 0 : *head_pp = timer->pending_prev->pending_next = timer->pending_next;
81 : }
82 : // Unlink the timer being returned
83 90 : timer->pending_next = NULL;
84 90 : timer->pending_prev = NULL;
85 90 : return 1;
86 : } else {
87 90 : return 0;
88 : }
89 : }
90 :
91 : /**
92 : * Remove the specified timer from the pending list, if it is in there. If it
93 : * is not in the list, do nothing. (unsynchronised)
94 : */
95 41 : static void excimer_timer_list_remove(excimer_timer *timer)
96 : {
97 41 : excimer_timer **head_pp = &excimer_timer_tls.pending_head;
98 41 : if (timer->pending_next) {
99 0 : if (timer->pending_next == timer) {
100 0 : *head_pp = NULL;
101 : } else {
102 0 : timer->pending_next->pending_prev = timer->pending_prev;
103 0 : timer->pending_prev->pending_next = timer->pending_next;
104 0 : if (*head_pp == timer) {
105 0 : *head_pp = timer->pending_next;
106 : }
107 : }
108 0 : timer->pending_next = NULL;
109 0 : timer->pending_prev = NULL;
110 : }
111 41 : }
112 :
113 : /**
114 : * Atomically dequeue a timer and get its event count at the time of removal
115 : * from the queue. The timer may be immediately re-added to the queue by the
116 : * event handler.
117 : *
118 : * @param[out] timer_pp Where to put the pointer to the timer
119 : * @param[out] event_count_p Where to put the event count
120 : * @return True if a timer was removed, false if the list was empty.
121 : */
122 180 : static int excimer_timer_pending_dequeue(excimer_timer **timer_pp, zend_long *event_count_p)
123 : {
124 180 : excimer_mutex_lock(&excimer_timer_tls.mutex);
125 180 : int ret = excimer_timer_list_dequeue(timer_pp);
126 180 : if (ret) {
127 90 : *event_count_p = (*timer_pp)->event_count;
128 90 : (*timer_pp)->event_count = 0;
129 : }
130 180 : excimer_mutex_unlock(&excimer_timer_tls.mutex);
131 180 : return ret;
132 : }
133 :
134 : // Note: functions with external linkage are documented in the header
135 :
136 17 : void excimer_timer_module_init()
137 : {
138 17 : excimer_timer_globals.old_zend_interrupt_function = zend_interrupt_function;
139 17 : zend_interrupt_function = excimer_timer_interrupt;
140 17 : }
141 :
142 17 : void excimer_timer_module_shutdown()
143 : {
144 17 : }
145 :
146 17 : void excimer_timer_thread_init()
147 : {
148 17 : excimer_timer_tls = (excimer_timer_tls_t){
149 : .mutex = PTHREAD_MUTEX_INITIALIZER
150 : };
151 17 : }
152 :
153 17 : void excimer_timer_thread_shutdown()
154 : {
155 17 : if (excimer_timer_tls.timers_active) {
156 : // If this ever happens, it means we've got the logic wrong and we need
157 : // to rethink. It's very bad for timers to keep existing after thread
158 : // termination, because the mutex will be a dangling pointer. It's not
159 : // much help to avoid excimer_mutex_destroy() here because the whole TLS
160 : // segment will be destroyed and reused.
161 0 : php_error_docref(NULL, E_WARNING, "Timer still active at thread termination");
162 : } else {
163 17 : excimer_mutex_destroy(&excimer_timer_tls.mutex);
164 : }
165 17 : }
166 :
167 41 : int excimer_timer_init(excimer_timer *timer, int event_type,
168 : excimer_timer_callback callback, void *user_data)
169 : {
170 : zval z_timer;
171 :
172 41 : memset(timer, 0, sizeof(excimer_timer));
173 41 : ZVAL_PTR(&z_timer, timer);
174 41 : timer->vm_interrupt_ptr = &EG(vm_interrupt);
175 41 : timer->callback = callback;
176 41 : timer->user_data = user_data;
177 41 : timer->tls = &excimer_timer_tls;
178 :
179 41 : if (timerlib_timer_init(&timer->tl_timer, event_type, &excimer_timer_handle, timer) == FAILURE) {
180 0 : timerlib_timer_destroy(&timer->tl_timer);
181 0 : return FAILURE;
182 : }
183 :
184 41 : excimer_timer_tls.timers_active++;
185 41 : timer->is_valid = 1;
186 41 : timer->is_running = 0;
187 41 : return SUCCESS;
188 : }
189 :
190 43 : void excimer_timer_start(excimer_timer *timer,
191 : struct timespec *period, struct timespec *initial)
192 : {
193 43 : if (!timer->is_valid) {
194 0 : php_error_docref(NULL, E_WARNING, "Unable to start uninitialised timer" );
195 0 : return;
196 : }
197 :
198 : /* If a periodic timer has an initial value of 0, use the period instead,
199 : * since it_value=0 means disarmed */
200 43 : if (timerlib_timespec_is_zero(initial)) {
201 12 : initial = period;
202 : }
203 : /* If the value is still zero, flag an error */
204 43 : if (timerlib_timespec_is_zero(initial)) {
205 0 : php_error_docref(NULL, E_WARNING, "Unable to start timer with a value of zero "
206 : "duration and period");
207 0 : return;
208 : }
209 :
210 43 : if (timerlib_timer_start(&timer->tl_timer, period, initial) == SUCCESS) {
211 43 : timer->is_running = 1;
212 : }
213 : }
214 :
215 7 : void excimer_timer_stop(excimer_timer *timer)
216 : {
217 7 : if (!timer->is_valid) {
218 0 : php_error_docref(NULL, E_WARNING, "Unable to start uninitialised timer" );
219 0 : return;
220 : }
221 7 : if (timer->is_running) {
222 7 : if (timerlib_timer_stop(&timer->tl_timer) == SUCCESS) {
223 7 : timer->is_running = 0;
224 : }
225 : }
226 : }
227 :
228 41 : void excimer_timer_destroy(excimer_timer *timer)
229 : {
230 41 : if (!timer->is_valid) {
231 : /* This could happen if the timer is manually destroyed after
232 : * excimer_timer_thread_shutdown() is called */
233 0 : return;
234 : }
235 41 : if (timer->tls != &excimer_timer_tls) {
236 0 : php_error_docref(NULL, E_WARNING,
237 : "Cannot delete a timer belonging to a different thread");
238 0 : return;
239 : }
240 :
241 : /* Stop the timer */
242 41 : if (timer->is_running) {
243 36 : timer->is_running = 0;
244 36 : timerlib_timer_stop(&timer->tl_timer);
245 : }
246 : /* Destroy the timer. This will wait until any events are done. */
247 41 : timerlib_timer_destroy(&timer->tl_timer);
248 41 : excimer_timer_tls.timers_active--;
249 :
250 : /* Remove the timer from the pending list */
251 41 : excimer_mutex_lock(&excimer_timer_tls.mutex);
252 41 : excimer_timer_list_remove(timer);
253 41 : excimer_mutex_unlock(&excimer_timer_tls.mutex);
254 :
255 41 : timer->is_valid = 0;
256 41 : timer->tls = NULL;
257 : }
258 :
259 90 : static void excimer_timer_handle(void * data, int overrun_count)
260 : {
261 90 : excimer_timer *timer = (excimer_timer*)data;
262 90 : excimer_mutex_lock(&excimer_timer_tls.mutex);
263 90 : timer->event_count += overrun_count + 1;
264 90 : excimer_timer_list_enqueue(timer);
265 90 : excimer_mutex_unlock(&excimer_timer_tls.mutex);
266 90 : excimer_timer_atomic_bool_store(timer->vm_interrupt_ptr, 1);
267 90 : }
268 :
269 90 : static void excimer_timer_interrupt(zend_execute_data *execute_data)
270 : {
271 90 : excimer_timer *timer = NULL;
272 90 : zend_long count = 0;
273 180 : while (excimer_timer_pending_dequeue(&timer, &count)) {
274 90 : timer->callback(count, timer->user_data);
275 : }
276 :
277 90 : if (excimer_timer_globals.old_zend_interrupt_function) {
278 90 : excimer_timer_globals.old_zend_interrupt_function(execute_data);
279 : }
280 90 : }
281 :
282 3 : void excimer_timer_get_time(excimer_timer *timer, struct timespec *remaining)
283 : {
284 3 : if (!timer->is_valid || !timer->is_running) {
285 2 : remaining->tv_sec = 0;
286 2 : remaining->tv_nsec = 0;
287 2 : return;
288 : }
289 :
290 1 : timerlib_timer_get_time(&timer->tl_timer, remaining);
291 : }
|