blob: 1d99e19ff4b205450178c1f1079ce80d6131685d [file] [log] [blame]
#include <sys/time.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include "types.h"
#include "timer.h"
#include "util.h"
#include "uniq.h"
#include "log.h"
#include "mt.h"
struct timer {
uniq_t handle;
struct timer *next;
uint64_t fireTime;
timerCbk cbk;
uint64_t cbkData;
};
static pthread_mutex_t mTimersLock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t mTimersCond = PTHREAD_COND_INITIALIZER;
static pthread_t mTimersThread = 0;
static struct timer *mTimers = NULL;
static clockid_t mClock;
static bool mDie = false;
/*
* FUNCTION: timerCurTime
* USE: Get current time in milliseconds
* PARAMS: NONE
* RETURN: the time
* NOTES: Time returned here need only be monotonically increasing,
* it need not be based on any particular base time, nor
* is it required to be the same base every reboot.
*/
static uint64_t timerCurTime(void)
{
struct timespec ts;
uint64_t ret;
int err;
err = clock_gettime(mClock, &ts);
if (err)
loge("Unexpected error %d getting clock\n", err);
ret = ts.tv_sec;
ret *= 1000ULL;
ret += ts.tv_nsec / 1000000ULL;
return ret;
}
/*
* FUNCTION: timersWaitOnCondWithTimeout
* USE: pthread_cond_wait but with an msec-precise timeout
* PARAMS: c - cond variable
* m - mutex
* msec - timeout length
* RETURN: 0 on success, else return value of pthread_cond_timedwait() - the error code
* NOTES:
*/
static int timersWaitOnCondWithTimeout(pthread_cond_t *c, pthread_mutex_t *m, unsigned msec) {
struct timeval tv;
struct timespec ts;
gettimeofday(&tv, NULL);
ts.tv_sec = tv.tv_sec + msec / 1000;
ts.tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L) * 1000L;
return pthread_cond_timedwait(c, m, &ts);
}
/*
* FUNCTION: timersThread
* USE: Worker thread for timers code
* PARAMS: unused - unused
* RETURN: void* - unused
* NOTES:
*/
static void* timersThread(void *unused)
{
struct timer *t, *toFire;
pthread_setname_np(pthread_self(), "bt_timers");
do {
pthread_mutex_lock(&mTimersLock);
do {
struct timer *p = NULL;
uint64_t curTime;
/* grab the part of the list that is due, disconnect it, save head to "toFire" */
curTime = timerCurTime();
t = mTimers;
while (t && t->fireTime <= curTime) {
p = t;
t = t->next;
}
toFire = mTimers;
mTimers = t;
if (p)
p->next = NULL;
else
toFire = NULL;
if (toFire)
break;
if (mTimers)
timersWaitOnCondWithTimeout(&mTimersCond, &mTimersLock, mTimers->fireTime - curTime);
else
pthread_cond_wait(&mTimersCond, &mTimersLock);
if (mDie)
goto out;
} while(1);
pthread_mutex_unlock(&mTimersLock);
while (toFire) {
t = toFire;
toFire = toFire->next;
t->cbk(t->handle, t->cbkData);
free(t);
}
} while(1);
out:
while (mTimers) {
t = mTimers;
mTimers = mTimers->next;
free(t);
}
pthread_mutex_unlock(&mTimersLock);
return unused;
}
/*
* FUNCTION: timersInit
* USE: Perform one-time init of timer code
* PARAMS: NONE
* RETURN: success
* NOTES:
*/
bool timersInit(void)
{
struct timespec dummy;
int ret;
#ifdef CLOCK_BOOTTIME
/* find which clock we'll use */
ret = clock_gettime(mClock = CLOCK_BOOTTIME, &dummy);
#else
ret = -1;
#endif
if (ret) {
logw("CLOCK_BOOTTIME not available on this system. Trying fallbacks\n");
if (!clock_gettime(mClock = CLOCK_MONOTONIC, &dummy))
logw("Clock fallback: CLOCK_MONOTONIC\n");
else if(!clock_gettime(mClock = CLOCK_REALTIME, &dummy))
logw("Clock fallback: CLOCK_REALTIME\n");
else
loge("No clock fallback found!\n");
}
/* get going on the worker thread */
mDie = false;
ret = pthread_create(&mTimersThread, NULL, timersThread, NULL);
if (ret) {
loge("Timer thread creatin error: %d\n", ret);
return false;
}
return true;
}
/*
* FUNCTION: timersDeinit
* USE: Perform deinit of timers
* PARAMS: NONE
* RETURN: NONE
* NOTES:
*/
void timersDeinit(void)
{
mDie = true;
pthread_cond_signal(&mTimersCond);
pthread_join(mTimersThread, NULL);
}
/*
* FUNCTION: timerSet
* USE: Create a timer
* PARAMS: msec - how many msce from now to fire in
* cbk - callback to call when timer fires
* cbkData - data to pass to said callback
* RETURN: uniq_t handle of the timer or 0 for failure
* NOTES:
*/
uniq_t timerSet(uint64_t msec, timerCbk cbk, uint64_t cbkData)
{
struct timer *t, *c, *p = NULL;
bool wake = false;
uniq_t ret = 0;
/* prepare the struct */
t = (struct timer*)malloc(sizeof(struct timer));
if (!t)
return 0;
t->handle = ret = uniqGetNext();
t->cbk = cbk;
t->cbkData = cbkData;
t->fireTime = timerCurTime() + msec;
pthread_mutex_lock(&mTimersLock);
/* find where it belongs */
c = mTimers;
while (c && c->fireTime < t->fireTime) {
p = c;
c = c->next;
}
/* link it in */
t->next = c;
if (p)
p->next = t;
else {
mTimers = t;
wake = true;
}
/* wake the timer thread if we added a new first */
pthread_cond_signal(&mTimersCond);
pthread_mutex_unlock(&mTimersLock);
return ret;
}
/*
* FUNCTION: timerCancel
* USE: Cancel a timer
* PARAMS: timer the handle for a timer
* RETURN: true if we cancelled the timer before it had a
* chance to fire. False in all other cases like:
* handle not found, already fired, etc...
* NOTES:
*/
bool timerCancel(uniq_t timer)
{
struct timer *t, *p = NULL;
pthread_mutex_lock(&mTimersLock);
t = mTimers;
while (t && t->handle != timer) {
p = t;
t = t->next;
}
if (t) {
if (p)
p->next = t->next;
else
mTimers = t->next;
free(t);
}
pthread_mutex_unlock(&mTimersLock);
return !!t;
}
/*
* FUNCTION: timerSyncCbk
* USE: Used by timerSync to flush the "toFire" queue
* PARAMS: which - which timer fired
* cbkData - cbk data (currently pointer to a semaphore)
* RETURN: NONE
* NOTES:
*/
static void timerSyncCbk(uniq_t which, uint64_t cbkData)
{
sem_post((sem_t*)(uintptr_t)cbkData);
}
/*
* FUNCTION: timerSync
* USE: Make sure all cancelled timers have either fired or will not fire for sure
* PARAMS: NONE
* RETURN: NONE
* NOTES: May block for a while
*/
void timerSync(void)
{
sem_t sem;
if (sem_init(&sem, 0, 0)) {
loge("Timersync fails: sem_init\n");
return;
}
timerSet(0, timerSyncCbk, (uint64_t)(uintptr_t)&sem);
r_sem_wait(&sem);
sem_destroy(&sem);
}