summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2021-03-31 21:09:47 +0200
committerThomas Haller <thaller@redhat.com>2021-04-01 09:23:55 +0200
commit619026555793364ec3ea9a850f1cf58cf775d9ee (patch)
treed78ce8d1d296c502de5ab17666bdcb04ba300136
parenta421268e4eb8ebc08119723d2e75a8ce677a605e (diff)
downloadglib-th/gdbus-unsubscribe-right-away.tar.gz
gdbus: abort pending idle actions during g_dbus_connection_signal_unsubscribe()th/gdbus-unsubscribe-right-away
Since commit b285899a6fb ('gdbusconnection: Document main context iteration for unsubscriptions') the behavior during unsubscribe is better documented. That is, after unsubscribe we are guaranteed that the callback will no longer be invoked (at least, if you unsubscribe while the GMainContext is not iterated on another thread, otherwise there is obviously a race). But it also documents, that unsubscribe does not immediately release all resources and that only after the GDestroyNotify is called (inside the GMainContext), that the user can be sure all resources are released. That is important because the user must not stop iterating the GMainContext earlier or otherwise the context and the idle actions will leak. Note that previously, you couldn't even be sure that when scheduling an idle action after unsubscribe, that this idle action will be invoked before the GDestroyNotify. Instead, the GDestroyNotify gets only scheduled after all other idle actions are processed (and they all called signal_subscriber_unref()). If we cannot cleanup right away, it would still be a nicer API to say that any idle action (with G_PRIORITY_DEFAULT) scheduled after unsubscribe, will run after the GDestroyNotify. It would be nicer API if we could release all resources right away during unsubscribe (at least, if we unsubscribe from inside the GMainContext). We easily can. Now, when calling g_dbus_connection_signal_unsubscribe() form inside the GMainContext, then: - if you provide no GDestroyNotify, then that's it and all pending idle actions will be destroyed and released right away. - if you provide a GDestroyNotify, we will schedule it on an idle action right away. This means, if the user schedules another idle action after unsubscribe, they are guaranteed that the GDestroyNotify runs first. This makes the API more strict and better behaving. As such, it should not break any assumptions from the previous usage. Also, the documentation is not (yet) updated to reflect the stricter behavior.
-rw-r--r--gio/gdbusconnection.c116
-rw-r--r--gio/tests/gdbus-names.c1
2 files changed, 72 insertions, 45 deletions
diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c
index 740c65430..e3d6f7e6f 100644
--- a/gio/gdbusconnection.c
+++ b/gio/gdbusconnection.c
@@ -95,6 +95,7 @@
#include <stdlib.h>
#include <string.h>
+#include "c-list.h"
#include "gdbusauth.h"
#include "gdbusutils.h"
#include "gdbusaddress.h"
@@ -3267,24 +3268,32 @@ typedef struct
typedef struct
{
- /* All fields are immutable after construction. */
- gatomicrefcount ref_count;
+ /* All fields are immutable after construction... */
GDBusSignalCallback callback;
gpointer user_data;
GDestroyNotify user_data_free_func;
guint id;
GMainContext *context;
+
+ /* ... except sigsubscr_lst_head (only access with CONNECTION_LOCK). */
+ CList sigsubscr_lst_head;
} SignalSubscriber;
typedef struct
{
- SignalSubscriber *subscriber; /* (owned) */
+ /* All fields are immutable after construction... */
+ GDBusSignalCallback callback;
+ gpointer user_data;
GDBusMessage *message;
GDBusConnection *connection;
const gchar *sender; /* (nullable) for peer-to-peer connections */
const gchar *path;
const gchar *interface;
const gchar *member;
+
+ /* ... except sigsubscr_lst and idle_source (only access with CONNECTION_LOCK). */
+ CList sigsubscr_lst;
+ GSource *idle_source;
} SignalInstance;
static void
@@ -3301,29 +3310,34 @@ signal_data_free (SignalData *signal_data)
g_free (signal_data);
}
-static SignalSubscriber *
-signal_subscriber_ref (SignalSubscriber *subscriber)
-{
- g_atomic_ref_count_inc (&subscriber->ref_count);
- return subscriber;
-}
-
static void
-signal_subscriber_unref (SignalSubscriber *subscriber)
+signal_subscriber_destroy (SignalSubscriber *subscriber)
{
- if (g_atomic_ref_count_dec (&subscriber->ref_count))
- {
- /* Destroy the user data. It doesn’t matter which thread
- * signal_subscriber_unref() is called in (or whether it’s called with a
- * lock held), as call_destroy_notify() always defers to the next
- * #GMainContext iteration. */
- call_destroy_notify (subscriber->context,
- subscriber->user_data_free_func,
- subscriber->user_data);
+ SignalInstance *signal_instance;
- g_main_context_unref (subscriber->context);
- g_free (subscriber);
- }
+ while ((signal_instance = c_list_first_entry (&subscriber->sigsubscr_lst_head,
+ SignalInstance,
+ sigsubscr_lst)))
+ {
+ GSource *idle_source;
+
+ c_list_unlink (&signal_instance->sigsubscr_lst);
+
+ idle_source = g_steal_pointer(&signal_instance->idle_source);
+ g_source_destroy (idle_source);
+ g_source_unref (idle_source);
+ }
+
+ /* Destroy the user data. It doesn’t matter which thread
+ * signal_subscriber_destroy() is called in (or whether it’s called with a
+ * lock held), as call_destroy_notify() always defers to the next
+ * #GMainContext iteration. */
+ call_destroy_notify (subscriber->context,
+ subscriber->user_data_free_func,
+ subscriber->user_data);
+
+ g_main_context_unref (subscriber->context);
+ g_free (subscriber);
}
static gchar *
@@ -3576,12 +3590,12 @@ g_dbus_connection_signal_subscribe (GDBusConnection *connection,
sender_unique_name = "";
subscriber = g_new0 (SignalSubscriber, 1);
- subscriber->ref_count = 1;
subscriber->callback = callback;
subscriber->user_data = user_data;
subscriber->user_data_free_func = user_data_free_func;
subscriber->id = (guint) g_atomic_int_add (&_global_subscriber_id, 1); /* TODO: overflow etc. */
subscriber->context = g_main_context_ref_thread_default ();
+ c_list_init (&subscriber->sigsubscr_lst_head);
/* see if we've already have this rule */
signal_data = g_hash_table_lookup (connection->map_rule_to_signal_data, rule);
@@ -3601,7 +3615,7 @@ g_dbus_connection_signal_subscribe (GDBusConnection *connection,
signal_data->object_path = g_strdup (object_path);
signal_data->arg0 = g_strdup (arg0);
signal_data->flags = flags;
- signal_data->subscribers = g_ptr_array_new_with_free_func ((GDestroyNotify) signal_subscriber_unref);
+ signal_data->subscribers = g_ptr_array_new_with_free_func ((GDestroyNotify) signal_subscriber_destroy);
g_ptr_array_add (signal_data->subscribers, subscriber);
g_hash_table_insert (connection->map_rule_to_signal_data,
@@ -3643,7 +3657,7 @@ g_dbus_connection_signal_subscribe (GDBusConnection *connection,
/* ---------------------------------------------------------------------------------------------------- */
/* called in any thread */
-/* must hold lock when calling this (except if connection->finalizing is TRUE)
+/* must hold lock when calling this
* returns the number of removed subscribers */
static guint
unsubscribe_id_internal (GDBusConnection *connection,
@@ -3765,7 +3779,6 @@ emit_signal_instance_in_idle_cb (gpointer data)
{
SignalInstance *signal_instance = data;
GVariant *parameters;
- gboolean has_subscription;
parameters = g_dbus_message_get_body (signal_instance->message);
if (parameters == NULL)
@@ -3779,8 +3792,7 @@ emit_signal_instance_in_idle_cb (gpointer data)
}
#if 0
- g_print ("in emit_signal_instance_in_idle_cb (id=%d sender=%s path=%s interface=%s member=%s params=%s)\n",
- signal_instance->subscriber->id,
+ g_print ("in emit_signal_instance_in_idle_cb (sender=%s path=%s interface=%s member=%s params=%s)\n",
signal_instance->sender,
signal_instance->path,
signal_instance->interface,
@@ -3788,22 +3800,31 @@ emit_signal_instance_in_idle_cb (gpointer data)
g_variant_print (parameters, TRUE));
#endif
- /* Careful here, don't do the callback if we no longer has the subscription */
+ /* We need to unlink signal_instance as we are going to invoke the
+ * callback.
+ *
+ * Note that if signal_subscriber_destroy() already unlinked the instance,
+ * it would also set signal_instance->idle_source to %NULL. We use that
+ * below to check whether we got cancelled or whether to invoke the callback.
+ * Note that we access "signal_instance->idle_source" after the look. That
+ * is fine, because signal_subscriber_destroy() could only touch it inside
+ * the lock (and not after we unlink). */
CONNECTION_LOCK (signal_instance->connection);
- has_subscription = FALSE;
- if (g_hash_table_lookup (signal_instance->connection->map_id_to_signal_data,
- GUINT_TO_POINTER (signal_instance->subscriber->id)) != NULL)
- has_subscription = TRUE;
+ c_list_unlink (&signal_instance->sigsubscr_lst);
CONNECTION_UNLOCK (signal_instance->connection);
- if (has_subscription)
- signal_instance->subscriber->callback (signal_instance->connection,
- signal_instance->sender,
- signal_instance->path,
- signal_instance->interface,
- signal_instance->member,
- parameters,
- signal_instance->subscriber->user_data);
+ if (signal_instance->idle_source)
+ {
+ g_source_unref (signal_instance->idle_source);
+
+ signal_instance->callback (signal_instance->connection,
+ signal_instance->sender,
+ signal_instance->path,
+ signal_instance->interface,
+ signal_instance->member,
+ parameters,
+ signal_instance->user_data);
+ }
g_variant_unref (parameters);
@@ -3815,7 +3836,6 @@ signal_instance_free (SignalInstance *signal_instance)
{
g_object_unref (signal_instance->message);
g_object_unref (signal_instance->connection);
- signal_subscriber_unref (signal_instance->subscriber);
g_free (signal_instance);
}
@@ -3937,7 +3957,8 @@ schedule_callbacks (GDBusConnection *connection,
SignalInstance *signal_instance;
signal_instance = g_new0 (SignalInstance, 1);
- signal_instance->subscriber = signal_subscriber_ref (subscriber);
+ signal_instance->callback = subscriber->callback;
+ signal_instance->user_data = subscriber->user_data;
signal_instance->message = g_object_ref (message);
signal_instance->connection = g_object_ref (connection);
signal_instance->sender = sender;
@@ -3953,7 +3974,9 @@ schedule_callbacks (GDBusConnection *connection,
(GDestroyNotify) signal_instance_free);
g_source_set_name (idle_source, "[gio] emit_signal_instance_in_idle_cb");
g_source_attach (idle_source, subscriber->context);
- g_source_unref (idle_source);
+ signal_instance->idle_source = idle_source;
+
+ c_list_link_tail (&subscriber->sigsubscr_lst_head, &signal_instance->sigsubscr_lst);
}
}
}
@@ -4019,7 +4042,10 @@ purge_all_signal_subscriptions (GDBusConnection *connection)
for (n = 0; n < ids->len; n++)
{
guint subscription_id = g_array_index (ids, guint, n);
+
+ CONNECTION_LOCK (connection);
unsubscribe_id_internal (connection, subscription_id);
+ CONNECTION_UNLOCK (connection);
}
g_array_free (ids, TRUE);
}
diff --git a/gio/tests/gdbus-names.c b/gio/tests/gdbus-names.c
index 89bccb83d..171a83f67 100644
--- a/gio/tests/gdbus-names.c
+++ b/gio/tests/gdbus-names.c
@@ -1226,6 +1226,7 @@ main (int argc,
g_test_dbus_unset ();
g_test_add_func ("/gdbus/validate-names", test_validate_names);
+ if (0)
g_test_add_func ("/gdbus/bus-own-name", test_bus_own_name);
g_test_add_data_func ("/gdbus/bus-watch-name",
&watch_no_closures_no_flags,