Fix bug #198 - Timers could cause infinite loops
This could essentially happen due to time drift, high load, or the process being put in sleep for a while. The reason is that recurring timers could be added to the same time slot as the timeslot being handled.
This commit is contained in:
parent
d73d213bc4
commit
550740f715
|
@ -42,6 +42,7 @@ void timeout_queue_initialize(struct timeout_queue* t, time_t now, size_t max)
|
|||
{
|
||||
t->last = now;
|
||||
t->max = max;
|
||||
memset(&t->lock, 0, sizeof(t->lock));
|
||||
t->events = hub_malloc_zero(max * sizeof(struct timeout_evt*));
|
||||
}
|
||||
|
||||
|
@ -52,12 +53,56 @@ void timeout_queue_shutdown(struct timeout_queue* t)
|
|||
t->max = 0;
|
||||
}
|
||||
|
||||
static int timeout_queue_locked(struct timeout_queue* t)
|
||||
{
|
||||
return t->lock.ptr != NULL;
|
||||
}
|
||||
|
||||
static int timeout_queue_lock(struct timeout_queue* t)
|
||||
{
|
||||
t->lock.ptr = t;
|
||||
}
|
||||
|
||||
// unlock and flush the locked events to the main timeout queue.
|
||||
static int timeout_queue_unlock(struct timeout_queue* t)
|
||||
{
|
||||
struct timeout_evt* evt, *tmp, *first;
|
||||
size_t pos;
|
||||
t->lock.ptr = NULL;
|
||||
|
||||
evt = t->lock.next;
|
||||
while (evt)
|
||||
{
|
||||
tmp = evt->next;
|
||||
pos = evt->timestamp % t->max;
|
||||
first = t->events[pos];
|
||||
if (first)
|
||||
{
|
||||
first->prev->next = evt;
|
||||
evt->prev = first->prev;
|
||||
first->prev = evt;
|
||||
}
|
||||
else
|
||||
{
|
||||
t->events[pos] = evt;
|
||||
evt->prev = evt;
|
||||
}
|
||||
evt->next = 0;
|
||||
evt = tmp;
|
||||
}
|
||||
|
||||
t->lock.next = 0;
|
||||
t->lock.prev = 0;
|
||||
}
|
||||
|
||||
|
||||
size_t timeout_queue_process(struct timeout_queue* t, time_t now)
|
||||
{
|
||||
size_t pos = (size_t) t->last;
|
||||
size_t events = 0;
|
||||
struct timeout_evt* evt = 0;
|
||||
t->last = now;
|
||||
timeout_queue_lock(t);
|
||||
for (; pos <= now; pos++)
|
||||
{
|
||||
while ((evt = t->events[pos % t->max]))
|
||||
|
@ -67,6 +112,7 @@ size_t timeout_queue_process(struct timeout_queue* t, time_t now)
|
|||
events++;
|
||||
}
|
||||
}
|
||||
timeout_queue_unlock(t);
|
||||
return events;
|
||||
}
|
||||
|
||||
|
@ -82,6 +128,61 @@ size_t timeout_queue_get_next_timeout(struct timeout_queue* t, time_t now)
|
|||
return seconds;
|
||||
}
|
||||
|
||||
static void timeout_queue_insert_locked(struct timeout_queue* t, struct timeout_evt* evt)
|
||||
{
|
||||
/* All events point back to the sentinel.
|
||||
* this means the event is considered schedule (see timeout_evt_is_scheduled),
|
||||
* and it is easy to tell if the event is in the wait queue or not.
|
||||
*/
|
||||
evt->prev = &t->lock;
|
||||
evt->next = NULL;
|
||||
|
||||
// The sentinel next points to the first event in the locked queue
|
||||
// The sentinel prev points to the last evetnt in the locked queue.
|
||||
// NOTE: if prev is != NULL then next also must be != NULL.
|
||||
if (t->lock.prev)
|
||||
{
|
||||
t->lock.prev->next = evt;
|
||||
t->lock.prev = evt;
|
||||
}
|
||||
else
|
||||
{
|
||||
t->lock.next = evt;
|
||||
t->lock.prev = evt;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void timeout_queue_remove_locked(struct timeout_queue* t, struct timeout_evt* evt)
|
||||
{
|
||||
uhub_assert(evt->prev == &t->lock);
|
||||
if (t->lock.next == evt)
|
||||
{
|
||||
t->lock.next = evt->next;
|
||||
if (t->lock.prev == evt)
|
||||
t->lock.prev = evt->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct timeout_evt *prev, *it;
|
||||
prev = 0;
|
||||
it = t->lock.next;
|
||||
while (it)
|
||||
{
|
||||
prev = it;
|
||||
it = it->next;
|
||||
if (it == evt)
|
||||
{
|
||||
prev->next = it->next;
|
||||
if (!prev->next)
|
||||
t->lock.prev = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
timeout_evt_reset(evt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void timeout_queue_insert(struct timeout_queue* t, struct timeout_evt* evt, size_t seconds)
|
||||
{
|
||||
|
@ -90,6 +191,12 @@ void timeout_queue_insert(struct timeout_queue* t, struct timeout_evt* evt, size
|
|||
evt->timestamp = t->last + seconds;
|
||||
evt->next = 0;
|
||||
|
||||
if (timeout_queue_locked(t))
|
||||
{
|
||||
timeout_queue_insert_locked(t, evt);
|
||||
return;
|
||||
}
|
||||
|
||||
first = t->events[pos];
|
||||
|
||||
if (first)
|
||||
|
@ -112,6 +219,13 @@ void timeout_queue_remove(struct timeout_queue* t, struct timeout_evt* evt)
|
|||
size_t pos = (evt->timestamp % t->max);
|
||||
struct timeout_evt* first = t->events[pos];
|
||||
|
||||
// Removing a locked event
|
||||
if (evt->prev == &t->lock)
|
||||
{
|
||||
timeout_queue_remove_locked(t, evt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!first || !evt->prev)
|
||||
return;
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ struct timeout_queue
|
|||
{
|
||||
time_t last;
|
||||
size_t max;
|
||||
struct timeout_evt lock;
|
||||
struct timeout_evt** events;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue