Coverage Report

Created: 2025-07-01 07:09

/src/glib/gio/gcontextspecificgroup.c
Line
Count
Source (jump to first uncovered line)
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
37
g_context_specific_source_dispatch (GSource     *source,
38
                                    GSourceFunc  callback,
39
                                    gpointer     user_data)
40
0
{
41
0
  GContextSpecificSource *css = (GContextSpecificSource *) source;
42
0
  guint signal_id;
43
44
0
  g_mutex_lock (&css->lock);
45
46
0
  g_assert (!g_queue_is_empty (&css->pending));
47
0
  signal_id = GPOINTER_TO_UINT (g_queue_pop_head (&css->pending));
48
49
0
  if (g_queue_is_empty (&css->pending))
50
0
    g_source_set_ready_time (source, -1);
51
52
0
  g_mutex_unlock (&css->lock);
53
54
0
  g_signal_emit (css->instance, signal_id, 0);
55
56
0
  return TRUE;
57
0
}
58
59
static void
60
g_context_specific_source_finalize (GSource *source)
61
0
{
62
0
  GContextSpecificSource *css = (GContextSpecificSource *) source;
63
64
0
  g_mutex_clear (&css->lock);
65
0
  g_queue_clear (&css->pending);
66
0
}
67
68
static GContextSpecificSource *
69
g_context_specific_source_new (const gchar *name,
70
                               gpointer     instance)
71
0
{
72
0
  static GSourceFuncs source_funcs = {
73
0
    NULL,
74
0
    NULL,
75
0
    g_context_specific_source_dispatch,
76
0
    g_context_specific_source_finalize,
77
0
    NULL, NULL
78
0
  };
79
0
  GContextSpecificSource *css;
80
0
  GSource *source;
81
82
0
  source = g_source_new (&source_funcs, sizeof (GContextSpecificSource));
83
0
  css = (GContextSpecificSource *) source;
84
85
0
  g_source_set_name (source, name);
86
87
0
  g_mutex_init (&css->lock);
88
0
  g_queue_init (&css->pending);
89
0
  css->instance = instance;
90
91
0
  return css;
92
0
}
93
94
static gboolean
95
g_context_specific_group_change_state (gpointer user_data)
96
0
{
97
0
  GContextSpecificGroup *group = user_data;
98
99
0
  g_mutex_lock (&group->lock);
100
101
0
  if (group->requested_state != group->effective_state)
102
0
    {
103
0
      (* group->requested_func) ();
104
105
0
      group->effective_state = group->requested_state;
106
0
      group->requested_func = NULL;
107
108
0
      g_cond_broadcast (&group->cond);
109
0
    }
110
111
0
  g_mutex_unlock (&group->lock);
112
113
0
  return FALSE;
114
0
}
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
129
g_context_specific_group_request_state (GContextSpecificGroup *group,
130
                                        gboolean               requested_state,
131
                                        GCallback              requested_func)
132
0
{
133
0
  if (requested_state != group->requested_state)
134
0
    {
135
0
      if (group->effective_state != group->requested_state)
136
0
        {
137
          /* abort the currently pending state transition */
138
0
          g_assert (group->effective_state == requested_state);
139
140
0
          group->requested_state = requested_state;
141
0
          group->requested_func = NULL;
142
0
        }
143
0
      else
144
0
        {
145
          /* start a new state transition */
146
0
          group->requested_state = requested_state;
147
0
          group->requested_func = requested_func;
148
149
0
          g_main_context_invoke (GLIB_PRIVATE_CALL(g_get_worker_context) (),
150
0
                                 g_context_specific_group_change_state, group);
151
0
        }
152
0
    }
153
154
  /* we only block for positive transitions */
155
0
  if (requested_state)
156
0
    {
157
0
      while (group->requested_state != group->effective_state)
158
0
        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
0
      g_assert (group->effective_state);
165
0
    }
166
0
}
167
168
gpointer
169
g_context_specific_group_get (GContextSpecificGroup *group,
170
                              GType                  type,
171
                              goffset                context_offset,
172
                              GCallback              start_func)
173
0
{
174
0
  GContextSpecificSource *css;
175
0
  GMainContext *context;
176
177
0
  context = g_main_context_get_thread_default ();
178
0
  if (!context)
179
0
    context = g_main_context_default ();
180
181
0
  g_mutex_lock (&group->lock);
182
183
0
  if (!group->table)
184
0
    group->table = g_hash_table_new (NULL, NULL);
185
186
0
  css = g_hash_table_lookup (group->table, context);
187
188
0
  if (!css)
189
0
    {
190
0
      gpointer instance;
191
192
0
      instance = g_object_new (type, NULL);
193
0
      css = g_context_specific_source_new (g_type_name (type), instance);
194
0
      G_STRUCT_MEMBER (GMainContext *, instance, context_offset) = g_main_context_ref (context);
195
0
      g_source_attach ((GSource *) css, context);
196
197
0
      g_hash_table_insert (group->table, context, css);
198
0
    }
199
0
  else
200
0
    g_object_ref (css->instance);
201
202
0
  if (start_func)
203
0
    g_context_specific_group_request_state (group, TRUE, start_func);
204
205
0
  g_mutex_unlock (&group->lock);
206
207
0
  return css->instance;
208
0
}
209
210
void
211
g_context_specific_group_remove (GContextSpecificGroup *group,
212
                                 GMainContext          *context,
213
                                 gpointer               instance,
214
                                 GCallback              stop_func)
215
0
{
216
0
  GContextSpecificSource *css;
217
218
0
  if (!context)
219
0
    {
220
0
      g_critical ("Removing %s with NULL context.  This object was probably directly constructed from a "
221
0
                  "dynamic language.  This is not a valid use of the API.", G_OBJECT_TYPE_NAME (instance));
222
0
      return;
223
0
    }
224
225
0
  g_mutex_lock (&group->lock);
226
0
  css = g_hash_table_lookup (group->table, context);
227
0
  g_hash_table_remove (group->table, context);
228
0
  g_assert (css);
229
230
  /* stop only if we were the last one */
231
0
  if (stop_func && g_hash_table_size (group->table) == 0)
232
0
    g_context_specific_group_request_state (group, FALSE, stop_func);
233
234
0
  g_mutex_unlock (&group->lock);
235
236
0
  g_assert (css->instance == instance);
237
238
0
  g_source_destroy ((GSource *) css);
239
0
  g_source_unref ((GSource *) css);
240
0
  g_main_context_unref (context);
241
0
}
242
243
void
244
g_context_specific_group_emit (GContextSpecificGroup *group,
245
                               guint                  signal_id)
246
0
{
247
0
  g_mutex_lock (&group->lock);
248
249
0
  if (group->table)
250
0
    {
251
0
      GHashTableIter iter;
252
0
      gpointer value;
253
0
      gpointer ptr;
254
255
0
      ptr = GUINT_TO_POINTER (signal_id);
256
257
0
      g_hash_table_iter_init (&iter, group->table);
258
0
      while (g_hash_table_iter_next (&iter, NULL, &value))
259
0
        {
260
0
          GContextSpecificSource *css = value;
261
262
0
          g_mutex_lock (&css->lock);
263
264
0
          g_queue_remove (&css->pending, ptr);
265
0
          g_queue_push_tail (&css->pending, ptr);
266
267
0
          g_source_set_ready_time ((GSource *) css, 0);
268
269
0
          g_mutex_unlock (&css->lock);
270
0
        }
271
0
    }
272
273
0
  g_mutex_unlock (&group->lock);
274
0
}