• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     NULL, NULL
78   };
79   GContextSpecificSource *css;
80   GSource *source;
81 
82   source = g_source_new (&source_funcs, sizeof (GContextSpecificSource));
83   css = (GContextSpecificSource *) source;
84 
85   g_source_set_name (source, name);
86 
87   g_mutex_init (&css->lock);
88   g_queue_init (&css->pending);
89   css->instance = instance;
90 
91   return css;
92 }
93 
94 static gboolean
g_context_specific_group_change_state(gpointer user_data)95 g_context_specific_group_change_state (gpointer user_data)
96 {
97   GContextSpecificGroup *group = user_data;
98 
99   g_mutex_lock (&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 (&group->cond);
109     }
110 
111   g_mutex_unlock (&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  */
128 static void
g_context_specific_group_request_state(GContextSpecificGroup * group,gboolean requested_state,GCallback requested_func)129 g_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                                  g_context_specific_group_change_state, 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 (&group->cond, &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 
168 gpointer
g_context_specific_group_get(GContextSpecificGroup * group,GType type,goffset context_offset,GCallback start_func)169 g_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 (&group->lock);
182 
183   if (!group->table)
184     group->table = g_hash_table_new (NULL, NULL);
185 
186   css = g_hash_table_lookup (group->table, context);
187 
188   if (!css)
189     {
190       gpointer instance;
191 
192       instance = g_object_new (type, NULL);
193       css = g_context_specific_source_new (g_type_name (type), instance);
194       G_STRUCT_MEMBER (GMainContext *, instance, context_offset) = g_main_context_ref (context);
195       g_source_attach ((GSource *) css, context);
196 
197       g_hash_table_insert (group->table, context, css);
198     }
199   else
200     g_object_ref (css->instance);
201 
202   if (start_func)
203     g_context_specific_group_request_state (group, TRUE, start_func);
204 
205   g_mutex_unlock (&group->lock);
206 
207   return css->instance;
208 }
209 
210 void
g_context_specific_group_remove(GContextSpecificGroup * group,GMainContext * context,gpointer instance,GCallback stop_func)211 g_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 (&group->lock);
226   css = g_hash_table_lookup (group->table, context);
227   g_hash_table_remove (group->table, context);
228   g_assert (css);
229 
230   /* stop only if we were the last one */
231   if (stop_func && g_hash_table_size (group->table) == 0)
232     g_context_specific_group_request_state (group, FALSE, stop_func);
233 
234   g_mutex_unlock (&group->lock);
235 
236   g_assert (css->instance == instance);
237 
238   g_source_destroy ((GSource *) css);
239   g_source_unref ((GSource *) css);
240   g_main_context_unref (context);
241 }
242 
243 void
g_context_specific_group_emit(GContextSpecificGroup * group,guint signal_id)244 g_context_specific_group_emit (GContextSpecificGroup *group,
245                                guint                  signal_id)
246 {
247   g_mutex_lock (&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, group->table);
258       while (g_hash_table_iter_next (&iter, NULL, &value))
259         {
260           GContextSpecificSource *css = value;
261 
262           g_mutex_lock (&css->lock);
263 
264           g_queue_remove (&css->pending, ptr);
265           g_queue_push_tail (&css->pending, ptr);
266 
267           g_source_set_ready_time ((GSource *) css, 0);
268 
269           g_mutex_unlock (&css->lock);
270         }
271     }
272 
273   g_mutex_unlock (&group->lock);
274 }
275