#include <sys/time.h>
#include <stdlib.h>
#include <time.h>
#include <isc/heap.h>
#include <isc/task.h>
#include <isc/timer.h>
#include <isc/util.h>
#include "timer_p.h"
struct isc_timer {
isc_timermgr_t * manager;
unsigned int references;
struct timespec idle;
struct timespec interval;
isc_task_t * task;
isc_taskaction_t action;
void * arg;
unsigned int index;
struct timespec due;
LINK(isc_timer_t) link;
};
struct isc_timermgr {
int done;
LIST(isc_timer_t) timers;
unsigned int nscheduled;
struct timespec due;
unsigned int refs;
isc_heap_t * heap;
};
static isc_timermgr_t *timermgr = NULL;
static inline isc_result_t
schedule(isc_timer_t *timer) {
isc_result_t result;
isc_timermgr_t *manager;
struct timespec due;
manager = timer->manager;
due = timer->idle;
if (timer->index > 0) {
if (timespeccmp(&due, &timer->due, <))
isc_heap_increased(manager->heap, timer->index);
else if (timespeccmp(&due, &timer->due, >))
isc_heap_decreased(manager->heap, timer->index);
timer->due = due;
} else {
timer->due = due;
result = isc_heap_insert(manager->heap, timer);
if (result != ISC_R_SUCCESS) {
INSIST(result == ISC_R_NOMEMORY);
return (ISC_R_NOMEMORY);
}
manager->nscheduled++;
}
if (timer->index == 1 && timespeccmp(&timer->due, &manager->due, <))
manager->due = timer->due;
return (ISC_R_SUCCESS);
}
static inline void
deschedule(isc_timer_t *timer) {
isc_timermgr_t *manager;
manager = timer->manager;
if (timer->index > 0) {
isc_heap_delete(manager->heap, timer->index);
timer->index = 0;
INSIST(manager->nscheduled > 0);
manager->nscheduled--;
}
}
static void
destroy(isc_timer_t *timer) {
isc_timermgr_t *manager = timer->manager;
(void)isc_task_purgerange(timer->task,
timer,
ISC_TIMEREVENT_FIRSTEVENT,
ISC_TIMEREVENT_LASTEVENT,
NULL);
deschedule(timer);
UNLINK(manager->timers, timer, link);
isc_task_detach(&timer->task);
free(timer);
}
isc_result_t
isc_timer_create(isc_timermgr_t *manager0, const struct timespec *interval,
isc_task_t *task, isc_taskaction_t action, void *arg,
isc_timer_t **timerp)
{
isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
isc_timer_t *timer;
isc_result_t result;
struct timespec now;
REQUIRE(task != NULL);
REQUIRE(action != NULL);
REQUIRE(interval != NULL);
REQUIRE(timespecisset(interval));
REQUIRE(timerp != NULL && *timerp == NULL);
clock_gettime(CLOCK_MONOTONIC, &now);
timer = malloc(sizeof(*timer));
if (timer == NULL)
return (ISC_R_NOMEMORY);
timer->manager = manager;
timer->references = 1;
if (timespecisset(interval))
timespecadd(&now, interval, &timer->idle);
timer->interval = *interval;
timer->task = NULL;
isc_task_attach(task, &timer->task);
timer->action = action;
DE_CONST(arg, timer->arg);
timer->index = 0;
ISC_LINK_INIT(timer, link);
result = schedule(timer);
if (result == ISC_R_SUCCESS)
APPEND(manager->timers, timer, link);
if (result != ISC_R_SUCCESS) {
isc_task_detach(&timer->task);
free(timer);
return (result);
}
*timerp = (isc_timer_t *)timer;
return (ISC_R_SUCCESS);
}
isc_result_t
isc_timer_reset(isc_timer_t *timer, const struct timespec *interval,
int purge)
{
struct timespec now;
isc_result_t result;
REQUIRE(interval != NULL);
REQUIRE(timespecisset(interval));
clock_gettime(CLOCK_MONOTONIC, &now);
if (purge)
(void)isc_task_purgerange(timer->task,
timer,
ISC_TIMEREVENT_FIRSTEVENT,
ISC_TIMEREVENT_LASTEVENT,
NULL);
timer->interval = *interval;
if (timespecisset(interval)) {
timespecadd(&now, interval, &timer->idle);
} else {
timespecclear(&timer->idle);
}
result = schedule(timer);
return (result);
}
void
isc_timer_touch(isc_timer_t *timer) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
timespecadd(&now, &timer->interval, &timer->idle);
}
void
isc_timer_detach(isc_timer_t **timerp) {
isc_timer_t *timer;
int free_timer = 0;
REQUIRE(timerp != NULL);
timer = (isc_timer_t *)*timerp;
REQUIRE(timer->references > 0);
timer->references--;
if (timer->references == 0)
free_timer = 1;
if (free_timer)
destroy(timer);
*timerp = NULL;
}
static void
dispatch(isc_timermgr_t *manager, struct timespec *now) {
int done = 0, post_event, need_schedule;
isc_timerevent_t *event;
isc_eventtype_t type = 0;
isc_timer_t *timer;
isc_result_t result;
int idle;
while (manager->nscheduled > 0 && !done) {
timer = isc_heap_element(manager->heap, 1);
INSIST(timer != NULL);
if (timespeccmp(now, &timer->due, >=)) {
idle = 0;
if (timespecisset(&timer->idle) && timespeccmp(now,
&timer->idle, >=)) {
idle = 1;
}
if (idle) {
type = ISC_TIMEREVENT_IDLE;
post_event = 1;
need_schedule = 0;
} else {
post_event = 0;
need_schedule = 1;
}
if (post_event) {
event = (isc_timerevent_t *)isc_event_allocate(
timer,
type,
timer->action,
timer->arg,
sizeof(*event));
if (event != NULL) {
event->due = timer->due;
isc_task_send(timer->task,
ISC_EVENT_PTR(&event));
} else
UNEXPECTED_ERROR(__FILE__, __LINE__, "%s",
"couldn't allocate event");
}
timer->index = 0;
isc_heap_delete(manager->heap, 1);
manager->nscheduled--;
if (need_schedule) {
result = schedule(timer);
if (result != ISC_R_SUCCESS)
UNEXPECTED_ERROR(__FILE__, __LINE__,
"%s: %u",
"couldn't schedule timer",
result);
}
} else {
manager->due = timer->due;
done = 1;
}
}
}
static int
sooner(void *v1, void *v2) {
isc_timer_t *t1, *t2;
t1 = v1;
t2 = v2;
if (timespeccmp(&t1->due, &t2->due, <))
return (1);
return (0);
}
static void
set_index(void *what, unsigned int index) {
isc_timer_t *timer;
timer = what;
timer->index = index;
}
isc_result_t
isc_timermgr_create(isc_timermgr_t **managerp) {
isc_timermgr_t *manager;
isc_result_t result;
REQUIRE(managerp != NULL && *managerp == NULL);
if (timermgr != NULL) {
timermgr->refs++;
*managerp = (isc_timermgr_t *)timermgr;
return (ISC_R_SUCCESS);
}
manager = malloc(sizeof(*manager));
if (manager == NULL)
return (ISC_R_NOMEMORY);
manager->done = 0;
INIT_LIST(manager->timers);
manager->nscheduled = 0;
timespecclear(&manager->due);
manager->heap = NULL;
result = isc_heap_create(sooner, set_index, 0, &manager->heap);
if (result != ISC_R_SUCCESS) {
INSIST(result == ISC_R_NOMEMORY);
free(manager);
return (ISC_R_NOMEMORY);
}
manager->refs = 1;
timermgr = manager;
*managerp = (isc_timermgr_t *)manager;
return (ISC_R_SUCCESS);
}
void
isc_timermgr_destroy(isc_timermgr_t **managerp) {
isc_timermgr_t *manager;
REQUIRE(managerp != NULL);
manager = (isc_timermgr_t *)*managerp;
manager->refs--;
if (manager->refs > 0) {
*managerp = NULL;
return;
}
timermgr = NULL;
isc_timermgr_dispatch((isc_timermgr_t *)manager);
REQUIRE(EMPTY(manager->timers));
manager->done = 1;
isc_heap_destroy(&manager->heap);
free(manager);
*managerp = NULL;
timermgr = NULL;
}
isc_result_t
isc_timermgr_nextevent(isc_timermgr_t *manager0, struct timespec *when) {
isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
if (manager == NULL)
manager = timermgr;
if (manager == NULL || manager->nscheduled == 0)
return (ISC_R_NOTFOUND);
*when = manager->due;
return (ISC_R_SUCCESS);
}
void
isc_timermgr_dispatch(isc_timermgr_t *manager0) {
isc_timermgr_t *manager = (isc_timermgr_t *)manager0;
struct timespec now;
if (manager == NULL)
manager = timermgr;
if (manager == NULL)
return;
clock_gettime(CLOCK_MONOTONIC, &now);
dispatch(manager, &now);
}