1 /*
2 * Copyright © 2015 Canonical Limited
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 *
17 * Author: Ryan Lortie <desrt@desrt.ca>
18 */
19
20 #include "config.h"
21
22 #include "gcontextspecificgroup.h"
23
24 #include <glib-object.h>
25 #include "glib-private.h"
26
27 typedef struct
28 {
29 GSource source;
30
31 GMutex lock;
32 gpointer instance;
33 GQueue pending;
34 } GContextSpecificSource;
35
36 static gboolean
g_context_specific_source_dispatch(GSource * source,GSourceFunc callback,gpointer user_data)37 g_context_specific_source_dispatch (GSource *source,
38 GSourceFunc callback,
39 gpointer user_data)
40 {
41 GContextSpecificSource *css = (GContextSpecificSource *) source;
42 guint signal_id;
43
44 g_mutex_lock (&css->lock);
45
46 g_assert (!g_queue_is_empty (&css->pending));
47 signal_id = GPOINTER_TO_UINT (g_queue_pop_head (&css->pending));
48
49 if (g_queue_is_empty (&css->pending))
50 g_source_set_ready_time (source, -1);
51
52 g_mutex_unlock (&css->lock);
53
54 g_signal_emit (css->instance, signal_id, 0);
55
56 return TRUE;
57 }
58
59 static void
g_context_specific_source_finalize(GSource * source)60 g_context_specific_source_finalize (GSource *source)
61 {
62 GContextSpecificSource *css = (GContextSpecificSource *) source;
63
64 g_mutex_clear (&css->lock);
65 g_queue_clear (&css->pending);
66 }
67
68 static GContextSpecificSource *
g_context_specific_source_new(const gchar * name,gpointer instance)69 g_context_specific_source_new (const gchar *name,
70 gpointer instance)
71 {
72 static GSourceFuncs source_funcs = {
73 NULL,
74 NULL,
75 g_context_specific_source_dispatch,
76 g_context_specific_source_finalize
77 };
78 GContextSpecificSource *css;
79 GSource *source;
80
81 source = g_source_new (&source_funcs, sizeof (GContextSpecificSource));
82 css = (GContextSpecificSource *) source;
83
84 g_source_set_name (source, name);
85
86 g_mutex_init (&css->lock);
87 g_queue_init (&css->pending);
88 css->instance = instance;
89
90 return css;
91 }
92
93 static gboolean
g_context_specific_group_change_state(gpointer user_data)94 g_context_specific_group_change_state (gpointer user_data)
95 {
96 GContextSpecificGroup *group = user_data;
97
98 g_mutex_lock (&group->lock);
99
100 if (group->requested_state != group->effective_state)
101 {
102 (* group->requested_func) ();
103
104 group->effective_state = group->requested_state;
105 group->requested_func = NULL;
106
107 g_cond_broadcast (&group->cond);
108 }
109
110 g_mutex_unlock (&group->lock);
111
112 return FALSE;
113 }
114
115 /* this is not the most elegant way to deal with this, but it's probably
116 * the best. there are only two other things we could do, really:
117 *
118 * - run the start function (but not the stop function) from the user's
119 * thread under some sort of lock. we don't run the stop function
120 * from the user's thread to avoid the destroy-while-emitting problem
121 *
122 * - have some check-and-compare functionality similar to what
123 * gsettings does where we send an artificial event in case we notice
124 * a change during the potential race period (using stat, for
125 * example)
126 */
127 static void
g_context_specific_group_request_state(GContextSpecificGroup * group,gboolean requested_state,GCallback requested_func)128 g_context_specific_group_request_state (GContextSpecificGroup *group,
129 gboolean requested_state,
130 GCallback requested_func)
131 {
132 if (requested_state != group->requested_state)
133 {
134 if (group->effective_state != group->requested_state)
135 {
136 /* abort the currently pending state transition */
137 g_assert (group->effective_state == requested_state);
138
139 group->requested_state = requested_state;
140 group->requested_func = NULL;
141 }
142 else
143 {
144 /* start a new state transition */
145 group->requested_state = requested_state;
146 group->requested_func = requested_func;
147
148 g_main_context_invoke (GLIB_PRIVATE_CALL(g_get_worker_context) (),
149 g_context_specific_group_change_state, group);
150 }
151 }
152
153 /* we only block for positive transitions */
154 if (requested_state)
155 {
156 while (group->requested_state != group->effective_state)
157 g_cond_wait (&group->cond, &group->lock);
158
159 /* there is no way this could go back to FALSE because the object
160 * that we just created in this thread would have to have been
161 * destroyed again (from this thread) before that could happen.
162 */
163 g_assert (group->effective_state);
164 }
165 }
166
167 gpointer
g_context_specific_group_get(GContextSpecificGroup * group,GType type,goffset context_offset,GCallback start_func)168 g_context_specific_group_get (GContextSpecificGroup *group,
169 GType type,
170 goffset context_offset,
171 GCallback start_func)
172 {
173 GContextSpecificSource *css;
174 GMainContext *context;
175
176 context = g_main_context_get_thread_default ();
177 if (!context)
178 context = g_main_context_default ();
179
180 g_mutex_lock (&group->lock);
181
182 if (!group->table)
183 group->table = g_hash_table_new (NULL, NULL);
184
185 css = g_hash_table_lookup (group->table, context);
186
187 if (!css)
188 {
189 gpointer instance;
190
191 instance = g_object_new (type, NULL);
192 css = g_context_specific_source_new (g_type_name (type), instance);
193 G_STRUCT_MEMBER (GMainContext *, instance, context_offset) = g_main_context_ref (context);
194 g_source_attach ((GSource *) css, context);
195
196 g_hash_table_insert (group->table, context, css);
197 }
198 else
199 g_object_ref (css->instance);
200
201 if (start_func)
202 g_context_specific_group_request_state (group, TRUE, start_func);
203
204 g_mutex_unlock (&group->lock);
205
206 return css->instance;
207 }
208
209 void
g_context_specific_group_remove(GContextSpecificGroup * group,GMainContext * context,gpointer instance,GCallback stop_func)210 g_context_specific_group_remove (GContextSpecificGroup *group,
211 GMainContext *context,
212 gpointer instance,
213 GCallback stop_func)
214 {
215 GContextSpecificSource *css;
216
217 if (!context)
218 {
219 g_critical ("Removing %s with NULL context. This object was probably directly constructed from a "
220 "dynamic language. This is not a valid use of the API.", G_OBJECT_TYPE_NAME (instance));
221 return;
222 }
223
224 g_mutex_lock (&group->lock);
225 css = g_hash_table_lookup (group->table, context);
226 g_hash_table_remove (group->table, context);
227 g_assert (css);
228
229 /* stop only if we were the last one */
230 if (stop_func && g_hash_table_size (group->table) == 0)
231 g_context_specific_group_request_state (group, FALSE, stop_func);
232
233 g_mutex_unlock (&group->lock);
234
235 g_assert (css->instance == instance);
236
237 g_source_destroy ((GSource *) css);
238 g_source_unref ((GSource *) css);
239 g_main_context_unref (context);
240 }
241
242 void
g_context_specific_group_emit(GContextSpecificGroup * group,guint signal_id)243 g_context_specific_group_emit (GContextSpecificGroup *group,
244 guint signal_id)
245 {
246 g_mutex_lock (&group->lock);
247
248 if (group->table)
249 {
250 GHashTableIter iter;
251 gpointer value;
252 gpointer ptr;
253
254 ptr = GUINT_TO_POINTER (signal_id);
255
256 g_hash_table_iter_init (&iter, group->table);
257 while (g_hash_table_iter_next (&iter, NULL, &value))
258 {
259 GContextSpecificSource *css = value;
260
261 g_mutex_lock (&css->lock);
262
263 g_queue_remove (&css->pending, ptr);
264 g_queue_push_tail (&css->pending, ptr);
265
266 g_source_set_ready_time ((GSource *) css, 0);
267
268 g_mutex_unlock (&css->lock);
269 }
270 }
271
272 g_mutex_unlock (&group->lock);
273 }
274