/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* Copyright (C) 2013 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #include #include #include "red_common.h" #include "spice_timer_queue.h" #include "common/ring.h" static Ring timer_queue_list; static int queue_count = 0; static pthread_mutex_t queue_list_lock = PTHREAD_MUTEX_INITIALIZER; static void spice_timer_queue_init(void) { ring_init(&timer_queue_list); } struct SpiceTimer { RingItem link; RingItem active_link; SpiceTimerFunc func; void *opaque; SpiceTimerQueue *queue; int is_active; uint32_t ms; uint64_t expiry_time; }; struct SpiceTimerQueue { RingItem link; pthread_t thread; Ring timers; Ring active_timers; }; static SpiceTimerQueue *spice_timer_queue_find(void) { pthread_t self = pthread_self(); RingItem *queue_item; RING_FOREACH(queue_item, &timer_queue_list) { SpiceTimerQueue *queue = SPICE_CONTAINEROF(queue_item, SpiceTimerQueue, link); if (pthread_equal(self, queue->thread) != 0) { return queue; } } return NULL; } static SpiceTimerQueue *spice_timer_queue_find_with_lock(void) { SpiceTimerQueue *queue; pthread_mutex_lock(&queue_list_lock); queue = spice_timer_queue_find(); pthread_mutex_unlock(&queue_list_lock); return queue; } int spice_timer_queue_create(void) { SpiceTimerQueue *queue; pthread_mutex_lock(&queue_list_lock); if (queue_count == 0) { spice_timer_queue_init(); } if (spice_timer_queue_find() != NULL) { spice_printerr("timer queue was already created for the thread"); return FALSE; } queue = spice_new0(SpiceTimerQueue, 1); queue->thread = pthread_self(); ring_init(&queue->timers); ring_init(&queue->active_timers); ring_add(&timer_queue_list, &queue->link); queue_count++; pthread_mutex_unlock(&queue_list_lock); return TRUE; } void spice_timer_queue_destroy(void) { RingItem *item; SpiceTimerQueue *queue; pthread_mutex_lock(&queue_list_lock); queue = spice_timer_queue_find(); spice_assert(queue != NULL); while ((item = ring_get_head(&queue->timers))) { SpiceTimer *timer; timer = SPICE_CONTAINEROF(item, SpiceTimer, link); spice_timer_remove(timer); } ring_remove(&queue->link); free(queue); queue_count--; pthread_mutex_unlock(&queue_list_lock); } SpiceTimer *spice_timer_queue_add(SpiceTimerFunc func, void *opaque) { SpiceTimer *timer = spice_new0(SpiceTimer, 1); SpiceTimerQueue *queue = spice_timer_queue_find_with_lock(); spice_assert(queue != NULL); ring_item_init(&timer->link); ring_item_init(&timer->active_link); timer->opaque = opaque; timer->func = func; timer->queue = queue; ring_add(&queue->timers, &timer->link); return timer; } static void _spice_timer_set(SpiceTimer *timer, uint32_t ms, uint64_t now) { RingItem *next_item; SpiceTimerQueue *queue; if (timer->is_active) { spice_timer_cancel(timer); } queue = timer->queue; timer->expiry_time = now + ms; timer->ms = ms; RING_FOREACH(next_item, &queue->active_timers) { SpiceTimer *next_timer = SPICE_CONTAINEROF(next_item, SpiceTimer, active_link); if (timer->expiry_time <= next_timer->expiry_time) { break; } } if (next_item) { ring_add_before(&timer->active_link, next_item); } else { ring_add_before(&timer->active_link, &queue->active_timers); } timer->is_active = TRUE; } void spice_timer_set(SpiceTimer *timer, uint32_t ms) { struct timespec now; spice_assert(pthread_equal(timer->queue->thread, pthread_self()) != 0); clock_gettime(CLOCK_MONOTONIC, &now); _spice_timer_set(timer, ms, (uint64_t)now.tv_sec * 1000 + (now.tv_nsec / 1000 / 1000)); } void spice_timer_cancel(SpiceTimer *timer) { spice_assert(pthread_equal(timer->queue->thread, pthread_self()) != 0); if (!ring_item_is_linked(&timer->active_link)) { spice_assert(!timer->is_active); return; } spice_assert(timer->is_active); ring_remove(&timer->active_link); timer->is_active = FALSE; } void spice_timer_remove(SpiceTimer *timer) { spice_assert(timer->queue); spice_assert(ring_item_is_linked(&timer->link)); spice_assert(pthread_equal(timer->queue->thread, pthread_self()) != 0); if (timer->is_active) { spice_assert(ring_item_is_linked(&timer->active_link)); ring_remove(&timer->active_link); } ring_remove(&timer->link); free(timer); } unsigned int spice_timer_queue_get_timeout_ms(void) { struct timespec now; int64_t now_ms; RingItem *head; SpiceTimer *head_timer; SpiceTimerQueue *queue = spice_timer_queue_find_with_lock(); spice_assert(queue != NULL); if (ring_is_empty(&queue->active_timers)) { return -1; } head = ring_get_head(&queue->active_timers); head_timer = SPICE_CONTAINEROF(head, SpiceTimer, active_link); clock_gettime(CLOCK_MONOTONIC, &now); now_ms = ((int64_t)now.tv_sec * 1000) + (now.tv_nsec / 1000 / 1000); return MAX(0, ((int64_t)head_timer->expiry_time - now_ms)); } void spice_timer_queue_cb(void) { struct timespec now; uint64_t now_ms; RingItem *head; SpiceTimerQueue *queue = spice_timer_queue_find_with_lock(); spice_assert(queue != NULL); if (ring_is_empty(&queue->active_timers)) { return; } clock_gettime(CLOCK_MONOTONIC, &now); now_ms = ((uint64_t)now.tv_sec * 1000) + (now.tv_nsec / 1000 / 1000); while ((head = ring_get_head(&queue->active_timers))) { SpiceTimer *timer = SPICE_CONTAINEROF(head, SpiceTimer, active_link); if (timer->expiry_time > now_ms) { break; } else { /* Remove active timer before calling the timer function. * Timer function could delete the timer making the timer * pointer point to freed data. */ spice_timer_cancel(timer); timer->func(timer->opaque); /* timer could now be invalid ! */ } } }