/* * Copyright © 2015 Canonical Limited * * SPDX-License-Identifier: LGPL-2.1-or-later * * 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.1 of the License, 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 . * * Author: Ryan Lortie */ #include "config.h" #include "gcontextspecificgroup.h" #include #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, NULL, NULL }; 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); } } while (group->requested_state != group->effective_state) g_cond_wait (&group->cond, &group->lock); } 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); }