| /* | |
| * 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 <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, | |
| 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); | |
| } |