Line data Source code
1 : /* Copyright 2025 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 : // For gettid, pthread_attr_setsigmask_np
17 : #ifndef _GNU_SOURCE
18 : #define _GNU_SOURCE 1
19 : #endif
20 :
21 : #include "timerlib.h"
22 :
23 : #include <signal.h>
24 : #include <unistd.h>
25 :
26 : // https://sourceware.org/bugzilla/show_bug.cgi?id=27417
27 : # ifndef sigev_notify_thread_id
28 : # define sigev_notify_thread_id _sigev_un._tid
29 : # endif
30 :
31 : #ifdef HAVE_PTHREAD_ATTR_SETSIGMASK_NP
32 : // glibc 2.32+: set the new thread's sigmask using an attribute
33 : static inline void timerlib_block_signals(pthread_attr_t *attr, sigset_t *old_sigmask)
34 : {
35 : sigset_t sigmask;
36 : sigfillset(&sigmask);
37 : pthread_attr_setsigmask_np(attr, &sigmask);
38 : }
39 :
40 : static inline void timerlib_unblock_signals(sigset_t *old_sigmask)
41 : {
42 : }
43 :
44 : #else
45 : // glibc before 2.32: save and restore the main thread's sigmask so that the new
46 : // thread will inherit a sigmask with all signals blocked
47 41 : static inline void timerlib_block_signals(pthread_attr_t *attr, sigset_t *old_sigmask)
48 : {
49 : sigset_t sigmask;
50 41 : sigfillset(&sigmask);
51 41 : pthread_sigmask(SIG_BLOCK, &sigmask, old_sigmask);
52 41 : }
53 :
54 41 : static inline void timerlib_unblock_signals(sigset_t *old_sigmask)
55 : {
56 41 : pthread_sigmask(SIG_SETMASK, old_sigmask, NULL);
57 41 : }
58 : #endif
59 :
60 : #ifndef HAVE_GETTID
61 : #include <sys/syscall.h>
62 : #define gettid() ((pid_t)syscall(SYS_gettid))
63 : #endif
64 :
65 : #include "timerlib_pthread_mutex.h"
66 :
67 : /**
68 : * This is called by the handler thread to notify the main thread that
69 : * timer->tid is valid.
70 : */
71 41 : static void timerlib_notify_ready(timerlib_timer_t *timer)
72 : {
73 41 : timerlib_mutex_lock(&timer->ready_mutex);
74 41 : timer->ready = 1;
75 41 : int error = pthread_cond_broadcast(&timer->ready_cond);
76 41 : if (error) {
77 0 : timerlib_abort("pthread_cond_broadcast", error);
78 : }
79 41 : timerlib_mutex_unlock(&timer->ready_mutex);
80 41 : }
81 :
82 : /**
83 : * Stop the handler thread (called from the main thread)
84 : */
85 41 : static int timerlib_graceful_exit(timerlib_timer_t *timer)
86 : {
87 : // We share TIMERLIB_SIGNAL between timer expiration and shutdown, mostly
88 : // to be less intrusive on the application. But if an expiration signal
89 : // is already pending, sending another will be ignored. We set a variable
90 : // so that the thread will terminate anyway in that case.
91 41 : timer->killed = 1;
92 41 : int error = pthread_kill(timer->thread, TIMERLIB_SIGNAL);
93 41 : if (error) {
94 0 : timerlib_report_errno("pthread_kill", error);
95 0 : return TIMERLIB_FAILURE;
96 : }
97 41 : return TIMERLIB_SUCCESS;
98 : }
99 :
100 : /**
101 : * Join the handler thread, wait for it to exit.
102 : */
103 41 : static int timerlib_join(timerlib_timer_t *timer)
104 : {
105 41 : int error = pthread_join(timer->thread, NULL);
106 41 : if (error) {
107 0 : timerlib_report_errno("pthread_join", error);
108 0 : return TIMERLIB_FAILURE;
109 : }
110 41 : return TIMERLIB_SUCCESS;
111 : }
112 :
113 : /**
114 : * Convert a timerlib clock constant to a POSIX clock
115 : * @param clock May be either TIMERLIB_REAL or TIMERLIB_CPU
116 : */
117 107 : static clockid_t timerlib_map_clock(int clock)
118 : {
119 107 : if (clock == TIMERLIB_REAL) {
120 106 : return CLOCK_MONOTONIC;
121 : } else {
122 1 : clockid_t c = CLOCK_MONOTONIC;
123 1 : int error = pthread_getcpuclockid(pthread_self(), &c);
124 1 : if (error) {
125 0 : timerlib_report_errno("pthread_getcpuclockid", error);
126 : }
127 1 : return c;
128 : }
129 : }
130 :
131 : /**
132 : * The start routine of the handler thread
133 : */
134 41 : static void* timerlib_timer_thread_main(void *data)
135 : {
136 41 : timerlib_timer_t *timer = data;
137 41 : timer->tid = gettid();
138 :
139 : // Tell the main thread we are ready to start
140 41 : timerlib_notify_ready(timer);
141 :
142 : // Receive only our signal
143 : sigset_t sigset;
144 41 : sigemptyset(&sigset);
145 41 : sigaddset(&sigset, TIMERLIB_SIGNAL);
146 :
147 90 : while (1) {
148 : siginfo_t si;
149 : // There is an identical empty loop in the glibc SIGEV_THREAD
150 : // implementation. The documentation indicates that EINTR is the only
151 : // possible error.
152 131 : while (sigwaitinfo(&sigset, &si) < 0);
153 : // If timer_stop() has been called, exit the thread
154 131 : if (timer->killed) {
155 41 : return NULL;
156 : }
157 : // If signal occurred due to a timer expiration, call the callback.
158 90 : if (si.si_code == SI_TIMER) {
159 90 : timer->notify_function(timer->notify_data, si.si_overrun);
160 : }
161 : }
162 : }
163 :
164 41 : int timerlib_timer_init(timerlib_timer_t *timer, int clock,
165 : timerlib_notify_function_t *notify_function, void *notify_data)
166 : {
167 : // Initialise the data. Fields that are not named are automatically zeroed.
168 41 : *timer = (timerlib_timer_t){
169 : .clock = clock,
170 : .notify_function = notify_function,
171 : .notify_data = notify_data,
172 : .ready_cond = PTHREAD_COND_INITIALIZER,
173 : .ready_mutex = PTHREAD_MUTEX_INITIALIZER,
174 : };
175 :
176 : // Block all signals. This prevents the thread from receiving process-directed
177 : // signals which are normally handled by the main thread.
178 : pthread_attr_t attr;
179 : sigset_t old_sigset;
180 41 : pthread_attr_init(&attr);
181 41 : timerlib_block_signals(&attr, &old_sigset);
182 :
183 : // Create the thread
184 41 : int error = pthread_create(&timer->thread, &attr, timerlib_timer_thread_main, timer);
185 41 : timerlib_unblock_signals(&old_sigset);
186 41 : pthread_attr_destroy(&attr);
187 41 : if (error) {
188 0 : timerlib_report_errno("pthread_create", error);
189 0 : return TIMERLIB_FAILURE;
190 : }
191 41 : timer->thread_valid = 1;
192 :
193 : // Wait for timer->tid to become valid
194 41 : timerlib_mutex_lock(&timer->ready_mutex);
195 82 : while (!timer->ready) {
196 41 : error = pthread_cond_wait(&timer->ready_cond, &timer->ready_mutex);
197 41 : if (error) {
198 0 : timerlib_report_errno("pthread_cond_wait", error);
199 0 : return TIMERLIB_FAILURE;
200 : }
201 : }
202 41 : timerlib_mutex_unlock(&timer->ready_mutex);
203 :
204 : // Create the timer
205 : // This needs to be done in the main thread, otherwise it silently fails
206 : // to deliver any events in CPU mode.
207 82 : struct sigevent sev = {
208 41 : .sigev_signo = TIMERLIB_SIGNAL,
209 : .sigev_notify = SIGEV_THREAD_ID,
210 41 : .sigev_notify_thread_id = timer->tid
211 : };
212 41 : if (timer_create(timerlib_map_clock(timer->clock), &sev, &timer->timer)) {
213 0 : timerlib_report_errno("timer_create", errno);
214 0 : return TIMERLIB_FAILURE;
215 : }
216 :
217 : // Remember that timer->timer is valid and needs to be deleted
218 41 : timer->timer_valid = 1;
219 :
220 41 : return TIMERLIB_SUCCESS;
221 : }
222 :
223 43 : int timerlib_timer_start(timerlib_timer_t *timer, timerlib_timespec_t *period, timerlib_timespec_t *initial)
224 : {
225 43 : struct itimerspec its = {
226 : .it_interval = *period,
227 : .it_value = *initial
228 : };
229 :
230 43 : if (!timer->timer_valid) {
231 : // No point reporting another error, since we presumably already reported
232 : // an error in timerlib_timer_init
233 0 : return TIMERLIB_FAILURE;
234 : }
235 :
236 43 : if (timerlib_timespec_is_zero(initial)) {
237 : // Make sure the timer is armed
238 0 : its.it_value.tv_nsec = 1;
239 : }
240 :
241 43 : if (timer_settime(timer->timer, 0, &its, NULL) != 0) {
242 0 : timerlib_report_errno("timer_settime", errno);
243 0 : return TIMERLIB_FAILURE;
244 : }
245 :
246 43 : return TIMERLIB_SUCCESS;
247 : }
248 :
249 43 : int timerlib_timer_stop(timerlib_timer_t * timer)
250 : {
251 43 : struct itimerspec its = {0};
252 :
253 43 : if (!timer->timer_valid) {
254 0 : return TIMERLIB_FAILURE;
255 : }
256 :
257 43 : if (timer_settime(timer->timer, 0, &its, NULL) != 0) {
258 0 : timerlib_report_errno("timer_settime", errno);
259 0 : return TIMERLIB_FAILURE;
260 : }
261 :
262 43 : return TIMERLIB_SUCCESS;
263 : }
264 :
265 41 : void timerlib_timer_destroy(timerlib_timer_t * timer)
266 : {
267 41 : if (timer->thread_valid) {
268 41 : timer->thread_valid = 0;
269 41 : if (timerlib_graceful_exit(timer) == TIMERLIB_SUCCESS) {
270 41 : timerlib_join(timer);
271 : }
272 : }
273 41 : if (timer->timer_valid) {
274 41 : timer->timer_valid = 0;
275 41 : if (timer_delete(timer->timer)) {
276 0 : timerlib_report_errno("timer_delete", errno);
277 : }
278 : }
279 41 : }
280 :
281 1 : int timerlib_timer_get_time(timerlib_timer_t *timer, timerlib_timespec_t *remaining)
282 : {
283 1 : int ret = TIMERLIB_FAILURE;
284 1 : struct itimerspec its = {0};
285 : // Write to *remaining even on error, so that an unchecked error value will
286 : // not lead to the caller using uninitialised memory.
287 1 : if (timer->timer_valid) {
288 1 : if (timer_gettime(timer->timer, &its)) {
289 0 : timerlib_report_errno("timer_gettime", errno);
290 : } else {
291 1 : ret = TIMERLIB_SUCCESS;
292 : }
293 : }
294 1 : *remaining = its.it_value;
295 1 : return ret;
296 : }
297 :
298 66 : int timerlib_clock_get_time(int clock, timerlib_timespec_t * time)
299 : {
300 66 : if (clock_gettime(timerlib_map_clock(clock), time)) {
301 0 : *time = (timerlib_timespec_t){0};
302 0 : timerlib_report_errno("timer_gettime", errno);
303 0 : return TIMERLIB_FAILURE;
304 : } else {
305 66 : return TIMERLIB_SUCCESS;
306 : }
307 : }
|