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