(root)/
glib-2.79.0/
gio/
gcontextspecificgroup.c
       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  }