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
27typedef struct
28{
29 GSource source;
30
31 GMutex lock;
32 gpointer instance;
33 GQueue pending;
34} GContextSpecificSource;
35
36static gboolean
37g_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 (mutex: &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 (queue: &css->pending))
50 g_source_set_ready_time (source, ready_time: -1);
51
52 g_mutex_unlock (mutex: &css->lock);
53
54 g_signal_emit (instance: css->instance, signal_id, detail: 0);
55
56 return TRUE;
57}
58
59static void
60g_context_specific_source_finalize (GSource *source)
61{
62 GContextSpecificSource *css = (GContextSpecificSource *) source;
63
64 g_mutex_clear (mutex: &css->lock);
65 g_queue_clear (queue: &css->pending);
66}
67
68static GContextSpecificSource *
69g_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 NULL, NULL
78 };
79 GContextSpecificSource *css;
80 GSource *source;
81
82 source = g_source_new (source_funcs: &source_funcs, struct_size: sizeof (GContextSpecificSource));
83 css = (GContextSpecificSource *) source;
84
85 g_source_set_name (source, name);
86
87 g_mutex_init (mutex: &css->lock);
88 g_queue_init (queue: &css->pending);
89 css->instance = instance;
90
91 return css;
92}
93
94static gboolean
95g_context_specific_group_change_state (gpointer user_data)
96{
97 GContextSpecificGroup *group = user_data;
98
99 g_mutex_lock (mutex: &group->lock);
100
101 if (group->requested_state != group->effective_state)
102 {
103 (* group->requested_func) ();
104
105 group->effective_state = group->requested_state;
106 group->requested_func = NULL;
107
108 g_cond_broadcast (cond: &group->cond);
109 }
110
111 g_mutex_unlock (mutex: &group->lock);
112
113 return FALSE;
114}
115
116/* this is not the most elegant way to deal with this, but it's probably
117 * the best. there are only two other things we could do, really:
118 *
119 * - run the start function (but not the stop function) from the user's
120 * thread under some sort of lock. we don't run the stop function
121 * from the user's thread to avoid the destroy-while-emitting problem
122 *
123 * - have some check-and-compare functionality similar to what
124 * gsettings does where we send an artificial event in case we notice
125 * a change during the potential race period (using stat, for
126 * example)
127 */
128static void
129g_context_specific_group_request_state (GContextSpecificGroup *group,
130 gboolean requested_state,
131 GCallback requested_func)
132{
133 if (requested_state != group->requested_state)
134 {
135 if (group->effective_state != group->requested_state)
136 {
137 /* abort the currently pending state transition */
138 g_assert (group->effective_state == requested_state);
139
140 group->requested_state = requested_state;
141 group->requested_func = NULL;
142 }
143 else
144 {
145 /* start a new state transition */
146 group->requested_state = requested_state;
147 group->requested_func = requested_func;
148
149 g_main_context_invoke (GLIB_PRIVATE_CALL(g_get_worker_context) (),
150 function: g_context_specific_group_change_state, data: group);
151 }
152 }
153
154 /* we only block for positive transitions */
155 if (requested_state)
156 {
157 while (group->requested_state != group->effective_state)
158 g_cond_wait (cond: &group->cond, mutex: &group->lock);
159
160 /* there is no way this could go back to FALSE because the object
161 * that we just created in this thread would have to have been
162 * destroyed again (from this thread) before that could happen.
163 */
164 g_assert (group->effective_state);
165 }
166}
167
168gpointer
169g_context_specific_group_get (GContextSpecificGroup *group,
170 GType type,
171 goffset context_offset,
172 GCallback start_func)
173{
174 GContextSpecificSource *css;
175 GMainContext *context;
176
177 context = g_main_context_get_thread_default ();
178 if (!context)
179 context = g_main_context_default ();
180
181 g_mutex_lock (mutex: &group->lock);
182
183 if (!group->table)
184 group->table = g_hash_table_new (NULL, NULL);
185
186 css = g_hash_table_lookup (hash_table: group->table, key: context);
187
188 if (!css)
189 {
190 gpointer instance;
191
192 instance = g_object_new (object_type: type, NULL);
193 css = g_context_specific_source_new (name: g_type_name (type), instance);
194 G_STRUCT_MEMBER (GMainContext *, instance, context_offset) = g_main_context_ref (context);
195 g_source_attach (source: (GSource *) css, context);
196
197 g_hash_table_insert (hash_table: group->table, key: context, value: css);
198 }
199 else
200 g_object_ref (css->instance);
201
202 if (start_func)
203 g_context_specific_group_request_state (group, TRUE, requested_func: start_func);
204
205 g_mutex_unlock (mutex: &group->lock);
206
207 return css->instance;
208}
209
210void
211g_context_specific_group_remove (GContextSpecificGroup *group,
212 GMainContext *context,
213 gpointer instance,
214 GCallback stop_func)
215{
216 GContextSpecificSource *css;
217
218 if (!context)
219 {
220 g_critical ("Removing %s with NULL context. This object was probably directly constructed from a "
221 "dynamic language. This is not a valid use of the API.", G_OBJECT_TYPE_NAME (instance));
222 return;
223 }
224
225 g_mutex_lock (mutex: &group->lock);
226 css = g_hash_table_lookup (hash_table: group->table, key: context);
227 g_hash_table_remove (hash_table: group->table, key: context);
228 g_assert (css);
229
230 /* stop only if we were the last one */
231 if (stop_func && g_hash_table_size (hash_table: group->table) == 0)
232 g_context_specific_group_request_state (group, FALSE, requested_func: stop_func);
233
234 g_mutex_unlock (mutex: &group->lock);
235
236 g_assert (css->instance == instance);
237
238 g_source_destroy (source: (GSource *) css);
239 g_source_unref (source: (GSource *) css);
240 g_main_context_unref (context);
241}
242
243void
244g_context_specific_group_emit (GContextSpecificGroup *group,
245 guint signal_id)
246{
247 g_mutex_lock (mutex: &group->lock);
248
249 if (group->table)
250 {
251 GHashTableIter iter;
252 gpointer value;
253 gpointer ptr;
254
255 ptr = GUINT_TO_POINTER (signal_id);
256
257 g_hash_table_iter_init (iter: &iter, hash_table: group->table);
258 while (g_hash_table_iter_next (iter: &iter, NULL, value: &value))
259 {
260 GContextSpecificSource *css = value;
261
262 g_mutex_lock (mutex: &css->lock);
263
264 g_queue_remove (queue: &css->pending, data: ptr);
265 g_queue_push_tail (queue: &css->pending, data: ptr);
266
267 g_source_set_ready_time (source: (GSource *) css, ready_time: 0);
268
269 g_mutex_unlock (mutex: &css->lock);
270 }
271 }
272
273 g_mutex_unlock (mutex: &group->lock);
274}
275

source code of gtk/subprojects/glib/gio/gcontextspecificgroup.c