Coverage Report

Created: 2025-06-13 06:55

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