fis-gtm/sr_unix/gt_timers.c

1031 lines
35 KiB
C

/****************************************************************
* *
* 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 <errno.h>
#include <stddef.h>
#include <stdarg.h>
#ifdef GTM_PTHREAD
# include <pthread.h>
#endif
#include <signal.h>
#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 <bsd/sys/time.h>
#else
# include <sys/time.h>
#endif
#ifndef __MVS__
# include <sys/param.h>
#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=<tid> 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, &current_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;
}