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->last = now;
 | 
				
			||||||
	t->max = max;
 | 
						t->max = max;
 | 
				
			||||||
 | 
						memset(&t->lock, 0, sizeof(t->lock));
 | 
				
			||||||
	t->events = hub_malloc_zero(max * sizeof(struct timeout_evt*));
 | 
						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;
 | 
						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 timeout_queue_process(struct timeout_queue* t, time_t now)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	size_t pos = (size_t) t->last;
 | 
						size_t pos = (size_t) t->last;
 | 
				
			||||||
	size_t events = 0;
 | 
						size_t events = 0;
 | 
				
			||||||
	struct timeout_evt* evt = 0;
 | 
						struct timeout_evt* evt = 0;
 | 
				
			||||||
	t->last = now;
 | 
						t->last = now;
 | 
				
			||||||
 | 
						timeout_queue_lock(t);
 | 
				
			||||||
	for (; pos <= now; pos++)
 | 
						for (; pos <= now; pos++)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		while ((evt = t->events[pos % t->max]))
 | 
							while ((evt = t->events[pos % t->max]))
 | 
				
			||||||
@ -67,6 +112,7 @@ size_t timeout_queue_process(struct timeout_queue* t, time_t now)
 | 
				
			|||||||
			events++;
 | 
								events++;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						timeout_queue_unlock(t);
 | 
				
			||||||
	return events;
 | 
						return events;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -82,6 +128,61 @@ size_t timeout_queue_get_next_timeout(struct timeout_queue* t, time_t now)
 | 
				
			|||||||
	return seconds;
 | 
						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)
 | 
					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->timestamp = t->last + seconds;
 | 
				
			||||||
	evt->next = 0;
 | 
						evt->next = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (timeout_queue_locked(t))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							timeout_queue_insert_locked(t, evt);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	first = t->events[pos];
 | 
						first = t->events[pos];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (first)
 | 
						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);
 | 
						size_t pos = (evt->timestamp % t->max);
 | 
				
			||||||
	struct timeout_evt* first = t->events[pos];
 | 
						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)
 | 
						if (!first || !evt->prev)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -43,6 +43,7 @@ struct timeout_queue
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	time_t last;
 | 
						time_t last;
 | 
				
			||||||
	size_t max;
 | 
						size_t max;
 | 
				
			||||||
 | 
						struct timeout_evt lock;
 | 
				
			||||||
	struct timeout_evt** events;
 | 
						struct timeout_evt** events;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user