/**************************************************************** * * * Copyright 2001, 2013 Fidelity Information Services, Inc * * * * This source code contains the intellectual property * * of its copyright holder(s), and is made available * * under a license. If you do not know the terms of * * the license, please stop and do not read further. * * * ****************************************************************/ /* This file contains a general purpose timer package. Simultaneous multiple timers are supported. * All outstanding timers are contained in a queue of pending requests. New timer is added to the * queue in an expiration time order. The first timer in a queue expires first, and the last one * expires last. When the timer expires, the signal is generated and the process is awakened. This * timer is then removed from the queue, and the first timer in a queue is started again, and so on. * Starting a timer with the timer id equal to one of the existing timers in a chain will remove the * existing timer from the chain and add a new one instead. * * It is a responsibility of the user to go to hibernation mode by executing appropriate system call * if the user needs to wait for the timer expiration. * * Additionally, certain timers, designated by "safe" flag, can be processed---and, if necessary, out * of order---while we are deferred on interrupts. All regular timers that pop within the deferred * zone, will be handler in order as soon as we reenable interrupt processing. * * Following are top-level user-callable routines of this package: * * void sys_get_cur_time(ABS_TIME *atp) * fetch absolute time into stucture * * void hiber_start(uint4 hiber) * used to sleep for hiber milliseconds * * void start_timer(TID tid, int4 time_to_expir, void (*handler)(), int4 dlen, char *data) * Used to start a new timer. * * void cancel_timer(TID tid) * Cancel an existing timer. * Cancelling timer with tid = 0, cancels all timers. */ #include "mdef.h" #include #include #include #ifdef GTM_PTHREAD # include #endif #include #include "gtm_time.h" #include "gtm_string.h" #include "gtmimagename.h" #if (defined(__ia64) && defined(__linux__)) || defined(__MVS__) # include "gtm_unistd.h" #endif /* __ia64 && __linux__ or __MVS__ */ #include "gt_timer.h" #include "wake_alarm.h" #ifdef DEBUG # include "wbox_test_init.h" # include "io.h" #endif #if defined(mips) && !defined(_SYSTYPE_SVR4) # include #else # include #endif #ifndef __MVS__ # include #endif #include "send_msg.h" #include "eintr_wrappers.h" #include "gtmio.h" #include "have_crit.h" #include "util.h" #include "sleep.h" #if defined(__osf__) # define HZ CLK_TCK #elif defined(__MVS__) # define HZ gtm_zos_HZ STATICDEF int gtm_zos_HZ = 100; /* see prealloc_gt_timers below */ #endif #ifdef ITIMER_REAL # define BSD_TIMER #else /* check def of time() including arg - see below; should be time_t * (from sys/types.h) and traditionally unsigned long */ # ifndef __osf__ int4 time(); # endif #endif #define TIMER_BLOCK_SIZE 64 /* # of timer entries allocated initially as well as at every expansion */ #define GT_TIMER_EXPAND_TRIGGER 8 /* if the # of timer entries in the free queue goes below this, allocate more */ #define GT_TIMER_INIT_DATA_LEN 8 #define MAX_TIMER_POP_TRACE_SZ 32 #define ADD_SAFE_HNDLR(HNDLR) \ { \ assert((ARRAYSIZE(safe_handlers) - 1) > safe_handlers_cnt); \ safe_handlers[safe_handlers_cnt++] = HNDLR; \ } #ifdef BSD_TIMER STATICDEF struct itimerval sys_timer, old_sys_timer; #endif #define DUMMY_SIG_NUM 0 /* following can be used to see why timer_handler was called */ STATICDEF volatile GT_TIMER *timeroot = NULL; /* chain of pending timer requests in time order */ STATICDEF boolean_t first_timeset = TRUE; STATICDEF struct sigaction prev_alrm_handler; /* save previous SIGALRM handler, if any */ /* Chain of unused timer request blocks */ STATICDEF volatile GT_TIMER *timefree = NULL; STATICDEF volatile int4 num_timers_free; /* # of timers in the unused queue */ STATICDEF int4 timeblk_hdrlen; STATICDEF volatile st_timer_alloc *timer_allocs = NULL; STATICDEF int safe_timer_cnt, timer_pop_cnt; /* Number of safe timers in queue/popped */ STATICDEF TID *deferred_tids; STATICDEF timer_hndlr safe_handlers[MAX_TIMER_HNDLRS + 1]; /* +1 for NULL to terminate list, or can use safe_handlers_cnt */ STATICDEF int safe_handlers_cnt; STATICDEF boolean_t stolen_timer = FALSE; /* only complain once, used in check_for_timer_pops() */ STATICDEF char *whenstolen[] = {"check_for_timer_pops", "check_for_timer_pops first time"}; /* for check_for_timer_pops */ #ifdef DEBUG STATICDEF int trc_timerpop_idx; STATICDEF GT_TIMER trc_timerpop_array[MAX_TIMER_POP_TRACE_SZ]; # define TRACE_TIMER_POP(TIMER_INFO) \ { \ memcpy(&trc_timerpop_array[trc_timerpop_idx], TIMER_INFO, SIZEOF(GT_TIMER)); \ trc_timerpop_idx = (trc_timerpop_idx + 1) % MAX_TIMER_POP_TRACE_SZ; \ } #endif /* Flag signifying timer is active. Especially useful when the timer handlers get nested. This has not been moved to a * threaded framework because we do not know how timers will be used with threads. */ GBLDEF volatile boolean_t timer_active = FALSE; GBLDEF volatile int4 timer_stack_count = 0; GBLDEF volatile boolean_t timer_in_handler = FALSE; GBLDEF void (*wcs_clean_dbsync_fptr)(); /* Reference to wcs_clean_dbsync() to be used in gt_timers.c. */ GBLDEF void (*wcs_stale_fptr)(); /* Reference to wcs_stale() to be used in gt_timers.c. */ GBLDEF boolean_t deferred_timers_check_needed; /* Indicator whether check_for_deferred_timers() should be called * upon leaving deferred zone. */ GBLREF boolean_t blocksig_initialized; /* Set to TRUE when blockalrm, block_ttinout, and block_sigsent are * initialized. */ GBLREF sigset_t blockalrm; GBLREF sigset_t block_ttinout; GBLREF sigset_t block_sigsent; GBLREF boolean_t heartbeat_started; GBLREF void (*heartbeat_timer_ptr)(void); /* Initialized only in gtm_startup(). */ GBLREF int4 error_condition; GBLREF int4 outofband; GBLREF int process_exiting; error_def(ERR_TIMERHANDLER); /* Called when a hiber_start timer pops. Set flag so a given timer will wake up (not go back to sleep). */ STATICFNDEF void hiber_wake(TID tid, int4 hd_len, int4 **waitover_flag) { **waitover_flag = TRUE; } /* Preallocate some memory for timers. */ void gt_timers_alloc(void) { int4 gt_timer_cnt; GT_TIMER *timeblk, *timeblks; st_timer_alloc *new_alloc; /* Allocate timer blocks putting each timer on the free queue */ assert(1 > timer_stack_count); timeblk_hdrlen = OFFSETOF(GT_TIMER, hd_data[0]); timeblk = timeblks = (GT_TIMER *)malloc((timeblk_hdrlen + GT_TIMER_INIT_DATA_LEN) * TIMER_BLOCK_SIZE); new_alloc = (st_timer_alloc *)malloc(SIZEOF(st_timer_alloc)); new_alloc->addr = timeblk; new_alloc->next = (st_timer_alloc *)timer_allocs; timer_allocs = new_alloc; for (gt_timer_cnt = TIMER_BLOCK_SIZE; 0 < gt_timer_cnt; --gt_timer_cnt) { timeblk->hd_len_max = GT_TIMER_INIT_DATA_LEN; /* Set amount it can store */ timeblk->next = (GT_TIMER *)timefree; /* Put on free queue */ timefree = timeblk; timeblk = (GT_TIMER *)((char *)timeblk + timeblk_hdrlen + GT_TIMER_INIT_DATA_LEN); /* Next! */ } assert(((char *)timeblk - (char *)timeblks) == (timeblk_hdrlen + GT_TIMER_INIT_DATA_LEN) * TIMER_BLOCK_SIZE); num_timers_free += TIMER_BLOCK_SIZE; } void add_safe_timer_handler(int safetmr_cnt, ...) { int i; va_list var; timer_hndlr tmrhndlr; VAR_START(var, safetmr_cnt); for (i = 1; i <= safetmr_cnt; i++) { tmrhndlr = va_arg(var, timer_hndlr); ADD_SAFE_HNDLR(tmrhndlr); } va_end(var); } /* Do the initialization of blockalrm, block_ttinout and block_sigsent, and set blocksig_initialized to TRUE, so * that we can later block signals when there is a need. This function should be called very early * in the main() routines of modules that wish to do their own interrupt handling. */ void set_blocksig(void) { sigemptyset(&blockalrm); sigaddset(&blockalrm, SIGALRM); sigemptyset(&block_ttinout); sigaddset(&block_ttinout, SIGTTIN); sigaddset(&block_ttinout, SIGTTOU); sigemptyset(&block_sigsent); sigaddset(&block_sigsent, SIGINT); sigaddset(&block_sigsent, SIGQUIT); sigaddset(&block_sigsent, SIGTERM); sigaddset(&block_sigsent, SIGTSTP); sigaddset(&block_sigsent, SIGCONT); sigaddset(&block_sigsent, SIGALRM); blocksig_initialized = TRUE; /* note the fact that blockalrm and block_sigsent are initialized */ } /* Initialize group of timer blocks */ void prealloc_gt_timers(void) { /* On certain boxes SYSCONF in this function might get called earlier than * the one in set_num_additional_processors(), so unset white_box_enabled * for this SYSCONF to avoid issues */ # ifdef __MVS__ # ifdef DEBUG boolean_t white_box_enabled = gtm_white_box_test_case_enabled; if (white_box_enabled) gtm_white_box_test_case_enabled = FALSE; # endif SYSCONF(_SC_CLK_TCK, gtm_zos_HZ); /* get the real value */ # ifdef DEBUG if (white_box_enabled) gtm_white_box_test_case_enabled = TRUE; # endif # endif /* Preallocate some timer blocks. This will be all the timer blocks we hope to need. * Allocate them with 8 bytes of possible data each. * If more timer blocks are needed, we will allocate them as needed. */ gt_timers_alloc(); /* Allocate timers */ /* Now initialize the safe timers. Must be done dynamically to avoid the situation where this module always references all * possible safe timers thus pulling extra stuff into executables that don't need or want it. * * First step, fill in the safe timers contained within this module which are always available. */ ADD_SAFE_HNDLR(&hiber_wake); /* Resident in this module */ ADD_SAFE_HNDLR(&hiber_start_wait_any); /* Resident in this module */ ADD_SAFE_HNDLR(&wake_alarm); /* Standalone module containing on one global reference */ } /* Get current clock time. Fill-in the structure with the absolute time of system clock. * Arguments: atp - pointer to structure of absolute time */ void sys_get_curr_time(ABS_TIME *atp) { # ifdef BSD_TIMER struct timeval tv; struct timezone tz; /* getclock or clock_gettime perhaps to avoid tz just to ignore */ gettimeofday(&tv, &tz); atp->at_sec = (int4)tv.tv_sec; atp->at_usec = (int4)tv.tv_usec; # else atp->at_sec = time((int4 *) 0); atp->at_usec = 0; # endif } /* Start hibernating by starting a timer and waiting for it. */ void hiber_start(uint4 hiber) { int4 waitover; int4 *waitover_addr; TID tid; sigset_t savemask; assertpro(1 > timer_stack_count); /* timer services are unavailable from within a timer handler */ sigprocmask(SIG_BLOCK, &blockalrm, &savemask); /* block SIGALRM signal */ /* sigsuspend() sets the signal mask to 'savemask' and waits for an ALARM signal. If the SIGALRM is a member of savemask, * this process will never receive SIGALRM, and it will hang indefinitely. One such scenario would be if we interrupted a * timer handler with kill -15, thus getting all timer setup reset by generic_signal_handler, and the gtm_exit_handler * ended up invoking hiber_start (when starting gtmsecshr server, for instance). In such situations rely on something other * than GT.M timers. */ if (sigismember(&savemask, SIGALRM)) { NANOSLEEP(hiber); } else { waitover = FALSE; /* when OUR timer pops, it will set this flag */ waitover_addr = &waitover; tid = (TID)waitover_addr; /* unique id of this timer */ start_timer_int((TID)tid, hiber, hiber_wake, SIZEOF(waitover_addr), &waitover_addr, TRUE); /* we will loop here until OUR timer pops and sets OUR flag */ do { assert(!sigismember(&savemask, SIGALRM)); sigsuspend(&savemask); /* unblock SIGALRM and wait for timer interrupt */ if (outofband) { cancel_timer(tid); break; } } while(FALSE == waitover); } sigprocmask(SIG_SETMASK, &savemask, NULL); /* reset signal handlers */ } /* Hibernate by starting a timer and waiting for it or any other timer to pop. */ void hiber_start_wait_any(uint4 hiber) { sigset_t savemask; if (1000 > hiber) { SHORT_SLEEP(hiber); /* note: some platforms call hiber_start */ return; } assertpro(1 > timer_stack_count); /* timer services are unavailable from within a timer handler */ sigprocmask(SIG_BLOCK, &blockalrm, &savemask); /* block SIGALRM signal and set new timer */ /* Even though theoretically it is possible for any signal other than SIGALRM to discontinue the wait in sigsuspend, * the intended use of this function targets only timer-scheduled events. For that reason, assert that SIGALRMs are * not blocked prior to scheduling a timer, whose delivery we will be waiting upon, as otherwise we might end up * waiting indefinitely. Note, however, that the use of NANOSLEEP in hiber_start, explained in the accompanying * comment, should not be required in hiber_start_wait_any, as we presently do not invoke this function in interrupt- * induced code, and so we should not end up here with SIGALARMs blocked. */ assert(!sigismember(&savemask, SIGALRM)); start_timer_int((TID)hiber_start_wait_any, hiber, NULL, 0, NULL, TRUE); sigsuspend(&savemask); /* unblock SIGALRM and wait for timer interrupt */ cancel_timer((TID)hiber_start_wait_any); /* cancel timer block before reenabling */ sigprocmask(SIG_SETMASK, &savemask, NULL); /* reset signal handlers */ } /* Wrapper function for start_timer() that is exposed for outside use. The function ensure that time_to_expir is positive. If * negative value or 0 is passed, set time_to_expir to SLACKTIME and invoke start_timer(). The reason we have not merged this * functionality with start_timer() is because there is no easy way to determine whether the function is invoked from inside * GT.M or by an external routine. * Arguments: tid - timer id * time_to_expir - time to expiration in msecs * handler - pointer to handler routine * hdata_len - length of handler data next arg * hdata - data to pass to handler (if any) */ void gtm_start_timer(TID tid, int4 time_to_expir, void (*handler)(), int4 hdata_len, void *hdata) { if (0 >= time_to_expir) time_to_expir = SLACKTIME; start_timer(tid, time_to_expir, handler, hdata_len, hdata); } /* Start the timer. If timer chain is empty or this is the first timer to expire, actually start the system timer. * Arguments: tid - timer id * time_to_expir - time to expiration in msecs * handler - pointer to handler routine * hdata_len - length of handler data next arg * hdata - data to pass to handler (if any) */ void start_timer(TID tid, int4 time_to_expir, void (*handler)(), int4 hdata_len, void *hdata) { sigset_t savemask; boolean_t safe_timer = FALSE, safe_to_add = FALSE; int i; assertpro(0 < time_to_expir); /* Callers should verify non-zero time */ if (NULL == handler) { safe_to_add = TRUE; safe_timer = TRUE; } else if ((wcs_clean_dbsync_fptr == handler) || (wcs_stale_fptr == handler)) safe_to_add = TRUE; else { for (i = 0; NULL != safe_handlers[i]; i++) if (safe_handlers[i] == handler) { safe_to_add = TRUE; safe_timer = TRUE; break; } } if (!safe_to_add && (process_exiting || (INTRPT_OK_TO_INTERRUPT != intrpt_ok_state))) { assert(WBTEST_ENABLED(WBTEST_RECOVER_ENOSPC)); return; } sigprocmask(SIG_BLOCK, &blockalrm, &savemask); /* block SIGALRM signal */ start_timer_int(tid, time_to_expir, handler, hdata_len, hdata, safe_timer); sigprocmask(SIG_SETMASK, &savemask, NULL); /* reset signal handlers */ } /* Internal version of start_timer that does not protect itself, assuming this has already been done. * Otherwise does as explained above in start_timer. */ STATICFNDEF void start_timer_int(TID tid, int4 time_to_expir, void (*handler)(), int4 hdata_len, void *hdata, boolean_t safe_timer) { ABS_TIME at; assert(0 != time_to_expir); sys_get_curr_time(&at); if (first_timeset) { init_timers(); first_timeset = FALSE; } /* We expect no timer with id= to exist in the timer queue currently. This is asserted in "add_timer" call below. * In pro though, we'll be safe and remove any tids that exist before adding a new entry with the same tid - 2009/10. * If a few years pass without the assert failing, it might be safe then to remove the PRO_ONLY code below. */ # ifndef DEBUG if (timeroot && (timeroot->tid == tid)) sys_canc_timer(); remove_timer(tid); /* Remove timer from chain */ # endif /* Check if # of free timer slots is less than minimum threshold. If so, allocate more of those while it is safe to do so */ if ((GT_TIMER_EXPAND_TRIGGER > num_timers_free) && (1 > timer_stack_count)) gt_timers_alloc(); add_timer(&at, tid, time_to_expir, handler, hdata_len, hdata, safe_timer); /* Put new timer in the queue. */ if ((timeroot->tid == tid) || !timer_active) start_first_timer(&at); } /* Uninitialize all timers, since we will not be needing them anymore. */ STATICFNDEF void uninit_all_timers(void) { st_timer_alloc *next_timeblk; sys_canc_timer(); first_timeset = TRUE; for (; timer_allocs; timer_allocs = next_timeblk) /* loop over timer_allocs entries and deallocate them */ { next_timeblk = timer_allocs->next; free(timer_allocs->addr); /* free the timeblk */ free((st_timer_alloc *)timer_allocs); /* free the container */ } /* after all timers are removed, we need to set the below pointers to NULL */ timeroot = NULL; timefree = NULL; num_timers_free = 0; /* empty the blockalrm and sigsent entries */ sigemptyset(&blockalrm); sigemptyset(&block_sigsent); sigaction(SIGALRM, &prev_alrm_handler, NULL); timer_active = FALSE; } /* Cancel timer. * Arguments: tid - timer id */ void cancel_timer(TID tid) { ABS_TIME at; sigset_t savemask; sigprocmask(SIG_BLOCK, &blockalrm, &savemask); /* block SIGALRM signal */ sys_get_curr_time(&at); if (tid == 0) { assert(process_exiting || IS_GTMSECSHR_IMAGE); /* wcs_phase2_commit_wait relies on this flag being set BEFORE * cancelling all timers. But secshr doesn't have it. */ cancel_all_timers(); uninit_all_timers(); timer_stack_count = 0; sigprocmask(SIG_SETMASK, &savemask, NULL); return; } if (timeroot && (timeroot->tid == tid)) /* if this is the first timer in the chain, stop it */ sys_canc_timer(); remove_timer(tid); /* remove it from the chain */ start_first_timer(&at); /* start the first timer in the chain */ sigprocmask(SIG_SETMASK, &savemask, NULL); } /* Clear the timers' state for the forked-off process. */ void clear_timers(void) { sigset_t savemask; sigprocmask(SIG_BLOCK, &blockalrm, &savemask); /* block SIGALRM signal */ while (timeroot) remove_timer(timeroot->tid); timer_in_handler = FALSE; timer_active = FALSE; heartbeat_started = FALSE; sigprocmask(SIG_SETMASK, &savemask, NULL); return; } /* System call to set timer. Time is given im msecs. * Arguments: tid - timer id * time_to_expir - time to expiration * handler - address of handler routine */ STATICFNDEF void sys_settimer (TID tid, ABS_TIME *time_to_expir, void (*handler)()) { # ifdef BSD_TIMER if ((time_to_expir->at_sec == 0) && (time_to_expir->at_usec < (1000000 / HZ))) { sys_timer.it_value.tv_sec = 0; sys_timer.it_value.tv_usec = 1000000 / HZ; } else { sys_timer.it_value.tv_sec = time_to_expir->at_sec; sys_timer.it_value.tv_usec = (gtm_tv_usec_t)time_to_expir->at_usec; } sys_timer.it_interval.tv_sec = sys_timer.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &sys_timer, &old_sys_timer); # else if (time_to_expir->at_sec == 0) alarm((unsigned)1); else alarm(time_to_expir->at_sec); # endif timer_active = TRUE; } /* Start the first timer in the timer chain * Arguments: curr_time - current time assumed within the function */ STATICFNDEF void start_first_timer(ABS_TIME *curr_time) { ABS_TIME eltime, interval; GT_TIMER *tpop; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if ((1 < timer_stack_count) || (TRUE == timer_in_handler)) { deferred_timers_check_needed = FALSE; return; } if ((INTRPT_OK_TO_INTERRUPT == intrpt_ok_state) && !process_exiting) { while (timeroot) /* check if some timer expired while this function was getting invoked */ { eltime = sub_abs_time((ABS_TIME *)&timeroot->expir_time, curr_time); if ((0 <= eltime.at_sec) || (0 < timer_stack_count)) /* nothing has expired yet */ break; timer_handler(DUMMY_SIG_NUM); /* otherwise, drive the handler */ } if (timeroot) /* we still have a timer to set? */ { add_int_to_abs_time(&eltime, SLACKTIME, &interval); deferred_timers_check_needed = FALSE; sys_settimer(timeroot->tid, &interval, timeroot->handler); /* set system timer */ } } else if (0 < safe_timer_cnt) /* there are some safe timers */ { tpop = (GT_TIMER *)timeroot; /* regular timers are not allowed here, so only handle safe timers */ while (tpop) { eltime = sub_abs_time((ABS_TIME *)&tpop->expir_time, curr_time); if (tpop->safe) { if (0 > eltime.at_sec) /* at least one safe timer has expired */ timer_handler(DUMMY_SIG_NUM); /* so, drive what we can */ else { add_int_to_abs_time(&eltime, SLACKTIME, &interval); sys_settimer(tpop->tid, &interval, tpop->handler); } break; } else if (0 > eltime.at_sec) deferred_timers_check_needed = TRUE; tpop = tpop->next; } } } /* Timer handler. This is the main handler routine that is being called by the kernel upon receipt * of timer signal. It dispatches to the user handler routine, and removes first timer in a timer * queue. If the queue is not empty, it starts the first timer in the queue. The why parameter is a * no-op in our case, but is required to maintain compatibility with the system type of __sighandler_t, * which is (void*)(int). */ STATICFNDEF void timer_handler(int why) { int4 cmp, save_error_condition; GT_TIMER *tpop, *tpop_prev = NULL; ABS_TIME at; int save_errno, timer_defer_cnt, offset; TID *deferred_tid; boolean_t tid_found; char *save_util_outptr; va_list save_last_va_list_ptr; boolean_t util_copy_saved = FALSE; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (SIGALRM == why) { /* If why is 0, we know that timer_handler() was called directly, so no need * to check if the signal needs to be forwarded to appropriate thread. */ FORWARD_SIG_TO_MAIN_THREAD_IF_NEEDED(SIGALRM); } # ifdef DEBUG if (IS_GTM_IMAGE) { tpop = find_timer((TID)heartbeat_timer_ptr, &tpop); assert(process_exiting || (((NULL != tpop) && heartbeat_started) || ((NULL == tpop) && !heartbeat_started))); } # endif if (0 < timer_stack_count) return; timer_stack_count++; deferred_timers_check_needed = FALSE; save_errno = errno; save_error_condition = error_condition; /* aka SIGNAL */ timer_active = FALSE; /* timer has popped; system timer not active anymore */ sys_get_curr_time(&at); tpop = (GT_TIMER *)timeroot; timer_defer_cnt = 0; /* reset the deferred timer count, since we are in timer_handler */ SAVE_UTIL_OUT_BUFFER(save_util_outptr, save_last_va_list_ptr, util_copy_saved); while (tpop) /* fire all handlers that expired */ { cmp = abs_time_comp(&at, (ABS_TIME *)&tpop->expir_time); if (cmp < 0) break; /* A timer might pop while we are in the non-zero intrpt_ok_state zone, which could cause collisions. Instead, * we will defer timer events and drive them once the deferral is removed, unless the timer is safe. */ if (((INTRPT_OK_TO_INTERRUPT == intrpt_ok_state) && (FALSE == process_exiting)) || (tpop->safe)) { if (NULL != tpop_prev) tpop_prev->next = tpop->next; else timeroot = tpop->next; if (tpop->safe) { safe_timer_cnt--; assert(0 <= safe_timer_cnt); } if (NULL != tpop->handler) /* if there is a handler, call it */ { # ifdef DEBUG if (gtm_white_box_test_case_enabled && (WBTEST_DEFERRED_TIMERS == gtm_white_box_test_case_number) && ((void *)tpop->handler != (void*)heartbeat_timer_ptr)) { DBGFPF((stderr, "TIMER_HANDLER: handled a timer\n")); timer_pop_cnt++; } # endif timer_in_handler = TRUE; (*tpop->handler)(tpop->tid, tpop->hd_len, tpop->hd_data); timer_in_handler = FALSE; if (!tpop->safe) /* if safe, avoid a system call */ sys_get_curr_time(&at); /* refresh current time if called a handler */ DEBUG_ONLY(TRACE_TIMER_POP(tpop)); } tpop->next = (GT_TIMER *)timefree; /* put timer block on the free chain */ timefree = tpop; if (NULL != tpop_prev) tpop = tpop_prev->next; else tpop = (GT_TIMER *)timeroot; num_timers_free++; assert(0 < num_timers_free); } else { timer_defer_cnt++; # ifdef DEBUG if (gtm_white_box_test_case_enabled && (WBTEST_DEFERRED_TIMERS == gtm_white_box_test_case_number)) { if (!deferred_tids) { deferred_tids = (TID *)malloc(SIZEOF(TID) * 2); *deferred_tids = tpop->tid; *(deferred_tids + 1) = -1; DBGFPF((stderr, "TIMER_HANDLER: deferred a timer\n")); } else { tid_found = FALSE; deferred_tid = deferred_tids; while (-1 != *deferred_tid) { if (*deferred_tid == tpop->tid) { tid_found = TRUE; break; } deferred_tid++; } if (!tid_found) { offset = deferred_tid - deferred_tids; deferred_tid = (TID *)malloc((offset + 2) * SIZEOF(TID)); memcpy(deferred_tid, deferred_tids, offset * SIZEOF(TID)); free(deferred_tids); deferred_tids = deferred_tid; *(deferred_tids + offset++) = tpop->tid; *(deferred_tids + offset) = -1; DBGFPF((stderr, "TIMER_HANDLER: deferred a timer\n")); } } } # endif tpop_prev = tpop; tpop = tpop->next; if (0 == safe_timer_cnt) /* no more safe timers left, so quit */ break; } } RESTORE_UTIL_OUT_BUFFER(save_util_outptr, save_last_va_list_ptr, util_copy_saved); if (((FALSE == process_exiting) && (INTRPT_OK_TO_INTERRUPT == intrpt_ok_state)) || (0 < safe_timer_cnt)) start_first_timer(&at); else if ((NULL != timeroot) || (0 < timer_defer_cnt)) deferred_timers_check_needed = TRUE; /* Restore mainline error_condition global variable. This way any gtm_putmsg or rts_errors that occurred inside * interrupt code do not affect the error_condition global variable that mainline code was relying on. * For example, not doing this restore caused the update process (in updproc_ch) to issue a GTMASSERT (GTM-7526). */ error_condition = save_error_condition; errno = save_errno; /* restore mainline errno by similar reasoning as mainline error_condition */ timer_stack_count--; } /* Find a timer given by tid in the timer chain. * Arguments: tid - timer id * tprev - address of pointer to previous node * Return: pointer to timer in the chain, or 0 if timer is not found * Note: tprev is set to the link previous to the tid link */ STATICFNDEF GT_TIMER *find_timer(TID tid, GT_TIMER **tprev) { GT_TIMER *tc; tc = (GT_TIMER *)timeroot; *tprev = NULL; while (tc) { if (tc->tid == tid) return tc; *tprev = tc; tc = tc->next; } return 0; } /* Add timer to timer chain. Allocate a new link for a timer. Convert time to expiration into absolute time. * Insert new link into chain in timer order. * Arguments: tid - timer id * time_to_expir - elapsed time to expiration * handler - pointer to handler routine * hdata_len - length of data to follow * hdata - data to pass to timer rtn if any * safe_timer - timer's handler is in safe_handlers array */ STATICFNDEF void add_timer(ABS_TIME *atp, TID tid, int4 time_to_expir, void (*handler)(), int4 hdata_len, void *hdata, boolean_t safe_timer) { GT_TIMER *tp, *tpp, *ntp, *lastntp; int4 cmp, i; st_timer_alloc *new_alloc; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; /* assert that no timer entry with the same "tid" exists in the timer chain */ assert(NULL == find_timer(tid, &tpp)); /* obtain a new timer block */ ntp = (GT_TIMER *)timefree; lastntp = NULL; for ( ; NULL != ntp; ) { /* we expect all callers of timer functions to not require more than 8 bytes of data; any violations * of this assumption need to be caught---hence the assert below */ assert(GT_TIMER_INIT_DATA_LEN == ntp->hd_len_max); assert(ntp->hd_len_max >= hdata_len); if (ntp->hd_len_max >= hdata_len) /* found one that can hold our data */ { /* dequeue block */ if (NULL == lastntp) /* first one on queue */ timefree = ntp->next; /* dequeue 1st element */ else /* is not 1st on queue -- use simple dequeue */ lastntp->next = ntp->next; assert(0 < num_timers_free); num_timers_free--; break; } lastntp = ntp; /* still looking, try next block */ ntp = ntp->next; } /* if didn't find one, fail if dbg; else malloc a new one */ if (NULL == ntp) { assert(FALSE); /* if dbg, we should have enough already */ ntp = (GT_TIMER *)malloc(timeblk_hdrlen + hdata_len); /* if we are in a timer, malloc may error out */ new_alloc = (st_timer_alloc *)malloc(SIZEOF(st_timer_alloc)); /* insert in front of the list */ new_alloc->addr = ntp; new_alloc->next = (st_timer_alloc *)timer_allocs; timer_allocs = new_alloc; ntp->hd_len_max = hdata_len; } ntp->tid = tid; ntp->handler = handler; if (safe_timer) { ntp->safe = TRUE; safe_timer_cnt++; assert(0 < safe_timer_cnt); } else ntp->safe = FALSE; ntp->hd_len = hdata_len; if (0 < hdata_len) memcpy(ntp->hd_data, hdata, hdata_len); add_int_to_abs_time(atp, time_to_expir, &ntp->expir_time); ntp->start_time.at_sec = atp->at_sec; ntp->start_time.at_usec = atp->at_usec; tp = (GT_TIMER *)timeroot; tpp = NULL; while (tp) { cmp = abs_time_comp(&tp->expir_time, &ntp->expir_time); if (cmp >= 0) break; tpp = tp; tp = tp->next; } ntp->next = tp; if (NULL == tpp) timeroot = ntp; else tpp->next = ntp; return; } /* Remove timer from the timer chain. */ STATICFNDEF void remove_timer(TID tid) { GT_TIMER *tprev, *tp, *tpp; DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (tp = find_timer(tid, &tprev)) /* Warning: assignment */ { if (tprev) tprev->next = tp->next; else timeroot = tp->next; if (tp->safe) safe_timer_cnt--; tp->next = (GT_TIMER *)timefree; /* place element on free queue */ timefree = tp; num_timers_free++; assert(0 < num_timers_free); /* assert that no duplicate timer entry with the same "tid" exists in the timer chain */ assert((NULL == find_timer(tid, &tpp))); } } /* System call to cancel timer. Not static because can be called from generic_signal_handler() to stop timers * from popping yet preserve the blocks so gtmpcat can pick them out of the core. Note that once we exit, * timers are cleared at the top of the exit handler. */ void sys_canc_timer() { # ifdef BSD_TIMER struct itimerval zero; memset(&zero, 0, SIZEOF(struct itimerval)); setitimer(ITIMER_REAL, &zero, &old_sys_timer); # else alarm(0); # endif timer_active = FALSE; /* no timer is active now */ } /* Cancel all timers. * Note: The timer signal must be blocked prior to entry */ STATICFNDEF void cancel_all_timers(void) { DEBUG_ONLY(int4 cnt = 0;) DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; if (timeroot) sys_canc_timer(); while (timeroot) { /* remove timer from the chain */ remove_timer(timeroot->tid); DEBUG_ONLY(cnt++;) } safe_timer_cnt = 0; if (!timeroot) { deferred_timers_check_needed = FALSE; } # ifdef DEBUG if (gtm_white_box_test_case_enabled && WBTEST_DEFERRED_TIMERS == gtm_white_box_test_case_number) { DBGFPF((stderr, "CANCEL_ALL_TIMERS:\n")); DBGFPF((stderr, " Timer pops handled: %d\n", timer_pop_cnt)); DBGFPF((stderr, " Timers canceled: %d\n", cnt)); } # endif } /* Initialize timers. */ STATICFNDEF void init_timers() { struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = timer_handler; sigaction(SIGALRM, &act, &prev_alrm_handler); if (first_timeset && /* not from timer_handler to prevent dup message */ (SIG_IGN != prev_alrm_handler.sa_handler) && /* as set by sig_init */ (SIG_DFL != prev_alrm_handler.sa_handler)) /* utils, compile */ { send_msg_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_TIMERHANDLER, 3, prev_alrm_handler.sa_handler, LEN_AND_LIT("init_timers")); rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_TIMERHANDLER, 3, prev_alrm_handler.sa_handler, LEN_AND_LIT("init_timers")); assert(FALSE); } } /* Check for deferred timers. Drive any timers that have been deferred. In case the system timer is * disabled, launch it for the next scheduled event. This function should be called upon leaving the * interrupt-deferred zone. */ void check_for_deferred_timers(void) { sigset_t savemask; deferred_timers_check_needed = FALSE; sigprocmask(SIG_BLOCK, &blockalrm, &savemask); /* block SIGALRM signal */ timer_handler(DUMMY_SIG_NUM); sigprocmask(SIG_SETMASK, &savemask, NULL); /* reset signal handlers */ } /* Check for timer pops. If any timers are on the queue, pretend a sigalrm occurred, and we have to * check everything. This is mainly for use after external calls until such time as external calls * can use this timing facility. Current problem is that external calls are doing their own catching * of sigalarms that should be ours, so we end up hung. */ void check_for_timer_pops() { int stolenwhen = 0; /* 0 = no, 1 = not first, 2 = first time */ sigset_t savemask; struct sigaction current_sa; sigaction(SIGALRM, NULL, ¤t_sa); /* get current info */ if (!first_timeset) { if (timer_handler != current_sa.sa_handler) /* check if what we expected */ { init_timers(); if (!stolen_timer) { stolen_timer = TRUE; stolenwhen = 1; } } } else /* we haven't set so should be ... */ { if ((SIG_IGN != current_sa.sa_handler) && /* as set by sig_init */ (SIG_DFL != current_sa.sa_handler)) /* utils, compile */ { if (!stolen_timer) { stolen_timer = TRUE; stolenwhen = 2; } } } if (timeroot && (1 > timer_stack_count)) { sigprocmask(SIG_BLOCK, &blockalrm, &savemask); /* block SIGALRM signal */ timer_handler(DUMMY_SIG_NUM); sigprocmask(SIG_SETMASK, &savemask, NULL); /* reset signal handlers */ } if (stolenwhen) { send_msg_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_TIMERHANDLER, 3, current_sa.sa_handler, LEN_AND_STR(whenstolen[stolenwhen - 1])); rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_TIMERHANDLER, 3, current_sa.sa_handler, LEN_AND_STR(whenstolen[stolenwhen - 1])); assert(FALSE); /* does not return here */ } } /* Externally exposed routine that does a find_timer and is SIGALRM interrupt safe. */ GT_TIMER *find_timer_intr_safe(TID tid, GT_TIMER **tprev) { sigset_t savemask; GT_TIMER *tcur; /* Before scanning timer queues, block SIGALRM signal as otherwise that signal could cause an interrupt * timer routine to be driven which could in turn modify the timer queues while this mainline code is * examining the very same queue. This could cause all sorts of invalid returns (of tcur and tprev) * from the find_timer call below. */ sigprocmask(SIG_BLOCK, &blockalrm, &savemask); tcur = find_timer(tid, tprev); sigprocmask(SIG_SETMASK, &savemask, NULL); return tcur; }