/*
* gnome-keyring
*
* Copyright (C) 2009 Stefan Walter
*
* This program 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 program 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 program; if not, see
* .
*/
#include "config.h"
#include "gkm-timer.h"
#include "egg/egg-error.h"
#include
struct _GkmTimer {
gint64 when;
GMutex *mutex;
gpointer identifier;
GkmTimerFunc callback;
gpointer user_data;
};
static GMutex timer_mutex = { 0, };
static GQueue *timer_queue = NULL;
static GThread *timer_thread = NULL;
static GCond timer_condition;
static GCond *timer_cond = NULL;
static gboolean timer_run = FALSE;
static gint timer_refs = 0;
static gint
compare_timers (gconstpointer a, gconstpointer b, gpointer user_data)
{
const GkmTimer *ta = a;
const GkmTimer *tb = b;
if (ta->when < tb->when)
return -1;
return ta->when > tb->when;
}
static gpointer
timer_thread_func (gpointer unused)
{
GkmTimer *timer;
g_mutex_lock (&timer_mutex);
while (timer_run) {
timer = g_queue_peek_head (timer_queue);
/* Nothing in the queue, wait until we have action */
if (!timer) {
g_cond_wait (timer_cond, &timer_mutex);
continue;
}
if (timer->when) {
gint64 offset = timer->when - g_get_monotonic_time ();
if (offset > 0) {
g_cond_wait_until (timer_cond, &timer_mutex, g_get_monotonic_time () + offset);
continue;
}
}
/* Leave our thread mutex, and enter the module */
g_mutex_unlock (&timer_mutex);
g_mutex_lock (timer->mutex);
if (timer->callback)
(timer->callback) (timer, timer->user_data);
/* Leave the module, and go back into our thread mutex */
g_mutex_unlock (timer->mutex);
g_mutex_lock (&timer_mutex);
/* There's a chance that the timer may no longer be at head of queue */
g_queue_remove (timer_queue, timer);
g_slice_free (GkmTimer, timer);
}
g_mutex_unlock (&timer_mutex);
return NULL;
}
void
gkm_timer_initialize (void)
{
GError *error = NULL;
g_mutex_lock (&timer_mutex);
g_atomic_int_inc (&timer_refs);
if (!timer_thread) {
timer_run = TRUE;
timer_thread = g_thread_new ("timer", timer_thread_func, NULL);
if (timer_thread) {
g_assert (timer_queue == NULL);
timer_queue = g_queue_new ();
g_assert (timer_cond == NULL);
timer_cond = &timer_condition;
g_cond_init (timer_cond);
} else {
g_warning ("could not create timer thread: %s",
egg_error_message (error));
}
}
g_mutex_unlock (&timer_mutex);
}
void
gkm_timer_shutdown (void)
{
GkmTimer *timer;
if (g_atomic_int_dec_and_test (&timer_refs)) {
g_mutex_lock (&timer_mutex);
timer_run = FALSE;
g_assert (timer_cond);
g_cond_broadcast (timer_cond);
g_mutex_unlock (&timer_mutex);
g_assert (timer_thread);
g_thread_join (timer_thread);
timer_thread = NULL;
g_assert (timer_queue);
/* Cleanup any outstanding timers */
while (!g_queue_is_empty (timer_queue)) {
timer = g_queue_pop_head (timer_queue);
g_slice_free (GkmTimer, timer);
}
g_queue_free (timer_queue);
timer_queue = NULL;
g_cond_clear (timer_cond);
timer_cond = NULL;
}
}
/* We're the only caller of this function */
GMutex* _gkm_module_get_scary_mutex_that_you_should_not_touch (GkmModule *self);
GkmTimer*
gkm_timer_start (GkmModule *module, glong seconds, GkmTimerFunc callback, gpointer user_data)
{
GkmTimer *timer;
g_return_val_if_fail (callback, NULL);
g_return_val_if_fail (timer_queue, NULL);
timer = g_slice_new (GkmTimer);
timer->when = g_get_monotonic_time () + seconds * G_TIME_SPAN_SECOND;
timer->callback = callback;
timer->user_data = user_data;
timer->mutex = _gkm_module_get_scary_mutex_that_you_should_not_touch (module);
g_return_val_if_fail (timer->mutex, NULL);
g_mutex_lock (&timer_mutex);
g_assert (timer_queue);
g_queue_insert_sorted (timer_queue, timer, compare_timers, NULL);
g_assert (timer_cond);
g_cond_broadcast (timer_cond);
g_mutex_unlock (&timer_mutex);
/*
* Note that the timer thread could not already completed this timer.
* This is because we're in the module, and in order to complete a timer
* the timer thread must enter the module mutex.
*/
return timer;
}
void
gkm_timer_cancel (GkmTimer *timer)
{
GList *link;
g_return_if_fail (timer_queue);
g_mutex_lock (&timer_mutex);
g_assert (timer_queue);
link = g_queue_find (timer_queue, timer);
if (link) {
/*
* For thread safety the timer struct must be freed
* from the timer thread. So to cancel, what we do
* is move the timer to the front of the queue,
* and reset the callback and when.
*/
timer->when = 0;
timer->callback = NULL;
g_queue_delete_link (timer_queue, link);
g_queue_push_head (timer_queue, timer);
g_assert (timer_cond);
g_cond_broadcast (timer_cond);
}
g_mutex_unlock (&timer_mutex);
}