Line data Source code
1 : /*
2 : * gnome-keyring
3 : *
4 : * Copyright (C) 2009 Stefan Walter
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU Lesser General Public License as
8 : * published by the Free Software Foundation; either version 2.1 of
9 : * the License, or (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful, but
12 : * WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : * Lesser General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU Lesser General Public
17 : * License along with this program; if not, see
18 : * <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include "config.h"
22 :
23 : #include "gkm-timer.h"
24 :
25 : #include "egg/egg-error.h"
26 :
27 : #include <glib.h>
28 :
29 : struct _GkmTimer {
30 : gint64 when;
31 : GMutex *mutex;
32 : gpointer identifier;
33 : GkmTimerFunc callback;
34 : gpointer user_data;
35 : };
36 :
37 : static GMutex timer_mutex = { 0, };
38 : static GQueue *timer_queue = NULL;
39 : static GThread *timer_thread = NULL;
40 : static GCond timer_condition;
41 : static GCond *timer_cond = NULL;
42 : static gboolean timer_run = FALSE;
43 : static gint timer_refs = 0;
44 :
45 : static gint
46 6 : compare_timers (gconstpointer a, gconstpointer b, gpointer user_data)
47 : {
48 6 : const GkmTimer *ta = a;
49 6 : const GkmTimer *tb = b;
50 6 : if (ta->when < tb->when)
51 3 : return -1;
52 3 : return ta->when > tb->when;
53 : }
54 :
55 : static gpointer
56 206 : timer_thread_func (gpointer unused)
57 : {
58 : GkmTimer *timer;
59 :
60 206 : g_mutex_lock (&timer_mutex);
61 :
62 421 : while (timer_run) {
63 215 : timer = g_queue_peek_head (timer_queue);
64 :
65 : /* Nothing in the queue, wait until we have action */
66 215 : if (!timer) {
67 201 : g_cond_wait (timer_cond, &timer_mutex);
68 201 : continue;
69 : }
70 :
71 14 : if (timer->when) {
72 13 : gint64 offset = timer->when - g_get_monotonic_time ();
73 13 : if (offset > 0) {
74 6 : g_cond_wait_until (timer_cond, &timer_mutex, g_get_monotonic_time () + offset);
75 6 : continue;
76 : }
77 : }
78 :
79 : /* Leave our thread mutex, and enter the module */
80 8 : g_mutex_unlock (&timer_mutex);
81 8 : g_mutex_lock (timer->mutex);
82 :
83 8 : if (timer->callback)
84 7 : (timer->callback) (timer, timer->user_data);
85 :
86 : /* Leave the module, and go back into our thread mutex */
87 8 : g_mutex_unlock (timer->mutex);
88 8 : g_mutex_lock (&timer_mutex);
89 :
90 : /* There's a chance that the timer may no longer be at head of queue */
91 8 : g_queue_remove (timer_queue, timer);
92 8 : g_slice_free (GkmTimer, timer);
93 : }
94 :
95 206 : g_mutex_unlock (&timer_mutex);
96 206 : return NULL;
97 : }
98 :
99 : void
100 288 : gkm_timer_initialize (void)
101 : {
102 288 : GError *error = NULL;
103 288 : g_mutex_lock (&timer_mutex);
104 :
105 288 : g_atomic_int_inc (&timer_refs);
106 288 : if (!timer_thread) {
107 206 : timer_run = TRUE;
108 206 : timer_thread = g_thread_new ("timer", timer_thread_func, NULL);
109 206 : if (timer_thread) {
110 206 : g_assert (timer_queue == NULL);
111 206 : timer_queue = g_queue_new ();
112 :
113 206 : g_assert (timer_cond == NULL);
114 206 : timer_cond = &timer_condition;
115 206 : g_cond_init (timer_cond);
116 : } else {
117 0 : g_warning ("could not create timer thread: %s",
118 : egg_error_message (error));
119 : }
120 : }
121 :
122 288 : g_mutex_unlock (&timer_mutex);
123 288 : }
124 :
125 : void
126 288 : gkm_timer_shutdown (void)
127 : {
128 : GkmTimer *timer;
129 :
130 288 : if (g_atomic_int_dec_and_test (&timer_refs)) {
131 :
132 206 : g_mutex_lock (&timer_mutex);
133 :
134 206 : timer_run = FALSE;
135 206 : g_assert (timer_cond);
136 206 : g_cond_broadcast (timer_cond);
137 :
138 206 : g_mutex_unlock (&timer_mutex);
139 :
140 206 : g_assert (timer_thread);
141 206 : g_thread_join (timer_thread);
142 206 : timer_thread = NULL;
143 :
144 206 : g_assert (timer_queue);
145 :
146 : /* Cleanup any outstanding timers */
147 209 : while (!g_queue_is_empty (timer_queue)) {
148 3 : timer = g_queue_pop_head (timer_queue);
149 3 : g_slice_free (GkmTimer, timer);
150 : }
151 :
152 206 : g_queue_free (timer_queue);
153 206 : timer_queue = NULL;
154 :
155 206 : g_cond_clear (timer_cond);
156 206 : timer_cond = NULL;
157 : }
158 288 : }
159 :
160 : /* We're the only caller of this function */
161 : GMutex* _gkm_module_get_scary_mutex_that_you_should_not_touch (GkmModule *self);
162 :
163 : GkmTimer*
164 11 : gkm_timer_start (GkmModule *module, glong seconds, GkmTimerFunc callback, gpointer user_data)
165 : {
166 : GkmTimer *timer;
167 :
168 11 : g_return_val_if_fail (callback, NULL);
169 11 : g_return_val_if_fail (timer_queue, NULL);
170 :
171 11 : timer = g_slice_new (GkmTimer);
172 11 : timer->when = g_get_monotonic_time () + seconds * G_TIME_SPAN_SECOND;
173 11 : timer->callback = callback;
174 11 : timer->user_data = user_data;
175 :
176 11 : timer->mutex = _gkm_module_get_scary_mutex_that_you_should_not_touch (module);
177 11 : g_return_val_if_fail (timer->mutex, NULL);
178 :
179 11 : g_mutex_lock (&timer_mutex);
180 :
181 11 : g_assert (timer_queue);
182 11 : g_queue_insert_sorted (timer_queue, timer, compare_timers, NULL);
183 11 : g_assert (timer_cond);
184 11 : g_cond_broadcast (timer_cond);
185 :
186 11 : g_mutex_unlock (&timer_mutex);
187 :
188 : /*
189 : * Note that the timer thread could not already completed this timer.
190 : * This is because we're in the module, and in order to complete a timer
191 : * the timer thread must enter the module mutex.
192 : */
193 :
194 11 : return timer;
195 : }
196 :
197 : void
198 1 : gkm_timer_cancel (GkmTimer *timer)
199 : {
200 : GList *link;
201 :
202 1 : g_return_if_fail (timer_queue);
203 :
204 1 : g_mutex_lock (&timer_mutex);
205 :
206 1 : g_assert (timer_queue);
207 :
208 1 : link = g_queue_find (timer_queue, timer);
209 1 : if (link) {
210 :
211 : /*
212 : * For thread safety the timer struct must be freed
213 : * from the timer thread. So to cancel, what we do
214 : * is move the timer to the front of the queue,
215 : * and reset the callback and when.
216 : */
217 :
218 1 : timer->when = 0;
219 1 : timer->callback = NULL;
220 :
221 1 : g_queue_delete_link (timer_queue, link);
222 1 : g_queue_push_head (timer_queue, timer);
223 :
224 1 : g_assert (timer_cond);
225 1 : g_cond_broadcast (timer_cond);
226 : }
227 :
228 1 : g_mutex_unlock (&timer_mutex);
229 : }
|