LCOV - code coverage report
Current view: top level - src/timerlib - timerlib_posix.c (source / functions) Hit Total Coverage
Test: mediawiki/php/excimer test coverage report Lines: 93 117 79.5 %
Date: 2025-08-22 19:33:56 Functions: 13 13 100.0 %

          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             : }

Generated by: LCOV version 1.14