LCOV - code coverage report
Current view: top level - src - excimer_timer.c (source / functions) Hit Total Coverage
Test: mediawiki/php/excimer test coverage report Lines: 112 128 87.5 %
Date: 2024-02-28 22:03:29 Functions: 11 11 100.0 %

          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(union sigval sv);
      38             : static void excimer_timer_interrupt(zend_execute_data *execute_data);
      39             : 
      40          24 : static inline int excimer_timer_is_zero(struct timespec *ts)
      41             : {
      42          24 :     return ts->tv_sec == 0 && ts->tv_nsec == 0;
      43             : }
      44             : 
      45          22 : void excimer_timer_module_init()
      46             : {
      47          22 :     excimer_timer_globals.timers_by_id = malloc(sizeof(HashTable));
      48          22 :     zend_hash_init(excimer_timer_globals.timers_by_id, 0, NULL, NULL, 1);
      49          22 :     excimer_timer_globals.next_id = 1;
      50          22 :     excimer_mutex_init(&excimer_timer_globals.mutex);
      51             : 
      52          22 :     excimer_timer_globals.old_zend_interrupt_function = zend_interrupt_function;
      53          22 :     zend_interrupt_function = excimer_timer_interrupt;
      54          22 : }
      55             : 
      56          22 : void excimer_timer_module_shutdown()
      57             : {
      58             :     /* Wait for handler to finish, hopefully no more events are queued */
      59          22 :     excimer_mutex_lock(&excimer_timer_globals.mutex);
      60          22 :     zend_hash_destroy(excimer_timer_globals.timers_by_id);
      61          22 :     free(excimer_timer_globals.timers_by_id);
      62             : 
      63             :     /* "Attempting to destroy a locked mutex results in undefined behaviour" */
      64          22 :     excimer_mutex_unlock(&excimer_timer_globals.mutex);
      65          22 :     excimer_mutex_destroy(&excimer_timer_globals.mutex);
      66             : 
      67          22 : }
      68             : 
      69          22 : void excimer_timer_thread_init()
      70             : {
      71          22 :     excimer_timer_tls.event_counts = malloc(sizeof(HashTable));
      72          22 :     zend_hash_init(excimer_timer_tls.event_counts, 0, NULL, NULL, 1);
      73             : 
      74          22 :     excimer_mutex_init(&excimer_timer_tls.mutex);
      75             : 
      76          22 :     excimer_timer_tls.timers_by_id = malloc(sizeof(HashTable));
      77          22 :     zend_hash_init(excimer_timer_tls.timers_by_id, 0, NULL, NULL, 1);
      78          22 : }
      79             : 
      80          22 : void excimer_timer_thread_shutdown()
      81             : {
      82             :     zval *zp_thread;
      83             : 
      84             :     /* Destroy any timers still active in this thread */
      85          22 :     ZEND_HASH_FOREACH_VAL(excimer_timer_tls.timers_by_id, zp_thread) {
      86           0 :         excimer_timer *timer = (excimer_timer*)Z_PTR_P(zp_thread);
      87           0 :         excimer_timer_destroy(timer);
      88             :     }
      89             :     ZEND_HASH_FOREACH_END();
      90             : 
      91          22 :     zend_hash_destroy(excimer_timer_tls.timers_by_id);
      92          22 :     free(excimer_timer_tls.timers_by_id);
      93          22 :     excimer_timer_tls.timers_by_id = NULL;
      94             : 
      95             :     /* Acquire the thread so that we can write to event_counts.
      96             :      * This will wait for the handler to finish. */
      97          22 :     excimer_mutex_lock(&excimer_timer_tls.mutex);
      98          22 :     zend_hash_destroy(excimer_timer_tls.event_counts);
      99          22 :     free(excimer_timer_tls.event_counts);
     100          22 :     excimer_timer_tls.event_counts = NULL;
     101          22 :     excimer_mutex_unlock(&excimer_timer_tls.mutex);
     102             : 
     103          22 :     excimer_mutex_destroy(&excimer_timer_tls.mutex);
     104          22 : }
     105             : 
     106          12 : int excimer_timer_init(excimer_timer *timer, int event_type,
     107             :     excimer_timer_callback callback, void *user_data)
     108             : {
     109             :     zval z_timer;
     110             : 
     111          12 :     memset(timer, 0, sizeof(excimer_timer));
     112          12 :     ZVAL_PTR(&z_timer, timer);
     113          12 :     timer->vm_interrupt_ptr = &EG(vm_interrupt);
     114          12 :     timer->callback = callback;
     115          12 :     timer->user_data = user_data;
     116          12 :     timer->event_counts_ptr = &excimer_timer_tls.event_counts;
     117          12 :     timer->thread_mutex_ptr = &excimer_timer_tls.mutex;
     118             : 
     119          12 :     excimer_mutex_lock(&excimer_timer_globals.mutex);
     120          12 :     timer->id = excimer_timer_globals.next_id++;
     121          12 :     if (timer->id == 0) {
     122           0 :         excimer_mutex_unlock(&excimer_timer_globals.mutex);
     123           0 :         php_error_docref(NULL, E_WARNING, "Timer ID counter has overflowed");
     124           0 :         return FAILURE;
     125             :     }
     126             : 
     127          12 :     zend_hash_index_add(excimer_timer_globals.timers_by_id, timer->id, &z_timer);
     128          12 :     excimer_mutex_unlock(&excimer_timer_globals.mutex);
     129             : 
     130          12 :     zend_hash_index_add(excimer_timer_tls.timers_by_id, timer->id, &z_timer);
     131             : 
     132          12 :     if (excimer_os_timer_create(event_type, timer->id, &timer->os_timer, &excimer_timer_handle) == FAILURE) {
     133           0 :         return FAILURE;
     134             :     }
     135             : 
     136          12 :     timer->is_valid = 1;
     137          12 :     timer->is_running = 0;
     138          12 :     return SUCCESS;
     139             : }
     140             : 
     141          12 : void excimer_timer_start(excimer_timer *timer,
     142             :     struct timespec *period, struct timespec *initial)
     143             : {
     144          12 :     if (!timer->is_valid) {
     145           0 :         php_error_docref(NULL, E_WARNING, "Unable to start uninitialised timer" );
     146           0 :         return;
     147             :     }
     148             : 
     149             :     /* If a periodic timer has an initial value of 0, use the period instead,
     150             :      * since it_value=0 means disarmed */
     151          12 :     if (excimer_timer_is_zero(initial)) {
     152           2 :         initial = period;
     153             :     }
     154             :     /* If the value is still zero, flag an error */
     155          12 :     if (excimer_timer_is_zero(initial)) {
     156           0 :         php_error_docref(NULL, E_WARNING, "Unable to start timer with a value of zero "
     157             :             "duration and period");
     158           0 :         return;
     159             :     }
     160             : 
     161          12 :     if (excimer_os_timer_start(&timer->os_timer, period, initial) == SUCCESS) {
     162          12 :         timer->is_running = 1;
     163             :     }
     164             : }
     165             : 
     166          12 : void excimer_timer_destroy(excimer_timer *timer)
     167             : {
     168          12 :     if (!timer->is_valid) {
     169             :         /* This could happen if the timer is manually destroyed after
     170             :          * excimer_timer_thread_shutdown() is called */
     171           0 :         return;
     172             :     }
     173          12 :     if (timer->event_counts_ptr != &excimer_timer_tls.event_counts) {
     174           0 :         php_error_docref(NULL, E_WARNING,
     175             :             "Cannot delete a timer belonging to a different thread");
     176           0 :         return;
     177             :     }
     178             : 
     179             :     /* Stop the timer. This does not take effect immediately. */
     180          12 :     if (timer->is_running) {
     181          12 :         timer->is_running = 0;
     182             : 
     183          12 :         excimer_os_timer_stop(&timer->os_timer);
     184             :     }
     185             : 
     186             :     /* Wait for the handler to finish if it is running */
     187          12 :     excimer_mutex_lock(&excimer_timer_globals.mutex);
     188             :     /* Remove the ID from the global hashtable */
     189          12 :     zend_hash_index_del(excimer_timer_globals.timers_by_id, timer->id);
     190          12 :     excimer_mutex_unlock(&excimer_timer_globals.mutex);
     191             : 
     192          12 :     timer->is_valid = 0;
     193          12 :     timer->event_counts_ptr = NULL;
     194             : 
     195             :     /* Get the thread-local mutex */
     196          12 :     excimer_mutex_lock(&excimer_timer_tls.mutex);
     197             :     /* Remove the timer from the thread-local tables */
     198          12 :     zend_hash_index_del(excimer_timer_tls.event_counts, timer->id);
     199          12 :     zend_hash_index_del(excimer_timer_tls.timers_by_id, timer->id);
     200          12 :     excimer_mutex_unlock(&excimer_timer_tls.mutex);
     201             : 
     202          12 :     excimer_os_timer_delete(&timer->os_timer);
     203             : }
     204             : 
     205          77 : static void excimer_timer_handle(union sigval sv)
     206             : {
     207             :     excimer_timer *timer;
     208             :     zval *zp_event_count;
     209             :     zend_long event_count;
     210          77 :     intptr_t id = (intptr_t)sv.sival_ptr;
     211             : 
     212             :     /* Acquire the global mutex, which protects timers_by_id */
     213          77 :     excimer_mutex_lock(&excimer_timer_globals.mutex);
     214          77 :     timer = (excimer_timer*)zend_hash_index_find_ptr(excimer_timer_globals.timers_by_id, id);
     215          77 :     if (!timer || !timer->is_running) {
     216             :         /* Timer has been deleted, ignore event */
     217           0 :         excimer_mutex_unlock(&excimer_timer_globals.mutex);
     218           0 :         return;
     219             :     }
     220             : 
     221             :     /* Acquire the thread-specific mutex */
     222          77 :     excimer_mutex_lock(timer->thread_mutex_ptr);
     223             : 
     224             :     /* Add the event count to the thread-local hashtable */
     225          77 :     event_count = excimer_os_timer_get_overrun_count(&timer->os_timer) + 1;
     226          77 :     zp_event_count = zend_hash_index_find(*timer->event_counts_ptr, id);
     227          77 :     if (!zp_event_count) {
     228             :         zval tmp;
     229          77 :         ZVAL_LONG(&tmp, event_count);
     230          77 :         zend_hash_index_add_new(*timer->event_counts_ptr, id, &tmp);
     231             :     } else {
     232           0 :         Z_LVAL_P(zp_event_count) += event_count;
     233             :     }
     234          77 :     excimer_timer_atomic_bool_store(timer->vm_interrupt_ptr, 1);
     235             :     /* Release the mutexes */
     236          77 :     excimer_mutex_unlock(timer->thread_mutex_ptr);
     237          77 :     excimer_mutex_unlock(&excimer_timer_globals.mutex);
     238             : }
     239             : 
     240          77 : static void excimer_timer_interrupt(zend_execute_data *execute_data)
     241             : {
     242             :     zend_long id;
     243             :     zval *zp_count;
     244             :     HashTable *event_counts;
     245             : 
     246          77 :     excimer_mutex_lock(&excimer_timer_tls.mutex);
     247          77 :     event_counts = excimer_timer_tls.event_counts;
     248          77 :     excimer_timer_tls.event_counts = malloc(sizeof(HashTable));
     249          77 :     zend_hash_init(excimer_timer_tls.event_counts, 0, NULL, NULL, 1);
     250          77 :     excimer_mutex_unlock(&excimer_timer_tls.mutex);
     251             : 
     252         338 :     ZEND_HASH_FOREACH_NUM_KEY_VAL(event_counts, id, zp_count) {
     253          77 :         excimer_timer *timer = zend_hash_index_find_ptr(excimer_timer_tls.timers_by_id, id);
     254             :         /* If a previous callback destroyed this timer, then it would be
     255             :          * missing from the timers_by_id hashtable. */
     256          77 :         if (timer) {
     257          77 :             timer->callback(Z_LVAL_P(zp_count), timer->user_data);
     258             :         }
     259             :     }
     260             :     ZEND_HASH_FOREACH_END();
     261             : 
     262          77 :     zend_hash_destroy(event_counts);
     263          77 :     free(event_counts);
     264             : 
     265          77 :     if (excimer_timer_globals.old_zend_interrupt_function) {
     266          77 :         excimer_timer_globals.old_zend_interrupt_function(execute_data);
     267             :     }
     268          77 : }
     269             : 
     270           3 : void excimer_timer_get_time(excimer_timer *timer, struct timespec *remaining)
     271             : {
     272           3 :     if (!timer->is_valid || !timer->is_running) {
     273           2 :         remaining->tv_sec = 0;
     274           2 :         remaining->tv_nsec = 0;
     275           2 :         return;
     276             :     }
     277             : 
     278           1 :     excimer_os_timer_get_time(&timer->os_timer, remaining);
     279             : }

Generated by: LCOV version 1.13