/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
/*
* Timer operations are batched in the changes array to improve instruction
* and data cache locality of rbtree operations.
*
* nxt_timer_add() adds or modify a timer.
*
* nxt_timer_disable() disables a timer.
*
* nxt_timer_delete() deletes a timer. It returns 1 if there are pending
* changes in the changes array or 0 otherwise.
*/
static intptr_t nxt_timer_rbtree_compare(nxt_rbtree_node_t *node1,
nxt_rbtree_node_t *node2);
static void nxt_timer_change(nxt_event_engine_t *engine, nxt_timer_t *timer,
nxt_timer_operation_t change, nxt_msec_t time);
static void nxt_timer_changes_commit(nxt_event_engine_t *engine);
static void nxt_timer_handler(nxt_task_t *task, void *obj, void *data);
nxt_int_t
nxt_timers_init(nxt_timers_t *timers, nxt_uint_t mchanges)
{
nxt_rbtree_init(&timers->tree, nxt_timer_rbtree_compare);
timers->mchanges = mchanges;
timers->changes = nxt_malloc(sizeof(nxt_timer_change_t) * mchanges);
if (nxt_fast_path(timers->changes != NULL)) {
return NXT_OK;
}
return NXT_ERROR;
}
static intptr_t
nxt_timer_rbtree_compare(nxt_rbtree_node_t *node1, nxt_rbtree_node_t *node2)
{
nxt_timer_t *timer1, *timer2;
timer1 = (nxt_timer_t *) node1;
timer2 = (nxt_timer_t *) node2;
/*
* Timer values are distributed in small range, usually several minutes
* and overflow every 49 days if nxt_msec_t is stored in 32 bits.
* This signed comparison takes into account that overflow.
*/
/* timer1->time < timer2->time */
return nxt_msec_diff(timer1->time , timer2->time);
}
void
nxt_timer_add(nxt_event_engine_t *engine, nxt_timer_t *timer,
nxt_msec_t timeout)
{
int32_t diff;
uint32_t time;
time = engine->timers.now + timeout;
if (timer->state != NXT_TIMER_CHANGING) {
if (nxt_timer_is_in_tree(timer)) {
diff = nxt_msec_diff(time, timer->time);
/*
* Use the previous timer if difference between it and the
* new timer is less than required precision milliseconds: this
* decreases number of rbtree operations for fast connections.
*/
if (nxt_abs(diff) < timer->precision) {
nxt_debug(timer->task, "timer previous: %M:%d",
time, timer->state);
timer->state = NXT_TIMER_WAITING;
return;
}
}
}
nxt_debug(timer->task, "timer add: %M:%d %M:%M",
timer->time, timer->state, timeout, time);
nxt_timer_change(engine, timer, NXT_TIMER_ADD, time);
}
void
nxt_timer_disable(nxt_event_engine_t *engine, nxt_timer_t *timer)
{
nxt_debug(timer->task, "timer disable: %M:%d", timer->time, timer->state);
if (timer->state != NXT_TIMER_CHANGING) {
timer->state = NXT_TIMER_DISABLED;
} else {
nxt_timer_change(engine, timer, NXT_TIMER_DISABLE, 0);
}
}
nxt_bool_t
nxt_timer_delete(nxt_event_engine_t *engine, nxt_timer_t *timer)
{
nxt_bool_t pending;
if (nxt_timer_is_in_tree(timer) || timer->state == NXT_TIMER_CHANGING) {
nxt_debug(timer->task, "timer delete: %M:%d",
timer->time, timer->state);
nxt_timer_change(engine, timer, NXT_TIMER_DELETE, 0);
return 1;
}
pending = (timer->state == NXT_TIMER_ENQUEUED);
timer->state = NXT_TIMER_DISABLED;
return pending;
}
static void
nxt_timer_change(nxt_event_engine_t *engine, nxt_timer_t *timer,
nxt_timer_operation_t change, nxt_msec_t time)
{
nxt_timers_t *timers;
nxt_timer_change_t *ch;
timers = &engine->timers;
if (timers->nchanges >= timers->mchanges) {
nxt_timer_changes_commit(engine);
}
nxt_debug(timer->task, "timer change: %M:%d", time, change);
timer->state = NXT_TIMER_CHANGING;
ch = &timers->changes[timers->nchanges];
ch->change = change;
ch->time = time;
ch->timer = timer;
timers->nchanges++;
}
static void
nxt_timer_changes_commit(nxt_event_engine_t *engine)
{
int32_t diff;
nxt_timer_t *timer;
nxt_timers_t *timers;
nxt_timer_state_t state;
nxt_timer_change_t *ch, *end;
timers = &engine->timers;
nxt_debug(&engine->task, "timers changes: %ui", timers->nchanges);
ch = timers->changes;
end = ch + timers->nchanges;
while (ch < end) {
state = NXT_TIMER_DISABLED;
timer = ch->timer;
switch (ch->change) {
case NXT_TIMER_ADD:
if (nxt_timer_is_in_tree(timer)) {
diff = nxt_msec_diff(ch->time, timer->time);
/* See the comment in nxt_timer_add(). */
if (nxt_abs(diff) < timer->precision) {
nxt_debug(timer->task, "timer rbtree previous: %M:%d",
ch->time, timer->state);
state = NXT_TIMER_WAITING;
break;
}
nxt_debug(timer->task, "timer rbtree delete: %M:%d",
timer->time, timer->state);
nxt_rbtree_delete(&timers->tree, &timer->node);
}
timer->time = ch->time;
nxt_debug(timer->task, "timer rbtree insert: %M", timer->time);
nxt_rbtree_insert(&timers->tree, &timer->node);
nxt_timer_in_tree_set(timer);
state = NXT_TIMER_WAITING;
break;
case NXT_TIMER_DELETE:
if (nxt_timer_is_in_tree(timer)) {
nxt_debug(timer->task, "timer rbtree delete: %M:%d",
timer->time, timer->state);
nxt_rbtree_delete(&timers->tree, &timer->node);
}
break;
case NXT_TIMER_DISABLE:
break;
}
timer->state = state;
ch++;
}
timers->nchanges = 0;
}
nxt_msec_t
nxt_timer_find(nxt_event_engine_t *engine)
{
int32_t time;
nxt_timer_t *timer;
nxt_timers_t *timers;
nxt_rbtree_t *tree;
nxt_rbtree_node_t *node, *next;
timers = &engine->timers;
if (timers->nchanges != 0) {
nxt_timer_changes_commit(engine);
}
tree = &timers->tree;
for (node = nxt_rbtree_min(tree);
nxt_rbtree_is_there_successor(tree, node);
node = next)
{
next = nxt_rbtree_node_successor(tree, node);
timer = (nxt_timer_t *) node;
/*
* Disabled timers are not deleted here since the minimum active
* timer may be larger than a disabled timer, but event poll may
* return much earlier and the disabled timer can be reactivated.
*/
if (timer->state != NXT_TIMER_DISABLED) {
time = timer->time;
timers->minimum = time;
nxt_debug(timer->task, "timer found minimum: %M:%M",
time, timers->now);
time = nxt_msec_diff(time, timers->now);
return (nxt_msec_t) nxt_max(time, 0);
}
}
/* Set minimum time one day ahead. */
timers->minimum = timers->now + 24 * 60 * 60 * 1000;
return NXT_INFINITE_MSEC;
}
void
nxt_timer_expire(nxt_event_engine_t *engine, nxt_msec_t now)
{
nxt_timer_t *timer;
nxt_timers_t *timers;
nxt_rbtree_t *tree;
nxt_rbtree_node_t *node, *next;
timers = &engine->timers;
timers->now = now;
nxt_debug(&engine->task, "timer expire minimum: %M:%M",
timers->minimum, now);
/* timers->minimum > now */
if (nxt_msec_diff(timers->minimum , now) > 0) {
return;
}
tree = &timers->tree;
for (node = nxt_rbtree_min(tree);
nxt_rbtree_is_there_successor(tree, node);
node = next)
{
timer = (nxt_timer_t *) node;
/* timer->time > now */
if (nxt_msec_diff(timer->time , now) > 0) {
return;
}
next = nxt_rbtree_node_successor(tree, node);
nxt_debug(timer->task, "timer expire delete: %M:%d",
timer->time, timer->state);
nxt_rbtree_delete(tree, &timer->node);
nxt_timer_in_tree_clear(timer);
if (timer->state != NXT_TIMER_DISABLED) {
timer->state = NXT_TIMER_ENQUEUED;
nxt_work_queue_add(timer->work_queue, nxt_timer_handler,
timer->task, timer, NULL);
}
}
}
static void
nxt_timer_handler(nxt_task_t *task, void *obj, void *data)
{
nxt_timer_t *timer;
timer = obj;
if (timer->state == NXT_TIMER_ENQUEUED) {
timer->state = NXT_TIMER_DISABLED;
timer->handler(task, timer, NULL);
}
}