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