Blame gio/gcontextspecificgroup.c

Packit ae235b
/*
Packit ae235b
 * Copyright © 2015 Canonical Limited
Packit ae235b
 *
Packit ae235b
 * This library is free software; you can redistribute it and/or
Packit ae235b
 * modify it under the terms of the GNU Lesser General Public
Packit ae235b
 * License as published by the Free Software Foundation; either
Packit ae235b
 * version 2.1 of the License, or (at your option) any later version.
Packit ae235b
 *
Packit ae235b
 * This library is distributed in the hope that it will be useful,
Packit ae235b
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
 * Lesser General Public License for more details.
Packit ae235b
 *
Packit ae235b
 * You should have received a copy of the GNU Lesser General Public
Packit ae235b
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
 *
Packit ae235b
 * Author: Ryan Lortie <desrt@desrt.ca>
Packit ae235b
 */
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
Packit ae235b
#include "gcontextspecificgroup.h"
Packit ae235b
Packit ae235b
#include <glib-object.h>
Packit ae235b
#include "glib-private.h"
Packit ae235b
Packit ae235b
typedef struct
Packit ae235b
{
Packit ae235b
  GSource   source;
Packit ae235b
Packit ae235b
  GMutex    lock;
Packit ae235b
  gpointer  instance;
Packit ae235b
  GQueue    pending;
Packit ae235b
} GContextSpecificSource;
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
g_context_specific_source_dispatch (GSource     *source,
Packit ae235b
                                    GSourceFunc  callback,
Packit ae235b
                                    gpointer     user_data)
Packit ae235b
{
Packit ae235b
  GContextSpecificSource *css = (GContextSpecificSource *) source;
Packit ae235b
  guint signal_id;
Packit ae235b
Packit ae235b
  g_mutex_lock (&css->lock);
Packit ae235b
Packit ae235b
  g_assert (!g_queue_is_empty (&css->pending));
Packit ae235b
  signal_id = GPOINTER_TO_UINT (g_queue_pop_head (&css->pending));
Packit ae235b
Packit ae235b
  if (g_queue_is_empty (&css->pending))
Packit ae235b
    g_source_set_ready_time (source, -1);
Packit ae235b
Packit ae235b
  g_mutex_unlock (&css->lock);
Packit ae235b
Packit ae235b
  g_signal_emit (css->instance, signal_id, 0);
Packit ae235b
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_context_specific_source_finalize (GSource *source)
Packit ae235b
{
Packit ae235b
  GContextSpecificSource *css = (GContextSpecificSource *) source;
Packit ae235b
Packit ae235b
  g_mutex_clear (&css->lock);
Packit ae235b
  g_queue_clear (&css->pending);
Packit ae235b
}
Packit ae235b
Packit ae235b
static GContextSpecificSource *
Packit ae235b
g_context_specific_source_new (const gchar *name,
Packit ae235b
                               gpointer     instance)
Packit ae235b
{
Packit ae235b
  static GSourceFuncs source_funcs = {
Packit ae235b
    NULL,
Packit ae235b
    NULL,
Packit ae235b
    g_context_specific_source_dispatch,
Packit ae235b
    g_context_specific_source_finalize
Packit ae235b
  };
Packit ae235b
  GContextSpecificSource *css;
Packit ae235b
  GSource *source;
Packit ae235b
Packit ae235b
  source = g_source_new (&source_funcs, sizeof (GContextSpecificSource));
Packit ae235b
  css = (GContextSpecificSource *) source;
Packit ae235b
Packit ae235b
  g_source_set_name (source, name);
Packit ae235b
Packit ae235b
  g_mutex_init (&css->lock);
Packit ae235b
  g_queue_init (&css->pending);
Packit ae235b
  css->instance = instance;
Packit ae235b
Packit ae235b
  return css;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
g_context_specific_group_change_state (gpointer user_data)
Packit ae235b
{
Packit ae235b
  GContextSpecificGroup *group = user_data;
Packit ae235b
Packit ae235b
  g_mutex_lock (&group->lock);
Packit ae235b
Packit ae235b
  if (group->requested_state != group->effective_state)
Packit ae235b
    {
Packit ae235b
      (* group->requested_func) ();
Packit ae235b
Packit ae235b
      group->effective_state = group->requested_state;
Packit ae235b
      group->requested_func = NULL;
Packit ae235b
Packit ae235b
      g_cond_broadcast (&group->cond);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_mutex_unlock (&group->lock);
Packit ae235b
Packit ae235b
  return FALSE;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* this is not the most elegant way to deal with this, but it's probably
Packit ae235b
 * the best.  there are only two other things we could do, really:
Packit ae235b
 *
Packit ae235b
 *  - run the start function (but not the stop function) from the user's
Packit ae235b
 *    thread under some sort of lock.  we don't run the stop function
Packit ae235b
 *    from the user's thread to avoid the destroy-while-emitting problem
Packit ae235b
 *
Packit ae235b
 *  - have some check-and-compare functionality similar to what
Packit ae235b
 *    gsettings does where we send an artificial event in case we notice
Packit ae235b
 *    a change during the potential race period (using stat, for
Packit ae235b
 *    example)
Packit ae235b
 */
Packit ae235b
static void
Packit ae235b
g_context_specific_group_request_state (GContextSpecificGroup *group,
Packit ae235b
                                        gboolean               requested_state,
Packit ae235b
                                        GCallback              requested_func)
Packit ae235b
{
Packit ae235b
  if (requested_state != group->requested_state)
Packit ae235b
    {
Packit ae235b
      if (group->effective_state != group->requested_state)
Packit ae235b
        {
Packit ae235b
          /* abort the currently pending state transition */
Packit ae235b
          g_assert (group->effective_state == requested_state);
Packit ae235b
Packit ae235b
          group->requested_state = requested_state;
Packit ae235b
          group->requested_func = NULL;
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        {
Packit ae235b
          /* start a new state transition */
Packit ae235b
          group->requested_state = requested_state;
Packit ae235b
          group->requested_func = requested_func;
Packit ae235b
Packit ae235b
          g_main_context_invoke (GLIB_PRIVATE_CALL(g_get_worker_context) (),
Packit ae235b
                                 g_context_specific_group_change_state, group);
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  /* we only block for positive transitions */
Packit ae235b
  if (requested_state)
Packit ae235b
    {
Packit ae235b
      while (group->requested_state != group->effective_state)
Packit ae235b
        g_cond_wait (&group->cond, &group->lock);
Packit ae235b
Packit ae235b
      /* there is no way this could go back to FALSE because the object
Packit ae235b
       * that we just created in this thread would have to have been
Packit ae235b
       * destroyed again (from this thread) before that could happen.
Packit ae235b
       */
Packit ae235b
      g_assert (group->effective_state);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
gpointer
Packit ae235b
g_context_specific_group_get (GContextSpecificGroup *group,
Packit ae235b
                              GType                  type,
Packit ae235b
                              goffset                context_offset,
Packit ae235b
                              GCallback              start_func)
Packit ae235b
{
Packit ae235b
  GContextSpecificSource *css;
Packit ae235b
  GMainContext *context;
Packit ae235b
Packit ae235b
  context = g_main_context_get_thread_default ();
Packit ae235b
  if (!context)
Packit ae235b
    context = g_main_context_default ();
Packit ae235b
Packit ae235b
  g_mutex_lock (&group->lock);
Packit ae235b
Packit ae235b
  if (!group->table)
Packit ae235b
    group->table = g_hash_table_new (NULL, NULL);
Packit ae235b
Packit ae235b
  css = g_hash_table_lookup (group->table, context);
Packit ae235b
Packit ae235b
  if (!css)
Packit ae235b
    {
Packit ae235b
      gpointer instance;
Packit ae235b
Packit ae235b
      instance = g_object_new (type, NULL);
Packit ae235b
      css = g_context_specific_source_new (g_type_name (type), instance);
Packit ae235b
      G_STRUCT_MEMBER (GMainContext *, instance, context_offset) = g_main_context_ref (context);
Packit ae235b
      g_source_attach ((GSource *) css, context);
Packit ae235b
Packit ae235b
      g_hash_table_insert (group->table, context, css);
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    g_object_ref (css->instance);
Packit ae235b
Packit ae235b
  if (start_func)
Packit ae235b
    g_context_specific_group_request_state (group, TRUE, start_func);
Packit ae235b
Packit ae235b
  g_mutex_unlock (&group->lock);
Packit ae235b
Packit ae235b
  return css->instance;
Packit ae235b
}
Packit ae235b
Packit ae235b
void
Packit ae235b
g_context_specific_group_remove (GContextSpecificGroup *group,
Packit ae235b
                                 GMainContext          *context,
Packit ae235b
                                 gpointer               instance,
Packit ae235b
                                 GCallback              stop_func)
Packit ae235b
{
Packit ae235b
  GContextSpecificSource *css;
Packit ae235b
Packit ae235b
  if (!context)
Packit ae235b
    {
Packit ae235b
      g_critical ("Removing %s with NULL context.  This object was probably directly constructed from a "
Packit ae235b
                  "dynamic language.  This is not a valid use of the API.", G_OBJECT_TYPE_NAME (instance));
Packit ae235b
      return;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_mutex_lock (&group->lock);
Packit ae235b
  css = g_hash_table_lookup (group->table, context);
Packit ae235b
  g_hash_table_remove (group->table, context);
Packit ae235b
  g_assert (css);
Packit ae235b
Packit ae235b
  /* stop only if we were the last one */
Packit ae235b
  if (stop_func && g_hash_table_size (group->table) == 0)
Packit ae235b
    g_context_specific_group_request_state (group, FALSE, stop_func);
Packit ae235b
Packit ae235b
  g_mutex_unlock (&group->lock);
Packit ae235b
Packit ae235b
  g_assert (css->instance == instance);
Packit ae235b
Packit ae235b
  g_source_destroy ((GSource *) css);
Packit ae235b
  g_source_unref ((GSource *) css);
Packit ae235b
  g_main_context_unref (context);
Packit ae235b
}
Packit ae235b
Packit ae235b
void
Packit ae235b
g_context_specific_group_emit (GContextSpecificGroup *group,
Packit ae235b
                               guint                  signal_id)
Packit ae235b
{
Packit ae235b
  g_mutex_lock (&group->lock);
Packit ae235b
Packit ae235b
  if (group->table)
Packit ae235b
    {
Packit ae235b
      GHashTableIter iter;
Packit ae235b
      gpointer value;
Packit ae235b
      gpointer ptr;
Packit ae235b
Packit ae235b
      ptr = GUINT_TO_POINTER (signal_id);
Packit ae235b
Packit ae235b
      g_hash_table_iter_init (&iter, group->table);
Packit ae235b
      while (g_hash_table_iter_next (&iter, NULL, &value))
Packit ae235b
        {
Packit ae235b
          GContextSpecificSource *css = value;
Packit ae235b
Packit ae235b
          g_mutex_lock (&css->lock);
Packit ae235b
Packit ae235b
          g_queue_remove (&css->pending, ptr);
Packit ae235b
          g_queue_push_tail (&css->pending, ptr);
Packit ae235b
Packit ae235b
          g_source_set_ready_time ((GSource *) css, 0);
Packit ae235b
Packit ae235b
          g_mutex_unlock (&css->lock);
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_mutex_unlock (&group->lock);
Packit ae235b
}